introduce-ietf-05-framing-and-commandline-options.patch

This adds 05 support, and -v switches on test-client and test-ping
to allow setting their ietf protocol version to 4 or 5.

It also optimizes the masking to us a function pointer, which
takes some conditionals out of the fast path.

Signed-off-by: Andy Green <andy@warmcat.com>
diff --git a/lib/client-handshake.c b/lib/client-handshake.c
index b805cd1..0624dcb 100644
--- a/lib/client-handshake.c
+++ b/lib/client-handshake.c
@@ -87,6 +87,8 @@
  * @protocol:	Comma-separated list of protocols being asked for from
  *		the server, or just one.  The server will pick the one it
  *		likes best.
+ * @ietf_version_or_minus_one: -1 to ask to connect using the default, latest
+ * 		protocol supported, or the specific protocol ordinal
  *
  *	This function creates a connection to a remote server
  */
@@ -99,7 +101,8 @@
 			      const char *path,
 			      const char *host,
 			      const char *origin,
-			      const char *protocol)
+			      const char *protocol,
+			      int ietf_version_or_minus_one)
 {
 	struct hostent *server_hostent;
 	struct sockaddr_in server_addr;
@@ -137,13 +140,41 @@
 
 	this->wsi[this->fds_count] = wsi;
 
-	wsi->ietf_spec_revision = 4;
+	/* -1 means just use latest supported */
+
+	if (ietf_version_or_minus_one == -1)
+		ietf_version_or_minus_one = 5;
+
+	wsi->ietf_spec_revision = ietf_version_or_minus_one;
 	wsi->name_buffer_pos = 0;
 	wsi->user_space = NULL;
 	wsi->state = WSI_STATE_CLIENT_UNCONNECTED;
 	wsi->pings_vs_pongs = 0;
 	wsi->protocol = NULL;
 
+	/* set up appropriate masking */
+
+	wsi->xor_mask = xor_no_mask;
+
+	switch (wsi->ietf_spec_revision) {
+	case 4:
+		wsi->xor_mask = xor_mask_04;
+		break;
+	case 5:
+		wsi->xor_mask = xor_mask_05;
+		break;
+	default:
+		fprintf(stderr,
+			"Client ietf version %d not supported\n",
+						       wsi->ietf_spec_revision);
+		return NULL;
+	}
+
+	/* force no mask if he asks for that though */
+
+	if (this->options & LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK)
+		wsi->xor_mask = xor_no_mask;
+
 	for (n = 0; n < WSI_TOKEN_COUNT; n++) {
 		wsi->utf8_token[n].token = NULL;
 		wsi->utf8_token[n].token_len = 0;
@@ -309,7 +340,8 @@
 	 p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a", origin);
 	 if (protocol != NULL)
 		p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", protocol);
-	 p += sprintf(p, "Sec-WebSocket-Version: 4\x0d\x0a\x0d\x0a");
+	 p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a\x0d\x0a",
+						       wsi->ietf_spec_revision);
 
 
 	/* prepare the expected server accept response */
diff --git a/lib/handshake.c b/lib/handshake.c
index ab2f09e..2a4ebb6 100644
--- a/lib/handshake.c
+++ b/lib/handshake.c
@@ -222,7 +222,7 @@
  */
 
 static int
-handshake_04(struct libwebsocket *wsi)
+handshake_0405(struct libwebsocket *wsi)
 {
 	static const char *websocket_magic_guid_04 =
 					 "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@@ -537,7 +537,6 @@
 			wsi->ietf_spec_revision =
 				 atoi(wsi->utf8_token[WSI_TOKEN_VERSION].token);
 
-
 		/*
 		 * Perform the handshake according to the protocol version the
 		 * client announced
@@ -545,20 +544,32 @@
 
 		switch (wsi->ietf_spec_revision) {
 		case 0: /* applies to 76 and 00 */
+			wsi->xor_mask = xor_no_mask;
 			if (handshake_00(wsi))
 				goto bail;
 			break;
 		case 4: /* 04 */
+			wsi->xor_mask = xor_mask_04;
 			debug("libwebsocket_parse calling handshake_04\n");
-			if (handshake_04(wsi))
+			if (handshake_0405(wsi))
 				goto bail;
 			break;
+		case 5: /* 05 */
+			wsi->xor_mask = xor_mask_05;
+			debug("libwebsocket_parse calling handshake_04\n");
+			if (handshake_0405(wsi))
+				goto bail;
+			break;
+
 		default:
 			fprintf(stderr, "Unknown client spec version %d\n",
 						       wsi->ietf_spec_revision);
 			goto bail;
 		}
 
+		fprintf(stderr, "accepted v%02d connection\n",
+						       wsi->ietf_spec_revision);
+
 		break;
 
 	case WSI_STATE_ESTABLISHED:
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 6a74a77..d4c8ac4 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -607,6 +607,7 @@
  *			else ignored
  * @gid:	group id to change to after setting listen socket, or -1.
  * @uid:	user id to change to after setting listen socket, or -1.
+ * @options:	0, or LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK
  *
  *	This function creates the listening socket and takes care
  *	of all initialization in one step.
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index e579303..10d8584 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -24,7 +24,10 @@
 
 #define CONTEXT_PORT_NO_LISTEN 0
 
-#define LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK 1
+
+enum libwebsocket_context_options {
+	LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK = 1,
+};
 
 enum libwebsocket_callback_reasons {
 	LWS_CALLBACK_ESTABLISHED,
@@ -258,7 +261,8 @@
 			      const char *path,
 			      const char *host,
 			      const char *origin,
-			      const char *protocol);
+			      const char *protocol,
+			      int ietf_version_or_minus_one);
 
 extern const char *
 libwebsocket_canonical_hostname(struct libwebsocket_context *this);
