[Spice-commits] 26 commits - configure.ac .gitlab-ci.yml server/Makefile.am server/meson.build server/reds.c server/red-stream.c server/red-stream.h server/tests server/websocket.c server/websocket.h

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Thu Jun 27 08:43:46 UTC 2019


 .gitlab-ci.yml                     |  141 ------
 configure.ac                       |    9 
 server/Makefile.am                 |    2 
 server/meson.build                 |    2 
 server/red-stream.c                |   56 ++
 server/red-stream.h                |    2 
 server/reds.c                      |   15 
 server/tests/.gitignore            |    1 
 server/tests/Makefile.am           |    6 
 server/tests/autobahn-check-report |   18 
 server/tests/fuzzingclient.json    |   11 
 server/tests/meson.build           |    1 
 server/tests/test-glib-compat.h    |    2 
 server/tests/test-websocket.c      |  300 +++++++++++++
 server/websocket.c                 |  806 +++++++++++++++++++++++++++++++++++++
 server/websocket.h                 |   43 +
 16 files changed, 1291 insertions(+), 124 deletions(-)

New commits:
commit b5f40eb53575bf8fed389d01698f426d10a950b1
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Sat Jun 22 07:33:41 2019 +0100

    SAVE for local debug

diff --git a/server/tests/test-websocket.c b/server/tests/test-websocket.c
index dc7b7d34..da57e130 100644
--- a/server/tests/test-websocket.c
+++ b/server/tests/test-websocket.c
@@ -164,6 +164,7 @@ main(int argc, char **argv)
     struct sockaddr_in sin;
     memset(&sin, 0, sizeof(sin));
     sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+    sin.sin_addr.s_addr = 0;
     sin.sin_port = htons((short) port);
     sin.sin_family = AF_INET;
 
commit 24d1a6b22eb42e0245fa1caf14f80eab720e51b3
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:42:46 2016 +0000

    SAVE

diff --git a/server/websocket.c b/server/websocket.c
index f5df63f8..c559ce72 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -136,6 +136,16 @@ static inline void pong_init(WebSocketControl *pong)
     control_init(pong, FIN_FLAG | PONG_FRAME);
 }
 
+static inline void pong_check(const WebSocketControl *pong)
+{
+#ifdef REDS_DEBUG
+    spice_assert(pong);
+    spice_assert(pong->type == FIN_FLAG | PONG_FRAME);
+    spice_assert(pong->data_len < MAX_PONG_DATA);
+    spice_assert(pong->raw_pos <= get_pong_raw_len(pong));
+#endif
+}
+
 /* 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 */
commit 7e49543b0df2861d789c042985470c72ddfe2160
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Thu Jun 20 12:40:04 2019 +0100

    ci: Make test faster XXX temp

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b303d465..f6189313 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,131 +12,6 @@ before_script:
   - git clone ${CI_REPOSITORY_URL/spice.git/spice-protocol.git}
   - (cd spice-protocol && ./autogen.sh --prefix=/usr && make install)
 
-makecheck:
-  script:
-  # Also check out-of-tree build
-  - git clean -fdx # cleanup after previous builds
-  - git submodule foreach --recursive git clean -fdx
-  - mkdir builddir
-  - cd builddir
-  - >
-    CFLAGS='-O2 -pipe -g -fsanitize=address -fno-omit-frame-pointer -Wframe-larger-than=40920'
-    LDFLAGS='-fsanitize=address -lasan'
-    ../autogen.sh --enable-celt051
-  - make
-  - make -C server check || (cat server/tests/test-suite.log && exit 1)
-  - cd ..
-
-meson-makecheck:
-  script:
-  - >
-    CFLAGS='-O2 -pipe -g -fsanitize=address -fno-omit-frame-pointer -Wframe-larger-than=40920'
-    LDFLAGS='-fsanitize=address -lasan'
-    meson --buildtype=release build -Dcelt051=enabled || (cat build/meson-logs/meson-log.txt && exit 1)
-  - ninja -C build
-  - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1)
-
-# check non-standard options, currently
-# --enable-statistics  compile statistic code
-# --without-sasl       disable SASL
-options:
-  script:
-  - ./autogen.sh --enable-statistics --without-sasl --disable-celt051
-  - make
-  - make -C server check || (cat server/tests/test-suite.log && exit 1)
-
-meson-options:
-  script:
-  - meson --buildtype=release -Dstatistics=true -Dsasl=false -Dcelt051=disabled build
-  - ninja -C build
-  - (cd build && meson test) || (cat build/meson-logs/testlog.txt && exit 1)
-
-check-valgrind:
-  script:
-  - dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm -y
-  - dnf debuginfo-install spice-server glib2 -y
-  - >
-    dnf install valgrind
-    gstreamer1-libav gstreamer1-plugins-ugly gstreamer1-plugins-good gstreamer1-plugins-bad-free
-    -y
-  - >
-    CFLAGS='-O2 -pipe -g -D_FORTIFY_SOURCE=0'
-    ./autogen.sh --enable-valgrind --enable-extra-checks --enable-celt051
-  - make
-  - make check-valgrind || (cat server/tests/test-suite-memcheck.log && exit 1)
-
-syntax-check:
-  script:
-  - ./autogen.sh --enable-celt051
-  - make syntax-check
-
-distcheck:
-  script:
-  - ./autogen.sh --enable-celt051 --enable-manual
-  - make distcheck
-
-# Same as makecheck job but use a Centos image
-makecheck-centos:
-  before_script:
-    - >
-      yum install git libtool make libasan orc-devel glib-networking
-      yum-utils gcc glib2-devel celt051-devel
-      opus-devel pixman-devel openssl-devel libjpeg-devel
-      libcacard-devel cyrus-sasl-devel lz4-devel
-      gstreamer1-devel gstreamer1-plugins-base-devel
-      git-core pyparsing python-six
-      -y
-    - git clone ${CI_REPOSITORY_URL/spice.git/spice-protocol.git}
-    - (cd spice-protocol && ./autogen.sh --prefix=/usr && make install)
-  image: centos:latest
-  script:
-  - >
-    CFLAGS='-O2 -pipe -g -fsanitize=address -fno-omit-frame-pointer -Wframe-larger-than=40920'
-    LDFLAGS='-fsanitize=address -lasan'
-    ./autogen.sh --enable-celt051
-  - make
-  - make -C server check || (cat server/tests/test-suite.log && exit 1)
-
-# Same as makecheck job but use a Centos image
-makecheck-centos32:
-  before_script:
-    - >
-      yum install git libtool make libasan orc-devel glib-networking
-      yum-utils gcc glib2-devel celt051-devel
-      opus-devel pixman-devel openssl-devel libjpeg-devel
-      libcacard-devel cyrus-sasl-devel lz4-devel
-      gstreamer1-devel gstreamer1-plugins-base-devel
-      git-core pyparsing python-six
-      -y
-    - git clone ${CI_REPOSITORY_URL/spice.git/spice-protocol.git}
-    - (cd spice-protocol && ./autogen.sh --prefix=/usr && make install)
-  image: i386/centos:latest
-  script:
-  - >
-    CFLAGS='-O2 -pipe -g -fsanitize=address -fno-omit-frame-pointer -Wframe-larger-than=40920'
-    LDFLAGS='-fsanitize=address -lasan'
-    ./autogen.sh --enable-celt051
-  - make
-  - make -C server check || (cat server/tests/test-suite.log && exit 1)
-
-# Same as makecheck job but use Windows build
-makecheck-windows:
-  script:
-  - >
-    dnf install -y
-    wine-core.x86_64 mingw64-gcc-c++
-    mingw64-openssl mingw64-glib2 mingw64-glib-networking mingw64-libjpeg-turbo
-    mingw64-pixman mingw64-opus mingw64-winpthreads mingw64-zlib
-    mingw64-gstreamer1-plugins-base mingw64-gstreamer1-plugins-good mingw64-orc
-  - cd spice-protocol
-  - mingw64-configure
-  - mingw64-make install
-  - cd ..
-  - NOCONFIGURE=yes ./autogen.sh
-  - mingw64-configure --disable-celt051
-  - mingw64-make
-  - mingw64-make LOG_COMPILE=wine -C server check || (cat server/tests/test-suite.log && exit 1)
-
 websocket-autobahn:
   before_script:
   - apt-get update || true
commit a68fd56b418aa891242c589bee7fcac172ba28fe
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Thu Jun 20 12:39:31 2019 +0100

    ci: Add test for websockets
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6e5204af..b303d465 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -136,3 +136,23 @@ makecheck-windows:
   - mingw64-configure --disable-celt051
   - mingw64-make
   - mingw64-make LOG_COMPILE=wine -C server check || (cat server/tests/test-suite.log && exit 1)
+
+websocket-autobahn:
+  before_script:
+  - apt-get update || true
+  - apt-get install -y python-six python-pyparsing libopus-dev libssl-dev libglib2.0-dev
+  - git clone ${CI_REPOSITORY_URL/spice.git/spice-protocol.git}
+  - (cd spice-protocol && ./autogen.sh --prefix=/usr && make install)
+  image: crossbario/autobahn-testsuite
+  script:
+  - ./autogen.sh
+  - make -j4
+  - ./server/tests/test-websocket & pid=$!
+  - wstest -m fuzzingclient -s server/tests/fuzzingclient.json
+  - kill $pid
+  - server/tests/autobahn-check-report reports/servers/index.json
+  - rm -rf reports/servers
+  - ./server/tests/test-websocket -n & pid=$!
+  - wstest -m fuzzingclient -s server/tests/fuzzingclient.json
+  - kill $pid
+  - server/tests/autobahn-check-report reports/servers/index.json
diff --git a/server/tests/autobahn-check-report b/server/tests/autobahn-check-report
new file mode 100755
index 00000000..9e29128c
--- /dev/null
+++ b/server/tests/autobahn-check-report
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+import sys
+import json
+
+num_tests = 0
+for server in json.load(open(sys.argv[1])).values():
+  for test, result in server.items():
+    is_test = 0
+    for k, v in result.items():
+      if k[:8].lower() != 'behavior':
+        continue
+      is_test = 1
+      if v != 'OK' and v != 'INFORMATIONAL':
+        raise Exception('Invalid %s %s for test %s' % (k, v, test))
+    num_tests += is_test
+if num_tests < 100:
+  raise Exception('Too few tests done %s' % num_tests)
+print('Output report is fine')
diff --git a/server/tests/fuzzingclient.json b/server/tests/fuzzingclient.json
new file mode 100644
index 00000000..8bc1b718
--- /dev/null
+++ b/server/tests/fuzzingclient.json
@@ -0,0 +1,11 @@
+{
+   "outdir": "./reports/servers",
+   "servers": [
+      {
+         "url": "ws://127.0.0.1:7777"
+      }
+   ],
+   "cases": ["*"],
+   "exclude-cases": ["6.*","12.*","13.*"],
+   "exclude-agent-cases": {}
+}
commit c1c5d8bf5abb78054c52093c144931a8d9aa1c80
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Fri Feb 3 19:05:22 2017 +0000

    websocket: Handle continuation and 0-size frames
    
    The WebSocket protocol allows 0-size frames so a returned lenth of
    0 does not only mean an issue but it's perfectly expected.
    This is also required by WebSocket specification.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/tests/test-websocket.c b/server/tests/test-websocket.c
index 120a522f..dc7b7d34 100644
--- a/server/tests/test-websocket.c
+++ b/server/tests/test-websocket.c
@@ -219,21 +219,28 @@ handle_client(int new_sock)
     assert(ws);
 
     char buffer[4096];
