From 79e063d6aa6116231fdfb374f52507236ca3770e Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 25 May 2019 09:10:55 +0200 Subject: Basic forwarding and builtin extension functions --- builtins.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- index.html | 2 +- makefile | 2 +- websocksy.c | 15 ++++++- websocksy.h | 4 +- ws_proto.c | 34 ++++++++++++++- 6 files changed, 182 insertions(+), 15 deletions(-) diff --git a/builtins.c b/builtins.c index b75dcd0..fa73260 100644 --- a/builtins.c +++ b/builtins.c @@ -1,26 +1,150 @@ -static ws_peer_info default_peer = { - .transport = peer_tcp_client, - .host = "localhost", - .port = "5900" -}; +#define UTF8_BYTE(a) ((a & 0xC0) == 0x80) -//TODO backend configuration +/* + * The defaultpeer backend returns the same peer configured peer for any + * incoming connection. This may be useful for testing or for configurations + * where there is just one peer anyway. + */ + +/* Global data storage for the backend */ +static ws_peer_info default_peer = {0}; +static char* default_peer_proto = NULL; + +/* + * Initialization function for the defaultpeer backend + * Heap-allocate all data so it can be safely free'd at cleanup + */ +uint64_t backend_defaultpeer_init(){ + ws_peer_info startup_peer = { + .transport = peer_tcp_client, + .host = strdup("localhost"), + .port = strdup("5900") + }; + + default_peer = startup_peer; + return WEBSOCKSY_API_VERSION; +} + +/* + * Configuration function for the defaultpeer backend + */ +uint64_t backend_defaultpeer_configure(char* key, char* value){ + if(!strcmp(key, "host")){ + //TODO extract peer protocol + free(default_peer.host); + default_peer.host = strdup(value); + } + else if(!strcmp(key, "port")){ + free(default_peer.port); + default_peer.port = strdup(value); + } + else if(!strcmp(key, "protocol")){ + free(default_peer_proto); + default_peer_proto = strdup(value); + } + return 1; +} + +/* + * defaultpeer backend core + * Returns the configured default peer for any incoming request and selects either + * a matching or no subprotocol. + */ ws_peer_info backend_defaultpeer_query(char* endpoint, size_t protocols, char** protocol, size_t headers, ws_http_header* header, websocket* ws){ + size_t p; //return a copy of the default peer ws_peer_info peer = default_peer; peer.host = (default_peer.host) ? strdup(default_peer.host) : NULL; peer.port = (default_peer.port) ? strdup(default_peer.port) : NULL; - //TODO backend protocol discovery + //if none set, announce none peer.protocol = protocols; + + if(default_peer_proto){ + for(p = 0; p < protocols; p++){ + if(!strcasecmp(protocol[p], default_peer_proto)){ + peer.protocol = p; + break; + } + } + + //if any, return first + if(!strcmp(default_peer_proto, "*")){ + peer.protocol = 0; + } + } return peer; } +/* + * Cleanup function for the defaultpeer backend. + * Frees all allocated data. + */ +void backend_defaultpeer_cleanup(){ + free(default_peer.host); + default_peer.host = NULL; + free(default_peer.port); + default_peer.port = NULL; + free(default_peer_proto); + default_peer_proto = NULL; +} + +/* + * The `auto` stream framing function forwards all incoming data from the peer immediately, + * with the text frame type if the data is valid UTF8 and the binary frame type otherwise. + */ int64_t framing_auto(uint8_t* data, size_t length, size_t last_read, ws_operation* opcode, void** framing_data, char* config){ - //TODO implement auto framer + size_t p; + uint8_t valid = 1; + for(p = 0; p < length; p++){ + //4-byte codepoint + if((data[p] & 0xF8) == 0xF0){ + if((p + 3) >= length + || !UTF8_BYTE(data[p + 1]) + || !UTF8_BYTE(data[p + 2]) + || !UTF8_BYTE(data[p + 3])){ + valid = 0; + break; + } + p += 3; + } + //3 byte codepoint + else if((data[p] & 0xF0) == 0xE0){ + if((p + 2) >= length + || !UTF8_BYTE(data[p + 1]) + || !UTF8_BYTE(data[p + 2])){ + valid = 0; + break; + } + p += 2; + } + //2 byte codepoint + else if((data[p] & 0xE0) == 0xC0){ + if((p + 1) >= length + || !UTF8_BYTE(data[p + 1])){ + valid = 0; + break; + } + p++; + } + //not a 1 byte codepoint -> not utf8 + else if(data[p] & 0x80){ + valid = 0; + break; + } + } + + //if valid utf8, send as text frame + if(valid){ + *opcode = ws_frame_text; + } + return length; } +/* + * The `binary` peer stream framing function forwards all incoming data from the peer immediately as binary frames + */ int64_t framing_binary(uint8_t* data, size_t length, size_t last_read, ws_operation* opcode, void** framing_data, char* config){ return length; } diff --git a/index.html b/index.html index 3ce4343..1981b83 100644 --- a/index.html +++ b/index.html @@ -18,7 +18,7 @@ openSocket(); setInterval(function(){ webSocket.send(payload); - payload += payload; + //payload += payload; }, 1000); } diff --git a/makefile b/makefile index 2d23909..0a9c4ae 100644 --- a/makefile +++ b/makefile @@ -3,7 +3,7 @@ LDLIBS=-lnettle all: websocksy -websocksy: websocksy.c websocksy.h ws_proto.c +websocksy: websocksy.c websocksy.h ws_proto.c builtins.c $(CC) $(CFLAGS) $(LDLIBS) $< -o $@ clean: diff --git a/websocksy.c b/websocksy.c index e4f7886..6ab0f81 100644 --- a/websocksy.c +++ b/websocksy.c @@ -50,7 +50,11 @@ static struct { } config = { .host = "::", .port = "8001", - .backend.query = backend_defaultpeer_query + /* Assign the built-in defaultpeer backend by default */ + .backend.init = backend_defaultpeer_init, + .backend.config = backend_defaultpeer_configure, + .backend.query = backend_defaultpeer_query, + .backend.cleanup = backend_defaultpeer_cleanup }; int connect_peer(websocket* ws){ @@ -185,6 +189,12 @@ int main(int argc, char** argv){ exit(usage(argv[0])); } + //initialize the selected peer discovery backend + if(config.backend.init() != WEBSOCKSY_API_VERSION){ + fprintf(stderr, "The selected backend was built for another API version than the core\n"); + exit(EXIT_FAILURE); + } + //open listening socket listen_fd = network_socket(config.host, config.port, SOCK_STREAM, 1); if(listen_fd < 0){ @@ -255,6 +265,9 @@ int main(int argc, char** argv){ } //cleanup + if(config.backend.cleanup){ + config.backend.cleanup(); + } ws_cleanup(); close(listen_fd); return 0; diff --git a/websocksy.h b/websocksy.h index a3206f7..59ec5b2 100644 --- a/websocksy.h +++ b/websocksy.h @@ -164,8 +164,8 @@ typedef struct /*_web_socket*/ { */ /* - * Called once for the initialization of backend resources if the backend shared objects exports it as - * the `init` symbol. + * Called once for the initialization of backend resources. Exported as the `init` symbol from + * the backend shared object. * Returns the WEBSOCKSY_API_VERSION used to compile the backend. */ typedef uint64_t (*ws_backend_init)(); diff --git a/ws_proto.c b/ws_proto.c index 54da838..c75fe22 100644 --- a/ws_proto.c +++ b/ws_proto.c @@ -1,6 +1,8 @@ #define RFC6455_MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" +#define WS_FRAME_HEADER_LEN 16 -#define WS_GET_FIN(a) ((a & 0x80) >> 7) +#define WS_FLAG_FIN 0x80 +#define WS_GET_FIN(a) ((a & WS_FLAG_FIN) >> 7) #define WS_GET_RESERVED(a) ((a & 0xE0) >> 4) #define WS_GET_OP(a) ((a & 0x0F)) #define WS_GET_MASK(a) ((a & 0x80) >> 7) @@ -172,7 +174,8 @@ int ws_upgrade_http(websocket* ws){ //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])){ + || network_send_str(ws->ws_fd, ws->protocol[ws->peer.protocol]) + || network_send_str(ws->ws_fd, "\r\n")){ ws_close(ws, ws_close_http, NULL); return 0; } @@ -399,6 +402,33 @@ size_t ws_frame(websocket* ws){ int ws_send_frame(websocket* ws, ws_operation opcode, uint8_t* data, size_t len){ fprintf(stderr, "Peer -> WS %lu bytes\n", len); + uint8_t frame_header[WS_FRAME_HEADER_LEN]; + size_t header_bytes = 2; + uint16_t* payload_len16 = (uint16_t*) (frame_header + 2); + uint64_t* payload_len64 = (uint64_t*) (frame_header + 2); + + //FIXME might want to support segmented transfers here + //set up the basic frame header + frame_header[0] = WS_FLAG_FIN | opcode; + if(len <= 125){ + frame_header[1] = len; + } + else if(len <= 0xFFFF){ + frame_header[1] = 126; + *payload_len16 = htobe16(len); + header_bytes += 2; + } + else{ + frame_header[1] = 127; + *payload_len64 = htobe64(len); + header_bytes += 8; + } + + if(network_send(ws->ws_fd, frame_header, header_bytes) + || network_send(ws->ws_fd, data, len)){ + return 1; + } + return 0; } -- cgit v1.2.3