diff --git a/lib/parsers.c b/lib/parsers.c
index 304c574..7551f14 100644
--- a/lib/parsers.c
+++ b/lib/parsers.c
@@ -220,13 +220,15 @@
 	return 0;
 }
 
-static inline unsigned char
-xor_mask(struct libwebsocket *wsi, unsigned char c)
+unsigned char
+xor_no_mask(struct libwebsocket *wsi, unsigned char c)
 {
-	if (wsi->protocol->owning_server->options &
-					   LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK)
-		return c;
-	
+	return c;
+}
+
+unsigned char
+xor_mask_04(struct libwebsocket *wsi, unsigned char c)
+{
 	c ^= wsi->masking_key_04[wsi->frame_mask_index++];
 	if (wsi->frame_mask_index == 20)
 		wsi->frame_mask_index = 0;
@@ -234,6 +236,13 @@
 	return c;
 }
 
+unsigned char
+xor_mask_05(struct libwebsocket *wsi, unsigned char c)
+{
+	return c ^ wsi->frame_masking_nonce_04[(wsi->frame_mask_index++) & 3];
+}
+
+
 
 static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c)
 {
@@ -255,9 +264,14 @@
 			}
 			break;
 		case 4:
+		case 5:
 			wsi->frame_masking_nonce_04[0] = c;
 			wsi->lws_rx_parse_state = LWS_RXPS_04_MASK_NONCE_1;
 			break;
+		default:
+			fprintf(stderr, "libwebsocket_rx_sm doesn't know "
+			    "about spec version %d\n", wsi->ietf_spec_revision);
+			break;
 		}
 		break;
 	case LWS_RXPS_04_MASK_NONCE_1:
@@ -275,6 +289,9 @@
 					   LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK)
 			goto post_mask;
 
+		if (wsi->ietf_spec_revision > 4)
+			goto post_sha1;
+
 		/*
 		 * we are able to compute the frame key now
 		 * it's a SHA1 of ( frame nonce we were just sent, concatenated
@@ -297,6 +314,8 @@
 
 		SHA1((unsigned char *)buf, 4 + 20, wsi->frame_mask_04);
 
+post_sha1:
+
 		/*
 		 * start from the zero'th byte in the XOR key buffer since
 		 * this is the start of a frame with a new key
@@ -356,7 +375,7 @@
 		 *		FIN (b7)
 		 */
 
