--- server/websocket.c | 124 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 107 insertions(+), 17 deletions(-) diff --git a/server/websocket.c b/server/websocket.c index 7f03138..a802faf 100644 --- a/server/websocket.c +++ b/server/websocket.c @@ -53,11 +53,27 @@ #define LENGTH_64BIT 0x7F #define MASK_FLAG 0x80 +#define MASK_CONTROL_FRAME 0x8 #define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" #define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4) +#define MAX_PONG_DATA 125 +#define PONG_HDR_LEN 2 + +typedef struct { + uint8_t raw_pos; + union { + uint8_t raw_data[MAX_PONG_DATA + PONG_HDR_LEN]; + struct { + uint8_t type; + uint8_t data_len; + uint8_t data[MAX_PONG_DATA]; + }; + }; +} WebSocketPong; + typedef struct { guint8 type; guint8 header[WEBSOCKET_MAX_HEADER_SIZE]; @@ -77,6 +93,8 @@ struct RedsWebSocket { guint8 write_header[WEBSOCKET_MAX_HEADER_SIZE]; guint8 write_header_pos, write_header_len; gboolean close_pending; + WebSocketPong pong; + WebSocketPong pending_pong; void *raw_stream; websocket_read_cb_t raw_read; @@ -87,6 +105,23 @@ struct RedsWebSocket { static int websocket_ack_close(RedsWebSocket *ws); static int send_pending_data(RedsWebSocket *ws); +static inline int get_pong_raw_len(const WebSocketPong *pong) +{ + return pong->data_len + PONG_HDR_LEN; +} + +static inline void pong_init(WebSocketPong *pong) +{ + pong->raw_pos = PONG_HDR_LEN; + pong->type = FIN_FLAG | PONG_FRAME; + pong->data_len = 0; +} + +static inline gboolean pong_sent(const WebSocketPong *pong) +{ + return pong->raw_pos >= get_pong_raw_len(pong); +} + /* Perform a case insensitive search for needle in haystack. If found, return a pointer to the byte after the end of needle. Otherwise, return NULL */ @@ -203,14 +238,14 @@ static void websocket_clear_frame(websocket_frame_t *frame) } /* Extract a frame header of data from a set of data transmitted by - a WebSocket client. */ -static void websocket_get_frame_header(websocket_frame_t *frame) + a WebSocket client. Returns success or error */ +static gboolean websocket_get_frame_header(websocket_frame_t *frame) { int fin; int used = 0; if (frame_bytes_needed(frame) > 0) { - return; + return TRUE; } fin = frame->header[0] & FIN_FLAG; @@ -232,22 +267,25 @@ static void websocket_get_frame_header(websocket_frame_t *frame) memcpy(frame->mask, frame->header + used, 4); } + /* control frames cannor have more than 125 bytes of data */ + if ((frame->type & MASK_CONTROL_FRAME) != 0 && + frame->expected_len >= LENGTH_16BIT) { + return FALSE; + } + frame->relayed = 0; frame->frame_ready = TRUE; + return TRUE; } -static int relay_data(guint8* buf, size_t size, websocket_frame_t *frame) +static void relay_data(guint8* buf, size_t size, websocket_frame_t *frame) { - int i; - int n = MIN(size, frame->expected_len - frame->relayed); - if (frame->masked) { - for (i = 0; i < n; i++, frame->relayed++) { - *buf++ ^= frame->mask[frame->relayed % 4]; + size_t i; + for (i = 0; i < size; i++) { + *buf++ ^= frame->mask[(frame->relayed + i) % 4]; } } - - return n; } int websocket_read(RedsWebSocket *ws, guchar *buf, size_t size) @@ -273,7 +311,17 @@ int websocket_read(RedsWebSocket *ws, guchar *buf, size_t size) } frame->header_pos += rc; - websocket_get_frame_header(frame); + if (!websocket_get_frame_header(frame)) { + ws->closed = TRUE; + errno = EIO; + return -1; + } + /* discard a pending ping, replace with current one */ + if (frame->frame_ready && frame->type == PING_FRAME) { + pong_init(&ws->pong); + ws->pong.data_len = frame->expected_len; + } + continue; } else if (frame->type == CLOSE_FRAME) { ws->close_pending = TRUE; websocket_clear_frame(frame); @@ -286,19 +334,39 @@ int websocket_read(RedsWebSocket *ws, guchar *buf, size_t size) goto read_error; } - rc = relay_data(buf, rc, frame); + relay_data(buf, rc, frame); n += rc; buf += rc; size -= rc; - if (frame->relayed >= frame->expected_len) { - websocket_clear_frame(frame); + } else if (frame->type == PING_FRAME) { + spice_assert(ws->pong.data_len == frame->expected_len); + rc = ws->raw_read(ws->raw_stream, ws->pong.raw_data + ws->pong.raw_pos, + ws->pong.data_len - (ws->pong.raw_pos - PONG_HDR_LEN)); + if (rc <= 0) { + goto read_error; } - } else { - /* TODO - We don't handle PING at this point */ + ws->pong.raw_pos += rc; + if (ws->pong.raw_pos - PONG_HDR_LEN >= ws->pong.data_len) { + ws->pong.raw_pos = 0; + send_pending_data(ws); + } + } else if (frame->type == PONG_FRAME) { + /* client could sent a PONG just as heartbeat */ + uint8_t discard[128]; + rc = ws->raw_read(ws->raw_stream, discard, + frame->expected_len - frame->relayed); + if (rc <= 0) { + goto read_error; + } + } else { spice_warning("Unexpected WebSocket frame.type %d. Failure now likely.", frame->type); websocket_clear_frame(frame); continue; } + frame->relayed += rc; + if (frame->relayed >= frame->expected_len) { + websocket_clear_frame(frame); + } } return n; @@ -426,6 +494,25 @@ static int send_pending_data(RedsWebSocket *ws) return rc; } } + + WebSocketPong *pong = &ws->pending_pong; + if (!pong_sent(pong)) { + rc = ws->raw_write(ws->raw_stream, pong->raw_data + pong->raw_pos, + get_pong_raw_len(pong) - pong->raw_pos); + if (rc <= 0) { + return rc; + } + pong->raw_pos += rc; + if (!pong_sent(pong)) { + errno = EAGAIN; + return -1; + } + /* already another pending */ + if (ws->pong.raw_pos == 0) { + ws->pending_pong = ws->pong; + pong_init(&ws->pong); + } + } return 1; } @@ -598,6 +685,9 @@ RedsWebSocket *websocket_new(gchar *rbuf, void *stream, websocket_read_cb_t read ws->raw_write = write_cb; ws->raw_writev = writev_cb; + pong_init(&ws->pong); + pong_init(&ws->pending_pong); + return ws; } -- 2.7.4 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel