aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2019-05-19 11:03:45 +0200
committercbdev <cb@cbcdn.com>2019-05-19 11:03:45 +0200
commit92638847a3b7f51fb795c7986b793536ece6beee (patch)
treefe67899cf254c9e00f28a93cc12d4fb1928f2b32
parent75d1e4955b220495f6d2655b18989db567faa5ce (diff)
downloadwebsocksy-92638847a3b7f51fb795c7986b793536ece6beee.tar.gz
websocksy-92638847a3b7f51fb795c7986b793536ece6beee.tar.bz2
websocksy-92638847a3b7f51fb795c7986b793536ece6beee.zip
Documentation & peer discovery API
-rw-r--r--builtins.c11
-rw-r--r--index.html6
-rw-r--r--websocksy.c12
-rw-r--r--websocksy.h49
-rw-r--r--ws_proto.c66
5 files changed, 127 insertions, 17 deletions
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 @@
<head>
<script type="text/javascript">
var webSocket = null;
+ var payload = "foo";
function openSocket(){
setTimeout(function(){
- webSocket = new WebSocket("ws://localhost:8001/foo/bar");
+ webSocket = new WebSocket("ws://localhost:8001/foo/bar", ["p1", "p2"]);
webSocket.onclose = openSocket;
webSocket.onmessage = function(event){
console.log(event.data);
@@ -16,7 +17,8 @@
function init(){
openSocket();
setInterval(function(){
- webSocket.send("foo");
+ webSocket.send(payload);
+ payload += payload;
}, 1000);
}
</script>
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");