-		c = xor_mask(wsi, c);
+		c = wsi->xor_mask(wsi, c);
 
 		if (c & 0x70) {
 			fprintf(stderr,
@@ -381,7 +400,7 @@
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN:
-		c = xor_mask(wsi, c);
+		c = wsi->xor_mask(wsi, c);
 
 		if (c & 0x80) {
 			fprintf(stderr, "Frame has extensions "
@@ -430,14 +449,14 @@
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN16_2:
-		c = xor_mask(wsi, c);
+		c = wsi->xor_mask(wsi, c);
 
 		wsi->rx_packet_length = c << 8;
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1;
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN16_1:
-		c = xor_mask(wsi, c);
+		c = wsi->xor_mask(wsi, c);
 
 		wsi->rx_packet_length |= c;
 		wsi->lws_rx_parse_state =
@@ -445,7 +464,7 @@
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_8:
-		c = xor_mask(wsi, c);
+		c = wsi->xor_mask(wsi, c);
 		if (c & 0x80) {
 			fprintf(stderr, "b63 of length must be zero\n");
 			/* kill the connection */
@@ -461,42 +480,42 @@
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_7:
 #if defined __LP64__
-		wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 48;
+		wsi->rx_packet_length |= ((size_t)wsi->xor_mask(wsi, c)) << 48;
 #endif
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_6:
 #if defined __LP64__
-		wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 40;
+		wsi->rx_packet_length |= ((size_t)wsi->xor_mask(wsi, c)) << 40;
 #endif
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_5:
 #if defined __LP64__
-		wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 32;
+		wsi->rx_packet_length |= ((size_t)wsi->xor_mask(wsi, c)) << 32;
 #endif
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4;
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_4:
-		wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 24;
+		wsi->rx_packet_length |= ((size_t)wsi->xor_mask(wsi, c)) << 24;
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3;
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_3:
-		wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 16;
+		wsi->rx_packet_length |= ((size_t)wsi->xor_mask(wsi, c)) << 16;
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2;
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_2:
-		wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 8;
+		wsi->rx_packet_length |= ((size_t)wsi->xor_mask(wsi, c)) << 8;
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1;
 		break;
 
 	case LWS_RXPS_04_FRAME_HDR_LEN64_1:
-		wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c));
+		wsi->rx_packet_length |= ((size_t)wsi->xor_mask(wsi, c));
 		wsi->lws_rx_parse_state =
 					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
 		break;
@@ -541,7 +560,8 @@
 
 	case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED:
 		wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING +
-			       (wsi->rx_user_buffer_head++)] = xor_mask(wsi, c);
+			       (wsi->rx_user_buffer_head++)] =
+							  wsi->xor_mask(wsi, c);
 
 		if (--wsi->rx_packet_length == 0) {
 			wsi->lws_rx_parse_state = LWS_RXPS_NEW;
@@ -627,6 +647,7 @@
 			}
 			break;
 		case 4:
+		case 5:
 	/*
 	 *  04 logical framing from the spec (all this is masked when
 	 *  incoming and has to be unmasked)
@@ -695,6 +716,11 @@
 
 			wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
 			break;
+		default:
+			fprintf(stderr, "client_rx_sm doesn't know how "
+				"to handle spec version %02d\n",
+						       wsi->ietf_spec_revision);
+			break;
 		}
 		break;
 
@@ -943,7 +969,7 @@
 
 
 static int
-libwebsocket_04_frame_mask_generate(struct libwebsocket *wsi)
+libwebsocket_0405_frame_mask_generate(struct libwebsocket *wsi)
 {
 	int fd;
 	char buf[4 + 20];
@@ -965,21 +991,27 @@
 	}
 	close(fd);
 
+	/* start masking from first byte of masking key buffer */
+	wsi->frame_mask_index = 0;
+
+	if (wsi->ietf_spec_revision != 4)
+		return 0;
+
+	/* 04 only does SHA-1 more complex key */
+
 	/*
 	 * the frame key is the frame nonce (4 bytes) followed by the
 	 * connection masking key, hashed by SHA1
 	 */
 
 	memcpy(buf, wsi->frame_masking_nonce_04, 4);
+	
 	memcpy(buf + 4, wsi->masking_key_04, 20);
 
 	/* concatenate the nonce with the connection key then hash it */
 
 	SHA1((unsigned char *)buf, 4 + 20, wsi->frame_mask_04);
 
-	/* start masking from first byte of masking key buffer */
-	wsi->frame_mask_index = 0;
-
 	return 0;
 }
 
