aboutsummaryrefslogtreecommitdiff
path: root/websocksy.h
blob: 4f9b91a935254173b586c06968868f74e951fa11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#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