iodine 0.2.12 → 0.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of iodine might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/.travis.yml +1 -1
 - data/CHANGELOG.md +9 -1
 - data/README.md +10 -9
 - data/bin/ws-echo +2 -0
 - data/ext/iodine/websockets.c +91 -74
 - data/ext/iodine/websockets.h +5 -3
 - data/iodine.gemspec +2 -2
 - data/lib/iodine/version.rb +1 -1
 - metadata +4 -4
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: dc27a8990d89bbb19294133f6ec8178d2a586ea2
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 8e9ec06545307abd666902d434438679e5bdd7ad
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: a25fe9b7584dbeba407d55c1f150fdfab048fb7892a46ec32777d55b38ab3742faf0f7a972c118d3d6f26804380825324ff6f4b2665f91c56b0cb44f7f6c5ae6
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: b0e1ca2d60baae702e1f953cd6b9e13566168ae4fe989e1d31e6e0fd37cbbfc8bf3c3dfcf1e4e83049e09c45ca369c9efd08779965562b17cd576308925573e7
         
     | 
    
        data/.travis.yml
    CHANGED
    
    
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -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 
     | 
| 
      
 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,  
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
      
 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  
     | 
| 
      
 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  
     | 
| 
      
 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 
     | 
| 
      
 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  
     | 
| 
      
 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  
     | 
| 
      
 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  
     | 
| 
      
 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 
     | 
    
         | 
    
        data/bin/ws-echo
    CHANGED
    
    | 
         @@ -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();
         
     | 
    
        data/ext/iodine/websockets.c
    CHANGED
    
    | 
         @@ -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  
     | 
| 
      
 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  
     | 
| 
      
 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[ 
     | 
| 
      
 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 
     | 
    
         
            -
               
     | 
| 
       204 
     | 
    
         
            -
             
     | 
| 
      
 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 (! 
     | 
| 
      
 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 (! 
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
       226 
     | 
    
         
            -
             
     | 
| 
      
 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 ( 
     | 
| 
      
 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 
     | 
    
         
            -
                  } 
     | 
| 
       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 
     | 
    
         
            -
                   
     | 
| 
       298 
     | 
    
         
            -
                  if ( 
     | 
| 
       299 
     | 
    
         
            -
                     
     | 
| 
      
 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 <  
     | 
| 
      
 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. 
     | 
| 
      
 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 ( 
     | 
| 
       317 
     | 
    
         
            -
                      ( 
     | 
| 
       318 
     | 
    
         
            -
                       (ws->parser. 
     | 
| 
       319 
     | 
    
         
            -
             
     | 
| 
       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,  
     | 
| 
       339 
     | 
    
         
            -
                    ws->length +=  
     | 
| 
      
 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 
     | 
    
         
            -
                   
     | 
| 
       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 +=  
     | 
| 
      
 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- 
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
       358 
     | 
    
         
            -
             
     | 
| 
       359 
     | 
    
         
            -
                       
     | 
| 
       360 
     | 
    
         
            -
             
     | 
| 
       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 
     | 
    
         
            -
                                          
     | 
| 
       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 
     | 
    
         
            -
                     
     | 
| 
       397 
     | 
    
         
            -
             
     | 
| 
      
 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 +=  
     | 
| 
       407 
     | 
    
         
            -
                  //  
     | 
| 
       408 
     | 
    
         
            -
                   
     | 
| 
       409 
     | 
    
         
            -
             
     | 
| 
       410 
     | 
    
         
            -
             
     | 
| 
       411 
     | 
    
         
            -
                   
     | 
| 
       412 
     | 
    
         
            -
                   
     | 
| 
       413 
     | 
    
         
            -
                   
     | 
| 
       414 
     | 
    
         
            -
                   
     | 
| 
       415 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
              ( 
     | 
| 
      
 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);
         
     | 
    
        data/ext/iodine/websockets.h
    CHANGED
    
    | 
         @@ -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.  
     | 
| 
       140 
     | 
    
         
            -
             
     | 
| 
       141 
     | 
    
         
            -
             
     | 
| 
      
 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,
         
     | 
    
        data/iodine.gemspec
    CHANGED
    
    | 
         @@ -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  
     | 
| 
      
 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'
         
     | 
    
        data/lib/iodine/version.rb
    CHANGED
    
    
    
        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. 
     | 
| 
      
 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- 
     | 
| 
      
 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  
     | 
| 
      
 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: 
         
     |