iodine 0.4.8 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of iodine might be problematic. Click here for more details.

Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +26 -0
  4. data/README.md +18 -12
  5. data/SPEC-Websocket-Draft.md +9 -5
  6. data/bin/ws-echo +3 -0
  7. data/examples/config.ru +0 -1
  8. data/examples/echo.ru +3 -1
  9. data/examples/redis.ru +0 -1
  10. data/ext/iodine/base64.c +97 -105
  11. data/ext/iodine/defer.c +16 -1
  12. data/ext/iodine/defer.h +10 -0
  13. data/ext/iodine/evio.c +35 -13
  14. data/ext/iodine/extconf.rb +1 -1
  15. data/ext/iodine/facil.c +12 -1
  16. data/ext/iodine/facil.h +3 -1
  17. data/ext/iodine/fio2resp.c +71 -0
  18. data/ext/iodine/fio2resp.h +50 -0
  19. data/ext/iodine/fio_cli_helper.c +404 -0
  20. data/ext/iodine/fio_cli_helper.h +152 -0
  21. data/ext/iodine/fiobj.h +631 -0
  22. data/ext/iodine/fiobj_alloc.c +81 -0
  23. data/ext/iodine/fiobj_ary.c +290 -0
  24. data/ext/iodine/fiobj_generic.c +260 -0
  25. data/ext/iodine/fiobj_hash.c +447 -0
  26. data/ext/iodine/fiobj_io.c +58 -0
  27. data/ext/iodine/fiobj_json.c +779 -0
  28. data/ext/iodine/fiobj_misc.c +213 -0
  29. data/ext/iodine/fiobj_numbers.c +113 -0
  30. data/ext/iodine/fiobj_primitives.c +98 -0
  31. data/ext/iodine/fiobj_str.c +261 -0
  32. data/ext/iodine/fiobj_sym.c +213 -0
  33. data/ext/iodine/fiobj_tests.c +474 -0
  34. data/ext/iodine/fiobj_types.h +290 -0
  35. data/ext/iodine/http1.c +54 -36
  36. data/ext/iodine/http1_parser.c +143 -35
  37. data/ext/iodine/http1_parser.h +6 -3
  38. data/ext/iodine/http1_response.c +0 -1
  39. data/ext/iodine/http_response.c +1 -1
  40. data/ext/iodine/iodine.c +20 -4
  41. data/ext/iodine/iodine_protocol.c +5 -4
  42. data/ext/iodine/iodine_pubsub.c +1 -1
  43. data/ext/iodine/random.c +5 -5
  44. data/ext/iodine/sha1.c +5 -8
  45. data/ext/iodine/sha2.c +8 -11
  46. data/ext/iodine/sha2.h +3 -3
  47. data/ext/iodine/sock.c +29 -31
  48. data/ext/iodine/websocket_parser.h +428 -0
  49. data/ext/iodine/websockets.c +112 -377
  50. data/ext/iodine/xor-crypt.c +16 -12
  51. data/lib/iodine/version.rb +1 -1
  52. metadata +21 -3
  53. data/ext/iodine/empty.h +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: '08f011b1bc43d3c47d266fc0e2dab7379708e5de'
4
- data.tar.gz: 1197234946e68f0515ce51d3f8858d948a0a77ef
3
+ metadata.gz: d8b8c9242c96289b1e60cbf4e0ffe72c343ba468
4
+ data.tar.gz: 9334e2369ab360f6559f233ebb1be190232b4439
5
5
  SHA512:
