iodine 0.4.19 → 0.5.0

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +22 -0
  4. data/LIMITS.md +19 -9
  5. data/README.md +92 -77
  6. data/SPEC-PubSub-Draft.md +113 -0
  7. data/SPEC-Websocket-Draft.md +127 -143
  8. data/bin/http-hello +0 -1
  9. data/bin/raw-rbhttp +1 -1
  10. data/bin/raw_broadcast +8 -10
  11. data/bin/updated api +2 -2
  12. data/bin/ws-broadcast +2 -4
  13. data/bin/ws-echo +2 -2
  14. data/examples/config.ru +13 -13
  15. data/examples/echo.ru +5 -6
  16. data/examples/hello.ru +2 -3
  17. data/examples/info.md +316 -0
  18. data/examples/pubsub_engine.ru +81 -0
  19. data/examples/redis.ru +9 -9
  20. data/examples/shootout.ru +45 -11
  21. data/ext/iodine/defer.c +194 -297
  22. data/ext/iodine/defer.h +61 -53
  23. data/ext/iodine/evio.c +0 -260
  24. data/ext/iodine/evio.h +50 -22
  25. data/ext/iodine/evio_callbacks.c +26 -0
  26. data/ext/iodine/evio_epoll.c +251 -0
  27. data/ext/iodine/evio_kqueue.c +193 -0
  28. data/ext/iodine/extconf.rb +1 -1
  29. data/ext/iodine/facil.c +1420 -542
  30. data/ext/iodine/facil.h +151 -64
  31. data/ext/iodine/fio_ary.h +418 -0
  32. data/ext/iodine/{base64.c → fio_base64.c} +33 -24
  33. data/ext/iodine/{base64.h → fio_base64.h} +6 -7
  34. data/ext/iodine/{fio_cli_helper.c → fio_cli.c} +77 -58
  35. data/ext/iodine/{fio_cli_helper.h → fio_cli.h} +9 -4
  36. data/ext/iodine/fio_hashmap.h +759 -0
  37. data/ext/iodine/fio_json_parser.h +651 -0
  38. data/ext/iodine/fio_llist.h +257 -0
  39. data/ext/iodine/fio_mem.c +672 -0
  40. data/ext/iodine/fio_mem.h +140 -0
  41. data/ext/iodine/fio_random.c +248 -0
  42. data/ext/iodine/{random.h → fio_random.h} +11 -14
  43. data/ext/iodine/{sha1.c → fio_sha1.c} +28 -24
  44. data/ext/iodine/{sha1.h → fio_sha1.h} +38 -16
  45. data/ext/iodine/{sha2.c → fio_sha2.c} +66 -49
  46. data/ext/iodine/{sha2.h → fio_sha2.h} +57 -26
  47. data/ext/iodine/{fiobj_internal.c → fio_siphash.c} +9 -90
  48. data/ext/iodine/fio_siphash.h +18 -0
  49. data/ext/iodine/fio_tmpfile.h +38 -0
  50. data/ext/iodine/fiobj.h +24 -7
  51. data/ext/iodine/fiobj4sock.h +23 -0
  52. data/ext/iodine/fiobj_ary.c +143 -226
  53. data/ext/iodine/fiobj_ary.h +17 -16
  54. data/ext/iodine/fiobj_data.c +1160 -0
  55. data/ext/iodine/fiobj_data.h +164 -0
  56. data/ext/iodine/fiobj_hash.c +298 -406
  57. data/ext/iodine/fiobj_hash.h +101 -54
  58. data/ext/iodine/fiobj_json.c +478 -601
  59. data/ext/iodine/fiobj_json.h +34 -9
  60. data/ext/iodine/fiobj_numbers.c +383 -51
  61. data/ext/iodine/fiobj_numbers.h +87 -11
  62. data/ext/iodine/fiobj_str.c +423 -184
  63. data/ext/iodine/fiobj_str.h +81 -32
  64. data/ext/iodine/fiobject.c +273 -522
  65. data/ext/iodine/fiobject.h +477 -112
  66. data/ext/iodine/http.c +2243 -83
  67. data/ext/iodine/http.h +842 -121
  68. data/ext/iodine/http1.c +810 -385
  69. data/ext/iodine/http1.h +16 -39
  70. data/ext/iodine/http1_parser.c +146 -74
  71. data/ext/iodine/http1_parser.h +15 -4
  72. data/ext/iodine/http_internal.c +1258 -0
  73. data/ext/iodine/http_internal.h +226 -0
  74. data/ext/iodine/http_mime_parser.h +341 -0
  75. data/ext/iodine/iodine.c +86 -68
  76. data/ext/iodine/iodine.h +26 -11
  77. data/ext/iodine/iodine_helpers.c +8 -7
  78. data/ext/iodine/iodine_http.c +487 -324
  79. data/ext/iodine/iodine_json.c +304 -0
  80. data/ext/iodine/iodine_json.h +6 -0
  81. data/ext/iodine/iodine_protocol.c +107 -45
  82. data/ext/iodine/iodine_pubsub.c +526 -225
  83. data/ext/iodine/iodine_pubsub.h +10 -0
  84. data/ext/iodine/iodine_websockets.c +268 -510
  85. data/ext/iodine/iodine_websockets.h +2 -4
  86. data/ext/iodine/pubsub.c +726 -432
  87. data/ext/iodine/pubsub.h +85 -103
  88. data/ext/iodine/rb-call.c +4 -4
  89. data/ext/iodine/rb-defer.c +46 -22
  90. data/ext/iodine/rb-fiobj2rb.h +117 -0
  91. data/ext/iodine/rb-rack-io.c +73 -238
  92. data/ext/iodine/rb-rack-io.h +2 -2
  93. data/ext/iodine/rb-registry.c +35 -93
  94. data/ext/iodine/rb-registry.h +1 -0
  95. data/ext/iodine/redis_engine.c +742 -304
  96. data/ext/iodine/redis_engine.h +42 -39
  97. data/ext/iodine/resp_parser.h +311 -0
  98. data/ext/iodine/sock.c +627 -490
  99. data/ext/iodine/sock.h +345 -297
  100. data/ext/iodine/spnlock.inc +15 -4
  101. data/ext/iodine/websocket_parser.h +16 -20
  102. data/ext/iodine/websockets.c +188 -257
  103. data/ext/iodine/websockets.h +24 -133
  104. data/lib/iodine.rb +52 -7
  105. data/lib/iodine/cli.rb +6 -24
  106. data/lib/iodine/json.rb +40 -0
  107. data/lib/iodine/version.rb +1 -1
  108. data/lib/iodine/websocket.rb +5 -3
  109. data/lib/rack/handler/iodine.rb +58 -13
  110. metadata +38 -48
  111. data/bin/ws-shootout +0 -107
  112. data/examples/broadcast.ru +0 -56
  113. data/ext/iodine/bscrypt-common.h +0 -116
  114. data/ext/iodine/bscrypt.h +0 -49
  115. data/ext/iodine/fio2resp.c +0 -60
  116. data/ext/iodine/fio2resp.h +0 -51
  117. data/ext/iodine/fio_dict.c +0 -446
  118. data/ext/iodine/fio_dict.h +0 -99
  119. data/ext/iodine/fio_hash_table.h +0 -370
  120. data/ext/iodine/fio_list.h +0 -111
  121. data/ext/iodine/fiobj_internal.h +0 -280
  122. data/ext/iodine/fiobj_primitives.c +0 -131
  123. data/ext/iodine/fiobj_primitives.h +0 -55
  124. data/ext/iodine/fiobj_sym.c +0 -135
  125. data/ext/iodine/fiobj_sym.h +0 -60
  126. data/ext/iodine/hex.c +0 -124
  127. data/ext/iodine/hex.h +0 -70
  128. data/ext/iodine/http1_request.c +0 -81
  129. data/ext/iodine/http1_request.h +0 -58
  130. data/ext/iodine/http1_response.c +0 -417
  131. data/ext/iodine/http1_response.h +0 -95
  132. data/ext/iodine/http_request.c +0 -111
  133. data/ext/iodine/http_request.h +0 -102
  134. data/ext/iodine/http_response.c +0 -1703
  135. data/ext/iodine/http_response.h +0 -250
  136. data/ext/iodine/misc.c +0 -182
  137. data/ext/iodine/misc.h +0 -74
  138. data/ext/iodine/random.c +0 -208
  139. data/ext/iodine/redis_connection.c +0 -278
  140. data/ext/iodine/redis_connection.h +0 -86
  141. data/ext/iodine/resp.c +0 -842
  142. data/ext/iodine/resp.h +0 -261
  143. data/ext/iodine/siphash.c +0 -154
  144. data/ext/iodine/siphash.h +0 -22
  145. data/ext/iodine/xor-crypt.c +0 -193
  146. data/ext/iodine/xor-crypt.h +0 -107