@@ -1065,7 +1097,7 @@
 		break;
 
 	case 4:
-
+	case 5:
 		switch (protocol & 0xf) {
 		case LWS_WRITE_TEXT:
 			n = LWS_WS_OPCODE_04__TEXT_FRAME;
@@ -1135,11 +1167,12 @@
 #endif
 
 	/*
-	 * Deal with masking if appropriate
+	 * Deal with masking if we are in client -> server direction and
+	 * the protocol demands it
 	 */
 
 	if (wsi->mode == LWS_CONNMODE_WS_CLIENT &&
-						 wsi->ietf_spec_revision == 4) {
+						 wsi->ietf_spec_revision >= 4) {
 
 		/*
 		 * this is only useful for security tests where it's required
@@ -1148,7 +1181,7 @@
 
 		if (!(protocol & LWS_WRITE_CLIENT_IGNORE_XOR_MASK)) {
 
-			if (libwebsocket_04_frame_mask_generate(wsi)) {
+			if (libwebsocket_0405_frame_mask_generate(wsi)) {
 				fprintf(stderr, "libwebsocket_write: "
 					      "frame mask generation failed\n");
 				return 1;
@@ -1160,7 +1193,7 @@
 			 */
 
 			for (n = 0; n < (len + pre + post); n++)
-				buf[n - pre] = xor_mask(wsi, buf[n - pre]);
+				buf[n - pre] = wsi->xor_mask(wsi, buf[n - pre]);
 		}
 
 		/* make space for the frame nonce in clear */
@@ -1168,6 +1201,7 @@
 
 		/* copy the frame nonce into place */
 		memcpy(&buf[0 - pre], wsi->frame_masking_nonce_04, 4);
+
 	}
 
 send_raw:
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 8028b94..14d4fb5 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -224,6 +224,7 @@
 	unsigned char final;
 
 	int pings_vs_pongs;
+	unsigned char (*xor_mask)(struct libwebsocket *, unsigned char);
 
 	/* client support */
 	char initial_handshake_hash_base64[30];
@@ -261,3 +262,12 @@
 
 extern int
 lws_b64_selftest(void);
+
+extern unsigned char
+xor_no_mask(struct libwebsocket *wsi, unsigned char c);
+
+extern unsigned char
+xor_mask_04(struct libwebsocket *wsi, unsigned char c);
+
+extern unsigned char
+xor_mask_05(struct libwebsocket *wsi, unsigned char c);
diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html
index b98904f..422ed7c 100644
--- a/libwebsockets-api-doc.html
+++ b/libwebsockets-api-doc.html
@@ -161,6 +161,8 @@
 <dd>group id to change to after setting listen socket, or -1.
 <dt><b>uid</b>
 <dd>user id to change to after setting listen socket, or -1.
+<dt><b>options</b>
+<dd>0, or LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK
 </dl>
 <h3>Description</h3>
 <blockquote>
@@ -340,7 +342,8 @@
 <i>const char *</i> <b>path</b>,
 <i>const char *</i> <b>host</b>,
 <i>const char *</i> <b>origin</b>,
-<i>const char *</i> <b>protocol</b>)
+<i>const char *</i> <b>protocol</b>,
+<i>int</i> <b>ietf_version_or_minus_one</b>)
 <h3>Arguments</h3>
 <dl>
 <dt><b>this</b>
@@ -362,6 +365,9 @@
 <dd>Comma-separated list of protocols being asked for from
 the server, or just one.  The server will pick the one it
 likes best.
+<dt><b>ietf_version_or_minus_one</b>
+<dd>-1 to ask to connect using the default, latest
+protocol supported, or the specific protocol ordinal
 </dl>
 <h3>Description</h3>
 <blockquote>
diff --git a/test-server/test-client.c b/test-server/test-client.c
index 59595e0..3508a52 100644
--- a/test-server/test-client.c
+++ b/test-server/test-client.c
@@ -158,6 +158,7 @@
 	{ "port",	required_argument,	NULL, 'p' },
 	{ "ssl",	no_argument,		NULL, 's' },
 	{ "killmask",	no_argument,		NULL, 'k' },
