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.

Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +26 -0
  4. data/README.md +18 -12
  5. data/SPEC-Websocket-Draft.md +9 -5
  6. data/bin/ws-echo +3 -0
  7. data/examples/config.ru +0 -1
  8. data/examples/echo.ru +3 -1
  9. data/examples/redis.ru +0 -1
  10. data/ext/iodine/base64.c +97 -105
  11. data/ext/iodine/defer.c +16 -1
  12. data/ext/iodine/defer.h +10 -0
  13. data/ext/iodine/evio.c +35 -13
  14. data/ext/iodine/extconf.rb +1 -1
  15. data/ext/iodine/facil.c +12 -1
  16. data/ext/iodine/facil.h +3 -1
  17. data/ext/iodine/fio2resp.c +71 -0
  18. data/ext/iodine/fio2resp.h +50 -0
  19. data/ext/iodine/fio_cli_helper.c +404 -0
  20. data/ext/iodine/fio_cli_helper.h +152 -0
  21. data/ext/iodine/fiobj.h +631 -0
  22. data/ext/iodine/fiobj_alloc.c +81 -0
  23. data/ext/iodine/fiobj_ary.c +290 -0
  24. data/ext/iodine/fiobj_generic.c +260 -0
  25. data/ext/iodine/fiobj_hash.c +447 -0
  26. data/ext/iodine/fiobj_io.c +58 -0
  27. data/ext/iodine/fiobj_json.c +779 -0
  28. data/ext/iodine/fiobj_misc.c +213 -0
  29. data/ext/iodine/fiobj_numbers.c +113 -0
  30. data/ext/iodine/fiobj_primitives.c +98 -0
  31. data/ext/iodine/fiobj_str.c +261 -0
  32. data/ext/iodine/fiobj_sym.c +213 -0
  33. data/ext/iodine/fiobj_tests.c +474 -0
  34. data/ext/iodine/fiobj_types.h +290 -0
  35. data/ext/iodine/http1.c +54 -36
  36. data/ext/iodine/http1_parser.c +143 -35
  37. data/ext/iodine/http1_parser.h +6 -3
  38. data/ext/iodine/http1_response.c +0 -1
  39. data/ext/iodine/http_response.c +1 -1
  40. data/ext/iodine/iodine.c +20 -4
  41. data/ext/iodine/iodine_protocol.c +5 -4
  42. data/ext/iodine/iodine_pubsub.c +1 -1
  43. data/ext/iodine/random.c +5 -5
  44. data/ext/iodine/sha1.c +5 -8
  45. data/ext/iodine/sha2.c +8 -11
  46. data/ext/iodine/sha2.h +3 -3
  47. data/ext/iodine/sock.c +29 -31
  48. data/ext/iodine/websocket_parser.h +428 -0
  49. data/ext/iodine/websockets.c +112 -377
  50. data/ext/iodine/xor-crypt.c +16 -12
  51. data/lib/iodine/version.rb +1 -1
  52. metadata +21 -3
  53. data/ext/iodine/empty.h +0 -26
@@ -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
- size_t content_length;
40
- size_t read;
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
 
@@ -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 +=
@@ -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 (ext[pos] && pos < 8) {
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
- (void (*)(void *))Registry.remove);
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
- (void (*)(void *))Registry.remove);
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 who's capacity is less then 1Kb.
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. Returns `nil` if no data was available.
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) {
@@ -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 += 64;
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 += 32;
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 += 16;
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 += 8;
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
- ++target;
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 += partial;
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 += 64;
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(TEST_OPENSSL) && defined(__has_include)
280
- # if __has_include(<openssl/sha.h>)
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 HAS_OPEN_SSL
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 += partial;
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 += 128;
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 += partial;
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 += 64;
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(TEST_OPENSSL) && defined(__has_include)
662
- # if __has_include(<openssl/sha.h>)
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 HAS_OPEN_SSL
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
- goto error;
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
- size_t len) {
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
- size_t len) {
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
- size_t len) {
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 = NULL;
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
- 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;
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
- 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));
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
- if (init_exit)
301
- return 0;
302
- init_exit = 1;
303
- atexit(clear_sock_lib);
300
+ if (init_exit)
304
301
  return 0;
305
- }
306
- return -1;
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