aboutsummaryrefslogtreecommitdiff
#ifndef WEBSOCKSY_HEADER_INCLUDED
#define WEBSOCKSY_HEADER_INCLUDED
#include <stdint.h>
#include <stdlib.h>
#include <nettle/sha1.h>
#include <nettle/base64.h>

/* Version defines */
#define WEBSOCKSY_API_VERSION 1
#define WEBSOCKSY_VERSION "0.1"

/* HTTP/WS read buffer size & limit */
#define WS_MAX_LINE 16384
/* Peer read buffer size / proxy packet limit */
#define PEER_BUFFER_SIZE 16384
/* Maximum number of HTTP headers to accept */
#define WS_HEADER_LIMIT 10

/*
 * State machine for WebSocket connections
 */
typedef enum {
	ws_new = 0, /* Initial state */
	ws_http, /* HTTP Headers */
	ws_open, /* Upgrade performed, forwarding */
	ws_closed /* Close frame sent */
} ws_state;

/*
 * WebSocket frame opcodes
 * RFC Section 5.2
 */
typedef enum {
	ws_frame_continuation = 0,
	ws_frame_text = 1,
	ws_frame_binary = 2,
	ws_frame_close = 8,
	ws_frame_ping = 9,
	ws_frame_pong = 10,
	ws_frame_discard /* Special opcode to discard bytes from the peer buffer */
} ws_operation;

/*
 * WebSocket close reasons / response codes
 * RFC Section 7.4.1
 */
typedef enum {
	ws_close_http = 100,
	ws_close_normal = 1000,
	ws_close_shutdown = 1001,
	ws_close_proto = 1002,
	ws_close_data = 1003,
	ws_close_format = 1007,
	ws_close_policy = 1008,
	ws_close_limit = 1009,
	ws_close_unexpected = 1011
} ws_close_reason;

/*
 * HTTP header split into tag and value
 */
typedef struct /*_ws_http_header*/ {
	char* tag;
	char* value;
} ws_http_header;

/*
 * Peer stream framing function
 *
 * Since the WebSocket protocol transfers its payload using discrete frames, unlike the peer's
 * underlying TCP/Unix sockets (UDP is a different matter and may be framed directly), we need
 * a method to indicate whether a complete frame to be forwarded has been received from the peer
 * and with what WebSocket frame type to forward it (binary/text). Since this is largely
 * protocol-dependent, this functionality needs to be user-extendable.
 *
 * We do however provide some default framing functions:
 * 	* auto: Based on the content, forward every read result as binary/text
 * 	* binary: Always forward all reads as binary frames
 * 	* separator: Separate binary frames on a sequence of bytes
 * 	* newline: Forward text frames separated by newlines (\r\n)
 *
 * The separator function is called once for every successful read from the peer socket and called
 * again when it indicates a frame boundary but there is still data in the buffer.
 * The `framing_data` pointer can be used to store data on a per-connection basis.
 * If the pointer is nonzero when the connection is terminated, the function will be called with a
 * NULL `data` pointer as an indication that any allocation within `framing_data` is to be freed.
 * The return value is the number of bytes to be sent to the peer.
 */
typedef int64_t (*ws_framing)(uint8_t* data, size_t length, size_t last_read, ws_operation* opcode, void** framing_data, const char* config);

/* Peer connection modes */
typedef enum {
	peer_transport_detect,
	peer_tcp_client,
	peer_udp_client,
	peer_udp_listen,
	peer_fifo_tx,
	peer_fifo_rx,
	peer_unix_stream,
	peer_unix_dgram
} peer_transport;

/* Peer address model */
typedef struct /*_ws_peer_info*/ {
	/* Peer protocol data */
	peer_transport transport;
	char* host;
	char* port;

	/* Framing function for this peer */
	ws_framing framing;
	char* framing_config;

	/* WebSocket subprotocol indication index*/
	size_t protocol;
} ws_peer_info;

/* Core connection model */
typedef struct /*_web_socket*/ {
	/* WebSocket state & data */
	int ws_fd;
	uint8_t read_buffer[WS_MAX_LINE];
	size_t read_buffer_offset;
	ws_state state;
	time_t last_event;

	/* HTTP request headers */
	size_t headers;
	ws_http_header header[WS_HEADER_LIMIT];

	/* WebSocket parameters */
	char* request_path;
	unsigned websocket_version;
	char* socket_key;
	unsigned want_upgrade;

	/* WebSocket indicated subprotocols*/
	size_t protocols;
	char** protocol;

	/* Peer data */
	ws_peer_info peer;
	int peer_fd;
	uint8_t peer_buffer[PEER_BUFFER_SIZE];
	size_t peer_buffer_offset;
	void* peer_framing_data;
} websocket;

/*
 * Peer discovery backend API
 *
 * 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`: Required, 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. 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)();
/*
 * 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_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;

/* Core API */
ws_framing core_framing(char* name);
int core_register_framing(char* name, ws_framing func);

/* Internal helper functions */
char* xstr_lower(char* in);
int client_register(websocket* ws);
int client_connect(websocket* ws);
#endif