+    bool got_message = false;
     size_t to_send = 0;
     unsigned ws_flags = WEBSOCKET_BINARY_FINAL;
     while (!got_term) {
         int events = 0;
-        if (sizeof(buffer) > to_send) {
+        if (sizeof(buffer) > to_send &&
+            (!got_message || (ws_flags & WEBSOCKET_FINAL) == 0)) {
             events |= POLLIN;
         }
-        if (to_send) {
+        assert(!to_send || got_message);
+        if (got_message) {
             events |= POLLOUT;
         }
         events = wait_for(new_sock, events);
         if (events & POLLIN) {
             assert(sizeof(buffer) > to_send);
+            unsigned flags;
             int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send,
-                                      &ws_flags);
+                                      &flags);
+            if (flags) {
+                ws_flags = flags;
+            }
 
             if (size < 0) {
                 if (errno == EIO) {
@@ -245,14 +252,15 @@ handle_client(int new_sock)
                 err(1, "recv");
             }
 
-            if (size == 0) {
+            if (size == 0 && flags == 0) {
                 break;
             }
 
             if (debug) {
-                printf("received %d bytes of data\n", size);
+                printf("received %d bytes of data flags %x\n", size, flags);
             }
             to_send += size;
+            got_message = true;
         }
 
         if (events & POLLOUT) {
@@ -275,12 +283,11 @@ handle_client(int new_sock)
                 printf("sent %d bytes of data\n", size);
             }
 
-            if (size == 0) {
-                errx(1, "Unexpected short write\n");
-            }
-
             to_send -= size;
             memmove(buffer, buffer + size, to_send);
+            if (!to_send) {
+                got_message = false;
+            }
         }
     }
 
diff --git a/server/websocket.c b/server/websocket.c
index e076645d..f5df63f8 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -83,7 +83,7 @@ typedef struct {
 } WebSocketControl;
 
 typedef struct {
-    uint8_t type;
+    uint8_t type, fin, unfinished;
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     int header_pos;
     bool frame_ready:1;
@@ -100,6 +100,7 @@ struct RedsWebSocket {
     uint64_t write_remainder;
     uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
     uint8_t write_header_pos, write_header_len;
+    bool send_unfinished;
     bool close_pending;
     WebSocketControl pong;
     WebSocketControl pending_pong;
@@ -247,7 +248,9 @@ static char *generate_reply_key(char *buf)
 
 static void websocket_clear_frame(websocket_frame_t *frame)
 {
+    uint8_t unfinished = frame->unfinished;
     memset(frame, 0, sizeof(*frame));
+    frame->unfinished = unfinished;
 }
 
 /* Extract a frame header of data from a set of data transmitted by
@@ -261,7 +264,7 @@ static bool websocket_get_frame_header(websocket_frame_t *frame)
         return true;
     }
 
-    fin = frame->header[0] & FIN_FLAG;
+    fin = frame->fin = frame->header[0] & FIN_FLAG;
     frame->type = frame->header[0] & TYPE_MASK;
     used++;
 
@@ -282,8 +285,16 @@ static bool websocket_get_frame_header(websocket_frame_t *frame)
     /* This is a Spice specific optimization.  We don't really
        care about assembling frames fully, so we treat
        a frame in process as a finished frame and pass it along. */
-    if (!fin && frame->type == CONTINUATION_FRAME) {
-        frame->type = BINARY_FRAME;
+    if ((frame->type & CONTROL_FRAME_MASK) == 0) {
+        if (frame->type == CONTINUATION_FRAME) {
+            if (!frame->unfinished) {
+                return false;
+            }
+            frame->type = frame->unfinished;
+        } else if (frame->unfinished) {
+            return false;
+        }
+        frame->unfinished = fin ? 0 : frame->type;
     }
 
     frame->expected_len = extract_length(frame->header + used, &used);
@@ -358,18 +369,21 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size, unsigned *flags
             send_pending_data(ws);
             return 0;
         } else if (frame->type == BINARY_FRAME || frame->type == TEXT_FRAME) {
-            rc = ws->raw_read(ws->raw_stream, buf,
-                              MIN(size, frame->expected_len - frame->relayed));
-            if (rc <= 0) {
-                goto read_error;
+            rc = 0;
+            if (frame->expected_len > frame->relayed) {
+                rc = ws->raw_read(ws->raw_stream, buf,
+                                  MIN(size, frame->expected_len - frame->relayed));
+                if (rc <= 0) {
+                    goto read_error;
+                }
+
+                relay_data(buf, rc, frame);
+                n += rc;
+                buf += rc;
+                size -= rc;
             }
 
             *flags = frame->type;
-
-            relay_data(buf, rc, frame);
-            n += rc;
-            buf += rc;
-            size -= rc;
         } else if (frame->type == PING_FRAME) {
             spice_assert(ws->pong.data_len == frame->expected_len);
             rc = 0;
@@ -409,8 +423,11 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size, unsigned *flags
         }
         frame->relayed += rc;
         if (frame->relayed >= frame->expected_len) {
+            if (*flags) {
+                *flags |= frame->fin;
+            }
             websocket_clear_frame(frame);
-            if (n) {
+            if (*flags) {
                 break;
             }
         }
@@ -433,8 +450,8 @@ static int fill_header(uint8_t *header, uint64_t len, uint8_t type)
     int used = 0;
     int i;
 
-    type &= TYPE_MASK;
-    header[0] = FIN_FLAG | (type ? type : BINARY_FRAME);
+    type &= FIN_FLAG|TYPE_MASK;
+    header[0] = type;
     used++;
 
     header[1] = 0;
@@ -512,7 +529,11 @@ static int send_data_header(RedsWebSocket *ws, uint64_t len, uint8_t type)
 
     /* fill a new header */
     ws->write_header_pos = 0;
+    if (ws->send_unfinished) {
+        type &= FIN_FLAG;
+    }
     ws->write_header_len = fill_header(ws->write_header, len, type);
+    ws->send_unfinished = (type & FIN_FLAG) == 0;
 
     return send_data_header_left(ws);
 }
commit 9d6a438aae15826943756e45cb5be7a2c705f1a0
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Sun Jun 23 09:03:54 2019 +0100

    websocket: Handle text data
    
    Allows to specify and get frame type.
    Type and flags are returned calling websocket_read and returned
    calling websocket_write or websocket_writev.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/red-stream.c b/server/red-stream.c
index 1e0103b0..04be3af3 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -1163,17 +1163,23 @@ error:
 
 static ssize_t stream_websocket_read(RedStream *s, void *buf, size_t size)
 {
-    return websocket_read(s->priv->ws, buf, size);
+    unsigned flags;
+    int len;
+
+    do {
+        len = websocket_read(s->priv->ws, buf, size, &flags);
+    } while (len == 0 && flags != 0);
+    return len;
 }
 
 static ssize_t stream_websocket_write(RedStream *s, const void *buf, size_t size)
 {
-    return websocket_write(s->priv->ws, buf, size);
+    return websocket_write(s->priv->ws, buf, size, WEBSOCKET_BINARY_FINAL);
 }
 
 static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, int iovcnt)
 {
-    return websocket_writev(s->priv->ws, (struct iovec *) iov, iovcnt);
+    return websocket_writev(s->priv->ws, (struct iovec *) iov, iovcnt, WEBSOCKET_BINARY_FINAL);
 }
 
 /*
diff --git a/server/tests/test-websocket.c b/server/tests/test-websocket.c
index 6596c27e..120a522f 100644
--- a/server/tests/test-websocket.c
+++ b/server/tests/test-websocket.c
@@ -220,6 +220,7 @@ handle_client(int new_sock)
 
     char buffer[4096];
     size_t to_send = 0;
+    unsigned ws_flags = WEBSOCKET_BINARY_FINAL;
     while (!got_term) {
         int events = 0;
         if (sizeof(buffer) > to_send) {
@@ -231,7 +232,8 @@ handle_client(int new_sock)
         events = wait_for(new_sock, events);
         if (events & POLLIN) {
             assert(sizeof(buffer) > to_send);
-            int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send);
+            int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send,
+                                      &ws_flags);
 
             if (size < 0) {
                 if (errno == EIO) {
@@ -254,7 +256,7 @@ handle_client(int new_sock)
         }
 
         if (events & POLLOUT) {
-            int size = websocket_write(ws, buffer, to_send);
+            int size = websocket_write(ws, buffer, to_send, ws_flags);
 
             if (size < 0) {
                 switch (errno) {
diff --git a/server/websocket.c b/server/websocket.c
index 514e72c9..e076645d 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -313,12 +313,14 @@ static void relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
     }
 }
 
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size, unsigned *flags)
 {
     int n = 0;
     int rc;
     websocket_frame_t *frame = &ws->read_frame;
 
+    *flags = 0;
+
     if (ws->closed || ws->close_pending) {
         /* this avoids infinite loop in the case connection is still open and we have
          * pending data */
@@ -355,13 +357,15 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
             websocket_clear_frame(frame);
             send_pending_data(ws);
             return 0;
-        } else if (frame->type == BINARY_FRAME) {
+        } else if (frame->type == BINARY_FRAME || frame->type == TEXT_FRAME) {
             rc = ws->raw_read(ws->raw_stream, buf,
                               MIN(size, frame->expected_len - frame->relayed));
             if (rc <= 0) {
                 goto read_error;
             }
 
+            *flags = frame->type;
+
             relay_data(buf, rc, frame);
             n += rc;
             buf += rc;
@@ -406,6 +410,9 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
         frame->relayed += rc;
         if (frame->relayed >= frame->expected_len) {
             websocket_clear_frame(frame);
+            if (n) {
+                break;
+            }
         }
     }
 
@@ -421,12 +428,13 @@ read_error:
     return rc;
 }
 
-static int fill_header(uint8_t *header, uint64_t len)
+static int fill_header(uint8_t *header, uint64_t len, uint8_t type)
 {
     int used = 0;
     int i;
 
-    header[0] = FIN_FLAG | BINARY_FRAME;
+    type &= TYPE_MASK;
+    header[0] = FIN_FLAG | (type ? type : BINARY_FRAME);
     used++;
 
     header[1] = 0;
@@ -497,14 +505,14 @@ static int send_data_header_left(RedsWebSocket *ws)
     return -1;
 }
 
-static int send_data_header(RedsWebSocket *ws, uint64_t len)
+static int send_data_header(RedsWebSocket *ws, uint64_t len, uint8_t type)
 {
     spice_assert(ws->write_header_pos >= ws->write_header_len);
     spice_assert(ws->write_remainder == 0);
 
     /* fill a new header */
     ws->write_header_pos = 0;
-    ws->write_header_len = fill_header(ws->write_header, len);
+    ws->write_header_len = fill_header(ws->write_header, len, type);
 
     return send_data_header_left(ws);
 }
@@ -557,7 +565,7 @@ static int send_pending_data(RedsWebSocket *ws)
 }
 
 /* Write a WebSocket frame with the enclosed data out. */
-int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt, unsigned flags)
 {
     uint64_t len;
     int rc;
@@ -595,7 +603,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
     }
 
     ws->write_header_pos = 0;
-    ws->write_header_len = fill_header(ws->write_header, len);
+    ws->write_header_len = fill_header(ws->write_header, len, flags);
     iov_out[0].iov_len = ws->write_header_len;
     iov_out[0].iov_base = ws->write_header;
     rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
@@ -622,7 +630,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
     return rc;
 }
 
-int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len, unsigned flags)
 {
     int rc;
 
@@ -636,7 +644,7 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
         return rc;
     }
     if (ws->write_remainder == 0) {
-        rc = send_data_header(ws, len);
+        rc = send_data_header(ws, len, flags);
         if (rc <= 0) {
             return rc;
         }
diff --git a/server/websocket.h b/server/websocket.h
index b586e303..22120d93 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -21,9 +21,23 @@ typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int io
 
 typedef struct RedsWebSocket RedsWebSocket;
 
+enum {
+    WEBSOCKET_TEXT = 1,
+    WEBSOCKET_BINARY= 2,
+    WEBSOCKET_FINAL = 0x80,
+    WEBSOCKET_TEXT_FINAL = WEBSOCKET_TEXT | WEBSOCKET_FINAL,
+    WEBSOCKET_BINARY_FINAL = WEBSOCKET_BINARY | WEBSOCKET_FINAL,
+};
+
 RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocket_read_cb_t read_cb,
                              websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb);
 void websocket_free(RedsWebSocket *ws);
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
-int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
-int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
+
+/**
+ * Read data from websocket.
+ * Can return 0 in case client sent an empty text/binary data, check
+ * flags to detect this.
+ */
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len, unsigned *flags);
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len, unsigned flags);
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt, unsigned flags);
commit 0c7f91511d979e94e0daa978a760dc98e4917bec
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Sat Jun 22 18:14:56 2019 +0100

    websocket: Do not require "Sec-WebSocket-Protocol" header
    
    Not strictly needed, client can work even without specifying
    that.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index cb222c69..514e72c9 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -668,37 +668,41 @@ static int websocket_ack_close(RedsWebSocket *ws)
     return rc;
 }
 
-static bool websocket_is_start(char *buf)
+static bool websocket_is_start(char *buf, bool *p_has_prototol)
 {
-    const char *protocol = find_str(buf, "\nSec-WebSocket-Protocol:");
+    *p_has_prototol = false;
     const char *key = find_str(buf, "\nSec-WebSocket-Key:");
 
     if (strncmp(buf, "GET ", 4) != 0 ||
-            protocol == NULL || key == NULL ||
+            key == NULL ||
             !g_str_has_suffix(buf, "\r\n\r\n")) {
         return false;
     }
 
-    /* check protocol value ignoring spaces before and after */
-    int binary_pos = -1;
-    sscanf(protocol, " binary %n", &binary_pos);
-    if (binary_pos <= 0) {
-        return false;
+    const char *protocol = find_str(buf, "\nSec-WebSocket-Protocol:");
+    if (protocol) {
+        *p_has_prototol = true;
+        /* check protocol value ignoring spaces before and after */
+        int binary_pos = -1;
+        sscanf(protocol, " binary %n", &binary_pos);
+        if (binary_pos <= 0) {
+            return false;
+        }
     }
 
     return true;
 }
 
-static void websocket_create_reply(char *buf, char *outbuf)
+static void websocket_create_reply(char *buf, char *outbuf, bool has_protocol)
 {
     char *key;
 
     key = generate_reply_key(buf);
     sprintf(outbuf, "HTTP/1.1 101 Switching Protocols\r\n"
-                    "Upgrade: websocket\r\n"
+                    "Upgrade: WebSocket\r\n"
                     "Connection: Upgrade\r\n"
-                    "Sec-WebSocket-Accept: %s\r\n"
-                    "Sec-WebSocket-Protocol: binary\r\n\r\n", key);
+                    "Sec-WebSocket-Accept: %s\r\n%s\r\n", key,
+                    has_protocol ? "Sec-WebSocket-Protocol: binary\r\n": "");
     g_free(key);
 }
 
@@ -731,13 +735,14 @@ RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocke
               so it seems wisest to live with this theoretical flaw.
     */
 
