introduce-04-control-frames.patch

Signed-off-by: Andy Green <andy@warmcat.com>
diff --git a/lib/handshake.c b/lib/handshake.c
index 5dfdc1d..3f5704f 100644
--- a/lib/handshake.c
+++ b/lib/handshake.c
@@ -72,7 +72,7 @@
 
 
 static int
-handshake_76(struct libwebsocket *wsi)
+handshake_00(struct libwebsocket *wsi)
 {
 	unsigned long key1, key2;
 	unsigned char sum[16];
@@ -80,7 +80,7 @@
 	char *p;
 	int n;
 
-	/* Websocket 76? - confirm we have all the necessary pieces */
+	/* Confirm we have all the necessary pieces */
 
 	if (!wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len ||
 		!wsi->utf8_token[WSI_TOKEN_HOST].token_len ||
@@ -391,6 +391,7 @@
 	free(response);
 	wsi->state = WSI_STATE_ESTABLISHED;
 	wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+	wsi->rx_packet_length = 0;
 
 	/* notify user code that we're ready to roll */
 
@@ -514,14 +515,15 @@
 			wsi->ietf_spec_revision =
 				 atoi(wsi->utf8_token[WSI_TOKEN_VERSION].token);
 
+
 		/*
-		 * Websocket 04+?
-		 * confirm we have all the necessary pieces
+		 * Perform the handshake according to the protocol version the
+		 * client announced
 		 */
 
 		switch (wsi->ietf_spec_revision) {
 		case 0: /* applies to 76 and 00 */
-			if (handshake_76(wsi))
+			if (handshake_00(wsi))
 				goto bail;
 			break;
 		case 4: /* 04 */
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index fe82856..af37413 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -34,7 +34,13 @@
 enum libwebsocket_write_protocol {
 	LWS_WRITE_TEXT,
 	LWS_WRITE_BINARY,
-	LWS_WRITE_HTTP
+	LWS_WRITE_HTTP,
+
+	/* special 04 opcodes */
+
+	LWS_WRITE_CLOSE,
+	LWS_WRITE_PING,
+	LWS_WRITE_PONG
 };
 
 struct libwebsocket;
@@ -177,4 +183,7 @@
 extern const struct libwebsocket_protocols *
 libwebsockets_get_protocol(struct libwebsocket *wsi);
 
+extern size_t
+libwebsockets_remaining_packet_payload(struct libwebsocket *wsi);
+
 #endif
diff --git a/lib/parsers.c b/lib/parsers.c
index dbee05f..a3d2eba 100644
--- a/lib/parsers.c
+++ b/lib/parsers.c
@@ -216,17 +216,16 @@
 		case 0:
 			if (c == 0xff)
 				wsi->lws_rx_parse_state = LWS_RXPS_SEEN_76_FF;
+			if (c == 0) {
+				wsi->lws_rx_parse_state = LWS_RXPS_EAT_UNTIL_76_FF;
+				wsi->rx_user_buffer_head = 0;
+			}
 			break;
 		case 4:
 			wsi->frame_masking_nonce_04[0] = c;
 			wsi->lws_rx_parse_state = LWS_RXPS_04_MASK_NONCE_1;
 			break;
 		}
-
-		if (c == 0) {
-			wsi->lws_rx_parse_state = LWS_RXPS_EAT_UNTIL_76_FF;
-			wsi->rx_user_buffer_head = 0;
-		}
 		break;
 	case LWS_RXPS_04_MASK_NONCE_1:
 		wsi->frame_masking_nonce_04[1] = c;
@@ -255,7 +254,7 @@
 		memcpy(buf + 4, wsi->masking_key_04, 20);
 
 		/*
-		 * wsi->frame_mask_04 is our recirculating 20-byte XOR key
+		 * wsi->frame_mask_04 will be our recirculating 20-byte XOR key
 		 * for this frame
 		 */
 	
@@ -270,6 +269,188 @@
 		
 		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_1;
 		break;
+
+	/*
+	 *  04 logical framing from the spec (all this is masked when incoming
+	 *  and has to be unmasked)
+	 *
+	 * We ignore the possibility of extension data because we don't
+	 * negotiate any extensions at the moment.
+	 * 
+	 *    0                   1                   2                   3
+	 *    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+	 *   +-+-+-+-+-------+-+-------------+-------------------------------+
+	 *   |F|R|R|R| opcode|R| Payload len |    Extended payload length    |
+	 *   |I|S|S|S|  (4)  |S|     (7)     |             (16/63)           |
+	 *   |N|V|V|V|       |V|             |   (if payload len==126/127)   |
+	 *   | |1|2|3|       |4|             |                               |
+	 *   +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+	 *   |     Extended payload length continued, if payload len == 127  |
+	 *   + - - - - - - - - - - - - - - - +-------------------------------+
+	 *   |                               |         Extension data        |
+	 *   +-------------------------------+ - - - - - - - - - - - - - - - +
+	 *   :                                                               :
+	 *   +---------------------------------------------------------------+
+	 *   :                       Application data                        :
+	 *   +---------------------------------------------------------------+
+	 *
+	 *  We pass payload through to userland as soon as we get it, ignoring
+	 *  FIN.  It's up to userland to buffer it up if it wants to see a
+	 *  whole unfragmented block of the original size (which may be up to
+	 *  2^63 long!)
+	 */
+
+	case LWS_RXPS_04_FRAME_HDR_1:
+		/*
+		 * 04 spec defines the opcode like this: (1, 2, and 3 are
+		 * "control frame" opcodes which may not be fragmented or
+		 * have size larger than 126)
+		 * 
+		 *       frame-opcode           =
+		 * 	       %x0 ; continuation frame
+		 *           / %x1 ; connection close
+		 *           / %x2 ; ping
+		 *           / %x3 ; pong
+		 *           / %x4 ; text frame
+		 *           / %x5 ; binary frame
+		 *           / %x6-F ; reserved
+		 *
+		 * 	  FIN (b7)
+		 */
+
+		c = unmask(wsi, c);
+
+		if (c & 0x70) {
+			fprintf(stderr, "Frame has extensions set illegally\n");
+			/* kill the connection */
+			return -1;
+		}
+
+		wsi->opcode = c & 0xf;
+		wsi->final = !!((c >> 7) & 1);
+
+		if (wsi->final &&
+			wsi->opcode == LWS_WS_OPCODE_04__CONTINUATION &&
+						   wsi->rx_packet_length == 0) {
+			fprintf(stderr,
+				      "Frame starts with final continuation\n");
+			/* kill the connection */
+			return -1;
+		}
+
+		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN;
+		break;
+
+	case LWS_RXPS_04_FRAME_HDR_LEN:
+		c = unmask(wsi, c);
+
+		if (c & 0x80) {
+			fprintf(stderr, "Frame has extensions set illegally\n");
+			/* kill the connection */
+			return -1;
+		}
+
+		switch (c) {
+		case 126:
+			/* control frames are not allowed to have big lengths */
+			switch (wsi->opcode) {
+			case LWS_WS_OPCODE_04__CLOSE:
+			case LWS_WS_OPCODE_04__PING:
+			case LWS_WS_OPCODE_04__PONG:
+				fprintf(stderr, "Control frame asking for "
+						"extended length is illegal\n");
+				/* kill the connection */
+				return -1;
+			default:
+				break;
+			}
+			wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2;
+			break;
+		case 127:
+			/* control frames are not allowed to have big lengths */
+			switch (wsi->opcode) {
+			case LWS_WS_OPCODE_04__CLOSE:
+			case LWS_WS_OPCODE_04__PING:
+			case LWS_WS_OPCODE_04__PONG:
+				fprintf(stderr, "Control frame asking for "
+						"extended length is illegal\n");
+				/* kill the connection */
+				return -1;
+			default:
+				break;
+			}
+			wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8;
+			break;
+		default:
+			wsi->rx_packet_length = c;
+			wsi->lws_rx_parse_state =
+					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+			break;
+		}
+		break;
+
+	case LWS_RXPS_04_FRAME_HDR_LEN16_2:
+		c = unmask(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 = unmask(wsi, c);
+
+		wsi->rx_packet_length |= c;
+		wsi->lws_rx_parse_state =
+					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+		break;
+
+	case LWS_RXPS_04_FRAME_HDR_LEN64_8:
+		c = unmask(wsi, c);
+		if (c & 0x80) {
+			fprintf(stderr, "b63 of length must be zero\n");
+			/* kill the connection */
+			return -1;
+		}
+		wsi->rx_packet_length = ((size_t)c) << 56;
+		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7;
+		break;
+
+	case LWS_RXPS_04_FRAME_HDR_LEN64_7:
+		wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 48;
+		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6;
+		break;
+
+	case LWS_RXPS_04_FRAME_HDR_LEN64_6:
+		wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 40;
+		wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5;
+		break;
+
+	case LWS_RXPS_04_FRAME_HDR_LEN64_5:
+		wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 32;
+		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)unmask(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)unmask(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)unmask(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)unmask(wsi, c));
+		wsi->lws_rx_parse_state =
+					LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED;
+		break;
+
 	case LWS_RXPS_EAT_UNTIL_76_FF:
 		if (c == 0xff) {
 			wsi->lws_rx_parse_state = LWS_RXPS_NEW;
@@ -307,7 +488,61 @@
 
 	case LWS_RXPS_PULLING_76_LENGTH:
 		break;
+
 	case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED:
+		wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING +
+				 (wsi->rx_user_buffer_head++)] = unmask(wsi, c);
+		if (--wsi->rx_packet_length == 0) {
+			wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+			goto spill;
+		}
+		if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER)
+			break;
+spill:
+		/*
+		 * is this frame a control packet we should take care of at this
+		 * layer?  If so service it and hide it from the user callback
+		 */
+
+		switch (wsi->opcode) {
+		case LWS_WS_OPCODE_04__CLOSE:
+			/* parrot the close packet payload back */
+			n = libwebsocket_write(wsi, (unsigned char *)
+			   &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING],
+				     wsi->rx_user_buffer_head, LWS_WRITE_CLOSE);
+			/* close the connection */
+			return -1;
+
+		case LWS_WS_OPCODE_04__PING:
+			/* parrot the ping packet payload back as a pong*/
+			n = libwebsocket_write(wsi, (unsigned char *)
+			    &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING],
+				    wsi->rx_user_buffer_head, LWS_WRITE_PONG);
+			break;
+
+		case LWS_WS_OPCODE_04__PONG:
+			/* keep the statistics... */
+			wsi->pings_vs_pongs--;
+			/* ... then just drop it */
+			wsi->rx_user_buffer_head = 0;
+			return 0;
+
+		default:
+			break;
+		}
+
+		/*
+		 * No it's real payload, pass it up to the user callback.
+		 * It's nicely buffered with the pre-padding taken care of
+		 * so it can be sent straight out again using libwebsocket_write
+		 */
+
+		if (wsi->protocol->callback)
+			wsi->protocol->callback(wsi, LWS_CALLBACK_RECEIVE,
+			  wsi->user_space,
+			  &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING],
+			  wsi->rx_user_buffer_head);
+		wsi->rx_user_buffer_head = 0;
 		break;
 	}
 
