diff --git a/Makefile b/Makefile index 402227ac..251925de 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,14 @@ +CFLAGS= -Wall -Werror -rdynamic -fPIC -c + all: - gcc -Wall -Werror -rdynamic -fPIC -c libwebsockets.c - gcc -Wall -Werror -rdynamic -fPIC -c md5.c + gcc $(CFLAGS) libwebsockets.c + gcc $(CFLAGS) md5.c gcc libwebsockets.o md5.o --shared -o libwebsockets.so + + gcc $(CFLAGS) test-server.c + gcc test-server.o ./libwebsockets.so -o test-server clean: - rm -f *.o *.so + rm -f *.o *.so test-server + diff --git a/libwebsockets.c b/libwebsockets.c index 9e7c1d89..6f2e03b3 100644 --- a/libwebsockets.c +++ b/libwebsockets.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -14,8 +15,69 @@ #include "libwebsockets.h" void md5(const unsigned char *input, int ilen, unsigned char output[16]); +static void libwebsocket_service(struct libwebsocket *wsi, int sock); + +#define LWS_MAX_HEADER_NAME_LENGTH 64 +#define LWS_MAX_HEADER_LEN 4096 +#define LWS_INITIAL_HDR_ALLOC 256 +#define LWS_ADDITIONAL_HDR_ALLOC 64 + + +enum lws_connection_states { + WSI_STATE_CLOSED, + WSI_STATE_HANDSHAKE_RX, + WSI_STATE_DEAD_SOCKET, + WSI_STATE_ESTABLISHED +}; + +enum lws_token_indexes { + WSI_TOKEN_GET_URI, + WSI_TOKEN_HOST, + WSI_TOKEN_CONNECTION, + WSI_TOKEN_KEY1, + WSI_TOKEN_KEY2, + WSI_TOKEN_PROTOCOL, + WSI_TOKEN_UPGRADE, + WSI_TOKEN_ORIGIN, + WSI_TOKEN_CHALLENGE, + + /* always last real token index*/ + WSI_TOKEN_COUNT, + /* parser state additions */ + WSI_TOKEN_NAME_PART, + WSI_TOKEN_SKIPPING, + WSI_TOKEN_SKIPPING_SAW_CR, + WSI_PARSING_COMPLETE +}; + + +struct lws_tokens { + char * token; + int token_len; +}; + + +/* + * This is totally opaque to code using the library. It's exported as a + * forward-reference pointer-only declaration. + */ + +struct libwebsocket { + int (*callback)(struct libwebsocket *, + enum libwebsocket_callback_reasons reason); + + enum lws_connection_states state; + + char name_buffer[LWS_MAX_HEADER_NAME_LENGTH]; + int name_buffer_pos; + int current_alloc_len; + enum lws_token_indexes parser_state; + struct lws_tokens utf8_token[WSI_TOKEN_COUNT]; + int ietf_spec_revision; + + int sock; +}; -void dostuff(struct libwebsocket *wsi, int sock); const struct lws_tokens lws_tokens[WSI_TOKEN_COUNT] = { { "GET ", 4 }, @@ -29,24 +91,47 @@ const struct lws_tokens lws_tokens[WSI_TOKEN_COUNT] = { { "\x0d\x0a", 2 }, }; -int libwebsocket_init(struct libwebsocket *wsi, int port) +int libwebsocket_create_server(int port, int (*callback)(struct libwebsocket *, enum libwebsocket_callback_reasons)) { int n; int sockfd, newsockfd; unsigned int clilen; struct sockaddr_in serv_addr, cli_addr; int pid; + struct libwebsocket *wsi = malloc(sizeof(struct libwebsocket)); + + if (!wsi) + return -1; wsi->state = WSI_STATE_CLOSED; wsi->name_buffer_pos = 0; - wsi->response = NULL; for (n = 0; n < WSI_TOKEN_COUNT; n++) { wsi->utf8_token[n].token = NULL; wsi->utf8_token[n].token_len = 0; } + + wsi->callback = callback; + wsi->ietf_spec_revision = 0; - /* fork off a master server for this websocket server */ + /* sit there listening for connects, accept and spawn session servers */ + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + fprintf(stderr, "ERROR opening socket"); + } + 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 %d %d\n", n, errno); + return -1; + } + + /* fork off a master server for this websocket server */ n = fork(); if (n < 0) { @@ -59,18 +144,6 @@ int libwebsocket_init(struct libwebsocket *wsi, int port) if (n) return 0; - /* sit there listening for connects, accept and spawn session servers */ - - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if (sockfd < 0) - fprintf(stderr, "ERROR opening socket"); - 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); - if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) - fprintf(stderr, "ERROR on binding"); listen(sockfd, 5); @@ -78,23 +151,32 @@ int libwebsocket_init(struct libwebsocket *wsi, int port) clilen = sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); - if (newsockfd < 0) + if (newsockfd < 0) { fprintf(stderr, "ERROR on accept"); + continue; + } /* fork off a new server instance */ pid = fork(); - if (pid < 0) + if (pid < 0) { fprintf(stderr, "ERROR on fork"); - if (!pid) { - close(sockfd); - - /* sit in dostuff() until session socket closed */ - - dostuff(wsi, newsockfd); - exit(0); - } else + continue; + } + + if (pid) { close(newsockfd); + continue; + } + + /* we are the session process */ + + close(sockfd); + + /* sit in libwebsocket_service() until session socket closed */ + + libwebsocket_service(wsi, newsockfd); + exit(0); } } @@ -104,15 +186,12 @@ void libwebsocket_close(struct libwebsocket *wsi) wsi->state = WSI_STATE_DEAD_SOCKET; - if (wsi->websocket_closed_callback) - wsi->websocket_closed_callback(wsi); + if (wsi->callback) + wsi->callback(wsi, LWS_CALLBACK_CLOSED); for (n = 0; n < WSI_TOKEN_COUNT; n++) if (wsi->utf8_token[n].token) free(wsi->utf8_token[n].token); - - if (wsi->response) - free(wsi->response); } @@ -174,12 +253,12 @@ static int libwebsocket_parse(struct libwebsocket *wsi, unsigned char c) wsi->utf8_token[wsi->parser_state].token_len++] = c; /* special payload limiting */ - if (wsi->parser_state == WSI_TOKEN_CHALLENGE) - if (wsi->utf8_token[wsi->parser_state].token_len == 8) { -// fprintf(stderr, "Setting WSI_PARSING_COMPLETE\n"); - wsi->parser_state = WSI_PARSING_COMPLETE; - break; - } + if (wsi->parser_state == WSI_TOKEN_CHALLENGE && + wsi->utf8_token[wsi->parser_state].token_len == 8) { +// fprintf(stderr, "Setting WSI_PARSING_COMPLETE\n"); + wsi->parser_state = WSI_PARSING_COMPLETE; + break; + } break; @@ -274,7 +353,8 @@ static int interpret_key(const char *key, unsigned int *result) /* * We have to take care about parsing because the headers may be split * into multiple fragments. They may contain unknown headers with arbitrary - * argument lengths. + * argument lengths. So, we parse using a single-character at a time state + * machine that is completely independent of packet size. */ int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) @@ -283,11 +363,13 @@ int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) char *p; unsigned int key1, key2; unsigned char sum[16]; + char *response; switch (wsi->state) { case WSI_STATE_CLOSED: wsi->state = WSI_STATE_HANDSHAKE_RX; wsi->parser_state = WSI_TOKEN_NAME_PART; + /* fallthru */ case WSI_STATE_HANDSHAKE_RX: fprintf(stderr, "issuing %ld bytes to parser\n", len); @@ -320,7 +402,7 @@ int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) /* create the response packet */ - wsi->response = malloc(256 + + response = malloc(256 + wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len + wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len + wsi->utf8_token[WSI_TOKEN_HOST].token_len + @@ -331,7 +413,7 @@ int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) fprintf(stderr, "'%s;\n", wsi->utf8_token[WSI_TOKEN_HOST].token); - p = wsi->response; + p = response; strcpy(p, "HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0aUpgrade: WebSocket\x0d\x0a"); p += strlen("HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0aUpgrade: WebSocket\x0d\x0a"); strcpy(p, "Connection: Upgrade\x0d\x0aSec-WebSocket-Origin: "); @@ -349,15 +431,22 @@ int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { strcpy(p, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); p += wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len; + } else { + strcpy(p, "none"); + p += strlen("none"); } strcpy(p, "\x0d\x0a\x0d\x0a"); p += strlen("\x0d\x0a\x0d\x0a"); + /* convert the two keys into 32-bit integers */ + if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY1].token, &key1)) goto bail; if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY2].token, &key2)) goto bail; + + /* lay them out in network byte order (MSB first */ sum[0] = key1 >> 24; sum[1] = key1 >> 16; @@ -367,20 +456,42 @@ int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) sum[5] = key2 >> 16; sum[6] = key2 >> 8; sum[7] = key2; + + /* follow them with the challenge token we were sent */ + memcpy(&sum[8], wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 8); + /* + * compute the md5sum of that 16-byte series and use as our + * payload after our headers + */ + md5(sum, 16, (unsigned char *)p); p += 16; - wsi->response_length = p - wsi->response; - - wsi->state = WSI_STATE_ISSUE_HANDSHAKE; + /* it's complete, go ahead and send it */ - if (wsi->websocket_established_callback) - wsi->websocket_established_callback(wsi); + fprintf(stderr, "issuing response packet %d len\n", + (int)(p - response)); + fwrite(response, 1, p - response, stderr); + + n = write(wsi->sock, response, p - response); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + goto bail; + } + + free(response); + wsi->state = WSI_STATE_ESTABLISHED; + + /* notify user code that we're ready to roll */ + + if (wsi->callback) + wsi->callback(wsi, LWS_CALLBACK_ESTABLISHED); break; case WSI_STATE_ESTABLISHED: + fprintf(stderr, "received %ld byte packet\n", len); break; default: break; @@ -393,8 +504,74 @@ bail: return -1; } +int libwebsocket_write(struct libwebsocket * wsi, void *buf, size_t len) +{ + int n; + unsigned char hdr[9]; + + if (wsi->state != WSI_STATE_ESTABLISHED) + return -1; -void dostuff(struct libwebsocket *wsi, int sock) + switch (wsi->ietf_spec_revision) { + /* chrome */ + case 0: + hdr[0] = 0xff; + hdr[1] = len >> 56; + hdr[2] = len >> 48; + hdr[3] = len >> 40; + hdr[4] = len >> 32; + hdr[5] = len >> 24; + hdr[6] = len >> 16; + hdr[7] = len >> 8; + hdr[8] = len; + + n = write(wsi->sock, hdr, sizeof hdr); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + return -1; + } + break; + /* just an unimplemented spec right now apparently */ + case 2: + n = 0; + if (len < 126) { + hdr[n++] = 0x04; + hdr[n++] = len; + } else { + if (len < 65536) { + hdr[n++] = 0x04; /* text frame */ + hdr[n++] = 126; + hdr[n++] = len >> 8; + hdr[n++] = len; + } else { + hdr[n++] = 0x04; + hdr[n++] = 127; + hdr[n++] = len >> 24; + hdr[n++] = len >> 16; + hdr[n++] = len >> 8; + hdr[n++] = len; + } + } + n = write(wsi->sock, hdr, n); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + return -1; + } + break; + } + + n = write(wsi->sock, buf, len); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + return -1; + } + + fprintf(stderr, "written %d bytes to websocket\n", (int)len); + + return 0; +} + +static void libwebsocket_service(struct libwebsocket *wsi, int sock) { int n; unsigned char buf[256]; @@ -435,23 +612,11 @@ void dostuff(struct libwebsocket *wsi, int sock) libwebsocket_read(wsi, buf, n); } - if (wsi->state == WSI_STATE_ISSUE_HANDSHAKE) { - - fprintf(stderr, "issuing response packet %d len\n", wsi->response_length); - - fwrite(wsi->response, 1, wsi->response_length, stderr); - - n = write(sock, wsi->response, wsi->response_length); - if (n < 0) { - fprintf(stderr, "ERROR writing to socket"); - exit(1); - } - wsi->state = WSI_STATE_ESTABLISHED; + if (wsi->state != WSI_STATE_ESTABLISHED) continue; - } - if (wsi->websocket_send_callback) - wsi->websocket_send_callback(wsi); + if (wsi->callback) + wsi->callback(wsi, LWS_CALLBACK_SEND); } } diff --git a/libwebsockets.h b/libwebsockets.h index 5258a178..870b7fe3 100644 --- a/libwebsockets.h +++ b/libwebsockets.h @@ -1,68 +1,15 @@ -#define LWS_MAX_HEADER_NAME_LENGTH 64 -#define LWS_MAX_HEADER_LEN 4096 -#define LWS_INITIAL_HDR_ALLOC 256 -#define LWS_ADDITIONAL_HDR_ALLOC 64 - - -enum lws_connection_states { - WSI_STATE_CLOSED, - WSI_STATE_HANDSHAKE_RX, - WSI_STATE_ISSUE_HANDSHAKE, - WSI_STATE_DEAD_SOCKET, - WSI_STATE_ESTABLISHED +enum libwebsocket_callback_reasons { + LWS_CALLBACK_ESTABLISHED, + LWS_CALLBACK_CLOSED, + LWS_CALLBACK_SEND, + LWS_CALLBACK_RECEIVE, }; -enum lws_token_indexes { - WSI_TOKEN_GET_URI, - WSI_TOKEN_HOST, - WSI_TOKEN_CONNECTION, - WSI_TOKEN_KEY1, - WSI_TOKEN_KEY2, - WSI_TOKEN_PROTOCOL, - WSI_TOKEN_UPGRADE, - WSI_TOKEN_ORIGIN, - WSI_TOKEN_CHALLENGE, - - /* always last real token index*/ - WSI_TOKEN_COUNT, - /* parser state additions */ - WSI_TOKEN_NAME_PART, - WSI_TOKEN_SKIPPING, - WSI_TOKEN_SKIPPING_SAW_CR, - WSI_PARSING_COMPLETE -}; +struct libwebsocket; +extern int libwebsocket_create_server(int port, + int (*callback)(struct libwebsocket *, + enum libwebsocket_callback_reasons)); -struct lws_tokens { - char * token; - int token_len; -}; - -struct libwebsocket { - - /* set these up before calling libwebsocket_init */ - - int (*websocket_established_callback)(struct libwebsocket *); - int (*websocket_closed_callback)(struct libwebsocket *); - int (*websocket_send_callback)(struct libwebsocket *); - int (*websocket_receive_callback)(struct libwebsocket *); - - /* these are all opaque and maintained by the library */ - - enum lws_connection_states state; - - char name_buffer[LWS_MAX_HEADER_NAME_LENGTH]; - int name_buffer_pos; - int current_alloc_len; - enum lws_token_indexes parser_state; - struct lws_tokens utf8_token[WSI_TOKEN_COUNT]; - char * response; - int response_length; - - int sock; -}; - - -extern int libwebsocket_init(struct libwebsocket *wsi, int port); - +extern int libwebsocket_write(struct libwebsocket *, void *buf, size_t len); diff --git a/test-server.c b/test-server.c new file mode 100644 index 00000000..0056e805 --- /dev/null +++ b/test-server.c @@ -0,0 +1,55 @@ +#include +#include +#include + +#include "libwebsockets.h" + +#define PORT 7681 + +int websocket_callback(struct libwebsocket * wsi, + enum libwebsocket_callback_reasons reason) +{ + int n; + char buf[256]; + static int bump; + + switch (reason) { + case LWS_CALLBACK_ESTABLISHED: + fprintf(stderr, "Websocket connection established\n"); + break; + + case LWS_CALLBACK_CLOSED: + fprintf(stderr, "Websocket connection closed\n"); + break; + + case LWS_CALLBACK_SEND: + sleep(1); + n = sprintf(buf, "%d\n", bump++); + n = libwebsocket_write(wsi, buf, n); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + exit(1); + } + break; + + case LWS_CALLBACK_RECEIVE: + break; + } + return 0; +} + + +int main(int argv, char **argc) +{ + if (libwebsocket_create_server(PORT, websocket_callback) < 0) { + fprintf(stderr, "libwebsocket init failed\n"); + return -1; + } + + fprintf(stderr, "Listening on port %d\n", PORT); + + while (1) + sleep(1); + + return 0; +} diff --git a/test.html b/test.html new file mode 100644 index 00000000..5d8b46f8 --- /dev/null +++ b/test.html @@ -0,0 +1,39 @@ + + + + + Minimal Websocket message sender + + + + + + +