/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "private-lib-core.h"

struct lws *
lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback,
				 ssize_t plen)
{
#if defined(LWS_CLIENT_HTTP_PROXYING)
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
	struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
#endif
#endif
	const char *meth;
	struct lws_pollfd pfd;
	const char *cce = "";
	int n, m, rawish = 0;

	meth = lws_wsi_client_stash_item(wsi, CIS_METHOD,
					 _WSI_TOKEN_CLIENT_METHOD);

	if ((meth && (!strcmp(meth, "RAW") ||
		     !strcmp(meth, "MQTT") ||
		     !strcmp(meth, "QUIC") ||
		     !strcmp(meth, "UDP"))) ||
	    !strcmp(wsi->role_ops->name, "quic"))
		rawish = 1;

	if (wsi_piggyback)
		goto send_hs;

#if defined(LWS_CLIENT_HTTP_PROXYING)
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
	/* we are connected to server, or proxy */

	/* http proxy */
	if (wsi->a.vhost->http.http_proxy_port) {
		const char *cpa;

		cpa = lws_wsi_client_stash_item(wsi, CIS_ADDRESS,
						_WSI_TOKEN_CLIENT_PEER_ADDRESS);
		if (!cpa)
			goto failed;

		lwsl_wsi_info(wsi, "going via proxy");

		plen = lws_snprintf((char *)pt->serv_buf, 256,
			"CONNECT %s:%u HTTP/1.1\x0d\x0a"
			"Host: %s:%u\x0d\x0a"
			"User-agent: lws\x0d\x0a", cpa, wsi->ocport,
						   cpa, wsi->ocport);

#if defined(LWS_WITH_HTTP_BASIC_AUTH)
		if (wsi->a.vhost->proxy_basic_auth_token[0])
			plen += lws_snprintf((char *)pt->serv_buf + plen, 256,
					"Proxy-authorization: basic %s\x0d\x0a",
					wsi->a.vhost->proxy_basic_auth_token);
#endif

		plen += lws_snprintf((char *)pt->serv_buf + plen, 5,
					"\x0d\x0a");

		/* lwsl_hexdump_notice(pt->serv_buf, plen); */

		/*
		 * OK from now on we talk via the proxy, so connect to that
		 */
		if (wsi->stash)
			wsi->stash->cis[CIS_ADDRESS] =
				wsi->a.vhost->http.http_proxy_address;
		else
			if (lws_hdr_simple_create(wsi,
					_WSI_TOKEN_CLIENT_PEER_ADDRESS,
					wsi->a.vhost->http.http_proxy_address))
			goto failed;
		wsi->c_port = (uint16_t)wsi->a.vhost->http.http_proxy_port;

		n = (int)send(wsi->desc.sockfd, (char *)pt->serv_buf,
			      (unsigned int)plen,
			 MSG_NOSIGNAL);
		if (n < 0) {
			lwsl_wsi_debug(wsi, "ERROR writing to proxy socket");
			cce = "proxy write failed";
			goto failed;
		}

		lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
				(int)wsi->a.context->timeout_secs);

		wsi->conn_port = wsi->c_port;
		lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY);

		return wsi;
	}
#endif
#endif

	/* coverity */
	if (!wsi->a.protocol)
		return NULL;

#if defined(LWS_WITH_SOCKS5)
	if (lwsi_state(wsi) != 	LRS_ESTABLISHED)
		switch (lws_socks5c_greet(wsi, &cce)) {
		case -1:
			goto failed;
		case 1:
			return wsi;
		default:
			break;
		}
#endif

send_hs:

#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) || defined(LWS_ROLE_H3)
	if (wsi_piggyback &&
	    !lws_dll2_is_detached(&wsi->dll2_cli_txn_queue)) {
		/*
		 * We are pipelining on an already-established connection...
		 * we can skip tls establishment.
		 *
		 * Set these queued guys to a state where they won't actually
		 * send their headers until we decide later.
		 */

		lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS);

		/*
		 * we can't send our headers directly, because they have to
		 * be sent when the parent is writeable.  The parent will check
		 * for anybody on his client transaction queue that is in
		 * LRS_H1C_ISSUE_HANDSHAKE2, and let them write.
		 *
		 * If we are trying to do this too early, before the network
		 * connection has written his own headers, then it will just
		 * wait in the queue until it's possible to send them.
		 */
		lws_callback_on_writable(wsi_piggyback);

		lwsl_wsi_info(wsi, "waiting to send hdrs (par state 0x%x)",
			      lwsi_state(wsi_piggyback));
	} else
