iodine 0.1.21 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/.travis.yml +23 -2
- data/CHANGELOG.md +9 -2
- data/README.md +232 -179
- data/Rakefile +13 -1
- data/bin/config.ru +63 -0
- data/bin/console +6 -0
- data/bin/echo +42 -32
- data/bin/http-hello +62 -0
- data/bin/http-playground +124 -0
- data/bin/playground +62 -0
- data/bin/poc/Gemfile.lock +23 -0
- data/bin/poc/README.md +37 -0
- data/bin/poc/config.ru +66 -0
- data/bin/poc/gemfile +1 -0
- data/bin/poc/www/index.html +57 -0
- data/bin/raw-rbhttp +35 -0
- data/bin/raw_broadcast +66 -0
- data/bin/test_with_faye +40 -0
- data/bin/ws-broadcast +108 -0
- data/bin/ws-echo +108 -0
- data/exe/iodine +59 -0
- data/ext/iodine/base64.c +264 -0
- data/ext/iodine/base64.h +72 -0
- data/ext/iodine/bscrypt-common.h +109 -0
- data/ext/iodine/bscrypt.h +49 -0
- data/ext/iodine/extconf.rb +41 -0
- data/ext/iodine/hex.c +123 -0
- data/ext/iodine/hex.h +70 -0
- data/ext/iodine/http.c +200 -0
- data/ext/iodine/http.h +128 -0
- data/ext/iodine/http1.c +402 -0
- data/ext/iodine/http1.h +56 -0
- data/ext/iodine/http1_simple_parser.c +473 -0
- data/ext/iodine/http1_simple_parser.h +59 -0
- data/ext/iodine/http_request.h +128 -0
- data/ext/iodine/http_response.c +1606 -0
- data/ext/iodine/http_response.h +393 -0
- data/ext/iodine/http_response_http1.h +374 -0
- data/ext/iodine/iodine_core.c +641 -0
- data/ext/iodine/iodine_core.h +70 -0
- data/ext/iodine/iodine_http.c +615 -0
- data/ext/iodine/iodine_http.h +19 -0
- data/ext/iodine/iodine_websocket.c +430 -0
- data/ext/iodine/iodine_websocket.h +21 -0
- data/ext/iodine/libasync.c +552 -0
- data/ext/iodine/libasync.h +117 -0
- data/ext/iodine/libreact.c +347 -0
- data/ext/iodine/libreact.h +244 -0
- data/ext/iodine/libserver.c +912 -0
- data/ext/iodine/libserver.h +435 -0
- data/ext/iodine/libsock.c +950 -0
- data/ext/iodine/libsock.h +478 -0
- data/ext/iodine/misc.c +181 -0
- data/ext/iodine/misc.h +76 -0
- data/ext/iodine/random.c +193 -0
- data/ext/iodine/random.h +48 -0
- data/ext/iodine/rb-call.c +127 -0
- data/ext/iodine/rb-call.h +60 -0
- data/ext/iodine/rb-libasync.h +79 -0
- data/ext/iodine/rb-rack-io.c +389 -0
- data/ext/iodine/rb-rack-io.h +17 -0
- data/ext/iodine/rb-registry.c +213 -0
- data/ext/iodine/rb-registry.h +33 -0
- data/ext/iodine/sha1.c +359 -0
- data/ext/iodine/sha1.h +85 -0
- data/ext/iodine/sha2.c +825 -0
- data/ext/iodine/sha2.h +138 -0
- data/ext/iodine/siphash.c +136 -0
- data/ext/iodine/siphash.h +15 -0
- data/ext/iodine/spnlock.h +235 -0
- data/ext/iodine/websockets.c +696 -0
- data/ext/iodine/websockets.h +120 -0
- data/ext/iodine/xor-crypt.c +189 -0
- data/ext/iodine/xor-crypt.h +107 -0
- data/iodine.gemspec +25 -18
- data/lib/iodine.rb +57 -58
- data/lib/iodine/http.rb +0 -189
- data/lib/iodine/protocol.rb +36 -245
- data/lib/iodine/version.rb +1 -1
- data/lib/rack/handler/iodine.rb +145 -2
- metadata +115 -37
- data/bin/core_http_test +0 -51
- data/bin/em playground +0 -56
- data/bin/hello_world +0 -75
- data/bin/setup +0 -7
- data/lib/iodine/client.rb +0 -5
- data/lib/iodine/core.rb +0 -102
- data/lib/iodine/core_init.rb +0 -143
- data/lib/iodine/http/hpack.rb +0 -553
- data/lib/iodine/http/http1.rb +0 -251
- data/lib/iodine/http/http2.rb +0 -507
- data/lib/iodine/http/rack_support.rb +0 -108
- data/lib/iodine/http/request.rb +0 -462
- data/lib/iodine/http/response.rb +0 -474
- data/lib/iodine/http/session.rb +0 -143
- data/lib/iodine/http/websocket_client.rb +0 -335
- data/lib/iodine/http/websocket_handler.rb +0 -101
- data/lib/iodine/http/websockets.rb +0 -336
- data/lib/iodine/io.rb +0 -56
- data/lib/iodine/logging.rb +0 -46
- data/lib/iodine/settings.rb +0 -158
- data/lib/iodine/ssl_connector.rb +0 -48
- data/lib/iodine/timers.rb +0 -95
@@ -0,0 +1,696 @@
|
|
1
|
+
#include "libserver.h"
|
2
|
+
#include "websockets.h"
|
3
|
+
#include "bscrypt.h"
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <stdio.h>
|
6
|
+
#include <string.h>
|
7
|
+
#include <arpa/inet.h>
|
8
|
+
|
9
|
+
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
|
10
|
+
#include <endian.h>
|
11
|
+
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
|
12
|
+
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
13
|
+
#define __BIG_ENDIAN__
|
14
|
+
#endif
|
15
|
+
#endif
|
16
|
+
|
17
|
+
/*******************************************************************************
|
18
|
+
Buffer management - update to change the way the buffer is handled.
|
19
|
+
*/
|
20
|
+
struct buffer_s {
|
21
|
+
void* data;
|
22
|
+
size_t size;
|
23
|
+
};
|
24
|
+
|
25
|
+
#pragma weak create_ws_buffer
|
26
|
+
/** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
27
|
+
struct buffer_s create_ws_buffer(ws_s* owner);
|
28
|
+
|
29
|
+
#pragma weak resize_ws_buffer
|
30
|
+
/** returns a buffer_s struct, with a buffer (at least) `size` long. */
|
31
|
+
struct buffer_s resize_ws_buffer(ws_s* owner, struct buffer_s);
|
32
|
+
|
33
|
+
#pragma weak free_ws_buffer
|
34
|
+
/** releases an existing buffer. */
|
35
|
+
void free_ws_buffer(ws_s* owner, struct buffer_s);
|
36
|
+
|
37
|
+
/** Sets the initial buffer size. (16Kb)*/
|
38
|
+
#define WS_INITIAL_BUFFER_SIZE 16384
|
39
|
+
|
40
|
+
/*******************************************************************************
|
41
|
+
Buffer management - simple implementation...
|
42
|
+
Since Websocket connections have a long life expectancy, optimizing this part of
|
43
|
+
the code probably wouldn't offer a high performance boost.
|
44
|
+
*/
|
45
|
+
|
46
|
+
// buffer increments by 4,096 Bytes (4Kb)
|
47
|
+
#define round_up_buffer_size(size) (((size) >> 12) + 1) << 12
|
48
|
+
|
49
|
+
struct buffer_s create_ws_buffer(ws_s* owner) {
|
50
|
+
struct buffer_s buff;
|
51
|
+
buff.size = round_up_buffer_size(WS_INITIAL_BUFFER_SIZE);
|
52
|
+
buff.data = malloc(buff.size);
|
53
|
+
return buff;
|
54
|
+
}
|
55
|
+
|
56
|
+
struct buffer_s resize_ws_buffer(ws_s* owner, struct buffer_s buff) {
|
57
|
+
buff.size = round_up_buffer_size(buff.size);
|
58
|
+
void* tmp = realloc(buff.data, buff.size);
|
59
|
+
if (!tmp) {
|
60
|
+
free_ws_buffer(owner, buff);
|
61
|
+
buff.size = 0;
|
62
|
+
}
|
63
|
+
buff.data = tmp;
|
64
|
+
return buff;
|
65
|
+
}
|
66
|
+
void free_ws_buffer(ws_s* owner, struct buffer_s buff) {
|
67
|
+
if (buff.data)
|
68
|
+
free(buff.data);
|
69
|
+
}
|
70
|
+
|
71
|
+
#undef round_up_buffer_size
|
72
|
+
|
73
|
+
/*******************************************************************************
|
74
|
+
Create/Destroy the websocket object (prototypes)
|
75
|
+
*/
|
76
|
+
|
77
|
+
static ws_s* new_websocket();
|
78
|
+
static void destroy_ws(ws_s* ws);
|
79
|
+
|
80
|
+
/*******************************************************************************
|
81
|
+
The Websocket object (protocol + parser)
|
82
|
+
*/
|
83
|
+
struct Websocket {
|
84
|
+
/** The Websocket protocol */
|
85
|
+
protocol_s protocol;
|
86
|
+
/** connection data */
|
87
|
+
intptr_t fd;
|
88
|
+
/** callbacks */
|
89
|
+
void (*on_message)(ws_s* ws, char* data, size_t size, uint8_t is_text);
|
90
|
+
void (*on_shutdown)(ws_s* ws);
|
91
|
+
void (*on_close)(ws_s* ws);
|
92
|
+
/** Opaque user data. */
|
93
|
+
void* udata;
|
94
|
+
/** The maximum websocket message size */
|
95
|
+
size_t max_msg_size;
|
96
|
+
/** message buffer. */
|
97
|
+
struct buffer_s buffer;
|
98
|
+
/** message length (how much of the buffer actually used). */
|
99
|
+
size_t length;
|
100
|
+
/** parser. */
|
101
|
+
struct {
|
102
|
+
union {
|
103
|
+
unsigned len1 : 16;
|
104
|
+
unsigned long len2 : 64;
|
105
|
+
char bytes[8];
|
106
|
+
} psize;
|
107
|
+
size_t length;
|
108
|
+
size_t received;
|
109
|
+
int pos;
|
110
|
+
int data_len;
|
111
|
+
char mask[4];
|
112
|
+
char tmp_buffer[1024];
|
113
|
+
struct {
|
114
|
+
unsigned op_code : 4;
|
115
|
+
unsigned rsv3 : 1;
|
116
|
+
unsigned rsv2 : 1;
|
117
|
+
unsigned rsv1 : 1;
|
118
|
+
unsigned fin : 1;
|
119
|
+
} head, head2;
|
120
|
+
struct {
|
121
|
+
unsigned size : 7;
|
122
|
+
unsigned masked : 1;
|
123
|
+
} sdata;
|
124
|
+
struct {
|
125
|
+
unsigned has_mask : 1;
|
126
|
+
unsigned at_mask : 2;
|
127
|
+
unsigned has_len : 1;
|
128
|
+
unsigned at_len : 3;
|
129
|
+
unsigned client : 1;
|
130
|
+
} state;
|
131
|
+
} parser;
|
132
|
+
};
|
133
|
+
|
134
|
+
/**
|
135
|
+
The Websocket Protocol Identifying String. Used for the `each` function.
|
136
|
+
*/
|
137
|
+
char* WEBSOCKET_ID_STR = "websockets";
|
138
|
+
|
139
|
+
/*******************************************************************************
|
140
|
+
The Websocket Protocol implementation
|
141
|
+
*/
|
142
|
+
|
143
|
+
#define ws_protocol(fd) ((ws_s*)(server_get_protocol(fd)))
|
144
|
+
|
145
|
+
static void ws_ping(intptr_t fd, protocol_s* _ws) {
|
146
|
+
sock_packet_s* packet;
|
147
|
+
while ((packet = sock_checkout_packet()) == NULL)
|
148
|
+
sock_flush_all();
|
149
|
+
*packet = (sock_packet_s){
|
150
|
+
.buffer = "\x89\x00", .length = 2, .metadata.urgent = 1,
|
151
|
+
};
|
152
|
+
sock_send_packet(fd, packet);
|
153
|
+
}
|
154
|
+
|
155
|
+
static void on_close(protocol_s* _ws) {
|
156
|
+
destroy_ws((ws_s*)_ws);
|
157
|
+
}
|
158
|
+
|
159
|
+
static void on_open(intptr_t fd, protocol_s* _ws, void* callback) {
|
160
|
+
if (callback && _ws && _ws->service == WEBSOCKET_ID_STR)
|
161
|
+
((void (*)(void*))callback)(_ws);
|
162
|
+
}
|
163
|
+
|
164
|
+
static void on_shutdown(intptr_t fd, protocol_s* _ws) {
|
165
|
+
if (_ws && ((ws_s*)_ws)->on_shutdown)
|
166
|
+
((ws_s*)_ws)->on_shutdown((ws_s*)_ws);
|
167
|
+
}
|
168
|
+
|
169
|
+
/* later */
|
170
|
+
static void websocket_write_impl(intptr_t fd,
|
171
|
+
void* data,
|
172
|
+
size_t len,
|
173
|
+
char text,
|
174
|
+
char first,
|
175
|
+
char last,
|
176
|
+
char client);
|
177
|
+
|
178
|
+
static void on_data(intptr_t sockfd, protocol_s* _ws) {
|
179
|
+
#define ws ((ws_s*)_ws)
|
180
|
+
if (ws == NULL || ws->protocol.service != WEBSOCKET_ID_STR)
|
181
|
+
return;
|
182
|
+
ssize_t len = 0;
|
183
|
+
while ((len = sock_read(sockfd, ws->parser.tmp_buffer, 1024)) > 0) {
|
184
|
+
ws->parser.data_len = 0;
|
185
|
+
ws->parser.pos = 0;
|
186
|
+
while (ws->parser.pos < len) {
|
187
|
+
// collect the frame's head
|
188
|
+
if (!(*(char*)(&ws->parser.head))) {
|
189
|
+
*((char*)(&(ws->parser.head))) = ws->parser.tmp_buffer[ws->parser.pos];
|
190
|
+
// save a copy if it's the first head in a fragmented message
|
191
|
+
if (!(*(char*)(&ws->parser.head2))) {
|
192
|
+
ws->parser.head2 = ws->parser.head;
|
193
|
+
}
|
194
|
+
// advance
|
195
|
+
ws->parser.pos++;
|
196
|
+
// go back to the `while` head, to review if there's more data
|
197
|
+
continue;
|
198
|
+
}
|
199
|
+
|
200
|
+
// save the mask and size information
|
201
|
+
if (!(*(char*)(&ws->parser.sdata))) {
|
202
|
+
*((char*)(&(ws->parser.sdata))) = ws->parser.tmp_buffer[ws->parser.pos];
|
203
|
+
// set length
|
204
|
+
ws->parser.state.at_len = ws->parser.sdata.size == 127
|
205
|
+
? 7
|
206
|
+
: ws->parser.sdata.size == 126 ? 1 : 0;
|
207
|
+
ws->parser.pos++;
|
208
|
+
continue;
|
209
|
+
}
|
210
|
+
|
211
|
+
// check that if we need to collect the length data
|
212
|
+
if (ws->parser.sdata.size >= 126 && !(ws->parser.state.has_len)) {
|
213
|
+
// avoiding a loop so we don't mixup the meaning of "continue" and
|
214
|
+
// "break"
|
215
|
+
collect_len:
|
216
|
+
////////// NOTICE: Network Byte Order requires us to translate the data
|
217
|
+
#ifdef __BIG_ENDIAN__
|
218
|
+
if ((ws->parser.state.at_len == 1 && ws->parser.sdata.size == 126) ||
|
219
|
+
(ws->parser.state.at_len == 7 && ws->parser.sdata.size == 127)) {
|
220
|
+
ws->parser.psize.bytes[ws->parser.state.at_len] =
|
221
|
+
ws->parser.tmp_buffer[ws->parser.pos++];
|
222
|
+
ws->parser.state.has_len = 1;
|
223
|
+
ws->parser.length = (ws->parser.sdata.size == 126)
|
224
|
+
? ws->parser.psize.len1
|
225
|
+
: ws->parser.psize.len2;
|
226
|
+
} else {
|
227
|
+
ws->parser.psize.bytes[ws->parser.state.at_len++] =
|
228
|
+
ws->parser.tmp_buffer[ws->parser.pos++];
|
229
|
+
if (ws->parser.pos < len)
|
230
|
+
goto collect_len;
|
231
|
+
}
|
232
|
+
#else
|
233
|
+
if (ws->parser.state.at_len == 0) {
|
234
|
+
ws->parser.psize.bytes[ws->parser.state.at_len] =
|
235
|
+
ws->parser.tmp_buffer[ws->parser.pos++];
|
236
|
+
ws->parser.state.has_len = 1;
|
237
|
+
ws->parser.length = (ws->parser.sdata.size == 126)
|
238
|
+
? ws->parser.psize.len1
|
239
|
+
: ws->parser.psize.len2;
|
240
|
+
} else {
|
241
|
+
ws->parser.psize.bytes[ws->parser.state.at_len--] =
|
242
|
+
ws->parser.tmp_buffer[ws->parser.pos++];
|
243
|
+
if (ws->parser.pos < len)
|
244
|
+
goto collect_len;
|
245
|
+
}
|
246
|
+
#endif
|
247
|
+
continue;
|
248
|
+
} else if (!ws->parser.length && ws->parser.sdata.size < 126)
|
249
|
+
// we should have the length data in the head
|
250
|
+
ws->parser.length = ws->parser.sdata.size;
|
251
|
+
|
252
|
+
// check that the data is masked and that we didn't colleced the mask yet
|
253
|
+
if (ws->parser.sdata.masked && !(ws->parser.state.has_mask)) {
|
254
|
+
// avoiding a loop so we don't mixup the meaning of "continue" and "break"
|
255
|
+
collect_mask:
|
256
|
+
if (ws->parser.state.at_mask == 3) {
|
257
|
+
ws->parser.mask[ws->parser.state.at_mask] =
|
258
|
+
ws->parser.tmp_buffer[ws->parser.pos++];
|
259
|
+
ws->parser.state.has_mask = 1;
|
260
|
+
ws->parser.state.at_mask = 0;
|
261
|
+
} else {
|
262
|
+
ws->parser.mask[ws->parser.state.at_mask++] =
|
263
|
+
ws->parser.tmp_buffer[ws->parser.pos++];
|
264
|
+
if (ws->parser.pos < len)
|
265
|
+
goto collect_mask;
|
266
|
+
else
|
267
|
+
continue;
|
268
|
+
}
|
269
|
+
// since it's possible that there's no more data (0 length frame),
|
270
|
+
// we don't use `continue` (check while loop) and we process what we
|
271
|
+
// have.
|
272
|
+
}
|
273
|
+
|
274
|
+
// Now that we know everything about the frame, let's collect the data
|
275
|
+
|
276
|
+
// How much data in the buffer is part of the frame?
|
277
|
+
ws->parser.data_len = len - ws->parser.pos;
|
278
|
+
if (ws->parser.data_len + ws->parser.received > ws->parser.length)
|
279
|
+
ws->parser.data_len = ws->parser.length - ws->parser.received;
|
280
|
+
|
281
|
+
// a note about unmasking: since ws->parser.state.at_mask is only 2 bits,
|
282
|
+
// it will wrap around (i.e. 3++ == 0), so no modulus is required :-)
|
283
|
+
// unmask:
|
284
|
+
if (ws->parser.sdata.masked) {
|
285
|
+
for (int i = 0; i < ws->parser.data_len; i++) {
|
286
|
+
ws->parser.tmp_buffer[i + ws->parser.pos] ^=
|
287
|
+
ws->parser.mask[ws->parser.state.at_mask++];
|
288
|
+
}
|
289
|
+
} else if (ws->parser.state.client == 0) {
|
290
|
+
// enforce masking unless acting as client, also for security reasons...
|
291
|
+
fprintf(stderr, "ERROR Websockets: unmasked frame, disconnecting.\n");
|
292
|
+
sock_close(sockfd);
|
293
|
+
return;
|
294
|
+
}
|
295
|
+
// Copy the data to the Ruby buffer - only if it's a user message
|
296
|
+
// RubyCaller.call_c(copy_data_to_buffer_in_gvl, ws);
|
297
|
+
if (ws->parser.head.op_code == 1 || ws->parser.head.op_code == 2 ||
|
298
|
+
(!ws->parser.head.op_code &&
|
299
|
+
(ws->parser.head2.op_code == 1 || ws->parser.head2.op_code == 2))) {
|
300
|
+
// check message size limit
|
301
|
+
if (ws->max_msg_size < ws->length + ws->parser.data_len) {
|
302
|
+
// close connection!
|
303
|
+
fprintf(stderr, "ERROR Websocket: Payload too big, review limits.\n");
|
304
|
+
sock_close(sockfd);
|
305
|
+
return;
|
306
|
+
}
|
307
|
+
// review and resize the buffer's capacity - it can only grow.
|
308
|
+
if (ws->length + ws->parser.length - ws->parser.received >
|
309
|
+
ws->buffer.size) {
|
310
|
+
ws->buffer = resize_ws_buffer(ws, ws->buffer);
|
311
|
+
if (!ws->buffer.data) {
|
312
|
+
// no memory.
|
313
|
+
websocket_close(ws);
|
314
|
+
return;
|
315
|
+
}
|
316
|
+
}
|
317
|
+
// copy here
|
318
|
+
memcpy(ws->buffer.data + ws->length,
|
319
|
+
ws->parser.tmp_buffer + ws->parser.pos, ws->parser.data_len);
|
320
|
+
ws->length += ws->parser.data_len;
|
321
|
+
}
|
322
|
+
// set the frame's data received so far (copied or not)
|
323
|
+
// we couldn't do it soonet, because we needed the value to compute the
|
324
|
+
// Ruby buffer capacity (within the GVL resize function).
|
325
|
+
ws->parser.received += ws->parser.data_len;
|
326
|
+
|
327
|
+
// check that we have collected the whole of the frame.
|
328
|
+
if (ws->parser.length > ws->parser.received) {
|
329
|
+
ws->parser.pos += ws->parser.data_len;
|
330
|
+
continue;
|
331
|
+
}
|
332
|
+
|
333
|
+
// we have the whole frame, time to process the data.
|
334
|
+
// pings, pongs and other non-Ruby handled messages.
|
335
|
+
if (ws->parser.head.op_code == 0 || ws->parser.head.op_code == 1 ||
|
336
|
+
ws->parser.head.op_code == 2) {
|
337
|
+
/* a user data frame */
|
338
|
+
if (ws->parser.head.fin) {
|
339
|
+
/* This was the last frame */
|
340
|
+
if (ws->parser.head2.op_code == 1) {
|
341
|
+
/* text data */
|
342
|
+
} else if (ws->parser.head2.op_code == 2) {
|
343
|
+
/* binary data */
|
344
|
+
} else // not a recognized frame, don't act
|
345
|
+
goto reset_parser;
|
346
|
+
// call the on_message callback
|
347
|
+
|
348
|
+
if (ws->on_message)
|
349
|
+
ws->on_message(ws, ws->buffer.data, ws->length,
|
350
|
+
ws->parser.head2.op_code == 1);
|
351
|
+
goto reset_parser;
|
352
|
+
}
|
353
|
+
} else if (ws->parser.head.op_code == 8) {
|
354
|
+
/* close */
|
355
|
+
websocket_close(ws);
|
356
|
+
if (ws->parser.head2.op_code == ws->parser.head.op_code)
|
357
|
+
goto reset_parser;
|
358
|
+
} else if (ws->parser.head.op_code == 9) {
|
359
|
+
/* ping */
|
360
|
+
// write Pong - including ping data...
|
361
|
+
websocket_write_impl(sockfd, ws->parser.tmp_buffer + ws->parser.pos,
|
362
|
+
ws->parser.data_len, 10, 1, 1,
|
363
|
+
ws->parser.state.client);
|
364
|
+
if (ws->parser.head2.op_code == ws->parser.head.op_code)
|
365
|
+
goto reset_parser;
|
366
|
+
} else if (ws->parser.head.op_code == 10) {
|
367
|
+
/* pong */
|
368
|
+
// do nothing... almost
|
369
|
+
if (ws->parser.head2.op_code == ws->parser.head.op_code)
|
370
|
+
goto reset_parser;
|
371
|
+
} else if (ws->parser.head.op_code > 2 && ws->parser.head.op_code < 8) {
|
372
|
+
/* future control frames. ignore. */
|
373
|
+
if (ws->parser.head2.op_code == ws->parser.head.op_code)
|
374
|
+
goto reset_parser;
|
375
|
+
} else {
|
376
|
+
/* WTF? */
|
377
|
+
if (ws->parser.head.fin)
|
378
|
+
goto reset_parser;
|
379
|
+
}
|
380
|
+
// not done, but move the pos marker along
|
381
|
+
ws->parser.pos += ws->parser.data_len;
|
382
|
+
continue;
|
383
|
+
|
384
|
+
reset_parser:
|
385
|
+
// move the pos marker along - in case we have more then one frame in the
|
386
|
+
// buffer
|
387
|
+
ws->parser.pos += ws->parser.data_len;
|
388
|
+
// clear the parser
|
389
|
+
*((char*)(&(ws->parser.state))) = *((char*)(&(ws->parser.sdata))) =
|
390
|
+
*((char*)(&(ws->parser.head2))) = *((char*)(&(ws->parser.head))) = 0;
|
391
|
+
// // // the above should be the same as... but it isn't
|
392
|
+
// *((uint32_t*)(&(ws->parser.head))) = 0;
|
393
|
+
// set the union size to 0
|
394
|
+
ws->length = ws->parser.received = ws->parser.length =
|
395
|
+
ws->parser.psize.len2 = ws->parser.data_len = 0;
|
396
|
+
}
|
397
|
+
}
|
398
|
+
#undef ws
|
399
|
+
}
|
400
|
+
|
401
|
+
/*******************************************************************************
|
402
|
+
Create/Destroy the websocket object
|
403
|
+
*/
|
404
|
+
|
405
|
+
static ws_s* new_websocket() {
|
406
|
+
// allocate the protocol object (TODO: (maybe) pooling)
|
407
|
+
ws_s* ws = calloc(sizeof(*ws), 1);
|
408
|
+
|
409
|
+
// setup the protocol & protocol callbacks
|
410
|
+
ws->protocol.ping = ws_ping;
|
411
|
+
ws->protocol.service = WEBSOCKET_ID_STR;
|
412
|
+
ws->protocol.on_data = on_data;
|
413
|
+
ws->protocol.on_close = on_close;
|
414
|
+
ws->protocol.on_shutdown = on_shutdown;
|
415
|
+
// return the object
|
416
|
+
return ws;
|
417
|
+
}
|
418
|
+
static void destroy_ws(ws_s* ws) {
|
419
|
+
if (ws->on_close)
|
420
|
+
ws->on_close(ws);
|
421
|
+
free_ws_buffer(ws, ws->buffer);
|
422
|
+
free(ws);
|
423
|
+
}
|
424
|
+
|
425
|
+
/*******************************************************************************
|
426
|
+
Writing to the Websocket
|
427
|
+
*/
|
428
|
+
|
429
|
+
#define WS_MAX_FRAME_SIZE 65532 // should be less then `unsigned short`
|
430
|
+
|
431
|
+
static void websocket_write_impl(intptr_t fd,
|
432
|
+
void* data,
|
433
|
+
size_t len,
|
434
|
+
char text, /* TODO: add client masking */
|
435
|
+
char first,
|
436
|
+
char last,
|
437
|
+
char client) {
|
438
|
+
if (len < 126) {
|
439
|
+
struct {
|
440
|
+
unsigned op_code : 4;
|
441
|
+
unsigned rsv3 : 1;
|
442
|
+
unsigned rsv2 : 1;
|
443
|
+
unsigned rsv1 : 1;
|
444
|
+
unsigned fin : 1;
|
445
|
+
unsigned size : 7;
|
446
|
+
unsigned masked : 1;
|
447
|
+
} head = {.op_code = (first ? (!text ? 2 : text) : 0),
|
448
|
+
.fin = last,
|
449
|
+
.size = len,
|
450
|
+
.masked = client};
|
451
|
+
char buff[len + (client ? 6 : 2)];
|
452
|
+
memcpy(buff, &head, 2);
|
453
|
+
memcpy(buff + (client ? 6 : 2), data, len);
|
454
|
+
sock_write(fd, buff, len + 2);
|
455
|
+
} else if (len <= WS_MAX_FRAME_SIZE) {
|
456
|
+
/* head is 4 bytes */
|
457
|
+
struct {
|
458
|
+
unsigned op_code : 4;
|
459
|
+
unsigned rsv3 : 1;
|
460
|
+
unsigned rsv2 : 1;
|
461
|
+
unsigned rsv1 : 1;
|
462
|
+
unsigned fin : 1;
|
463
|
+
unsigned size : 7;
|
464
|
+
unsigned masked : 1;
|
465
|
+
unsigned length : 16;
|
466
|
+
} head = {.op_code = (first ? (text ? 1 : 2) : 0),
|
467
|
+
.fin = last,
|
468
|
+
.size = 126,
|
469
|
+
.masked = 0,
|
470
|
+
.length = htons(len)};
|
471
|
+
if (len >> 15) { // if len is larger then 32,767 Bytes.
|
472
|
+
/* head MUST be 4 bytes */
|
473
|
+
void* buff = malloc(len + (client ? 8 : 4));
|
474
|
+
memcpy(buff, &head, 4);
|
475
|
+
memcpy(buff + (client ? 8 : 4), data, len);
|
476
|
+
sock_write2(.fduuid = fd, .buffer = buff, .length = len + 4, .move = 1);
|
477
|
+
} else {
|
478
|
+
/* head MUST be 4 bytes */
|
479
|
+
char buff[len + (client ? 8 : 4)];
|
480
|
+
memcpy(buff, &head, 4);
|
481
|
+
memcpy(buff + (client ? 8 : 4), data, len);
|
482
|
+
sock_write(fd, buff, len + 4);
|
483
|
+
}
|
484
|
+
} else {
|
485
|
+
/* frame fragmentation is better for large data then large frames */
|
486
|
+
while (len > WS_MAX_FRAME_SIZE) {
|
487
|
+
websocket_write_impl(fd, data, WS_MAX_FRAME_SIZE, text, first, 0, client);
|
488
|
+
data += WS_MAX_FRAME_SIZE;
|
489
|
+
first = 0;
|
490
|
+
}
|
491
|
+
websocket_write_impl(fd, data, len, text, first, 1, client);
|
492
|
+
}
|
493
|
+
return;
|
494
|
+
}
|
495
|
+
|
496
|
+
/*******************************************************************************
|
497
|
+
The API implementation
|
498
|
+
*/
|
499
|
+
|
500
|
+
/** The upgrade */
|
501
|
+
#undef websocket_upgrade
|
502
|
+
ssize_t websocket_upgrade(websocket_settings_s settings) {
|
503
|
+
// A static data used for all websocket connections.
|
504
|
+
static char ws_key_accpt_str[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
505
|
+
// a temporary response object, in case none is provided.
|
506
|
+
http_response_s tmp_response;
|
507
|
+
// require either a request or a response.
|
508
|
+
if (((uintptr_t)settings.request | (uintptr_t)settings.response) ==
|
509
|
+
(uintptr_t)NULL)
|
510
|
+
return -1;
|
511
|
+
if (settings.max_msg_size == 0)
|
512
|
+
settings.max_msg_size = 262144; /** defaults to ~250KB */
|
513
|
+
if (settings.timeout == 0)
|
514
|
+
settings.timeout = 40; /* defaults to 40 seconds */
|
515
|
+
// make sure we have a response object.
|
516
|
+
http_response_s* response = settings.response;
|
517
|
+
if (response == NULL) {
|
518
|
+
/* initialize a default upgrade response */
|
519
|
+
tmp_response = http_response_init(settings.request);
|
520
|
+
response = &tmp_response;
|
521
|
+
} else
|
522
|
+
settings.request = response->metadata.request;
|
523
|
+
// allocate the protocol object (TODO: (maybe) pooling)
|
524
|
+
ws_s* ws = new_websocket();
|
525
|
+
if (!ws)
|
526
|
+
goto refuse;
|
527
|
+
|
528
|
+
// setup the socket-server data
|
529
|
+
ws->fd = response->metadata.request->metadata.fd;
|
530
|
+
// Setup ws callbacks
|
531
|
+
ws->on_close = settings.on_close;
|
532
|
+
ws->on_message = settings.on_message;
|
533
|
+
ws->on_shutdown = settings.on_shutdown;
|
534
|
+
|
535
|
+
// setup any user data
|
536
|
+
ws->udata = settings.udata;
|
537
|
+
// buffer limits
|
538
|
+
ws->max_msg_size = settings.max_msg_size;
|
539
|
+
const char* recv_str;
|
540
|
+
|
541
|
+
recv_str =
|
542
|
+
http_request_find_header(settings.request, "sec-websocket-version", 21);
|
543
|
+
if (recv_str == NULL || recv_str[0] != '1' || recv_str[1] != '3')
|
544
|
+
goto refuse;
|
545
|
+
|
546
|
+
recv_str =
|
547
|
+
http_request_find_header(settings.request, "sec-websocket-key", 17);
|
548
|
+
if (recv_str == NULL)
|
549
|
+
goto refuse;
|
550
|
+
size_t recv_len =
|
551
|
+
settings.request->headers[settings.request->metadata.headers_pos]
|
552
|
+
.value_length;
|
553
|
+
|
554
|
+
// websocket extentions (none)
|
555
|
+
|
556
|
+
// the accept Base64 Hash - we need to compute this one and set it
|
557
|
+
// the client's unique string
|
558
|
+
// use the SHA1 methods provided to concat the client string and hash
|
559
|
+
sha1_s sha1;
|
560
|
+
sha1 = bscrypt_sha1_init();
|
561
|
+
bscrypt_sha1_write(&sha1, recv_str, recv_len);
|
562
|
+
bscrypt_sha1_write(&sha1, ws_key_accpt_str, sizeof(ws_key_accpt_str) - 1);
|
563
|
+
// base encode the data
|
564
|
+
char websockets_key[32];
|
565
|
+
int len =
|
566
|
+
bscrypt_base64_encode(websockets_key, bscrypt_sha1_result(&sha1), 20);
|
567
|
+
|
568
|
+
// websocket extentions (none)
|
569
|
+
|
570
|
+
// upgrade taking place, make sure the upgrade headers are valid for the
|
571
|
+
// response.
|
572
|
+
response->status = 101;
|
573
|
+
http_response_write_header(response, .name = "Connection", .name_length = 10,
|
574
|
+
.value = "Upgrade", .value_length = 7);
|
575
|
+
http_response_write_header(response, .name = "Upgrade", .name_length = 7,
|
576
|
+
.value = "websocket", .value_length = 9);
|
577
|
+
http_response_write_header(response, .name = "sec-websocket-version",
|
578
|
+
.name_length = 21, .value = "13",
|
579
|
+
.value_length = 2);
|
580
|
+
// set the string's length and encoding
|
581
|
+
http_response_write_header(response, .name = "Sec-WebSocket-Accept",
|
582
|
+
.name_length = 20, .value = websockets_key,
|
583
|
+
.value_length = len);
|
584
|
+
// inform about 0 extension support
|
585
|
+
recv_str = http_request_find_header(settings.request,
|
586
|
+
"sec-websocket-extensions", 24);
|
587
|
+
// if (recv_str != NULL)
|
588
|
+
// http_response_write_header(response, .name = "Sec-Websocket-Extensions",
|
589
|
+
// .name_length = 24);
|
590
|
+
|
591
|
+
goto cleanup;
|
592
|
+
refuse:
|
593
|
+
// set the negative response
|
594
|
+
response->status = 400;
|
595
|
+
cleanup:
|
596
|
+
http_response_finish(response);
|
597
|
+
if (response->status == 101) {
|
598
|
+
// update the protocol object, cleanning up the old one
|
599
|
+
server_switch_protocol(ws->fd, (void*)ws);
|
600
|
+
// we have an active websocket connection - prep the connection buffer
|
601
|
+
ws->buffer = create_ws_buffer(ws);
|
602
|
+
// update the timeout
|
603
|
+
server_set_timeout(ws->fd, settings.timeout);
|
604
|
+
// call the on_open callback
|
605
|
+
if (settings.on_open)
|
606
|
+
server_task(ws->fd, on_open, settings.on_open, NULL);
|
607
|
+
return 0;
|
608
|
+
}
|
609
|
+
destroy_ws(ws);
|
610
|
+
return -1;
|
611
|
+
}
|
612
|
+
#define websocket_upgrade(...) \
|
613
|
+
websocket_upgrade((websocket_settings_s){__VA_ARGS__})
|
614
|
+
|
615
|
+
/** Returns the opaque user data associated with the websocket. */
|
616
|
+
void* websocket_get_udata(ws_s* ws) {
|
617
|
+
return ws->udata;
|
618
|
+
}
|
619
|
+
/** Returns the the process specific connection's UUID (see `libsock`). */
|
620
|
+
intptr_t websocket_get_fduuid(ws_s* ws) {
|
621
|
+
return ws->fd;
|
622
|
+
}
|
623
|
+
/** Sets the opaque user data associated with the websocket.
|
624
|
+
* Returns the old value, if any. */
|
625
|
+
void* websocket_set_udata(ws_s* ws, void* udata) {
|
626
|
+
void* old = ws->udata;
|
627
|
+
ws->udata = udata;
|
628
|
+
return old;
|
629
|
+
}
|
630
|
+
/** Writes data to the websocket. Returns -1 on failure (0 on success). */
|
631
|
+
int websocket_write(ws_s* ws, void* data, size_t size, uint8_t is_text) {
|
632
|
+
if (sock_isvalid(ws->fd)) {
|
633
|
+
websocket_write_impl(ws->fd, data, size, is_text, 1, 1,
|
634
|
+
ws->parser.state.client);
|
635
|
+
return 0;
|
636
|
+
}
|
637
|
+
return -1;
|
638
|
+
}
|
639
|
+
/** Closes a websocket connection. */
|
640
|
+
void websocket_close(ws_s* ws) {
|
641
|
+
sock_packet_s* packet;
|
642
|
+
while ((packet = sock_checkout_packet()) == NULL)
|
643
|
+
sock_flush_all();
|
644
|
+
*packet = (sock_packet_s){
|
645
|
+
.buffer = "\x88\x00", .length = 2,
|
646
|
+
};
|
647
|
+
sock_send_packet(ws->fd, packet);
|
648
|
+
sock_close(ws->fd);
|
649
|
+
return;
|
650
|
+
}
|
651
|
+
|
652
|
+
/**
|
653
|
+
Counts the number of websocket connections.
|
654
|
+
*/
|
655
|
+
size_t websocket_count(ws_s* ws) {
|
656
|
+
return server_count(WEBSOCKET_ID_STR);
|
657
|
+
}
|
658
|
+
|
659
|
+
/*******************************************************************************
|
660
|
+
Each Implementation
|
661
|
+
*/
|
662
|
+
|
663
|
+
/** A task container. */
|
664
|
+
struct WSTask {
|
665
|
+
void (*task)(ws_s*, void*);
|
666
|
+
void (*on_finish)(ws_s*, void*);
|
667
|
+
void* arg;
|
668
|
+
};
|
669
|
+
/** Performs a task on each websocket connection that shares the same process */
|
670
|
+
static void perform_ws_task(intptr_t fd, protocol_s* _ws, void* _arg) {
|
671
|
+
struct WSTask* tsk = _arg;
|
672
|
+
tsk->task((ws_s*)(_ws), tsk->arg);
|
673
|
+
}
|
674
|
+
/** clears away a wesbocket task. */
|
675
|
+
static void finish_ws_task(intptr_t fd, protocol_s* _ws, void* _arg) {
|
676
|
+
struct WSTask* tsk = _arg;
|
677
|
+
if (tsk->on_finish)
|
678
|
+
tsk->on_finish((ws_s*)(_ws), tsk->arg);
|
679
|
+
free(tsk);
|
680
|
+
}
|
681
|
+
|
682
|
+
/**
|
683
|
+
Performs a task on each websocket connection that shares the same process
|
684
|
+
(except the originating `ws_s` connection which is allowed to be NULL).
|
685
|
+
*/
|
686
|
+
void websocket_each(ws_s* ws_originator,
|
687
|
+
void (*task)(ws_s* ws_target, void* arg),
|
688
|
+
void* arg,
|
689
|
+
void (*on_finish)(ws_s* ws_originator, void* arg)) {
|
690
|
+
struct WSTask* tsk = malloc(sizeof(*tsk));
|
691
|
+
tsk->arg = arg;
|
692
|
+
tsk->on_finish = on_finish;
|
693
|
+
tsk->task = task;
|
694
|
+
server_each((ws_originator ? ws_originator->fd : -1), WEBSOCKET_ID_STR,
|
695
|
+
perform_ws_task, tsk, finish_ws_task);
|
696
|
+
}
|