introduce-04-control-frames.patch

Signed-off-by: Andy Green <andy@warmcat.com>
This commit is contained in:
Andy Green 2011-01-19 12:20:27 +00:00
parent 3e5eb78490
commit 38e57bbd71
5 changed files with 363 additions and 41 deletions

View file

@ -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 */

View file

@ -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

View file

@ -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;
}

View file

@ -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;

View file

@ -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>