[Spice-devel] [PATCH spice ] Add support for clients connecting with the WebSocket protocol.

Pavel Grunt pgrunt at redhat.com
Tue Nov 3 02:24:56 PST 2015


Hi Jeremy,

On Mon, 2015-11-02 at 15:20 -0600, Jeremy White wrote:
> Hi Pavel,
> 
> Thanks for the review.
> 
> > > 
> > > +static void reds_handle_new_link(RedLinkInfo *link);
> > >  static void reds_handle_read_header_done(void *opaque)
> > >  {
> > >      RedLinkInfo *link = (RedLinkInfo *)opaque;
> > > @@ -2182,6 +2183,11 @@ static void reds_handle_read_header_done(void
> > > *opaque)
> > >      header->size = GUINT32_FROM_LE(header->size);
> > >  
> > >      if (header->magic != SPICE_MAGIC) {
> > > +        if (reds_stream_is_websocket(link->stream,
> > > +            (guchar *) header, sizeof(*header))) {
I did not mentioned it, but please fix the indentation ^
> > > +            reds_handle_new_link(link);
> > > +            return;
> > > +        }
> > This looks hacky, it would be nice to detect that we are dealing with
> > websockets
> > before reds_handle_read_header_done() is called.
> 
> The problem is that we cannot know we're dealing with websockets until
> we've done an initial read.  And a websocket connection is going to have
> to write at least 16 bytes, so it seemed to me that we may as well read
> the 16 bytes in a SpiceLinkHeader.

ok, I think it deserves a comment in the code 
> 
> The alternate I considered was to inject a 3 byte read before
> read_header_done and then have that do an async read of
> sizeof(SpiceLinkHeader) - 3 if we do not have the 'GET' that signals a
> websocket connection.  That didn't seem any better to me; I'm happy to
> defer to others if they feel that would be superior (or if there is an
> altogether different approach I've missed).

I think your current approach is better.

> 
> In reviewing this code, there is a flaw I've skipped over.  That is, the
> current implementation makes the assumption that the websocket header
> will arrive in a single packet; that there is no chance we'll get a few
> bytes, and then need another read in order to get the remaining bytes.
> I think that, in practice, this will always be the case.  Further,
> afaik, we don't really have a mechanism to do a variable length/timed
> read in the current code base.  My instinct is to say that limitation is
> acceptable; that the complexity of a theoretically correct
> implementation would do more harm than accepting this assumption.  But I
> bring it up to give others the chance to correct me <grin>.
> 
You can mention the limitation in the commit message.
> [snip]
> > > +        websocket_create_reply(rbuf, len, outbuf);
> > > +        rc = stream->priv->write(stream, outbuf, strlen(outbuf));
> > > +        if (rc == strlen(outbuf)) {
> > > +            stream->priv->ws = spice_malloc0(sizeof(*stream->priv->ws));
> > 
> > It is not freed
> 
> Yes, right, will fix it.
> 
> > > +int websocket_is_start(gchar *buf, int len)
> > 
> > It sounds like boolean
> 
> Fair enough, I'll fix it.
> 
> > 
> > > +{
> > > +    if (len < 4)
> > > +        return 0;
> > > +
> > > +    if (find_str(buf, "GET", len) == (buf + 3) &&
> > > +            find_str(buf, "Sec-WebSocket-Protocol: binary", len) &&
> > > +            find_str(buf, "Sec-WebSocket-Key:", len) &&
> > > +            buf[len - 2] == '\r' && buf[len - 1] == '\n')
> > > +        return 1;
> > > +
> > > +    return 0;
> > > +}
> > > +
> > > +int websocket_create_reply(gchar *buf, int len, gchar *outbuf)
> > 
> > It always returns 0
> 
> Yep, should be a void, I'll fix it.
> 
> > 
> > > +{
> > > +    gchar *key;
> > > +
> > > +    key = generate_reply_key(buf, len);
> > 
> > Can NULL be valid value? Should we have a warning for it, or change the
> > return
> > value?
> 
> NULL is not a valid value; this code path should only be exercised if
> we've found the Sec-WebSocket-Key already.
> 
> > 
> > > +    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);
> > > +    return 0;
> > > +}
> > > diff --git a/server/websocket.h b/server/websocket.h
> > > new file mode 100644
> > > index 0000000..b35cb5e
> > > --- /dev/null
> > > +++ b/server/websocket.h
> > > @@ -0,0 +1,62 @@
> > > +/*
> > > + *  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/lice
> > > nses
> > > />.
> > > + */
> > > +
> > > +#define WEBSOCKET_MAX_HEADER_SIZE (1 + 9 + 4)
> > > +
> > > +#define FIN_FLAG        0x80
> > > +#define TYPE_MASK       0x0F
> > > +
> > > +#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"
> > > +
> > > +typedef struct
> > > +{
> > > +    int type;
> > > +    int masked;
> > > +    guint8 header[WEBSOCKET_MAX_HEADER_SIZE];
> > > +    int header_pos;
> > > +    int frame_ready:1;
> > > +    guint8 mask[4];
> > > +    guint64 relayed;
> > > +    guint64 expected_len;
> > > +} websocket_frame_t;
> > 
> > I would prefer to have these ^ definitions private.
> 
> I could probably use some guidance; there may be conventions I've
> missed.  It would be easy to move most of the websocket specific
> constants into websocket.c, I'm happy to do that.
thanks
> 
> It would be a bit more work to make the websocket_frame_t type be
> opaque; it would require an allocation and a free I'd find untidy. 

ok, it is not the expected standard. You can keep it as it is.

>  I'm
> willing to do the work if that's the expected standard; but I don't see
> the harm of having a non private WEBSOCKET_MAX_HEADER_SIZE and
> websocket_frame_t structure.

> > 
> > > +
> > > +typedef size_t (*websocket_writev_cb_t)(void *opaque, struct iovec *iov,
> > > int
> > > iovcnt);
> > > +typedef size_t (*websocket_write_cb_t)(void *opaque, const void *buf,
> > > size_t
> > > nbyte);
> > > +typedef size_t (*websocket_read_cb_t)(void *opaque, const void *buf,
> > > size_t
> > > nbyte);
> > 
> > RedsStreamPrivate has these ^ as ssize_t
> 
> Yes, right, I'll fix it.
> 
> > 
> > > +
> > > +int websocket_is_start(gchar *buf, int len);
> > > +int websocket_create_reply(gchar *buf, int len, gchar *outbuf);
> > > +int websocket_read(void *opaque, guchar *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 guchar *buf, int len, guint64
> > > *remainder,
> > > +         websocket_write_cb_t write_cb);
> > > +int websocket_writev(void *opaque, struct iovec *iov, int iovcnt, guint64
> > > *remainder,
> > > +         websocket_writev_cb_t writev_cb);
> > > +void websocket_ack_close(void *opaque, websocket_write_cb_t write_cb);
> > > +
> > extra line
> 
> Right, will fix.
> 
> Thanks again for the review.
> 
> Cheers,
> 
> Jeremy

Thanks,
Pavel



More information about the Spice-devel mailing list