iodine 0.2.12 → 0.2.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f452e7b373f6e571cfde2d15c813a2b56b68b7ec
4
- data.tar.gz: ae82d8b0e5a8e89a341e94ad8fa64f4bdcbe2bab
3
+ metadata.gz: dc27a8990d89bbb19294133f6ec8178d2a586ea2
4
+ data.tar.gz: 8e9ec06545307abd666902d434438679e5bdd7ad
5
5
  SHA512:
6
- metadata.gz: e10f6de46108d5282babbbbde57f24889def07db0807cdc5674257881c6082ec219107d44f7c25c1523bb9365386ae5b188129ef4cf410d49c9ab62dacb255bf
7
- data.tar.gz: 5f9dddd11aaad923cedb1151902fc92398e863a281360873642594dd77dd6fbb844f9c1498ff0a6d622cc69de6b857b370b7eab35c2de672f51e6acd47098fc5
6
+ metadata.gz: a25fe9b7584dbeba407d55c1f150fdfab048fb7892a46ec32777d55b38ab3742faf0f7a972c118d3d6f26804380825324ff6f4b2665f91c56b0cb44f7f6c5ae6
7
+ data.tar.gz: b0e1ca2d60baae702e1f953cd6b9e13566168ae4fe989e1d31e6e0fd37cbbfc8bf3c3dfcf1e4e83049e09c45ca369c9efd08779965562b17cd576308925573e7
@@ -5,7 +5,7 @@ os:
5
5
  before_install:
6
6
  - gem install bundler -v 1.10.6
7
7
  rvm:
8
- - ruby-2.4.0-rc1
8
+ - 2.4.0
9
9
  - 2.3.1
10
10
  - 2.3.0
11
11
  - 2.2.4
@@ -8,9 +8,17 @@ Please notice that this change log contains changes for upcoming releases as wel
8
8
 
9
9
  ***
10
10
 