6
- metadata.gz: aab2abfb29dea3efd0041c31abfbe93b3aac2fa21cf48fa48629263d58d6b636ff054d43acc33b3c7e24904a7240e42ef6e865218b1530bce530bf785adc4ef6
7
- data.tar.gz: 4ed2825719fc986132ecbdb28d3c56c2bc23e752f1d67df794a92fc12c5f89c84c0b914ef7ad49bece19d2f01ac3790ddce73b858fe024a1fffaeab985eba6aa
6
+ metadata.gz: 8b1ce71015afc887cb0c748fe106774ea722884d58407b5d1a5836b1b6f0c60f05439afbc0508435aa89619d85e05723a2f802431c07bd659bc3663d603c9756
7
+ data.tar.gz: 8b38c0ecefb0b83a9bd5667a5f7403967e1e9fbbc984b9df990976a8071499bf9ec3f9b67cb65bfcc27a9bc728cc09e6b9e84cfbfe32c0a1e536f457df338e6e
data/.gitignore CHANGED
@@ -12,5 +12,6 @@
12
12
  *.so
13
13
 
14
14
  .ruby-version
15
+ .clang_complete
15
16
 
16
17
  .DS_Store
data/CHANGELOG.md CHANGED
@@ -6,6 +6,26 @@ Please notice that this change log contains changes for upcoming releases as wel
6
6
 
7
7
  ## Changes:
8
8
 
