aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2019-05-25 09:10:55 +0200
committercbdev <cb@cbcdn.com>2019-05-25 09:10:55 +0200
commit79e063d6aa6116231fdfb374f52507236ca3770e (patch)
treeb4b0acd9faa1576b69d3fc18b0496f8132539e51
parentde23df891940464080297cd9b17324d8ee4601d7 (diff)
downloadwebsocksy-79e063d6aa6116231fdfb374f52507236ca3770e.tar.gz
websocksy-79e063d6aa6116231fdfb374f52507236ca3770e.tar.bz2
websocksy-79e063d6aa6116231fdfb374f52507236ca3770e.zip
Basic forwarding and builtin extension functions
-rw-r--r--builtins.c140
-rw-r--r--index.html2
-rw-r--r--makefile2
-rw-r--r--websocksy.c15
-rw-r--r--websocksy.h4
-rw-r--r--ws_proto.c34
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);
}
</script>
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;
}