break client connect into states and apply timeout

Doing a client connect was atomic until now, blocking
all the other service while it waited for proxy and / or
server response.

This patch uses the new timeout system and breaks the
client connect sequence into three states handled by
the normal poll() processing.  It means that there are
now no blocking network delays and it's all handled
by the main state machine.

Signed-off-by: Andy Green <andy@warmcat.com>
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index a35193d..0e64817 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -21,6 +21,19 @@
 
 #include "private-libwebsockets.h"
 
+/*
+ * In-place str to lower case
+ */
+
+static void
+strtolower(char *s)
+{
+	while (*s) {
+		*s = tolower(*s);
+		s++;
+	}
+}
+
 /* file descriptor hash management */
 
 struct libwebsocket *
@@ -285,7 +298,18 @@
 	unsigned int clilen;
 	struct sockaddr_in cli_addr;
 	struct timeval tv;
-
+	static const char magic_websocket_guid[] =
+					 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+	static const char magic_websocket_04_masking_guid[] =
+					 "61AC5F19-FBBA-4540-B96F-6561F1AB40A8";
+	char hash[20];
+	char pkt[1024];
+	char *p = &pkt[0];
+	const char *pc;
+	int okay = 0;
+#ifdef LWS_OPENSSL_SUPPORT
+	char ssl_err_buf[512];
+#endif
 	/*
 	 * you can call us with pollfd = NULL to just allow the once-per-second
 	 * global timeout checks; if less than a second since the last check
@@ -601,6 +625,397 @@
 		}
 		break;
 
+	case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY:
+
+		/* handle proxy hung up on us */
+
+		if (pollfd->revents & (POLLERR | POLLHUP)) {
+
+			fprintf(stderr, "Proxy connection %p (fd=%d) dead\n",
+				(void *)wsi, pollfd->fd);
+
+			libwebsocket_close_and_free_session(this, wsi);
+			return 1;
+		}
+
+		n = recv(wsi->sock, pkt, sizeof pkt, 0);
+		if (n < 0) {
+			libwebsocket_close_and_free_session(this, wsi);
+			fprintf(stderr, "ERROR reading from proxy socket\n");
+			return 1;
+		}
+
+		pkt[13] = '\0';
+		if (strcmp(pkt, "HTTP/1.0 200 ") != 0) {
+			libwebsocket_close_and_free_session(this, wsi);
+			fprintf(stderr, "ERROR from proxy: %s\n", pkt);
+			return 1;
+		}
+
+		/* clear his proxy connection timeout */
+
+		libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+		/* fallthru */
+
+	case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE:
+
+	#ifdef LWS_OPENSSL_SUPPORT
+		if (wsi->use_ssl) {
+
+			wsi->ssl = SSL_new(this->ssl_client_ctx);
+			wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE);
+			SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio);
+
+			if (SSL_connect(wsi->ssl) <= 0) {
+				fprintf(stderr, "SSL connect error %s\n",
+					ERR_error_string(ERR_get_error(), ssl_err_buf));
+				libwebsocket_close_and_free_session(this, wsi);
+				return 1;
+			}
+
+			n = SSL_get_verify_result(wsi->ssl);
+			if (n != X509_V_OK) {
+				if (n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
+							    wsi->use_ssl != 2) {
+
+					fprintf(stderr, "server's cert didn't "
+								   "look good %d\n", n);
+					libwebsocket_close_and_free_session(this, wsi);
+					return 1;
+				}
+			}
+		} else {
+			wsi->ssl = NULL;
+	#endif
+
+
+	#ifdef LWS_OPENSSL_SUPPORT
+		}
+	#endif
+
+		/*
+		 * create the random key
+		 */
+
+		n = read(this->fd_random, hash, 16);
+		if (n != 16) {
+			fprintf(stderr, "Unable to read from random dev %s\n",
+							SYSTEM_RANDOM_FILEPATH);
+			free(wsi->c_path);
+			free(wsi->c_host);
+			free(wsi->c_origin);
+			if (wsi->c_protocol)
+				free(wsi->c_protocol);
+			libwebsocket_close_and_free_session(this, wsi);
+			return 1;
+		}
+
+		lws_b64_encode_string(hash, 16, wsi->key_b64,
+							   sizeof wsi->key_b64);
+
+		/*
+		 * 04 example client handshake
+		 *
+		 * GET /chat HTTP/1.1
+		 * Host: server.example.com
+		 * Upgrade: websocket
+		 * Connection: Upgrade
+		 * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
+		 * Sec-WebSocket-Origin: http://example.com
+		 * Sec-WebSocket-Protocol: chat, superchat
+		 * Sec-WebSocket-Version: 4
+		 */
+
+		 p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", wsi->c_path);
+		 p += sprintf(p, "Host: %s\x0d\x0a", wsi->c_host);
+		 p += sprintf(p, "Upgrade: websocket\x0d\x0a");
+		 p += sprintf(p, "Connection: Upgrade\x0d\x0a"
+					"Sec-WebSocket-Key: ");
+		 strcpy(p, wsi->key_b64);
+		 p += strlen(wsi->key_b64);
+		 p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a",
+								 wsi->c_origin);
+		 if (wsi->c_protocol != NULL)
+			p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a",
+							       wsi->c_protocol);
+		 p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a\x0d\x0a",
+						       wsi->ietf_spec_revision);
+
+		/* done with these now */
+
+		free(wsi->c_path);
+		free(wsi->c_host);
+		free(wsi->c_origin);
+
+		/* prepare the expected server accept response */
+
+		strcpy((char *)buf, wsi->key_b64);
+		strcpy((char *)&buf[strlen((char *)buf)], magic_websocket_guid);
+
+		SHA1(buf, strlen((char *)buf), (unsigned char *)hash);
+
+		lws_b64_encode_string(hash, 20,
+				wsi->initial_handshake_hash_base64,
+				     sizeof wsi->initial_handshake_hash_base64);
+
+		/* send our request to the server */
+
+	#ifdef LWS_OPENSSL_SUPPORT
+		if (wsi->use_ssl)
+			n = SSL_write(wsi->ssl, pkt, p - pkt);
+		else
+	#endif
+			n = send(wsi->sock, pkt, p - pkt, 0);
+
+		if (n < 0) {
+			fprintf(stderr, "ERROR writing to client socket\n");
+			libwebsocket_close_and_free_session(this, wsi);
+			return 1;
+		}
+
+		wsi->parser_state = WSI_TOKEN_NAME_PART;
+		wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY;
+		libwebsocket_set_timeout(wsi,
+				PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, 5);
+
+		break;
+
+	case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY:
+
+		/* handle server hung up on us */
+
+		if (pollfd->revents & (POLLERR | POLLHUP)) {
+
+			fprintf(stderr, "Server connection %p (fd=%d) dead\n",
+				(void *)wsi, pollfd->fd);
+
+			goto bail3;
+		}
+
+
+		/* interpret the server response */
+
+		/*
+		 *  HTTP/1.1 101 Switching Protocols
+		 *  Upgrade: websocket
+		 *  Connection: Upgrade
+		 *  Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
+		 *  Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
+		 *  Sec-WebSocket-Protocol: chat
+		 */
+
+	#ifdef LWS_OPENSSL_SUPPORT
+		if (wsi->use_ssl)
+			len = SSL_read(wsi->ssl, pkt, sizeof pkt);
+		else
+	#endif
+			len = recv(wsi->sock, pkt, sizeof pkt, 0);
+
+		if (len < 0) {
+			fprintf(stderr,
+				  "libwebsocket_client_handshake read error\n");
+			goto bail3;
+		}
+
+		p = pkt;
+		for (n = 0; n < len; n++)
+			libwebsocket_parse(wsi, *p++);
+
+		if (wsi->parser_state != WSI_PARSING_COMPLETE) {
+			fprintf(stderr, "libwebsocket_client_handshake "
+					"server response ailed parsing\n");
+			goto bail3;
+		}
+
+		/*
+		 * well, what the server sent looked reasonable for syntax.
+		 * Now let's confirm it sent all the necessary headers
+		 */
+
+		 if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len ||
+			!wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len ||
+			!wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len ||
+			!wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len ||
+			!wsi->utf8_token[WSI_TOKEN_NONCE].token_len ||
+			(!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len &&
+						     wsi->c_protocol != NULL)) {
+			fprintf(stderr, "libwebsocket_client_handshake "
+							"missing required header(s)\n");
+			pkt[len] = '\0';
+			fprintf(stderr, "%s", pkt);
+			goto bail3;
+		}
+
+		/*
+		 * Everything seems to be there, now take a closer look at what
+		 * is in each header
+		 */
+
+		strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token);
+		if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token,
+						   "101 switching protocols")) {
+			fprintf(stderr, "libwebsocket_client_handshake "
+					"server sent bad HTTP response '%s'\n",
+					 wsi->utf8_token[WSI_TOKEN_HTTP].token);
+			goto bail3;
+		}
+
+		strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
+		if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token,
+								 "websocket")) {
+			fprintf(stderr, "libwebsocket_client_handshake server "
+					"sent bad Upgrade header '%s'\n",
+				      wsi->utf8_token[WSI_TOKEN_UPGRADE].token);
+			goto bail3;
+		}
+
+		strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
+		if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token,
+								   "upgrade")) {
+			fprintf(stderr, "libwebsocket_client_handshake server "
+					"sent bad Connection hdr '%s'\n",
+				   wsi->utf8_token[WSI_TOKEN_CONNECTION].token);
+			goto bail3;
+		}
+
+
+		pc = wsi->c_protocol;
+
+		/*
+		 * confirm the protocol the server wants to talk was in the list
+		 * of protocols we offered
+		 */
+
+		if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) {
+
+			/*
+			 * no protocol name to work from,
+			 * default to first protocol
+			 */
+			wsi->protocol = &this->protocols[0];
+
+			free(wsi->c_protocol);
+
+			goto check_accept;
+		}
+
+		while (*pc && !okay) {
+			if ((!strncmp(pc,
+				wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
+			   wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) &&
+		 (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' ||
+		   pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) {
+				okay = 1;
+				continue;
+			}
+			while (*pc && *pc != ',')
+				pc++;
+			while (*pc && *pc != ' ')
+				pc++;
+		}
+
+		/* done with him now */
+
+		if (wsi->c_protocol)
+			free(wsi->c_protocol);
+
+
+		if (!okay) {
+			fprintf(stderr, "libwebsocket_client_handshake server "
+						"sent bad protocol '%s'\n",
+				     wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
+			goto bail2;
+		}
+
+		/*
+		 * identify the selected protocol struct and set it
+		 */
+		n = 0;
+		wsi->protocol = NULL;
+		while (this->protocols[n].callback) {
+			if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
+					       this->protocols[n].name) == 0)
+				wsi->protocol = &this->protocols[n];
+			n++;
+		}
+
+		if (wsi->protocol == NULL) {
+			fprintf(stderr, "libwebsocket_client_handshake server "
+					"requested protocol '%s', which we "
+					"said we supported but we don't!\n",
+				     wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
+			goto bail2;
+		}
+
+	check_accept:
+		/*
+		 * Confirm his accept token is the one we precomputed
+		 */
+
+		if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
+					  wsi->initial_handshake_hash_base64)) {
+			fprintf(stderr, "libwebsocket_client_handshake server "
+				"sent bad ACCEPT '%s' vs computed '%s'\n",
+				wsi->utf8_token[WSI_TOKEN_ACCEPT].token,
+					    wsi->initial_handshake_hash_base64);
+			goto bail2;
+		}
+
+		/*
+		 * Calculate the masking key to use when sending data to server
+		 */
+
+		strcpy((char *)buf, wsi->key_b64);
+		p = (char *)buf + strlen(wsi->key_b64);
+		strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token);
+		p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len;
+		strcpy(p, magic_websocket_04_masking_guid);
+		SHA1(buf, strlen((char *)buf), wsi->masking_key_04);
+
+		/* allocate the per-connection user memory (if any) */
+
+		if (wsi->protocol->per_session_data_size) {
+			wsi->user_space = malloc(
+					  wsi->protocol->per_session_data_size);
+			if (wsi->user_space  == NULL) {
+				fprintf(stderr, "Out of memory for "
+							   "conn user space\n");
+				goto bail2;
+			}
+		} else
+			wsi->user_space = NULL;
+
+		/* clear his proxy connection timeout */
+
+		libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+
+		/* mark him as being alive */
+
+		wsi->state = WSI_STATE_ESTABLISHED;
+		wsi->mode = LWS_CONNMODE_WS_CLIENT;
+
+		fprintf(stderr, "handshake OK for protocol %s\n",
+							   wsi->protocol->name);
+
+		/* call him back to inform him he is up */
+
+		wsi->protocol->callback(this, wsi,
+				 LWS_CALLBACK_CLIENT_ESTABLISHED,
+				 wsi->user_space,
+				 NULL, 0);
+
+		break;
+
+bail3:
+		if (wsi->c_protocol)
+			free(wsi->c_protocol);
+
+bail2:
+		libwebsocket_close_and_free_session(this, wsi);
+		return 1;
+		
+
 	case LWS_CONNMODE_WS_SERVING:
 	case LWS_CONNMODE_WS_CLIENT:
 
@@ -829,6 +1244,28 @@
 	return 0;
 }
 
+/**
+ * libwebsocket_set_timeout() - marks the wsi as subject to a timeout
+ *
+ * You will not need this unless you are doing something special
+ *
+ * @wsi:	Websocket connection instance
+ * @reason:	timeout reason
+ * @secs:	how many seconds
+ */
+
+void
+libwebsocket_set_timeout(struct libwebsocket *wsi,
+					  enum pending_timeout reason, int secs)
+{
+	struct timeval tv;
+
+	gettimeofday(&tv, NULL);
+
+	wsi->pending_timeout_limit = tv.tv_sec + secs;
+	wsi->pending_timeout = reason;
+}
+
 
 /**
  * libwebsocket_get_socket_fd() - returns the socket file descriptor