From 4739e5c4509d2448b57c1aa8f1cf0da601ea32a7 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Sat, 22 Jan 2011 12:51:57 +0000 Subject: [PATCH] introduce-client-support.patch Signed-off-by: Andy Green --- README-test-server | 31 ++- lib/Makefile.am | 2 + lib/Makefile.in | 13 +- lib/base64-decode.c | 10 +- lib/client-handshake.c | 394 ++++++++++++++++++++++++++++++++++++ lib/handshake.c | 46 ++++- lib/libwebsockets.c | 75 ++++--- lib/libwebsockets.h | 20 +- lib/parsers.c | 472 +++++++++++++++++++++++++++++++++++++++++--- lib/private-libwebsockets.h | 29 ++- libwebsockets-api-doc.html | 8 +- test-server/Makefile.am | 5 +- test-server/Makefile.in | 20 +- test-server/test-client.c | 390 ++++++++++++++++++++++++++++++++++++ test-server/test-server.c | 10 +- 15 files changed, 1424 insertions(+), 101 deletions(-) create mode 100644 lib/client-handshake.c create mode 100644 test-server/test-client.c diff --git a/README-test-server b/README-test-server index 4b30fab..811e0a6 100644 --- a/README-test-server +++ b/README-test-server @@ -15,8 +15,8 @@ $ libwebsockets-test-server should be enough to get a test server listening on port 7861. -Testing -------- +Testing server with a browser +----------------------------- If you point your browser (eg, Chrome) to @@ -29,6 +29,9 @@ Incrementing numbers should appear in the browser display. Using SSL --------- +The client side operation does not support SSL yet, but the +server side does. + To test it using SSL/WSS, just run the test server with $ libwebsockets-test-server --ssl @@ -55,15 +58,27 @@ libwebsockets from your own main loop instead. Use the configure option --nofork and simply call libwebsocket_service() from your own main loop as shown in the test app sources. +Testing websocket client support +-------------------------------- + +If you run the test server as described above, you can also +connect to it using the test client as well as a browser. + +$ libwebsockets-test-client localhost + +will by default connect to the test server on localhost:7681 +and print the dumb increment number from the server at the +same time as drawing random circles in the mirror protocol; +if you connect to the test server using a browser at the +same time you will be able to see the circles being drawn. + Websocket version supported --------------------------- -Right now this is tested and working on websockets protocol 76/00 -Untested code is in for 04 support, there is no browser support -available yet to test it with. Libwebsockets should autoselect -between the supported versions according to what the browser -asks for. +The websocket client code is 04 version, the server supports +both 00/76 in text mode and 04 dynamically per-connection +depending on the version of the client / browser. -2011-01-20 Andy Green +2011-01-22 Andy Green diff --git a/lib/Makefile.am b/lib/Makefile.am index 4db463b..fb0f239 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -5,6 +5,7 @@ dist_libwebsockets_la_SOURCES=libwebsockets.c \ parsers.c \ libwebsockets.h \ base64-decode.c \ + client-handshake.c \ private-libwebsockets.h libwebsockets_la_CFLAGS=-Wall -Werror -std=gnu99 -pedantic -rdynamic -fPIC -c libwebsockets_la_LDFLAGS=-version-info 0:2 -lcrypto @@ -13,6 +14,7 @@ all-local: ../scripts/kernel-doc -html \ libwebsockets.c \ parsers.c \ + client-handshake.c \ libwebsockets.h \ > ../libwebsockets-api-doc.html diff --git a/lib/Makefile.in b/lib/Makefile.in index 304866c..62dc216 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -72,7 +72,8 @@ LTLIBRARIES = $(lib_LTLIBRARIES) libwebsockets_la_LIBADD = dist_libwebsockets_la_OBJECTS = libwebsockets_la-libwebsockets.lo \ libwebsockets_la-handshake.lo libwebsockets_la-parsers.lo \ - libwebsockets_la-base64-decode.lo + libwebsockets_la-base64-decode.lo \ + libwebsockets_la-client-handshake.lo libwebsockets_la_OBJECTS = $(dist_libwebsockets_la_OBJECTS) libwebsockets_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(libwebsockets_la_CFLAGS) \ @@ -216,6 +217,7 @@ dist_libwebsockets_la_SOURCES = libwebsockets.c \ parsers.c \ libwebsockets.h \ base64-decode.c \ + client-handshake.c \ private-libwebsockets.h libwebsockets_la_CFLAGS = -Wall -Werror -std=gnu99 -pedantic -rdynamic -fPIC -c @@ -295,6 +297,7 @@ distclean-compile: -rm -f *.tab.c @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-handshake.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-libwebsockets.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_la-parsers.Plo@am__quote@ @@ -348,6 +351,13 @@ libwebsockets_la-base64-decode.lo: base64-decode.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-base64-decode.lo `test -f 'base64-decode.c' || echo '$(srcdir)/'`base64-decode.c +libwebsockets_la-client-handshake.lo: client-handshake.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-client-handshake.lo -MD -MP -MF $(DEPDIR)/libwebsockets_la-client-handshake.Tpo -c -o libwebsockets_la-client-handshake.lo `test -f 'client-handshake.c' || echo '$(srcdir)/'`client-handshake.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libwebsockets_la-client-handshake.Tpo $(DEPDIR)/libwebsockets_la-client-handshake.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='client-handshake.c' object='libwebsockets_la-client-handshake.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-client-handshake.lo `test -f 'client-handshake.c' || echo '$(srcdir)/'`client-handshake.c + mostlyclean-libtool: -rm -f *.lo @@ -582,6 +592,7 @@ all-local: ../scripts/kernel-doc -html \ libwebsockets.c \ parsers.c \ + client-handshake.c \ libwebsockets.h \ > ../libwebsockets-api-doc.html diff --git a/lib/base64-decode.c b/lib/base64-decode.c index d38ba45..e0206c8 100644 --- a/lib/base64-decode.c +++ b/lib/base64-decode.c @@ -48,7 +48,7 @@ static const char decode[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW" "$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; int -lws_b64_encode_string(const char *in, char *out, int out_size) +lws_b64_encode_string(const char *in, int in_len, char *out, int out_size) { unsigned char triple[3]; int i; @@ -56,12 +56,13 @@ lws_b64_encode_string(const char *in, char *out, int out_size) int line = 0; int done = 0; - while (*in) { + while (in_len) { len = 0; for (i = 0; i < 3; i++) { - if (*in) { + if (in_len) { triple[i] = *in++; len++; + in_len--; } else triple[i] = 0; } @@ -175,7 +176,8 @@ lws_b64_selftest(void) for (test = 0; test < sizeof plaintext / sizeof(plaintext[0]); test++) { buf[sizeof(buf) - 1] = '\0'; - n = lws_b64_encode_string(plaintext[test], buf, sizeof buf); + n = lws_b64_encode_string(plaintext[test], + strlen(plaintext[test]), buf, sizeof buf); if (n != strlen(coded[test]) || strcmp(buf, coded[test])) { fprintf(stderr, "Failed lws_b64 encode selftest " "%d result '%s' %d\n", test, buf, n); diff --git a/lib/client-handshake.c b/lib/client-handshake.c new file mode 100644 index 0000000..1ef80f4 --- /dev/null +++ b/lib/client-handshake.c @@ -0,0 +1,394 @@ +#include "private-libwebsockets.h" +#include + + +/* + * In-place str to lower case + */ + +void +strtolower(char *s) +{ + while (*s) + *s++ = tolower(*s); +} + +void +libwebsocket_client_close(struct libwebsocket *wsi) +{ + int n = wsi->state; + struct libwebsocket_context *clients; + + /* mark the WSI as dead and let the callback know */ + + wsi->state = WSI_STATE_DEAD_SOCKET; + + if (wsi->protocol) { + if (wsi->protocol->callback && n == WSI_STATE_ESTABLISHED) + wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED, + wsi->user_space, NULL, 0); + + /* remove it from the client polling list */ + clients = wsi->protocol->owning_server; + if (clients) + for (n = 0; n < clients->fds_count; n++) { + if (clients->wsi[n] != wsi) + continue; + while (n < clients->fds_count - 1) { + clients->fds[n] = clients->fds[n + 1]; + clients->wsi[n] = clients->wsi[n + 1]; + } + /* we only have to deal with one */ + n = clients->fds_count; + } + + } + + /* clean out any parsing allocations */ + + for (n = 0; n < WSI_TOKEN_COUNT; n++) + if (wsi->utf8_token[n].token) + free(wsi->utf8_token[n].token); + + /* shut down reasonably cleanly */ + +#ifdef LWS_OPENSSL_SUPPORT + if (use_ssl) { + n = SSL_get_fd(wsi->ssl); + SSL_shutdown(wsi->ssl); + close(n); + SSL_free(wsi->ssl); + } else { +#endif + shutdown(wsi->sock, SHUT_RDWR); + close(wsi->sock); +#ifdef LWS_OPENSSL_SUPPORT + } +#endif +} + +struct libwebsocket * +libwebsocket_client_connect(struct libwebsocket_context *clients, + const char *address, + int port, + const char *path, + const char *host, + const char *origin, + const char *protocol) +{ + struct hostent *server_hostent; + struct sockaddr_in server_addr; + char buf[150]; + char key_b64[150]; + char hash[20]; + int fd; + struct pollfd pfd; + 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 pkt[1024]; + char *p = &pkt[0]; + const char * pc; + int len; + int okay = 0; + struct libwebsocket *wsi; + int n; + + wsi = malloc(sizeof (struct libwebsocket)); + if (wsi == NULL) + return NULL; + + clients->wsi[clients->fds_count] = wsi; + + wsi->ietf_spec_revision = 4; + wsi->name_buffer_pos = 0; + wsi->user_space = NULL; + wsi->state = WSI_STATE_CLIENT_UNCONNECTED; + wsi->pings_vs_pongs = 0; + + for (n = 0; n < WSI_TOKEN_COUNT; n++) { + wsi->utf8_token[n].token = NULL; + wsi->utf8_token[n].token_len = 0; + } + + /* + * prepare the actual connection + */ + + server_hostent = gethostbyname(address); + if (server_hostent == NULL) { + fprintf(stderr, "Unable to get host name from %s\n", address); + goto bail1; + } + + wsi->sock = socket(AF_INET, SOCK_STREAM, 0); + + if (wsi->sock < 0) { + fprintf(stderr, "Unable to open socket\n"); + goto bail1; + } + + + 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); + + if (connect(wsi->sock, (struct sockaddr *)&server_addr, + sizeof(struct sockaddr)) == -1) { + fprintf(stderr, "Connect failed"); + goto bail1; + } + + /* + * create the random key + */ + + fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY); + if (fd < 1) { + fprintf(stderr, "Unable to open random device %s\n", + SYSTEM_RANDOM_FILEPATH); + goto bail2; + } + n = read(fd, hash, 16); + if (n != 16) { + fprintf(stderr, "Unable to read from random device %s\n", + SYSTEM_RANDOM_FILEPATH); + close(fd); + goto bail2; + } + close(fd); + + lws_b64_encode_string(hash, 16, key_b64, sizeof key_b64); + + /* + * 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", path); + p += sprintf(p, "Host: %s\x0d\x0a", host); + p += sprintf(p, "Upgrade: websocket\x0d\x0a"); + p += sprintf(p, "Connection: Upgrade\x0d\x0aSec-WebSocket-Key: "); + strcpy(p, key_b64); + p += strlen(key_b64); + p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a", origin); + if (protocol != NULL) + p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", protocol); + p += sprintf(p, "Sec-WebSocket-Version: 4\x0d\x0a\x0d\x0a"); + + + /* prepare the expected server accept response */ + + strcpy(buf, key_b64); + strcpy(&buf[strlen(buf)], magic_websocket_guid); + + SHA1((unsigned char *)buf, strlen(buf), (unsigned char *)hash); + + lws_b64_encode_string(hash, 20, wsi->initial_handshake_hash_base64, + sizeof wsi->initial_handshake_hash_base64); + + /* send our request to the server */ + + n = send(wsi->sock, pkt, p - pkt, 0); + + wsi->parser_state = WSI_TOKEN_NAME_PART; + + pfd.fd = wsi->sock; + pfd.events = POLLIN; + pfd.revents = 0; + + n = poll(&pfd, 1, 5000); + if (n < 0) { + fprintf(stderr, "libwebsocket_client_handshake socket error " + "while waiting for handshake response"); + goto bail2; + } + if (n == 0) { + fprintf(stderr, "libwebsocket_client_handshake timeout " + "while waiting for handshake response"); + goto bail2; + } + + /* interpret the server response */ + + /* + * HTTP/1.1 101 Switching Protocols + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= + * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== + * Sec-WebSocket-Protocol: chat + */ + + len = recv(wsi->sock, pkt, sizeof pkt, 0); + if (len < 0) { + fprintf(stderr, "libwebsocket_client_handshake read error\n"); + goto bail2; + } + + p = pkt; + for (n = 0; n < len; n++) + libwebsocket_parse(wsi, *p++); + + if (wsi->parser_state != WSI_PARSING_COMPLETE) { + fprintf(stderr, "libwebsocket_client_handshake server response" + " failed parsing\n"); + goto bail2; + } + + /* + * 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->utf8_token[WSI_TOKEN_PROTOCOL].token_len && + protocol != NULL)) { + fprintf(stderr, "libwebsocket_client_handshake " + "missing required header\n"); + goto bail2; + } + + /* + * 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 bail2; + } + + 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 bail2; + } + + 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 bail2; + } + /* + * 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 = &clients->protocols[0]; + + goto check_accept; + } + + pc = protocol; + 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++; + } + 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 (clients->protocols[n].callback) { + if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, + clients->protocols[n].name) == 0) + wsi->protocol = &clients->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; + } + +check_accept: + /* + * Confirm his accept token is the same as 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; + } + + /* + * Calculate the masking key to use when sending data to server + */ + + strcpy(buf, key_b64); + p = buf + strlen(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((unsigned char *)buf, strlen(buf), wsi->masking_key_04); + + /* okay he is good to go */ + + clients->fds[clients->fds_count].fd = wsi->sock; + clients->fds[clients->fds_count].revents = 0; + clients->fds[clients->fds_count++].events = POLLIN; + + wsi->state = WSI_STATE_ESTABLISHED; + wsi->client_mode = 1; + + fprintf(stderr, "handshake OK for protocol %s\n", wsi->protocol->name); + + return wsi; + + +bail2: + libwebsocket_client_close(wsi); +bail1: + free(wsi); + + return NULL; +} diff --git a/lib/handshake.c b/lib/handshake.c index 3f5704f..842b764 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -240,12 +240,13 @@ handshake_04(struct libwebsocket *wsi) int nonce_len; int accept_len; - if (!wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len || + if (!wsi->utf8_token[WSI_TOKEN_SWORIGIN].token_len || !wsi->utf8_token[WSI_TOKEN_HOST].token_len || - !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || - !wsi->utf8_token[WSI_TOKEN_KEY].token_len) + !wsi->utf8_token[WSI_TOKEN_KEY].token_len) { + debug("handshake_04 missing pieces\n"); /* completed header processing, but missing some bits */ goto bail; + } if (wsi->utf8_token[WSI_TOKEN_KEY].token_len >= MAX_WEBSOCKET_04_KEY_LEN) { @@ -262,7 +263,7 @@ handshake_04(struct libwebsocket *wsi) wsi->utf8_token[WSI_TOKEN_KEY].token_len + strlen(websocket_magic_guid_04), hash); - accept_len = lws_b64_encode_string((char *)hash, accept_buf, + accept_len = lws_b64_encode_string((char *)hash, 20, accept_buf, sizeof accept_buf); if (accept_len < 0) { fprintf(stderr, "Base64 encoded hash too long\n"); @@ -307,31 +308,37 @@ handshake_04(struct libwebsocket *wsi) strcpy(p, accept_buf); p += accept_len; + strcpy(p, "\x0d\x0aSec-WebSocket-Nonce: "); + p += strlen("\x0d\x0aSec-WebSocket-Nonce: "); + /* select the nonce */ fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY); if (fd < 1) { fprintf(stderr, "Unable to open random device %s\n", SYSTEM_RANDOM_FILEPATH); - free(wsi->user_space); + if (wsi->user_space) + free(wsi->user_space); goto bail; } n = read(fd, hash, 16); if (n != 16) { - fprintf(stderr, "Unable to read from random device %s\n", - SYSTEM_RANDOM_FILEPATH); - free(wsi->user_space); + fprintf(stderr, "Unable to read from random device %s %d\n", + SYSTEM_RANDOM_FILEPATH, n); + if (wsi->user_space) + free(wsi->user_space); goto bail; } close(fd); /* encode the nonce */ - nonce_len = lws_b64_encode_string((const char *)hash, nonce_buf, + nonce_len = lws_b64_encode_string((const char *)hash, 16, nonce_buf, sizeof nonce_buf); if (nonce_len < 0) { fprintf(stderr, "Failed to base 64 encode the nonce\n"); - free(wsi->user_space); + if (wsi->user_space) + free(wsi->user_space); goto bail; } @@ -455,12 +462,22 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) #ifdef DEBUG fwrite(buf, 1, len, stderr); #endif + + if (wsi->client_mode) { + for (n = 0; n < len; n++) + libwebsocket_client_rx_sm(wsi, *buf++); + + return 0; + } + for (n = 0; n < len; n++) libwebsocket_parse(wsi, *buf++); if (wsi->parser_state != WSI_PARSING_COMPLETE) break; + debug("libwebsocket_parse sees parsing complete\n"); + /* is this websocket protocol or normal http 1.0? */ if (!wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || @@ -527,6 +544,7 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) goto bail; break; case 4: /* 04 */ + debug("libwebsocket_parse calling handshake_04\n"); if (handshake_04(wsi)) goto bail; break; @@ -539,6 +557,14 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) break; case WSI_STATE_ESTABLISHED: + if (wsi->client_mode) { + for (n = 0; n < len; n++) + libwebsocket_client_rx_sm(wsi, *buf++); + + return 0; + } + + if (libwebsocket_interpret_incoming_packet(wsi, buf, len) < 0) goto bail; break; diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 5aa7c26..aae6690 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -131,6 +131,11 @@ libwebsocket_poll_connections(struct libwebsocket_context *this) if (wsi->state != WSI_STATE_ESTABLISHED) continue; + /* only to clients connected to us */ + + if (wsi->client_mode) + continue; + /* * only broadcast to connections using * the requested protocol @@ -207,7 +212,13 @@ libwebsocket_service(struct libwebsocket_context *this, int timeout_ms) if (this == NULL) return 1; - n = poll(this->fds, this->fds_count, timeout_ms); + /* don't check listen socket if we are not listening */ + + if (this->listen_port) + n = poll(this->fds, this->fds_count, timeout_ms); + else + n = poll(&this->fds[1], this->fds_count - 1, timeout_ms); + if (n < 0 || this->fds[0].revents & (POLLERR | POLLHUP)) { fprintf(stderr, "Listen Socket dead\n"); @@ -308,6 +319,7 @@ libwebsocket_service(struct libwebsocket_context *this, int timeout_ms) this->wsi[this->fds_count]->sock = fd; this->wsi[this->fds_count]->state = WSI_STATE_HTTP; this->wsi[this->fds_count]->name_buffer_pos = 0; + this->wsi[this->fds_count]->client_mode = 0; for (n = 0; n < WSI_TOKEN_COUNT; n++) { this->wsi[this->fds_count]-> @@ -378,8 +390,10 @@ fatal: /** - * libwebsocket_create_server() - Create the listening websockets server - * @port: Port to listen on + * libwebsocket_create_context() - Create the websocket handler + * @port: Port to listen on... you can use 0 to suppress listening on + * any port, that's what you want if you are not running a + * websocket server at all but just using it as a client * @protocols: Array of structures listing supported protocols and a protocol- * specific callback for each one. The list is ended with an * entry that has a NULL callback pointer. @@ -419,14 +433,14 @@ fatal: */ struct libwebsocket_context * -libwebsocket_create_server(int port, +libwebsocket_create_context(int port, struct libwebsocket_protocols *protocols, const char *ssl_cert_filepath, const char *ssl_private_key_filepath, int gid, int uid) { int n; - int sockfd; + int sockfd = 0; int fd; struct sockaddr_in serv_addr, cli_addr; int opt = 1; @@ -487,7 +501,7 @@ libwebsocket_create_server(int port, /* set the private key from KeyFile */ if (SSL_CTX_use_PrivateKey_file(ssl_ctx, ssl_private_key_filepath, - SSL_FILETYPE_PEM) != 1) { + SSL_FILETYPE_PEM) != 1) { fprintf(stderr, "ssl problem getting key '%s': %s\n", ssl_private_key_filepath, ERR_error_string(ERR_get_error(), ssl_err_buf)); @@ -512,28 +526,33 @@ libwebsocket_create_server(int port, this = malloc(sizeof(struct libwebsocket_context)); this->protocols = protocols; + this->listen_port = port; /* set up our external listening socket we serve on */ - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) { - fprintf(stderr, "ERROR opening socket"); - return NULL; - } + if (port) { - /* allow us to restart even if old sockets in TIME_WAIT */ - setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + fprintf(stderr, "ERROR opening socket"); + return NULL; + } + + /* allow us to restart even if old sockets in TIME_WAIT */ + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); - bzero((char *) &serv_addr, sizeof(serv_addr)); - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = INADDR_ANY; - serv_addr.sin_port = htons(port); + bzero((char *) &serv_addr, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = INADDR_ANY; + serv_addr.sin_port = htons(port); - n = bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); - if (n < 0) { - fprintf(stderr, "ERROR on binding to port %d (%d %d)\n", + n = bind(sockfd, (struct sockaddr *) &serv_addr, + sizeof(serv_addr)); + if (n < 0) { + fprintf(stderr, "ERROR on binding to port %d (%d %d)\n", port, n, errno); - return NULL; + return NULL; + } } /* drop any root privs for this process */ @@ -561,8 +580,10 @@ libwebsocket_create_server(int port, this->use_ssl = use_ssl; #endif - listen(sockfd, 5); - fprintf(stderr, " Listening on port %d\n", port); + if (port) { + listen(sockfd, 5); + fprintf(stderr, " Listening on port %d\n", port); + } /* set up our internal broadcast trigger sockets per-protocol */ @@ -617,6 +638,7 @@ libwebsocket_create_server(int port, return this; } + #ifndef LWS_NO_FORK /** @@ -653,7 +675,7 @@ libwebsockets_fork_service_loop(struct libwebsocket_context *this) } cli_addr.sin_family = AF_INET; cli_addr.sin_port = htons( - this->protocols[client - 1].broadcast_socket_port); + this->protocols[client - 1].broadcast_socket_port); cli_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); n = connect(fd, (struct sockaddr *)&cli_addr, sizeof cli_addr); @@ -664,7 +686,8 @@ libwebsockets_fork_service_loop(struct libwebsocket_context *this) return -1; } - this->protocols[client - 1].broadcast_socket_user_fd = fd; + this->protocols[client - 1].broadcast_socket_user_fd = + fd; } @@ -749,12 +772,10 @@ libwebsockets_broadcast(const struct libwebsocket_protocols *protocol, continue; /* never broadcast to non-established connection */ - if (this->wsi[n]->state != WSI_STATE_ESTABLISHED) continue; /* only broadcast to guys using requested protocol */ - if (this->wsi[n]->protocol != protocol) continue; diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index ba18cc9..b6d7335 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -22,11 +22,13 @@ #ifndef __LIBWEBSOCKET_H__ #define __LIBWEBSOCKET_H__ +#define CONTEXT_PORT_NO_LISTEN 0 enum libwebsocket_callback_reasons { LWS_CALLBACK_ESTABLISHED, LWS_CALLBACK_CLOSED, LWS_CALLBACK_RECEIVE, + LWS_CALLBACK_CLIENT_RECEIVE, LWS_CALLBACK_HTTP, LWS_CALLBACK_BROADCAST }; @@ -135,7 +137,7 @@ struct libwebsocket_protocols { }; extern struct libwebsocket_context * -libwebsocket_create_server(int port, +libwebsocket_create_context(int port, struct libwebsocket_protocols *protocols, const char *ssl_cert_filepath, const char *ssl_private_key_filepath, int gid, int uid); @@ -170,7 +172,9 @@ libwebsocket_service(struct libwebsocket_context *this, int timeout_ms); * use the whole buffer without taking care of the above. */ -#define LWS_SEND_BUFFER_PRE_PADDING 12 +/* this is the frame nonce plus two header plus 8 length */ + +#define LWS_SEND_BUFFER_PRE_PADDING (4 + 10) #define LWS_SEND_BUFFER_POST_PADDING 1 extern int @@ -193,4 +197,16 @@ libwebsockets_get_protocol(struct libwebsocket *wsi); extern size_t libwebsockets_remaining_packet_payload(struct libwebsocket *wsi); +extern struct libwebsocket * +libwebsocket_client_connect(struct libwebsocket_context *clients, + const char *address, + int port, + const char *path, + const char *host, + const char *origin, + const char *protocol); + +void +libwebsocket_client_close(struct libwebsocket *wsi); + #endif diff --git a/lib/parsers.c b/lib/parsers.c index a3d2eba..aafa38d 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -22,20 +22,25 @@ #include "private-libwebsockets.h" const struct lws_tokens lws_tokens[WSI_TOKEN_COUNT] = { - [WSI_TOKEN_GET_URI] = { "GET ", 4 }, - [WSI_TOKEN_HOST] = { "Host:", 5 }, + [WSI_TOKEN_GET_URI] = { "GET ", 4 }, + [WSI_TOKEN_HOST] = { "Host:", 5 }, [WSI_TOKEN_CONNECTION] = { "Connection:", 11 }, [WSI_TOKEN_KEY1] = { "Sec-WebSocket-Key1:", 19 }, [WSI_TOKEN_KEY2] = { "Sec-WebSocket-Key2:", 19 }, [WSI_TOKEN_PROTOCOL] = { "Sec-WebSocket-Protocol:", 23 }, - [WSI_TOKEN_UPGRADE] = { "Upgrade:", 8 }, - [WSI_TOKEN_ORIGIN] = { "Origin:", 7 }, + [WSI_TOKEN_UPGRADE] = { "Upgrade:", 8 }, + [WSI_TOKEN_ORIGIN] = { "Origin:", 7 }, [WSI_TOKEN_DRAFT] = { "Sec-WebSocket-Draft:", 20 }, - [WSI_TOKEN_CHALLENGE] = { "\x0d\x0a", 2 }, + [WSI_TOKEN_CHALLENGE] = { "\x0d\x0a", 2 }, [WSI_TOKEN_KEY] = { "Sec-WebSocket-Key:", 18 }, [WSI_TOKEN_VERSION] = { "Sec-WebSocket-Version:", 22 }, + [WSI_TOKEN_ACCEPT] = { "Sec-WebSocket-Accept:", 21 }, + [WSI_TOKEN_NONCE] = { "Sec-WebSocket-Nonce:", 20 }, + [WSI_TOKEN_HTTP] = { "HTTP/1.1 ", 9 }, + [WSI_TOKEN_SWORIGIN] = { "Sec-WebSocket-Origin:", 21 }, + }; int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) @@ -51,10 +56,14 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) case WSI_TOKEN_PROTOCOL: case WSI_TOKEN_UPGRADE: case WSI_TOKEN_ORIGIN: + case WSI_TOKEN_SWORIGIN: case WSI_TOKEN_DRAFT: case WSI_TOKEN_CHALLENGE: case WSI_TOKEN_KEY: case WSI_TOKEN_VERSION: + case WSI_TOKEN_ACCEPT: + case WSI_TOKEN_NONCE: + case WSI_TOKEN_HTTP: debug("WSI_TOKEN_(%d) '%c'\n", wsi->parser_state, c); /* collect into malloc'd buffers */ @@ -140,6 +149,7 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) debug("known hdr '%s'\n", wsi->name_buffer); wsi->parser_state = WSI_TOKEN_GET_URI + n; 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; @@ -155,15 +165,33 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) break; } + if (wsi->parser_state != WSI_TOKEN_CHALLENGE) + break; + /* don't look for payload when it can just be http headers */ - if (wsi->parser_state == WSI_TOKEN_CHALLENGE && - !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len) { + if (!wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len) { /* they're HTTP headers, not websocket upgrade! */ debug("Setting WSI_PARSING_COMPLETE " "from http headers\n"); wsi->parser_state = WSI_PARSING_COMPLETE; } + + /* 04 version has no packet content after end of hdrs */ + + if (wsi->utf8_token[WSI_TOKEN_VERSION].token_len && + atoi(wsi->utf8_token[WSI_TOKEN_VERSION].token) >= 4) { + debug("04 header completed\n"); + wsi->parser_state = WSI_PARSING_COMPLETE; + } + + /* client parser? */ + + if (wsi->ietf_spec_revision >= 4) { + debug("04 header completed\n"); + wsi->parser_state = WSI_PARSING_COMPLETE; + } + break; /* skipping arg part of a name we didn't recognize */ @@ -193,7 +221,7 @@ int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) } static unsigned char inline -unmask(struct libwebsocket *wsi, unsigned char c) +xor_mask(struct libwebsocket *wsi, unsigned char c) { c ^= wsi->masking_key_04[wsi->frame_mask_index++]; if (wsi->frame_mask_index == 20) @@ -217,7 +245,8 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) if (c == 0xff) wsi->lws_rx_parse_state = LWS_RXPS_SEEN_76_FF; if (c == 0) { - wsi->lws_rx_parse_state = LWS_RXPS_EAT_UNTIL_76_FF; + wsi->lws_rx_parse_state = + LWS_RXPS_EAT_UNTIL_76_FF; wsi->rx_user_buffer_head = 0; } break; @@ -318,10 +347,10 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) * FIN (b7) */ - c = unmask(wsi, c); + c = xor_mask(wsi, c); if (c & 0x70) { - fprintf(stderr, "Frame has extensions set illegally\n"); + fprintf(stderr, "Frame has extensions set illegally 1\n"); /* kill the connection */ return -1; } @@ -342,10 +371,11 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) break; case LWS_RXPS_04_FRAME_HDR_LEN: - c = unmask(wsi, c); + c = xor_mask(wsi, c); if (c & 0x80) { - fprintf(stderr, "Frame has extensions set illegally\n"); + fprintf(stderr, "Frame has extensions " + "set illegally 2\n"); /* kill the connection */ return -1; } @@ -382,6 +412,7 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; break; default: +// fprintf(stderr, "seen incoming 04 frame len %d\n", c); wsi->rx_packet_length = c; wsi->lws_rx_parse_state = LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; @@ -390,14 +421,14 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) break; case LWS_RXPS_04_FRAME_HDR_LEN16_2: - c = unmask(wsi, c); + c = xor_mask(wsi, c); wsi->rx_packet_length = c << 8; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; break; case LWS_RXPS_04_FRAME_HDR_LEN16_1: - c = unmask(wsi, c); + c = xor_mask(wsi, c); wsi->rx_packet_length |= c; wsi->lws_rx_parse_state = @@ -405,7 +436,7 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) break; case LWS_RXPS_04_FRAME_HDR_LEN64_8: - c = unmask(wsi, c); + c = xor_mask(wsi, c); if (c & 0x80) { fprintf(stderr, "b63 of length must be zero\n"); /* kill the connection */ @@ -416,37 +447,37 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) break; case LWS_RXPS_04_FRAME_HDR_LEN64_7: - wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 48; + wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 48; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; break; case LWS_RXPS_04_FRAME_HDR_LEN64_6: - wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 40; + wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 40; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; break; case LWS_RXPS_04_FRAME_HDR_LEN64_5: - wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 32; + wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 32; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; break; case LWS_RXPS_04_FRAME_HDR_LEN64_4: - wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 24; + wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 24; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; break; case LWS_RXPS_04_FRAME_HDR_LEN64_3: - wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 16; + wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 16; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; break; case LWS_RXPS_04_FRAME_HDR_LEN64_2: - wsi->rx_packet_length |= ((size_t)unmask(wsi, c)) << 8; + wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)) << 8; wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; break; case LWS_RXPS_04_FRAME_HDR_LEN64_1: - wsi->rx_packet_length |= ((size_t)unmask(wsi, c)); + wsi->rx_packet_length |= ((size_t)xor_mask(wsi, c)); wsi->lws_rx_parse_state = LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; break; @@ -491,7 +522,8 @@ issue: case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + - (wsi->rx_user_buffer_head++)] = unmask(wsi, c); + (wsi->rx_user_buffer_head++)] = xor_mask(wsi, c); + if (--wsi->rx_packet_length == 0) { wsi->lws_rx_parse_state = LWS_RXPS_NEW; goto spill; @@ -537,18 +569,324 @@ spill: * so it can be sent straight out again using libwebsocket_write */ + wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + + wsi->rx_user_buffer_head] = '\0'; + if (wsi->protocol->callback) wsi->protocol->callback(wsi, LWS_CALLBACK_RECEIVE, - wsi->user_space, + wsi->user_space, &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], - wsi->rx_user_buffer_head); + wsi->rx_user_buffer_head); + wsi->rx_user_buffer_head = 0; + break; + } + + return 0; +} + + +int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c) +{ + int n; + unsigned char buf[20 + 4]; + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: + + switch (wsi->ietf_spec_revision) { + /* Firefox 4.0b6 likes this as of 30 Oct */ + case 0: + if (c == 0xff) + wsi->lws_rx_parse_state = LWS_RXPS_SEEN_76_FF; + if (c == 0) { + wsi->lws_rx_parse_state = + LWS_RXPS_EAT_UNTIL_76_FF; + wsi->rx_user_buffer_head = 0; + } + break; + case 4: + /* + * 04 logical framing from the spec (all this is masked when incoming + * and has to be unmasked) + * + * We ignore the possibility of extension data because we don't + * negotiate any extensions at the moment. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|R| Payload len | Extended payload length | + * |I|S|S|S| (4) |S| (7) | (16/63) | + * |N|V|V|V| |V| | (if payload len==126/127) | + * | |1|2|3| |4| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | | Extension data | + * +-------------------------------+ - - - - - - - - - - - - - - - + + * : : + * +---------------------------------------------------------------+ + * : Application data : + * +---------------------------------------------------------------+ + * + * We pass payload through to userland as soon as we get it, ignoring + * FIN. It's up to userland to buffer it up if it wants to see a + * whole unfragmented block of the original size (which may be up to + * 2^63 long!) + */ + + /* + * 04 spec defines the opcode like this: (1, 2, and 3 are + * "control frame" opcodes which may not be fragmented or + * have size larger than 126) + * + * frame-opcode = + * %x0 ; continuation frame + * / %x1 ; connection close + * / %x2 ; ping + * / %x3 ; pong + * / %x4 ; text frame + * / %x5 ; binary frame + * / %x6-F ; reserved + * + * FIN (b7) + */ + + if (c & 0x70) { + fprintf(stderr, "Frame has extensions set " + "illegally on first framing byte %02X\n", c); + /* kill the connection */ + return -1; + } + + wsi->opcode = c & 0xf; + wsi->final = !!((c >> 7) & 1); + + if (wsi->final && + wsi->opcode == LWS_WS_OPCODE_04__CONTINUATION && + wsi->rx_packet_length == 0) { + fprintf(stderr, + "Frame starts with final continuation\n"); + /* kill the connection */ + return -1; + } + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + break; + } + break; + + + case LWS_RXPS_04_FRAME_HDR_LEN: + + if (c & 0x80) { + fprintf(stderr, + "Frame has extensions set illegally 4\n"); + /* kill the connection */ + return -1; + } + + switch (c) { + case 126: + /* control frames are not allowed to have big lengths */ + switch (wsi->opcode) { + case LWS_WS_OPCODE_04__CLOSE: + case LWS_WS_OPCODE_04__PING: + case LWS_WS_OPCODE_04__PONG: + fprintf(stderr, "Control frame asking for " + "extended length is illegal\n"); + /* kill the connection */ + return -1; + default: + break; + } + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; + break; + case 127: + /* control frames are not allowed to have big lengths */ + switch (wsi->opcode) { + case LWS_WS_OPCODE_04__CLOSE: + case LWS_WS_OPCODE_04__PING: + case LWS_WS_OPCODE_04__PONG: + fprintf(stderr, "Control frame asking for " + "extended length is illegal\n"); + /* kill the connection */ + return -1; + default: + break; + } + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; + break; + default: + wsi->rx_packet_length = c; + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_2: + wsi->rx_packet_length = c << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_1: + wsi->rx_packet_length |= c; + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_8: + if (c & 0x80) { + fprintf(stderr, "b63 of length must be zero\n"); + /* kill the connection */ + return -1; + } + wsi->rx_packet_length = ((size_t)c) << 56; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_7: + wsi->rx_packet_length |= ((size_t)c) << 48; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_6: + wsi->rx_packet_length |= ((size_t)c) << 40; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_5: + wsi->rx_packet_length |= ((size_t)c) << 32; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_4: + wsi->rx_packet_length |= ((size_t)c) << 24; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_3: + wsi->rx_packet_length |= ((size_t)c) << 16; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_2: + wsi->rx_packet_length |= ((size_t)c) << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_1: + wsi->rx_packet_length |= (size_t)c; + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + break; + + case LWS_RXPS_EAT_UNTIL_76_FF: + if (c == 0xff) { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto issue; + } + wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + + (wsi->rx_user_buffer_head++)] = c; + + if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER) + break; +issue: + if (wsi->protocol->callback) + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_RECEIVE, + wsi->user_space, + &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], + wsi->rx_user_buffer_head); wsi->rx_user_buffer_head = 0; break; + case LWS_RXPS_SEEN_76_FF: + if (c) + break; + + debug("Seen that client is requesting " + "a v76 close, sending ack\n"); + buf[0] = 0xff; + buf[1] = 0; + n = libwebsocket_write(wsi, buf, 2, LWS_WRITE_HTTP); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + return -1; + } + debug(" v76 close ack sent, server closing skt\n"); + /* returning < 0 will get it closed in parent */ + return -1; + + case LWS_RXPS_PULLING_76_LENGTH: + break; + + case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: + wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + + (wsi->rx_user_buffer_head++)] = c; + if (--wsi->rx_packet_length == 0) { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER) + break; +spill: + /* + * 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 + */ + + switch (wsi->opcode) { + case LWS_WS_OPCODE_04__CLOSE: + /* parrot the close packet payload back */ + n = libwebsocket_write(wsi, (unsigned char *) + &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], + wsi->rx_user_buffer_head, LWS_WRITE_CLOSE); + /* close the connection */ + return -1; + + case LWS_WS_OPCODE_04__PING: + /* parrot the ping packet payload back as a pong*/ + n = libwebsocket_write(wsi, (unsigned char *) + &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], + wsi->rx_user_buffer_head, LWS_WRITE_PONG); + break; + + case LWS_WS_OPCODE_04__PONG: + /* keep the statistics... */ + wsi->pings_vs_pongs--; + /* ... then just drop it */ + wsi->rx_user_buffer_head = 0; + return 0; + + default: + break; + } + + /* + * No it's real payload, pass it up to the user callback. + * It's nicely buffered with the pre-padding taken care of + * so it can be sent straight out again using libwebsocket_write + */ + + if (wsi->protocol->callback) + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_RECEIVE, + wsi->user_space, + &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], + wsi->rx_user_buffer_head); + wsi->rx_user_buffer_head = 0; + break; + default: + fprintf(stderr, "client rx illegal state\n"); + return 1; } return 0; } + + int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi, unsigned char *buf, size_t len) { @@ -564,15 +902,55 @@ int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi, /* let the rx protocol state machine have as much as it needs */ n = 0; - while (wsi->lws_rx_parse_state != - LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED && n < len) + while (n < len) if (libwebsocket_rx_sm(wsi, buf[n++]) < 0) return -1; - return -0; + return 0; } +static int +libwebsocket_04_frame_mask_generate(struct libwebsocket *wsi) +{ + int fd; + char buf[4 + 20]; + int n; + + /* fetch the per-frame nonce */ + + fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY); + if (fd < 1) { + fprintf(stderr, "Unable to open random device %s\n", + SYSTEM_RANDOM_FILEPATH); + return 1; + } + n = read(fd, wsi->frame_masking_nonce_04, 4); + if (n != 4) { + fprintf(stderr, "Unable to read from random device %s %d\n", + SYSTEM_RANDOM_FILEPATH, n); + return 1; + } + close(fd); + + /* + * the frame key is the frame nonce (4 bytes) followed by the + * connection masking key, hashed by SHA1 + */ + + memcpy(buf, wsi->frame_masking_nonce_04, 4); + memcpy(buf + 4, wsi->masking_key_04, 20); + + /* concatenate the nonce with the connection key then hash it */ + + SHA1((unsigned char *)buf, 4 + 20, wsi->frame_mask_04); + + /* start masking from first byte of masking key buffer */ + wsi->frame_mask_index = 0; + + return 0; +} + /** * libwebsocket_write() - Apply protocol then write data to client @@ -676,6 +1054,13 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, return -1; } + /* + * We don't really support the metaframe concept with FIN. + * Just set FIN on every packet for now + */ + + n |= 1 << 7; + if (len < 126) { buf[-2] = n; buf[-1] = len; @@ -718,6 +1103,34 @@ int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf, fprintf(stderr, "\n"); #endif + /* + * Deal with masking if appropriate + */ + + if (wsi->client_mode && wsi->ietf_spec_revision == 4) { + + if (libwebsocket_04_frame_mask_generate(wsi)) { + fprintf(stderr, "libwebsocket_write: " + "frame mask generation failed\n"); + return 1; + } + + /* + * use the XOR masking against everything we send + * past the frame nonce + */ + + for (n = 0; n < (len + pre + post); n++) + buf[n - pre] = xor_mask(wsi, buf[n - pre]); + + /* make space for the frame nonce in clear */ + pre += 4; + + /* copy the frame nonce into place */ + memcpy(&buf[0 - pre], wsi->frame_masking_nonce_04, 4); + } + + send_raw: #ifdef LWS_OPENSSL_SUPPORT if (use_ssl) { @@ -795,6 +1208,7 @@ int libwebsockets_serve_http_file(struct libwebsocket *wsi, const char *file, return 0; } + /** * libwebsockets_remaining_packet_payload() - Bytes to come before "overall" * rx packet is complete diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index a63d902..6f35a1e 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -51,12 +51,14 @@ #include #include "libwebsockets.h" -/* #define DEBUG */ +//#define DEBUG #ifdef DEBUG -#define debug \ - fprintf(stderr, +static inline void debug(const char *format, ...) { + va_list ap; + va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); +} #else static inline void debug(const char *format, ...) { } #endif @@ -77,7 +79,7 @@ extern int use_ssl; #define LWS_MAX_PROTOCOLS 10 #define MAX_WEBSOCKET_04_KEY_LEN 128 -#define SYSTEM_RANDOM_FILEPATH "/dev/random" +#define SYSTEM_RANDOM_FILEPATH "/dev/urandom" enum lws_websocket_opcodes_04 { LWS_WS_OPCODE_04__CONTINUATION = 0, @@ -92,7 +94,8 @@ enum lws_connection_states { WSI_STATE_HTTP, WSI_STATE_HTTP_HEADERS, WSI_STATE_DEAD_SOCKET, - WSI_STATE_ESTABLISHED + WSI_STATE_ESTABLISHED, + WSI_STATE_CLIENT_UNCONNECTED }; enum lws_token_indexes { @@ -110,6 +113,12 @@ enum lws_token_indexes { /* new for 04 */ WSI_TOKEN_KEY, WSI_TOKEN_VERSION, + WSI_TOKEN_SWORIGIN, + + /* client receives these */ + WSI_TOKEN_ACCEPT, + WSI_TOKEN_NONCE, + WSI_TOKEN_HTTP, /* always last real token index*/ WSI_TOKEN_COUNT, @@ -159,6 +168,7 @@ struct libwebsocket_context { struct libwebsocket *wsi[MAX_CLIENTS + 1]; struct pollfd fds[MAX_CLIENTS + 1]; int fds_count; + int listen_port; #ifdef LWS_OPENSSL_SUPPORT int use_ssl; #endif @@ -204,6 +214,10 @@ struct libwebsocket { int pings_vs_pongs; + /* client support */ + char initial_handshake_hash_base64[30]; + int client_mode; + #ifdef LWS_OPENSSL_SUPPORT SSL *ssl; #endif @@ -211,6 +225,9 @@ struct libwebsocket { void *user_space; }; +extern int +libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c); + extern void libwebsocket_close_and_free_session(struct libwebsocket *wsi); @@ -225,7 +242,7 @@ extern int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len); extern int -lws_b64_encode_string(const char *in, char *out, int out_size); +lws_b64_encode_string(const char *in, int in_len, char *out, int out_size); extern int lws_b64_decode_string(const char *in, char *out, int out_size); diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html index 77aea3e..6def9b3 100644 --- a/libwebsockets-api-doc.html +++ b/libwebsockets-api-doc.html @@ -1,6 +1,6 @@ -

libwebsocket_create_server - Create the listening websockets server

+

libwebsocket_create_context - Create the websocket handler

struct libwebsocket_context * -libwebsocket_create_server +libwebsocket_create_context (int port, struct libwebsocket_protocols * protocols, const char * ssl_cert_filepath, @@ -10,7 +10,9 @@

Arguments

port -
Port to listen on +
Port to listen on... you can use 0 to suppress listening on +any port, that's what you want if you are not running a +websocket server at all but just using it as a client
protocols
Array of structures listing supported protocols and a protocol- specific callback for each one. The list is ended with an diff --git a/test-server/Makefile.am b/test-server/Makefile.am index 0332251..43d3c7c 100644 --- a/test-server/Makefile.am +++ b/test-server/Makefile.am @@ -1,7 +1,8 @@ -bin_PROGRAMS=libwebsockets-test-server +bin_PROGRAMS=libwebsockets-test-server libwebsockets-test-client libwebsockets_test_server_SOURCES=test-server.c libwebsockets_test_server_LDADD=-L../lib -lwebsockets - +libwebsockets_test_client_SOURCES=test-client.c +libwebsockets_test_client_LDADD=-L../lib -lwebsockets # # cook a random test cert and key # notice your real cert and key will want to be 0600 permissions diff --git a/test-server/Makefile.in b/test-server/Makefile.in index dbe97f6..03ad8a8 100644 --- a/test-server/Makefile.in +++ b/test-server/Makefile.in @@ -34,7 +34,8 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ -bin_PROGRAMS = libwebsockets-test-server$(EXEEXT) +bin_PROGRAMS = libwebsockets-test-server$(EXEEXT) \ + libwebsockets-test-client$(EXEEXT) subdir = test-server DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -47,6 +48,10 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) +am_libwebsockets_test_client_OBJECTS = test-client.$(OBJEXT) +libwebsockets_test_client_OBJECTS = \ + $(am_libwebsockets_test_client_OBJECTS) +libwebsockets_test_client_DEPENDENCIES = am_libwebsockets_test_server_OBJECTS = test-server.$(OBJEXT) libwebsockets_test_server_OBJECTS = \ $(am_libwebsockets_test_server_OBJECTS) @@ -64,8 +69,10 @@ CCLD = $(CC) LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ -SOURCES = $(libwebsockets_test_server_SOURCES) -DIST_SOURCES = $(libwebsockets_test_server_SOURCES) +SOURCES = $(libwebsockets_test_client_SOURCES) \ + $(libwebsockets_test_server_SOURCES) +DIST_SOURCES = $(libwebsockets_test_client_SOURCES) \ + $(libwebsockets_test_server_SOURCES) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) @@ -184,6 +191,8 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ libwebsockets_test_server_SOURCES = test-server.c libwebsockets_test_server_LDADD = -L../lib -lwebsockets +libwebsockets_test_client_SOURCES = test-client.c +libwebsockets_test_client_LDADD = -L../lib -lwebsockets all: all-am .SUFFIXES: @@ -261,6 +270,9 @@ clean-binPROGRAMS: list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list +libwebsockets-test-client$(EXEEXT): $(libwebsockets_test_client_OBJECTS) $(libwebsockets_test_client_DEPENDENCIES) + @rm -f libwebsockets-test-client$(EXEEXT) + $(LINK) $(libwebsockets_test_client_OBJECTS) $(libwebsockets_test_client_LDADD) $(LIBS) libwebsockets-test-server$(EXEEXT): $(libwebsockets_test_server_OBJECTS) $(libwebsockets_test_server_DEPENDENCIES) @rm -f libwebsockets-test-server$(EXEEXT) $(LINK) $(libwebsockets_test_server_OBJECTS) $(libwebsockets_test_server_LDADD) $(LIBS) @@ -271,6 +283,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-client.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/test-server.Po@am__quote@ .c.o: @@ -501,7 +514,6 @@ uninstall-am: uninstall-binPROGRAMS mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags uninstall uninstall-am uninstall-binPROGRAMS - # # cook a random test cert and key # notice your real cert and key will want to be 0600 permissions diff --git a/test-server/test-client.c b/test-server/test-client.c new file mode 100644 index 0000000..6869d6d --- /dev/null +++ b/test-server/test-client.c @@ -0,0 +1,390 @@ +/* + * libwebsockets-test-client - libwebsockets test implementation + * + * Copyright (C) 2011 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + + +#include "../lib/libwebsockets.h" +#include + +/* + * This demo shows how to connect multiple websockets simultaneously to a + * websocket server (there is no restriction on their having to be the same + * server just it simplifies the demo). + * + * dumb-increment-protocol: we connect to the server and print the number + * we are given + * + * lws-mirror-protocol: draws random circles, which are mirrored on to every + * client (see them being drawn in every browser + * session also using the test server) + */ + +enum demo_protocols { + + PROTOCOL_DUMB_INCREMENT, + PROTOCOL_LWS_MIRROR, + + /* always last */ + DEMO_PROTOCOL_COUNT +}; + + +/* dumb_increment protocol */ + +static int +callback_dumb_increment(struct libwebsocket *wsi, + enum libwebsocket_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + + case LWS_CALLBACK_CLIENT_RECEIVE: + fprintf(stderr, "rx %d '%s'\n", len, in); + break; + + default: + break; + } + + return 0; +} + + +/* lws-mirror_protocol */ + +/* "how to draw a circle" */ + +struct coord { + int x; + int y; +}; + +static struct coord circle[] = { + +{ 0, 240 }, +{ 12, 239 }, +{ 25, 238 }, +{ 37, 237 }, +{ 49, 234 }, +{ 62, 231 }, +{ 74, 228 }, +{ 86, 224 }, +{ 97, 219 }, +{ 108, 213 }, +{ 120, 207 }, +{ 130, 201 }, +{ 141, 194 }, +{ 151, 186 }, +{ 160, 178 }, +{ 169, 169 }, +{ 178, 160 }, +{ 186, 151 }, +{ 194, 141 }, +{ 201, 130 }, +{ 207, 120 }, +{ 213, 108 }, +{ 219, 97 }, +{ 224, 86 }, +{ 228, 74 }, +{ 231, 62 }, +{ 234, 49 }, +{ 237, 37 }, +{ 238, 25 }, +{ 239, 12 }, +{ 240, 0 }, +{ 239, -12 }, +{ 238, -25 }, +{ 237, -37 }, +{ 234, -49 }, +{ 231, -62 }, +{ 228, -74 }, +{ 224, -86 }, +{ 219, -97 }, +{ 213, -108 }, +{ 207, -120 }, +{ 201, -130 }, +{ 194, -141 }, +{ 186, -151 }, +{ 178, -160 }, +{ 169, -169 }, +{ 160, -178 }, +{ 151, -186 }, +{ 141, -194 }, +{ 130, -201 }, +{ 120, -207 }, +{ 108, -213 }, +{ 97, -219 }, +{ 86, -224 }, +{ 74, -228 }, +{ 62, -231 }, +{ 49, -234 }, +{ 37, -237 }, +{ 25, -238 }, +{ 12, -239 }, +{ 0, -240 }, +{ -12, -239 }, +{ -25, -238 }, +{ -37, -237 }, +{ -49, -234 }, +{ -62, -231 }, +{ -74, -228 }, +{ -86, -224 }, +{ -97, -219 }, +{ -108, -213 }, +{ -119, -207 }, +{ -130, -201 }, +{ -141, -194 }, +{ -151, -186 }, +{ -160, -178 }, +{ -169, -169 }, +{ -178, -160 }, +{ -186, -151 }, +{ -194, -141 }, +{ -201, -130 }, +{ -207, -120 }, +{ -213, -108 }, +{ -219, -97 }, +{ -224, -86 }, +{ -228, -74 }, +{ -231, -62 }, +{ -234, -49 }, +{ -237, -37 }, +{ -238, -25 }, +{ -239, -12 }, +{ -240, 0 }, +{ -239, 12 }, +{ -238, 25 }, +{ -237, 37 }, +{ -234, 49 }, +{ -231, 62 }, +{ -228, 74 }, +{ -224, 86 }, +{ -219, 97 }, +{ -213, 108 }, +{ -207, 120 }, +{ -201, 130 }, +{ -194, 141 }, +{ -186, 151 }, +{ -178, 160 }, +{ -169, 169 }, +{ -160, 178 }, +{ -151, 186 }, +{ -141, 194 }, +{ -130, 201 }, +{ -119, 207 }, +{ -108, 213 }, +{ -97, 219 }, +{ -86, 224 }, +{ -74, 228 }, +{ -62, 231 }, +{ -49, 234 }, +{ -37, 237 }, +{ -25, 238 }, +{ -12, 239 }, +{ 0, 240 }, + +}; + +static int +callback_lws_mirror(struct libwebsocket *wsi, + enum libwebsocket_callback_reasons reason, + void *user, void *in, size_t len) +{ + switch (reason) { + + case LWS_CALLBACK_CLIENT_RECEIVE: +// fprintf(stderr, "rx %d '%s'\n", len, in); + break; + + default: + break; + } + + return 0; +} + + +/* list of supported protocols and callbacks */ + +static struct libwebsocket_protocols protocols[] = { + + [PROTOCOL_DUMB_INCREMENT] = { + .name = "dumb-increment-protocol", + .callback = callback_dumb_increment, + }, + [PROTOCOL_LWS_MIRROR] = { + .name = "lws-mirror-protocol", + .callback = callback_lws_mirror, + }, + [DEMO_PROTOCOL_COUNT] = { /* end of list */ + .callback = NULL + } +}; + +static struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "port", required_argument, NULL, 'p' }, + { "ssl", no_argument, NULL, 's' }, + { NULL, 0, 0, 0 } +}; + + +int main(int argc, char **argv) +{ + int n = 0; + int port = 7681; + int use_ssl = 0; + struct libwebsocket_context *context; + const char * address = argv[1]; + struct libwebsocket *wsi_dumb; + struct libwebsocket *wsi_mirror; + unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1024 + + LWS_SEND_BUFFER_POST_PADDING]; + int len; + int i = 0; + int xofs; + int yofs; + int oldx; + int oldy; + int scale; + int colour; + + fprintf(stderr, "libwebsockets test client\n" + "(C) Copyright 2010 Andy Green " + "licensed under LGPL2.1\n"); + + if (argc < 2) + goto usage; + + optind++; + + while (n >= 0) { + n = getopt_long(argc, argv, "hsp:", options, NULL); + if (n < 0) + continue; + switch (n) { + case 's': + use_ssl = 1; + break; + case 'p': + port = atoi(optarg); + break; + case 'h': + goto usage; + } + } + + /* + * create the websockets context. This tracks open connections and + * knows how to route any traffic and which protocol version to use, + * and if each connection is client or server side. + * + * For this client-only demo, we tell it to not listen on any port. + */ + + context = libwebsocket_create_context(CONTEXT_PORT_NO_LISTEN, + protocols, NULL, NULL, -1, -1); + if (context == NULL) { + fprintf(stderr, "Creating libwebsocket context failed\n"); + return 1; + } + + + /* create a client websocket using dumb increment protocol */ + + wsi_dumb = libwebsocket_client_connect(context, address, port, "/", + "http://host", "origin", + protocols[PROTOCOL_DUMB_INCREMENT].name); + + if (wsi_dumb == NULL) { + fprintf(stderr, "libwebsocket dumb connect failed\n"); + return -1; + } + + /* create a client websocket using mirror protocol */ + + wsi_mirror = libwebsocket_client_connect(context, address, port, "/", + "http://host", "origin", + protocols[PROTOCOL_LWS_MIRROR].name); + + if (wsi_mirror == NULL) { + fprintf(stderr, "libwebsocket dumb connect failed\n"); + return -1; + } + + fprintf(stderr, "Websocket connections opened\n"); + + /* + * sit there servicing the websocket context to handle incoming + * packets, and drawing random circles on the mirror protocol websocket + */ + + n = 0; + while (n >= 0) { + + usleep(10000); + + if (i == sizeof circle / sizeof circle[0]) + i = 0; + + if (i == 0) { + xofs = random() % 500; + yofs = random() % 250; + scale = random() % 24; + if (!scale) + scale = 1; + + oldx = xofs + (circle[i].x / scale); + oldy = yofs + (circle[i].y / scale); + colour = random() & 0xffffff; + } + + len = sprintf(&buf[LWS_SEND_BUFFER_PRE_PADDING], + "d #%06X %d %d %d %d", colour, oldx, oldy, + xofs + (circle[i].x / scale), + yofs + (circle[i].y / scale)); + oldx = xofs + (circle[i].x / scale); + oldy = yofs + (circle[i].y / scale); + i++; + + libwebsocket_write(wsi_mirror, + &buf[LWS_SEND_BUFFER_PRE_PADDING], len, LWS_WRITE_TEXT); + + + n = libwebsocket_service(context, 0); + } + + libwebsocket_client_close(wsi_dumb); + libwebsocket_client_close(wsi_mirror); + + return 0; + +usage: + fprintf(stderr, "Usage: libwebsockets-test-client " + " [--port=

] " + "[--ssl]\n"); + return 1; +} diff --git a/test-server/test-server.c b/test-server/test-server.c index 36a1978..dc3cfd9 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -230,7 +230,7 @@ int main(int argc, char **argv) LWS_SEND_BUFFER_POST_PADDING]; int port = 7681; int use_ssl = 0; - struct libwebsocket_context *server; + struct libwebsocket_context *context; fprintf(stderr, "libwebsockets test server\n" "(C) Copyright 2010 Andy Green " @@ -257,9 +257,9 @@ int main(int argc, char **argv) if (!use_ssl) cert_path = key_path = NULL; - server = libwebsocket_create_server(port, protocols, cert_path, + context = libwebsocket_create_context(port, protocols, cert_path, key_path, -1, -1); - if (server == NULL) { + if (context == NULL) { fprintf(stderr, "libwebsocket init failed\n"); return -1; } @@ -304,7 +304,7 @@ int main(int argc, char **argv) * immediately and quickly. */ - libwebsocket_service(server, 0); + libwebsocket_service(context, 0); } #else @@ -320,7 +320,7 @@ int main(int argc, char **argv) * don't have to take care about it. */ - n = libwebsockets_fork_service_loop(server); + n = libwebsockets_fork_service_loop(context); if (n < 0) { fprintf(stderr, "Unable to fork service loop %d\n", n); return 1; -- 2.7.4