diff --git a/README-test-server b/README-test-server index aeb5d217..9c8475f0 100644 --- a/README-test-server +++ b/README-test-server @@ -48,6 +48,9 @@ There are a couple of other possible configure options It needs some unixy environment that may choke in other build contexts, this lets you cleanly stop it being built + +--enable-x-google-mux Enable experimental x-google-mux support + in the build (see notes later in document) Testing server with a browser ----------------------------- @@ -246,5 +249,39 @@ LWS_CALLBACK_SET_MODE_POLL_FD and LWS_CALLBACK_CLEAR_MODE_POLL_FD appear in the callback for protocol 0 and allow interface code to manage socket descriptors in other poll loops. -2011-02-12 Andy Green + +x-google-mux support +-------------------- + +Experimental and super-preliminary x-google-mux support is available if +enabled in ./configure with --enable-x-google-mux. Note that when changing +configurations, you will need to do a make distclean before, then the new +configure and then make ; make install. Don't forget the necessary other +flags for your platform as described at the top of the readme. + +It has the following notes: + + 1) To enable it, reconfigure with --enable-x-google-mux + + 2) It conflicts with deflate-stream, use the -u switch on + the test client to disable deflate-stream + + 3) It deviates from the google standard by sending full + headers in the addchannel subcommand rather than just + changed ones from original connect + + 4) Quota is not implemented yet + + 5) Close of subchannel is not really implemented yet + + 6) Google opcode 0xf is changed to 0x7 to account for + v7 protocol changes to opcode layout + + However despite those caveats, in fact it can run the + test client reliably over one socket (both dumb-increment + and lws-mirror-protocol), you can open a browser on the + same test server too and see the circles, etc. + + +2011-05-23 Andy Green diff --git a/configure b/configure index 5470e9b5..063a6686 100755 --- a/configure +++ b/configure @@ -619,6 +619,8 @@ LIBOBJS NOPING_FALSE NOPING_TRUE clientcertdir +EXT_GOOGLE_MUX_FALSE +EXT_GOOGLE_MUX_TRUE LIBCRYPTO_FALSE LIBCRYPTO_TRUE CPP @@ -742,6 +744,7 @@ enable_libtool_lock enable_openssl enable_nofork enable_libcrypto +enable_x_google_mux with_client_cert_dir enable_noping ' @@ -753,12 +756,7 @@ CFLAGS LDFLAGS LIBS CPPFLAGS -CPP -CPPFLAGS -CC -LDFLAGS -LIBS -CPPFLAGS' +CPP' # Initialize some variables set by options. @@ -1389,6 +1387,7 @@ Optional Features: --enable-openssl Enables https support and needs openssl libs --enable-nofork Disables fork-related options --enable-libcrypto Use libcrypto MD5 and SHA1 implementations + --enable-x-google-mux Build experimental x-google-mux --enable-noping Do not build ping test app, which has some unixy stuff in sources Optional Packages: @@ -12294,6 +12293,28 @@ fi +# +# +# +# Check whether --enable-x-google-mux was given. +if test "${enable_x_google_mux+set}" = set; then : + enableval=$enable_x_google_mux; x_google_mux=yes + +fi + +if test "x$x_google_mux" = "xyes" ; then +CFLAGS="$CFLAGS -DLWS_EXT_GOOGLE_MUX" +fi + if test x$x_google_mux = xyes; then + EXT_GOOGLE_MUX_TRUE= + EXT_GOOGLE_MUX_FALSE='#' +else + EXT_GOOGLE_MUX_TRUE='#' + EXT_GOOGLE_MUX_FALSE= +fi + + + # # # @@ -12645,6 +12666,10 @@ if test -z "${LIBCRYPTO_TRUE}" && test -z "${LIBCRYPTO_FALSE}"; then as_fn_error $? "conditional \"LIBCRYPTO\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${EXT_GOOGLE_MUX_TRUE}" && test -z "${EXT_GOOGLE_MUX_FALSE}"; then + as_fn_error $? "conditional \"EXT_GOOGLE_MUX\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${NOPING_TRUE}" && test -z "${NOPING_FALSE}"; then as_fn_error $? "conditional \"NOPING\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 diff --git a/configure.ac b/configure.ac index 057f42c4..e3124857 100644 --- a/configure.ac +++ b/configure.ac @@ -60,6 +60,19 @@ fi AM_CONDITIONAL(LIBCRYPTO, test x$libcrypto = xyes) +# +# +# +AC_ARG_ENABLE(x-google-mux, + [ --enable-x-google-mux Build experimental x-google-mux], + [ x_google_mux=yes + ]) +if test "x$x_google_mux" = "xyes" ; then +CFLAGS="$CFLAGS -DLWS_EXT_GOOGLE_MUX" +fi +AM_CONDITIONAL(EXT_GOOGLE_MUX, test x$x_google_mux = xyes) + + # # # diff --git a/lib/Makefile.am b/lib/Makefile.am index 9cd8a08f..e8d064c0 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -9,6 +9,11 @@ dist_libwebsockets_la_SOURCES=libwebsockets.c \ extension.c \ extension-deflate-stream.c \ private-libwebsockets.h + +if EXT_GOOGLE_MUX +dist_libwebsockets_la_SOURCES += extension-x-google-mux.c +endif + if LIBCRYPTO else dist_libwebsockets_la_SOURCES += md5.c sha-1.c diff --git a/lib/Makefile.in b/lib/Makefile.in index af9be59b..b98c997b 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -35,7 +35,8 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ -@LIBCRYPTO_FALSE@am__append_1 = md5.c sha-1.c +@EXT_GOOGLE_MUX_TRUE@am__append_1 = extension-x-google-mux.c +@LIBCRYPTO_FALSE@am__append_2 = md5.c sha-1.c subdir = lib DIST_COMMON = $(include_HEADERS) $(srcdir)/Makefile.am \ $(srcdir)/Makefile.in @@ -74,15 +75,17 @@ libwebsockets_la_LIBADD = am__dist_libwebsockets_la_SOURCES_DIST = libwebsockets.c handshake.c \ parsers.c libwebsockets.h base64-decode.c client-handshake.c \ extension.c extension-deflate-stream.c private-libwebsockets.h \ - md5.c sha-1.c -@LIBCRYPTO_FALSE@am__objects_1 = libwebsockets_la-md5.lo \ + extension-x-google-mux.c md5.c sha-1.c +@EXT_GOOGLE_MUX_TRUE@am__objects_1 = libwebsockets_la-extension-x-google-mux.lo +@LIBCRYPTO_FALSE@am__objects_2 = libwebsockets_la-md5.lo \ @LIBCRYPTO_FALSE@ libwebsockets_la-sha-1.lo dist_libwebsockets_la_OBJECTS = libwebsockets_la-libwebsockets.lo \ libwebsockets_la-handshake.lo libwebsockets_la-parsers.lo \ libwebsockets_la-base64-decode.lo \ libwebsockets_la-client-handshake.lo \ libwebsockets_la-extension.lo \ - libwebsockets_la-extension-deflate-stream.lo $(am__objects_1) + libwebsockets_la-extension-deflate-stream.lo $(am__objects_1) \ + $(am__objects_2) libwebsockets_la_OBJECTS = $(dist_libwebsockets_la_OBJECTS) libwebsockets_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(libwebsockets_la_CFLAGS) \ @@ -225,7 +228,7 @@ include_HEADERS = libwebsockets.h dist_libwebsockets_la_SOURCES = libwebsockets.c handshake.c parsers.c \ libwebsockets.h base64-decode.c client-handshake.c extension.c \ extension-deflate-stream.c private-libwebsockets.h \ - $(am__append_1) + $(am__append_1) $(am__append_2) libwebsockets_la_CFLAGS := -rdynamic -fPIC -Wall -Werror -std=gnu99 -pedantic -c \ -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" @@ -307,6 +310,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-base64-decode.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-client-handshake.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-extension-deflate-stream.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-extension-x-google-mux.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-extension.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-handshake.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-libwebsockets.Plo@am__quote@ @@ -384,6 +388,13 @@ libwebsockets_la-extension-deflate-stream.lo: extension-deflate-stream.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_la_CFLAGS) $(CFLAGS) -c -o libwebsockets_la-extension-deflate-stream.lo `test -f 'extension-deflate-stream.c' || echo '$(srcdir)/'`extension-deflate-stream.c +libwebsockets_la-extension-x-google-mux.lo: extension-x-google-mux.c +@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_la_CFLAGS) $(CFLAGS) -MT libwebsockets_la-extension-x-google-mux.lo -MD -MP -MF $(DEPDIR)/libwebsockets_la-extension-x-google-mux.Tpo -c -o libwebsockets_la-extension-x-google-mux.lo `test -f 'extension-x-google-mux.c' || echo '$(srcdir)/'`extension-x-google-mux.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libwebsockets_la-extension-x-google-mux.Tpo $(DEPDIR)/libwebsockets_la-extension-x-google-mux.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='extension-x-google-mux.c' object='libwebsockets_la-extension-x-google-mux.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_la_CFLAGS) $(CFLAGS) -c -o libwebsockets_la-extension-x-google-mux.lo `test -f 'extension-x-google-mux.c' || echo '$(srcdir)/'`extension-x-google-mux.c + libwebsockets_la-md5.lo: md5.c @am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_la_CFLAGS) $(CFLAGS) -MT libwebsockets_la-md5.lo -MD -MP -MF $(DEPDIR)/libwebsockets_la-md5.Tpo -c -o libwebsockets_la-md5.lo `test -f 'md5.c' || echo '$(srcdir)/'`md5.c @am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libwebsockets_la-md5.Tpo $(DEPDIR)/libwebsockets_la-md5.Plo diff --git a/lib/client-handshake.c b/lib/client-handshake.c index adcee07a..d41777ce 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -2,6 +2,147 @@ #include +struct libwebsocket * __libwebsocket_client_connect_2( + struct libwebsocket_context *context, + struct libwebsocket *wsi +) { + struct pollfd pfd; + struct timeval tv; + struct hostent *server_hostent; + struct sockaddr_in server_addr; + int n; + int plen = 0; + char pkt[512]; + int opt = 1; + + fprintf(stderr, "__libwebsocket_client_connect_2\n"); + + wsi->candidate_children_list = NULL; + + /* + * proxy? + */ + + if (context->http_proxy_port) { + plen = sprintf(pkt, "CONNECT %s:%u HTTP/1.0\x0d\x0a" + "User-agent: libwebsockets\x0d\x0a" +/*Proxy-authorization: basic aGVsbG86d29ybGQ= */ + "\x0d\x0a", wsi->c_address, wsi->c_port); + + /* OK from now on we talk via the proxy */ + + free(wsi->c_address); + wsi->c_address = strdup(context->http_proxy_address); + wsi->c_port = context->http_proxy_port; + } + + /* + * prepare the actual connection (to the proxy, if any) + */ + + server_hostent = gethostbyname(wsi->c_address); + if (server_hostent == NULL) { + fprintf(stderr, "Unable to get host name from %s\n", + wsi->c_address); + goto oom4; + } + + wsi->sock = socket(AF_INET, SOCK_STREAM, 0); + + if (wsi->sock < 0) { + fprintf(stderr, "Unable to open socket\n"); + goto oom4; + } + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(wsi->c_port); + server_addr.sin_addr = *((struct in_addr *)server_hostent->h_addr); + bzero(&server_addr.sin_zero, 8); + + /* Disable Nagle */ + setsockopt(wsi->sock, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt)); + + /* Set receiving timeout */ + tv.tv_sec = 0; + tv.tv_usec = 100 * 1000; + setsockopt(wsi->sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv); + + if (connect(wsi->sock, (struct sockaddr *)&server_addr, + sizeof(struct sockaddr)) == -1) { + fprintf(stderr, "Connect failed\n"); + goto oom4; + } + + fprintf(stderr, "connected\n"); + + /* into fd -> wsi hashtable */ + + insert_wsi(context, wsi); + + /* into internal poll list */ + + context->fds[context->fds_count].fd = wsi->sock; + context->fds[context->fds_count].revents = 0; + context->fds[context->fds_count++].events = POLLIN; + + /* external POLL support via protocol 0 */ + context->protocols[0].callback(context, wsi, + LWS_CALLBACK_ADD_POLL_FD, + (void *)(long)wsi->sock, NULL, POLLIN); + + /* we are connected to server, or proxy */ + + if (context->http_proxy_port) { + + n = send(wsi->sock, pkt, plen, 0); + if (n < 0) { +#ifdef WIN32 + closesocket(wsi->sock); +#else + close(wsi->sock); +#endif + fprintf(stderr, "ERROR writing to proxy socket\n"); + goto bail1; + } + + libwebsocket_set_timeout(wsi, + PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, 5); + + wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY; + + return wsi; + } + + /* + * provoke service to issue the handshake directly + * we need to do it this way because in the proxy case, this is the + * next state and executed only if and when we get a good proxy + * response inside the state machine + */ + + wsi->mode = LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE; + pfd.fd = wsi->sock; + pfd.revents = POLLIN; + libwebsocket_service_fd(context, &pfd); + + return wsi; + +oom4: + if (wsi->c_protocol) + free(wsi->c_protocol); + + if (wsi->c_origin) + free(wsi->c_origin); + + free(wsi->c_host); + free(wsi->c_path); + +bail1: + free(wsi); + + return NULL; +} + /** * libwebsocket_client_connect() - Connect to another websocket server * @context: Websocket context @@ -32,14 +173,11 @@ libwebsocket_client_connect(struct libwebsocket_context *context, const char *protocol, int ietf_version_or_minus_one) { - struct hostent *server_hostent; - struct sockaddr_in server_addr; - char pkt[512]; - struct pollfd pfd; struct libwebsocket *wsi; int n; - int opt = 1; - int plen = 0; + int m; + struct libwebsocket_extension *ext; + int handled; #ifndef LWS_OPENSSL_SUPPORT if (ssl_connection) { fprintf(stderr, "libwebsockets not configured for ssl\n"); @@ -70,6 +208,9 @@ libwebsocket_client_connect(struct libwebsocket_context *context, wsi->use_ssl = ssl_connection; #endif + wsi->c_port = port; + wsi->c_address = strdup(address); + /* copy parameters over so state machine has access */ wsi->c_path = malloc(strlen(path) + 1); @@ -129,103 +270,42 @@ libwebsocket_client_connect(struct libwebsocket_context *context, } /* - * proxy? + * Check with each extension if it is able to route and proxy this + * connection for us. For example, an extension like x-google-mux + * can handle this and then we don't need an actual socket for this + * connection. */ - if (context->http_proxy_port) { - plen = sprintf(pkt, "CONNECT %s:%u HTTP/1.0\x0d\x0a" - "User-agent: libwebsockets\x0d\x0a" -/*Proxy-authorization: basic aGVsbG86d29ybGQ= */ - "\x0d\x0a", address, port); + handled = 0; + ext = context->extensions; + n = 0; - /* OK from now on we talk via the proxy */ + while (ext && ext->callback && !handled) { + m = ext->callback(context, ext, wsi, + LWS_EXT_CALLBACK_CAN_PROXY_CLIENT_CONNECTION, + (void *)(long)n, (void *)address, port); + if (m) + handled = 1; - address = context->http_proxy_address; - port = context->http_proxy_port; + ext++; + n++; } - /* - * prepare the actual connection (to the proxy, if any) - */ - - server_hostent = gethostbyname(address); - if (server_hostent == NULL) { - fprintf(stderr, "Unable to get host name from %s\n", address); - goto oom4; - } - - wsi->sock = socket(AF_INET, SOCK_STREAM, 0); - - if (wsi->sock < 0) { - fprintf(stderr, "Unable to open socket\n"); - goto oom4; - } - - server_addr.sin_family = AF_INET; - server_addr.sin_port = htons(port); - server_addr.sin_addr = *((struct in_addr *)server_hostent->h_addr); - bzero(&server_addr.sin_zero, 8); - - /* Disable Nagle */ - setsockopt(wsi->sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); - - if (connect(wsi->sock, (struct sockaddr *)&server_addr, - sizeof(struct sockaddr)) == -1) { - fprintf(stderr, "Connect failed\n"); - goto oom4; - } - - /* into fd -> wsi hashtable */ - - insert_wsi(context, wsi); - - /* into internal poll list */ - - context->fds[context->fds_count].fd = wsi->sock; - context->fds[context->fds_count].revents = 0; - context->fds[context->fds_count++].events = POLLIN; - - /* external POLL support via protocol 0 */ - context->protocols[0].callback(context, wsi, - LWS_CALLBACK_ADD_POLL_FD, - (void *)(long)wsi->sock, NULL, POLLIN); - - /* we are connected to server, or proxy */ - - if (context->http_proxy_port) { - - n = send(wsi->sock, pkt, plen, 0); - if (n < 0) { -#ifdef WIN32 - closesocket(wsi->sock); -#else - close(wsi->sock); -#endif - fprintf(stderr, "ERROR writing to proxy socket\n"); - goto bail1; - } + if (handled) { + fprintf(stderr, "libwebsocket_client_connect: " + "ext handling conn\n"); libwebsocket_set_timeout(wsi, - PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, 5); - - wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY; + PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE, 5); + wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT; return wsi; } - /* - * provoke service to issue the handshake directly - * we need to do it this way because in the proxy case, this is the - * next state and executed only if and when we get a good proxy - * response inside the state machine - */ + fprintf(stderr, "libwebsocket_client_connect: direct conn\n"); - wsi->mode = LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE; - pfd.fd = wsi->sock; - pfd.revents = POLLIN; - libwebsocket_service_fd(context, &pfd); + return __libwebsocket_client_connect_2(context, wsi); - return wsi; oom4: if (wsi->c_protocol) diff --git a/lib/extension-deflate-stream.c b/lib/extension-deflate-stream.c index 3505e950..3c20fe2a 100644 --- a/lib/extension-deflate-stream.c +++ b/lib/extension-deflate-stream.c @@ -140,7 +140,10 @@ int lws_extension_callback_deflate_stream( /* we don't need calling again until new input data comes */ - return 0; + return 0; + + default: + break; } return 0; diff --git a/lib/extension-x-google-mux.c b/lib/extension-x-google-mux.c new file mode 100644 index 00000000..1f56994d --- /dev/null +++ b/lib/extension-x-google-mux.c @@ -0,0 +1,929 @@ +#include "private-libwebsockets.h" +#include "extension-x-google-mux.h" + +static int ongoing_subchannel; +static struct libwebsocket * tag_with_parent = NULL; + +static int lws_addheader_mux_opcode(unsigned char *pb, int len) +{ + unsigned char *start = pb; + + *pb++ = LWS_WS_OPCODE_07__NOSPEC__MUX | 0x80; + if (len < 126) + *pb++ = len; + else { + if (len > 65535) { + *pb++ = 127; + *pb++ = 0; + *pb++ = 0; + *pb++ = 0; + *pb++ = 0; + *pb++ = (len ) >> 24; + *pb++ = (len) >> 16; + *pb++ = (len) >> 8; + *pb++ = (len) >> 0; + } else { + *pb++ = 126; + *pb++ = (len) >> 8; + *pb++ = (len) >> 0; + } + } + + return pb - start; +} + +static int lws_mux_subcommand_header(int cmd, int channel, unsigned char *pb, int len) +{ + unsigned char *start = pb; + + *pb++ = ((channel >> 8) << 3) | cmd; + *pb++ = channel; + + if (len <= 253) + *pb++ = len; + else { + *pb++ = 254; + *pb++ = len >> 8; + *pb++ = len; + } + + return pb - start; +} + +static int lws_ext_x_google_mux__send_addchannel( + struct libwebsocket_context *context, + struct libwebsocket *wsi, + struct libwebsocket *wsi_child, + int channel, + const char *url +) { + + unsigned char send_buf[LWS_SEND_BUFFER_PRE_PADDING + 2048 + + LWS_SEND_BUFFER_POST_PADDING]; + unsigned char *pb = &send_buf[LWS_SEND_BUFFER_PRE_PADDING]; + char *p; + char delta_headers[1536]; + int delta_headers_len; + int subcommand_length; + + wsi_child->ietf_spec_revision = wsi->ietf_spec_revision; + + p = libwebsockets_generate_client_handshake(context, wsi_child, delta_headers); + delta_headers_len = p - delta_headers; + + subcommand_length = lws_mux_subcommand_header(LWS_EXT_XGM_OPC__ADDCHANNEL, channel, pb, delta_headers_len); + + pb += lws_addheader_mux_opcode(pb, subcommand_length + delta_headers_len); + pb += lws_mux_subcommand_header(LWS_EXT_XGM_OPC__ADDCHANNEL, channel, pb, delta_headers_len); + +// n = sprintf((char *)pb, "%s\x0d\x0a", url); +// pb += n; + + if (delta_headers_len) + memcpy(pb, delta_headers, delta_headers_len); + + pb += delta_headers_len; + + muxdebug("add channel sends %ld\n", + pb - &send_buf[LWS_SEND_BUFFER_PRE_PADDING]); + + /* send the request to the server */ + + return lws_issue_raw(wsi, &send_buf[LWS_SEND_BUFFER_PRE_PADDING], + pb - &send_buf[LWS_SEND_BUFFER_PRE_PADDING]); +} + +/** + * lws_extension_x_google_mux_parser(): Parse mux buffer headers coming in + * from a muxed connection into subchannel + * specific actions + * @wsi: muxed websocket instance + * @conn: x-google-mux private data bound to that @wsi + * @c: next character in muxed stream + */ + +static int +lws_extension_x_google_mux_parser(struct libwebsocket_context *context, + struct libwebsocket *wsi, + struct libwebsocket_extension *this_ext, + struct lws_ext_x_google_mux_conn *conn, unsigned char c) +{ + struct libwebsocket *wsi_child = NULL; + struct libwebsocket_extension *ext; + struct lws_ext_x_google_mux_conn *child_conn = NULL; + int n; + void *v; + +// fprintf(stderr, "XRX: %02X %d %d\n", c, conn->state, conn->length); + + /* + * [ ] + * [ ] + */ + + switch (conn->state) { + + case LWS_EXT_XGM_STATE__MUX_BLOCK_1: + muxdebug("LWS_EXT_XGM_STATE__MUX_BLOCK_1: opc=%d\n", c & 7); + conn->block_subopcode = c & 7; + conn->block_subchannel = (c << 5) & ~0xff; + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_2; + break; + + case LWS_EXT_XGM_STATE__MUX_BLOCK_2: + conn->block_subchannel |= c; + muxdebug("LWS_EXT_XGM_STATE__MUX_BLOCK_2: subchannel=%d\n", conn->block_subchannel); + + ongoing_subchannel = ongoing_subchannel; + + /* + * convert the subchannel index to a child wsi + */ + + /* act on the muxing opcode */ + + switch (conn->block_subopcode) { + case LWS_EXT_XGM_OPC__DATA: + conn->state = LWS_EXT_XGM_STATE__DATA; + break; + case LWS_EXT_XGM_OPC__ADDCHANNEL: + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_LEN; + switch (wsi->mode) { + + /* client: parse accepted headers returned by server */ + + case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY: + case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE: + case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY: + case LWS_CONNMODE_WS_CLIENT: + wsi_child = conn->wsi_children[conn->block_subchannel]; + wsi_child->state = WSI_STATE_HTTP_HEADERS; + wsi_child->parser_state = WSI_TOKEN_NAME_PART; + break; + default: + wsi_child = libwebsocket_create_new_server_wsi(context); + conn->wsi_children[conn->block_subchannel] = wsi_child; + wsi_child->state = WSI_STATE_HTTP_HEADERS; + wsi_child->parser_state = WSI_TOKEN_NAME_PART; + wsi_child->extension_handles = wsi; + muxdebug("MUX LWS_EXT_XGM_OPC__ADDCHANNEL... " + "created child subchannel %d\n", conn->block_subchannel); + break; + } + break; + case LWS_EXT_XGM_OPC__DROPCHANNEL: + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + break; + case LWS_EXT_XGM_OPC__FLOWCONTROL: + conn->state = LWS_EXT_XGM_STATE__FLOWCONTROL_1; + break; + default: + fprintf(stderr, "xgm: unknown subopcode\n"); + return -1; + } + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_LEN: + switch (c) { + case 254: + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_LEN16_1; + break; + case 255: + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_1; + break; + default: + conn->length = c; + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS; + break; + } + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_LEN16_1: + conn->length = c << 8; + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_LEN16_2; + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_LEN16_2: + conn->length |= c; + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS; + muxdebug("conn->length in mux block is %d\n", conn->length); + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_1: + conn->length = c << 24; + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_2; + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_2: + conn->length |= c << 16; + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_3; + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_3: + conn->length |= c << 8; + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_4; + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_4: + conn->length |= c; + conn->state = LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS; + break; + + case LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS: + + switch (wsi->mode) { + + /* client: parse accepted headers returned by server */ + + case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY: + case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE: + case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY: + case LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT: + case LWS_CONNMODE_WS_CLIENT: + + muxdebug("Client LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS in %c\n", c); + wsi_child = conn->wsi_children[conn->block_subchannel]; + + libwebsocket_parse(wsi_child, c); + + if (--conn->length) + return 0; + + /* it's here we create the actual ext conn via callback */ + tag_with_parent = wsi; + lws_client_interpret_server_handshake(context, wsi_child); + tag_with_parent = NULL; + + // if (wsi->parser_state != WSI_PARSING_COMPLETE) +// break; + + /* client: we received all server's ADD ack */ + + child_conn = lws_get_extension_user_matching_ext(wsi_child, this_ext); + muxdebug("Received server's ADD Channel ACK for subchannel %d child_conn=%p!\n", conn->block_subchannel, (void *)child_conn); + + wsi_child->xor_mask = xor_no_mask; + wsi_child->ietf_spec_revision = wsi->ietf_spec_revision; + + wsi_child->mode = LWS_CONNMODE_WS_CLIENT; + wsi_child->state = WSI_STATE_ESTABLISHED; + + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + child_conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + + /* allocate the per-connection user memory (if any) */ + + if (wsi_child->protocol->per_session_data_size) { + wsi_child->user_space = malloc( + wsi_child->protocol->per_session_data_size); + if (wsi_child->user_space == NULL) { + fprintf(stderr, "Out of memory for " + "conn user space\n"); + goto bail2; + } + } else + wsi_child->user_space = NULL; + + /* clear his proxy connection timeout */ + + libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* mark him as being alive */ + + wsi_child->state = WSI_STATE_ESTABLISHED; + wsi_child->mode = LWS_CONNMODE_WS_CLIENT; + + if (wsi_child->protocol) + fprintf(stderr, "mux handshake OK for protocol %s\n", + wsi_child->protocol->name); + else + fprintf(stderr, "mux child handshake ends up with no protocol!\n"); + + /* + * inform all extensions, not just active ones since they + * already know + */ + + ext = context->extensions; + + while (ext && ext->callback) { + v = NULL; + for (n = 0; n < wsi_child->count_active_extensions; n++) + if (wsi_child->active_extensions[n] == ext) { + v = wsi_child->active_extensions_user[n]; + } + + ext->callback(context, ext, wsi_child, + LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED, v, NULL, 0); + ext++; + } + + /* call him back to inform him he is up */ + + wsi->protocol->callback(context, wsi_child, + LWS_CALLBACK_CLIENT_ESTABLISHED, + wsi_child->user_space, + NULL, 0); + + return 0; + +bail2: + exit(1); + + /* server: parse proposed changed headers from client */ + + default: + break; + } + + /* + * SERVER + */ + + wsi_child = conn->wsi_children[conn->block_subchannel]; + + muxdebug("Server LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS in\n"); + + libwebsocket_read(context, wsi_child, &c, 1); + + if (--conn->length >= 0) + break; + + muxdebug("Server LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS done\n"); + + /* + * server: header diffs are all seen, we must process + * the add action + */ + + /* reply with ADDCHANNEL to ack it */ + + wsi->xor_mask = xor_no_mask; + + +// lws_ext_x_google_mux__send_addchannel(context, wsi, wsi_child, +// conn->block_subchannel, "url-parsing-not-done-yet"); + + wsi_child->mode = LWS_CONNMODE_WS_SERVING; + wsi_child->state = WSI_STATE_ESTABLISHED; + wsi_child->lws_rx_parse_state = LWS_RXPS_NEW; + wsi_child->rx_packet_length = 0; + + /* allocate the per-connection user memory (if any) */ + + if (wsi_child->protocol->per_session_data_size) { + wsi_child->user_space = malloc( + wsi_child->protocol->per_session_data_size); + if (wsi_child->user_space == NULL) { + fprintf(stderr, "Out of memory for " + "conn user space\n"); + break; + } + } else + wsi_child->user_space = NULL; + + + conn->wsi_children[conn->block_subchannel] = wsi_child; + if (conn->count_children <= conn->block_subchannel) + conn->count_children = conn->block_subchannel + 1; + + + /* notify user code that we're ready to roll */ + + if (wsi_child->protocol->callback) + wsi_child->protocol->callback(wsi_child->protocol->owning_server, + wsi_child, LWS_CALLBACK_ESTABLISHED, wsi_child->user_space, + NULL, 0); + + muxdebug("setting conn state to LWS_EXT_XGM_STATE__MUX_BLOCK_1\n"); + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + break; + + case LWS_EXT_XGM_STATE__FLOWCONTROL_1: + conn->length = c << 24; + conn->state = LWS_EXT_XGM_STATE__FLOWCONTROL_2; + break; + + case LWS_EXT_XGM_STATE__FLOWCONTROL_2: + conn->length |= c << 16; + conn->state = LWS_EXT_XGM_STATE__FLOWCONTROL_3; + break; + + case LWS_EXT_XGM_STATE__FLOWCONTROL_3: + conn->length |= c << 8; + conn->state = LWS_EXT_XGM_STATE__FLOWCONTROL_4; + break; + + case LWS_EXT_XGM_STATE__FLOWCONTROL_4: + conn->length |= c; + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + break; + + case LWS_EXT_XGM_STATE__DATA: + +// fprintf(stderr, "LWS_EXT_XGM_STATE__DATA in\n"); + + /* + * we have cooked websocket frame content following just like + * it went on the wire without mux, including masking and any + * other extensions (including this guy can himself be another + * level of channel mux, there's no restriction). + * + * We deal with it by just feeding it to the child wsi's rx + * state machine. The only issue is, we need that state machine + * to tell us when it ate a full frame, so we watch its state + * afterwards + */ + if (conn->block_subchannel > conn->count_children) { + fprintf(stderr, "Illegal subchannel\n"); + return -1; + } + + wsi_child = conn->wsi_children[conn->block_subchannel]; + + switch (wsi_child->mode) { + + /* client receives something */ + + case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY: + case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE: + case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY: + case LWS_CONNMODE_WS_CLIENT: +// fprintf(stderr, " client\n"); + libwebsocket_client_rx_sm(wsi_child, c); + + return 0; + + /* server is receiving from client */ + + default: +// fprintf(stderr, " server\n"); + if (libwebsocket_rx_sm(wsi_child, c) < 0) + fprintf(stderr, "probs\n"); + + break; + } + break; + } + + return 0; +} + + + +int lws_extension_callback_x_google_mux( + struct libwebsocket_context *context, + struct libwebsocket_extension *ext, + struct libwebsocket *wsi, + enum libwebsocket_extension_callback_reasons reason, + void *user, void *in, size_t len) +{ + unsigned char send_buf[LWS_SEND_BUFFER_PRE_PADDING + 4096 + + LWS_SEND_BUFFER_POST_PADDING]; + struct lws_ext_x_google_mux_conn *conn = + (struct lws_ext_x_google_mux_conn *)user; + struct lws_ext_x_google_mux_conn *parent_conn; + struct lws_ext_x_google_mux_conn *child_conn; + int n; + struct lws_tokens *eff_buf = (struct lws_tokens *)in; + unsigned char *p = NULL; + struct lws_ext_x_google_mux_context *mux_ctx = + ext->per_context_private_data; + struct libwebsocket *wsi_parent; + struct libwebsocket *wsi_temp; + unsigned char *pin = (unsigned char *)in; + unsigned char *basepin; + int m; + int done = 0; + unsigned char *pb = &send_buf[LWS_SEND_BUFFER_PRE_PADDING]; + int subcommand_length; + + if (eff_buf) + p = (unsigned char *)eff_buf->token; + + switch (reason) { + + /* these guys are once per context */ + + case LWS_EXT_CALLBACK_SERVER_CONTEXT_CONSTRUCT: + case LWS_EXT_CALLBACK_CLIENT_CONTEXT_CONSTRUCT: + + ext->per_context_private_data = malloc( + sizeof (struct lws_ext_x_google_mux_context)); + mux_ctx = (struct lws_ext_x_google_mux_context *) + ext->per_context_private_data; + mux_ctx->active_conns = 0; + break; + + case LWS_EXT_CALLBACK_SERVER_CONTEXT_DESTRUCT: + case LWS_EXT_CALLBACK_CLIENT_CONTEXT_DESTRUCT: + + if (mux_ctx) { + for (n = 0; n < mux_ctx->active_conns; n++) + if (mux_ctx->wsi_muxconns[n]) { + libwebsocket_close_and_free_session( + context, + mux_ctx->wsi_muxconns[n], + LWS_CLOSE_STATUS_GOINGAWAY); + mux_ctx->wsi_muxconns[n] = NULL; + } + + free(mux_ctx); + } + break; + + /* + * channel management + */ + + case LWS_EXT_CALLBACK_CAN_PROXY_CLIENT_CONNECTION: + + muxdebug("LWS_EXT_CALLBACK_CAN_PROXY_CLIENT_CONNECTION %s:%u\n", (char *)in, (unsigned int)len); + + /* + * Does a physcial connection to the same server:port already + * exist so we can piggyback on it? + */ + + for (n = 0; n < mux_ctx->active_conns && !done; n++) { + + wsi_parent = mux_ctx->wsi_muxconns[n]; + if (!wsi_parent) + continue; + + muxdebug(" %s / %s\n", wsi_parent->c_address, (char *)in); + if (strcmp(wsi_parent->c_address, in)) + continue; + muxdebug(" %u / %u\n", wsi_parent->c_port, (unsigned int)len); + + if (wsi_parent->c_port != (unsigned int)len) + continue; + + /* + * does this potential parent already have an + * x-google-mux conn associated with him? + */ + + parent_conn = NULL; + for (m = 0; m < wsi_parent->count_active_extensions; m++) + if (ext == wsi_parent->active_extensions[m]) + parent_conn = (struct lws_ext_x_google_mux_conn *) + wsi_parent->active_extensions_user[m]; + + if (parent_conn == NULL) { + + /* + * he doesn't -- see if that's just because it + * is early in his connection sequence or if we + * should give up on him + */ + + switch (wsi_parent->mode) { + case LWS_CONNMODE_WS_SERVING: + case LWS_CONNMODE_WS_CLIENT: + continue; + default: + break; + } + + /* + * our putative parent is still connecting + * himself, we have to become a candidate child + * and find out our final fate when the parent + * completes connection + */ + + wsi->candidate_children_list = wsi_parent->candidate_children_list; + wsi_parent->candidate_children_list = wsi; + wsi->mode = LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD; + + done = 1; + continue; + } + + if (parent_conn->count_children >= + sizeof(parent_conn->wsi_children) / + sizeof(parent_conn->wsi_children[0])) + continue; + /* + * this established connection will do, bind them + * from now on child will only operate through parent + * connection + */ + + conn->wsi_parent = wsi_parent; + parent_conn->wsi_children[ + parent_conn->count_children] = wsi; + + /* + * and now we have to ask the server to allow us to do + * this + */ + + if (lws_ext_x_google_mux__send_addchannel(context, + wsi_parent, wsi, parent_conn->count_children, + (const char *)in) < 0) { + fprintf(stderr, "Addchannel failed\n"); + continue; + } + + parent_conn->count_children++; + + fprintf(stderr, "!x-google-mux: muxing connection! CHILD ADD %d to %p\n", parent_conn->count_children - 1, (void *)wsi); + + done = 1; + n = mux_ctx->active_conns; + } + + /* + * either way, note the existence of this connection in case + * he will become a possible mux parent later + */ + + mux_ctx->wsi_muxconns[mux_ctx->active_conns++] = wsi; + if (done) + return 1; + + fprintf(stderr, "x-google-mux: unable to mux connection\n"); + + break; + + /* these guys are once per connection */ + + case LWS_EXT_CALLBACK_CLIENT_CONSTRUCT: + muxdebug("LWS_EXT_CALLBACK_CLIENT_CONSTRUCT: setting parent = %p\n", (void *)tag_with_parent); + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + conn->wsi_parent = tag_with_parent; + break; + + case LWS_EXT_CALLBACK_CONSTRUCT: + muxdebug("LWS_EXT_CALLBACK_CONSTRUCT\n"); + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + break; + + case LWS_EXT_CALLBACK_DESTROY: + muxdebug("LWS_EXT_CALLBACK_DESTROY\n"); + break; + + case LWS_EXT_CALLBACK_DESTROY_ANY_WSI_CLOSING: + muxdebug("LWS_EXT_CALLBACK_DESTROY_ANY_WSI_CLOSING\n"); + + for (n = 0; n < mux_ctx->active_conns; n++) + if (mux_ctx->wsi_muxconns[n] == wsi) { + while (n++ < mux_ctx->active_conns) + mux_ctx->wsi_muxconns[n - 1] = + mux_ctx->wsi_muxconns[n]; + mux_ctx->active_conns--; + return 0; + } + + /* + * liberate any candidate children otherwise imprisoned + */ + + wsi_parent = wsi->candidate_children_list; + while (wsi_parent) { + wsi_temp = wsi_parent->candidate_children_list; + /* let them each connect privately then */ + __libwebsocket_client_connect_2(context, wsi_parent); + wsi_parent = wsi_temp; + } + + break; + + case LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED: + muxdebug("LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED\n"); + + /* + * did this putative parent get x-google-mux authorized in the + * end? + */ + + if (!conn) { + + muxdebug(" Putative parent didn't get mux extension, let them go it alone\n"); + + /* + * no, we can't be a parent for mux children. Let + * them all go it alone + */ + + wsi_parent = wsi->candidate_children_list; + while (wsi_parent) { + wsi_temp = wsi_parent->candidate_children_list; + /* let them each connect privately then */ + __libwebsocket_client_connect_2(context, wsi_parent); + wsi_parent = wsi_temp; + } + + break; + } + + /* + * we did get mux extension authorized by server, in that case + * if we have any candidate children let's try to attach them + * as mux subchannel real children + */ + + wsi_parent = wsi->candidate_children_list; + while (wsi_parent) { + + muxdebug(" using mux addchannel action for candidate child\n"); + + wsi_temp = wsi_parent->candidate_children_list; + /* let them each connect privately then */ + lws_ext_x_google_mux__send_addchannel(context, wsi, + wsi_parent, + conn->count_children, wsi->c_path); + + conn->wsi_children[conn->count_children++] = wsi_parent; + muxdebug("Setting CHILD LIST entry %d to %p\n", conn->count_children - 1, (void *)wsi_parent); + wsi_parent = wsi_temp; + } + wsi->candidate_children_list = NULL; + break; + + /* + * whenever we receive something on a muxed link + */ + + case LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX: + + muxdebug("LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX\n"); + + if (wsi->opcode != LWS_WS_OPCODE_07__NOSPEC__MUX) + return 0; /* unhandled */ + + conn->state = LWS_EXT_XGM_STATE__MUX_BLOCK_1; + + n = eff_buf->token_len; + while (n--) + lws_extension_x_google_mux_parser(context, wsi, ext, conn, *p++); + + return 1; /* handled */ + + /* + * when something might need sending on our transport + */ + + case LWS_EXT_CALLBACK_PACKET_TX_DO_SEND: + + muxdebug("LWS_EXT_CALLBACK_PACKET_TX_DO_SEND: %p\n", (void *)conn->wsi_parent); + + pin = *((unsigned char **)in); + + /* + * he's not a child connection of a mux + */ + + if (!conn->wsi_parent) + return 0; + + /* + * get parent / transport mux context + */ + + parent_conn = lws_get_extension_user_matching_ext(conn->wsi_parent, ext); + if (parent_conn == 0) { + fprintf(stderr, "failed to get parent conn\n"); + return 0; + } + + /* + * mux transport is in singular mode, let the caller send it + * no more muxified than it already is + */ + + if (parent_conn->count_children == 0) + return 0; + + /* + * otherwise we need to take care of the sending action using + * mux protocol. Prepend the channel + opcode + */ + + pin -= lws_addheader_mux_opcode(send_buf, len + 2) + 2; + basepin = pin; + pin += lws_addheader_mux_opcode(pin, len + 2); + + *pin++ = (conn->subchannel >> 8) | LWS_EXT_XGM_OPC__DATA; + *pin++ = conn->subchannel; + + /* + * recurse to allow nesting + */ + + lws_issue_raw(conn->wsi_parent, basepin, (pin - basepin) + len); + + return 1; /* handled */ + + case LWS_EXT_CALLBACK_1HZ: + /* + * if we have children, service their timeouts using the same + * handler as toplevel guys to allow recursion + */ + for (n = 0; n < conn->count_children; n++) + libwebsocket_service_timeout_check(context, + conn->wsi_children[n], len); + break; + + case LWS_EXT_CALLBACK_REQUEST_ON_WRITEABLE: + /* + * if a mux child is asking for callback on writable, we have + * to pass it up to his parent + */ + + muxdebug("LWS_EXT_CALLBACK_REQUEST_ON_WRITEABLE %s\n", wsi->protocol->name); + + if (conn->wsi_parent == NULL) { + muxdebug(" no parent\n"); + break; + } + + if (!conn->awaiting_POLLOUT) { + + muxdebug(" !conn->awaiting_POLLOUT\n"); + + conn->awaiting_POLLOUT = 1; + parent_conn = NULL; + for (m = 0; m < conn->wsi_parent->count_active_extensions; m++) + if (ext == conn->wsi_parent->active_extensions[m]) + parent_conn = (struct lws_ext_x_google_mux_conn *) + conn->wsi_parent->active_extensions_user[m]; + + if (parent_conn != NULL) { + parent_conn->count_children_needing_POLLOUT++; + muxdebug(" count_children_needing_POLLOUT bumped\n"); + } else + fprintf(stderr, "unable to identify parent conn\n"); + } + muxdebug(" requesting on parent %p\n", (void *)conn->wsi_parent); + libwebsocket_callback_on_writable(context, conn->wsi_parent); + + return 1; + + case LWS_EXT_CALLBACK_HANDSHAKE_REPLY_TX: + + fprintf(stderr, "LWS_EXT_CALLBACK_HANDSHAKE_REPLY_TX %p\n", (void *)wsi->extension_handles); + + /* send raw if we're not a child */ + + if (!wsi->extension_handles) + return 0; + + subcommand_length = lws_mux_subcommand_header(LWS_EXT_XGM_OPC__ADDCHANNEL, ongoing_subchannel, pb, len); + + pb += lws_addheader_mux_opcode(pb, subcommand_length + len); + pb += lws_mux_subcommand_header(LWS_EXT_XGM_OPC__ADDCHANNEL, ongoing_subchannel, pb, len); + memcpy(pb, in, len); + pb += len; + + lws_issue_raw(wsi->extension_handles, &send_buf[LWS_SEND_BUFFER_PRE_PADDING], + pb - &send_buf[LWS_SEND_BUFFER_PRE_PADDING]); + + + return 1; /* handled */ + + case LWS_EXT_CALLBACK_IS_WRITEABLE: + /* + * we are writable, inform children if any care + */ + muxdebug("LWS_EXT_CALLBACK_IS_WRITEABLE: %s\n", wsi->protocol->name); + + if (!conn->count_children_needing_POLLOUT) { + muxdebug(" no children need POLLOUT\n"); + return 0; + } + + for (n = 0; n < conn->count_children; n++) { + + child_conn = NULL; + for (m = 0; m < conn->wsi_children[n]->count_active_extensions; m++) + if (ext == conn->wsi_children[n]->active_extensions[m]) + child_conn = (struct lws_ext_x_google_mux_conn *) + conn->wsi_children[n]->active_extensions_user[m]; + + if (!child_conn) { + fprintf(stderr, "unable to identify child conn\n"); + continue; + } + + if (!child_conn->awaiting_POLLOUT) + continue; + + child_conn->awaiting_POLLOUT = 0; + conn->count_children_needing_POLLOUT--; + lws_handle_POLLOUT_event(context, conn->wsi_children[n], NULL); + if (!conn->count_children_needing_POLLOUT) + return 2; /* all handled */ + else + return 1; /* handled but need more */ + } + break; + + default: + break; + } + + return 0; +} diff --git a/lib/extension-x-google-mux.h b/lib/extension-x-google-mux.h new file mode 100644 index 00000000..a2cbb211 --- /dev/null +++ b/lib/extension-x-google-mux.h @@ -0,0 +1,94 @@ + +#if 0 +#ifdef WIN32 +static +#else +static inline +#endif +void muxdebug(const char *format, ...) +{ + va_list ap; + va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); +} +#else +#ifdef WIN32 +static +#else +static inline +#endif +void muxdebug(const char *format, ...) +{ +} +#endif + +#define MAX_XGM_SUBCHANNELS 8192 + +enum lws_ext_x_google_mux__parser_states { + LWS_EXT_XGM_STATE__MUX_BLOCK_1, + LWS_EXT_XGM_STATE__MUX_BLOCK_2, + LWS_EXT_XGM_STATE__ADDCHANNEL_LEN, + LWS_EXT_XGM_STATE__ADDCHANNEL_LEN16_1, + LWS_EXT_XGM_STATE__ADDCHANNEL_LEN16_2, + LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_1, + LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_2, + LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_3, + LWS_EXT_XGM_STATE__ADDCHANNEL_LEN32_4, + LWS_EXT_XGM_STATE__ADDCHANNEL_HEADERS, + LWS_EXT_XGM_STATE__FLOWCONTROL_1, + LWS_EXT_XGM_STATE__FLOWCONTROL_2, + LWS_EXT_XGM_STATE__FLOWCONTROL_3, + LWS_EXT_XGM_STATE__FLOWCONTROL_4, + LWS_EXT_XGM_STATE__DATA, +}; + +enum lws_ext_x_goole_mux__mux_opcodes { + LWS_EXT_XGM_OPC__DATA, + LWS_EXT_XGM_OPC__ADDCHANNEL, + LWS_EXT_XGM_OPC__DROPCHANNEL, + LWS_EXT_XGM_OPC__FLOWCONTROL, + LWS_EXT_XGM_OPC__RESERVED_4, + LWS_EXT_XGM_OPC__RESERVED_5, + LWS_EXT_XGM_OPC__RESERVED_6, + LWS_EXT_XGM_OPC__RESERVED_7, +}; + +/* one of these per context (server or client) */ + +struct lws_ext_x_google_mux_context { + /* + * these are listing physical connections, not children sharing a + * parent mux physical connection + */ + struct libwebsocket *wsi_muxconns[MAX_CLIENTS]; + /* + * when this is < 2, we do not do any mux blocks + * just pure websockets + */ + int active_conns; +}; + +inline int use_mux_blocks(struct lws_ext_x_google_mux_context * mux_context) { \ + return !!(mux_context->active_conns > 1); } + +/* one of these per connection (server or client) */ + +struct lws_ext_x_google_mux_conn { + enum lws_ext_x_goole_mux__mux_opcodes block_subopcode; + int block_subchannel; + unsigned int length; + enum lws_ext_x_google_mux__parser_states state; + /* child points to the mux wsi using this */ + struct libwebsocket *wsi_parent; + int subchannel; + struct libwebsocket *wsi_children[MAX_CLIENTS]; + int count_children; + char awaiting_POLLOUT; + int count_children_needing_POLLOUT; +}; + +extern int +lws_extension_callback_x_google_mux(struct libwebsocket_context *context, + struct libwebsocket_extension *ext, + struct libwebsocket *wsi, + enum libwebsocket_extension_callback_reasons reason, + void *user, void *in, size_t len); diff --git a/lib/extension.c b/lib/extension.c index df62bd5c..89093087 100644 --- a/lib/extension.c +++ b/lib/extension.c @@ -1,8 +1,16 @@ #include "private-libwebsockets.h" #include "extension-deflate-stream.h" +#include "extension-x-google-mux.h" struct libwebsocket_extension libwebsocket_internal_extensions[] = { +#ifdef LWS_EXT_GOOGLE_MUX + { + "x-google-mux", + lws_extension_callback_x_google_mux, + sizeof (struct lws_ext_x_google_mux_conn) + }, +#endif { "deflate-stream", lws_extension_callback_deflate_stream, diff --git a/lib/handshake.c b/lib/handshake.c index 46197ff0..14adbba4 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -72,7 +72,7 @@ interpret_key(const char *key, unsigned long *result) static int -handshake_00(struct libwebsocket *wsi) +handshake_00(struct libwebsocket_context *context, struct libwebsocket *wsi) { unsigned long key1, key2; unsigned char sum[16]; @@ -223,7 +223,7 @@ bail: */ static int -handshake_0405(struct libwebsocket *wsi) +handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi) { static const char *websocket_magic_guid_04 = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; @@ -367,10 +367,12 @@ handshake_0405(struct libwebsocket *wsi) */ c = wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token; + fprintf(stderr, "wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token = %s\n", wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token); + wsi->count_active_extensions = 0; n = 0; while (more) { - - if (*c && *c != ',') { + + if (*c && (*c != ',' && *c != ' ' && *c != '\t')) { ext_name[n] = *c++; if (n < sizeof(ext_name) - 1) n++; @@ -379,6 +381,11 @@ handshake_0405(struct libwebsocket *wsi) ext_name[n] = '\0'; if (!*c) more = 0; + else { + c++; + if (!n) + continue; + } /* check a client's extension against our support */ @@ -429,6 +436,10 @@ handshake_0405(struct libwebsocket *wsi) wsi->active_extensions_user[ wsi->count_active_extensions] = malloc(ext->per_session_data_size); + memset(wsi->active_extensions_user[ + wsi->count_active_extensions], 0, + ext->per_session_data_size); + wsi->active_extensions[ wsi->count_active_extensions] = ext; @@ -441,6 +452,7 @@ handshake_0405(struct libwebsocket *wsi) wsi->count_active_extensions], NULL, 0); wsi->count_active_extensions++; + fprintf(stderr, "wsi->count_active_extensions <- %d", wsi->count_active_extensions); ext++; } @@ -479,17 +491,23 @@ handshake_0405(struct libwebsocket *wsi) wsi->masking_key_04); } - /* okay send the handshake response accepting the connection */ + if (!lws_any_extension_handled(context, wsi, + LWS_EXT_CALLBACK_HANDSHAKE_REPLY_TX, + response, p - response)) { + + /* okay send the handshake response accepting the connection */ + + debug("issuing response packet %d len\n", (int)(p - response)); + #ifdef DEBUG + fwrite(response, 1, p - response, stderr); + #endif + n = libwebsocket_write(wsi, (unsigned char *)response, + p - response, LWS_WRITE_HTTP); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + goto bail; + } - debug("issuing response packet %d len\n", (int)(p - response)); -#ifdef DEBUG - fwrite(response, 1, p - response, stderr); -#endif - n = libwebsocket_write(wsi, (unsigned char *)response, - p - response, LWS_WRITE_HTTP); - if (n < 0) { - fprintf(stderr, "ERROR writing to socket"); - goto bail; } /* alright clean up and set ourselves into established state */ @@ -565,6 +583,10 @@ libwebsocket_read(struct libwebsocket_context *context, struct libwebsocket *wsi #endif switch (wsi->mode) { + case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY: + case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE: + case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY: + case LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT: case LWS_CONNMODE_WS_CLIENT: for (n = 0; n < len; n++) libwebsocket_client_rx_sm(wsi, *buf++); @@ -582,7 +604,9 @@ libwebsocket_read(struct libwebsocket_context *context, struct libwebsocket *wsi if (wsi->parser_state != WSI_PARSING_COMPLETE) break; - debug("libwebsocket_parse sees parsing complete\n"); + fprintf(stderr, "seem to be serving, mode is %d\n", wsi->mode); + + fprintf(stderr, "libwebsocket_parse sees parsing complete\n"); /* is this websocket protocol or normal http 1.0? */ @@ -596,6 +620,10 @@ libwebsocket_read(struct libwebsocket_context *context, struct libwebsocket *wsi return 0; } + if (!wsi->protocol) { + fprintf(stderr, "NULL protocol coming on libwebsocket_read\n"); + } + /* * It's websocket * @@ -659,13 +687,13 @@ libwebsocket_read(struct libwebsocket_context *context, struct libwebsocket *wsi switch (wsi->ietf_spec_revision) { case 0: /* applies to 76 and 00 */ wsi->xor_mask = xor_no_mask; - if (handshake_00(wsi)) + if (handshake_00(context, wsi)) goto bail; break; case 4: /* 04 */ wsi->xor_mask = xor_mask_04; debug("libwebsocket_parse calling handshake_04\n"); - if (handshake_0405(wsi)) + if (handshake_0405(context, wsi)) goto bail; break; case 5: @@ -673,7 +701,7 @@ libwebsocket_read(struct libwebsocket_context *context, struct libwebsocket *wsi case 7: wsi->xor_mask = xor_mask_05; debug("libwebsocket_parse calling handshake_04\n"); - if (handshake_0405(wsi)) + if (handshake_0405(context, wsi)) goto bail; break; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 95d12497..82696a5e 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -153,6 +153,7 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, int ret; int m; struct lws_tokens eff_buf; + struct libwebsocket_extension *ext; if (!wsi) return; @@ -250,7 +251,8 @@ just_kill_connection: * remove this fd from wsi mapping hashtable */ - delete_from_fd(context, wsi->sock); + if (wsi->sock) + delete_from_fd(context, wsi->sock); /* delete it from the internal poll list if still present */ @@ -267,8 +269,8 @@ just_kill_connection: } /* remove also from external POLL support via protocol 0 */ - - context->protocols[0].callback(context, wsi, + if (wsi->sock) + context->protocols[0].callback(context, wsi, LWS_CALLBACK_DEL_POLL_FD, (void *)(long)wsi->sock, NULL, 0); wsi->state = WSI_STATE_DEAD_SOCKET; @@ -294,12 +296,28 @@ just_kill_connection: free(wsi->active_extensions_user[n]); } + /* + * inform all extensions in case they tracked this guy out of band + * even though not active on him specifically + */ + + ext = context->extensions; + while (ext && ext->callback) { + ext->callback(context, ext, wsi, + LWS_EXT_CALLBACK_DESTROY_ANY_WSI_CLOSING, + NULL, NULL, 0); + ext++; + } + /* free up his parsing allocations */ for (n = 0; n < WSI_TOKEN_COUNT; n++) if (wsi->utf8_token[n].token) free(wsi->utf8_token[n].token); + if (wsi->c_address) + free(wsi->c_address); + /* fprintf(stderr, "closing fd=%d\n", wsi->sock); */ #ifdef LWS_OPENSSL_SUPPORT @@ -508,7 +526,7 @@ int lws_send_pipe_choked(struct libwebsocket *wsi) return 0; } -static int +int lws_handle_POLLOUT_event(struct libwebsocket_context *context, struct libwebsocket *wsi, struct pollfd *pollfd) { @@ -516,8 +534,24 @@ lws_handle_POLLOUT_event(struct libwebsocket_context *context, int n; int ret; int m; + int handled = 0; - if (!wsi->extension_data_pending) + for (n = 0; n < wsi->count_active_extensions; n++) { + if (!wsi->active_extensions[n]->callback) + continue; + + m = wsi->active_extensions[n]->callback(context, + wsi->active_extensions[n], wsi, + LWS_EXT_CALLBACK_IS_WRITEABLE, + wsi->active_extensions_user[n], NULL, 0); + if (m > handled) + handled = m; + } + + if (handled == 1) + goto notify_action; + + if (!wsi->extension_data_pending || handled == 2) goto user_service; /* @@ -597,12 +631,16 @@ lws_handle_POLLOUT_event(struct libwebsocket_context *context, user_service: /* one shot */ - pollfd->events &= ~POLLOUT; + if (pollfd) { + pollfd->events &= ~POLLOUT; - /* external POLL support via protocol 0 */ - context->protocols[0].callback(context, wsi, - LWS_CALLBACK_CLEAR_MODE_POLL_FD, - (void *)(long)wsi->sock, NULL, POLLOUT); + /* external POLL support via protocol 0 */ + context->protocols[0].callback(context, wsi, + LWS_CALLBACK_CLEAR_MODE_POLL_FD, + (void *)(long)wsi->sock, NULL, POLLOUT); + } + +notify_action: if (wsi->mode == LWS_CONNMODE_WS_CLIENT) n = LWS_CALLBACK_CLIENT_WRITEABLE; @@ -616,6 +654,721 @@ user_service: +void +libwebsocket_service_timeout_check(struct libwebsocket_context *context, + struct libwebsocket *wsi, unsigned int sec) +{ + int n; + + /* + * if extensions want in on it (eg, we are a mux parent) + * give them a chance to service child timeouts + */ + + for (n = 0; n < wsi->count_active_extensions; n++) + wsi->active_extensions[n]->callback( + context, wsi->active_extensions[n], + wsi, LWS_EXT_CALLBACK_1HZ, + wsi->active_extensions_user[n], NULL, sec); + + if (!wsi->pending_timeout) + return; + + /* + * if we went beyond the allowed time, kill the + * connection + */ + + if (sec > wsi->pending_timeout_limit) { + fprintf(stderr, "TIMEDOUT WAITING\n"); + libwebsocket_close_and_free_session(context, + wsi, LWS_CLOSE_STATUS_NOSTATUS); + } +} + +struct libwebsocket * +libwebsocket_create_new_server_wsi(struct libwebsocket_context *context) +{ + struct libwebsocket *new_wsi; + int n; + + new_wsi = malloc(sizeof(struct libwebsocket)); + if (new_wsi == NULL) { + fprintf(stderr, "Out of memory for new connection\n"); + return NULL; + } + + memset(new_wsi, 0, sizeof (struct libwebsocket)); + new_wsi->count_active_extensions = 0; + new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + + /* intialize the instance struct */ + + new_wsi->state = WSI_STATE_HTTP; + new_wsi->name_buffer_pos = 0; + new_wsi->mode = LWS_CONNMODE_WS_SERVING; + + for (n = 0; n < WSI_TOKEN_COUNT; n++) { + new_wsi->utf8_token[n].token = NULL; + new_wsi->utf8_token[n].token_len = 0; + } + + /* + * these can only be set once the protocol is known + * we set an unestablished connection's protocol pointer + * to the start of the supported list, so it can look + * for matching ones during the handshake + */ + new_wsi->protocol = context->protocols; + new_wsi->user_space = NULL; + + /* + * Default protocol is 76 / 00 + * After 76, there's a header specified to inform which + * draft the client wants, when that's seen we modify + * the individual connection's spec revision accordingly + */ + new_wsi->ietf_spec_revision = 0; + + return new_wsi; +} + +char * +libwebsockets_generate_client_handshake(struct libwebsocket_context *context, + struct libwebsocket *wsi, char *pkt) +{ + char hash[20]; + char *p = pkt; + int n; + struct libwebsocket_extension *ext; + int ext_count = 0; + unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + MAX_BROADCAST_PAYLOAD + + LWS_SEND_BUFFER_POST_PADDING]; + static const char magic_websocket_guid[] = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + /* + * create the random key + */ + + n = libwebsockets_get_random(context, hash, 16); + if (n != 16) { + fprintf(stderr, "Unable to read from random dev %s\n", + SYSTEM_RANDOM_FILEPATH); + free(wsi->c_path); + free(wsi->c_host); + if (wsi->c_origin) + free(wsi->c_origin); + if (wsi->c_protocol) + free(wsi->c_protocol); + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return NULL; + } + + lws_b64_encode_string(hash, 16, wsi->key_b64, + sizeof wsi->key_b64); + + /* + * 00 example client handshake + * + * GET /socket.io/websocket HTTP/1.1 + * Upgrade: WebSocket + * Connection: Upgrade + * Host: 127.0.0.1:9999 + * Origin: http://127.0.0.1 + * Sec-WebSocket-Key1: 1 0 2#0W 9 89 7 92 ^ + * Sec-WebSocket-Key2: 7 7Y 4328 B2v[8(z1 + * Cookie: socketio=websocket + * + * (Á®Ä0¶†≥ + * + * 04 example client handshake + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Sec-WebSocket-Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 4 + */ + + p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", wsi->c_path); + + if (wsi->ietf_spec_revision == 0) { + unsigned char spaces_1, spaces_2; + unsigned int max_1, max_2; + unsigned int num_1, num_2; + unsigned long product_1, product_2; + char key_1[40]; + char key_2[40]; + unsigned int seed; + unsigned int count; + char challenge[16]; + + libwebsockets_get_random(context, &spaces_1, + sizeof(char)); + libwebsockets_get_random(context, &spaces_2, + sizeof(char)); + + spaces_1 = (spaces_1 % 12) + 1; + spaces_2 = (spaces_2 % 12) + 1; + + max_1 = 4294967295 / spaces_1; + max_2 = 4294967295 / spaces_2; + + libwebsockets_get_random(context, &num_1, sizeof(int)); + libwebsockets_get_random(context, &num_2, sizeof(int)); + + num_1 = (num_1 % max_1); + num_2 = (num_2 % max_2); + + challenge[0] = num_1 >> 24; + challenge[1] = num_1 >> 16; + challenge[2] = num_1 >> 8; + challenge[3] = num_1; + challenge[4] = num_2 >> 24; + challenge[5] = num_2 >> 16; + challenge[6] = num_2 >> 8; + challenge[7] = num_2; + + product_1 = num_1 * spaces_1; + product_2 = num_2 * spaces_2; + + sprintf(key_1, "%lu", product_1); + sprintf(key_2, "%lu", product_2); + + libwebsockets_get_random(context, &seed, sizeof(int)); + libwebsockets_get_random(context, &count, sizeof(int)); + + libwebsockets_00_spam(key_1, (count % 12) + 1, seed); + + libwebsockets_get_random(context, &seed, sizeof(int)); + libwebsockets_get_random(context, &count, sizeof(int)); + + libwebsockets_00_spam(key_2, (count % 12) + 1, seed); + + libwebsockets_get_random(context, &seed, sizeof(int)); + + libwebsockets_00_spaceout(key_1, spaces_1, seed); + libwebsockets_00_spaceout(key_2, spaces_2, seed >> 16); + + p += sprintf(p, "Upgrade: WebSocket\x0d\x0a" + "Connection: Upgrade\x0d\x0aHost: %s\x0d\x0a", + wsi->c_host); + if (wsi->c_origin) + p += sprintf(p, "Origin: %s\x0d\x0a", + wsi->c_origin); + + if (wsi->c_protocol) + p += sprintf(p, "Sec-WebSocket-Protocol: %s" + "\x0d\x0a", wsi->c_protocol); + + p += sprintf(p, "Sec-WebSocket-Key1: %s\x0d\x0a", + key_1); + p += sprintf(p, "Sec-WebSocket-Key2: %s\x0d\x0a", + key_2); + + /* give userland a chance to append, eg, cookies */ + + context->protocols[0].callback(context, wsi, + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + NULL, &p, (pkt + sizeof(pkt)) - p - 12); + + p += sprintf(p, "\x0d\x0a"); + + if (libwebsockets_get_random(context, p, 8) != 8) + return NULL; + memcpy(&challenge[8], p, 8); + p += 8; + + /* precompute what we want to see from the server */ + + MD5((unsigned char *)challenge, 16, + (unsigned char *)wsi->initial_handshake_hash_base64); + + goto issue_hdr; + } + + p += sprintf(p, "Host: %s\x0d\x0a", wsi->c_host); + p += sprintf(p, "Upgrade: websocket\x0d\x0a"); + p += sprintf(p, "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Key: "); + strcpy(p, wsi->key_b64); + p += strlen(wsi->key_b64); + p += sprintf(p, "\x0d\x0a"); + if (wsi->c_origin) + p += sprintf(p, "Sec-WebSocket-Origin: %s\x0d\x0a", + wsi->c_origin); + if (wsi->c_protocol) + p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", + wsi->c_protocol); + + /* tell the server what extensions we could support */ + + p += sprintf(p, "Sec-WebSocket-Extensions: "); + + ext =context->extensions; + while (ext && ext->callback) { + + n = 0; + n = context->protocols[0].callback(context, wsi, + LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, + wsi->user_space, (char *)ext->name, 0); + + /* + * zero return from callback means + * go ahead and allow the extension, + * it's what we get if the callback is + * unhandled + */ + + if (n) { + ext++; + continue; + } + + /* apply it */ + + if (ext_count) + *p++ = ','; + p += sprintf(p, "%s", ext->name); + ext_count++; + + ext++; + } + + p += sprintf(p, "\x0d\x0a"); + + if (wsi->ietf_spec_revision) + p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", + wsi->ietf_spec_revision); + + /* give userland a chance to append, eg, cookies */ + + context->protocols[0].callback(context, wsi, + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + NULL, &p, (pkt + sizeof(pkt)) - p - 12); + + p += sprintf(p, "\x0d\x0a"); + + /* prepare the expected server accept response */ + + strcpy((char *)buf, wsi->key_b64); + strcpy((char *)&buf[strlen((char *)buf)], magic_websocket_guid); + + SHA1(buf, strlen((char *)buf), (unsigned char *)hash); + + lws_b64_encode_string(hash, 20, + wsi->initial_handshake_hash_base64, + sizeof wsi->initial_handshake_hash_base64); + +issue_hdr: + + /* done with these now */ + + free(wsi->c_path); + free(wsi->c_host); + if (wsi->c_origin) + free(wsi->c_origin); + + return p; +} + +int +lws_client_interpret_server_handshake(struct libwebsocket_context *context, + struct libwebsocket *wsi) +{ + unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + MAX_BROADCAST_PAYLOAD + + LWS_SEND_BUFFER_POST_PADDING]; + char pkt[1024]; + char *p = &pkt[0]; + const char *pc; + const char *c; + int more = 1; + int okay = 0; + char ext_name[128]; + struct libwebsocket_extension *ext; + void *v; + int len; + int n; + static const char magic_websocket_04_masking_guid[] = + "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; + + /* + * 00 / 76 --> + * + * HTTP/1.1 101 WebSocket Protocol Handshake + * Upgrade: WebSocket + * Connection: Upgrade + * Sec-WebSocket-Origin: http://127.0.0.1 + * Sec-WebSocket-Location: ws://127.0.0.1:9999/socket.io/websocket + * + * xxxxxxxxxxxxxxxx + */ + + if (wsi->ietf_spec_revision == 0) { + if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || + !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || + !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || + !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || + (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && + wsi->c_protocol != NULL)) { + fprintf(stderr, "libwebsocket_client_handshake " + "missing required header(s)\n"); + pkt[len] = '\0'; + fprintf(stderr, "%s", pkt); + goto bail3; + } + + strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); + if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, + "101 websocket protocol handshake")) { + fprintf(stderr, "libwebsocket_client_handshake " + "server sent bad HTTP response '%s'\n", + wsi->utf8_token[WSI_TOKEN_HTTP].token); + goto bail3; + } + + if (wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len < + 16) { + fprintf(stderr, "libwebsocket_client_handshake " + "challenge reply too short %d\n", + wsi->utf8_token[ + WSI_TOKEN_CHALLENGE].token_len); + pkt[len] = '\0'; + fprintf(stderr, "%s", pkt); + goto bail3; + + } + + goto select_protocol; + } + + /* + * well, what the server sent looked reasonable for syntax. + * Now let's confirm it sent all the necessary headers + */ +#if 0 + fprintf(stderr, "WSI_TOKEN_HTTP: %d\n", wsi->utf8_token[WSI_TOKEN_HTTP].token_len); + fprintf(stderr, "WSI_TOKEN_UPGRADE: %d\n", wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len); + fprintf(stderr, "WSI_TOKEN_CONNECTION: %d\n", wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len); + fprintf(stderr, "WSI_TOKEN_ACCEPT: %d\n", wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len); + fprintf(stderr, "WSI_TOKEN_NONCE: %d\n", wsi->utf8_token[WSI_TOKEN_NONCE].token_len); + fprintf(stderr, "WSI_TOKEN_PROTOCOL: %d\n", wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len); +#endif + if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || + !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || + !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || + !wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len || + (!wsi->utf8_token[WSI_TOKEN_NONCE].token_len && + wsi->ietf_spec_revision == 4) || + (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && + wsi->c_protocol != NULL)) { + fprintf(stderr, "libwebsocket_client_handshake " + "missing required header(s)\n"); + pkt[len] = '\0'; + fprintf(stderr, "%s", pkt); + goto bail3; + } + + /* + * Everything seems to be there, now take a closer look at what + * is in each header + */ + + strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); + if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, + "101 switching protocols")) { + fprintf(stderr, "libwebsocket_client_handshake " + "server sent bad HTTP response '%s'\n", + wsi->utf8_token[WSI_TOKEN_HTTP].token); + goto bail3; + } + + strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token); + if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token, + "websocket")) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad Upgrade header '%s'\n", + wsi->utf8_token[WSI_TOKEN_UPGRADE].token); + goto bail3; + } + + strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token); + if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token, + "upgrade")) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad Connection hdr '%s'\n", + wsi->utf8_token[WSI_TOKEN_CONNECTION].token); + goto bail3; + } + +select_protocol: + pc = wsi->c_protocol; + if (pc == NULL) + fprintf(stderr, "lws_client_interpret_server_handshake: NULL c_protocol\n"); + else + fprintf(stderr, "lws_client_interpret_server_handshake: cPprotocol='%s'\n", pc); + + /* + * confirm the protocol the server wants to talk was in the list + * of protocols we offered + */ + + if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) { + + fprintf(stderr, "lws_client_interpret_server_handshake WSI_TOKEN_PROTOCOL is null\n"); + /* + * no protocol name to work from, + * default to first protocol + */ + wsi->protocol = &context->protocols[0]; + + free(wsi->c_protocol); + + goto check_accept; + } + + while (*pc && !okay) { + if ((!strncmp(pc, + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) && + (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' || + pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) { + okay = 1; + continue; + } + while (*pc && *pc != ',') + pc++; + while (*pc && *pc != ' ') + pc++; + } + + /* done with him now */ + + if (wsi->c_protocol) + free(wsi->c_protocol); + + + if (!okay) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad protocol '%s'\n", + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); + goto bail2; + } + + /* + * identify the selected protocol struct and set it + */ + n = 0; + wsi->protocol = NULL; + while (context->protocols[n].callback) { + if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, + context->protocols[n].name) == 0) + wsi->protocol = &context->protocols[n]; + n++; + } + + if (wsi->protocol == NULL) { + fprintf(stderr, "libwebsocket_client_handshake server " + "requested protocol '%s', which we " + "said we supported but we don't!\n", + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); + goto bail2; + } + + + /* instantiate the accepted extensions */ + + if (!wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token_len) { + fprintf(stderr, "no client extenstions allowed by server \n"); + goto check_accept; + } + + /* + * break down the list of server accepted extensions + * and go through matching them or identifying bogons + */ + + c = wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token; + n = 0; + while (more) { + + if (*c && (*c != ',' && *c != ' ' && *c != '\t')) { + ext_name[n] = *c++; + if (n < sizeof(ext_name) - 1) + n++; + continue; + } + ext_name[n] = '\0'; + if (!*c) + more = 0; + else { + c++; + if (!n) + continue; + } + + /* check we actually support it */ + + fprintf(stderr, "checking client ext %s\n", ext_name); + + n = 0; + ext = wsi->protocol->owning_server->extensions; + while (ext && ext->callback) { + + if (strcmp(ext_name, ext->name)) { + ext++; + continue; + } + + n = 1; + + fprintf(stderr, "instantiating client ext %s\n", ext_name); + + /* instantiate the extension on this conn */ + + wsi->active_extensions_user[ + wsi->count_active_extensions] = + malloc(ext->per_session_data_size); + wsi->active_extensions[ + wsi->count_active_extensions] = ext; + + /* allow him to construct his context */ + + ext->callback(wsi->protocol->owning_server, + ext, wsi, + LWS_EXT_CALLBACK_CLIENT_CONSTRUCT, + wsi->active_extensions_user[ + wsi->count_active_extensions], + NULL, 0); + + wsi->count_active_extensions++; + + ext++; + } + + if (n == 0) { + fprintf(stderr, "Server said we should use" + "an unknown extension '%s'!\n", ext_name); + goto bail2; + } + + n = 0; + } + + +check_accept: + + if (wsi->ietf_spec_revision == 0) { + + if (memcmp(wsi->initial_handshake_hash_base64, + wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 16)) { + fprintf(stderr, "libwebsocket_client_handshake " + "failed 00 challenge compare\n"); + pkt[len] = '\0'; + fprintf(stderr, "%s", pkt); + goto bail2; + } + + goto accept_ok; + } + + /* + * Confirm his accept token is the one we precomputed + */ + + if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token, + wsi->initial_handshake_hash_base64)) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad ACCEPT '%s' vs computed '%s'\n", + wsi->utf8_token[WSI_TOKEN_ACCEPT].token, + wsi->initial_handshake_hash_base64); + goto bail2; + } + + if (wsi->ietf_spec_revision == 4) { + /* + * Calculate the 04 masking key to use when + * sending data to server + */ + + strcpy((char *)buf, wsi->key_b64); + p = (char *)buf + strlen(wsi->key_b64); + strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token); + p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len; + strcpy(p, magic_websocket_04_masking_guid); + SHA1(buf, strlen((char *)buf), wsi->masking_key_04); + } + accept_ok: + + /* allocate the per-connection user memory (if any) */ + + if (wsi->protocol->per_session_data_size) { + wsi->user_space = malloc( + wsi->protocol->per_session_data_size); + if (wsi->user_space == NULL) { + fprintf(stderr, "Out of memory for " + "conn user space\n"); + goto bail2; + } + } else + wsi->user_space = NULL; + + /* clear his proxy connection timeout */ + + libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* mark him as being alive */ + + wsi->state = WSI_STATE_ESTABLISHED; + wsi->mode = LWS_CONNMODE_WS_CLIENT; + + fprintf(stderr, "handshake OK for protocol %s\n", + wsi->protocol->name); + + /* call him back to inform him he is up */ + + wsi->protocol->callback(context, wsi, + LWS_CALLBACK_CLIENT_ESTABLISHED, + wsi->user_space, + NULL, 0); + + /* + * inform all extensions, not just active ones since they + * already know + */ + + ext = context->extensions; + + while (ext && ext->callback) { + v = NULL; + for (n = 0; n < wsi->count_active_extensions; n++) + if (wsi->active_extensions[n] == ext) + v = wsi->active_extensions_user[n]; + + ext->callback(context, ext, wsi, + LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED, v, NULL, 0); + ext++; + } + + return 0; + +bail3: + if (wsi->c_protocol) + free(wsi->c_protocol); + +bail2: + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return 1; +} + + + /** * libwebsocket_service_fd() - Service polled socket with something waiting * @context: Websocket context @@ -642,21 +1395,10 @@ libwebsocket_service_fd(struct libwebsocket_context *context, unsigned int clilen; struct sockaddr_in cli_addr; struct timeval tv; - static const char magic_websocket_guid[] = - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - static const char magic_websocket_04_masking_guid[] = - "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; - char hash[20]; char pkt[1024]; char *p = &pkt[0]; - const char *pc; - const char *c; int more = 1; - int okay = 0; - char ext_name[128]; struct lws_tokens eff_buf; - int ext_count = 0; - struct libwebsocket_extension *ext; int opt = 1; #ifdef LWS_OPENSSL_SUPPORT @@ -677,19 +1419,9 @@ libwebsocket_service_fd(struct libwebsocket_context *context, for (n = 0; n < context->fds_count; n++) { wsi = wsi_from_fd(context, context->fds[n].fd); - if (!wsi->pending_timeout) - continue; - /* - * if we went beyond the allowed time, kill the - * connection - */ - - if (tv.tv_sec > wsi->pending_timeout_limit) { - fprintf(stderr, "TIMEDOUT WAITING\n"); - libwebsocket_close_and_free_session(context, - wsi, LWS_CLOSE_STATUS_NOSTATUS); - } + libwebsocket_service_timeout_check(context, wsi, + tv.tv_sec); } } @@ -758,16 +1490,12 @@ libwebsocket_service_fd(struct libwebsocket_context *context, /* accepting connection to main listener */ - new_wsi = malloc(sizeof(struct libwebsocket)); - if (new_wsi == NULL) { - fprintf(stderr, "Out of memory for new connection\n"); + new_wsi = libwebsocket_create_new_server_wsi(context); + if (new_wsi == NULL) break; - } - memset(new_wsi, 0, sizeof (struct libwebsocket)); new_wsi->sock = accept_fd; - new_wsi->count_active_extensions = 0; - new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + #ifdef LWS_OPENSSL_SUPPORT new_wsi->ssl = NULL; @@ -813,34 +1541,6 @@ libwebsocket_service_fd(struct libwebsocket_context *context, debug("accepted new conn port %u on fd=%d\n", ntohs(cli_addr.sin_port), accept_fd); - /* intialize the instance struct */ - - new_wsi->state = WSI_STATE_HTTP; - new_wsi->name_buffer_pos = 0; - new_wsi->mode = LWS_CONNMODE_WS_SERVING; - - for (n = 0; n < WSI_TOKEN_COUNT; n++) { - new_wsi->utf8_token[n].token = NULL; - new_wsi->utf8_token[n].token_len = 0; - } - - /* - * these can only be set once the protocol is known - * we set an unestablished connection's protocol pointer - * to the start of the supported list, so it can look - * for matching ones during the handshake - */ - new_wsi->protocol = context->protocols; - new_wsi->user_space = NULL; - - /* - * Default protocol is 76 / 00 - * After 76, there's a header specified to inform which - * draft the client wants, when that's seen we modify - * the individual connection's spec revision accordingly - */ - new_wsi->ietf_spec_revision = 0; - insert_wsi(context, new_wsi); /* @@ -1073,232 +1773,9 @@ libwebsocket_service_fd(struct libwebsocket_context *context, } #endif - /* - * create the random key - */ - - n = libwebsockets_get_random(context, hash, 16); - if (n != 16) { - fprintf(stderr, "Unable to read from random dev %s\n", - SYSTEM_RANDOM_FILEPATH); - free(wsi->c_path); - free(wsi->c_host); - if (wsi->c_origin) - free(wsi->c_origin); - if (wsi->c_protocol) - free(wsi->c_protocol); - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); + p = libwebsockets_generate_client_handshake(context, wsi, p); + if (p ==NULL) return 1; - } - - lws_b64_encode_string(hash, 16, wsi->key_b64, - sizeof wsi->key_b64); - - /* - * 00 example client handshake - * - * GET /socket.io/websocket HTTP/1.1 - * Upgrade: WebSocket - * Connection: Upgrade - * Host: 127.0.0.1:9999 - * Origin: http://127.0.0.1 - * Sec-WebSocket-Key1: 1 0 2#0W 9 89 7 92 ^ - * Sec-WebSocket-Key2: 7 7Y 4328 B2v[8(z1 - * Cookie: socketio=websocket - * - * (Á®Ä0¶†≥ - * - * 04 example client handshake - * - * GET /chat HTTP/1.1 - * Host: server.example.com - * Upgrade: websocket - * Connection: Upgrade - * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - * Sec-WebSocket-Origin: http://example.com - * Sec-WebSocket-Protocol: chat, superchat - * Sec-WebSocket-Version: 4 - */ - - p += sprintf(p, "GET %s HTTP/1.1\x0d\x0a", wsi->c_path); - - if (wsi->ietf_spec_revision == 0) { - unsigned char spaces_1, spaces_2; - unsigned int max_1, max_2; - unsigned int num_1, num_2; - unsigned long product_1, product_2; - char key_1[40]; - char key_2[40]; - unsigned int seed; - unsigned int count; - char challenge[16]; - - libwebsockets_get_random(context, &spaces_1, - sizeof(char)); - libwebsockets_get_random(context, &spaces_2, - sizeof(char)); - - spaces_1 = (spaces_1 % 12) + 1; - spaces_2 = (spaces_2 % 12) + 1; - - max_1 = 4294967295 / spaces_1; - max_2 = 4294967295 / spaces_2; - - libwebsockets_get_random(context, &num_1, sizeof(int)); - libwebsockets_get_random(context, &num_2, sizeof(int)); - - num_1 = (num_1 % max_1); - num_2 = (num_2 % max_2); - - challenge[0] = num_1 >> 24; - challenge[1] = num_1 >> 16; - challenge[2] = num_1 >> 8; - challenge[3] = num_1; - challenge[4] = num_2 >> 24; - challenge[5] = num_2 >> 16; - challenge[6] = num_2 >> 8; - challenge[7] = num_2; - - product_1 = num_1 * spaces_1; - product_2 = num_2 * spaces_2; - - sprintf(key_1, "%lu", product_1); - sprintf(key_2, "%lu", product_2); - - libwebsockets_get_random(context, &seed, sizeof(int)); - libwebsockets_get_random(context, &count, sizeof(int)); - - libwebsockets_00_spam(key_1, (count % 12) + 1, seed); - - libwebsockets_get_random(context, &seed, sizeof(int)); - libwebsockets_get_random(context, &count, sizeof(int)); - - libwebsockets_00_spam(key_2, (count % 12) + 1, seed); - - libwebsockets_get_random(context, &seed, sizeof(int)); - - libwebsockets_00_spaceout(key_1, spaces_1, seed); - libwebsockets_00_spaceout(key_2, spaces_2, seed >> 16); - - p += sprintf(p, "Upgrade: WebSocket\x0d\x0a" - "Connection: Upgrade\x0d\x0aHost: %s\x0d\x0a", - wsi->c_host); - if (wsi->c_origin) - p += sprintf(p, "Origin: %s\x0d\x0a", - wsi->c_origin); - - if (wsi->c_protocol) - p += sprintf(p, "Sec-WebSocket-Protocol: %s" - "\x0d\x0a", wsi->c_protocol); - - p += sprintf(p, "Sec-WebSocket-Key1: %s\x0d\x0a", - key_1); - p += sprintf(p, "Sec-WebSocket-Key2: %s\x0d\x0a", - key_2); - - /* give userland a chance to append, eg, cookies */ - - context->protocols[0].callback(context, wsi, - LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, - NULL, &p, (pkt + sizeof(pkt)) - p - 12); - - p += sprintf(p, "\x0d\x0a"); - - if (libwebsockets_get_random(context, p, 8) != 8) - return -1; - memcpy(&challenge[8], p, 8); - p += 8; - - /* precompute what we want to see from the server */ - - MD5((unsigned char *)challenge, 16, - (unsigned char *)wsi->initial_handshake_hash_base64); - - goto issue_hdr; - } - - p += sprintf(p, "Host: %s\x0d\x0a", wsi->c_host); - p += sprintf(p, "Upgrade: websocket\x0d\x0a"); - p += sprintf(p, "Connection: Upgrade\x0d\x0a" - "Sec-WebSocket-Key: "); - strcpy(p, wsi->key_b64); - p += strlen(wsi->key_b64); - p += sprintf(p, "\x0d\x0a"); - if (wsi->c_origin) - p += sprintf(p, "Sec-WebSocket-Origin: %s\x0d\x0a", - wsi->c_origin); - if (wsi->c_protocol) - p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", - wsi->c_protocol); - - /* tell the server what extensions we could support */ - - p += sprintf(p, "Sec-WebSocket-Extensions: "); - - ext =context->extensions; - while (ext && ext->callback) { - - n = 0; - n = context->protocols[0].callback(context, wsi, - LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, - wsi->user_space, (char *)ext->name, 0); - - /* - * zero return from callback means - * go ahead and allow the extension, - * it's what we get if the callback is - * unhandled - */ - - if (n) { - ext++; - continue; - } - - /* apply it */ - - if (ext_count) - *p++ = ','; - p += sprintf(p, "%s", ext->name); - ext_count++; - - ext++; - } - - p += sprintf(p, "\x0d\x0a"); - - if (wsi->ietf_spec_revision) - p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", - wsi->ietf_spec_revision); - - /* give userland a chance to append, eg, cookies */ - - context->protocols[0].callback(context, wsi, - LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, - NULL, &p, (pkt + sizeof(pkt)) - p - 12); - - p += sprintf(p, "\x0d\x0a"); - - /* prepare the expected server accept response */ - - strcpy((char *)buf, wsi->key_b64); - strcpy((char *)&buf[strlen((char *)buf)], magic_websocket_guid); - - SHA1(buf, strlen((char *)buf), (unsigned char *)hash); - - lws_b64_encode_string(hash, 20, - wsi->initial_handshake_hash_base64, - sizeof wsi->initial_handshake_hash_base64); - -issue_hdr: - - /* done with these now */ - - free(wsi->c_path); - free(wsi->c_host); - if (wsi->c_origin) - free(wsi->c_origin); /* send our request to the server */ @@ -1373,334 +1850,23 @@ issue_hdr: if (wsi->parser_state != WSI_PARSING_COMPLETE) break; - /* - * 00 / 76 --> - * - * HTTP/1.1 101 WebSocket Protocol Handshake - * Upgrade: WebSocket - * Connection: Upgrade - * Sec-WebSocket-Origin: http://127.0.0.1 - * Sec-WebSocket-Location: ws://127.0.0.1:9999/socket.io/websocket - * - * xxxxxxxxxxxxxxxx - */ - - if (wsi->ietf_spec_revision == 0) { - if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || - !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || - !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || - !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || - (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && - wsi->c_protocol != NULL)) { - fprintf(stderr, "libwebsocket_client_handshake " - "missing required header(s)\n"); - pkt[len] = '\0'; - fprintf(stderr, "%s", pkt); - goto bail3; - } - - strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); - if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, - "101 websocket protocol handshake")) { - fprintf(stderr, "libwebsocket_client_handshake " - "server sent bad HTTP response '%s'\n", - wsi->utf8_token[WSI_TOKEN_HTTP].token); - goto bail3; - } - - if (wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len < - 16) { - fprintf(stderr, "libwebsocket_client_handshake " - "challenge reply too short %d\n", - wsi->utf8_token[ - WSI_TOKEN_CHALLENGE].token_len); - pkt[len] = '\0'; - fprintf(stderr, "%s", pkt); - goto bail3; - - } - - goto select_protocol; - } - - /* - * well, what the server sent looked reasonable for syntax. - * Now let's confirm it sent all the necessary headers - */ - - if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || - !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || - !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || - !wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len || - (!wsi->utf8_token[WSI_TOKEN_NONCE].token_len && - wsi->ietf_spec_revision == 4) || - (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && - wsi->c_protocol != NULL)) { - fprintf(stderr, "libwebsocket_client_handshake " - "missing required header(s)\n"); - pkt[len] = '\0'; - fprintf(stderr, "%s", pkt); - goto bail3; - } - - /* - * Everything seems to be there, now take a closer look at what - * is in each header - */ - - strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); - if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, - "101 switching protocols")) { - fprintf(stderr, "libwebsocket_client_handshake " - "server sent bad HTTP response '%s'\n", - wsi->utf8_token[WSI_TOKEN_HTTP].token); - goto bail3; - } - - strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token); - if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token, - "websocket")) { - fprintf(stderr, "libwebsocket_client_handshake server " - "sent bad Upgrade header '%s'\n", - wsi->utf8_token[WSI_TOKEN_UPGRADE].token); - goto bail3; - } - - strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token); - if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token, - "upgrade")) { - fprintf(stderr, "libwebsocket_client_handshake server " - "sent bad Connection hdr '%s'\n", - wsi->utf8_token[WSI_TOKEN_CONNECTION].token); - goto bail3; - } - -select_protocol: - pc = wsi->c_protocol; - - /* - * confirm the protocol the server wants to talk was in the list - * of protocols we offered - */ - - if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) { - - /* - * no protocol name to work from, - * default to first protocol - */ - wsi->protocol = &context->protocols[0]; - - free(wsi->c_protocol); - - goto check_accept; - } - - while (*pc && !okay) { - if ((!strncmp(pc, - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) && - (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' || - pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) { - okay = 1; - continue; - } - while (*pc && *pc != ',') - pc++; - while (*pc && *pc != ' ') - pc++; - } - - /* done with him now */ - - if (wsi->c_protocol) - free(wsi->c_protocol); - - - if (!okay) { - fprintf(stderr, "libwebsocket_client_handshake server " - "sent bad protocol '%s'\n", - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); - goto bail2; - } - - /* - * identify the selected protocol struct and set it - */ - n = 0; - wsi->protocol = NULL; - while (context->protocols[n].callback) { - if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, - context->protocols[n].name) == 0) - wsi->protocol = &context->protocols[n]; - n++; - } - - if (wsi->protocol == NULL) { - fprintf(stderr, "libwebsocket_client_handshake server " - "requested protocol '%s', which we " - "said we supported but we don't!\n", - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); - goto bail2; - } - - - /* instantiate the accepted extensions */ - - if (!wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token_len) - goto check_accept; - - /* - * break down the list of server accepted extensions - * and go through matching them or identifying bogons - */ - - c = wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token; - n = 0; - while (more) { - - if (*c && *c != ',') { - ext_name[n] = *c++; - if (n < sizeof(ext_name) - 1) - n++; - continue; - } - ext_name[n] = '\0'; - if (!*c) - more = 0; - - /* check we actually support it */ - - n = 0; - ext = wsi->protocol->owning_server->extensions; - while (ext && ext->callback) { - - if (strcmp(ext_name, ext->name)) { - ext++; - continue; - } - - n = 1; - - /* instantiate the extension on this conn */ - - wsi->active_extensions_user[ - wsi->count_active_extensions] = - malloc(ext->per_session_data_size); - wsi->active_extensions[ - wsi->count_active_extensions] = ext; - - /* allow him to construct his context */ - - ext->callback(wsi->protocol->owning_server, - ext, wsi, - LWS_EXT_CALLBACK_CLIENT_CONSTRUCT, - wsi->active_extensions_user[ - wsi->count_active_extensions], - NULL, 0); - - wsi->count_active_extensions++; - - ext++; - } - - if (n == 0) { - fprintf(stderr, "Server said we should use" - "an unknown extension '%s'!\n", ext_name); - goto bail2; - } - - n = 0; - } - - - check_accept: - - if (wsi->ietf_spec_revision == 0) { - - if (memcmp(wsi->initial_handshake_hash_base64, - wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 16)) { - fprintf(stderr, "libwebsocket_client_handshake " - "failed 00 challenge compare\n"); - pkt[len] = '\0'; - fprintf(stderr, "%s", pkt); - goto bail2; - } - - goto accept_ok; - } - - /* - * Confirm his accept token is the one we precomputed - */ - - if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token, - wsi->initial_handshake_hash_base64)) { - fprintf(stderr, "libwebsocket_client_handshake server " - "sent bad ACCEPT '%s' vs computed '%s'\n", - wsi->utf8_token[WSI_TOKEN_ACCEPT].token, - wsi->initial_handshake_hash_base64); - goto bail2; - } - - if (wsi->ietf_spec_revision == 4) { - /* - * Calculate the 04 masking key to use when - * sending data to server - */ - - strcpy((char *)buf, wsi->key_b64); - p = (char *)buf + strlen(wsi->key_b64); - strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token); - p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len; - strcpy(p, magic_websocket_04_masking_guid); - SHA1(buf, strlen((char *)buf), wsi->masking_key_04); - } -accept_ok: - - /* allocate the per-connection user memory (if any) */ - - if (wsi->protocol->per_session_data_size) { - wsi->user_space = malloc( - wsi->protocol->per_session_data_size); - if (wsi->user_space == NULL) { - fprintf(stderr, "Out of memory for " - "conn user space\n"); - goto bail2; - } - } else - wsi->user_space = NULL; - - /* clear his proxy connection timeout */ - - libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - - /* mark him as being alive */ - - wsi->state = WSI_STATE_ESTABLISHED; - wsi->mode = LWS_CONNMODE_WS_CLIENT; - - fprintf(stderr, "handshake OK for protocol %s\n", - wsi->protocol->name); - - /* call him back to inform him he is up */ - - wsi->protocol->callback(context, wsi, - LWS_CALLBACK_CLIENT_ESTABLISHED, - wsi->user_space, - NULL, 0); - - break; + return lws_client_interpret_server_handshake(context, wsi); bail3: if (wsi->c_protocol) free(wsi->c_protocol); - -bail2: libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); + LWS_CLOSE_STATUS_NOSTATUS); return 1; - + + case LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT: + fprintf(stderr, "LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT\n"); + break; + + case LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD: + fprintf(stderr, "LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD\n"); + break; + case LWS_CONNMODE_WS_SERVING: case LWS_CONNMODE_WS_CLIENT: @@ -1822,6 +1988,7 @@ libwebsocket_context_destroy(struct libwebsocket_context *context) int n; int m; struct libwebsocket *wsi; + struct libwebsocket_extension *ext; for (n = 0; n < FD_HASHTABLE_MODULUS; n++) for (m = 0; m < context->fd_hashtable[n].length; m++) { @@ -1830,6 +1997,20 @@ libwebsocket_context_destroy(struct libwebsocket_context *context) LWS_CLOSE_STATUS_GOINGAWAY); } + /* + * give all extensions a chance to clean up any per-context + * allocations they might have made + */ + + ext = context->extensions; + m = LWS_EXT_CALLBACK_CLIENT_CONTEXT_DESTRUCT; + if (context->listen_port) + m = LWS_EXT_CALLBACK_SERVER_CONTEXT_DESTRUCT; + while (ext->callback) { + ext->callback(context, ext, NULL, m, NULL, NULL, 0); + ext++; + } + #ifdef WIN32 #else close(context->fd_random); @@ -1917,6 +2098,47 @@ libwebsocket_service(struct libwebsocket_context *context, int timeout_ms) return 0; } +int +lws_any_extension_handled(struct libwebsocket_context *context, + struct libwebsocket *wsi, + enum libwebsocket_extension_callback_reasons r, + void *v, size_t len) +{ + int n; + int handled = 0; + + /* maybe an extension will take care of it for us */ + + for (n = 0; n < wsi->count_active_extensions && !handled; n++) { + if (!wsi->active_extensions[n]->callback) + continue; + + handled |= wsi->active_extensions[n]->callback(context, + wsi->active_extensions[n], wsi, + r, wsi->active_extensions_user[n], v, len); + } + + return handled; +} + + +void * +lws_get_extension_user_matching_ext(struct libwebsocket *wsi, + struct libwebsocket_extension * ext) +{ + int n = 0; + + while (n < wsi->count_active_extensions) { + if (wsi->active_extensions[n] != ext) { + n++; + continue; + } + return wsi->active_extensions_user[n]; + } + + return NULL; +} + /** * libwebsocket_callback_on_writable() - Request a callback when this socket * becomes able to be written to without @@ -1931,13 +2153,32 @@ libwebsocket_callback_on_writable(struct libwebsocket_context *context, struct libwebsocket *wsi) { int n; + int handled = 0; + + /* maybe an extension will take care of it for us */ + + for (n = 0; n < wsi->count_active_extensions; n++) { + if (!wsi->active_extensions[n]->callback) + continue; + + handled |= wsi->active_extensions[n]->callback(context, + wsi->active_extensions[n], wsi, + LWS_EXT_CALLBACK_REQUEST_ON_WRITEABLE, + wsi->active_extensions_user[n], NULL, 0); + } + + if (handled) + return 1; for (n = 0; n < context->fds_count; n++) if (context->fds[n].fd == wsi->sock) { context->fds[n].events |= POLLOUT; - n = context->fds_count; + n = context->fds_count + 1; } + if (n == context->fds_count) + fprintf(stderr, "libwebsocket_callback_on_writable: failed to find socket %d\n", wsi->sock); + /* external POLL support via protocol 0 */ context->protocols[0].callback(context, wsi, LWS_CALLBACK_SET_MODE_POLL_FD, @@ -2053,9 +2294,10 @@ libwebsocket_rx_flow_control(struct libwebsocket *wsi, int enable) LWS_CALLBACK_CLEAR_MODE_POLL_FD, (void *)(long)wsi->sock, NULL, POLLIN); - - fprintf(stderr, "libwebsocket_callback_on_writable " +#if 0 + fprintf(stderr, "libwebsocket_rx_flow_control " "unable to find socket\n"); +#endif return 1; } @@ -2172,6 +2414,7 @@ libwebsocket_create_context(int port, const char *interf, int gid, int uid, unsigned int options) { int n; + int m; int sockfd = 0; int fd; struct sockaddr_in serv_addr, cli_addr; @@ -2570,6 +2813,20 @@ libwebsocket_create_context(int port, const char *interf, (void *)(long)fd, NULL, POLLIN); } + /* + * give all extensions a chance to create any per-context + * allocations they need + */ + + m = LWS_EXT_CALLBACK_CLIENT_CONTEXT_CONSTRUCT; + if (port) + m = LWS_EXT_CALLBACK_SERVER_CONTEXT_CONSTRUCT; + while (extensions->callback) { + extensions->callback(context, extensions, + NULL, m, NULL, NULL, 0); + extensions++; + } + return context; } diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 95a7ae0e..e92f8929 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -40,7 +40,7 @@ extern "C" { #endif #define CONTEXT_PORT_NO_LISTEN 0 - +#define MAX_MUX_RECURSION 2 enum libwebsocket_context_options { LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK = 1, @@ -74,12 +74,25 @@ enum libwebsocket_callback_reasons { }; enum libwebsocket_extension_callback_reasons { + LWS_EXT_CALLBACK_SERVER_CONTEXT_CONSTRUCT, + LWS_EXT_CALLBACK_CLIENT_CONTEXT_CONSTRUCT, + LWS_EXT_CALLBACK_SERVER_CONTEXT_DESTRUCT, + LWS_EXT_CALLBACK_CLIENT_CONTEXT_DESTRUCT, LWS_EXT_CALLBACK_CONSTRUCT, LWS_EXT_CALLBACK_CLIENT_CONSTRUCT, LWS_EXT_CALLBACK_DESTROY, + LWS_EXT_CALLBACK_DESTROY_ANY_WSI_CLOSING, + LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED, LWS_EXT_CALLBACK_PACKET_RX_PREPARSE, LWS_EXT_CALLBACK_PACKET_TX_PRESEND, + LWS_EXT_CALLBACK_PACKET_TX_DO_SEND, + LWS_EXT_CALLBACK_HANDSHAKE_REPLY_TX, LWS_EXT_CALLBACK_FLUSH_PENDING_TX, + LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX, + LWS_EXT_CALLBACK_CAN_PROXY_CLIENT_CONNECTION, + LWS_EXT_CALLBACK_1HZ, + LWS_EXT_CALLBACK_REQUEST_ON_WRITEABLE, + LWS_EXT_CALLBACK_IS_WRITEABLE, }; enum libwebsocket_write_protocol { @@ -141,6 +154,7 @@ enum lws_token_indexes { WSI_TOKEN_ACCEPT, WSI_TOKEN_NONCE, WSI_TOKEN_HTTP, + WSI_TOKEN_MUXURL, /* always last real token index*/ WSI_TOKEN_COUNT, @@ -148,7 +162,8 @@ enum lws_token_indexes { WSI_TOKEN_NAME_PART, WSI_TOKEN_SKIPPING, WSI_TOKEN_SKIPPING_SAW_CR, - WSI_PARSING_COMPLETE + WSI_PARSING_COMPLETE, + WSI_INIT_TOKEN_MUXURL, }; /* @@ -523,6 +538,7 @@ struct libwebsocket_extension { enum libwebsocket_extension_callback_reasons reason, void *user, void *in, size_t len); size_t per_session_data_size; + void * per_context_private_data; }; @@ -574,11 +590,12 @@ libwebsocket_service_fd(struct libwebsocket_context *context, /* * this is the frame nonce plus two header plus 8 length + * there's an additional two for mux extension per mux nesting level * 2 byte prepend on close will already fit because control frames cannot use * the big length style */ -#define LWS_SEND_BUFFER_PRE_PADDING (4 + 10) +#define LWS_SEND_BUFFER_PRE_PADDING (4 + 10 + (2 * MAX_MUX_RECURSION)) #define LWS_SEND_BUFFER_POST_PADDING 1 extern int diff --git a/lib/parsers.c b/lib/parsers.c index 165f758c..f67af4f3 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -45,6 +45,7 @@ const struct lws_tokens lws_tokens[WSI_TOKEN_COUNT] = { /* [WSI_TOKEN_ACCEPT] = */{ "Sec-WebSocket-Accept:", 21 }, /* [WSI_TOKEN_NONCE] = */{ "Sec-WebSocket-Nonce:", 20 }, /* [WSI_TOKEN_HTTP] = */{ "HTTP/1.1 ", 9 }, +/* [WSI_TOKEN_MUXURL] = */{ "", -1 }, }; @@ -70,6 +71,8 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) case WSI_TOKEN_NONCE: case WSI_TOKEN_EXTENSIONS: case WSI_TOKEN_HTTP: + case WSI_TOKEN_MUXURL: + debug("WSI_TOKEN_(%d) '%c'\n", wsi->parser_state, c); /* collect into malloc'd buffers */ @@ -107,6 +110,7 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) wsi->utf8_token[wsi->parser_state].token[ wsi->utf8_token[wsi->parser_state].token_len] = '\0'; wsi->parser_state = WSI_TOKEN_SKIPPING_SAW_CR; + fprintf(stderr, "*\n"); break; } @@ -151,6 +155,15 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) wsi->parser_state = WSI_PARSING_COMPLETE; break; + case WSI_INIT_TOKEN_MUXURL: + wsi->parser_state = WSI_TOKEN_MUXURL; + wsi->current_alloc_len = LWS_INITIAL_HDR_ALLOC; + + wsi->utf8_token[wsi->parser_state].token = + malloc(wsi->current_alloc_len); + wsi->utf8_token[wsi->parser_state].token_len = 0; + break; + /* collecting and checking a name part */ case WSI_TOKEN_NAME_PART: debug("WSI_TOKEN_NAME_PART '%c'\n", c); @@ -269,10 +282,16 @@ xor_mask_05(struct libwebsocket *wsi, unsigned char c) -static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) +int +libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) { int n; unsigned char buf[20 + 4]; + struct lws_tokens eff_buf; + int handled; + int m; + +// fprintf(stderr, "RX: %02X ", c); switch (wsi->lws_rx_parse_state) { case LWS_RXPS_NEW: @@ -693,6 +712,8 @@ spill: * layer? If so service it and hide it from the user callback */ + debug("spill on %s\n", wsi->protocol->name); + switch (wsi->opcode) { case LWS_WS_OPCODE_07__CLOSE: /* is this an acknowledgement of our close? */ @@ -729,8 +750,45 @@ spill: wsi->rx_user_buffer_head = 0; return 0; - default: + case LWS_WS_OPCODE_07__TEXT_FRAME: + case LWS_WS_OPCODE_07__BINARY_FRAME: break; + + default: + + debug("passing opcode %x up to exts\n", wsi->opcode); + + /* + * It's something special we can't understand here. + * Pass the payload up to the extension's parsing + * state machine. + */ + + eff_buf.token = &wsi->rx_user_buffer[ + LWS_SEND_BUFFER_PRE_PADDING]; + eff_buf.token_len = wsi->rx_user_buffer_head; + + handled = 0; + for (n = 0; n < wsi->count_active_extensions; n++) { + m = wsi->active_extensions[n]->callback( + wsi->protocol->owning_server, + wsi->active_extensions[n], wsi, + LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX, + wsi->active_extensions_user[n], + &eff_buf, 0); + if (m) + handled = 1; + } + + if (!handled) { + /* kill the connection */ + fprintf(stderr, "Unhandled extended opcode " + "0x%x\n", wsi->opcode); + return -1; + } + + wsi->rx_user_buffer_head = 0; + return 0; } /* @@ -748,6 +806,9 @@ spill: wsi->user_space, &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], wsi->rx_user_buffer_head); + else + fprintf(stderr, "No callback on payload spill!\n"); + wsi->rx_user_buffer_head = 0; break; } @@ -768,6 +829,11 @@ int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c) int n; unsigned char buf[20 + 4]; int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; + int handled; + struct lws_tokens eff_buf; + int m; + + debug(" CRX: %02X %d\n", c, wsi->lws_rx_parse_state); switch (wsi->lws_rx_parse_state) { case LWS_RXPS_NEW: @@ -1083,6 +1149,9 @@ issue: if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER) break; spill: + + handled = 0; + /* * is this frame a control packet we should take care of at this * layer? If so service it and hide it from the user callback @@ -1124,7 +1193,42 @@ spill: callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; break; + case LWS_WS_OPCODE_07__CONTINUATION: + case LWS_WS_OPCODE_07__TEXT_FRAME: + case LWS_WS_OPCODE_07__BINARY_FRAME: + break; + default: + + fprintf(stderr, "Reserved opcode 0x%2X\n", wsi->opcode); + /* + * It's something special we can't understand here. + * Pass the payload up to the extension's parsing + * state machine. + */ + + eff_buf.token = &wsi->rx_user_buffer[ + LWS_SEND_BUFFER_PRE_PADDING]; + eff_buf.token_len = wsi->rx_user_buffer_head; + + for (n = 0; n < wsi->count_active_extensions; n++) { + m = wsi->active_extensions[n]->callback( + wsi->protocol->owning_server, + wsi->active_extensions[n], wsi, + LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX, + wsi->active_extensions_user[n], + &eff_buf, 0); + if (m) + handled = 1; + } + + if (!handled) { + /* kill the connection */ + fprintf(stderr, "Unhandled extended opcode " + "0x%x\n", wsi->opcode); + return -1; + } + break; } @@ -1134,7 +1238,7 @@ spill: * so it can be sent straight out again using libwebsocket_write */ - if (wsi->protocol->callback) + if (!handled && wsi->protocol->callback) wsi->protocol->callback(wsi->protocol->owning_server, wsi, callback_action, wsi->user_space, @@ -1223,9 +1327,75 @@ libwebsocket_0405_frame_mask_generate(struct libwebsocket *wsi) return 0; } +void lws_stderr_hexdump(unsigned char *buf, size_t len) +{ + int n; + int m; + int start; + + fprintf(stderr, "\n"); + + for (n = 0; n < len;) { + start = n; + + fprintf(stderr, "%04X: ", start); + + for (m = 0; m < 16 && n < len; m++) + fprintf(stderr, "%02X ", buf[n++]); + while (m++ < 16) + fprintf(stderr, " "); + + fprintf(stderr, " "); + + for (m = 0; m < 16 && (start + m) < len; m++) { + if (buf[start + m] >= ' ' && buf[start + m] <= 127) + fprintf(stderr, "%c", buf[start + m]); + else + fprintf(stderr, "."); + } + while (m++ < 16) + fprintf(stderr, " "); + + fprintf(stderr, "\n"); + } + fprintf(stderr, "\n"); +} + int lws_issue_raw(struct libwebsocket *wsi, unsigned char *buf, size_t len) { int n; + int m; + + /* + * one of the extensions is carrying our data itself? Like mux? + */ + + for (n = 0; n < wsi->count_active_extensions; n++) { + /* + * there can only be active extensions after handshake completed + * so we can rely on protocol being set already in here + */ + m = wsi->active_extensions[n]->callback( + wsi->protocol->owning_server, + wsi->active_extensions[n], wsi, + LWS_EXT_CALLBACK_PACKET_TX_DO_SEND, + wsi->active_extensions_user[n], &buf, len); + if (m < 0) { + fprintf(stderr, "Extension reports fatal error\n"); + return -1; + } + if (m) /* handled */ + return 0; + } + + /* + * nope, send it on the socket directly + */ + +#if 0 + fprintf(stderr, " TX: "); + lws_stderr_hexdump(buf, len); +#endif #ifdef LWS_OPENSSL_SUPPORT if (wsi->ssl) { @@ -1284,7 +1454,7 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, int shift = 7; struct lws_tokens eff_buf; int ret; - int masked7 = wsi->mode == LWS_CONNMODE_WS_CLIENT; + int masked7 = wsi->mode == LWS_CONNMODE_WS_CLIENT && wsi->xor_mask != xor_no_mask; unsigned char *dropmask = NULL; unsigned char is_masked_bit = 0; @@ -1490,7 +1660,7 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, * to control the raw packet payload content */ - if (!(protocol & LWS_WRITE_CLIENT_IGNORE_XOR_MASK)) { + if (!(protocol & LWS_WRITE_CLIENT_IGNORE_XOR_MASK) && wsi->xor_mask != xor_no_mask) { if (libwebsocket_0405_frame_mask_generate(wsi)) { fprintf(stderr, "libwebsocket_write: " @@ -1536,10 +1706,12 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, buf[2 - pre] = 0; buf[3 - pre] = 0; } else { - dropmask[0] = 0; - dropmask[1] = 0; - dropmask[2] = 0; - dropmask[3] = 0; + if (dropmask && wsi->xor_mask != xor_no_mask) { + dropmask[0] = 0; + dropmask[1] = 0; + dropmask[2] = 0; + dropmask[3] = 0; + } } } diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 1593a76b..bf2865da 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -126,17 +126,34 @@ enum lws_websocket_opcodes_04 { LWS_WS_OPCODE_04__PONG = 3, LWS_WS_OPCODE_04__TEXT_FRAME = 4, LWS_WS_OPCODE_04__BINARY_FRAME = 5, + + LWS_WS_OPCODE_04__RESERVED_6 = 6, + LWS_WS_OPCODE_04__RESERVED_7 = 7, + LWS_WS_OPCODE_04__RESERVED_8 = 8, + LWS_WS_OPCODE_04__RESERVED_9 = 9, + LWS_WS_OPCODE_04__RESERVED_A = 0xa, + LWS_WS_OPCODE_04__RESERVED_B = 0xb, + LWS_WS_OPCODE_04__RESERVED_C = 0xc, + LWS_WS_OPCODE_04__RESERVED_D = 0xd, + LWS_WS_OPCODE_04__RESERVED_E = 0xe, + LWS_WS_OPCODE_04__RESERVED_F = 0xf, }; enum lws_websocket_opcodes_07 { LWS_WS_OPCODE_07__CONTINUATION = 0, LWS_WS_OPCODE_07__TEXT_FRAME = 1, LWS_WS_OPCODE_07__BINARY_FRAME = 2, + + LWS_WS_OPCODE_07__NOSPEC__MUX = 7, + + /* control extensions 8+ */ + LWS_WS_OPCODE_07__CLOSE = 8, LWS_WS_OPCODE_07__PING = 9, LWS_WS_OPCODE_07__PONG = 0xa, }; + enum lws_connection_states { WSI_STATE_HTTP, WSI_STATE_HTTP_HEADERS, @@ -144,7 +161,7 @@ enum lws_connection_states { WSI_STATE_ESTABLISHED, WSI_STATE_CLIENT_UNCONNECTED, WSI_STATE_RETURNED_CLOSE_ALREADY, - WSI_STATE_AWAITING_CLOSE_ACK + WSI_STATE_AWAITING_CLOSE_ACK, }; enum lws_rx_parse_state { @@ -188,6 +205,8 @@ enum connection_mode { LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY, LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE, LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY, + LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT, + LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD, /* special internal types */ LWS_CONNMODE_SERVER_LISTENER, @@ -236,6 +255,7 @@ enum pending_timeout { PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, PENDING_TIMEOUT_AWAITING_PING, PENDING_TIMEOUT_CLOSE_ACK, + PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE, }; @@ -272,6 +292,8 @@ struct libwebsocket { enum lws_rx_parse_state lws_rx_parse_state; char extension_data_pending; + struct libwebsocket *candidate_children_list; + struct libwebsocket *extension_handles; /* 04 protocol specific */ @@ -301,6 +323,10 @@ struct libwebsocket { char *c_origin; char *c_protocol; + char *c_address; + int c_port; + + #ifdef LWS_OPENSSL_SUPPORT SSL *ssl; BIO *client_bio; @@ -353,6 +379,42 @@ extern int lws_issue_raw(struct libwebsocket *wsi, unsigned char *buf, size_t len); +extern void +libwebsocket_service_timeout_check(struct libwebsocket_context *context, + struct libwebsocket *wsi, unsigned int sec); + +extern struct libwebsocket * __libwebsocket_client_connect_2( + struct libwebsocket_context *context, + struct libwebsocket *wsi); + +extern struct libwebsocket * +libwebsocket_create_new_server_wsi(struct libwebsocket_context *context); + +extern char * +libwebsockets_generate_client_handshake(struct libwebsocket_context *context, + struct libwebsocket *wsi, char *pkt); + +extern int +lws_handle_POLLOUT_event(struct libwebsocket_context *context, + struct libwebsocket *wsi, struct pollfd *pollfd); + +extern int +lws_any_extension_handled(struct libwebsocket_context *context, + struct libwebsocket *wsi, + enum libwebsocket_extension_callback_reasons r, + void *v, size_t len); + +extern void * +lws_get_extension_user_matching_ext(struct libwebsocket *wsi, + struct libwebsocket_extension * ext); + +extern int +lws_client_interpret_server_handshake(struct libwebsocket_context *context, + struct libwebsocket *wsi); + +extern int +libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c); + #ifndef LWS_OPENSSL_SUPPORT unsigned char * diff --git a/test-server/test-server.c b/test-server/test-server.c index 2cb063c3..c1cfab81 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -124,7 +124,7 @@ static void dump_handshake_info(struct lws_tokens *lwst) { int n; - static const char *token_names[] = { + static const char *token_names[WSI_TOKEN_COUNT] = { /*[WSI_TOKEN_GET_URI] =*/ "GET URI", /*[WSI_TOKEN_HOST] =*/ "Host", /*[WSI_TOKEN_CONNECTION] =*/ "Connection", @@ -148,6 +148,7 @@ dump_handshake_info(struct lws_tokens *lwst) /*[WSI_TOKEN_ACCEPT] =*/ "Accept", /*[WSI_TOKEN_NONCE] =*/ "Nonce", /*[WSI_TOKEN_HTTP] =*/ "Http", + /*[WSI_TOKEN_MUXURL] =*/ "MuxURL", }; for (n = 0; n < WSI_TOKEN_COUNT; n++) { @@ -187,6 +188,7 @@ callback_dumb_increment(struct libwebsocket_context * context, switch (reason) { case LWS_CALLBACK_ESTABLISHED: + fprintf(stderr, "callback_dumb_increment: LWS_CALLBACK_ESTABLISHED\n"); pss->number = 0; break; @@ -266,6 +268,7 @@ callback_lws_mirror(struct libwebsocket_context * context, switch (reason) { case LWS_CALLBACK_ESTABLISHED: + fprintf(stderr, "callback_lws_mirror: LWS_CALLBACK_ESTABLISHED\n"); pss->ringbuffer_tail = ringbuffer_head; pss->wsi = wsi; break;