[Spice-devel] [PATCH spice-server 30/30] Handle PING and PONG frames
Frediano Ziglio
fziglio at redhat.com
Mon Nov 21 12:52:17 UTC 2016
---
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
More information about the Spice-devel
mailing list