-    if (!websocket_is_start(rbuf)) {
+    bool has_protocol;
+    if (!websocket_is_start(rbuf, &has_protocol)) {
         return NULL;
     }
 
     char outbuf[1024];
 
-    websocket_create_reply(rbuf, outbuf);
+    websocket_create_reply(rbuf, outbuf, has_protocol);
     rc = write_cb(stream, outbuf, strlen(outbuf));
     if (rc != strlen(outbuf)) {
         return NULL;
commit 975d444f2101b28de95e27498b3707c5fb6ef533
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Fri Jan 27 18:45:40 2017 +0000

    test-websocket: Write a test helper to make possible to run Autobahn testsuite
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/tests/.gitignore b/server/tests/.gitignore
index 81b604bc..36e978d4 100644
--- a/server/tests/.gitignore
+++ b/server/tests/.gitignore
@@ -28,5 +28,6 @@ test-gst
 test-leaks
 test-sasl
 test-record
+test-websocket
 /test-*.log
 /test-*.trs
diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
index c50826e6..26aadd5f 100644
--- a/server/tests/Makefile.am
+++ b/server/tests/Makefile.am
@@ -83,6 +83,12 @@ noinst_PROGRAMS =				\
 	$(check_PROGRAMS)			\
 	$(NULL)
 
+if !OS_WIN32
+noinst_PROGRAMS += \
+	test-websocket \
+	$(NULL)
+endif
+
 TESTS = $(check_PROGRAMS)			\
 	$(NULL)
 
diff --git a/server/tests/meson.build b/server/tests/meson.build
index b4269c58..b6cf8989 100644
--- a/server/tests/meson.build
+++ b/server/tests/meson.build
@@ -67,6 +67,7 @@ if host_machine.system() != 'windows'
   tests += [
     ['test-stream', true],
     ['test-stat-file', true],
+    ['test-websocket', false],
   ]
 endif
 
diff --git a/server/tests/test-websocket.c b/server/tests/test-websocket.c
new file mode 100644
index 00000000..6596c27e
--- /dev/null
+++ b/server/tests/test-websocket.c
@@ -0,0 +1,290 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2017-2019 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+/* Utility to allow checking our websocket implementaion using Autobahn
+ * Test Suite.
+ * This suite require a WebSocket server implementation echoing
+ * data sent to it
+ */
+#undef NDEBUG
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <unistd.h>
+#include <err.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <glib.h>
+#include <signal.h>
+
+#include "websocket.h"
+
+/*
+on data arrived on socket:
+  try to read data, read again till error, handle error, on EAGAIN polling again
+  queue readed data for echo
+
+on data writable (if we have data to write):
+  write data
+
+question... pings are handled ??
+
+if data size == 0 when we receive we must send it
+
+*/
+
+static int port = 7777;
+static gboolean non_blocking = false;
+static gboolean debug = false;
+static volatile bool got_term = false;
+static unsigned int num_connections = 0;
+
+static GOptionEntry cmd_entries[] = {
+  {"port", 'p', 0, G_OPTION_ARG_INT, &port,
+   "Local port to bind to", NULL},
+  {"non-blocking", 'n', 0, G_OPTION_ARG_NONE, &non_blocking,
+   "Enable non-blocking i/o", NULL},
+  {"debug", 0, 0, G_OPTION_ARG_NONE, &debug,
+   "Enable debug output", NULL},
+  {NULL}
+};
+
+static void handle_client(int new_sock);
+
+static void
+set_nonblocking(int sock)
+{
+        unsigned int ioctl_nonblocking = 1;
+
+        if (ioctl(sock, FIONBIO, &ioctl_nonblocking) < 0) {
+            err(1, "ioctl");
+        }
+}
+
+static int
+wait_for(int sock, short events)
+{
+    struct pollfd fds[1] = { { sock, events, 0 } };
+    for (;;) {
+        switch (poll(fds, 1, -1)) {
+        case -1:
+            if (errno == EINTR) {
+                if (got_term) {
+                    printf("handled %u connections\n", num_connections);
+                    exit(0);
+                }
+                break;
+            }
+            err(1, "poll");
+            break;
+        case 1:
+            if ((fds->revents & events) != 0) {
+                return fds->revents & events;
+            }
+            break;
+        case 0:
+            assert(0);
+        }
+    }
+}
+
+static ssize_t
+ws_read(void *opaque, void *buf, size_t nbyte)
+{
+    int sock = GPOINTER_TO_INT(opaque);
+    return recv(sock, buf, nbyte, MSG_NOSIGNAL);
+}
+
+static ssize_t
+ws_write(void *opaque, const void *buf, size_t nbyte)
+{
+    int sock = GPOINTER_TO_INT(opaque);
+    return send(sock, buf, nbyte, MSG_NOSIGNAL);
+}
+
+static ssize_t
+ws_writev(void *opaque, struct iovec *iov, int iovcnt)
+{
+    int sock = GPOINTER_TO_INT(opaque);
+    return writev(sock, iov, iovcnt);
+}
+
+static void
+go_out(int sig)
+{
+    got_term = true;
+}
+
+int
+main(int argc, char **argv)
+{
+    GError *error = NULL;
+    GOptionContext *context;
+
+    context = g_option_context_new(" - Websocket test");
+    g_option_context_add_main_entries(context, cmd_entries, NULL);
+    if (!g_option_context_parse(context, &argc, &argv, &error)) {
+        errx(1, "%s: %s\n", argv[0], error->message);
+    }
+
+    int sock = socket(AF_INET, SOCK_STREAM, 0);
+    if (sock < 0) {
+        err(1, "socket");
+    }
+
+    int enable = 1;
+    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
+
+    if (non_blocking) {
+        set_nonblocking(sock);
+    }
+
+    struct sockaddr_in sin;
+    memset(&sin, 0, sizeof(sin));
+    sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+    sin.sin_port = htons((short) port);
+    sin.sin_family = AF_INET;
+
+    if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
+        err(1, "bind");
+    }
+
+    if (listen(sock, 5) < 0) {
+        err(1, "listen");
+    }
+
+    signal(SIGTERM, go_out);
+    signal(SIGINT, go_out);
+
+    while (!got_term) {
+        wait_for(sock, POLLIN);
+
+        socklen_t sock_len = sizeof(sin);
+        int new_sock = accept(sock, (struct sockaddr *) &sin, &sock_len);
+        if (got_term) {
+            break;
+        }
+        if (new_sock < 0) {
+            err(1, "accept");
+        }
+
+        ++num_connections;
+        handle_client(new_sock);
+
+        close(new_sock);
+    }
+
+    close(sock);
+    printf("handled %u connections\n", num_connections);
+    return 0;
+}
+
+static void
+handle_client(int new_sock)
+{
+    if (non_blocking) {
+        set_nonblocking(new_sock);
+    }
+
+    int enable = 1;
+    setsockopt(new_sock, SOL_TCP, TCP_NODELAY, (const void *) &enable, sizeof(enable));
+
+    // wait header
+    wait_for(new_sock, POLLIN);
+
+    RedsWebSocket *ws = websocket_new("", 0, GINT_TO_POINTER(new_sock),
+                                      ws_read, ws_write, ws_writev);
+    assert(ws);
+
+    char buffer[4096];
+    size_t to_send = 0;
+    while (!got_term) {
+        int events = 0;
+        if (sizeof(buffer) > to_send) {
+            events |= POLLIN;
+        }
+        if (to_send) {
+            events |= POLLOUT;
+        }
+        events = wait_for(new_sock, events);
+        if (events & POLLIN) {
+            assert(sizeof(buffer) > to_send);
+            int size = websocket_read(ws, (void *) (buffer + to_send), sizeof(buffer) - to_send);
+
+            if (size < 0) {
+                if (errno == EIO) {
+                    break;
+                }
+                if (errno == EAGAIN) {
+                    continue;
+                }
+                err(1, "recv");
+            }
+
+            if (size == 0) {
+                break;
+            }
+
+            if (debug) {
+                printf("received %d bytes of data\n", size);
+            }
+            to_send += size;
+        }
+
+        if (events & POLLOUT) {
+            int size = websocket_write(ws, buffer, to_send);
+
+            if (size < 0) {
+                switch (errno) {
+                case EAGAIN:
+                case EINTR:
+                    continue;
+                case ECONNRESET:
+                    break;
+                default:
+                    err(1, "send");
+                }
+                break;
+            }
+
+            if (debug) {
+                printf("sent %d bytes of data\n", size);
+            }
+
+            if (size == 0) {
+                errx(1, "Unexpected short write\n");
+            }
+
+            to_send -= size;
+            memmove(buffer, buffer + size, to_send);
+        }
+    }
+
+    websocket_free(ws);
+
+    if (debug) {
+        printf("connection closed\n");
+    }
+}
commit 62582df789c581708bff5a63ba3c1b95813dbfda
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Wed Feb 1 17:20:07 2017 +0000

    websocket: Handle PING and PONG frames
    
    Websocket implementations are required to implement such messages.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 72a4b064..cb222c69 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -67,6 +67,21 @@
 
 #define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
 
+#define MAX_CONTROL_DATA 125
+#define CONTROL_HDR_LEN 2
+
+typedef struct {
+    uint8_t raw_pos;
+    union {
+        uint8_t raw_data[MAX_CONTROL_DATA + CONTROL_HDR_LEN];
+        struct {
+            uint8_t type;
+            uint8_t data_len;
+            uint8_t data[MAX_CONTROL_DATA];
+        };
+    };
+} WebSocketControl;
+
 typedef struct {
     uint8_t type;
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
@@ -86,6 +101,8 @@ struct RedsWebSocket {
     uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
     uint8_t write_header_pos, write_header_len;
     bool close_pending;
+    WebSocketControl pong;
+    WebSocketControl pending_pong;
 
     void *raw_stream;
     websocket_read_cb_t raw_read;
@@ -96,6 +113,28 @@ struct RedsWebSocket {
 static int websocket_ack_close(RedsWebSocket *ws);
 static int send_pending_data(RedsWebSocket *ws);
 
+static inline int get_control_raw_len(const WebSocketControl *control)
+{
+    return control->data_len + CONTROL_HDR_LEN;
+}
+
+static inline void control_init(WebSocketControl *control, uint8_t type)
+{
+    control->raw_pos = CONTROL_HDR_LEN;
+    control->type = type;
+    control->data_len = 0;
+}
+
+static inline bool control_sent(const WebSocketControl *control)
+{
+    return control->raw_pos >= get_control_raw_len(control);
+}
+
+static inline void pong_init(WebSocketControl *pong)
+{
+    control_init(pong, FIN_FLAG | PONG_FRAME);
+}
+
 /* 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 */
@@ -264,18 +303,14 @@ static bool websocket_get_frame_header(websocket_frame_t *frame)
     return true;
 }
 
-static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
+static void relay_data(uint8_t* 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, uint8_t *buf, size_t size)
@@ -307,7 +342,15 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
                 errno = EIO;
                 return -1;
             }
-        } else if (frame->type == CLOSE_FRAME) {
+            /* 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;
+        }
+
+        if (frame->type == CLOSE_FRAME) {
             ws->close_pending = true;
             websocket_clear_frame(frame);
             send_pending_data(ws);
@@ -319,18 +362,50 @@ int websocket_read(RedsWebSocket *ws, uint8_t *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 = 0;
+            if (ws->pong.data_len > (ws->pong.raw_pos - CONTROL_HDR_LEN)) {
+                rc = ws->raw_read(ws->raw_stream, ws->pong.raw_data + ws->pong.raw_pos,
+                                  ws->pong.data_len - (ws->pong.raw_pos - CONTROL_HDR_LEN));
+                if (rc <= 0) {
+                    goto read_error;
+                }
+                relay_data(ws->pong.raw_data + ws->pong.raw_pos, rc, frame);
+                ws->pong.raw_pos += rc;
+            }
+            if (ws->pong.raw_pos - CONTROL_HDR_LEN >= ws->pong.data_len) {
+                ws->pong.raw_pos = 0;
+                if (control_sent(&ws->pending_pong)) {
+                    ws->pending_pong = ws->pong;
+                    pong_init(&ws->pong);
+                }
+                send_pending_data(ws);
             }
         } else {
-            /* TODO - We don't handle PING at this point */
-            spice_warning("Unexpected WebSocket frame.type %d.  Failure now likely.", frame->type);
+            /* client could sent a PONG just as heartbeat */
+            if (frame->type != PONG_FRAME) {
+                spice_warning("Unexpected WebSocket frame.type %d.  Failure now likely.", frame->type);
+            }
+
+            // discard this data
+            uint8_t discard[128];
+            rc = 0;
+            if (frame->expected_len > frame->relayed) {
+                rc = ws->raw_read(ws->raw_stream, discard,
+                                  MIN(sizeof(discard), frame->expected_len - frame->relayed));
+                if (rc <= 0) {
+                    goto read_error;
+                }
+            }
+        }
+        frame->relayed += rc;
+        if (frame->relayed >= frame->expected_len) {
             websocket_clear_frame(frame);
-            continue;
         }
     }
 
@@ -459,6 +534,25 @@ static int send_pending_data(RedsWebSocket *ws)
             return rc;
         }
     }
