From 92638847a3b7f51fb795c7986b793536ece6beee Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 19 May 2019 11:03:45 +0200 Subject: Documentation & peer discovery API --- builtins.c | 11 +++++++++++ index.html | 6 ++++-- websocksy.c | 12 +++++++++-- websocksy.h | 49 +++++++++++++++++++++++++++++++++++++++------ ws_proto.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 builtins.c diff --git a/builtins.c b/builtins.c new file mode 100644 index 0000000..2e3d285 --- /dev/null +++ b/builtins.c @@ -0,0 +1,11 @@ +static ws_peer_info default_peer = { + 0 +}; + +ws_peer_info backend_defaultpeer_query(char* endpoint, size_t protocols, char** protocol, size_t headers, ws_http_header* header, websocket* ws){ + //return a copy of the default peer + ws_peer_info peer = { + 0 + }; + return peer; +} diff --git a/index.html b/index.html index 845744b..3ce4343 100644 --- a/index.html +++ b/index.html @@ -2,10 +2,11 @@ diff --git a/websocksy.c b/websocksy.c index 55e8d35..714c3f5 100644 --- a/websocksy.c +++ b/websocksy.c @@ -37,7 +37,7 @@ char* xstr_lower(char* in){ } #include "network.c" -#include "ws_proto.c" +#include "builtins.c" /* * WebSocket interface & peer discovery configuration @@ -48,9 +48,17 @@ static struct { ws_backend backend; } config = { .host = "::", - .port = "8001" + .port = "8001", + .backend.query = backend_defaultpeer_query }; +int connect_peer(websocket* ws){ + ws->peer = config.backend.query(ws->request_path, ws->protocols, ws->protocol, ws->headers, ws->header, ws); + return 0; +} + +#include "ws_proto.c" + /* * Signal handler, attached to SIGINT */ diff --git a/websocksy.h b/websocksy.h index 5a4f24c..97cc338 100644 --- a/websocksy.h +++ b/websocksy.h @@ -125,17 +125,54 @@ typedef struct /*_web_socket*/ { } websocket; /* - * Peer discovery backend + * Peer discovery backend API * - * Used to dynamically select the peer based on parameters supplied by the WebSocket connection. - * The backend maps WebSocket characteristics (such as the endpoint used, the supported protocols and - * HTTP client headers) to a TCP/UDP/Unix socket peer endpoint using some form of user-configurable - * provider (such as databases, files, crystal balls or sheer guesses). + * Peer discovery backends are used to dynamically select the peer based on parameters supplied by + * the WebSocket connection. The backend maps WebSocket characteristics (such as the endpoint used, + * the supported protocols and HTTP client headers) to a TCP/UDP/Unix socket peer endpoint using + * some form of possibly user-configurable provider (such as databases, files, crystal balls or + * sheer guesses). * + * Backends are supplied as shared objects exporting the following symbols: + * * `init`: Optional, initialize any storage or connections required + * * `configure`: Optional, configure the backend + * * `query`: Required, find a peer for the given parameters + * * `cleanup`: Optional, Release any acquired resources prior to shutdown + */ + +/* + * Called once for the initialization of backend resources if the backend shared objects exports it as + * the `init` symbol. + * Returns the WEBSOCKSY_API_VERSION used to compile the backend. + */ +typedef uint64_t (*ws_backend_init)(); +/* + * Called once for every backend configuration variable if the backend shared object exports it as + * the `configure` symbol. + * Returns 0 on success, anything else fails configuration. + */ +typedef uint64_t (*ws_backend_configure)(char* key, char* value); +/* + * Called when a WebSocket successfully negotiates a connection upgrade and is to be connected with + * a peer. Exported as the `query` symbol from the backend shared object. * The return value is a structure containing the destination address and transport to be connected to * the Web Socket, as well as the indicated subprotocol to use (or none, if set to the provided maximum * number of protocols). * The fields within the structure should be allocated with `calloc` and will be free'd by websocky * after use. */ -typedef ws_peer_info (*ws_backend)(char* endpoint, size_t protocols, char** protocol, size_t headers, ws_http_header* header, websocket* ws); +typedef ws_peer_info (*ws_backend_query)(char* endpoint, size_t protocols, char** protocol, size_t headers, ws_http_header* header, websocket* ws); +/* + * Called once for the release of all backend-internal resources if exported as the `cleanup` symbol. + */ +typedef void (*ws_backend_cleanup)(); + +/* + * Composite backend model structure + */ +typedef struct /*_ws_backend*/ { + ws_backend_init init; + ws_backend_configure config; + ws_backend_query query; + ws_backend_cleanup cleanup; +} ws_backend; diff --git a/ws_proto.c b/ws_proto.c index bfcce11..35d9933 100644 --- a/ws_proto.c +++ b/ws_proto.c @@ -11,6 +11,9 @@ static websocket* sock = NULL; int ws_close(websocket* ws, ws_close_reason code, char* reason){ size_t p; + ws_peer_info empty_peer = { + 0 + }; if(ws->state == ws_open && reason){ //TODO send close frame @@ -52,6 +55,10 @@ int ws_close(websocket* ws, ws_close_reason code, char* reason){ ws->websocket_version = 0; ws->want_upgrade = 0; + free(ws->peer.host); + free(ws->peer.port); + ws->peer = empty_peer; + return 0; } @@ -117,6 +124,13 @@ int ws_upgrade_http(websocket* ws){ if(ws->websocket_version == 13 && ws->socket_key && ws->want_upgrade == 3){ + + //find and connect peer + if(connect_peer(ws)){ + ws_close(ws, ws_close_http, "500 Peer connection failed"); + return 0; + } + if(network_send_str(ws->ws_fd, "HTTP/1.1 101 Upgrading\r\n") || network_send_str(ws->ws_fd, "Upgrade: websocket\r\n") || network_send_str(ws->ws_fd, "Connection: Upgrade\r\n")){ @@ -124,9 +138,9 @@ int ws_upgrade_http(websocket* ws){ return 0; } - //calculate the websocket key which for some reason is defined as + //calculate the websocket accept key, which for some reason is defined as //base64(sha1(concat(trim(client-key), "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))) - //which requires not one but 2 unnecessarily complex operations + //requiring not one but 2 unnecessarily complex operations size_t encode_offset = 0; struct sha1_ctx ws_accept_ctx; struct base64_encode_ctx ws_accept_encode; @@ -148,8 +162,14 @@ int ws_upgrade_http(websocket* ws){ return 0; } - //TODO Sec-Websocket-Protocol - //TODO find/connect peer + //acknowledge selected protocol + if(ws->peer.protocol < ws->protocols){ + if(network_send_str(ws->ws_fd, "Sec-WebSocket-Protocol: ") + || network_send_str(ws->ws_fd, ws->protocol[ws->peer.protocol])){ + ws_close(ws, ws_close_http, NULL); + return 0; + } + } ws->state = ws_open; if(network_send_str(ws->ws_fd, "\r\n")){ @@ -158,7 +178,11 @@ int ws_upgrade_http(websocket* ws){ } return 0; } - //TODO RFC 4.2.2.4: An unsupported version must be answered with HTTP 426 + //RFC 4.2.2.4: An unsupported version must be answered with HTTP 426 + if(ws->websocket_version != 13){ + ws_close(ws, ws_close_http, "426 Unsupported protocol version"); + return 0; + } return 1; } @@ -205,7 +229,34 @@ int ws_handle_http(websocket* ws){ ws->want_upgrade |= 2; } else if(!strcmp(header, "Sec-WebSocket-Protocol")){ - //TODO parse websocket protocol offers + //parse websocket protocol offers + for(value = strtok(value, ","); value; value = strtok(NULL, ",")){ + //ltrim + for(; *value && isspace(*value); value++){ + } + + //rtrim + for(p = strlen(value) - 1; p >= 0 && isspace(value[p]); p--){ + value[p] = 0; + } + + for(p = 0; p < ws->protocols; p++){ + if(!strcmp(ws->protocol[p], value)){ + break; + } + } + + //add new protocol + if(p == ws->protocols){ + ws->protocol = realloc(ws->protocol, (ws->protocols + 1) * sizeof(char*)); + if(!ws->protocol){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } + ws->protocol[ws->protocols] = strdup(value); + ws->protocols++; + } + } } else if(ws->headers < WS_HEADER_LIMIT){ ws->header[ws->headers].tag = strdup(header); @@ -349,7 +400,7 @@ int ws_data(websocket* ws){ //terminate new data ws->read_buffer[ws->read_buffer_offset + bytes_read] = 0; - + switch(ws->state){ case ws_new: case ws_http: @@ -384,6 +435,7 @@ int ws_data(websocket* ws){ memmove(ws->read_buffer, ws->read_buffer + n, ws->read_buffer_offset - n); ws->read_buffer_offset -= n; } + break; //this should never be reached, as ws_close also closes the client fd case ws_closed: fprintf(stderr, "This should not have happened\n"); -- cgit v1.2.3