[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