#endif
	{
		lwsl_wsi_info(wsi, "%s %s client created own conn "
			  "(raw %d) vh %s st 0x%x",
			  wsi->role_ops->name, wsi->a.protocol->name, rawish,
			  wsi->a.vhost->name, lwsi_state(wsi));

		/* we are making our own connection */

		if (!rawish
#if defined(LWS_WITH_TLS)
		    // && (!(wsi->tls.use_ssl & LCCSCF_USE_SSL) || wsi->tls.ssl)
#endif
		    ) {

#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
			if (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE2)
				lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE);
#endif
		} else {
			/* for a method = "RAW" connection, this makes us
			 * established */

#if defined(LWS_WITH_TLS)// && !defined(LWS_WITH_MBEDTLS)

			/* we have connected if we got here */

			if (lwsi_state(wsi) == LRS_WAITING_CONNECT &&
#if defined(LWS_ROLE_QUIC)
			    strcmp(wsi->role_ops->name, "quic") != 0 &&
#endif
			    (wsi->tls.use_ssl & LCCSCF_USE_SSL)) {
				int result;

				//lwsi_set_state(wsi, LRS_WAITING_SSL);

				/*
				 * We can retry this... just cook the SSL BIO
				 * the first time
				 */

				result = lws_client_create_tls(wsi, &cce, 1);
				switch (result) {
				case CCTLS_RETURN_DONE:
					break;
				case CCTLS_RETURN_RETRY:
					lwsl_wsi_debug(wsi, "create_tls RETRY");
					return wsi;
				default:
					lwsl_wsi_debug(wsi, "create_tls FAIL");
					goto failed;
				}

				/*
				 * We succeeded to negotiate a new client tls
				 * tunnel.  If it's h2 alpn, we have arranged
				 * to send the h2 prefix and set our state to
				 * LRS_H2_WAITING_TO_SEND_HEADERS already.
				 */

				lwsl_wsi_notice(wsi, "tls established st 0x%x, "
					    "client_h2_alpn %d", lwsi_state(wsi),
					    wsi->client_h2_alpn);

#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
				if (lwsi_state(wsi) !=
						LRS_H2_WAITING_TO_SEND_HEADERS)
					lwsi_set_state(wsi,
						LRS_H1C_ISSUE_HANDSHAKE2);
#endif
				lws_set_timeout(wsi,
					PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND,
					(int)wsi->a.context->timeout_secs);
#if 0
				/* ensure pollin enabled */
				if (lws_change_pollfd(wsi, 0, LWS_POLLIN))
					lwsl_wsi_notice(wsi,
							"unable to set POLLIN");
#endif

				goto provoke_service;
			}
#endif

#if defined(LWS_ROLE_QUIC)
			if ((meth && !strcmp(meth, "QUIC")) ||
			    !strcmp(wsi->role_ops->name, "quic")) {
				lwsi_set_state(wsi, LRS_WAITING_SSL);
				wsi->hdr_parsing_completed = 1;
				lws_callback_on_writable(wsi);
				return wsi;
			}
#endif

			/* clear his established timeout */
			lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);

			m = wsi->role_ops->adoption_cb[0];
			if (m) {
				n = user_callback_handle_rxflow(
						wsi->a.protocol->callback, wsi,
						(enum lws_callback_reasons)m,
						wsi->user_space, NULL, 0);
				if (n < 0) {
					lwsl_wsi_info(wsi, "RAW_PROXY_CLI_ADOPT err");
					goto failed;
				}
			}

			/* service.c pollout processing wants this */
			wsi->hdr_parsing_completed = 1;
#if defined(LWS_ROLE_MQTT)
			if (meth && !strcmp(meth, "MQTT")) {
#if defined(LWS_WITH_TLS)
				if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
					lwsi_set_state(wsi, LRS_WAITING_SSL);
					return wsi;
				}
#endif
				lwsl_wsi_info(wsi, "settings LRS_MQTTC_IDLE");
				lwsi_set_state(wsi, LRS_MQTTC_IDLE);

				/*
				 * provoke service to issue the CONNECT
				 * directly.
				 */
				lws_set_timeout(wsi,
					PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE,
						(int)wsi->a.context->timeout_secs);

				assert(lws_socket_is_valid(wsi->desc.sockfd));

				pfd.fd = wsi->desc.sockfd;
				pfd.events = LWS_POLLIN;
				pfd.revents = LWS_POLLOUT;

				lwsl_wsi_info(wsi, "going to service fd");
				n = lws_service_fd_tsi(wsi->a.context, &pfd, wsi->tsi);
				if (n < 0) {
					cce = "first service failed";
					goto failed;
				}
				if (n)
					/* returns 1 on fail after close wsi */
					return NULL;
				return wsi;
			}
#endif
			lwsl_wsi_info(wsi, "setting ESTABLISHED");
			lwsi_set_state(wsi, LRS_ESTABLISHED);

			return wsi;
		}

		/*
		 * provoke service to issue the handshake directly.
		 *
		 * we need to do it this way because in the proxy case, this is
		 * the next state and executed only if and when we get a good
		 * proxy response inside the state machine... but notice in
		 * SSL case this may not have sent anything yet with 0 return,
		 * and won't until many retries from main loop.  To stop that
		 * becoming endless, cover with a timeout.
		 */
#if defined(LWS_WITH_TLS) //&& !defined(LWS_WITH_MBEDTLS)
provoke_service:
#endif
		lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE,
				(int)wsi->a.context->timeout_secs);

		assert(lws_socket_is_valid(wsi->desc.sockfd));

		pfd.fd = wsi->desc.sockfd;
		pfd.events = LWS_POLLIN;
		pfd.revents = LWS_POLLIN;

		n = lws_service_fd_tsi(wsi->a.context, &pfd, wsi->tsi);
		if (n < 0) {
			cce = "first service failed";
			goto failed;
		}
		if (n) /* returns 1 on failure after closing wsi */
			return NULL;
	}
	return wsi;

failed:
	lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));

	lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect4");

	return NULL;
}