+
+    WebSocketControl *pong = &ws->pending_pong;
+    if (!control_sent(pong)) {
+        rc = ws->raw_write(ws->raw_stream, pong->raw_data + pong->raw_pos,
+                           get_control_raw_len(pong) - pong->raw_pos);
+        if (rc <= 0) {
+            return rc;
+        }
+        pong->raw_pos += rc;
+        if (!control_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;
 }
 
@@ -656,6 +750,9 @@ RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocke
     ws->raw_write = write_cb;
     ws->raw_writev = writev_cb;
 
+    pong_init(&ws->pong);
+    pong_init(&ws->pending_pong);
+
     return ws;
 }
 
commit 5a352c182304faa9aed42b491489bc13c9318bc8
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:42:43 2016 +0000

    websocket: Avoids to write close frame in the middle of data
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 9fd5fde5..72a4b064 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -85,6 +85,7 @@ struct RedsWebSocket {
     uint64_t write_remainder;
     uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
     uint8_t write_header_pos, write_header_len;
+    bool close_pending;
 
     void *raw_stream;
     websocket_read_cb_t raw_read;
@@ -92,7 +93,8 @@ struct RedsWebSocket {
     websocket_writev_cb_t raw_writev;
 };
 
-static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb);
+static int websocket_ack_close(RedsWebSocket *ws);
+static int send_pending_data(RedsWebSocket *ws);
 
 /* Perform a case insensitive search for needle in haystack.
    If found, return a pointer to the byte after the end of needle.
@@ -282,7 +284,11 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
     int rc;
     websocket_frame_t *frame = &ws->read_frame;
 
-    if (ws->closed) {
+    if (ws->closed || ws->close_pending) {
+        /* this avoids infinite loop in the case connection is still open and we have
+         * pending data */
+        uint8_t discard[128];
+        ws->raw_read(ws->raw_stream, discard, sizeof(discard));
         return 0;
     }
 
@@ -302,9 +308,9 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
                 return -1;
             }
         } else if (frame->type == CLOSE_FRAME) {
-            websocket_ack_close(ws->raw_stream, ws->raw_write);
+            ws->close_pending = true;
             websocket_clear_frame(frame);
-            ws->closed = true;
+            send_pending_data(ws);
             return 0;
         } else if (frame->type == BINARY_FRAME) {
             rc = ws->raw_read(ws->raw_stream, buf,
@@ -418,15 +424,44 @@ static int send_data_header_left(RedsWebSocket *ws)
 
 static int send_data_header(RedsWebSocket *ws, uint64_t len)
 {
-    /* if we don't have a pending header fill it */
-    if (ws->write_header_pos >= ws->write_header_len) {
-        ws->write_header_pos = 0;
-        ws->write_header_len = fill_header(ws->write_header, len);
-    }
+    spice_assert(ws->write_header_pos >= ws->write_header_len);
+    spice_assert(ws->write_remainder == 0);
+
+    /* fill a new header */
+    ws->write_header_pos = 0;
+    ws->write_header_len = fill_header(ws->write_header, len);
 
     return send_data_header_left(ws);
 }
 
+static int send_pending_data(RedsWebSocket *ws)
+{
+    int rc;
+
+    /* don't send while we are sending a data frame */
+    if (ws->write_remainder) {
+        return 1;
+    }
+
+    /* write pending data frame header not send completely */
+    if (ws->write_header_pos < ws->write_header_len) {
+        rc = send_data_header_left(ws);
+        if (rc <= 0) {
+            return rc;
+        }
+        return 1;
+    }
+
+    /* write close frame */
+    if (ws->close_pending) {
+        rc = websocket_ack_close(ws);
+        if (rc <= 0) {
+            return rc;
+        }
+    }
+    return 1;
+}
+
 /* Write a WebSocket frame with the enclosed data out. */
 int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
 {
@@ -440,11 +475,9 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
         errno = EPIPE;
         return -1;
     }
-    if (ws->write_header_pos < ws->write_header_len) {
-        rc = send_data_header_left(ws);
-        if (rc <= 0) {
-            return rc;
-        }
+    rc = send_pending_data(ws);
+    if (rc <= 0) {
+        return rc;
     }
     if (ws->write_remainder > 0) {
         constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, ws->write_remainder);
@@ -504,6 +537,10 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
         return -1;
     }
 
+    rc = send_pending_data(ws);
+    if (rc <= 0) {
+        return rc;
+    }
     if (ws->write_remainder == 0) {
         rc = send_data_header(ws, len);
         if (rc <= 0) {
@@ -521,14 +558,20 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
     return rc;
 }
 
-static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb)
+static int websocket_ack_close(RedsWebSocket *ws)
 {
     unsigned char header[2];
+    int rc;
 
     header[0] = FIN_FLAG | CLOSE_FRAME;
     header[1] = 0;
 
-    write_cb(stream, header, sizeof(header));
+    rc = ws->raw_write(ws->raw_stream, header, sizeof(header));
+    if (rc == sizeof(header)) {
+        ws->close_pending = false;
+        ws->closed = true;
+    }
+    return rc;
 }
 
 static bool websocket_is_start(char *buf)
commit b5a622df76af06155bb3152b336cb013f2ec97f8
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:42:09 2016 +0000

    websocket: Handle case if server cannot write the header entirely
    
    Quite rare case, can only happen with congestion, buffers very low
    and some space left in the former packet.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 96c6fce1..9fd5fde5 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -83,6 +83,8 @@ struct RedsWebSocket {
 
     websocket_frame_t read_frame;
     uint64_t write_remainder;
+    uint8_t write_header[WEBSOCKET_MAX_HEADER_SIZE];
+    uint8_t write_header_pos, write_header_len;
 
     void *raw_stream;
     websocket_read_cb_t raw_read;
@@ -391,22 +393,59 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
     *iov_out = iov;
 }
 
+static int send_data_header_left(RedsWebSocket *ws)
+{
+    /* send the pending header */
+    /* this can be tested capping the length with MIN with a small size like 3 */
+    int rc = ws->raw_write(ws->raw_stream, ws->write_header + ws->write_header_pos,
+                           ws->write_header_len - ws->write_header_pos);
+    if (rc <= 0) {
+        return rc;
+    }
+    ws->write_header_pos += rc;
+
+    /* if header was sent now we can send data */
+    if (ws->write_header_pos >= ws->write_header_len) {
+        int used = 1;
+        ws->write_remainder = extract_length(ws->write_header + used, &used);
+        return ws->write_header_len;
+    }
+
+    /* otherwise try to send the rest later */
+    errno = EAGAIN;
+    return -1;
+}
+
+static int send_data_header(RedsWebSocket *ws, uint64_t len)
+{
+    /* if we don't have a pending header fill it */
+    if (ws->write_header_pos >= ws->write_header_len) {
+        ws->write_header_pos = 0;
+        ws->write_header_len = fill_header(ws->write_header, len);
+    }
+
+    return send_data_header_left(ws);
+}
 
 /* Write a WebSocket frame with the enclosed data out. */
 int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
 {
-    uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     uint64_t len;
-    int rc = -1;
+    int rc;
     struct iovec *iov_out;
     int iov_out_cnt;
     int i;
-    int header_len;
 
     if (ws->closed) {
         errno = EPIPE;
         return -1;
     }
+    if (ws->write_header_pos < ws->write_header_len) {
+        rc = send_data_header_left(ws);
+        if (rc <= 0) {
+            return rc;
+        }
+    }
     if (ws->write_remainder > 0) {
         constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, ws->write_remainder);
         rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
@@ -428,23 +467,25 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
         iov_out[i + 1] = iov[i];
     }
 
-    memset(header, 0, sizeof(header));
-    header_len = fill_header(header, len);
-    iov_out[0].iov_len = header_len;
-    iov_out[0].iov_base = header;
+    ws->write_header_pos = 0;
+    ws->write_header_len = fill_header(ws->write_header, len);
+    iov_out[0].iov_len = ws->write_header_len;
+    iov_out[0].iov_base = ws->write_header;
     rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
     g_free(iov_out);
     if (rc <= 0) {
+        ws->write_header_len = 0;
         return rc;
     }
-    rc -= header_len;
 
-    /* TODO this in theory can happen if we can't write the header */
-    if (SPICE_UNLIKELY(rc < 0)) {
-        ws->closed = true;
-        errno = EPIPE;
+    /* this can happen if we can't write the header */
+    if (SPICE_UNLIKELY(rc < ws->write_header_len)) {
+        ws->write_header_pos = ws->write_header_len - rc;
+        errno = EAGAIN;
         return -1;
     }
+    ws->write_header_pos = ws->write_header_len;
+    rc -= ws->write_header_len;
 
     /* Key point:  if we did not write out all the data, remember how
        much more data the client is expecting, and write that data without
@@ -456,9 +497,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
 
 int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
 {
-    uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     int rc;
-    int header_len;
 
     if (ws->closed) {
         errno = EPIPE;
@@ -466,18 +505,11 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
     }
 
     if (ws->write_remainder == 0) {
-        header_len = fill_header(header, len);
-        rc = ws->raw_write(ws->raw_stream, header, header_len);
+        rc = send_data_header(ws, len);
         if (rc <= 0) {
             return rc;
         }
-        if (rc != header_len) {
-            /* TODO - In theory, we can handle this case.  In practice,
-                      it does not occur, and does not seem to be worth
-                      the code complexity */
-            errno = EPIPE;
-            return -1;
-        }
+        len = ws->write_remainder;
     } else {
         len = MIN(ws->write_remainder, len);
     }
commit 2ba66599c1404a16e998de83d0c88d867c69838d
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:41:56 2016 +0000

    websocket: Support correctly protocol values
    
    Ignore spaces before "binary" value.
    HTTP allows space before and after the value although usually
    browsers implementation start the value with a single ASCII space.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 6f1581f9..96c6fce1 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -501,15 +501,23 @@ static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb)
 
 static bool websocket_is_start(char *buf)
 {
-    if (strncmp(buf, "GET ", 4) == 0 &&
-            // TODO strip, do not assume a single space
-            find_str(buf, "\nSec-WebSocket-Protocol: binary") &&
-            find_str(buf, "\nSec-WebSocket-Key:") &&
-            g_str_has_suffix(buf, "\r\n\r\n")) {
-        return true;
+    const char *protocol = find_str(buf, "\nSec-WebSocket-Protocol:");
+    const char *key = find_str(buf, "\nSec-WebSocket-Key:");
+
+    if (strncmp(buf, "GET ", 4) != 0 ||
+            protocol == NULL || key == NULL ||
+            !g_str_has_suffix(buf, "\r\n\r\n")) {
+        return false;
     }
 
-    return false;
+    /* check protocol value ignoring spaces before and after */
+    int binary_pos = -1;
+    sscanf(protocol, " binary %n", &binary_pos);
+    if (binary_pos <= 0) {
+        return false;
+    }
+
+    return true;
 }
 
 static void websocket_create_reply(char *buf, char *outbuf)
commit 39a791a964a7b4553f7cb0235380c55af13c3f8a
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:41:52 2016 +0000

    websocket: Avoid possible server crash using websockets
    
    Currently code don't handle if system can't sent the
    header in a single write command.
    Don't cause abort but just close the connection.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index dda71f76..6f1581f9 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -439,7 +439,12 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
     }
     rc -= header_len;
 
-    spice_assert(rc >= 0);
+    /* TODO this in theory can happen if we can't write the header */
+    if (SPICE_UNLIKELY(rc < 0)) {
+        ws->closed = true;
+        errno = EPIPE;
+        return -1;
+    }
 
     /* Key point:  if we did not write out all the data, remember how
        much more data the client is expecting, and write that data without
commit 0c2f386bebdf894ed4fc2ea46f9a9c0055e6d63b
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Sat Jun 22 08:45:45 2019 +0100

    websocket: Fix updating remaining bytes to write in websocket_write
    
    "len" is not always the full remainder (consider the case when
    we are writing a partial frame).
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index afb7502a..dda71f76 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -478,10 +478,8 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
     }
 
     rc = ws->raw_write(ws->raw_stream, buf, len);
-    if (rc <= 0) {
-        ws->write_remainder = len;
-    } else {
-        ws->write_remainder = len - rc;
+    if (rc > 0) {
+        ws->write_remainder -= rc;
     }
     return rc;
 }
commit 9f1417c11fed4c3811bbf861fcf2d6552dc81429
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:41:33 2016 +0000

    websocket: Propagate some variable
    
    These were introduced moving code around.
    No more reason to copy, just use directly structure fields.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 38496fb5..afb7502a 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -90,7 +90,7 @@ struct RedsWebSocket {
     websocket_writev_cb_t raw_writev;
 };
 
-static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
+static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb);
 
 /* Perform a case insensitive search for needle in haystack.
    If found, return a pointer to the byte after the end of needle.
@@ -279,9 +279,6 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
     int n = 0;
     int rc;
     websocket_frame_t *frame = &ws->read_frame;
-    void *opaque = ws->raw_stream;
-    websocket_read_cb_t read_cb = (websocket_read_cb_t) ws->raw_read;
-    websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
 
     if (ws->closed) {
         return 0;
@@ -290,7 +287,8 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
     while (size > 0) {
         // make sure we have a proper frame ready
         if (!frame->frame_ready) {
-            rc = read_cb(ws->raw_stream, frame->header + frame->header_pos, frame_bytes_needed(frame));
+            rc = ws->raw_read(ws->raw_stream, frame->header + frame->header_pos,
+                              frame_bytes_needed(frame));
             if (rc <= 0) {
                 goto read_error;
             }
@@ -302,12 +300,13 @@ int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
                 return -1;
             }
         } else if (frame->type == CLOSE_FRAME) {
-            websocket_ack_close(opaque, write_cb);
+            websocket_ack_close(ws->raw_stream, ws->raw_write);
             websocket_clear_frame(frame);
             ws->closed = true;
             return 0;
         } else if (frame->type == BINARY_FRAME) {
-            rc = read_cb(opaque, buf, MIN(size, frame->expected_len - frame->relayed));
+            rc = ws->raw_read(ws->raw_stream, buf,
+                              MIN(size, frame->expected_len - frame->relayed));
             if (rc <= 0) {
                 goto read_error;
             }
@@ -403,24 +402,21 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
     int iov_out_cnt;
     int i;
     int header_len;
-    void *opaque = ws->raw_stream;
-    websocket_writev_cb_t writev_cb = (websocket_writev_cb_t) ws->raw_writev;
-    uint64_t *remainder = &ws->write_remainder;
 
     if (ws->closed) {
         errno = EPIPE;
         return -1;
     }
-    if (*remainder > 0) {
-        constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
-        rc = writev_cb(opaque, iov_out, iov_out_cnt);
+    if (ws->write_remainder > 0) {
+        constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, ws->write_remainder);
+        rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
         if (iov_out != iov) {
             g_free(iov_out);
         }
         if (rc <= 0) {
             return rc;
         }
-        *remainder -= rc;
+        ws->write_remainder -= rc;
         return rc;
     }
 
@@ -436,7 +432,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
     header_len = fill_header(header, len);
     iov_out[0].iov_len = header_len;
     iov_out[0].iov_base = header;
-    rc = writev_cb(opaque, iov_out, iov_out_cnt);
+    rc = ws->raw_writev(ws->raw_stream, iov_out, iov_out_cnt);
     g_free(iov_out);
     if (rc <= 0) {
         return rc;
@@ -448,7 +444,7 @@ int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
     /* Key point:  if we did not write out all the data, remember how
        much more data the client is expecting, and write that data without
        a header of any kind the next time around */
