aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2019-06-20 08:19:29 +0200
committercbdev <cb@cbcdn.com>2019-06-20 08:19:29 +0200
commite60aa0c5779bce199020236c0ac3a4beace571cb (patch)
tree52b5df2816b944ceeeb6bb2762d6e7bd5cea457b
parent303e464579e2fc9961d4d9f4841e916f9aa8963f (diff)
downloadwebsocksy-e60aa0c5779bce199020236c0ac3a4beace571cb.tar.gz
websocksy-e60aa0c5779bce199020236c0ac3a4beace571cb.tar.bz2
websocksy-e60aa0c5779bce199020236c0ac3a4beace571cb.zip
Implement additional framing functions (Fixes #3)
-rw-r--r--README.md2
-rw-r--r--builtins.c125
-rw-r--r--config.c4
-rw-r--r--config.h2
-rw-r--r--plugin.c14
-rw-r--r--websocket.c16
-rw-r--r--websocket.h1
-rw-r--r--websocksy.c9
-rw-r--r--websocksy.h3
9 files changed, 162 insertions, 14 deletions
diff --git a/README.md b/README.md
index fda4e08..3c8df33 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,8 @@ framing functions (both built in and loaded from plugins).
* `auto`: Send all data immediately, with the `text` type if the content was detected as valid UTF-8 string, otherwise use a `binary` frame
* `binary`: Send all data immediately as a `binary` frame
+* `separator`: Waits until a variable-length separator is found in the stream and sends data up to and including that position as binary frame. Takes as parameter the separator string. The escape sequences `\r`, `\t`, `\n`, `\0`, `\f` and `\\` are recognized, arbitrary bytes can be specified hexadecimally using `\x<hex>`
+* `newline`: Waits until a newline sequence is found in the stream and sends data up to and including that position as text frame if all data is valid UTF-8 (data frame otherwise). The configuration string may be one of `crlf`, `lfcr`, `lf`, `cr` specifying the sequence to look for.
# Configuration / Usage
diff --git a/builtins.c b/builtins.c
index 5c1dfb8..41d76d4 100644
--- a/builtins.c
+++ b/builtins.c
@@ -1,4 +1,6 @@
#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
#include "websocksy.h"
#include "builtins.h"
@@ -151,7 +153,7 @@ int64_t framing_auto(uint8_t* data, size_t length, size_t last_read, ws_operatio
}
//if valid utf8, send as text frame
- if(valid){
+ if(valid && opcode){
*opcode = ws_frame_text;
}
@@ -165,12 +167,121 @@ int64_t framing_binary(uint8_t* data, size_t length, size_t last_read, ws_operat
return length;
}
-int64_t framing_separator(uint8_t* data, size_t length, size_t last_read, ws_operation* opcode, void** framing_data, const char* config){
- //TODO implement separator framer
- return length;
+/*
+ * The `separator` framing function waits until a variable-length separator is found in the stream and sends all
+ * data up to and including that separator as binary frame. The configuration string is used as the separator, with
+ * the escape sequences \r, \t, \n, \0, \f, \\ being recognized as their ASCII expressions. Arbitrary bytes can be
+ * specified hexadecimally using the syntax \x<hexbyte>
+ */
+typedef struct /*_separator_framing_config*/ {
+ size_t length;
+ uint8_t* separator;
+} framing_separator_config;
+
+int64_t framing_separator(uint8_t* data, size_t length, size_t last_read, ws_operation* opcode, void** framing_data, const char* framing_config){
+ size_t u, p = 0;
+ unsigned hex;
+ framing_separator_config* config = framing_data ? *framing_data : NULL;
+
+ if(data && !config){
+ //parse configuration
+ config = calloc(1, sizeof(framing_separator_config));
+ config->separator = (uint8_t*) strdup(framing_config);
+ for(u = 0; config->separator[u]; u++){
+ config->separator[p] = config->separator[u];
+ if(config->separator[u] == '\\'){
+ switch(config->separator[u + 1]){
+ case 0:
+ u--;
+ //fall through
+ case '0':
+ config->separator[p] = 0;
+ break;
+ case 't':
+ config->separator[p] = '\t';
+ break;
+ case 'n':
+ config->separator[p] = '\n';
+ break;
+ case 'f':
+ config->separator[p] = '\f';
+ break;
+ case 'r':
+ config->separator[p] = '\r';
+ break;
+ case '\\':
+ config->separator[p] = '\\';
+ break;
+ case 'x':
+ if(!isxdigit(config->separator[u + 2])
+ || !isxdigit(config->separator[u + 3])){
+ fprintf(stderr, "Prematurely terminated hex byte sequence in separator framing function\n");
+ return -1;
+ }
+ sscanf((char*) (config->separator + u + 3), "%02x", &hex);
+ config->separator[p] = hex;
+ u += 2;
+ }
+ u++;
+ }
+ p++;
+ }
+ config->length = p;
+ *framing_data = config;
+ }
+ else if(!data && config){
+ //free parsed configuration
+ free(config->separator);
+ config->separator = NULL;
+ config->length = 0;
+ free(config);
+ return 0;
+ }
+
+ if(config->length){
+ for(u = 0; u < last_read && (last_read - u) >= config->length; u++){
+ if(!memcmp(data + (length - last_read) + u, config->separator, config->length)){
+ return (length - last_read) + u + config->length;
+ }
+ }
+ }
+ else{
+ return length;
+ }
+ return 0;
}
-int64_t framing_newline(uint8_t* data, size_t length, size_t last_read, ws_operation* opcode, void** framing_data, const char* config){
- //TODO implement separator framer
- return length;
+/*
+ * The `newline` framing function waits until a newline sequence is found in the buffer and sends all data up to and
+ * including the newline as text frame, if the data is detected as valid UTF-8. Otherwise, a binary frame is used.
+ * The configuration string may be one of
+ * * crlf
+ * * lfcr
+ * * lf
+ * * cr
+ */
+int64_t framing_newline(uint8_t* data, size_t length, size_t last_read, ws_operation* opcode, void** framing_data, const char* framing_config){
+ int64_t bytes = 0;
+ char* expression = "\\r\\n";
+
+ if(data && (!framing_data || !(*framing_data))){
+ if(!strcmp(framing_config, "crlf")){
+ expression = "\\r\\n";
+ }
+ if(!strcmp(framing_config, "lfcr")){
+ expression = "\\n\\r";
+ }
+ if(!strcmp(framing_config, "lf")){
+ expression = "\\n";
+ }
+ if(!strcmp(framing_config, "cr")){
+ expression = "\\r";
+ }
+ bytes = framing_separator(data, length, last_read, opcode, framing_data, expression);
+ }
+ else{
+ bytes = framing_separator(data, length, last_read, opcode, framing_data, NULL);
+ }
+
+ return framing_auto(data, bytes, 0, opcode, NULL, NULL);
}
diff --git a/config.c b/config.c
index 0b57714..2d50f3e 100644
--- a/config.c
+++ b/config.c
@@ -6,11 +6,13 @@
#include "config.h"
#include "plugin.h"
+/* Configuration file parser state */
static enum /*_config_file_section*/ {
cfg_main,
cfg_backend
} config_section = cfg_main;
+/* Evaluate a single line within a configuration file */
static int config_file_line(ws_config* config, char* key, char* value, size_t line_no){
if(!strcmp(key, "port")){
free(config->port);
@@ -37,6 +39,7 @@ static int config_file_line(ws_config* config, char* key, char* value, size_t li
return 0;
}
+/* Read and parse a configuration file */
int config_parse_file(ws_config* config, char* filename){
ssize_t line_current;
size_t line_alloc = 0, line_no = 1, key_len;
@@ -105,6 +108,7 @@ int config_parse_file(ws_config* config, char* filename){
return 0;
}
+/* Parse an argument list */
int config_parse_arguments(ws_config* config, int argc, char** argv){
size_t u;
char* option = NULL, *value = NULL;
diff --git a/config.h b/config.h
index 87502fb..e39f13e 100644
--- a/config.h
+++ b/config.h
@@ -1,8 +1,10 @@
+/* Core configuration storage */
typedef struct /*_websocksy_config*/ {
char* host;
char* port;
ws_backend backend;
} ws_config;
+/* Configuration parsing functions */
int config_parse_file(ws_config* config, char* filename);
int config_parse_arguments(ws_config* config, int argc, char** argv);
diff --git a/plugin.c b/plugin.c
index d8500fe..f48707f 100644
--- a/plugin.c
+++ b/plugin.c
@@ -18,6 +18,7 @@ static char** framing_function_name = NULL;
static size_t attached_libraries = 0;
static void** attached_library = NULL;
+/* Attach a shared object and register it to be unloaded at shutdown */
static void* plugin_attach(char* path){
void* module = dlopen(path, RTLD_NOW);
@@ -39,6 +40,12 @@ static void* plugin_attach(char* path){
return module;
}
+/*
+ * Try to load all framing plugins by attaching all shared objects in the
+ * plugin path that
+ * * are not backend plugins
+ * * have a filename ending in .so
+ */
int plugin_framing_load(char* path){
DIR* directory = opendir(path);
struct dirent* file = NULL;
@@ -74,6 +81,10 @@ int plugin_framing_load(char* path){
return 0;
}
+/*
+ * Load an external backend plugin by constructing it's module path and attaching it,
+ * then try to resolve the required backend symbols
+ */
int plugin_backend_load(char* path, char* backend_requested, ws_backend* backend){
char plugin_path[MAX_PLUGIN_PATH] = "";
void* handle = NULL;
@@ -103,6 +114,7 @@ int plugin_backend_load(char* path, char* backend_requested, ws_backend* backend
return 0;
}
+/* Register a new framing function to the library */
int plugin_register_framing(char* name, ws_framing func){
size_t u;
@@ -129,6 +141,7 @@ int plugin_register_framing(char* name, ws_framing func){
return 0;
}
+/* Query the framing function library */
ws_framing plugin_framing(char* name){
size_t u;
@@ -146,6 +159,7 @@ ws_framing plugin_framing(char* name){
return plugin_framing("auto");
}
+/* Release all allocated memory, detach all loaded shared objects */
void plugin_cleanup(){
size_t u;
diff --git a/websocket.c b/websocket.c
index fe4a39a..710178f 100644
--- a/websocket.c
+++ b/websocket.c
@@ -19,6 +19,10 @@
#define WS_GET_MASK(a) ((a & 0x80) >> 7)
#define WS_GET_LEN(a) ((a & 0x7F))
+/*
+ * Close and shut down a WebSocket connection, including a connected
+ * peer stream. Frees all resources associated with either connection.
+ */
int ws_close(websocket* ws, ws_close_reason code, char* reason){
size_t p;
ws_peer_info empty_peer = {
@@ -90,6 +94,7 @@ int ws_close(websocket* ws, ws_close_reason code, char* reason){
return 0;
}
+/* Accept a new WebSocket connection */
int ws_accept(int listen_fd){
websocket ws = {
.ws_fd = accept(listen_fd, NULL, NULL),
@@ -99,6 +104,7 @@ int ws_accept(int listen_fd){
return client_register(&ws);
}
+/* Handle data in the NEW state (expecting a HTTP negotiation) */
static int ws_handle_new(websocket* ws){
size_t u;
char* path, *proto;
@@ -126,6 +132,7 @@ static int ws_handle_new(websocket* ws){
return 0;
}
+/* Handle end of HTTP header data and upgrade the connection */
static int ws_upgrade_http(websocket* ws){
if(ws->websocket_version == 13
&& ws->socket_key
@@ -144,7 +151,7 @@ static int ws_upgrade_http(websocket* ws){
return 0;
}
- //calculate the websocket accept key, which for some reason is defined as
+ //calculate the websocket accept key, which for some reason is defined (RFC 4.2.2.5.4) as
//base64(sha1(concat(trim(client-key), "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")))
//requiring not one but 2 unnecessarily complex operations
size_t encode_offset = 0;
@@ -193,6 +200,7 @@ static int ws_upgrade_http(websocket* ws){
return 1;
}
+/* Handle incoming HTTP header lines */
static int ws_handle_http(websocket* ws){
char* header, *value;
ssize_t p;
@@ -296,8 +304,8 @@ static size_t ws_frame(websocket* ws){
//ignoring it for now
}
- //calculate the payload length (could've used a uint64 and be done with it...)
- //TODO test this for the bigger frames
+ //calculate the payload length from one of 3 cases (RFC 5.2)
+ //could've used a uint64 and be done with it...
payload_length = WS_GET_LEN(ws->read_buffer[1]);
if(WS_GET_MASK(ws->read_buffer[1])){
if(ws->read_buffer_offset < 6){
@@ -401,6 +409,7 @@ static size_t ws_frame(websocket* ws){
return ((payload - ws->read_buffer) + payload_length);
}
+/* Construct and send a WebSocket frame */
int ws_send_frame(websocket* ws, ws_operation opcode, uint8_t* data, size_t len){
fprintf(stderr, "Peer -> WS %lu bytes (%02X)\n", len, opcode);
uint8_t frame_header[WS_FRAME_HEADER_LEN];
@@ -433,6 +442,7 @@ int ws_send_frame(websocket* ws, ws_operation opcode, uint8_t* data, size_t len)
return 0;
}
+/* Handle incoming data on a WebSocket client */
int ws_data(websocket* ws){
ssize_t bytes_read, n, bytes_left = sizeof(ws->read_buffer) - ws->read_buffer_offset;
int rv = 0;
diff --git a/websocket.h b/websocket.h
index f2a4500..f57fccc 100644
--- a/websocket.h
+++ b/websocket.h
@@ -1,5 +1,6 @@
#include "websocksy.h"
+/* WebSocket connection handling functions */
int ws_close(websocket* ws, ws_close_reason code, char* reason);
int ws_accept(int listen_fd);
int ws_send_frame(websocket* ws, ws_operation opcode, uint8_t* data, size_t len);
diff --git a/websocksy.c b/websocksy.c
index 33ee794..c2f0631 100644
--- a/websocksy.c
+++ b/websocksy.c
@@ -23,6 +23,7 @@
/* TODO
* - TLS
* - pings
+ * - continuation
*/
/* Main loop condition, to be set from signal handler */
@@ -240,9 +241,11 @@ static int ws_peer_data(websocket* ws){
fprintf(stderr, "Overrun by framing function, have %lu + %lu bytes, framed %lu\n", ws->peer_buffer_offset, bytes_read, bytes_framed);
return 0;
}
- //send the indicated n bytes to the websocket peer
- if(ws_send_frame(ws, opcode, ws->peer_buffer, bytes_framed)){
- return 1;
+ if(opcode != ws_frame_discard){
+ //send the indicated n bytes to the websocket peer
+ if(ws_send_frame(ws, opcode, ws->peer_buffer, bytes_framed)){
+ return 1;
+ }
}
//copy back
memmove(ws->peer_buffer, ws->peer_buffer + bytes_framed, (ws->peer_buffer_offset + bytes_read) - bytes_framed);
diff --git a/websocksy.h b/websocksy.h
index b2b2285..9d9faf1 100644
--- a/websocksy.h
+++ b/websocksy.h
@@ -36,7 +36,8 @@ typedef enum {
ws_frame_binary = 2,
ws_frame_close = 8,
ws_frame_ping = 9,
- ws_frame_pong = 10
+ ws_frame_pong = 10,
+ ws_frame_discard /* Special opcode to discard bytes from the peer buffer */
} ws_operation;
/*