@@ -325,6 +560,7 @@
 		fprintf(stderr, "%02X ", buf[n]);
 	fprintf(stderr, "\n");
 #endif
+
 	/* let the rx protocol state machine have as much as it needs */
 
 	n = 0;
@@ -363,6 +599,8 @@
  *	packet while not burdening the user code with any protocol knowledge.
  */
 
+ /* FIXME FIN bit */
+
 int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf,
 			  size_t len, enum libwebsocket_write_protocol protocol)
 {
@@ -382,7 +620,7 @@
 	switch (wsi->ietf_spec_revision) {
 	/* chrome likes this as of 30 Oct */
 	/* Firefox 4.0b6 likes this as of 30 Oct */
-	case 76:
+	case 0:
 		if (protocol == LWS_WRITE_BINARY) {
 			/* in binary mode we send 7-bit used length blocks */
 			pre = 1;
@@ -413,31 +651,31 @@
 		post = 1;
 		break;
 
-	case 0:
-		buf[-9] = 0xff;
-#if defined __LP64__
-			buf[-8] = len >> 56;
-			buf[-7] = len >> 48;
-			buf[-6] = len >> 40;
-			buf[-5] = len >> 32;
-#else
-			buf[-8] = 0;
-			buf[-7] = 0;
-			buf[-6] = 0;
-			buf[-5] = 0;
-#endif
-		buf[-4] = len >> 24;
-		buf[-3] = len >> 16;
-		buf[-2] = len >> 8;
-		buf[-1] = len;
-		pre = 9;
-		break;
+	case 4:
 
-	/* just an unimplemented spec right now apparently */
-	case 3:
-		n = 4; /* text */
-		if (protocol == LWS_WRITE_BINARY)
-			n = 5; /* binary */
+		switch (protocol) {
+		case LWS_WRITE_TEXT:
+			n = LWS_WS_OPCODE_04__TEXT_FRAME;
+			break;
+		case LWS_WRITE_BINARY:
+			n = LWS_WS_OPCODE_04__BINARY_FRAME;
+			break;
+		case LWS_WRITE_CLOSE:
+			n = LWS_WS_OPCODE_04__CLOSE;
+			break;
+		case LWS_WRITE_PING:
+			n = LWS_WS_OPCODE_04__PING;
+			wsi->pings_vs_pongs++;
+			break;
+		case LWS_WRITE_PONG:
+			n = LWS_WS_OPCODE_04__PONG;
+			break;
+		default:
+			fprintf(stderr, "libwebsocket_write: unknown write "
+							 "opcode / protocol\n");
+			return -1;
+		}
+
 		if (len < 126) {
 			buf[-2] = n;
 			buf[-1] = len;
@@ -556,3 +794,26 @@
 
 	return 0;
 }
+
+/**
+ * libwebsockets_remaining_packet_payload() - Bytes to come before "overall"
+ * 					      rx packet is complete
+ * @wsi:		Websocket instance (available from user callback)
+ *
+ *	This function is intended to be called from the callback if the
+ *  user code is interested in "complete packets" from the client.
+ *  libwebsockets just passes through payload as it comes and issues a buffer
+ *  additionally when it hits a built-in limit.  The LWS_CALLBACK_RECEIVE
+ *  callback handler can use this API to find out if the buffer it has just
+ *  been given is the last piece of a "complete packet" from the client --
+ *  when that is the case libwebsockets_remaining_packet_payload() will return
+ *  0.
+ *
+ *  Many protocols won't care becuse their packets are always small.
+ */
+
+size_t
+libwebsockets_remaining_packet_payload(struct libwebsocket *wsi)
+{
+	return wsi->rx_packet_length;
+}
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 17b7277..3edc57a 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -77,6 +77,15 @@
 #define MAX_WEBSOCKET_04_KEY_LEN 128
 #define SYSTEM_RANDOM_FILEPATH "/dev/random"
 
+enum lws_websocket_opcodes_04 {
+	LWS_WS_OPCODE_04__CONTINUATION = 0,
+	LWS_WS_OPCODE_04__CLOSE = 1,
+	LWS_WS_OPCODE_04__PING = 2,
+	LWS_WS_OPCODE_04__PONG = 3,
+	LWS_WS_OPCODE_04__TEXT_FRAME = 4,
+	LWS_WS_OPCODE_04__BINARY_FRAME = 5,
+};
+
 enum lws_connection_states {
 	WSI_STATE_HTTP,
 	WSI_STATE_HTTP_HEADERS,
@@ -121,6 +130,17 @@
 	LWS_RXPS_04_MASK_NONCE_3,
 
 	LWS_RXPS_04_FRAME_HDR_1,
+	LWS_RXPS_04_FRAME_HDR_LEN,
+	LWS_RXPS_04_FRAME_HDR_LEN16_2,
+	LWS_RXPS_04_FRAME_HDR_LEN16_1,
+	LWS_RXPS_04_FRAME_HDR_LEN64_8,
+	LWS_RXPS_04_FRAME_HDR_LEN64_7,
+	LWS_RXPS_04_FRAME_HDR_LEN64_6,
+	LWS_RXPS_04_FRAME_HDR_LEN64_5,
+	LWS_RXPS_04_FRAME_HDR_LEN64_4,
+	LWS_RXPS_04_FRAME_HDR_LEN64_3,
+	LWS_RXPS_04_FRAME_HDR_LEN64_2,
+	LWS_RXPS_04_FRAME_HDR_LEN64_1,
 
 	LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED
 };
@@ -159,10 +179,6 @@
 	enum lws_token_indexes parser_state;
 	struct lws_tokens utf8_token[WSI_TOKEN_COUNT];
 	int ietf_spec_revision;
-	unsigned char masking_key_04[20];
-	unsigned char frame_mask_04[20];
-	unsigned char frame_masking_nonce_04[4];
-	unsigned char frame_mask_index;
 	char rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + MAX_USER_RX_BUFFER +
 						  LWS_SEND_BUFFER_POST_PADDING];
 	int rx_user_buffer_head;
@@ -170,7 +186,18 @@
 	int sock;
 
 	enum lws_rx_parse_state lws_rx_parse_state;
+
+	/* 04 protocol specific */
+
+	unsigned char masking_key_04[20];
+	unsigned char frame_masking_nonce_04[4];
+	unsigned char frame_mask_04[20];
+	unsigned char frame_mask_index;
 	size_t rx_packet_length;
+	unsigned char opcode;
+	unsigned char final;
+
+	int pings_vs_pongs;
 
 #ifdef LWS_OPENSSL_SUPPORT
 	SSL *ssl;
diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html
index 6562149..068db9c 100644
--- a/libwebsockets-api-doc.html
+++ b/libwebsockets-api-doc.html
@@ -162,6 +162,29 @@
 local files down the http link in a single step.
 </blockquote>
 <hr>
+<h2>libwebsockets_remaining_packet_payload - Bytes to come before "overall" rx packet is complete</h2>
+<i>size_t</i>
+<b>libwebsockets_remaining_packet_payload</b>
+(<i>struct libwebsocket *</i> <b>wsi</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>wsi</b>
+<dd>Websocket instance (available from user callback)
+</dl>
+<h3>Description</h3>
+<blockquote>
+This function is intended to be called from the callback if the
+user code is interested in "complete packets" from the client.
+libwebsockets just passes through payload as it comes and issues a buffer
+additionally when it hits a built-in limit.  The LWS_CALLBACK_RECEIVE
+callback handler can use this API to find out if the buffer it has just
+been given is the last piece of a "complete packet" from the client --
+when that is the case <b>libwebsockets_remaining_packet_payload</b> will return
+0.
+<p>
+Many protocols won't care becuse their packets are always small.
+</blockquote>
+<hr>
 <h2>callback - User server actions</h2>
 <i>int</i>
 <b>callback</b>