-    *remainder = len - rc;
+    ws->write_remainder = len - rc;
 
     return rc;
 }
@@ -458,18 +454,15 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     int rc;
     int header_len;
-    void *opaque = ws->raw_stream;
-    websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
-    uint64_t *remainder = &ws->write_remainder;
 
     if (ws->closed) {
         errno = EPIPE;
         return -1;
     }
 
-    if (*remainder == 0) {
+    if (ws->write_remainder == 0) {
         header_len = fill_header(header, len);
-        rc = write_cb(opaque, header, header_len);
+        rc = ws->raw_write(ws->raw_stream, header, header_len);
         if (rc <= 0) {
             return rc;
         }
@@ -481,26 +474,26 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
             return -1;
         }
     } else {
-        len = MIN(*remainder, len);
+        len = MIN(ws->write_remainder, len);
     }
 
-    rc = write_cb(opaque, buf, len);
+    rc = ws->raw_write(ws->raw_stream, buf, len);
     if (rc <= 0) {
-        *remainder = len;
+        ws->write_remainder = len;
     } else {
-        *remainder = len - rc;
+        ws->write_remainder = len - rc;
     }
     return rc;
 }
 
-static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
+static void websocket_ack_close(void *stream, websocket_write_cb_t write_cb)
 {
     unsigned char header[2];
 
     header[0] = FIN_FLAG | CLOSE_FRAME;
     header[1] = 0;
 
-    write_cb(opaque, header, sizeof(header));
+    write_cb(stream, header, sizeof(header));
 }
 
 static bool websocket_is_start(char *buf)
commit 6b41f3b5421d603a5b586c9f57673c160414afe8
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:41:12 2016 +0000

    websocket: Better variable types
    
    "type" is just 8 bit.
    "frame_ready" and "masked" as booleans.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 9a940037..38496fb5 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -68,11 +68,11 @@
 #define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
 
 typedef struct {
-    int type;
-    int masked;
+    uint8_t type;
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     int header_pos;
-    int frame_ready:1;
+    bool frame_ready:1;
+    bool masked:1;
     uint8_t mask[4];
     uint64_t relayed;
     uint64_t expected_len;
commit 007e009bc3b9354d4f693e1abf6b43faf83939da
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Wed Feb 1 17:19:57 2017 +0000

    websocket: Detect and handle some header error
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 01bcef82..9a940037 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -46,7 +46,9 @@
 /* Constants / masks all from RFC 6455 */
 
 #define FIN_FLAG        0x80
+#define RSV_MASK        0x70
 #define TYPE_MASK       0x0F
+#define CONTROL_FRAME_MASK 0x8
 
 #define CONTINUATION_FRAME  0x0
 #define TEXT_FRAME      0x1
@@ -206,21 +208,33 @@ 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 bool 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;
     frame->type = frame->header[0] & TYPE_MASK;
     used++;
 
-    frame->masked = frame->header[1] & MASK_FLAG;
+    // reserved bits are not expected
+    if (frame->header[0] & RSV_MASK) {
+        return false;
+    }
+    // control commands cannot be split
+    if (!fin && (frame->type & CONTROL_FRAME_MASK) != 0) {
+        return false;
+    }
+    if ((frame->type & ~CONTROL_FRAME_MASK) >= 3) {
+        return false;
+    }
+
+    frame->masked = !!(frame->header[1] & MASK_FLAG);
 
     /* This is a Spice specific optimization.  We don't really
        care about assembling frames fully, so we treat
@@ -235,8 +249,15 @@ static void websocket_get_frame_header(websocket_frame_t *frame)
         memcpy(frame->mask, frame->header + used, 4);
     }
 
+    /* control frames cannot have more than 125 bytes of data */
+    if ((frame->type & CONTROL_FRAME_MASK) != 0 &&
+        frame->expected_len >= LENGTH_16BIT) {
+        return false;
+    }
+
     frame->relayed = 0;
-    frame->frame_ready = 1;
+    frame->frame_ready = true;
+    return true;
 }
 
 static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
@@ -275,7 +296,11 @@ int websocket_read(RedsWebSocket *ws, uint8_t *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;
+            }
         } else if (frame->type == CLOSE_FRAME) {
             websocket_ack_close(opaque, write_cb);
             websocket_clear_frame(frame);
commit 55df5fa8417fd4c0e25188802be31838d4829c08
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:40:31 2016 +0000

    websocket: Better encapsulation
    
    Move websocket structure declarations to C file.
    Make some functions static as now not used externally.
    Introduce a websocket_free function for symmetry.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/red-stream.c b/server/red-stream.c
index fd3fdced..1e0103b0 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -436,7 +436,7 @@ void red_stream_free(RedStream *s)
         SSL_free(s->priv->ssl);
     }
 
-    g_free(s->priv->ws);
+    websocket_free(s->priv->ws);
 
     red_stream_remove_watch(s);
     socket_close(s->socket);
@@ -1184,38 +1184,11 @@ static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, in
 */
 bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
 {
-    char rbuf[4096];
-    int rc;
-
     if (stream->priv->ws) {
         return false;
     }
 
-    memcpy(rbuf, buf, len);
-    rc = stream->priv->read(stream, rbuf + len, sizeof(rbuf) - len - 1);
-    if (rc <= 0) {
-        return false;
-    }
-    len += rc;
-    rbuf[len] = 0;
-
-    /* TODO:  this has a theoretical flaw around packet buffering
-              that is not likely to occur in practice.  That is,
-              to be fully correct, we should repeatedly read bytes until
-              either we get the end of the GET header (\r\n\r\n), or until
-              an amount of time has passed.  Instead, we just read for
-              16 bytes, and then read up to the sizeof rbuf.  So if the
-              GET request is only partially complete at this point we
-              will fail.
-
-              A typical GET request is 520 bytes, and it's difficult to
-              imagine a real world case where that will come in fragmented
-              such that we trigger this failure.  Further, the spice reds
-              code has no real mechanism to do variable length/time based reads,
-              so it seems wisest to live with this theoretical flaw.
-    */
-
-    stream->priv->ws = websocket_new(rbuf, stream,
+    stream->priv->ws = websocket_new(buf, len, stream,
                                      (websocket_read_cb_t) stream->priv->read,
                                      (websocket_write_cb_t) stream->priv->write,
                                      (websocket_writev_cb_t) stream->priv->writev);
diff --git a/server/websocket.c b/server/websocket.c
index 03d93102..01bcef82 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -63,6 +63,31 @@
 
 #define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
 
+#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
+
+typedef struct {
+    int type;
+    int masked;
+    uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+    int header_pos;
+    int frame_ready:1;
+    uint8_t mask[4];
+    uint64_t relayed;
+    uint64_t expected_len;
+} websocket_frame_t;
+
+struct RedsWebSocket {
+    bool closed;
+
+    websocket_frame_t read_frame;
+    uint64_t write_remainder;
+
+    void *raw_stream;
+    websocket_read_cb_t raw_read;
+    websocket_write_cb_t raw_write;
+    websocket_writev_cb_t raw_writev;
+};
+
 static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
 
 /* Perform a case insensitive search for needle in haystack.
@@ -453,7 +478,7 @@ static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
     write_cb(opaque, header, sizeof(header));
 }
 
-bool websocket_is_start(char *buf)
+static bool websocket_is_start(char *buf)
 {
     if (strncmp(buf, "GET ", 4) == 0 &&
             // TODO strip, do not assume a single space
@@ -466,7 +491,7 @@ bool websocket_is_start(char *buf)
     return false;
 }
 
-void websocket_create_reply(char *buf, char *outbuf)
+static void websocket_create_reply(char *buf, char *outbuf)
 {
     char *key;
 
@@ -479,9 +504,35 @@ void websocket_create_reply(char *buf, char *outbuf)
     g_free(key);
 }
 
-RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_read_cb_t read_cb,
+RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocket_read_cb_t read_cb,
                              websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb)
 {
+    char rbuf[4096];
+
+    memcpy(rbuf, buf, len);
+    int rc = read_cb(stream, rbuf + len, sizeof(rbuf) - len - 1);
+    if (rc <= 0) {
+        return NULL;
+    }
+    len += rc;
+    rbuf[len] = 0;
+
+    /* TODO:  this has a theoretical flaw around packet buffering
+              that is not likely to occur in practice.  That is,
+              to be fully correct, we should repeatedly read bytes until
+              either we get the end of the GET header (\r\n\r\n), or until
+              an amount of time has passed.  Instead, we just read for
+              16 bytes, and then read up to the sizeof rbuf.  So if the
+              GET request is only partially complete at this point we
+              will fail.
+
+              A typical GET request is 520 bytes, and it's difficult to
+              imagine a real world case where that will come in fragmented
+              such that we trigger this failure.  Further, the spice reds
+              code has no real mechanism to do variable length/time based reads,
+              so it seems wisest to live with this theoretical flaw.
+    */
+
     if (!websocket_is_start(rbuf)) {
         return NULL;
     }
@@ -489,7 +540,7 @@ RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_rea
     char outbuf[1024];
 
     websocket_create_reply(rbuf, outbuf);
-    int rc = write_cb(stream, outbuf, strlen(outbuf));
+    rc = write_cb(stream, outbuf, strlen(outbuf));
     if (rc != strlen(outbuf)) {
         return NULL;
     }
@@ -503,3 +554,8 @@ RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_rea
 
     return ws;
 }
+
+void websocket_free(RedsWebSocket *ws)
+{
+    g_free(ws);
+}
diff --git a/server/websocket.h b/server/websocket.h
index ab9eb812..b586e303 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -15,41 +15,15 @@
  *  License along with this library; if not, see <http://www.gnu.org/licenses/>.
  */
 
-#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
-
-struct RedStream;
-
-typedef struct {
-    int type;
-    int masked;
-    uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
-    int header_pos;
-    int frame_ready:1;
-    uint8_t mask[4];
-    uint64_t relayed;
-    uint64_t expected_len;
-} websocket_frame_t;
-
 typedef ssize_t (*websocket_read_cb_t)(void *opaque, void *buf, size_t nbyte);
 typedef ssize_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t nbyte);
 typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int iovcnt);
 
-typedef struct {
-    int closed;
-
-    websocket_frame_t read_frame;
-    uint64_t write_remainder;
-
-    struct RedStream *raw_stream;
-    websocket_read_cb_t raw_read;
-    websocket_write_cb_t raw_write;
-    websocket_writev_cb_t raw_writev;
-} RedsWebSocket;
+typedef struct RedsWebSocket RedsWebSocket;
 