9
+ #### Change log v.0.4.10
10
+
11
+ **Portability**: (`mac OS High Sierra`) iodine will load the Objective C library on macOS machines before starting up the server - this will prevent `fork` from crashing the server on macOS High Sierra, see [discussion here](https://github.com/puma/puma/issues/1421).
12
+
13
+ **Fix**: (`facil.io`) fixes an error with the new Websocket parser (introduced in v. 0.4.9) that caused medium sized messages (127 Bytes - 64Kib) to be parsed incorrectly. Apologies. The test program I used seems to have validated messages using length comparison (instead of data comparison). Credit to Tom Lahti (@uidzip) for exposing the issue.
14
+
15
+ #### Change log v.0.4.9
16
+
17
+ **Change**: (`facil.io`) the internal Websocket parser was replaced with something easier to read, for maintainability reasons. Performance seems to be mostly unaffected (sometimes it's faster and sometimes it's slower, common case is slightly optimized).
18
+
19
+ **Change**: (`facil.io`) iodine will compile facil.io with the `NO_CHILD_REAPER` flag, in order to workaround the Rails ExecJS gem that [assumes no child reaping is performed](https://github.com/rails/execjs/issues/68). This workaround is, hopefully, temporary. Credit to @jerryshen for exposing the issue.
20
+
21
+ **Fix**: (`Iodine`) test for timer creation error in `run_after` and `run_every`.
22
+
23
+ **Fix**: (`facil.io`) timer creation now correctly detects if the reactor was stopped, allowing the creation of new timers before the reactor's reactivation.
24
+
25
+ **Fix**: (`facil.io`) timer timeout review is now correctly ignored, preventing the timer from being shut down prematurely.
26
+
27
+ ***
28
+
9
29
  #### Change log v.0.4.8
10
30
 
11
31
  **Change**: (`facil.io`) the internal HTTP parser was replaced with something easier to read, for maintainability reasons. Performance seems to be unaffected.
@@ -14,6 +34,8 @@ Please notice that this change log contains changes for upcoming releases as wel
14
34
 
15
35
  **Performance**: The `now` HTTP Date string is now cached for up to 2 seconds, improving performance for `Date`, `Last-Modified` and Iodine logging messages that relate to the current time. However, it's likely that Rack will write it's own date string, masking this feature.
16
36
 
37
+ ***
38
+
17
39
  #### Change log v.0.4.7
18
40
 
19
41
  **Update**: Now using `facil.io` edge (stripped down v.0.5.3).
@@ -26,12 +48,16 @@ Please notice that this change log contains changes for upcoming releases as wel
26
48
 
27
49
  **Fix**: (`defer`) a shutdown issue in `defer_perform_in_fork` was detected by @cdkrot and his fix was implemented.
28
50
 
51
+ ***
52
+
29
53
  #### Change log v.0.4.6
30
54
 
31
55
  **Update**: Now using `facil.io` v.0.5.2.
32
56
 
33
57
  **Fix**: (from `facil.io`) fix `SIGTERM` handling, make sure sibling processes exit when a sibling dies.
34
58
 
59
+ ***
60
+
35
61
  #### Change log v.0.4.5
36
62
 
37
63
  **Fix**: fix static file service for `X-Sendfile` as well as static error file fallback pages (404.html etc').
data/README.md CHANGED
@@ -21,11 +21,11 @@ Iodine is an **evented** framework with a simple API that builds off the low lev
21
21
 
22
22
  * Iodine can handle **thousands of concurrent connections** (tested with more then 20K connections)!
23
23
 
24
- * Iodine supports only **Linux/Unix** based systems (i.e. OS X, Ubuntu, FreeBSD etc'), which are ideal for evented IO (while Windows and Solaris are better at IO *completion* events, which are totally different).
24
+ * Iodine supports only **Linux/Unix** based systems (i.e. macOS, Ubuntu, FreeBSD etc'), which are ideal for evented IO (while Windows and Solaris are better at IO *completion* events, which are totally different).
25
25
 
26
26
  Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.2.2 and up... it should support the whole Ruby 2.0 MRI family, but Rack requires Ruby 2.2.2, and so iodine matches this requirement.
27
27
 
28
- ## Iodine::Rack == a fast and powerful HTTP + Websockets server with native Pub/Sub
28
+ ## Iodine::Rack == fast & powerful HTTP + Websockets server with native Pub/Sub
29
29
 
30
30
  Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC) and the [Websocket draft extension](./SPEC-Websocket-Draft.md).
31
31
 
@@ -263,8 +263,10 @@ In pseudo-code, this might look like this
263
263
  QUEUE = Queue.new
264
264
 
265
265
  def server_cycle
266
- QUEUE << get_next_32_socket_events # these events schedule the proper user code to run
267
- QUEUE << [server]
266
+ if(QUEUE.empty?)
267
+ QUEUE << get_next_32_socket_events # these events schedule the proper user code to run
268
+ end
269
+ QUEUE << server_cycle
268
270
  end
269
271
 
270
272
  def run_server
@@ -276,9 +278,13 @@ end
276
278
 
277
279
  In pure Ruby (without using C extensions or Java), it's possible to do the same by using `select`... and although `select` has some issues, it works well for smaller concurrency levels.
278
280
 
279
- The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything... but single threaded mode should probably be avoided.
281
+ The server events are fairly fast and fragmented (longer code is fragmented across multiple events), so one thread is enough to run the server including it's static file service and everything...
282
+
283
+ ...but single threaded mode should probably be avoided.
284
+
285
+ The thread pool is there to help slow user code.
280
286
 
281
- The thread pool is there to help slow user code. It's very common that the application's code will run slower and require external resources (i.e., databases, a pub/sub service, etc'). This slow code could "starve" the server (that is patiently waiting to run it's tasks on the same thread) - which is why a thread pool is often necessary.
287
+ It's very common that the application's code will run slower and require external resources (i.e., databases, a custom pub/sub service, etc'). This slow code could "starve" the server, which is patiently waiting to run it's tasks on the same thread.
282
288
 
283
289
  The slower your application code, the more threads you will need to keep the server running smoothly.
284
290
 
@@ -290,7 +296,7 @@ Since the HTTP and Websocket parsers are written in C (with no RegExp), they're
290
296
 
291
297
  Also, iodine's core and parsers are running outside of Ruby's global lock, meaning that they enjoy true concurrency before entering the Ruby layer (your application) - this offers iodine a big advantage over other Ruby servers.
292
298
 
293
- Another assumption iodine makes is that it is behind a load balancer / proxy (which is the normal way Ruby applications are deployed) - this allows iodine to disregard header validity checks (we're not checking for invalid characters) and focus it's resources on other security and performance concerns.
299
+ Another assumption iodine makes is that it is behind a load balancer / proxy (which is the normal way Ruby applications are deployed) - this allows iodine to disregard some header validity checks (we're not checking for invalid characters) and focus it's resources on other security and performance concerns.
294
300
 
295
301
  I recommend benchmarking the performance for yourself using `wrk` or `ab`:
296
302
 
@@ -325,7 +331,7 @@ $ RACK_ENV=production puma -p 3000 -t 16 -w 4
325
331
 
326
332
  When benchmarking with `wrk`, iodine performed significantly better, (~62K req/sec vs. ~44K req/sec) while keeping a lower memory foot print (~60Mb vs. ~111Mb).
327
333
 
328
- When benchmarking with `ab`, I got different results, where iodine still performed significantly better, (~72K req/sec vs. ~36K req/sec and ~61Mb vs. ~81.6Mb). I suspect the difference between the two benchmarks has to do with system calls to `write`, but I have no real proof.
334
+ When benchmarking with `ab`, I got different results, where iodine still performed significantly better, (~72K req/sec vs. ~36K req/sec and ~61Mb vs. ~81.6Mb). I suspect the difference between the two benchmarks has to do with system calls to `write` and possible packet fragmentation, but I have no real proof.
329
335
 
330
336
  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).
331
337
 
@@ -345,9 +351,9 @@ If you have the development headers but still can't compile the iodine extension
345
351
 
346
352
  ## Mr. Sandman, write me a server
347
353
 
348
- Girls love flowers, or so my ex used to keep telling me... but I think code is the way to really show that something is hot!
354
+ Iodine allows custom TCP/IP server authoring, for those cases where we need raw TCP/IP (UDP isn't supported just yet).
349
355
 
350
- I mean, look at this short and sweet echo server - No HTTP, just use `telnet`... but it's so elegant I could cry:
356
+ Here's a short and sweet echo server - No HTTP, just use `telnet`:
351
357
 
352
358
  ```ruby
353
359
 
@@ -427,12 +433,12 @@ Here's a few things you can use from this project and they seem to be handy to h
427
433
 
428
434
  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.
429
435
 
430
- * I was using a POSIX thread pool library ([`libasync.h`](https://github.com/boazsegev/facil.io/blob/master/lib/libasync.c)) until I realized how many issues Ruby has with non-Ruby threads... So now there's a Ruby-thread port for this library at ([`rb-libasync.h`](https://github.com/boazsegev/iodine/blob/master/ext/iodine/rb-libasync.h)).
436
+ * I was using a POSIX thread pool library ([`defer.h`](https://github.com/boazsegev/facil.io/blob/master/lib/facil/core/defer.c)) until I realized how many issues Ruby has with non-Ruby threads... So now there's a Ruby-thread patch for this library at ([`rb-defer.c`](https://github.com/boazsegev/iodine/blob/master/ext/iodine/rb-defer.c)).
431
437
 
432
438
  Notice that all the new threads are free from the GVL - this allows true concurrency... but, you can't make Ruby API calls in that state.
433
439
 
434
440
  To perform Ruby API calls you need to re-enter the global lock (GVL), albeit temporarily, using `rb_thread_call_with_gvl` and `rv_protect` (gotta watch out from Ruby `longjmp` exceptions).
435
441
 
436
- * Since I needed to call Ruby methods while multi-threading and running outside the GVL, I wrote [`RubyCaller`](https://github.com/boazsegev/iodine/blob/0.2.0/ext/core/rb-call.h) which let's me call an object's method and wraps all the `rb_thread_call_with_gvl` and `rb_protect` details in a secret hidden place I never have to see again. It also keeps track of the thread's state, so if we're already within the GVL, we won't enter it "twice" (which will crash Ruby sporadically).
442
+ * Since I needed to call Ruby methods while multi-threading and running outside the GVL, I wrote [`RubyCaller`](https://github.com/boazsegev/iodine/blob/0.2.0/ext/core/rb-call.h) which let's me call an object's method and wraps all the `rb_thread_call_with_gvl` and `rb_protect` details in a secret hidden place I never have to see again. It also keeps track of the thread's state, so if we're already within the GVL, we won't enter it "twice" (which could crash Ruby sporadically).
437
443
 
438
444
  These are nice code snippets that can be easily used in other extensions. They're easy enough to write, I guess, but I already did the legwork, so enjoy.
@@ -131,15 +131,19 @@ Internally, this extension also allows iodine to manage connection memory and re
131
131
 
132
132
  ## Iodine's Pub/Sub Extension to the Rack Websockets
133
133
 
134
- This extension separates the websocket Pub/Sub semantics from the Pub/Sub engine (i.e. Redis, MongoDB, etc') or the Server. If the Collection Extension is implemented, than this isn't necessarily a big step forward (the big step forward is what can be done with is).
134
+ This extension separates the websocket Pub/Sub semantics from the Pub/Sub engine (i.e. Redis, MongoDB, etc'). This allows the Pub/Sub pattern to integrate with the Websocket API without the need for servers or applications to implement the Pub/Sub features.
135
135
 
136
- I don't personally believe this has a chance of being adopted by other server implementors, but it's a very powerful tool that Iodine supports.
136
+ This allows Pub/Sub "engines" to seamlessly integrate with a server and offer Pub/Sub functionality for the specified environment, without the need for applications or servers to know anything about the Pub/Sub details or the environment.
137
+
138
+ For example, Iodine includes three Pub/Sub engines `Iodine::PubSub::CLUSTER` (the default, for single machine multi-process pub/sub), `Iodine::PubSub::SINGLE_PROCESS` (a single process pub/sub) and `Iodine::PubSub::RedisEngine` (for horizontal scaling across machine boundaries, using Redis pub/sub)...
137
139
 
138
- Including these semantics, in the form of the described API, allows servers and Pub/Sub engines to be optimized in different ways without distracting the application (or framework implementor with environmental details.
140
+ ...but it would be easy enough to write a gem that will add another engine for MongoDB Pub/Sub and that engine would be server agnostic.
139
141
 
140
- For example, Iodine includes two default Pub/Sub engines `Iodine::PubSub::Engine::CLUSTER` (the default) and `Iodine::PubSub::Engine::SINGLE_PROCESS` that implement a localized Pub/Sub engine within a process cluster.
142
+ If the Collection Extension is implemented, than this extension should be relatively easy to implement by the server.
143
+
144
+ I don't personally believe this has a chance of being adopted by other server implementors, but it's a very powerful tool that Iodine supports.
141
145
 
142
- The Websocket module (i.e. `Iodine::Websocket`) includes the following singleton methods:
146
+ The Websocket module (i.e. `Iodine::Websocket`) includes the following singleton methods:
143
147
 
144
148
  * `Iodine::Websocket.default_pubsub=` sets the default Pub/Sub engine.
145
149
 
data/bin/ws-echo CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+
4
+ $VERBOSE = true
5
+
3
6
  Dir.chdir(File.expand_path(File.join('..', '..'), __FILE__))
4
7
  puts `rake clean`
5
8
  puts `rake compile`
data/examples/config.ru CHANGED
@@ -28,7 +28,6 @@ module MyHTTPRouter
28
28
  # check if this is an upgrade request.
29
29
  if(env['upgrade.websocket?'.freeze])
30
30
  env['upgrade.websocket'.freeze] = WebsocketEcho
31
- return [0, {}, []]
32
31
  return WS_RESPONSE
33
32
  end
34
33
  # simply return the RESPONSE object, no matter what request was received.
data/examples/echo.ru CHANGED
@@ -12,6 +12,9 @@
12
12
  #
13
13
  # ab -c 2000 -t 5 -n 1000000 -k http://127.0.0.1:3000/
14
14
  # wrk -c2000 -d5 -t12 http://localhost:3000/
15
+ #
16
+ # Test websocket echo using the browser. For example:
17
+ # ws = new WebSocket("ws://localhost:3000"); ws.onmessage = function(e) {console.log("Got message!"); console.log(e.data);}; ws.onclose = function(e) {console.log("closed")}; ws.onopen = function(e) {ws.send("hi");};
15
18
 
16
19
 
17
20
  # A simple router - Checks for Websocket Upgrade and answers HTTP.
@@ -28,7 +31,6 @@ module MyHTTPRouter
28
31
  # check if this is an upgrade request.
29
32
  if(env['upgrade.websocket?'.freeze])
30
33
  env['upgrade.websocket'.freeze] = WebsocketEcho
31
- return [0, {}, []]
32
34
  return WS_RESPONSE
33
35
  end
34
36
  # simply return the RESPONSE object, no matter what request was received.
data/examples/redis.ru CHANGED
@@ -30,7 +30,6 @@ module MyHTTPRouter
30
30
  # check if this is an upgrade request.
31
31
  if(env['upgrade.websocket?'.freeze])
32
32
  env['upgrade.websocket'.freeze] = WS_RedisPubSub.new(env['PATH_INFO'] ? env['PATH_INFO'][1..-1] : "guest")
33
- return [0, {}, []]
34
33
  return WS_RESPONSE
35
34
  end
36
35
  # simply return the RESPONSE object, no matter what request was received.
data/ext/iodine/base64.c CHANGED
@@ -37,27 +37,6 @@ static unsigned base64_decodes[] = {
37
37
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
38
38
  0, 0, 0, 0, 0, 0, 0, 0, 0};
39
39
 
40
- /**
41
- a union used for Base64 parsing
42
- */
43
- union base64_parser_u {
44
- struct {
45
- unsigned tail : 2;
46
- unsigned data : 6;
47
- } byte1;
48
- struct {
49
- unsigned prev : 8;
50
- unsigned tail : 4;
51
- unsigned head : 4;
52
- } byte2;
53
- struct {
54
- unsigned prev : 16;
55
- unsigned data : 6;
56
- unsigned head : 2;
57
- } byte3;
58
- char bytes[3];
59
- };
60
-
61
40
  /**
62
41
  This will encode a byte array (data) of a specified length (len) and
63
42
  place the encoded data into the target byte buffer (target). The target buffer
@@ -75,45 +54,41 @@ Returns the number of bytes actually written to the target buffer
75
54
  A NULL terminator char is NOT written to the target buffer.
76
55
  */
77
56
  int bscrypt_base64_encode(char *target, const char *data, int len) {
78
- int written = 0;
79
- // // optional implementation: allow a non writing, length computation.
80
- // if (!target)
81
- // return (len % 3) ? (((len + 3) / 3) * 4) : (len / 3);
82
- // use a union to avoid padding issues.
83
- union base64_parser_u *section;
84
- while (len >= 3) {
85
- section = (void *)data;
86
- target[0] = base64_encodes[section->byte1.data];
87
- target[1] =
88
- base64_encodes[(section->byte1.tail << 4) | (section->byte2.head)];
89
- target[2] =
90
- base64_encodes[(section->byte2.tail << 2) | (section->byte3.head)];
91
- target[3] = base64_encodes[section->byte3.data];
92
-
93
- target += 4;
94
- data += 3;
95
- len -= 3;
96
- written += 4;
57
+ /* walk backwards, allowing fo inplace decoding (target == data) */
58
+ int groups = len / 3;
59
+ const int mod = len - (groups * 3);
60
+ const int target_size = (groups + (mod != 0)) * 4;
61
+ char *writer = target + target_size - 1;
62
+ const char *reader = data + len - 1;
63
+ writer[1] = 0;
64
+ switch (mod) {
65
+ case 2: {
66
+ char tmp2 = *(reader--);
67
+ char tmp1 = *(reader--);
68
+ *(writer--) = '=';
69
+ *(writer--) = base64_encodes[((tmp2 & 15) << 2)];
70
+ *(writer--) = base64_encodes[((tmp1 & 3) << 4) | ((tmp2 >> 4) & 15)];
71
+ *(writer--) = base64_encodes[(tmp1 >> 2) & 63];
72
+ } break;
73
+ case 1: {
74
+ char tmp1 = *(reader--);
75
+ *(writer--) = '=';
76
+ *(writer--) = '=';
77
+ *(writer--) = base64_encodes[(tmp1 & 3) << 4];
78
+ *(writer--) = base64_encodes[(tmp1 >> 2) & 63];
79
+ } break;
97
80
  }
98
- section = (void *)data;
99
- if (len == 2) {
100
- target[0] = base64_encodes[section->byte1.data];
101
- target[1] =
102
- base64_encodes[(section->byte1.tail << 4) | (section->byte2.head)];
103
- target[2] = base64_encodes[section->byte2.tail << 2];
104
- target[3] = '=';
105
- target += 4;
106
- written += 4;
107
- } else if (len == 1) {
108
- target[0] = base64_encodes[section->byte1.data];
109
- target[1] = base64_encodes[section->byte1.tail << 4];
110
- target[2] = '=';
111
- target[3] = '=';
112
- target += 4;
113
- written += 4;
81
+ while (groups) {
82
+ groups--;
83
+ const char tmp3 = *(reader--);
84
+ const char tmp2 = *(reader--);
85
+ const char tmp1 = *(reader--);
86
+ *(writer--) = base64_encodes[tmp3 & 63];
87
+ *(writer--) = base64_encodes[((tmp2 & 15) << 2) | ((tmp3 >> 6) & 3)];
88
+ *(writer--) = base64_encodes[(((tmp1 & 3) << 4) | ((tmp2 >> 4) & 15))];
89
+ *(writer--) = base64_encodes[(tmp1 >> 2) & 63];
114
90
  }
115
- target[0] = 0; // NULL terminator
116
- return written;
91
+ return target_size;
117
92
  }
118
93
 
119
94
  /**
@@ -136,60 +111,70 @@ Returns the number of bytes actually written to the target buffer (excluding
136
111
  the NULL terminator byte).
137
112
  */
138
113
  int bscrypt_base64_decode(char *target, char *encoded, int base64_len) {
139
- if (base64_len <= 0)
140
- return -1;
141
114
  if (!target)
142
115
  target = encoded;
143
- union base64_parser_u section;
116
+ if (base64_len <= 0) {
117
+ target[0] = 0;
118
+ return 0;
119
+ }
144
120
  int written = 0;
145
- // base64_encodes
146
- // a struct that will be used to read the data.
147
- while (base64_len >= 4) {
148
- base64_len -= 4; // make sure we don't loop forever.
149
- // copying the data allows us to write destructively to the same buffer
150
- section.byte1.data = base64_decodes[(unsigned char)(*encoded)];
151
- encoded++;
152
- section.byte1.tail = (base64_decodes[(unsigned char)(*encoded)] >> 4);
153
- section.byte2.head = base64_decodes[(unsigned char)(*encoded)];
154
- encoded++;
155
- section.byte2.tail = (base64_decodes[(unsigned char)(*encoded)] >> 2);
156
- section.byte3.head = base64_decodes[(unsigned char)(*encoded)];
121
+ char tmp1, tmp2, tmp3, tmp4;
122
+ while (*encoded == '\r' || *encoded == '\n' || *encoded == ' ') {
123
+ base64_len--;
157
124
  encoded++;
158
- section.byte3.data = base64_decodes[(unsigned char)(*encoded)];
159
- encoded++;
160
- // write to the target buffer
161
- *(target++) = section.bytes[0];
162
- *(target++) = section.bytes[1];
163
- *(target++) = section.bytes[2];
164
- // count written bytes
165
- written += section.bytes[2] ? 3 : section.bytes[1] ? 2 : 1;
166
125
  }
167
- // deal with the "tail" of the encoded stream
168
- if (base64_len) {
169
- // zero out data
170
- section.bytes[0] = 0;
171
- section.bytes[1] = 0;
172
- section.bytes[2] = 0;
173
- // byte 1 + 2 (2 might be padding)
174
- section.byte1.data = base64_decodes[(unsigned char)*(encoded++)];
175
- if (--base64_len) {
176
- section.byte1.tail = base64_decodes[(unsigned char)(*encoded)] >> 4;
177
- section.byte2.head = base64_decodes[(unsigned char)(*encoded)];
126
+ while (base64_len >= 4) {
127
+ tmp1 = *(encoded++);
128
+ tmp2 = *(encoded++);
129
+ tmp3 = *(encoded++);
130
+ tmp4 = *(encoded++);
131
+ *(target++) = (base64_decodes[(size_t)tmp1] << 2) |
132
+ (base64_decodes[(size_t)tmp2] >> 4);
133
+ *(target++) = (base64_decodes[(size_t)tmp2] << 4) |
134
+ (base64_decodes[(size_t)tmp3] >> 2);
135
+ *(target++) =
136
+ (base64_decodes[(size_t)tmp3] << 6) | (base64_decodes[(size_t)tmp4]);
137
+ // make sure we don't loop forever.
138
+ base64_len -= 4;
139
+ // count written bytes
140
+ written += 3;
141
+ // skip white space
142
+ while (base64_len &&
143
+ (*encoded == '\r' || *encoded == '\n' || *encoded == ' ')) {
144
+ base64_len--;
178
145
  encoded++;
179
- if (--base64_len) {
180
- section.byte2.tail = base64_decodes[(unsigned char)(*encoded)] >> 4;
181
- section.byte3.head = base64_decodes[(unsigned char)(*encoded)];
182
- // --base64_len; // will always be 0 at this point (or it was 4)
183
- }
184
146
  }
185
- // write to the target buffer
186
- *(target++) = section.bytes[0];
187
- if (section.bytes[1] || section.bytes[2])
188
- *(target++) = section.bytes[1];
189
- if (section.bytes[2])
190
- *(target++) = section.bytes[2];
191
- // count written bytes
192
- written += section.bytes[2] ? 3 : section.bytes[1] ? 2 : 1;
147
+ }
148
+ // deal with the "tail" of the mis-encoded stream - this shouldn't happen
149
+ tmp1 = 0;
150
+ tmp2 = 0;
151
+ tmp3 = 0;
152
+ tmp4 = 0;
153
+ switch (base64_len) {
154
+ case 1:
155
+ tmp1 = *(encoded++);
156
+ *(target++) = base64_decodes[(size_t)tmp1];
157
+ written += 1;
158
+ break;
159
+ case 2:
160
+ tmp1 = *(encoded++);
161
+ tmp2 = *(encoded++);
162
+ *(target++) = (base64_decodes[(size_t)tmp1] << 2) |
163
+ (base64_decodes[(size_t)tmp2] >> 6);
164
+ *(target++) = (base64_decodes[(size_t)tmp2] << 4);
165
+ written += 2;
166
+ break;
167
+ case 3:
168
+ tmp1 = *(encoded++);
169
+ tmp2 = *(encoded++);
170
+ tmp3 = *(encoded++);
171
+ *(target++) = (base64_decodes[(size_t)tmp1] << 2) |
172
+ (base64_decodes[(size_t)tmp2] >> 6);
173
+ *(target++) = (base64_decodes[(size_t)tmp2] << 4) |
174
+ (base64_decodes[(size_t)tmp3] >> 2);
175
+ *(target++) = base64_decodes[(size_t)tmp3] << 6;
176
+ written += 3;
177
+ break;
193
178
  }
194
179
  *target = 0;
195
180
  return written;
@@ -218,6 +203,13 @@ void bscrypt_test_base64(void) {
218
203
  {"any carnal pleasure.", "YW55IGNhcm5hbCBwbGVhc3VyZS4="},
219
204
  {"any carnal pleasure", "YW55IGNhcm5hbCBwbGVhc3VyZQ=="},
220
205
  {"any carnal pleasur", "YW55IGNhcm5hbCBwbGVhc3Vy"},
206
+ {"", ""},
207
+ {"f", "Zg=="},
208
+ {"fo", "Zm8="},
209
+ {"foo", "Zm9v"},
210
+ {"foob", "Zm9vYg=="},
211
+ {"fooba", "Zm9vYmE="},
212
+ {"foobar", "Zm9vYmFy"},
221
213
  {NULL, NULL} // Stop
222
214
  };
223
215
  int i = 0;