@@ -22,7 +22,7 @@ spinlock / sync for tasks
22
22
  /* nanosleep seems to be the most effective and efficient reschedule */
23
23
  #define reschedule_thread() \
24
24
  { \
25
- static const struct timespec tm = {.tv_nsec = 1}; \
25
+ const struct timespec tm = {.tv_nsec = 1}; \
26
26
  nanosleep(&tm, NULL); \
27
27
  }
28
28
  #define throttle_thread(micosec) \
@@ -44,11 +44,11 @@ typedef volatile unsigned char spn_lock_i;
44
44
 
45
45
  /* C11 Atomics are defined? */
46
46
  #if defined(__ATOMIC_RELAXED)
47
- #define SPN_LOCK_BUILTIN(...) __atomic_exchange_n(__VA_ARGS__, __ATOMIC_ACQ_REL)
47
+ #define SPN_LOCK_BUILTIN(...) __atomic_exchange_n(__VA_ARGS__, __ATOMIC_SEQ_CST)
48
48
  /** An atomic addition operation */
49
- #define spn_add(...) __atomic_add_fetch(__VA_ARGS__, __ATOMIC_ACQ_REL)
49
+ #define spn_add(...) __atomic_add_fetch(__VA_ARGS__, __ATOMIC_SEQ_CST)
50
50
  /** An atomic subtraction operation */
51
- #define spn_sub(...) __atomic_sub_fetch(__VA_ARGS__, __ATOMIC_ACQ_REL)
51
+ #define spn_sub(...) __atomic_sub_fetch(__VA_ARGS__, __ATOMIC_SEQ_CST)
52
52
 
53
53
  /* Select the correct compiler builtin method. */
54
54
  #elif defined(__has_builtin)
@@ -90,6 +90,7 @@ static inline __attribute__((unused)) int spn_is_locked(spn_lock_i *lock) {
90
90
  __asm__ volatile("" ::: "memory");
91
91
  return *lock;
92
92
  }
93
+
93
94
  /** Busy waits for the lock. */