-RedsWebSocket *websocket_new(char *buf, struct RedStream *s, websocket_read_cb_t read_cb,
+RedsWebSocket *websocket_new(const void *buf, size_t len, void *stream, websocket_read_cb_t read_cb,
                              websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb);
-bool websocket_is_start(char *buf);
-void websocket_create_reply(char *buf, char *outbuf);
+void websocket_free(RedsWebSocket *ws);
 int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
 int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
 int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
commit a5c05ad845a47739dd4daef8c31148dfc3a5438c
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:40:07 2016 +0000

    websocket: New interface to create RedsWebSocket
    
    Less coupling. This is a preparation for next patch.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/red-stream.c b/server/red-stream.c
index d6a00dd9..fd3fdced 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -1215,28 +1215,19 @@ bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
               so it seems wisest to live with this theoretical flaw.
     */
 
-    if (websocket_is_start(rbuf)) {
-        char outbuf[1024];
-
-        websocket_create_reply(rbuf, outbuf);
-        rc = stream->priv->write(stream, outbuf, strlen(outbuf));
-        if (rc == strlen(outbuf)) {
-            stream->priv->ws = g_malloc0(sizeof(*stream->priv->ws));
-
-            stream->priv->ws->raw_stream = stream;
-            stream->priv->ws->raw_read = stream->priv->read;
-            stream->priv->ws->raw_write = stream->priv->write;
-
-            stream->priv->read = stream_websocket_read;
-            stream->priv->write = stream_websocket_write;
-
-            if (stream->priv->writev) {
-                stream->priv->ws->raw_writev = stream->priv->writev;
-                stream->priv->writev = stream_websocket_writev;
-            }
+    stream->priv->ws = websocket_new(rbuf, stream,
+                                     (websocket_read_cb_t) stream->priv->read,
+                                     (websocket_write_cb_t) stream->priv->write,
+                                     (websocket_writev_cb_t) stream->priv->writev);
+    if (stream->priv->ws) {
+        stream->priv->read = stream_websocket_read;
+        stream->priv->write = stream_websocket_write;
 
-            return true;
+        if (stream->priv->writev) {
+            stream->priv->writev = stream_websocket_writev;
         }
+
+        return true;
     }
 
     return false;
diff --git a/server/websocket.c b/server/websocket.c
index 1b18aa61..03d93102 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -478,3 +478,28 @@ void websocket_create_reply(char *buf, char *outbuf)
                     "Sec-WebSocket-Protocol: binary\r\n\r\n", key);
     g_free(key);
 }
+
+RedsWebSocket *websocket_new(char *rbuf, struct RedStream *stream, websocket_read_cb_t read_cb,
+                             websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb)
+{
+    if (!websocket_is_start(rbuf)) {
+        return NULL;
+    }
+
+    char outbuf[1024];
+
+    websocket_create_reply(rbuf, outbuf);
+    int rc = write_cb(stream, outbuf, strlen(outbuf));
+    if (rc != strlen(outbuf)) {
+        return NULL;
+    }
+
+    RedsWebSocket *ws = g_new0(RedsWebSocket, 1);
+
+    ws->raw_stream = stream;
+    ws->raw_read = read_cb;
+    ws->raw_write = write_cb;
+    ws->raw_writev = writev_cb;
+
+    return ws;
+}
diff --git a/server/websocket.h b/server/websocket.h
index 17310905..ab9eb812 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -41,11 +41,13 @@ typedef struct {
     uint64_t write_remainder;
 
     struct RedStream *raw_stream;
-    ssize_t (*raw_read)(struct RedStream *s, void *buf, size_t nbyte);
-    ssize_t (*raw_write)(struct RedStream *s, const void *buf, size_t nbyte);
-    ssize_t (*raw_writev)(struct RedStream *s, const struct iovec *iov, int iovcnt);
+    websocket_read_cb_t raw_read;
+    websocket_write_cb_t raw_write;
+    websocket_writev_cb_t raw_writev;
 } RedsWebSocket;
 
+RedsWebSocket *websocket_new(char *buf, struct RedStream *s, websocket_read_cb_t read_cb,
+                             websocket_write_cb_t write_cb, websocket_writev_cb_t writev_cb);
 bool websocket_is_start(char *buf);
 void websocket_create_reply(char *buf, char *outbuf);
 int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
commit b5f3ed1502ac048b2ccb441ee5c7011768fa76fe
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Sat Jun 22 17:54:30 2019 +0100

    websocket: Make websocket_ack_close static
    
    It's used only in websocket.c.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index b0ea867e..1b18aa61 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -63,6 +63,8 @@
 
 #define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
 
+static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
+
 /* 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 */
@@ -441,7 +443,7 @@ int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
     return rc;
 }
 
-void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
+static void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
 {
     unsigned char header[2];
 
diff --git a/server/websocket.h b/server/websocket.h
index e3a61000..17310905 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -48,7 +48,6 @@ typedef struct {
 
 bool websocket_is_start(char *buf);
 void websocket_create_reply(char *buf, char *outbuf);
-void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
 int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
 int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
 int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
commit bbfb472154288ec69efed380fecb352356793fcc
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:39:54 2016 +0000

    websocket: Make websocket function more ABI compatibles with RedStream
    
    Use same argument types as red_stream_* functions.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 145d829f..b0ea867e 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -226,7 +226,7 @@ static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
     return n;
 }
 
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, int size)
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t size)
 {
     int n = 0;
     int rc;
@@ -342,7 +342,7 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
 
 
 /* Write a WebSocket frame with the enclosed data out. */
-int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt)
 {
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     uint64_t len;
@@ -360,7 +360,7 @@ int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
         return -1;
     }
     if (*remainder > 0) {
-        constrain_iov(iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
+        constrain_iov((struct iovec *) iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
         rc = writev_cb(opaque, iov_out, iov_out_cnt);
         if (iov_out != iov) {
             g_free(iov_out);
@@ -401,7 +401,7 @@ int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
     return rc;
 }
 
-int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len)
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len)
 {
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     int rc;
diff --git a/server/websocket.h b/server/websocket.h
index f65b4e3d..e3a61000 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -48,7 +48,7 @@ typedef struct {
 
 bool websocket_is_start(char *buf);
 void websocket_create_reply(char *buf, char *outbuf);
-int websocket_read(RedsWebSocket *ws, uint8_t *buf, int len);
-int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len);
-int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt);
 void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, size_t len);
+int websocket_write(RedsWebSocket *ws, const void *buf, size_t len);
+int websocket_writev(RedsWebSocket *ws, const struct iovec *iov, int iovcnt);
commit 98c183a6f6907e679749acb899454779e6f7b8a7
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:39:46 2016 +0000

    websocket: Move RedsWebSocket to header
    
    Intention is to make private in websockets.c and reduce
    changes in red-stream.c
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/red-stream.c b/server/red-stream.c
index 4b899241..d6a00dd9 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -78,17 +78,6 @@ typedef struct RedSASL {
 } RedSASL;
 #endif
 
-typedef struct {
-    int closed;
-
-    websocket_frame_t read_frame;
-    uint64_t write_remainder;
-
-    ssize_t (*raw_read)(RedStream *s, void *buf, size_t nbyte);
-    ssize_t (*raw_write)(RedStream *s, const void *buf, size_t nbyte);
-    ssize_t (*raw_writev)(RedStream *s, const struct iovec *iov, int iovcnt);
-} RedsWebSocket;
-
 struct RedStreamPrivate {
     SSL *ssl;
 
@@ -1174,39 +1163,17 @@ error:
 
 static ssize_t stream_websocket_read(RedStream *s, void *buf, size_t size)
 {
-    int rc;
-
-    if (s->priv->ws->closed)
-        return 0;
-
-    rc = websocket_read((void *)s, buf, size, &s->priv->ws->read_frame,
-        (websocket_read_cb_t) s->priv->ws->raw_read,
-        (websocket_write_cb_t) s->priv->ws->raw_write);
-
-    if (rc == 0)
-        s->priv->ws->closed = 1;
-
-    return rc;
+    return websocket_read(s->priv->ws, buf, size);
 }
 
 static ssize_t stream_websocket_write(RedStream *s, const void *buf, size_t size)
 {
-    if (s->priv->ws->closed) {
-        errno = EPIPE;
-        return -1;
-    }
-    return websocket_write((void *)s, buf, size, &s->priv->ws->write_remainder,
-        (websocket_write_cb_t) s->priv->ws->raw_write);
+    return websocket_write(s->priv->ws, buf, size);
 }
 
 static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, int iovcnt)
 {
-    if (s->priv->ws->closed) {
-        errno = EPIPE;
-        return -1;
-    }
-    return websocket_writev((void *)s, (struct iovec *) iov, iovcnt, &s->priv->ws->write_remainder,
-        (websocket_writev_cb_t) s->priv->ws->raw_writev);
+    return websocket_writev(s->priv->ws, (struct iovec *) iov, iovcnt);
 }
 
 /*
@@ -1256,6 +1223,7 @@ bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
         if (rc == strlen(outbuf)) {
             stream->priv->ws = g_malloc0(sizeof(*stream->priv->ws));
 
+            stream->priv->ws->raw_stream = stream;
             stream->priv->ws->raw_read = stream->priv->read;
             stream->priv->ws->raw_write = stream->priv->write;
 
diff --git a/server/websocket.c b/server/websocket.c
index fc1d82c2..145d829f 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -226,17 +226,23 @@ static int relay_data(uint8_t* buf, size_t size, websocket_frame_t *frame)
     return n;
 }
 
-int websocket_read(void *opaque, uint8_t *buf, int size, websocket_frame_t *frame,
-                    websocket_read_cb_t read_cb,
-                    websocket_write_cb_t write_cb)
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, int size)
 {
     int n = 0;
     int rc;
+    websocket_frame_t *frame = &ws->read_frame;
+    void *opaque = ws->raw_stream;
+    websocket_read_cb_t read_cb = (websocket_read_cb_t) ws->raw_read;
+    websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
+
+    if (ws->closed) {
+        return 0;
+    }
 
     while (size > 0) {
         // make sure we have a proper frame ready
         if (!frame->frame_ready) {
-            rc = read_cb(opaque, frame->header + frame->header_pos, frame_bytes_needed(frame));
+            rc = read_cb(ws->raw_stream, frame->header + frame->header_pos, frame_bytes_needed(frame));
             if (rc <= 0) {
                 goto read_error;
             }
@@ -246,6 +252,7 @@ int websocket_read(void *opaque, uint8_t *buf, int size, websocket_frame_t *fram
         } else if (frame->type == CLOSE_FRAME) {
             websocket_ack_close(opaque, write_cb);
             websocket_clear_frame(frame);
+            ws->closed = true;
             return 0;
         } else if (frame->type == BINARY_FRAME) {
             rc = read_cb(opaque, buf, MIN(size, frame->expected_len - frame->relayed));
@@ -274,6 +281,9 @@ read_error:
     if (n > 0 && rc == -1 && (errno == EINTR || errno == EAGAIN)) {
         return n;
     }
+    if (rc == 0) {
+        ws->closed = true;
+    }
     return rc;
 }
 
@@ -332,8 +342,7 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
 
 
 /* Write a WebSocket frame with the enclosed data out. */
-int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
-         websocket_writev_cb_t writev_cb)
+int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt)
 {
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     uint64_t len;
@@ -342,7 +351,14 @@ int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *rema
     int iov_out_cnt;
     int i;
     int header_len;
+    void *opaque = ws->raw_stream;
+    websocket_writev_cb_t writev_cb = (websocket_writev_cb_t) ws->raw_writev;
+    uint64_t *remainder = &ws->write_remainder;
 
+    if (ws->closed) {
+        errno = EPIPE;
+        return -1;
+    }
     if (*remainder > 0) {
         constrain_iov(iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
         rc = writev_cb(opaque, iov_out, iov_out_cnt);
@@ -385,12 +401,19 @@ int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *rema
     return rc;
 }
 
-int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
-         websocket_write_cb_t write_cb)
+int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len)
 {
     uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
     int rc;
     int header_len;
+    void *opaque = ws->raw_stream;
+    websocket_write_cb_t write_cb = (websocket_write_cb_t) ws->raw_write;
+    uint64_t *remainder = &ws->write_remainder;
+
+    if (ws->closed) {
+        errno = EPIPE;
+        return -1;
+    }
 
     if (*remainder == 0) {
         header_len = fill_header(header, len);
diff --git a/server/websocket.h b/server/websocket.h
index 63d7b10c..f65b4e3d 100644
--- a/server/websocket.h
+++ b/server/websocket.h
@@ -17,6 +17,8 @@
 
 #define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
 
+struct RedStream;
+
 typedef struct {
     int type;
     int masked;
@@ -32,13 +34,21 @@ typedef ssize_t (*websocket_read_cb_t)(void *opaque, void *buf, size_t nbyte);
 typedef ssize_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t nbyte);
 typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int iovcnt);
 
+typedef struct {
+    int closed;
+
+    websocket_frame_t read_frame;
+    uint64_t write_remainder;
+
+    struct RedStream *raw_stream;
+    ssize_t (*raw_read)(struct RedStream *s, void *buf, size_t nbyte);
+    ssize_t (*raw_write)(struct RedStream *s, const void *buf, size_t nbyte);
+    ssize_t (*raw_writev)(struct RedStream *s, const struct iovec *iov, int iovcnt);
+} RedsWebSocket;
+
 bool websocket_is_start(char *buf);
 void websocket_create_reply(char *buf, char *outbuf);
-int websocket_read(void *opaque, uint8_t *buf, int len, websocket_frame_t *frame,
-         websocket_read_cb_t read_cb,
-         websocket_write_cb_t write_cb);
-int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
-         websocket_write_cb_t write_cb);
-int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
-         websocket_writev_cb_t writev_cb);
+int websocket_read(RedsWebSocket *ws, uint8_t *buf, int len);
+int websocket_write(RedsWebSocket *ws, const uint8_t *buf, int len);
+int websocket_writev(RedsWebSocket *ws, struct iovec *iov, int iovcnt);
 void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
