iodine 0.4.8 → 0.4.10
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +26 -0
- data/README.md +18 -12
- data/SPEC-Websocket-Draft.md +9 -5
- data/bin/ws-echo +3 -0
- data/examples/config.ru +0 -1
- data/examples/echo.ru +3 -1
- data/examples/redis.ru +0 -1
- data/ext/iodine/base64.c +97 -105
- data/ext/iodine/defer.c +16 -1
- data/ext/iodine/defer.h +10 -0
- data/ext/iodine/evio.c +35 -13
- data/ext/iodine/extconf.rb +1 -1
- data/ext/iodine/facil.c +12 -1
- data/ext/iodine/facil.h +3 -1
- data/ext/iodine/fio2resp.c +71 -0
- data/ext/iodine/fio2resp.h +50 -0
- data/ext/iodine/fio_cli_helper.c +404 -0
- data/ext/iodine/fio_cli_helper.h +152 -0
- data/ext/iodine/fiobj.h +631 -0
- data/ext/iodine/fiobj_alloc.c +81 -0
- data/ext/iodine/fiobj_ary.c +290 -0
- data/ext/iodine/fiobj_generic.c +260 -0
- data/ext/iodine/fiobj_hash.c +447 -0
- data/ext/iodine/fiobj_io.c +58 -0
- data/ext/iodine/fiobj_json.c +779 -0
- data/ext/iodine/fiobj_misc.c +213 -0
- data/ext/iodine/fiobj_numbers.c +113 -0
- data/ext/iodine/fiobj_primitives.c +98 -0
- data/ext/iodine/fiobj_str.c +261 -0
- data/ext/iodine/fiobj_sym.c +213 -0
- data/ext/iodine/fiobj_tests.c +474 -0
- data/ext/iodine/fiobj_types.h +290 -0
- data/ext/iodine/http1.c +54 -36
- data/ext/iodine/http1_parser.c +143 -35
- data/ext/iodine/http1_parser.h +6 -3
- data/ext/iodine/http1_response.c +0 -1
- data/ext/iodine/http_response.c +1 -1
- data/ext/iodine/iodine.c +20 -4
- data/ext/iodine/iodine_protocol.c +5 -4
- data/ext/iodine/iodine_pubsub.c +1 -1
- data/ext/iodine/random.c +5 -5
- data/ext/iodine/sha1.c +5 -8
- data/ext/iodine/sha2.c +8 -11
- data/ext/iodine/sha2.h +3 -3
- data/ext/iodine/sock.c +29 -31
- data/ext/iodine/websocket_parser.h +428 -0
- data/ext/iodine/websockets.c +112 -377
- data/ext/iodine/xor-crypt.c +16 -12
- data/lib/iodine/version.rb +1 -1
- metadata +21 -3
- data/ext/iodine/empty.h +0 -26
data/ext/iodine/http1_parser.h
CHANGED
@@ -14,8 +14,11 @@ This is an attempt to replace the existing HTTP/1.x parser with something easier
|
|
14
14
|
to maintain and that could be used for an HTTP/1.x client as well.
|
15
15
|
*/
|
16
16
|
#define H_HTTP1_PARSER_H
|
17
|
+
#include <stddef.h>
|
17
18
|
#include <stdint.h>
|
19
|
+
#include <stdio.h>
|
18
20
|
#include <stdlib.h>
|
21
|
+
#include <sys/types.h>
|
19
22
|
|
20
23
|
#ifndef HTTP_HEADERS_LOWERCASE
|
21
24
|
/** when defined, HTTP headers will be converted to lowercase and header
|
@@ -36,9 +39,9 @@ to maintain and that could be used for an HTTP/1.x client as well.
|
|
36
39
|
typedef struct http1_parser_s {
|
37
40
|
void *udata;
|
38
41
|
struct http1_parser_protected_read_only_state_s {
|
39
|
-
|
40
|
-
|
41
|
-
uint8_t reserved;
|
42
|
+
ssize_t content_length; /* negative values indicate chuncked data state */
|
43
|
+
ssize_t read; /* total number of bytes read so far (body only) */
|
44
|
+
uint8_t reserved; /* for internal use */
|
42
45
|
} state;
|
43
46
|
} http1_parser_s;
|
44
47
|
|
data/ext/iodine/http1_response.c
CHANGED
@@ -160,7 +160,6 @@ static void http1_response_finalize_headers(http1_response_s *rs) {
|
|
160
160
|
rs->response.last_modified = rs->response.date;
|
161
161
|
else if (rs->response.date < rs->response.last_modified)
|
162
162
|
rs->response.date = rs->response.last_modified;
|
163
|
-
struct tm t;
|
164
163
|
/* date header */
|
165
164
|
h1p_protected_copy(rs, "Date: ", 6);
|
166
165
|
rs->buffer_end +=
|
data/ext/iodine/http_response.c
CHANGED
@@ -1669,7 +1669,7 @@ const char *http_response_ext2mime(const char *ext) {
|
|
1669
1669
|
char *extlow = (void *)(&ext8byte);
|
1670
1670
|
// change the copy to lowercase
|
1671
1671
|
size_t pos = 0;
|
1672
|
-
while (
|
1672
|
+
while (pos < 8 && ext[pos]) {
|
1673
1673
|
extlow[pos] =
|
1674
1674
|
(ext[pos] >= 'A' && ext[pos] <= 'Z') ? (ext[pos] | 32) : ext[pos];
|
1675
1675
|
++pos;
|
data/ext/iodine/iodine.c
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
#include "iodine_pubsub.h"
|
6
6
|
#include "iodine_websockets.h"
|
7
7
|
#include "rb-rack-io.h"
|
8
|
+
#include <dlfcn.h>
|
8
9
|
/*
|
9
10
|
Copyright: Boaz segev, 2016-2017
|
10
11
|
License: MIT
|
@@ -82,8 +83,11 @@ static VALUE iodine_run_after(VALUE self, VALUE milliseconds) {
|
|
82
83
|
if (block == Qnil)
|
83
84
|
return Qfalse;
|
84
85
|
Registry.add(block);
|
85
|
-
facil_run_every(milli, 1, iodine_run_task, (void *)block,
|
86
|
-
|
86
|
+
if (facil_run_every(milli, 1, iodine_run_task, (void *)block,
|
87
|
+
(void (*)(void *))Registry.remove) == -1) {
|
88
|
+
perror("ERROR: Iodine couldn't initialize timer");
|
89
|
+
return Qnil;
|
90
|
+
}
|
87
91
|
return block;
|
88
92
|
}
|
89
93
|
/**
|
@@ -123,8 +127,11 @@ static VALUE iodine_run_every(int argc, VALUE *argv, VALUE self) {
|
|
123
127
|
// requires a block to be passed
|
124
128
|
rb_need_block();
|
125
129
|
Registry.add(block);
|
126
|
-
facil_run_every(milli, repeat, iodine_run_task, (void *)block,
|
127
|
-
|
130
|
+
if (facil_run_every(milli, repeat, iodine_run_task, (void *)block,
|
131
|
+
(void (*)(void *))Registry.remove) == -1) {
|
132
|
+
perror("ERROR: Iodine couldn't initialize timer");
|
133
|
+
return Qnil;
|
134
|
+
}
|
128
135
|
return block;
|
129
136
|
}
|
130
137
|
|
@@ -320,6 +327,13 @@ VALUE iodine_print_registry(VALUE self) {
|
|
320
327
|
(void)self;
|
321
328
|
}
|
322
329
|
|
330
|
+
static void patch_env(void) {
|
331
|
+
#ifdef __APPLE__
|
332
|
+
/* patch for dealing with the High Sierra `fork` limitations */
|
333
|
+
void *obj_c_runtime = dlopen("Foundation.framework/Foundation", RTLD_LAZY);
|
334
|
+
#endif
|
335
|
+
}
|
336
|
+
|
323
337
|
/* *****************************************************************************
|
324
338
|
Library Initialization
|
325
339
|
***************************************************************************** */
|
@@ -330,6 +344,8 @@ Library Initialization
|
|
330
344
|
// Here we connect all the C code to the Ruby interface, completing the bridge
|
331
345
|
// between Lib-Server and Ruby.
|
332
346
|
void Init_iodine(void) {
|
347
|
+
// load any environment specific patches
|
348
|
+
patch_env();
|
333
349
|
// initialize globally used IDs, for faster access to the Ruby layer.
|
334
350
|
iodine_fd_var_id = rb_intern("scrtfd");
|
335
351
|
iodine_call_proc_id = rb_intern("call");
|
@@ -72,6 +72,7 @@ static VALUE not_implemented2(VALUE self, VALUE data) {
|
|
72
72
|
return Qnil;
|
73
73
|
}
|
74
74
|
|
75
|
+
static VALUE dyn_read(int argc, VALUE *argv, VALUE self);
|
75
76
|
/**
|
76
77
|
A default on_data implementation will read up to 1Kb into a reusable buffer from
|
77
78
|
the socket and call the `on_message` callback.
|
@@ -79,7 +80,6 @@ the socket and call the `on_message` callback.
|
|
79
80
|
It is recommended that you implement this callback if messages might require
|
80
81
|
more then 1Kb of space.
|
81
82
|
*/
|
82
|
-
static VALUE dyn_read(int argc, VALUE *argv, VALUE self);
|
83
83
|
static VALUE default_on_data(VALUE self) {
|
84
84
|
VALUE buff = rb_ivar_get(self, iodine_buff_var_id);
|
85
85
|
if (buff == Qnil) {
|
@@ -132,15 +132,16 @@ static VALUE dyn_run_each(VALUE self) {
|
|
132
132
|
}
|
133
133
|
|
134
134
|
/**
|
135
|
-
Reads `n` bytes from the network connection.
|
135
|
+
Reads up to `n` bytes from the network connection.
|
136
136
|
The number of bytes to be read (n) is:
|
137
137
|
- the number of bytes set in the optional `buffer_or_length` argument.
|
138
138
|
- the String capacity (not length) of the String passed as the optional
|
139
139
|
`buffer_or_length` argument.
|
140
140
|
- 1024 Bytes (1Kb) if the optional `buffer_or_length` is either missing or
|
141
|
-
contains a String
|
141
|
+
contains a String with a capacity less then 1Kb.
|
142
142
|
Returns a String (either the same one used as the buffer or a new one) on a
|
143
|
-
successful read.
|
143
|
+
successful read.
|
144
|
+
Returns `nil` if no data was available.
|
144
145
|
*/
|
145
146
|
static VALUE dyn_read(int argc, VALUE *argv, VALUE self) {
|
146
147
|
if (argc > 1) {
|
data/ext/iodine/iodine_pubsub.c
CHANGED
@@ -478,7 +478,7 @@ The function accepts a single argument (a Hash) and a required block.
|
|
478
478
|
Accepts a single Hash argument with the following possible options:
|
479
479
|
|
480
480
|
:engine :: If provided, the engine to use for pub/sub. Otherwise the default
|
481
|
-
engine is used.
|
481
|
+
:: engine is used.
|
482
482
|
|
483
483
|
:channel :: Required (unless :pattern). The channel to subscribe to.
|
484
484
|
|
data/ext/iodine/random.c
CHANGED
@@ -75,34 +75,34 @@ void bscrypt_rand_bytes(void *target, size_t length) {
|
|
75
75
|
while (length > 64) {
|
76
76
|
memcpy(target, sha2.digest.str, 64);
|
77
77
|
length -= 64;
|
78
|
-
target
|
78
|
+
target = (void *)((uintptr_t)target + 64);
|
79
79
|
bscrypt_sha2_write(&sha2, &cpu_state, sizeof(cpu_state));
|
80
80
|
bscrypt_sha2_result(&sha2);
|
81
81
|
}
|
82
82
|
if (length > 32) {
|
83
83
|
memcpy(target, sha2.digest.str, 32);
|
84
84
|
length -= 32;
|
85
|
-
target
|
85
|
+
target = (void *)((uintptr_t)target + 32);
|
86
86
|
bscrypt_sha2_write(&sha2, &cpu_state, sizeof(cpu_state));
|
87
87
|
bscrypt_sha2_result(&sha2);
|
88
88
|
}
|
89
89
|
if (length > 16) {
|
90
90
|
memcpy(target, sha2.digest.str, 16);
|
91
91
|
length -= 16;
|
92
|
-
target
|
92
|
+
target = (void *)((uintptr_t)target + 16);
|
93
93
|
bscrypt_sha2_write(&sha2, &cpu_state, sizeof(cpu_state));
|
94
94
|
bscrypt_sha2_result(&sha2);
|
95
95
|
}
|
96
96
|
if (length > 8) {
|
97
97
|
memcpy(target, sha2.digest.str, 8);
|
98
98
|
length -= 8;
|
99
|
-
target
|
99
|
+
target = (void *)((uintptr_t)target + 8);
|
100
100
|
bscrypt_sha2_write(&sha2, &cpu_state, sizeof(cpu_state));
|
101
101
|
bscrypt_sha2_result(&sha2);
|
102
102
|
}
|
103
103
|
while (length) {
|
104
104
|
*((uint8_t *)target) = sha2.digest.str[length];
|
105
|
-
|
105
|
+
target = (void *)((uintptr_t)target + 1);
|
106
106
|
--length;
|
107
107
|
}
|
108
108
|
}
|
data/ext/iodine/sha1.c
CHANGED
@@ -220,12 +220,12 @@ void bscrypt_sha1_write(sha1_s *s, const void *data, size_t len) {
|
|
220
220
|
if (in_buffer) {
|
221
221
|
memcpy(s->buffer + in_buffer, data, partial);
|
222
222
|
len -= partial;
|
223
|
-
data
|
223
|
+
data = (void *)((uintptr_t)data + partial);
|
224
224
|
perform_all_rounds(s, s->buffer);
|
225
225
|
}
|
226
226
|
while (len >= 64) {
|
227
227
|
perform_all_rounds(s, data);
|
228
|
-
data
|
228
|
+
data = (void *)((uintptr_t)data + 64);
|
229
229
|
len -= 64;
|
230
230
|
}
|
231
231
|
if (len) {
|
@@ -276,11 +276,8 @@ SHA-1 testing
|
|
276
276
|
#include <time.h>
|
277
277
|
|
278
278
|
// clang-format off
|
279
|
-
#if defined(
|
280
|
-
#
|
281
|
-
# include <openssl/sha.h>
|
282
|
-
# define HAS_OPEN_SSL 1
|
283
|
-
# endif
|
279
|
+
#if defined(HAVE_OPENSSL)
|
280
|
+
# include <openssl/sha.h>
|
284
281
|
#endif
|
285
282
|
// clang-format on
|
286
283
|
|
@@ -326,7 +323,7 @@ void bscrypt_test_sha1(void) {
|
|
326
323
|
}
|
327
324
|
fprintf(stderr, " SHA-1 passed.\n");
|
328
325
|
|
329
|
-
#ifdef
|
326
|
+
#ifdef HAVE_OPENSSL
|
330
327
|
fprintf(stderr, "===================================\n");
|
331
328
|
fprintf(stderr, "bscrypt SHA-1 struct size: %lu\n", sizeof(sha1_s));
|
332
329
|
fprintf(stderr, "OpenSSL SHA-1 struct size: %lu\n", sizeof(SHA_CTX));
|
data/ext/iodine/sha2.c
CHANGED
@@ -478,12 +478,12 @@ void bscrypt_sha2_write(sha2_s *s, const void *data, size_t len) {
|
|
478
478
|
if (in_buffer) {
|
479
479
|
memcpy(s->buffer + in_buffer, data, partial);
|
480
480
|
len -= partial;
|
481
|
-
data
|
481
|
+
data = (void *)((uintptr_t)data + partial);
|
482
482
|
perform_all_rounds(s, s->buffer);
|
483
483
|
}
|
484
484
|
while (len >= 128) {
|
485
485
|
perform_all_rounds(s, data);
|
486
|
-
data
|
486
|
+
data = (void *)((uintptr_t)data + 128);
|
487
487
|
len -= 128;
|
488
488
|
}
|
489
489
|
if (len) {
|
@@ -505,12 +505,12 @@ void bscrypt_sha2_write(sha2_s *s, const void *data, size_t len) {
|
|
505
505
|
if (in_buffer) {
|
506
506
|
memcpy(s->buffer + in_buffer, data, partial);
|
507
507
|
len -= partial;
|
508
|
-
data
|
508
|
+
data = (void *)((uintptr_t)data + partial);
|
509
509
|
perform_all_rounds(s, s->buffer);
|
510
510
|
}
|
511
511
|
while (len >= 64) {
|
512
512
|
perform_all_rounds(s, data);
|
513
|
-
data
|
513
|
+
data = (void *)((uintptr_t)data + 64);
|
514
514
|
len -= 64;
|
515
515
|
}
|
516
516
|
if (len) {
|
@@ -658,11 +658,8 @@ static char *sha2_variant_names[] = {
|
|
658
658
|
};
|
659
659
|
|
660
660
|
// clang-format off
|
661
|
-
#if defined(
|
662
|
-
#
|
663
|
-
# include <openssl/sha.h>
|
664
|
-
# define HAS_OPEN_SSL 1
|
665
|
-
# endif
|
661
|
+
#if defined(HAVE_OPENSSL)
|
662
|
+
# include <openssl/sha.h>
|
666
663
|
#endif
|
667
664
|
// clang-format on
|
668
665
|
|
@@ -761,7 +758,7 @@ void bscrypt_test_sha2(void) {
|
|
761
758
|
|
762
759
|
fprintf(stderr, " SHA-2 passed.\n");
|
763
760
|
|
764
|
-
#ifdef
|
761
|
+
#ifdef HAVE_OPENSSL
|
765
762
|
fprintf(stderr, "===================================\n");
|
766
763
|
fprintf(stderr, "bscrypt SHA-2 struct size: %lu\n", sizeof(sha2_s));
|
767
764
|
fprintf(stderr, "OpenSSL SHA-2/256 struct size: %lu\n", sizeof(SHA256_CTX));
|
@@ -812,7 +809,7 @@ void bscrypt_test_sha2(void) {
|
|
812
809
|
#endif
|
813
810
|
|
814
811
|
return;
|
815
|
-
|
812
|
+
|
816
813
|
error:
|
817
814
|
fprintf(stderr,
|
818
815
|
":\n--- bscrypt SHA-2 Test FAILED!\ntype: "
|
data/ext/iodine/sha2.h
CHANGED
@@ -96,7 +96,7 @@ An SHA2 helper function that performs initialiation, writing and finalizing.
|
|
96
96
|
Uses the SHA2 512 variant.
|
97
97
|
*/
|
98
98
|
static inline UNUSED_FUNC char *bscrypt_sha2_512(sha2_s *s, const void *data,
|
99
|
-
|
99
|
+
size_t len) {
|
100
100
|
*s = bscrypt_sha2_init(SHA_512);
|
101
101
|
bscrypt_sha2_write(s, data, len);
|
102
102
|
return bscrypt_sha2_result(s);
|
@@ -107,7 +107,7 @@ An SHA2 helper function that performs initialiation, writing and finalizing.
|
|
107
107
|
Uses the SHA2 256 variant.
|
108
108
|
*/
|
109
109
|
static inline UNUSED_FUNC char *bscrypt_sha2_256(sha2_s *s, const void *data,
|
110
|
-
|
110
|
+
size_t len) {
|
111
111
|
*s = bscrypt_sha2_init(SHA_256);
|
112
112
|
bscrypt_sha2_write(s, data, len);
|
113
113
|
return bscrypt_sha2_result(s);
|
@@ -118,7 +118,7 @@ An SHA2 helper function that performs initialiation, writing and finalizing.
|
|
118
118
|
Uses the SHA2 384 variant.
|
119
119
|
*/
|
120
120
|
static inline UNUSED_FUNC char *bscrypt_sha2_384(sha2_s *s, const void *data,
|
121
|
-
|
121
|
+
size_t len) {
|
122
122
|
*s = bscrypt_sha2_init(SHA_384);
|
123
123
|
bscrypt_sha2_write(s, data, len);
|
124
124
|
return bscrypt_sha2_result(s);
|
data/ext/iodine/sock.c
CHANGED
@@ -133,7 +133,7 @@ static inline void sock_packet_free(packet_s *packet) {
|
|
133
133
|
}
|
134
134
|
|
135
135
|
static inline packet_s *sock_packet_try_grab(void) {
|
136
|
-
packet_s *packet
|
136
|
+
packet_s *packet;
|
137
137
|
spn_lock(&packet_pool.lock);
|
138
138
|
packet = packet_pool.next;
|
139
139
|
if (packet == NULL)
|
@@ -236,7 +236,6 @@ struct fd_data_s {
|
|
236
236
|
|
237
237
|
static struct sock_data_store {
|
238
238
|
size_t capacity;
|
239
|
-
uint8_t exit_init;
|
240
239
|
struct fd_data_s *fds;
|
241
240
|
} sock_data_store;
|
242
241
|
|
@@ -270,40 +269,39 @@ static inline int initialize_sock_lib(size_t capacity) {
|
|
270
269
|
return 0;
|
271
270
|
struct fd_data_s *new_collection =
|
272
271
|
realloc(sock_data_store.fds, sizeof(struct fd_data_s) * capacity);
|
273
|
-
if (new_collection)
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
272
|
+
if (!new_collection)
|
273
|
+
return -1;
|
274
|
+
sock_data_store.fds = new_collection;
|
275
|
+
for (size_t i = sock_data_store.capacity; i < capacity; i++) {
|
276
|
+
fdinfo(i) =
|
277
|
+
(struct fd_data_s){.open = 0,
|
278
|
+
.lock = SPN_LOCK_INIT,
|
279
|
+
.rw_hooks = (sock_rw_hook_s *)&SOCK_DEFAULT_HOOKS,
|
280
|
+
.counter = 0};
|
281
|
+
}
|
282
|
+
sock_data_store.capacity = capacity;
|
283
283
|
|
284
284
|
#ifdef DEBUG
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
285
|
+
fprintf(stderr,
|
286
|
+
"\nInitialized libsock for %lu sockets, "
|
287
|
+
"each one requires %lu bytes.\n"
|
288
|
+
"overall ovearhead: %lu bytes.\n"
|
289
|
+
"Initialized packet pool for %d elements, "
|
290
|
+
"each one %lu bytes.\n"
|
291
|
+
"overall buffer ovearhead: %lu bytes.\n"
|
292
|
+
"=== Socket Library Total: %lu bytes ===\n\n",
|
293
|
+
capacity, sizeof(struct fd_data_s),
|
294
|
+
sizeof(struct fd_data_s) * capacity, BUFFER_PACKET_POOL,
|
295
|
+
sizeof(packet_s), sizeof(packet_s) * BUFFER_PACKET_POOL,
|
296
|
+
(sizeof(packet_s) * BUFFER_PACKET_POOL) +
|
297
|
+
(sizeof(struct fd_data_s) * capacity));
|
298
298
|
#endif
|
299
299
|
|
300
|
-
|
301
|
-
return 0;
|
302
|
-
init_exit = 1;
|
303
|
-
atexit(clear_sock_lib);
|
300
|
+
if (init_exit)
|
304
301
|
return 0;
|
305
|
-
|
306
|
-
|
302
|
+
init_exit = 1;
|
303
|
+
atexit(clear_sock_lib);
|
304
|
+
return 0;
|
307
305
|
}
|
308
306
|
|
309
307
|
static inline int clear_fd(uintptr_t fd, uint8_t is_open) {
|
@@ -0,0 +1,428 @@
|
|
1
|
+
/*
|
2
|
+
copyright: Boaz Segev, 2017
|
3
|
+
license: MIT
|
4
|
+
|
5
|
+
Feel free to copy, use and enjoy according to the license specified.
|
6
|
+
*/
|
7
|
+
#ifndef H_WEBSOCKET_PARSER_H
|
8
|
+
/**\file
|
9
|
+
|
10
|
+
A single file Websocket message parser and Websocket message wrapper, decoupled
|
11
|
+
from any IO layer.
|
12
|
+
|
13
|
+
Notice that this header file library includes static funnction declerations that
|
14
|
+
must be implemented by the including file (the callbacks).
|
15
|
+
|
16
|
+
*/
|
17
|
+
#define H_WEBSOCKET_PARSER_H
|
18
|
+
#include <stdint.h>
|
19
|
+
#include <stdlib.h>
|
20
|
+
#include <string.h>
|
21
|
+
/* *****************************************************************************
|
22
|
+
API - Internal Helpers
|
23
|
+
***************************************************************************** */
|
24
|
+
|
25
|
+
/** used internally to mask and unmask client messages. */
|
26
|
+
inline static void websocket_xmask(void *msg, uint64_t len, uint32_t mask);
|
27
|
+
|
28
|
+
/* *****************************************************************************
|
29
|
+
API - Message Wrapping
|
30
|
+
***************************************************************************** */
|
31
|
+
|
32
|
+
/** returns the length of the buffer required to wrap a message `len` long */
|
33
|
+
static inline __attribute__((unused)) uint64_t
|
34
|
+
websocket_wrapped_len(uint64_t len);
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Wraps a Websocket server message and writes it to the target buffer.
|
38
|
+
*
|
39
|
+
* The `first` and `last` flags can be used to support message fragmentation.
|
40
|
+
*
|
41
|
+
* * target: the target buffer to write to.
|
42
|
+
* * msg: the message to be wrapped.
|
43
|
+
* * len: the message length.
|
44
|
+
* * opcode: set to 1 for UTF-8 message, 2 for binary, etc'.
|
45
|
+
* * first: set to 1 if `msg` points the begining of the message.
|
46
|
+
* * last: set to 1 if `msg + len` ends the message.
|
47
|
+
* * client: set to 1 to use client mode (data masking).
|
48
|
+
*
|
49
|
+
* Further opcode values:
|
50
|
+
* * %x0 denotes a continuation frame
|
51
|
+
* * %x1 denotes a text frame
|
52
|
+
* * %x2 denotes a binary frame
|
53
|
+
* * %x3-7 are reserved for further non-control frames
|
54
|
+
* * %x8 denotes a connection close
|
55
|
+
* * %x9 denotes a ping
|
56
|
+
* * %xA denotes a pong
|
57
|
+
* * %xB-F are reserved for further control frames
|
58
|
+
*
|
59
|
+
* Returns the number of bytes written. Always `websocket_wrapped_len(len)`
|
60
|
+
*/
|
61
|
+
inline static uint64_t __attribute__((unused))
|
62
|
+
websocket_server_wrap(void *target, void *msg, uint64_t len,
|
63
|
+
unsigned char opcode, unsigned char first,
|
64
|
+
unsigned char last, unsigned char rsv);
|
65
|
+
|
66
|
+
/**
|
67
|
+
* Wraps a Websocket client message and writes it to the target buffer.
|
68
|
+
*
|
69
|
+
* The `first` and `last` flags can be used to support message fragmentation.
|
70
|
+
*
|
71
|
+
* * target: the target buffer to write to.
|
72
|
+
* * msg: the message to be wrapped.
|
73
|
+
* * len: the message length.
|
74
|
+
* * opcode: set to 1 for UTF-8 message, 2 for binary, etc'.
|
75
|
+
* * first: set to 1 if `msg` points the begining of the message.
|
76
|
+
* * last: set to 1 if `msg + len` ends the message.
|
77
|
+
* * client: set to 1 to use client mode (data masking).
|
78
|
+
*
|
79
|
+
* Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4`
|
80
|
+
*/
|
81
|
+
inline static __attribute__((unused)) uint64_t
|
82
|
+
websocket_client_wrap(void *target, void *msg, uint64_t len,
|
83
|
+
unsigned char opcode, unsigned char first,
|
84
|
+
unsigned char last, unsigned char rsv);
|
85
|
+
|
86
|
+
/* *****************************************************************************
|
87
|
+
Callbacks - Required functions that must be inplemented to use this header
|
88
|
+
***************************************************************************** */
|
89
|
+
|
90
|
+
static void websocket_on_unwrapped(void *udata, void *msg, uint64_t len,
|
91
|
+
char first, char last, char text,
|
92
|
+
unsigned char rsv);
|
93
|
+
static void websocket_on_protocol_ping(void *udata, void *msg, uint64_t len);
|
94
|
+
static void websocket_on_protocol_pong(void *udata, void *msg, uint64_t len);
|
95
|
+
static void websocket_on_protocol_close(void *udata);
|
96
|
+
static void websocket_on_protocol_error(void *udata);
|
97
|
+
|
98
|
+
/* *****************************************************************************
|
99
|
+
API - Parsing (unwrapping)
|
100
|
+
***************************************************************************** */
|
101
|
+
|
102
|
+
/** the returned value for `websocket_buffer_required` */
|
103
|
+
struct websocket_packet_info_s {
|
104
|
+
/** the expected packet length */
|
105
|
+
uint64_t packet_length;
|
106
|
+
/** the packet's "head" size (before the data) */
|
107
|
+
uint8_t head_length;
|
108
|
+
/** a flag indicating if the packet is masked */
|
109
|
+
uint8_t masked;
|
110
|
+
};
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Returns all known information regarding the upcoming message.
|
114
|
+
*
|
115
|
+
* @returns a struct websocket_packet_info_s.
|
116
|
+
*
|
117
|
+
* On protocol error, the `head_length` value is 0 (no valid head detected).
|
118
|
+
*/
|
119
|
+
inline static struct websocket_packet_info_s
|
120
|
+
websocket_buffer_peek(void *buffer, uint64_t len);
|
121
|
+
|
122
|
+
/**
|
123
|
+
* Consumes the data in the buffer, calling any callbacks required.
|
124
|
+
*
|
125
|
+
* Returns the remaining data in the existing buffer (can be 0).
|
126
|
+
*
|
127
|
+
* Notice: if there's any remaining data in the buffer, `memmove` is used to
|
128
|
+
* place the data at the begining of the buffer.
|
129
|
+
*/
|
130
|
+
inline static __attribute__((unused)) uint64_t
|
131
|
+
websocket_consume(void *buffer, uint64_t len, void *udata,
|
132
|
+
uint8_t require_masking);
|
133
|
+
|
134
|
+
/* *****************************************************************************
|
135
|
+
|
136
|
+
Implementation
|
137
|
+
|
138
|
+
***************************************************************************** */
|
139
|
+
|
140
|
+
/* *****************************************************************************
|
141
|
+
Message masking
|
142
|
+
***************************************************************************** */
|
143
|
+
/** used internally to mask and unmask client messages. */
|
144
|
+
void websocket_xmask(void *msg, uint64_t len, uint32_t mask) {
|
145
|
+
const uint64_t xmask = (((uint64_t)mask) << 32) | mask;
|
146
|
+
while (len >= 8) {
|
147
|
+
*((uint64_t *)msg) ^= xmask;
|
148
|
+
len -= 8;
|
149
|
+
msg = (void *)((uintptr_t)msg + 8);
|
150
|
+
}
|
151
|
+
switch (len) {
|
152
|
+
case 7:
|
153
|
+
((uint8_t *)msg)[6] ^= ((uint8_t *)(&mask))[2];
|
154
|
+
/* fallthrough */
|
155
|
+
case 6:
|
156
|
+
((uint8_t *)msg)[5] ^= ((uint8_t *)(&mask))[1];
|
157
|
+
/* fallthrough */
|
158
|
+
case 5:
|
159
|
+
((uint8_t *)msg)[4] ^= ((uint8_t *)(&mask))[0];
|
160
|
+
/* fallthrough */
|
161
|
+
case 4:
|
162
|
+
((uint8_t *)msg)[3] ^= ((uint8_t *)(&mask))[3];
|
163
|
+
/* fallthrough */
|
164
|
+
case 3:
|
165
|
+
((uint8_t *)msg)[2] ^= ((uint8_t *)(&mask))[2];
|
166
|
+
/* fallthrough */
|
167
|
+
case 2:
|
168
|
+
((uint8_t *)msg)[1] ^= ((uint8_t *)(&mask))[1];
|
169
|
+
/* fallthrough */
|
170
|
+
case 1:
|
171
|
+
((uint8_t *)msg)[0] ^= ((uint8_t *)(&mask))[0];
|
172
|
+
/* fallthrough */
|
173
|
+
}
|
174
|
+
}
|
175
|
+
|
176
|
+
/* *****************************************************************************
|
177
|
+
Message wrapping
|
178
|
+
***************************************************************************** */
|
179
|
+
|
180
|
+
// clang-format off
|
181
|
+
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
|
182
|
+
# if defined(__has_include)
|
183
|
+
# if __has_include(<endian.h>)
|
184
|
+
# include <endian.h>
|
185
|
+
# elif __has_include(<sys/endian.h>)
|
186
|
+
# include <sys/endian.h>
|
187
|
+
# endif
|
188
|
+
# endif
|
189
|
+
# if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) && \
|
190
|
+
__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
191
|
+
# define __BIG_ENDIAN__
|
192
|
+
# endif
|
193
|
+
#endif
|
194
|
+
// clang-format on
|
195
|
+
|
196
|
+
#ifdef __BIG_ENDIAN__
|
197
|
+
/** stub byte swap 64 bit integer */
|
198
|
+
#define netiswap64(i) (i)
|
199
|
+
/** stub byte swap 16 bit integer */
|
200
|
+
#define netiswap16(i) (i)
|
201
|
+
|
202
|
+
#else
|
203
|
+
// TODO: check for __builtin_bswap64
|
204
|
+
/** byte swap 64 bit integer */
|
205
|
+
#define netiswap64(i) \
|
206
|
+
((((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) | \
|
207
|
+
(((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) | \
|
208
|
+
(((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) | \
|
209
|
+
(((i)&0xFF000000000000ULL) >> 40) | (((i)&0xFF00000000000000ULL) >> 56))
|
210
|
+
/** byte swap 16 bit integer */
|
211
|
+
#define netiswap16(i) ((((i)&0x00ff) << 8) | (((i)&0xff00) >> 8))
|
212
|
+
|
213
|
+
#endif
|
214
|
+
|
215
|
+
/** returns the length of the buffer required to wrap a message `len` long */
|
216
|
+
static inline uint64_t websocket_wrapped_len(uint64_t len) {
|
217
|
+
if (len < 126)
|
218
|
+
return len + 2;
|
219
|
+
if (len < (1UL << 16))
|
220
|
+
return len + 4;
|
221
|
+
return len + 10;
|
222
|
+
}
|
223
|
+
|
224
|
+
/**
|
225
|
+
* Wraps a Websocket server message and writes it to the target buffer.
|
226
|
+
*
|
227
|
+
* The `first` and `last` flags can be used to support message fragmentation.
|
228
|
+
*
|
229
|
+
* * target: the target buffer to write to.
|
230
|
+
* * msg: the message to be wrapped.
|
231
|
+
* * len: the message length.
|
232
|
+
* * opcode: set to 1 for UTF-8 message, 2 for binary, etc'.
|
233
|
+
* * first: set to 1 if `msg` points the begining of the message.
|
234
|
+
* * last: set to 1 if `msg + len` ends the message.
|
235
|
+
* * client: set to 1 to use client mode (data masking).
|
236
|
+
*
|
237
|
+
* Further opcode values:
|
238
|
+
* * %x0 denotes a continuation frame
|
239
|
+
* * %x1 denotes a text frame
|
240
|
+
* * %x2 denotes a binary frame
|
241
|
+
* * %x3-7 are reserved for further non-control frames
|
242
|
+
* * %x8 denotes a connection close
|
243
|
+
* * %x9 denotes a ping
|
244
|
+
* * %xA denotes a pong
|
245
|
+
* * %xB-F are reserved for further control frames
|
246
|
+
*
|
247
|
+
* Returns the number of bytes written. Always `websocket_wrapped_len(len)`
|
248
|
+
*/
|
249
|
+
static uint64_t websocket_server_wrap(void *target, void *msg, uint64_t len,
|
250
|
+
unsigned char opcode, unsigned char first,
|
251
|
+
unsigned char last, unsigned char rsv) {
|
252
|
+
((uint8_t *)target)[0] = 0 |
|
253
|
+
/* opcode */ (((first ? opcode : 0) & 15)) |
|
254
|
+
/* rsv */ ((rsv & 7) << 4) |
|
255
|
+
/*fin*/ ((last & 1) << 7);
|
256
|
+
if (len < 126) {
|
257
|
+
((uint8_t *)target)[1] = len;
|
258
|
+
memcpy(((uint8_t *)target) + 2, msg, len);
|
259
|
+
return len + 2;
|
260
|
+
} else if (len < (1UL << 16)) {
|
261
|
+
/* head is 4 bytes */
|
262
|
+
((uint8_t *)target)[1] = 126;
|
263
|
+
((uint16_t *)target)[1] = netiswap16(len);
|
264
|
+
memcpy((uint8_t *)target + 4, msg, len);
|
265
|
+
return len + 4;
|
266
|
+
}
|
267
|
+
/* Really Long Message */
|
268
|
+
((uint8_t *)target)[1] = 127;
|
269
|
+
((uint64_t *)((uint8_t *)target + 2))[0] = netiswap64(len);
|
270
|
+
memcpy((uint8_t *)target + 10, msg, len);
|
271
|
+
return len + 10;
|
272
|
+
}
|
273
|
+
|
274
|
+
/**
|
275
|
+
* Wraps a Websocket client message and writes it to the target buffer.
|
276
|
+
*
|
277
|
+
* The `first` and `last` flags can be used to support message fragmentation.
|
278
|
+
*
|
279
|
+
* * target: the target buffer to write to.
|
280
|
+
* * msg: the message to be wrapped.
|
281
|
+
* * len: the message length.
|
282
|
+
* * opcode: set to 1 for UTF-8 message, 2 for binary, etc'.
|
283
|
+
* * first: set to 1 if `msg` points the begining of the message.
|
284
|
+
* * last: set to 1 if `msg + len` ends the message.
|
285
|
+
*
|
286
|
+
* Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4`
|
287
|
+
*/
|
288
|
+
static uint64_t websocket_client_wrap(void *target, void *msg, uint64_t len,
|
289
|
+
unsigned char opcode, unsigned char first,
|
290
|
+
unsigned char last, unsigned char rsv) {
|
291
|
+
uint32_t mask = rand() + 0x01020408;
|
292
|
+
((uint8_t *)target)[0] = 0 |
|
293
|
+
/* opcode */ (((first ? opcode : 0) & 15)) |
|
294
|
+
/* rsv */ ((rsv & 7) << 4) |
|
295
|
+
/*fin*/ ((last & 1) << 7);
|
296
|
+
if (len < 126) {
|
297
|
+
((uint8_t *)target)[1] = len | 128;
|
298
|
+
((uint32_t *)((uint8_t *)target + 2))[0] = mask;
|
299
|
+
memcpy(((uint8_t *)target) + 6, msg, len);
|
300
|
+
websocket_xmask((uint8_t *)target + 6, len, mask);
|
301
|
+
return len + 6;
|
302
|
+
} else if (len < (1UL << 16)) {
|
303
|
+
/* head is 4 bytes */
|
304
|
+
((uint8_t *)target)[1] = 126 | 128;
|
305
|
+
((uint16_t *)target)[1] = netiswap16(len);
|
306
|
+
((uint32_t *)((uint8_t *)target + 4))[0] = mask;
|
307
|
+
memcpy((uint8_t *)target + 8, msg, len);
|
308
|
+
websocket_xmask((uint8_t *)target + 8, len, mask);
|
309
|
+
return len + 8;
|
310
|
+
}
|
311
|
+
/* Really Long Message */
|
312
|
+
((uint8_t *)target)[1] = 255;
|
313
|
+
((uint64_t *)((uint8_t *)target + 2))[0] = netiswap64(len);
|
314
|
+
((uint32_t *)((uint8_t *)target + 10))[0] = mask;
|
315
|
+
memcpy((uint8_t *)target + 14, msg, len);
|
316
|
+
websocket_xmask((uint8_t *)target + 14, len, mask);
|
317
|
+
return len + 14;
|
318
|
+
}
|
319
|
+
|
320
|
+
/* *****************************************************************************
|
321
|
+
Message unwrapping
|
322
|
+
***************************************************************************** */
|
323
|
+
|
324
|
+
/**
|
325
|
+
* Returns all known information regarding the upcoming message.
|
326
|
+
*
|
327
|
+
* @returns a struct websocket_packet_info_s.
|
328
|
+
*
|
329
|
+
* On protocol error, the `head_length` value is 0 (no valid head detected).
|
330
|
+
*/
|
331
|
+
inline static struct websocket_packet_info_s
|
332
|
+
websocket_buffer_peek(void *buffer, uint64_t len) {
|
333
|
+
if (len < 2)
|
334
|
+
return (struct websocket_packet_info_s){0, 2, 0};
|
335
|
+
const uint8_t mask_f = (((uint8_t *)buffer)[1] >> 7) & 1;
|
336
|
+
const uint8_t mask_l = (mask_f << 2);
|
337
|
+
uint8_t len_indicator = (((uint8_t *)buffer)[1] & 127);
|
338
|
+
if (len < 126)
|
339
|
+
return (struct websocket_packet_info_s){len_indicator,
|
340
|
+
(uint8_t)(2 + mask_l), mask_f};
|
341
|
+
switch (len_indicator) {
|
342
|
+
case 126:
|
343
|
+
if (len < 4)
|
344
|
+
return (struct websocket_packet_info_s){0, (uint8_t)(4 + mask_l), mask_f};
|
345
|
+
return (struct websocket_packet_info_s){
|
346
|
+
(uint64_t)netiswap16(((uint16_t *)buffer)[1]), (uint8_t)(4 + mask_l),
|
347
|
+
mask_f};
|
348
|
+
case 127:
|
349
|
+
if (len < 10)
|
350
|
+
return (struct websocket_packet_info_s){0, (uint8_t)(10 + mask_l),
|
351
|
+
mask_f};
|
352
|
+
return (struct websocket_packet_info_s){
|
353
|
+
netiswap64(((uint64_t *)((uint8_t *)buffer + 2))[0]),
|
354
|
+
(uint8_t)(10 + mask_l), mask_f};
|
355
|
+
default:
|
356
|
+
return (struct websocket_packet_info_s){0, 0, 0};
|
357
|
+
}
|
358
|
+
}
|
359
|
+
|
360
|
+
/**
|
361
|
+
* Consumes the data in the buffer, calling any callbacks required.
|
362
|
+
*
|
363
|
+
* Returns the remaining data in the existing buffer (can be 0).
|
364
|
+
*/
|
365
|
+
static uint64_t websocket_consume(void *buffer, uint64_t len, void *udata,
|
366
|
+
uint8_t require_masking) {
|
367
|
+
struct websocket_packet_info_s info = websocket_buffer_peek(buffer, len);
|
368
|
+
if (info.head_length + info.packet_length > len)
|
369
|
+
return len;
|
370
|
+
uint64_t reminder = len;
|
371
|
+
uint8_t *pos = (uint8_t *)buffer;
|
372
|
+
while (info.head_length + info.packet_length <= reminder) {
|
373
|
+
/* parse head */
|
374
|
+
void *payload = (void *)(pos + info.head_length);
|
375
|
+
/* unmask? */
|
376
|
+
if (info.masked) {
|
377
|
+
/* masked */
|
378
|
+
const uint32_t mask = ((uint32_t *)payload)[-1];
|
379
|
+
websocket_xmask(payload, info.packet_length, mask);
|
380
|
+
} else if (require_masking) {
|
381
|
+
/* error */
|
382
|
+
websocket_on_protocol_error(udata);
|
383
|
+
}
|
384
|
+
/* call callback */
|
385
|
+
switch (pos[0] & 15) {
|
386
|
+
case 0:
|
387
|
+
/* continuation frame */
|
388
|
+
websocket_on_unwrapped(udata, payload, info.packet_length, 0,
|
389
|
+
((pos[0] >> 7) & 1), 0, ((pos[0] >> 4) & 7));
|
390
|
+
break;
|
391
|
+
case 1:
|
392
|
+
/* text frame */
|
393
|
+
websocket_on_unwrapped(udata, payload, info.packet_length, 1,
|
394
|
+
((pos[0] >> 7) & 1), 1, ((pos[0] >> 4) & 7));
|
395
|
+
break;
|
396
|
+
case 2:
|
397
|
+
/* data frame */
|
398
|
+
websocket_on_unwrapped(udata, payload, info.packet_length, 1,
|
399
|
+
((pos[0] >> 7) & 1), 0, ((pos[0] >> 4) & 7));
|
400
|
+
break;
|
401
|
+
case 8:
|
402
|
+
/* close frame */
|
403
|
+
websocket_on_protocol_close(udata);
|
404
|
+
break;
|
405
|
+
case 9:
|
406
|
+
/* ping frame */
|
407
|
+
websocket_on_protocol_ping(udata, payload, info.packet_length);
|
408
|
+
break;
|
409
|
+
case 10:
|
410
|
+
/* pong frame */
|
411
|
+
websocket_on_protocol_pong(udata, payload, info.packet_length);
|
412
|
+
break;
|
413
|
+
default:
|
414
|
+
websocket_on_protocol_error(udata);
|
415
|
+
}
|
416
|
+
/* step forward */
|
417
|
+
reminder -= info.head_length + info.packet_length;
|
418
|
+
pos += info.head_length + info.packet_length;
|
419
|
+
info = websocket_buffer_peek(pos, reminder);
|
420
|
+
}
|
421
|
+
/* reset buffer state - support pipelining */
|
422
|
+
if (!reminder)
|
423
|
+
return 0;
|
424
|
+
memmove(buffer, (uint8_t *)buffer + len - reminder, reminder);
|
425
|
+
return reminder;
|
426
|
+
}
|
427
|
+
|
428
|
+
#endif
|