94
95
  static inline __attribute__((unused)) void spn_lock(spn_lock_i *lock) {
95
96
  while (spn_trylock(lock)) {
@@ -97,4 +98,14 @@ static inline __attribute__((unused)) void spn_lock(spn_lock_i *lock) {
97
98
  }
98
99
  }
99
100
 
101
+ #if DEBUG_SPINLOCK
102
+ /** Busy waits for a lock, reports contention. */
103
+ #define spn_lock(lock) \
104
+ while (spn_trylock(lock)) { \
105
+ fprintf(stderr, "INFO: spinlock spin %s:%d\n", __FILE__, __LINE__); \
106
+ reschedule_thread(); \
107
+ }
108
+ #include <stdio.h>
109
+ #endif /* DEBUG */
110
+
100
111
  #endif /* H_SPNLOCK_H */
@@ -18,6 +18,9 @@ must be implemented by the including file (the callbacks).
18
18
  #include <stdint.h>
19
19
  #include <stdlib.h>
20
20
  #include <string.h>
21
+ #if DEBUG
22
+ #include <stdio.h>
23
+ #endif
21
24
  /* *****************************************************************************
22
25
  API - Message Wrapping
23
26
  ***************************************************************************** */
@@ -145,20 +148,6 @@ void websocket_xmask(void *msg, uint64_t len, uint32_t mask) {
145
148
  if (len > 7) {
146
149
  { /* XOR any unaligned memory (4 byte alignment) */
147
150
  const uintptr_t offset = 4 - ((uintptr_t)msg & 3);
148
- // switch (offset) {
149
- // case 3:
150
- // ((uint8_t *)msg)[2] ^= ((uint8_t *)(&mask))[2];
151
- // /* fallthrough */
152
- // case 2:
153
- // ((uint8_t *)msg)[1] ^= ((uint8_t *)(&mask))[1];
154
- // /* fallthrough */
155
- // case 1:
156
- // ((uint8_t *)msg)[0] ^= ((uint8_t *)(&mask))[0];
157
- // /* rotate mask and move pointer to first 4 byte alignment */
158
- // mask = (mask << (offset << 3)) | (mask >> ((4 - offset) << 3));
159
- // msg = (void *)((uintptr_t)msg + offset);
160
- // len -= offset;
161
- // }
162
151
  switch (offset) {
163
152
  case 3:
164
153
  ((uint8_t *)msg)[2] ^= ((uint8_t *)(&mask))[2];
@@ -396,7 +385,7 @@ static uint64_t websocket_server_wrap(void *target, void *msg, uint64_t len,
396
385
  static uint64_t websocket_client_wrap(void *target, void *msg, uint64_t len,
397
386
  unsigned char opcode, unsigned char first,
398
387
  unsigned char last, unsigned char rsv) {
399
- uint32_t mask = rand() + 0x01020408;
388
+ uint32_t mask = rand() | 0x01020408;
400
389
  ((uint8_t *)target)[0] = 0 |
401
390
  /* opcode */ (((first ? opcode : 0) & 15)) |
402
391
  /* rsv */ ((rsv & 7) << 4) |
@@ -459,8 +448,9 @@ Message unwrapping
459
448
  inline static struct websocket_packet_info_s
460
449
  websocket_buffer_peek(void *buffer, uint64_t len) {
461
450
  if (len < 2) {
462
- return (struct websocket_packet_info_s){0 /* packet */, 2 /* head */,
463
- 0 /* masked? */};
451
+ const struct websocket_packet_info_s info = {0 /* packet */, 2 /* head */,
452
+ 0 /* masked? */};
453
+ return info;
464
454
  }
465
455
  const uint8_t mask_f = (((uint8_t *)buffer)[1] >> 7) & 1;
466
456
  const uint8_t mask_l = (mask_f << 2);
@@ -503,14 +493,16 @@ static uint64_t websocket_consume(void *buffer, uint64_t len, void *udata,
503
493
  /* unmask? */
504
494
  if (info.masked) {
505
495
  /* masked */
506
- const uint32_t mask; // = ((uint32_t *)payload)[-1];
496
+ uint32_t mask; // = ((uint32_t *)payload)[-1];
507
497
  ((uint8_t *)(&mask))[0] = ((uint8_t *)(payload))[-4];
508
498
  ((uint8_t *)(&mask))[1] = ((uint8_t *)(payload))[-3];
509
499
  ((uint8_t *)(&mask))[2] = ((uint8_t *)(payload))[-2];
510
500
  ((uint8_t *)(&mask))[3] = ((uint8_t *)(payload))[-1];
511
501
  websocket_xmask(payload, info.packet_length, mask);
512
- } else if (require_masking) {
513
- /* error */
502
+ } else if (require_masking && info.packet_length) {
503
+ #if DEBUG
504
+ fprintf(stderr, "ERROR: Websocket protocol error - unmasked data.\n");
505
+ #endif
514
506
  websocket_on_protocol_error(udata);
515
507
  }
516
508
  /* call callback */
@@ -543,6 +535,10 @@ static uint64_t websocket_consume(void *buffer, uint64_t len, void *udata,
543
535
  websocket_on_protocol_pong(udata, payload, info.packet_length);
544
536
  break;
545
537
  default:
538
+ #if DEBUG
539
+ fprintf(stderr, "ERROR: Websocket protocol error - unknown opcode %u\n",
540
+ (unsigned int)(pos[0] & 15));
541
+ #endif
546
542
  websocket_on_protocol_error(udata);
547
543
  }
548
544
  /* step forward */
@@ -1,17 +1,20 @@
1
1
  /*
2
- copyright: Boaz segev, 2016-2017
2
+ copyright: Boaz Segev, 2016-2018
3
3
  license: MIT
4
4
 
5
5
  Feel free to copy, use and enjoy according to the license provided.
6
6
  */
7
7
  #include "spnlock.inc"
8
8
 
9
- #include "fio_list.h"
9
+ #include "fio_llist.h"
10
10
  #include "fiobj.h"
11
11
 
12
- #include "bscrypt.h"
12
+ #include "fio_base64.h"
13
+ #include "fio_sha1.h"
14
+ #include "http.h"
15
+ #include "http_internal.h"
16
+
13
17
  #include "pubsub.h"
14
- #include "websockets.h"
15
18
  #include <arpa/inet.h>
16
19
  #include <errno.h>
17
20
  #include <stdio.h>
@@ -19,6 +22,8 @@ Feel free to copy, use and enjoy according to the license provided.
19
22
  #include <string.h>
20
23
  #include <strings.h>
21
24
 
25
+ #include "fio_mem.h"
26
+
22
27
  #include "websocket_parser.h"
23
28
 
24
29
  #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
@@ -98,7 +103,7 @@ static void destroy_ws(ws_s *ws);
98
103
  /*******************************************************************************
99
104
  The Websocket object (protocol + parser)
100
105
  */
101
- struct Websocket {
106
+ struct ws_s {
102
107
  /** The Websocket protocol */
103
108
  protocol_s protocol;
104
109
  /** connection data */
@@ -107,19 +112,20 @@ struct Websocket {
107
112
  void (*on_message)(ws_s *ws, char *data, size_t size, uint8_t is_text);
108
113
  void (*on_shutdown)(ws_s *ws);
109
114
  void (*on_ready)(ws_s *ws);
110
- void (*on_close)(ws_s *ws);
115
+ void (*on_open)(ws_s *ws);
116
+ void (*on_close)(intptr_t uuid, void *udata);
111
117
  /** Opaque user data. */
112
118
  void *udata;
113
119
  /** The maximum websocket message size */
114
120
  size_t max_msg_size;
115
121
  /** active pub/sub subscriptions */
116
- fio_list_s subscriptions;
122
+ fio_ls_s subscriptions;
117
123
  /** socket buffer. */
118
124
  struct buffer_s buffer;
119
125
  /** data length (how much of the buffer actually used). */
120
126
  size_t length;
121
127
  /** message buffer. */
122
- fiobj_s *msg;
128
+ FIOBJ msg;
123
129
  /** latest text state. */
124
130
  uint8_t is_text;
125
131
  /** websocket connection type. */
@@ -135,28 +141,9 @@ char *WEBSOCKET_ID_STR = "websockets";
135
141
  Create/Destroy the websocket subscription objects
136
142
  ***************************************************************************** */
137
143
 
138
- typedef struct {
139
- fio_list_s node;
140
- pubsub_sub_pt sub;
141
- } subscription_s;
142
-
143
- static inline subscription_s *create_subscription(ws_s *ws, pubsub_sub_pt sub) {
144
- subscription_s *s = malloc(sizeof(*s));
145
- s->sub = sub;
146
- fio_list_add(&ws->subscriptions, &s->node);
147
- return s;
148
- }
149
-
150
- static inline void free_subscription(subscription_s *s) {
151
- fio_list_remove(&s->node);
152
- free(s);
153
- }
154
-
155
144
  static inline void clear_subscriptions(ws_s *ws) {
156
- subscription_s *s;
157
- fio_list_for_each(subscription_s, node, s, ws->subscriptions) {
158
- pubsub_unsubscribe(s->sub);
159
- free_subscription(s);
145
+ while (fio_ls_any(&ws->subscriptions)) {
146
+ pubsub_unsubscribe(fio_ls_pop(&ws->subscriptions));
160
147
  }
161
148
  }
162
149
 
@@ -164,17 +151,17 @@ static inline void clear_subscriptions(ws_s *ws) {
164
151
  Callbacks - Required functions for websocket_parser.h
165
152
  ***************************************************************************** */
166
153
 
167
- static void websocket_on_unwrapped(void *udata, void *msg, uint64_t len,
154
+ static void websocket_on_unwrapped(void *ws_p, void *msg, uint64_t len,
168
155
  char first, char last, char text,
169
156
  unsigned char rsv) {
170
- ws_s *ws = udata;
157
+ ws_s *ws = ws_p;
171
158
  if (last && first) {
172
159
  ws->on_message(ws, msg, len, (uint8_t)text);
173
160
  return;
174
161
  }
175
162
  if (first) {
176
163
  ws->is_text = (uint8_t)text;
177
- if (ws->msg == NULL)
164
+ if (ws->msg == FIOBJ_INVALID)
178
165
  ws->msg = fiobj_str_buf(len);
179
166
  fiobj_str_resize(ws->msg, 0);
180
167
  }
@@ -186,23 +173,35 @@ static void websocket_on_unwrapped(void *udata, void *msg, uint64_t len,
186
173
 
187
174
  (void)rsv;
188
175
  }
189
- static void websocket_on_protocol_ping(void *udata, void *msg_, uint64_t len) {
190
- ws_s *ws = udata;
191
- uint16_t *msg = msg_;
192
- msg[-1] = *((uint16_t *)"\x89\x00");
193
- sock_write2(.uuid = ws->fd, .buffer = (void *)(msg - 1), .length = 2 + len);
176
+ static void websocket_on_protocol_ping(void *ws_p, void *msg_, uint64_t len) {
177
+ ws_s *ws = ws_p;
178
+ if (msg_) {
179
+ void *buff = malloc(len + 16);
180
+ len = (((ws_s *)ws)->is_client
181
+ ? websocket_client_wrap(buff, msg_, len, 10, 1, 1, 0)
182
+ : websocket_server_wrap(buff, msg_, len, 10, 1, 1, 0));
183
+ sock_write2(.uuid = ws->fd, .buffer = buff, .length = len);
184
+ } else {
185
+ if (((ws_s *)ws)->is_client) {
186
+ sock_write2(.uuid = ws->fd, .buffer = "\x89\x80mask", .length = 2,
187
+ .dealloc = SOCK_DEALLOC_NOOP);
188
+ } else {
189
+ sock_write2(.uuid = ws->fd, .buffer = "\x89\x00", .length = 2,
190
+ .dealloc = SOCK_DEALLOC_NOOP);
191
+ }
192
+ }
194
193
  }
195
- static void websocket_on_protocol_pong(void *udata, void *msg, uint64_t len) {
194
+ static void websocket_on_protocol_pong(void *ws_p, void *msg, uint64_t len) {
196
195
  (void)len;
197
196
  (void)msg;
198
- (void)udata;
197
+ (void)ws_p;
199
198
  }
200
- static void websocket_on_protocol_close(void *udata) {
201
- ws_s *ws = udata;
199
+ static void websocket_on_protocol_close(void *ws_p) {
200
+ ws_s *ws = ws_p;
202
201
  sock_close(ws->fd);
203
202
  }
204
- static void websocket_on_protocol_error(void *udata) {
205
- ws_s *ws = udata;
203
+ static void websocket_on_protocol_error(void *ws_p) {
204
+ ws_s *ws = ws_p;
206
205
  sock_close(ws->fd);
207
206
  }
208
207
 
@@ -214,8 +213,13 @@ The Websocket Protocol implementation
214
213
 
215
214
  static void ws_ping(intptr_t fd, protocol_s *ws) {
216
215
  (void)(ws);
217
- sock_write2(.uuid = fd, .buffer = "\x89\x00", .length = 2, .move = 1,
218
- .dealloc = SOCK_DEALLOC_NOOP);
216
+ if (((ws_s *)ws)->is_client) {
217
+ sock_write2(.uuid = fd, .buffer = "\x89\x80MASK", .length = 6,
218
+ .dealloc = SOCK_DEALLOC_NOOP);
219
+ } else {
220
+ sock_write2(.uuid = fd, .buffer = "\x89\x00", .length = 2,
221
+ .dealloc = SOCK_DEALLOC_NOOP);
222
+ }
219
223
  }
220
224
 
221
225
  static void on_close(intptr_t uuid, protocol_s *_ws) {
@@ -229,20 +233,19 @@ static void on_ready(intptr_t fduuid, protocol_s *ws) {
229
233
  ((ws_s *)ws)->on_ready((ws_s *)ws);
230
234
  }
231
235
 
232
- // static void on_open(intptr_t fd, protocol_s *ws, void *callback) {
233
- // (void)(fd);
234
- // if (callback && ws && ws->service == WEBSOCKET_ID_STR)
235
- // ((void (*)(void *))callback)(ws);
236
- // }
237
-
238
236
  static void on_shutdown(intptr_t fd, protocol_s *ws) {
239
237
  (void)(fd);
240
238
  if (ws && ((ws_s *)ws)->on_shutdown)
241
239
  ((ws_s *)ws)->on_shutdown((ws_s *)ws);
240
+ if (((ws_s *)ws)->is_client) {
241
+ sock_write2(.uuid = fd, .buffer = "\x8a\x80MASK", .length = 6,
242
+ .dealloc = SOCK_DEALLOC_NOOP);
243
+ } else {
244
+ sock_write2(.uuid = fd, .buffer = "\x8a\x00", .length = 2,
245
+ .dealloc = SOCK_DEALLOC_NOOP);
246
+ }
242
247
  }
243
248
 
244
- /************** new implementation */
245
-
246
249
  static void on_data(intptr_t sockfd, protocol_s *ws_) {
247
250
  ws_s *const ws = (ws_s *)ws_;
248
251
  if (ws == NULL || ws->protocol.service != WEBSOCKET_ID_STR)
@@ -257,7 +260,7 @@ static void on_data(intptr_t sockfd, protocol_s *ws_) {
257
260
  return;
258
261
  }
259
262
  /* test buffer capacity */
260
- if (raw_length >= ws->buffer.size) {
263
+ if (raw_length > ws->buffer.size) {
261
264
  ws->buffer.size = (size_t)raw_length;
262
265
  ws->buffer = resize_ws_buffer(ws, ws->buffer);
263
266
  if (!ws->buffer.data) {
@@ -272,10 +275,25 @@ static void on_data(intptr_t sockfd, protocol_s *ws_) {
272
275
  if (len <= 0) {
273
276
  return;
274
277
  }
275
- ws->length = websocket_consume(ws->buffer.data, ws->length + len, ws, 1);
278
+ ws->length = websocket_consume(ws->buffer.data, ws->length + len, ws,
279
+ (~(ws->is_client) & 1));
280
+
281
+ facil_force_event(sockfd, FIO_EVENT_ON_DATA);
282
+ }
283
+
284
+ static void on_data_first(intptr_t sockfd, protocol_s *ws_) {
285
+ ws_s *const ws = (ws_s *)ws_;
286
+ if (ws->on_open)
287
+ ws->on_open(ws);
288
+ ws->protocol.on_data = on_data;
289
+
290
+ if (ws->length)
291
+ ws->length = websocket_consume(ws->buffer.data, ws->length, ws,
292
+ (~(ws->is_client) & 1));
276
293
 
277
294
  facil_force_event(sockfd, FIO_EVENT_ON_DATA);
278
295
  }
296
+
279
297
  /* later */
280
298
  static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
281
299
  char first, char last, char client);
@@ -284,35 +302,86 @@ static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
284
302
  Create/Destroy the websocket object
285
303
  */
286
304
 
287
- static ws_s *new_websocket() {
305
+ static ws_s *new_websocket(intptr_t uuid) {
288
306
  // allocate the protocol object
289
307
  ws_s *ws = malloc(sizeof(*ws));
290
308
  *ws = (ws_s){
291
309
  .protocol.service = WEBSOCKET_ID_STR,
292
310
  .protocol.ping = ws_ping,
293
- .protocol.on_data = on_data,
311
+ .protocol.on_data = on_data_first,
294
312
  .protocol.on_close = on_close,
295
313
  .protocol.on_ready = on_ready,
296
314
  .protocol.on_shutdown = on_shutdown,
297
- .subscriptions = FIO_LIST_INIT_STATIC(ws->subscriptions),
315
+ .subscriptions = FIO_LS_INIT(ws->subscriptions),
298
316
  .is_client = 0,
317
+ .fd = uuid,
299
318
  };
300
319
  return ws;
301
320
  }
302
321
  static void destroy_ws(ws_s *ws) {
303
- clear_subscriptions(ws);
304
322
  if (ws->on_close)
305
- ws->on_close(ws);
323
+ ws->on_close(ws->fd, ws->udata);
306
324
  if (ws->msg)
307
325
  fiobj_free(ws->msg);
326
+ clear_subscriptions(ws);
308
327
  free_ws_buffer(ws, ws->buffer);
309
328
  free(ws);
310
329
  }
311
330
 
331
+ void websocket_attach(intptr_t uuid, http_settings_s *http_settings,
332
+ websocket_settings_s *args, void *data, size_t length) {
333
+ ws_s *ws = new_websocket(uuid);
334
+ if (!ws) {
335
+ perror("FATAL ERROR: couldn't allocate Websocket protocol object");
336
+ exit(errno);
337
+ }
338
+ // we have an active websocket connection - prep the connection buffer
339
+ ws->buffer = create_ws_buffer(ws);
340
+ // Setup ws callbacks
341
+ ws->on_open = args->on_open;
342
+ ws->on_close = args->on_close;
343
+ ws->on_message = args->on_message;
344
+ ws->on_ready = args->on_ready;
345
+ ws->on_shutdown = args->on_shutdown;
346
+ // setup any user data
347
+ ws->udata = args->udata;
348
+ if (http_settings) {
349
+ // client mode?
350
+ ws->is_client = http_settings->is_client;
351
+ // buffer limits
352
+ ws->max_msg_size = http_settings->ws_max_msg_size;
353
+ // update the timeout
354
+ facil_set_timeout(uuid, http_settings->ws_timeout);
355
+ } else {
356
+ ws->max_msg_size = (1024 * 256);
357
+ facil_set_timeout(uuid, 40);
358
+ }
359
+
360
+ if (data && length) {
361
+ if (length > ws->buffer.size) {
362
+ ws->buffer.size = length;
363
+ ws->buffer = resize_ws_buffer(ws, ws->buffer);
364
+ if (!ws->buffer.data) {
365
+ // no memory.
366
+ facil_attach(uuid, (protocol_s *)ws);
367
+ websocket_close(ws);
368
+ return;
369
+ }
370
+ }
371
+ memcpy(ws->buffer.data, data, length);
372
+ ws->length = length;
373
+ }
374
+ // update the protocol object, cleanning up the old one
375
+ facil_attach(uuid, (protocol_s *)ws);
376
+ // allow the on_open and on_data to take over the control.
377
+ facil_force_event(uuid, FIO_EVENT_ON_DATA);
378
+ }
379
+
312
380
  /*******************************************************************************
313
381
  Writing to the Websocket
314
382
  */
315
- #define WS_MAX_FRAME_SIZE 65532 // should be less then `unsigned short`
383
+ #define WS_MAX_FRAME_SIZE \
384
+ (FIO_MEMORY_BLOCK_ALLOC_LIMIT - 4096) // should be less then `unsigned short`
316
385
 
317
386
  // clang-format off
318
387
  #if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
@@ -344,25 +413,15 @@ Writing to the Websocket
344
413
  (((i)&0xFF000000000000ULL) >> 40) | (((i)&0xFF00000000000000ULL) >> 56))
345
414
  #endif
346
415
 
347
- static void websocket_write_impl(intptr_t fd, void *data, size_t len,
348
- char text, /* TODO: add client masking */
416
+ static void websocket_write_impl(intptr_t fd, void *data, size_t len, char text,
349
417
  char first, char last, char client) {
350
- if (len < (BUFFER_PACKET_SIZE - 16)) {
351
- sock_buffer_s *sbuff = sock_buffer_checkout();
352
-
353
- sbuff->len =
354
- (client ? websocket_client_wrap(sbuff->buf, data, len, (text ? 1 : 2),
355
- first, last, 0)
356
- : websocket_server_wrap(sbuff->buf, data, len, (text ? 1 : 2),
357
- first, last, 0));
358
- sock_buffer_send(fd, sbuff);
359
- } else if (len <= WS_MAX_FRAME_SIZE) {
360
- void *buff = malloc(len + 16);
418
+ if (len <= WS_MAX_FRAME_SIZE) {
419
+ void *buff = fio_malloc(len + 16);
361
420
  len = (client ? websocket_client_wrap(buff, data, len, (text ? 1 : 2),
362
421
  first, last, 0)
363
422
  : websocket_server_wrap(buff, data, len, (text ? 1 : 2),
364
423
  first, last, 0));
365
- sock_write2(.uuid = fd, .buffer = buff, .length = len, .move = 1);
424
+ sock_write2(.uuid = fd, .buffer = buff, .length = len, .dealloc = fio_free);
366
425
  } else {
367
426
  /* frame fragmentation is better for large data then large frames */
368
427
  while (len > WS_MAX_FRAME_SIZE) {
@@ -432,9 +491,8 @@ static const uint8_t utf8d[] = {
432
491
 
433
492
  static inline uint32_t validate_utf8(uint8_t *str, size_t len) {
434
493
  uint32_t state = 0;
435
- uint32_t type;
436
494
  while (len) {
437
- type = utf8d[*str];
495
+ uint32_t type = utf8d[*str];
438
496
  state = utf8d[256 + state * 16 + type];
439
497
  if (state == UTF8_REJECT)
440
498
  return 0;
@@ -461,7 +519,9 @@ static void websocket_on_unsubscribe(void *u1, void *u2) {
461
519
  free(d);
462
520
  }
463
521
 
464
- static void websocket_on_pubsub_message_direct(pubsub_message_s *msg) {
522
+ static inline void
523
+ websocket_on_pubsub_message_direct_internal(pubsub_message_s *msg,
524
+ uint8_t txt) {
465
525
  protocol_s *pr =
466
526
  facil_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
467
527
  if (!pr) {
@@ -470,37 +530,36 @@ static void websocket_on_pubsub_message_direct(pubsub_message_s *msg) {
470
530
  pubsub_defer(msg);
471
531
  return;
472
532
  }
473
- websocket_write((ws_s *)pr, msg->msg.data, msg->msg.len,
474
- msg->msg.len >= (2 << 14)
475
- ? 0
476
- : validate_utf8((uint8_t *)msg->msg.data, msg->msg.len));
533
+ FIOBJ message;
534
+ fio_cstr_s tmp;
535
+ if (FIOBJ_TYPE_IS(msg->message, FIOBJ_T_STRING)) {
536
+ message = fiobj_dup(msg->message);
537
+ tmp = fiobj_obj2cstr(message);
538
+ if (txt == 2) {
539
+ /* unknown text state */
540
+ txt =
541
+ (tmp.len >= (2 << 14) ? 0
542
+ : validate_utf8((uint8_t *)tmp.data, tmp.len));
543
+ }
544
+ } else {
545
+ message = fiobj_obj2json(msg->message, 0);
546
+ tmp = fiobj_obj2cstr(message);
547
+ }
548
+ websocket_write((ws_s *)pr, tmp.data, tmp.len, txt & 1);
477
549
  facil_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
550
+ fiobj_free(message);
551
+ }
552
+
553
+ static void websocket_on_pubsub_message_direct(pubsub_message_s *msg) {
554
+ websocket_on_pubsub_message_direct_internal(msg, 2);
478
555
  }
479
556
 
480
557
  static void websocket_on_pubsub_message_direct_txt(pubsub_message_s *msg) {
481
- protocol_s *pr =
482
- facil_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
483
- if (!pr) {
484
- if (errno == EBADF)
485
- return;
486
- pubsub_defer(msg);
487
- return;
488
- }
489
- websocket_write((ws_s *)pr, msg->msg.data, msg->msg.len, 1);
490
- facil_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
558
+ websocket_on_pubsub_message_direct_internal(msg, 1);
491
559
  }
492
560
 
493
561
  static void websocket_on_pubsub_message_direct_bin(pubsub_message_s *msg) {
494
- protocol_s *pr =
495
- facil_protocol_try_lock((intptr_t)msg->udata1, FIO_PR_LOCK_WRITE);
496
- if (!pr) {
497
- if (errno == EBADF)
498
- return;
499
- pubsub_defer(msg);
500
- return;
501
- }
502
- websocket_write((ws_s *)pr, msg->msg.data, msg->msg.len, 0);
503
- facil_protocol_unlock(pr, FIO_PR_LOCK_WRITE);
562
+ websocket_on_pubsub_message_direct_internal(msg, 0);
504
563
  }
505
564
 
506
565
  static void websocket_on_pubsub_message(pubsub_message_s *msg) {
@@ -517,11 +576,9 @@ static void websocket_on_pubsub_message(pubsub_message_s *msg) {
517
576
  if (d->on_message)
518
577
  d->on_message((websocket_pubsub_notification_s){
519
578
  .ws = (ws_s *)pr,
520
- .engine = (pubsub_engine_s *)msg->engine,
521
579
  .subscription_id = (intptr_t)msg->subscription,
522
- .channel = {.name = msg->channel.name, .len = msg->channel.len},
523
- .msg = {.data = msg->msg.data, .len = msg->msg.len},
524
- .use_pattern = msg->use_pattern,
580
+ .channel = msg->channel,
581
+ .message = msg->message,
525
582
  });
526
583
  facil_protocol_unlock(pr, FIO_PR_LOCK_TASK);
527
584
  }
@@ -531,17 +588,15 @@ static void websocket_on_pubsub_message(pubsub_message_s *msg) {
531
588
  */
532
589
  #undef websocket_subscribe
533
590
  uintptr_t websocket_subscribe(struct websocket_subscribe_s args) {
591
+ if (!args.ws)
592
+ goto error;
534
593
  websocket_sub_data_s *d = malloc(sizeof(*d));
535
594
  *d = (websocket_sub_data_s){.udata = args.udata,
536
595
  .on_message = args.on_message,
537
596
  .on_unsubscribe = args.on_unsubscribe};
597
+
538
598
  pubsub_sub_pt sub = pubsub_subscribe(
539
- .engine = args.engine,
540
- .channel =
541
- {
542
- .name = (char *)args.channel.name, .len = args.channel.len,
543
- },
544
- .use_pattern = args.use_pattern,
599
+ .channel = args.channel, .use_pattern = args.use_pattern,
545
600
  .on_unsubscribe = websocket_on_unsubscribe,
546
601
  .on_message =
547
602
  (args.on_message
@@ -556,8 +611,12 @@ uintptr_t websocket_subscribe(struct websocket_subscribe_s args) {
556
611
  free(d);
557
612
  return 0;
558
613
  }
559
- subscription_s *s = create_subscription(args.ws, sub);
560
- return (uintptr_t)s;
614
+ fio_ls_push(&args.ws->subscriptions, sub);
615
+ return (uintptr_t)args.ws->subscriptions.prev;
616
+ error:
617
+ if (args.on_unsubscribe)
618
+ args.on_unsubscribe(args.udata);
619
+ return 0;
561
620
  }
562
621
 
563
622
  /**
@@ -566,12 +625,8 @@ uintptr_t websocket_subscribe(struct websocket_subscribe_s args) {
566
625
  #undef websocket_find_sub
567
626
  uintptr_t websocket_find_sub(struct websocket_subscribe_s args) {
568
627
  pubsub_sub_pt sub = pubsub_find_sub(
569
- .engine = args.engine,
570
- .channel =
571
- {
572
- .name = (char *)args.channel.name, .len = args.channel.len,
573
- },
574
- .use_pattern = args.use_pattern,
628
+ .channel = args.channel, .use_pattern = args.use_pattern,
629
+ .on_unsubscribe = websocket_on_unsubscribe,
575
630
  .on_message =
576
631
  (args.on_message
577
632
  ? websocket_on_pubsub_message
@@ -583,10 +638,9 @@ uintptr_t websocket_find_sub(struct websocket_subscribe_s args) {
583
638
  .udata1 = (void *)args.ws->fd, .udata2 = args.udata);
584
639
  if (!sub)
585
640
  return 0;
586
- subscription_s *s;
587
- fio_list_for_each(subscription_s, node, s, args.ws->subscriptions) {
588
- if (s->sub == sub)
589
- return (uintptr_t)s;
641
+ FIO_LS_FOR(&args.ws->subscriptions, pos) {
642
+ if (pos->obj == sub)
643
+ return (uintptr_t)pos;
590
644
  }
591
645
  return 0;
592
646
  }
@@ -595,140 +649,15 @@ uintptr_t websocket_find_sub(struct websocket_subscribe_s args) {
595
649
  * Unsubscribes from a channel.
596
650
  */
597
651
  void websocket_unsubscribe(ws_s *ws, uintptr_t subscription_id) {
598
- subscription_s *s;
599
- fio_list_for_each(subscription_s, node, s, ws->subscriptions) {
600
- if (s == (subscription_s *)subscription_id) {
601
- pubsub_unsubscribe(s->sub);
602
- free_subscription(s);
603
- return;
604
- }
605
- }
652
+ pubsub_unsubscribe((pubsub_sub_pt)((fio_ls_s *)subscription_id)->obj);
653
+ fio_ls_remove((fio_ls_s *)subscription_id);
654
+ (void)ws;
606
655
  }
607
656
 
608
657
  /*******************************************************************************
609
658
  The API implementation
610
659
  */
611
660
 
612
- static void deferred_on_open(void *func, void *pr) {
613
- ((void (*)(ws_s *))func)(pr);
614
- facil_protocol_unlock(pr, FIO_PR_LOCK_TASK);
615
- }
616
-
617
- /** The upgrade */
618
- #undef websocket_upgrade
619
- ssize_t websocket_upgrade(websocket_settings_s settings) {
620
- // A static data used for all websocket connections.
621
- static char ws_key_accpt_str[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
622
- // require either a request or a response.
623
- if (((uintptr_t)settings.request | (uintptr_t)settings.response) ==
624
- (uintptr_t)NULL)
625
- return -1;
626
- if (settings.max_msg_size == 0)
627
- settings.max_msg_size = 262144; /** defaults to ~250KB */
628
- if (settings.timeout == 0)
629
- settings.timeout = 40; /* defaults to 40 seconds */
630
- // make sure we have a response object.
631
- http_response_s *response = settings.response;
632
- if (response == NULL) {
633
- /* initialize a default upgrade response */
634
- response = http_response_create(settings.request);
635
- } else
636
- settings.request = response->request;
637
- // allocate the protocol object (TODO: (maybe) pooling)
638
- ws_s *ws = new_websocket();
639
- if (!ws)
640
- goto refuse;
641
-
642
- // setup the socket-server data
643
- ws->fd = response->request->fd;
644
- // Setup ws callbacks
645
- ws->on_close = settings.on_close;
646
- ws->on_message = settings.on_message;
647
- ws->on_ready = settings.on_ready;
648
- ws->on_shutdown = settings.on_shutdown;
649
- // setup any user data
650
- ws->udata = settings.udata;
651
- // buffer limits
652
- ws->max_msg_size = settings.max_msg_size;
653
- const char *recv_str;
654
-
655
- recv_str =
656
- http_request_header_find(settings.request, "sec-websocket-version", 21)
657
- .value;
658
- if (recv_str == NULL || recv_str[0] != '1' || recv_str[1] != '3')
659
- goto refuse;
660
-
661
- http_header_s sec_h =
662
- http_request_header_find(settings.request, "sec-websocket-key", 17);
663
- if (sec_h.value == NULL)
664
- goto refuse;
665
-
666
- // websocket extentions (none)
667
-
668
- // the accept Base64 Hash - we need to compute this one and set it
669
- // the client's unique string
670
- // use the SHA1 methods provided to concat the client string and hash
671
- sha1_s sha1;
672
- sha1 = bscrypt_sha1_init();
673
- bscrypt_sha1_write(&sha1, sec_h.value, sec_h.value_len);
674
- bscrypt_sha1_write(&sha1, ws_key_accpt_str, sizeof(ws_key_accpt_str) - 1);
675
- // base encode the data
676
- char websockets_key[32];
677
- int len =
678
- bscrypt_base64_encode(websockets_key, bscrypt_sha1_result(&sha1), 20);
679
-
680
- // websocket extentions (none)
681
-
682
- // upgrade taking place, make sure the upgrade headers are valid for the
683
- // response.
684
- response->status = 101;
685
- http_response_write_header(response, .name = "Connection", .name_len = 10,
686
- .value = "Upgrade", .value_len = 7);
687
- http_response_write_header(response, .name = "Upgrade", .name_len = 7,
688
- .value = "websocket", .value_len = 9);
689
- http_response_write_header(response, .name = "sec-websocket-version",
690
- .name_len = 21, .value = "13", .value_len = 2);
691
- // set the string's length and encoding
692
- http_response_write_header(response, .name = "Sec-WebSocket-Accept",
693
- .name_len = 20, .value = websockets_key,
694
- .value_len = len);
695
- // // inform about 0 extension support
696
- // sec_h = http_request_header_find(settings.request,
697
- // "sec-websocket-extensions", 24);
698
- // if (recv_str != NULL)
699
- // http_response_write_header(response, .name =
700
- // "Sec-Websocket-Extensions",
701
- // .name_length = 24);
702
-
703
- goto cleanup;
704
- refuse:
705
- // set the negative response
706
- response->status = 400;
707
- cleanup:
708
- if (response->status == 101) {
709
- // update the protocol object, cleanning up the old one
710
- facil_attach_locked(ws->fd, (protocol_s *)ws);
711
- // send the response
712
- http_response_finish(response);
713
- // we have an active websocket connection - prep the connection buffer
714
- ws->buffer = create_ws_buffer(ws);
715
- // update the timeout
716
- facil_set_timeout(ws->fd, settings.timeout);
717
- // call the on_open callback
718
- if (settings.on_open) {
719
- defer(deferred_on_open, (void *)settings.on_open, ws);
720
- } else {
721
- facil_protocol_unlock(&ws->protocol, FIO_PR_LOCK_TASK);
722
- }
723
- return 0;
724
- }
725
- http_response_finish(response);
726
- destroy_ws(ws);
727
- return -1;
728
- }
729
- #define websocket_upgrade(...) \
730
- websocket_upgrade((websocket_settings_s){__VA_ARGS__})
731
-
732
661
  /** Returns the opaque user data associated with the websocket. */
733
662
  void *websocket_udata(ws_s *ws) { return ws->udata; }
734
663
  /** Returns the the process specific connection's UUID (see `libsock`). */
@@ -750,7 +679,7 @@ int websocket_write(ws_s *ws, void *data, size_t size, uint8_t is_text) {
750
679
  }
751
680
  /** Closes a websocket connection. */
752
681
  void websocket_close(ws_s *ws) {
753
- sock_write2(.uuid = ws->fd, .buffer = "\x88\x00", .length = 2, .move = 1,
682
+ sock_write2(.uuid = ws->fd, .buffer = "\x88\x00", .length = 2,
754
683
  .dealloc = SOCK_DEALLOC_NOOP);
755
684
  sock_close(ws->fd);
756
685
  return;
@@ -799,7 +728,8 @@ Performs a task on each websocket connection that shares the same process
799
728
  (except the originating `ws_s` connection which is allowed to be NULL).
800
729
  */
801
730
  #undef websocket_each
802
- void websocket_each(struct websocket_each_args_s args) {
731
+ void __attribute__((deprecated))
732
+ websocket_each(struct websocket_each_args_s args) {
803
733
  struct WSTask *tsk = malloc(sizeof(*tsk));
804
734
  tsk->arg = args.arg;
805
735
  tsk->on_finish = args.on_finish;
@@ -850,7 +780,7 @@ static void ws_reduce_or_free_multi_write(void *buff) {
850
780
  if (mw->on_finished) {
851
781
  facil_defer(.uuid = mw->origin, .task = ws_mw_defered_on_finish,
852
782
  .arg = mw, .fallback = ws_mw_defered_on_finish_fb,
853
- .task_type = FIO_PR_LOCK_WRITE);
783
+ .type = FIO_PR_LOCK_WRITE);
854
784
  } else
855
785
  free(mw);
856
786
  } else
@@ -871,7 +801,7 @@ static void ws_direct_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
871
801
  multi->count += 1;
872
802
  spn_unlock(&multi->lock);
873
803
  sock_write2(.uuid = fd, .buffer = multi->buffer, .length = multi->length,
874
- .dealloc = ws_reduce_or_free_multi_write, .move = 1);
804
+ .dealloc = ws_reduce_or_free_multi_write);
875
805
  }
876
806
 
877
807
  static void ws_check_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
@@ -883,7 +813,8 @@ static void ws_check_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
883
813
  }
884
814
 
885
815
  #undef websocket_write_each
886
- int websocket_write_each(struct websocket_write_each_args_s args) {
816
+ int __attribute__((deprecated))
817
+ websocket_write_each(struct websocket_write_each_args_s args) {
887
818
  if (!args.data || !args.length)
888
819
  return -1;
889
820
  struct websocket_multi_write *multi =