11
+ Change log v.0.2.13
12
+
13
+ **Fix**: Fixed an issue presented in the C layer, where big fragmented websocket messages sent by the client could cause parsing errors and potentially, in some cases, cause a server thread to spin in a loop (DoS). Credit to @Filly for exposing the issue in the [`facil.io`](https://github.com/boazsegev/facil.io) layer. It should be noted that Chrome is the only browser where this issue could be invoked for testing.
14
+
15
+ **Credit**: credit to Elia Schito (@elia) and Augusts Bautra (@Epigene) for fixing parts of the documentation (PR #11 , #12).
16
+
17
+ ***
18
+
11
19
  Change log v.0.2.12
12
20
 
13
- **Fix** removed `mempool` after it failed some stress and concurrency tests.
21
+ **Fix**: removed `mempool` after it failed some stress and concurrency tests.
14
22
 
15
23
  ***
16
24
 
data/README.md CHANGED
@@ -45,9 +45,10 @@ Puma's model of 16 threads and 4 processes is easily adopted and proved to provi
45
45
  bundler exec iodine -p $PORT -t 16 -w 4
46
46
  ```
47
47
 
48
- It should be noted that automatic process scaling will cause issues with Websocket broadcast (`each`) support, ssince the `Websocket#each` method will be limited to the calling process (other clients might be connected to a different process.
48
+ It should be noted that automatic process scaling will cause issues with Websocket broadcast (`each`) support, since the `Websocket#each` method will be limited to the calling process (other clients might be connected to a different process).
49
49
 
50
- I recommended that you consider using Redis to scale Websocket "events" across processes / machines. look into [plezi.io](http://www.plezi.io) for automatic Websocket scaling with Redis and Iodine.
50
+ It is recommended that you consider using Redis to scale Websocket "events" across processes / machines.
51
+ Look into [plezi.io](http://www.plezi.io) for automatic Websocket scaling with Redis and Iodine.
51
52
 
52
53
  ### Writing data to the network layer
53
54
 
@@ -55,7 +56,7 @@ Iodine allows Ruby to write strings to the network layer. This includes HTTP and
55
56
 
56
57
  Iodine will handle an internal buffer (~4 to ~16 Mb, version depending) so that `write` can return immediately (non-blocking).
57
58
 
58
- However, when the buffer is full, `write` will block until enough space in he buffer becomes available. Sending up to 16Kb of data (a single buffer "packet") is optimal. Sending a larger response might effect concurrency. Best Websocket response length is ~1Kb (1 TCP / IP packet) and allows for faster transmissions.
59
+ However, when the buffer is full, `write` will block until enough space in the buffer becomes available. Sending up to 16Kb of data (a single buffer "packet") is optimal. Sending a larger response might effect concurrency. Best Websocket response length is ~1Kb (1 TCP / IP packet) and allows for faster transmissions.
59
60
 
60
61
  When using the Iodine's web server (`Iodine::Rack`), the static file service offered by Iodine streams files (instead of using the buffer). Every file response will require up to 2 buffer "packets" (~32Kb), one for the header and the other for file streaming.
61
62
 
@@ -155,7 +156,7 @@ Iodine.start
155
156
 
156
157
  #### TCP/IP (raw) sockets
157
158
 
158
- Upgrading to a custom protocol (i.e., in order to implement your own Websocket protocol with special extensions) is performed almost the ame way, using `env['upgrade.tcp']`. In the following (terminal) example, we'll use an echo server without (direct socket echo):
159
+ Upgrading to a custom protocol (i.e., in order to implement your own Websocket protocol with special extensions) is performed almost the same way, using `env['upgrade.tcp']`. In the following (terminal) example, we'll use an echo server without direct socket echo:
159
160
 
160
161
  ```ruby
161
162
  require 'iodine'
@@ -180,7 +181,7 @@ Iodine.start
180
181
 
181
182
  #### A few notes
182
183
 
183
- This design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems) etc'. This also allows us to use middleware without interfering with connection upgrades and provides up with backwards compatibility.
184
+ This design has a number of benefits, some of them related to better IO handling, resource optimization (no need for two IO polling systems), etc. This also allows us to use middleware without interfering with connection upgrades and provides backwards compatibility.
184
185
 
185
186
  Iodine::Rack imposes a few restrictions for performance and security reasons, such as that the headers (both sending and receiving) must be less than 8Kb in size. These restrictions shouldn't be an issue and are similar to limitations imposed by Apache.
186
187
 
@@ -270,7 +271,7 @@ Review the `iodine -?` help for more data.
270
271
 
271
272
  Remember to compare the memory footprint after running some requests - it's not just speed that C is helping with, it's also memory management and object pooling (i.e., Iodine uses a buffer packet pool management).
272
273
 
273
- ## Can I try before before I buy?
274
+ ## Can I try before I buy?
274
275
 
275
276
  Well, it is **free** and **open source**, no need to buy.. and of course you can try it out.
276
277
 
@@ -280,9 +281,9 @@ It's installable just like any other gem on MRI, run:
280
281
  $ gem install iodine
281
282
  ```
282
283
 
283
- If building the native C extension fails, please notice that some Ruby installations, such as on Ubuntu, require that you separately install the development headers (`ruby.h` and friends). I have no idea why they do that, as you will need the development headers for any native gems you want to install - so hurry up and get it.
284
+ If building the native C extension fails, please note that some Ruby installations, such as on Ubuntu, require that you separately install the development headers (`ruby.h` and friends). I have no idea why they do that, as you will need the development headers for any native gems you want to install - so hurry up and get them.
284
285
 
285
- If you have the development headers but still can't compile the Iodine extension, [open an issue](https://github.com/boazsegev/iodine/issues) with any messages you're getting and I be happy to look into it.
286
+ If you have the development headers but still can't compile the Iodine extension, [open an issue](https://github.com/boazsegev/iodine/issues) with any messages you're getting and I'll be happy to look into it.
286
287
 
287
288
  ## Mr. Sandman, write me a server
288
289
 
@@ -370,7 +371,7 @@ Here's a few things you can use from this project and they seem to be handy to h
370
371
 
371
372
  Some people use global Ruby arrays, adding and removing Ruby objects to the array, but that sounds like a performance hog to me.
372
373
 
373
- This one is a simple binary tree with a Ruby GC callback. Remember to initialize the Registry (`Registry.init(owner)`) so it's "owned" by some Roby-land object, this allows it to bridge the two worlds for the GC's mark and sweep.
374
+ This one is a simple binary tree with a Ruby GC callback. Remember to initialize the Registry (`Registry.init(owner)`) so it's "owned" by some Ruby-land object, this allows it to bridge the two worlds for the GC's mark and sweep.
374
375
 
375
376
  I'm attaching it to one of Iodine's library classes, just in-case someone adopts my code and decides the registry should be owned by the global Object class.
376
377
 
@@ -111,3 +111,5 @@ Iodine.start
111
111
  # ws.onclose = function(e) {console.log("closed")};
112
112
  # ws.onopen = function(e) {e.target.send("hi");};
113
113
  # };
114
+ # ws = new WebSocket("ws://localhost:3000"); ws.onmessage = function(e) {console.log("Got message " + e.data.length + " bytes long"); for(var i = 0; i < e.data.length ; i += 4) {if (e.data.slice(i, i+4) != "text") {console.log( "incoming message corrupted? ", e.data.slice(i, i+4) ); return;};}; }; ws.onclose = function(e) {console.log("closed")}; ws.onopen = function(e) {ws.send("hi");};
115
+ # str = "text"; function size_test() { if(ws.readyState != 1) return; if(str.length > 4194304) {console.log("str reached 4MiB!"); str = "test"; return;}; ws.send(str); str = str + str; window.setTimeout(size_test, 150); }; size_test();
@@ -41,7 +41,7 @@ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s);
41
41
  void free_ws_buffer(ws_s *owner, struct buffer_s);
42
42
 
43
43
  /** Sets the initial buffer size. (4Kb)*/
44
- #define WS_INITIAL_BUFFER_SIZE 4096
44
+ #define WS_INITIAL_BUFFER_SIZE 4096UL
45
45
 
46
46
  /*******************************************************************************
47
47
  Buffer management - simple implementation...
@@ -65,6 +65,7 @@ struct buffer_s resize_ws_buffer(ws_s *owner, struct buffer_s buff) {
65
65
  void *tmp = realloc(buff.data, buff.size);
66
66
  if (!tmp) {
67
67
  free_ws_buffer(owner, buff);
68
+ buff.data = NULL;
68
69
  buff.size = 0;
69
70
  }
70
71
  buff.data = tmp;
@@ -110,12 +111,11 @@ struct Websocket {
110
111
  struct {
111
112
  union {
112
113
  unsigned len1 : 16;
113
- unsigned long len2 : 64;
114
+ unsigned long long len2 : 64;
114
115
  char bytes[8];
115
116
  } psize;
116
117
  size_t length;
117
118
  size_t received;
118
- int data_len;
119
119
  char mask[4];
120
120
  struct {
121
121
  unsigned op_code : 4;
@@ -133,8 +133,9 @@ struct Websocket {
133
133
  unsigned at_mask : 2;
134
134
  unsigned has_len : 1;
135
135
  unsigned at_len : 3;
136
- unsigned client : 1;
136
+ unsigned has_head : 1;
137
137
  } state;
138
+ unsigned client : 1;
138
139
  } parser;
139
140
  };
140
141
 
@@ -146,9 +147,10 @@ char *WEBSOCKET_ID_STR = "websockets";
146
147
  /**
147
148
  A thread localized buffer used for reading and parsing data from the socket.
148
149
  */
150
+ #define WEBSOCKET_READ_MAX 4096
149
151
  static __thread struct {
150
152
  int pos;
151
- char buffer[1024];
153
+ char buffer[WEBSOCKET_READ_MAX];
152
154
  } read_buffer;
153
155
 
154
156
  /*******************************************************************************
@@ -200,12 +202,15 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
200
202
  if (ws == NULL || ws->protocol.service != WEBSOCKET_ID_STR)
201
203
  return;
202
204
  ssize_t len = 0;
203
- while ((len = sock_read(sockfd, read_buffer.buffer, 1024)) > 0) {
204
- ws->parser.data_len = 0;
205
+ ssize_t data_len = 0;
206
+ while ((len = sock_read(sockfd, read_buffer.buffer, WEBSOCKET_READ_MAX)) >
207
+ 0) {
208
+ data_len = 0;
205
209
  read_buffer.pos = 0;
206
210
  while (read_buffer.pos < len) {
207
211
  // collect the frame's head
208
- if (!(*(char *)(&ws->parser.head))) {
212
+ if (!ws->parser.state.has_head) {
213
+ ws->parser.state.has_head = 1;
209
214
  *((char *)(&(ws->parser.head))) = read_buffer.buffer[read_buffer.pos];
210
215
  // save a copy if it's the first head in a fragmented message
211
216
  if (!(*(char *)(&ws->parser.head2))) {
@@ -218,18 +223,24 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
218
223
  }
219
224
 
220
225
  // save the mask and size information
221
- if (!(*(char *)(&ws->parser.sdata))) {
226
+ if (!ws->parser.state.at_len && !ws->parser.state.has_len) {
227
+ // uint8_t tmp = ws->parser.sdata.masked;
222
228
  *((char *)(&(ws->parser.sdata))) = read_buffer.buffer[read_buffer.pos];
229
+ // ws->parser.sdata.masked |= tmp;
223
230
  // set length
224
- ws->parser.state.at_len = ws->parser.sdata.size == 127
225
- ? 7
226
- : ws->parser.sdata.size == 126 ? 1 : 0;
231
+ ws->parser.state.at_len = (ws->parser.sdata.size == 127
232
+ ? 7
233
+ : ws->parser.sdata.size == 126 ? 1 : 0);
234
+ if (!ws->parser.state.at_len) {
235
+ ws->parser.length = ws->parser.sdata.size;
236
+ ws->parser.state.has_len = 1;
237
+ }
227
238
  read_buffer.pos++;
228
239
  continue;
229
240
  }
230
241
 
231
242
  // check that if we need to collect the length data
232
- if (ws->parser.sdata.size >= 126 && !(ws->parser.state.has_len)) {
243
+ if (!ws->parser.state.has_len) {
233
244
  // avoiding a loop so we don't mixup the meaning of "continue" and
234
245
  // "break"
235
246
  collect_len:
@@ -264,10 +275,16 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
264
275
  goto collect_len;
265
276
  }
266
277
  #endif
278
+ // check message size limit
279
+ if (ws->max_msg_size <
280
+ ws->length + (ws->parser.length - ws->parser.received)) {
281
+ // close connection!
282
+ fprintf(stderr, "ERROR Websocket: Payload too big, review limits.\n");
283
+ sock_close(sockfd);
284
+ return;
285
+ }
267
286
  continue;
268
- } else if (!ws->parser.length && ws->parser.sdata.size < 126)
269
- // we should have the length data in the head
270
- ws->parser.length = ws->parser.sdata.size;
287
+ }
271
288
 
272
289
  // check that the data is masked and that we didn't colleced the mask yet
273
290
  if (ws->parser.sdata.masked && !(ws->parser.state.has_mask)) {
@@ -294,35 +311,29 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
294
311
  // Now that we know everything about the frame, let's collect the data
295
312
 
296
313
  // How much data in the buffer is part of the frame?
297
- ws->parser.data_len = len - read_buffer.pos;
298
- if (ws->parser.data_len + ws->parser.received > ws->parser.length)
299
- ws->parser.data_len = ws->parser.length - ws->parser.received;
314
+ data_len = len - read_buffer.pos;
315
+ if (data_len + ws->parser.received > ws->parser.length)
316
+ data_len = ws->parser.length - ws->parser.received;
300
317
 
301
318
  // a note about unmasking: since ws->parser.state.at_mask is only 2 bits,
302
319
  // it will wrap around (i.e. 3++ == 0), so no modulus is required :-)
303
320
  // unmask:
304
321
  if (ws->parser.sdata.masked) {
305
- for (int i = 0; i < ws->parser.data_len; i++) {
322
+ for (int i = 0; i < data_len; i++) {
306
323
  read_buffer.buffer[i + read_buffer.pos] ^=
307
324
  ws->parser.mask[ws->parser.state.at_mask++];
308
325
  }
309
- } else if (ws->parser.state.client == 0) {
326
+ } else if (ws->parser.client == 0) {
310
327
  // enforce masking unless acting as client, also for security reasons...
311
328
  fprintf(stderr, "ERROR Websockets: unmasked frame, disconnecting.\n");
312
329
  sock_close(sockfd);
313
330
  return;
314
331
  }
315
332
  // Copy the data to the Websocket buffer - only if it's a user message
316
- if (ws->parser.head.op_code == 1 || ws->parser.head.op_code == 2 ||
317
- (!ws->parser.head.op_code &&
318
- (ws->parser.head2.op_code == 1 || ws->parser.head2.op_code == 2))) {
319
- // check message size limit
320
- if (ws->max_msg_size < ws->length + ws->parser.data_len) {
321
- // close connection!
322
- fprintf(stderr, "ERROR Websocket: Payload too big, review limits.\n");
323
- sock_close(sockfd);
324
- return;
325
- }
333
+ if (data_len &&
334
+ (ws->parser.head.op_code == 1 || ws->parser.head.op_code == 2 ||
335
+ (!ws->parser.head.op_code && (ws->parser.head2.op_code == 1 ||
336
+ ws->parser.head2.op_code == 2)))) {
326
337
  // review and resize the buffer's capacity - it can only grow.
327
338
  if (ws->length + ws->parser.length - ws->parser.received >
328
339
  ws->buffer.size) {
@@ -335,42 +346,37 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
335
346
  }
336
347
  // copy here
337
348
  memcpy((uint8_t *)ws->buffer.data + ws->length,
338
- read_buffer.buffer + read_buffer.pos, ws->parser.data_len);
339
- ws->length += ws->parser.data_len;
349
+ read_buffer.buffer + read_buffer.pos, data_len);
350
+ ws->length += data_len;
340
351
  }
341
352
  // set the frame's data received so far (copied or not)
342
- // we couldn't do it soonet, because we needed the value to compute the
343
- // Ruby buffer capacity (within the GVL resize function).
344
- ws->parser.received += ws->parser.data_len;
353
+ ws->parser.received += data_len;
345
354
 
346
355
  // check that we have collected the whole of the frame.
347
356
  if (ws->parser.length > ws->parser.received) {
348
- read_buffer.pos += ws->parser.data_len;
357
+ read_buffer.pos += data_len;
358
+ // fprintf(stderr, "%p websocket has %lu out of %lu\n", (void *)ws,
359
+ // ws->parser.received, ws->parser.length);
349
360
  continue;
350
361
  }
351
362
 
352
363
  // we have the whole frame, time to process the data.
353
- // pings, pongs and other non-Ruby handled messages.
364
+ // pings, pongs and other non-user messages are handled independently.
354
365
  if (ws->parser.head.op_code == 0 || ws->parser.head.op_code == 1 ||
355
366
  ws->parser.head.op_code == 2) {
356
- /* a user data frame */
357
- if (ws->parser.head.fin) {
358
- /* This was the last frame */
359
- if (ws->parser.head2.op_code == 1) {
360
- /* text data */
361
- } else if (ws->parser.head2.op_code == 2) {
362
- /* binary data */
363
- } else // not a recognized frame, don't act
364
- goto reset_parser;
365
- // call the on_message callback
366
-
367
- if (ws->on_message)
368
- ws->on_message(ws, ws->buffer.data, ws->length,
369
- ws->parser.head2.op_code == 1);
370
- goto reset_parser;
367
+ /* a user data frame - make sure we got the `fin` flag, or an error
368
+ * occured */
369
+ if (!ws->parser.head.fin) {
370
+ /* This frame was a partial message. */
371
+ goto reset_state;
371
372
  }
373
+ /* This was the last frame */
374
+ if (ws->on_message) /* call the on_message callback */
375
+ ws->on_message(ws, ws->buffer.data, ws->length,
376
+ ws->parser.head2.op_code == 1);
377
+ goto reset_parser;
372
378
  } else if (ws->parser.head.op_code == 8) {
373
- /* close */
379
+ /* op-code == close */
374
380
  websocket_close(ws);
375
381
  if (ws->parser.head2.op_code == ws->parser.head.op_code)
376
382
  goto reset_parser;
@@ -378,8 +384,7 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
378
384
  /* ping */
379
385
  // write Pong - including ping data...
380
386
  websocket_write_impl(sockfd, read_buffer.buffer + read_buffer.pos,
381
- ws->parser.data_len, 10, 1, 1,
382
- ws->parser.state.client);
387
+ data_len, 10, 1, 1, ws->parser.client);
383
388
  if (ws->parser.head2.op_code == ws->parser.head.op_code)
384
389
  goto reset_parser;
385
390
  } else if (ws->parser.head.op_code == 10) {
@@ -393,26 +398,34 @@ static void on_data(intptr_t sockfd, protocol_s *_ws) {
393
398
  goto reset_parser;
394
399
  } else {
395
400
  /* WTF? */
396
- if (ws->parser.head.fin)
397
- goto reset_parser;
401
+ // fprintf(stderr, "%p websocket reached a WTF?! state..."
402
+ // "op1: %i , op2: %i\n",
403
+ // (void *)ws, ws->parser.head.op_code,
404
+ // ws->parser.head2.op_code);
405
+ fprintf(stderr, "ERROR Websockets: protocol error, disconnecting.\n");
406
+ sock_close(sockfd);
407
+ return;
398
408
  }
399
- // not done, but move the pos marker along
400
- read_buffer.pos += ws->parser.data_len;
401
- continue;
402
409
 
403
410
  reset_parser:
411
+ ws->length = 0;
412
+ // clear the parser's multi-frame state
413
+ *((char *)(&(ws->parser.head2))) = 0;
414
+ ws->parser.sdata.masked = 0;
415
+ reset_state:
404
416
  // move the pos marker along - in case we have more then one frame in the
405
417
  // buffer
406
- read_buffer.pos += ws->parser.data_len;
407
- // clear the parser
408
- *((char *)(&(ws->parser.state))) = *((char *)(&(ws->parser.sdata))) =
409
- *((char *)(&(ws->parser.head2))) = *((char *)(&(ws->parser.head))) =
410
- 0;
411
- // // // the above should be the same as... but it isn't
412
- // *((uint32_t*)(&(ws->parser.head))) = 0;
413
- // set the union size to 0
414
- ws->length = ws->parser.received = ws->parser.length =
415
- ws->parser.psize.len2 = ws->parser.data_len = 0;
418
+ read_buffer.pos += data_len;
419
+ // reset parser state
420
+ ws->parser.state.has_len = 0;
421
+ ws->parser.state.at_len = 0;
422
+ ws->parser.state.has_mask = 0;
423
+ ws->parser.state.at_mask = 0;
424
+ ws->parser.state.has_head = 0;
425
+ ws->parser.sdata.size = 0;
426
+ *((char *)(&(ws->parser.head))) = 0;
427
+ ws->parser.received = ws->parser.length = ws->parser.psize.len2 =
428
+ data_len = 0;
416
429
  }
417
430
  }
418
431
  #undef ws
@@ -733,8 +746,7 @@ void *websocket_set_udata(ws_s *ws, void *udata) {
733
746
  /** Writes data to the websocket. Returns -1 on failure (0 on success). */
734
747
  int websocket_write(ws_s *ws, void *data, size_t size, uint8_t is_text) {
735
748
  if (sock_isvalid(ws->fd)) {
736
- websocket_write_impl(ws->fd, data, size, is_text, 1, 1,
737
- ws->parser.state.client);
749
+ websocket_write_impl(ws->fd, data, size, is_text, 1, 1, ws->parser.client);
738
750
  return 0;
739
751
  }
740
752
  return -1;
@@ -809,6 +821,7 @@ struct websocket_multi_write {
809
821
  spn_lock_i lock;
810
822
  size_t count;
811
823
  size_t length;
824
+ uint8_t as_client;
812
825
  uint8_t buffer[];
813
826
  };
814
827
 
@@ -832,7 +845,8 @@ static void ws_finish_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
832
845
 
833
846
  static void ws_direct_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
834
847
  struct websocket_multi_write *multi = arg;
835
- (void)(_ws);
848
+ if (((ws_s *)(_ws))->parser.client != multi->as_client)
849
+ return;
836
850
 
837
851
  sock_packet_s *packet = sock_checkout_packet();
838
852
  *packet = (sock_packet_s){
@@ -852,6 +866,8 @@ static void ws_direct_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
852
866
 
853
867
  static void ws_check_multi_write(intptr_t fd, protocol_s *_ws, void *arg) {
854
868
  struct websocket_multi_write *multi = arg;
869
+ if (((ws_s *)(_ws))->parser.client != multi->as_client)
870
+ return;
855
871
  if (multi->if_callback((void *)_ws, multi->arg))
856
872
  ws_direct_multi_write(fd, _ws, arg);
857
873
  }
@@ -868,6 +884,7 @@ void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
868
884
  multi->arg = arg;
869
885
  multi->lock = SPN_LOCK_INIT;
870
886
  multi->count = 1;
887
+ multi->as_client = as_client;
871
888
  server_each((ws_originator ? ws_originator->fd : -1), WEBSOCKET_ID_STR,
872
889
  (if_callback ? ws_check_multi_write : ws_direct_multi_write),
873
890
  multi, ws_finish_multi_write);
@@ -136,9 +136,11 @@ If an `if_callback` is provided, the data will be written to the connection only
136
136
  if the `if_callback` returns TRUE (a non zero value).
137
137
 
138
138
  The `as_client` is a boolean value indicating if the data should be masked (sent
139
- to a server, in client mode) or not. If some data should be sent in client mode
140
- and other in server mode, consider using the `if_callback` to make sure the data
141
- is encoded properly.
139
+ to a server, in client mode) or not. The data will only be sent to the
140
+ connections matching the required state (i.e., if `as_client == 1`, the data
141
+ will only be sent to connections where this process behaves as a websocket
142
+ client). If some data should be sent in client mode and other in server mode,
143
+ than the function must be called twice.
142
144
  */
143
145
  void websocket_write_each(ws_s *ws_originator, void *data, size_t len,
144
146
  uint8_t is_text, uint8_t as_client,
@@ -36,8 +36,8 @@ Gem::Specification.new do |spec|
36
36
 
37
37
  spec.requirements << 'A Unix based system: Linux / macOS / BSD.'
38
38
  spec.requirements << 'An updated C compiler.'
39
- spec.requirements << 'Ruby >= 2.2.2'
40
- spec.requirements << 'Ruby >= 2.3.0 is recommended.'
39
+ spec.requirements << 'Ruby >= 2.2.2 required for Rack.'
40
+ spec.requirements << 'Ruby >= 2.3.0 recommended.'
41
41
 
42
42
  spec.add_development_dependency 'bundler', '~> 1.10'
43
43
  spec.add_development_dependency 'rake', '~> 12.0'
@@ -1,3 +1,3 @@
1
1
  module Iodine
2
- VERSION = '0.2.12'.freeze
2
+ VERSION = '0.2.13'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iodine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.2.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boaz Segev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-02-21 00:00:00.000000000 Z
11
+ date: 2017-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -199,8 +199,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
199
199
  requirements:
200
200
  - 'A Unix based system: Linux / macOS / BSD.'
201
201
  - An updated C compiler.
202
- - Ruby >= 2.2.2
203
- - Ruby >= 2.3.0 is recommended.
202
+ - Ruby >= 2.2.2 required for Rack.
203
+ - Ruby >= 2.3.0 recommended.
204
204
  rubyforge_project:
205
205
  rubygems_version: 2.6.8
206
206
  signing_key: