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.

Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -2
  3. data/.travis.yml +23 -2
  4. data/CHANGELOG.md +9 -2
  5. data/README.md +232 -179
  6. data/Rakefile +13 -1
  7. data/bin/config.ru +63 -0
  8. data/bin/console +6 -0
  9. data/bin/echo +42 -32
  10. data/bin/http-hello +62 -0
  11. data/bin/http-playground +124 -0
  12. data/bin/playground +62 -0
  13. data/bin/poc/Gemfile.lock +23 -0
  14. data/bin/poc/README.md +37 -0
  15. data/bin/poc/config.ru +66 -0
  16. data/bin/poc/gemfile +1 -0
  17. data/bin/poc/www/index.html +57 -0
  18. data/bin/raw-rbhttp +35 -0
  19. data/bin/raw_broadcast +66 -0
  20. data/bin/test_with_faye +40 -0
  21. data/bin/ws-broadcast +108 -0
  22. data/bin/ws-echo +108 -0
  23. data/exe/iodine +59 -0
  24. data/ext/iodine/base64.c +264 -0
  25. data/ext/iodine/base64.h +72 -0
  26. data/ext/iodine/bscrypt-common.h +109 -0
  27. data/ext/iodine/bscrypt.h +49 -0
  28. data/ext/iodine/extconf.rb +41 -0
  29. data/ext/iodine/hex.c +123 -0
  30. data/ext/iodine/hex.h +70 -0
  31. data/ext/iodine/http.c +200 -0
  32. data/ext/iodine/http.h +128 -0
  33. data/ext/iodine/http1.c +402 -0
  34. data/ext/iodine/http1.h +56 -0
  35. data/ext/iodine/http1_simple_parser.c +473 -0
  36. data/ext/iodine/http1_simple_parser.h +59 -0
  37. data/ext/iodine/http_request.h +128 -0
  38. data/ext/iodine/http_response.c +1606 -0
  39. data/ext/iodine/http_response.h +393 -0
  40. data/ext/iodine/http_response_http1.h +374 -0
  41. data/ext/iodine/iodine_core.c +641 -0
  42. data/ext/iodine/iodine_core.h +70 -0
  43. data/ext/iodine/iodine_http.c +615 -0
  44. data/ext/iodine/iodine_http.h +19 -0
  45. data/ext/iodine/iodine_websocket.c +430 -0
  46. data/ext/iodine/iodine_websocket.h +21 -0
  47. data/ext/iodine/libasync.c +552 -0
  48. data/ext/iodine/libasync.h +117 -0
  49. data/ext/iodine/libreact.c +347 -0
  50. data/ext/iodine/libreact.h +244 -0
  51. data/ext/iodine/libserver.c +912 -0
  52. data/ext/iodine/libserver.h +435 -0
  53. data/ext/iodine/libsock.c +950 -0
  54. data/ext/iodine/libsock.h +478 -0
  55. data/ext/iodine/misc.c +181 -0
  56. data/ext/iodine/misc.h +76 -0
  57. data/ext/iodine/random.c +193 -0
  58. data/ext/iodine/random.h +48 -0
  59. data/ext/iodine/rb-call.c +127 -0
  60. data/ext/iodine/rb-call.h +60 -0
  61. data/ext/iodine/rb-libasync.h +79 -0
  62. data/ext/iodine/rb-rack-io.c +389 -0
  63. data/ext/iodine/rb-rack-io.h +17 -0
  64. data/ext/iodine/rb-registry.c +213 -0
  65. data/ext/iodine/rb-registry.h +33 -0
  66. data/ext/iodine/sha1.c +359 -0
  67. data/ext/iodine/sha1.h +85 -0
  68. data/ext/iodine/sha2.c +825 -0
  69. data/ext/iodine/sha2.h +138 -0
  70. data/ext/iodine/siphash.c +136 -0
  71. data/ext/iodine/siphash.h +15 -0
  72. data/ext/iodine/spnlock.h +235 -0
  73. data/ext/iodine/websockets.c +696 -0
  74. data/ext/iodine/websockets.h +120 -0
  75. data/ext/iodine/xor-crypt.c +189 -0
  76. data/ext/iodine/xor-crypt.h +107 -0
  77. data/iodine.gemspec +25 -18
  78. data/lib/iodine.rb +57 -58
  79. data/lib/iodine/http.rb +0 -189
  80. data/lib/iodine/protocol.rb +36 -245
  81. data/lib/iodine/version.rb +1 -1
  82. data/lib/rack/handler/iodine.rb +145 -2
  83. metadata +115 -37
  84. data/bin/core_http_test +0 -51
  85. data/bin/em playground +0 -56
  86. data/bin/hello_world +0 -75
  87. data/bin/setup +0 -7
  88. data/lib/iodine/client.rb +0 -5
  89. data/lib/iodine/core.rb +0 -102
  90. data/lib/iodine/core_init.rb +0 -143
  91. data/lib/iodine/http/hpack.rb +0 -553
  92. data/lib/iodine/http/http1.rb +0 -251
  93. data/lib/iodine/http/http2.rb +0 -507
  94. data/lib/iodine/http/rack_support.rb +0 -108
  95. data/lib/iodine/http/request.rb +0 -462
  96. data/lib/iodine/http/response.rb +0 -474
  97. data/lib/iodine/http/session.rb +0 -143
  98. data/lib/iodine/http/websocket_client.rb +0 -335
  99. data/lib/iodine/http/websocket_handler.rb +0 -101
  100. data/lib/iodine/http/websockets.rb +0 -336
  101. data/lib/iodine/io.rb +0 -56
  102. data/lib/iodine/logging.rb +0 -46
  103. data/lib/iodine/settings.rb +0 -158
  104. data/lib/iodine/ssl_connector.rb +0 -48
  105. 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
+ }