commit 26fad876a08b4343aa77e197932b0c5735c67655
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Nov 21 12:42:17 2016 +0000

    websocket: Simplify and fix constrain_iov
    
    Use g_memdup instead of manual copy.
    Trim the original iov if necessary.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/websocket.c b/server/websocket.c
index 58f36da0..fc1d82c2 100644
--- a/server/websocket.c
+++ b/server/websocket.c
@@ -310,26 +310,24 @@ static void constrain_iov(struct iovec *iov, int iovcnt,
                           struct iovec **iov_out, int *iov_out_cnt,
                           uint64_t maxlen)
 {
-    int i, j;
-
-    *iov_out = iov;
-    *iov_out_cnt = iovcnt;
+    int i;
 
     for (i = 0; i < iovcnt && maxlen > 0; i++) {
         if (iov[i].iov_len > maxlen) {
             /* TODO - This code has never triggered afaik... */
-            *iov_out_cnt = i + 1;
-            *iov_out = g_malloc((*iov_out_cnt) * sizeof (**iov_out));
-            for (j = 0; j < i; j++) {
-                (*iov_out)[j].iov_base = iov[j].iov_base;
-                (*iov_out)[j].iov_len = iov[j].iov_len;
-            }
-            (*iov_out)[j].iov_base = iov[j].iov_base;
-            (*iov_out)[j].iov_len = maxlen;
-            break;
+            *iov_out_cnt = ++i;
+            *iov_out = g_memdup(iov, i * sizeof (*iov));
+            (*iov_out)[i-1].iov_len = maxlen;
+            return;
         }
         maxlen -= iov[i].iov_len;
     }
+
+    /* we must trim the iov in case maxlen initially matches some chunks
+     * For instance if initially we had 2 chunks 256 and 128 bytes respectively
+     * and a maxlen of 256 we should just return the first chunk */
+    *iov_out_cnt = i;
+    *iov_out = iov;
 }
 
 
commit cd2a31709626def76d65e70b5187a7dda1498618
Author: Jeremy White <jwhite at codeweavers.com>
Date:   Tue Jun 28 09:26:28 2016 -0500

    Add support for clients connecting with the WebSocket protocol.
    
    We do this by auto detecting the inbound http(s) 'GET' and probing
    for a well formulated WebSocket binary connection, such as used
    by the spice-html5 client.  If detected, we implement a set of
    cover functions that abstract the read/write/writev functions,
    in a fashion similar to the SASL implementation.
    
    This includes a limited implementation of the WebSocket protocol,
    sufficient for our purposes.
    
    Signed-off-by: Jeremy White <jwhite at codeweavers.com>
    Acked-by: Frediano Ziglio <fziglio at redhat.com>

diff --git a/server/Makefile.am b/server/Makefile.am
index 20adf65f..71c7e173 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -177,6 +177,8 @@ libserver_la_SOURCES =				\
 	video-encoder.h				\
 	video-stream.c				\
 	video-stream.h				\
+	websocket.c				\
+	websocket.h				\
 	zlib-encoder.c				\
 	zlib-encoder.h				\
 	$(NULL)
diff --git a/server/meson.build b/server/meson.build
index a7676f7e..395811c8 100644
--- a/server/meson.build
+++ b/server/meson.build
@@ -144,6 +144,8 @@ spice_server_sources = [
   'video-encoder.h',
   'video-stream.c',
   'video-stream.h',
+  'websocket.c',
+  'websocket.h',
   'zlib-encoder.c',
   'zlib-encoder.h',
 ]
diff --git a/server/red-stream.c b/server/red-stream.c
index 77fed097..4b899241 100644
--- a/server/red-stream.c
+++ b/server/red-stream.c
@@ -39,6 +39,7 @@
 #include "red-common.h"
 #include "red-stream.h"
 #include "reds.h"
+#include "websocket.h"
 
 // compatibility for *BSD systems
 #if !defined(TCP_CORK) && !defined(_WIN32)
@@ -77,6 +78,17 @@ typedef struct RedSASL {
 } RedSASL;
 #endif
 