+	{ "version",	required_argument,	NULL, 'v' },
 	{ NULL, 0, 0, 0 }
 };
 
@@ -171,6 +172,7 @@
 	const char *address;
 	struct libwebsocket *wsi_dumb;
 	struct libwebsocket *wsi_mirror;
+	int ietf_version = -1; /* latest */
 
 	fprintf(stderr, "libwebsockets test client\n"
 			"(C) Copyright 2010 Andy Green <andy@warmcat.com> "
@@ -179,10 +181,8 @@
 	if (argc < 2)
 		goto usage;
 
-	optind++;
-
 	while (n >= 0) {
-		n = getopt_long(argc, argv, "khsp:", options, NULL);
+		n = getopt_long(argc, argv, "v:khsp:", options, NULL);
 		if (n < 0)
 			continue;
 		switch (n) {
@@ -195,6 +195,9 @@
 		case 'k':
 			opts = LWS_WRITE_CLIENT_IGNORE_XOR_MASK;
 			break;
+		case 'v':
+			ietf_version = atoi(optarg);
+			break;
 		case 'h':
 			goto usage;
 		}
@@ -225,7 +228,7 @@
 
 	wsi_dumb = libwebsocket_client_connect(context, address, port, use_ssl,
 			"/", argv[optind], argv[optind],
-				       protocols[PROTOCOL_DUMB_INCREMENT].name);
+			 protocols[PROTOCOL_DUMB_INCREMENT].name, ietf_version);
 
 	if (wsi_dumb == NULL) {
 		fprintf(stderr, "libwebsocket dumb connect failed\n");
@@ -236,7 +239,7 @@
 
 	wsi_mirror = libwebsocket_client_connect(context, address, port,
 	     use_ssl,  "/", argv[optind], argv[optind],
-				       protocols[PROTOCOL_LWS_MIRROR].name);
+			     protocols[PROTOCOL_LWS_MIRROR].name, ietf_version);
 
 	if (wsi_mirror == NULL) {
 		fprintf(stderr, "libwebsocket dumb connect failed\n");
diff --git a/test-server/test-ping.c b/test-server/test-ping.c
index e1cb0ad..25a3226 100644
--- a/test-server/test-ping.c
+++ b/test-server/test-ping.c
@@ -277,6 +277,7 @@
 	{ "mirror",	no_argument,		NULL, 'm' },
 	{ "replicate",	required_argument,	NULL, 'r' },
 	{ "killmask",	no_argument,		NULL, 'k' },
+	{ "version",	required_argument,	NULL, 'v' },
 	{ NULL, 0, 0, 0 }
 };
 
@@ -310,6 +311,7 @@
 	struct winsize w;
 	unsigned long oldus = 0;
 	unsigned long l;
+	int ietf_version = -1;
 
 	if (argc < 2)
 		goto usage;
@@ -318,7 +320,7 @@
 	optind++;
 
 	while (n >= 0) {
-		n = getopt_long(argc, argv, "kr:hmfts:n:i:p:", options, NULL);
+		n = getopt_long(argc, argv, "v:kr:hmfts:n:i:p:", options, NULL);
 		if (n < 0)
 			continue;
 		switch (n) {
@@ -356,6 +358,9 @@
 		case 'k':
 			write_options = LWS_WRITE_CLIENT_IGNORE_XOR_MASK;
 			break;
+		case 'v':
+			ietf_version = atoi(optarg);
+			break;
 
 		case 'h':
 			goto usage;
@@ -393,8 +398,9 @@
 
 	for (n = 0; n < clients; n++) {
 		wsi[n] = libwebsocket_client_connect(context, address, port,
-			use_ssl, "/", libwebsocket_canonical_hostname(context),
-				 "origin", protocols[PROTOCOL_LWS_MIRROR].name);
+						     use_ssl, "/", address,
+				 "origin", protocols[PROTOCOL_LWS_MIRROR].name,
+								  ietf_version);
 		if (wsi[n] == NULL) {
 			fprintf(stderr, "client connnection %d failed to "
 								"connect\n", n);