diff options
-rw-r--r-- | lib/handshake.c | 12 | ||||
-rw-r--r-- | lib/libwebsockets.h | 11 | ||||
-rw-r--r-- | lib/parsers.c | 323 | ||||
-rw-r--r-- | lib/private-libwebsockets.h | 35 | ||||
-rw-r--r-- | libwebsockets-api-doc.html | 23 |
5 files changed, 363 insertions, 41 deletions
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 @@ interpret_key(const char *key, unsigned long *result) static int -handshake_76(struct libwebsocket *wsi) +handshake_00(struct libwebsocket *wsi) { unsigned long key1, key2; unsigned char sum[16]; @@ -80,7 +80,7 @@ handshake_76(struct libwebsocket *wsi) 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 @@ handshake_04(struct libwebsocket *wsi) 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 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) 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_callback_reasons { 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 @@ libwebsockets_broadcast(const struct libwebsocket_protocols * protocol, 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 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) 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 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) 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 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) 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 @@ issue: 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 @@ int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi, 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 @@ int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi, * 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 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, 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 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, 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: + + 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; + } - /* just an unimplemented spec right now apparently */ - case 3: - n = 4; /* text */ - if (protocol == LWS_WRITE_BINARY) - n = 5; /* binary */ if (len < 126) { buf[-2] = n; buf[-1] = len; @@ -556,3 +794,26 @@ int libwebsockets_serve_http_file(struct libwebsocket *wsi, const char *file, 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 @@ extern int use_ssl; #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 @@ enum lws_rx_parse_state { 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 @@ struct libwebsocket { 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 @@ struct libwebsocket { 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 @@ to http requests from the client. It allows the callback to issue 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> |