+typedef struct {
+    int closed;
+
+    websocket_frame_t read_frame;
+    uint64_t write_remainder;
+
+    ssize_t (*raw_read)(RedStream *s, void *buf, size_t nbyte);
+    ssize_t (*raw_write)(RedStream *s, const void *buf, size_t nbyte);
+    ssize_t (*raw_writev)(RedStream *s, const struct iovec *iov, int iovcnt);
+} RedsWebSocket;
+
 struct RedStreamPrivate {
     SSL *ssl;
 
@@ -86,6 +98,8 @@ struct RedStreamPrivate {
 
     AsyncRead async_read;
 
+    RedsWebSocket *ws;
+
     /* life time of info:
      * allocated when creating RedStream.
      * deallocated when main_dispatcher handles the SPICE_CHANNEL_EVENT_DISCONNECTED
@@ -433,6 +447,8 @@ void red_stream_free(RedStream *s)
         SSL_free(s->priv->ssl);
     }
 
+    g_free(s->priv->ws);
+
     red_stream_remove_watch(s);
     socket_close(s->socket);
 
@@ -1155,3 +1171,105 @@ error:
     return false;
 }
 #endif
+
+static ssize_t stream_websocket_read(RedStream *s, void *buf, size_t size)
+{
+    int rc;
+
+    if (s->priv->ws->closed)
+        return 0;
+
+    rc = websocket_read((void *)s, buf, size, &s->priv->ws->read_frame,
+        (websocket_read_cb_t) s->priv->ws->raw_read,
+        (websocket_write_cb_t) s->priv->ws->raw_write);
+
+    if (rc == 0)
+        s->priv->ws->closed = 1;
+
+    return rc;
+}
+
+static ssize_t stream_websocket_write(RedStream *s, const void *buf, size_t size)
+{
+    if (s->priv->ws->closed) {
+        errno = EPIPE;
+        return -1;
+    }
+    return websocket_write((void *)s, buf, size, &s->priv->ws->write_remainder,
+        (websocket_write_cb_t) s->priv->ws->raw_write);
+}
+
+static ssize_t stream_websocket_writev(RedStream *s, const struct iovec *iov, int iovcnt)
+{
+    if (s->priv->ws->closed) {
+        errno = EPIPE;
+        return -1;
+    }
+    return websocket_writev((void *)s, (struct iovec *) iov, iovcnt, &s->priv->ws->write_remainder,
+        (websocket_writev_cb_t) s->priv->ws->raw_writev);
+}
+
+/*
+    If we detect that a newly opened stream appears to be using
+    the WebSocket protocol, we will put in place cover functions
+    that will speak WebSocket to the client, but allow the server
+    to continue to use normal stream read/write/writev semantics.
+*/
+bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len)
+{
+    char rbuf[4096];
+    int rc;
+
+    if (stream->priv->ws) {
+        return false;
+    }
+
+    memcpy(rbuf, buf, len);
+    rc = stream->priv->read(stream, rbuf + len, sizeof(rbuf) - len - 1);
+    if (rc <= 0) {
+        return false;
+    }
+    len += rc;
+    rbuf[len] = 0;
+
+    /* TODO:  this has a theoretical flaw around packet buffering
+              that is not likely to occur in practice.  That is,
+              to be fully correct, we should repeatedly read bytes until
+              either we get the end of the GET header (\r\n\r\n), or until
+              an amount of time has passed.  Instead, we just read for
+              16 bytes, and then read up to the sizeof rbuf.  So if the
+              GET request is only partially complete at this point we
+              will fail.
+
+              A typical GET request is 520 bytes, and it's difficult to
+              imagine a real world case where that will come in fragmented
+              such that we trigger this failure.  Further, the spice reds
+              code has no real mechanism to do variable length/time based reads,
+              so it seems wisest to live with this theoretical flaw.
+    */
+
+    if (websocket_is_start(rbuf)) {
+        char outbuf[1024];
+
+        websocket_create_reply(rbuf, outbuf);
+        rc = stream->priv->write(stream, outbuf, strlen(outbuf));
+        if (rc == strlen(outbuf)) {
+            stream->priv->ws = g_malloc0(sizeof(*stream->priv->ws));
+
+            stream->priv->ws->raw_read = stream->priv->read;
+            stream->priv->ws->raw_write = stream->priv->write;
+
+            stream->priv->read = stream_websocket_read;
+            stream->priv->write = stream_websocket_write;
+
+            if (stream->priv->writev) {
+                stream->priv->ws->raw_writev = stream->priv->writev;
+                stream->priv->writev = stream_websocket_writev;
+            }
+
+            return true;
+        }
+    }
+
+    return false;
+}
diff --git a/server/red-stream.h b/server/red-stream.h
index ca6dc71a..a191dd42 100644
--- a/server/red-stream.h
+++ b/server/red-stream.h
@@ -91,6 +91,8 @@ bool red_stream_set_auto_flush(RedStream *stream, bool auto_flush);
  */
 void red_stream_flush(RedStream *stream);
 
+bool red_stream_is_websocket(RedStream *stream, const void *buf, size_t len);
+
 typedef enum {
     RED_SASL_ERROR_OK,
     RED_SASL_ERROR_GENERIC,
diff --git a/server/reds.c b/server/reds.c
index b4061fbc..671e0a86 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -2418,6 +2418,7 @@ static void reds_handle_link_error(void *opaque, int err)
     reds_link_free(link);
 }
 
+static void reds_handle_new_link(RedLinkInfo *link);
 static void reds_handle_read_header_done(void *opaque)
 {
     RedLinkInfo *link = (RedLinkInfo *)opaque;
@@ -2460,6 +2461,18 @@ static void reds_handle_read_magic_done(void *opaque)
     const SpiceLinkHeader *header = &link->link_header;
 
     if (header->magic != SPICE_MAGIC) {
+        /* Attempt to detect and support a WebSocket connection,
+           which will be proceeded by a variable length GET style request.
+           We cannot know we are dealing with a WebSocket connection
+           until we have read at least 3 bytes, and we will have to
+           read many more bytes than are contained in a SpiceLinkHeader.
+           So we may as well read a SpiceLinkHeader's worth of data, and if it's
+           clear that a WebSocket connection was requested, we switch
+           before proceeding further. */
+        if (red_stream_is_websocket(link->stream, &header->magic, sizeof(header->magic))) {
+            reds_handle_new_link(link);
+            return;
+        }
         reds_send_link_error(link, SPICE_LINK_ERR_INVALID_MAGIC);
         reds_link_free(link);
         return;
diff --git a/server/websocket.c b/server/websocket.c
new file mode 100644
index 00000000..58f36da0
--- /dev/null
+++ b/server/websocket.c
@@ -0,0 +1,457 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2015 Jeremy White
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#define _GNU_SOURCE
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#ifndef _WIN32
+#include <sys/socket.h>
+#include <unistd.h>
+#endif
+
+#include <glib.h>
+
+#include <common/log.h>
+#include <common/mem.h>
+
+#include "sys-socket.h"
+#include "websocket.h"
+
+#ifdef _WIN32
+#include <shlwapi.h>
+#define strcasestr(haystack, needle) StrStrIA(haystack, needle)
+#endif
+
+/* Constants / masks all from RFC 6455 */
+
+#define FIN_FLAG        0x80
+#define TYPE_MASK       0x0F
+
+#define CONTINUATION_FRAME  0x0
+#define TEXT_FRAME      0x1
+#define BINARY_FRAME    0x2
+#define CLOSE_FRAME     0x8
+#define PING_FRAME      0x9
+#define PONG_FRAME      0xA
+
+#define LENGTH_MASK     0x7F
+#define LENGTH_16BIT    0x7E
+#define LENGTH_64BIT    0x7F
+
+#define MASK_FLAG       0x80
+
+#define WEBSOCKET_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+/* 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 */
+static const char *find_str(const char *haystack, const char *needle)
+{
+    const char *s = strcasestr(haystack, needle);
+
+    if (s) {
+        return s + strlen(needle);
+    }
+    return NULL;
+}
+
+/* Extract WebSocket style length. Returns 0 if not enough data present,
+   Always updates the output 'used' variable to the number of bytes
+   required to extract the length; useful for tracking where the
+   mask will be.
+*/
+static uint64_t extract_length(const uint8_t *buf, int *used)
+{
+    int i;
+    uint64_t outlen = (*buf++) & LENGTH_MASK;
+
+    (*used)++;
+
+    switch (outlen) {
+    case LENGTH_64BIT:
+        *used += 8;
+        outlen = 0;
+        for (i = 56; i >= 0; i -= 8) {
+            outlen |= (*buf++) << i;
+        }
+        break;
+
+    case LENGTH_16BIT:
+        *used += 2;
+        outlen = ((*buf) << 8) | *(buf + 1);
+        break;
+
+    default:
+        break;
+    }
+    return outlen;
+}
+
+static int frame_bytes_needed(websocket_frame_t *frame)
+{
+    int needed = 2;
+    if (frame->header_pos < needed) {
+        return needed - frame->header_pos;
+    }
+
+    switch (frame->header[1] & LENGTH_MASK) {
+    case LENGTH_64BIT:
+        needed += 8;
+        break;
+    case LENGTH_16BIT:
+        needed += 2;
+        break;
+    }
+
+    if (frame->header[1] & MASK_FLAG) {
+        needed += 4;
+    }
+
+    return needed - frame->header_pos;
+}
+
+/*
+* Generate WebSocket style response key, based on the
+*  original key sent to us
+* If non null, caller must free returned key string.
+*/
+static char *generate_reply_key(char *buf)
+{
+    GChecksum *checksum;
+    char *b64 = NULL;
+    uint8_t *sha1;
+    size_t sha1_size;
+    const char *key;
+    const char *p;
+    char *k;
+
+    key = find_str(buf, "\nSec-WebSocket-Key:");
+    if (key) {
+        p = strchr(key, '\r');
+        if (p) {
+            k = g_strndup(key, p - key);
+            k = g_strstrip(k);
+            checksum = g_checksum_new(G_CHECKSUM_SHA1);
+            g_checksum_update(checksum, (uint8_t *) k, strlen(k));
+            g_checksum_update(checksum, (uint8_t *) WEBSOCKET_GUID, strlen(WEBSOCKET_GUID));
+            g_free(k);
+
+            sha1_size = g_checksum_type_get_length(G_CHECKSUM_SHA1);
+            sha1 = g_malloc(sha1_size);
+
+            g_checksum_get_digest(checksum, sha1, &sha1_size);
+
+            b64 = g_base64_encode(sha1, sha1_size);
+
+            g_checksum_free(checksum);
+            g_free(sha1);
+        }
+    }
+
+    return b64;
+}
+
+
+static void websocket_clear_frame(websocket_frame_t *frame)
+{
+    memset(frame, 0, sizeof(*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)
+{
+    int fin;
+    int used = 0;
+
+    if (frame_bytes_needed(frame) > 0) {
+        return;
+    }
+
+    fin = frame->header[0] & FIN_FLAG;
+    frame->type = frame->header[0] & TYPE_MASK;
+    used++;
+
+    frame->masked = frame->header[1] & MASK_FLAG;
+
+    /* This is a Spice specific optimization.  We don't really
+       care about assembling frames fully, so we treat
+       a frame in process as a finished frame and pass it along. */
+    if (!fin && frame->type == CONTINUATION_FRAME) {
+        frame->type = BINARY_FRAME;
+    }
+
+    frame->expected_len = extract_length(frame->header + used, &used);
+
+    if (frame->masked) {
+        memcpy(frame->mask, frame->header + used, 4);
+    }
+
+    frame->relayed = 0;
+    frame->frame_ready = 1;
+}
+
+static int relay_data(uint8_t* 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];
+        }
+    }
+
+    return n;
+}
+
+int websocket_read(void *opaque, uint8_t *buf, int size, websocket_frame_t *frame,
+                    websocket_read_cb_t read_cb,
+                    websocket_write_cb_t write_cb)
+{
+    int n = 0;
+    int rc;
+
+    while (size > 0) {
+        // make sure we have a proper frame ready
+        if (!frame->frame_ready) {
+            rc = read_cb(opaque, frame->header + frame->header_pos, frame_bytes_needed(frame));
+            if (rc <= 0) {
+                goto read_error;
+            }
+            frame->header_pos += rc;
+
+            websocket_get_frame_header(frame);
+        } else if (frame->type == CLOSE_FRAME) {
+            websocket_ack_close(opaque, write_cb);
+            websocket_clear_frame(frame);
+            return 0;
+        } else if (frame->type == BINARY_FRAME) {
+            rc = read_cb(opaque, buf, MIN(size, frame->expected_len - frame->relayed));
+            if (rc <= 0) {
+                goto read_error;
+            }
+
+            rc = relay_data(buf, rc, frame);
+            n += rc;
+            buf += rc;
+            size -= rc;
+            if (frame->relayed >= frame->expected_len) {
+                websocket_clear_frame(frame);
+            }
+        } else {
+            /* TODO - We don't handle PING at this point */
+            spice_warning("Unexpected WebSocket frame.type %d.  Failure now likely.", frame->type);
+            websocket_clear_frame(frame);
+            continue;
+        }
+    }
+
+    return n;
+
+read_error:
+    if (n > 0 && rc == -1 && (errno == EINTR || errno == EAGAIN)) {
+        return n;
+    }
+    return rc;
+}
+
+static int fill_header(uint8_t *header, uint64_t len)
+{
+    int used = 0;
+    int i;
+
+    header[0] = FIN_FLAG | BINARY_FRAME;
+    used++;
+
+    header[1] = 0;
+    used++;
+    if (len > 65535) {
+        header[1] |= LENGTH_64BIT;
+        for (i = 9; i >= 2; i--) {
+            header[i] = len & 0xFF;
+            len >>= 8;
+        }
+        used += 8;
+    } else if (len >= LENGTH_16BIT) {
+        header[1] |= LENGTH_16BIT;
+        header[2] = len >> 8;
+        header[3] = len & 0xFF;
+        used += 2;
+    } else {
+        header[1] |= len;
+    }
+
+    return used;
+}
+
+static void constrain_iov(struct iovec *iov, int iovcnt,
+                          struct iovec **iov_out, int *iov_out_cnt,
+                          uint64_t maxlen)
+{
+    int i, j;
+
+    *iov_out = iov;
+    *iov_out_cnt = iovcnt;
+
+    for (i = 0; i < iovcnt && maxlen > 0; i++) {
+        if (iov[i].iov_len > maxlen) {
+            /* TODO - This code has never triggered afaik... */
+            *iov_out_cnt = i + 1;
+            *iov_out = g_malloc((*iov_out_cnt) * sizeof (**iov_out));
+            for (j = 0; j < i; j++) {
+                (*iov_out)[j].iov_base = iov[j].iov_base;
+                (*iov_out)[j].iov_len = iov[j].iov_len;
+            }
+            (*iov_out)[j].iov_base = iov[j].iov_base;
+            (*iov_out)[j].iov_len = maxlen;
+            break;
+        }
+        maxlen -= iov[i].iov_len;
+    }
+}
+
+
+/* Write a WebSocket frame with the enclosed data out. */
+int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
+         websocket_writev_cb_t writev_cb)
+{
+    uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+    uint64_t len;
+    int rc = -1;
+    struct iovec *iov_out;
+    int iov_out_cnt;
+    int i;
+    int header_len;
+
+    if (*remainder > 0) {
+        constrain_iov(iov, iovcnt, &iov_out, &iov_out_cnt, *remainder);
+        rc = writev_cb(opaque, iov_out, iov_out_cnt);
+        if (iov_out != iov) {
+            g_free(iov_out);
+        }
+        if (rc <= 0) {
+            return rc;
+        }
+        *remainder -= rc;
+        return rc;
+    }
+
+    iov_out_cnt = iovcnt + 1;
+    iov_out = g_malloc(iov_out_cnt * sizeof(*iov_out));
+
+    for (i = 0, len = 0; i < iovcnt; i++) {
+        len += iov[i].iov_len;
+        iov_out[i + 1] = iov[i];
+    }
+
+    memset(header, 0, sizeof(header));
+    header_len = fill_header(header, len);
+    iov_out[0].iov_len = header_len;
+    iov_out[0].iov_base = header;
+    rc = writev_cb(opaque, iov_out, iov_out_cnt);
+    g_free(iov_out);
+    if (rc <= 0) {
+        return rc;
+    }
+    rc -= header_len;
+
+    spice_assert(rc >= 0);
+
+    /* Key point:  if we did not write out all the data, remember how
+       much more data the client is expecting, and write that data without
+       a header of any kind the next time around */
+    *remainder = len - rc;
+
+    return rc;
+}
+
+int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
+         websocket_write_cb_t write_cb)
+{
+    uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+    int rc;
+    int header_len;
+
+    if (*remainder == 0) {
+        header_len = fill_header(header, len);
+        rc = write_cb(opaque, header, header_len);
+        if (rc <= 0) {
+            return rc;
+        }
+        if (rc != header_len) {
+            /* TODO - In theory, we can handle this case.  In practice,
+                      it does not occur, and does not seem to be worth
+                      the code complexity */
+            errno = EPIPE;
+            return -1;
+        }
+    } else {
+        len = MIN(*remainder, len);
+    }
+
+    rc = write_cb(opaque, buf, len);
+    if (rc <= 0) {
+        *remainder = len;
+    } else {
+        *remainder = len - rc;
+    }
+    return rc;
+}
+
+void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb)
+{
+    unsigned char header[2];
+
+    header[0] = FIN_FLAG | CLOSE_FRAME;
+    header[1] = 0;
+
+    write_cb(opaque, header, sizeof(header));
+}
+
+bool websocket_is_start(char *buf)
+{
+    if (strncmp(buf, "GET ", 4) == 0 &&
+            // TODO strip, do not assume a single space
+            find_str(buf, "\nSec-WebSocket-Protocol: binary") &&
+            find_str(buf, "\nSec-WebSocket-Key:") &&
+            g_str_has_suffix(buf, "\r\n\r\n")) {
+        return true;
+    }
+
+    return false;
+}
+
+void websocket_create_reply(char *buf, char *outbuf)
+{
+    char *key;
+
+    key = generate_reply_key(buf);
+    sprintf(outbuf, "HTTP/1.1 101 Switching Protocols\r\n"
+                    "Upgrade: websocket\r\n"
+                    "Connection: Upgrade\r\n"
+                    "Sec-WebSocket-Accept: %s\r\n"
+                    "Sec-WebSocket-Protocol: binary\r\n\r\n", key);
+    g_free(key);
+}
diff --git a/server/websocket.h b/server/websocket.h
new file mode 100644
index 00000000..63d7b10c
--- /dev/null
+++ b/server/websocket.h
@@ -0,0 +1,44 @@
+/*
+ *  Copyright (C) 2015 Jeremy White
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
+
+typedef struct {
+    int type;
+    int masked;
+    uint8_t header[WEBSOCKET_MAX_HEADER_SIZE];
+    int header_pos;
+    int frame_ready:1;
+    uint8_t mask[4];
+    uint64_t relayed;
+    uint64_t expected_len;
+} websocket_frame_t;
+
+typedef ssize_t (*websocket_read_cb_t)(void *opaque, void *buf, size_t nbyte);
+typedef ssize_t (*websocket_write_cb_t)(void *opaque, const void *buf, size_t nbyte);
+typedef ssize_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov, int iovcnt);
+
+bool websocket_is_start(char *buf);
+void websocket_create_reply(char *buf, char *outbuf);
+int websocket_read(void *opaque, uint8_t *buf, int len, websocket_frame_t *frame,
+         websocket_read_cb_t read_cb,
+         websocket_write_cb_t write_cb);
+int websocket_write(void *opaque, const uint8_t *buf, int len, uint64_t *remainder,
+         websocket_write_cb_t write_cb);
+int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, uint64_t *remainder,
+         websocket_writev_cb_t writev_cb);
+void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
commit 214736dce643ce3ee257da017373e88cc19d2d3b
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Thu Jun 20 13:26:11 2019 +0100

    reds: Fix SSL_CTX_set_ecdh_auto call for some old OpenSSL
    
    SSL_CTX_set_ecdh_auto is not defined in some old versions of OpenSSL
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/configure.ac b/configure.ac
index e12d7e85..49c009d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -209,6 +209,15 @@ AC_SUBST(SSL_CFLAGS)
 AC_SUBST(SSL_LIBS)
 AS_VAR_APPEND([SPICE_REQUIRES], [" openssl"])
 
+save_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS $SSL_CFLAGS"
+AC_CHECK_DECLS([SSL_CTX_set_ecdh_auto], [], [], [
+AC_INCLUDES_DEFAULT
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+])
+CFLAGS="$save_CFLAGS"
+
 AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
     AC_MSG_CHECKING([for jpeglib.h])
     AC_TRY_CPP(
diff --git a/server/reds.c b/server/reds.c
index 792e9838..b4061fbc 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -2937,7 +2937,9 @@ static int reds_init_ssl(RedsState *reds)
     }
 
     SSL_CTX_set_options(reds->ctx, ssl_options);
+#if HAVE_DECL_SSL_CTX_SET_ECDH_AUTO || defined(SSL_CTX_set_ecdh_auto)
     SSL_CTX_set_ecdh_auto(reds->ctx, 1);
+#endif
 
     /* Load our keys and certificates*/
     return_code = SSL_CTX_use_certificate_chain_file(reds->ctx, reds->config->ssl_parameters.certs_file);
commit 89b0a07c72d23a42a0834cd8515efe726c759f57
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Thu Jun 20 12:38:32 2019 +0100

    test-glib-compat: Fix G_PID_FORMAT definition for old systems
    
    The G_PID_FORMAT constant is defined only if GLib does not support it.
    The constant was wrongly defined.
    Jessie Debian 32 shows this issue (printf format error).
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Jeremy White <jwhite at codeweavers.com>

diff --git a/server/tests/test-glib-compat.h b/server/tests/test-glib-compat.h
index eef07494..17ebcf48 100644
--- a/server/tests/test-glib-compat.h
+++ b/server/tests/test-glib-compat.h
@@ -70,7 +70,7 @@ g_test_assert_expected_messages_internal_no_warnings(const char *domain,
 
 /* Added in glib 2.50 */
 #ifndef G_PID_FORMAT
-#ifdef G_OS_WIN32
+#ifndef G_OS_WIN32
 #define G_PID_FORMAT "i"
 #else
 #define G_PID_FORMAT "p"


More information about the Spice-commits mailing list