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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +26 -0
- data/README.md +18 -12
- data/SPEC-Websocket-Draft.md +9 -5
- data/bin/ws-echo +3 -0
- data/examples/config.ru +0 -1
- data/examples/echo.ru +3 -1
- data/examples/redis.ru +0 -1
- data/ext/iodine/base64.c +97 -105
- data/ext/iodine/defer.c +16 -1
- data/ext/iodine/defer.h +10 -0
- data/ext/iodine/evio.c +35 -13
- data/ext/iodine/extconf.rb +1 -1
- data/ext/iodine/facil.c +12 -1
- data/ext/iodine/facil.h +3 -1
- data/ext/iodine/fio2resp.c +71 -0
- data/ext/iodine/fio2resp.h +50 -0
- data/ext/iodine/fio_cli_helper.c +404 -0
- data/ext/iodine/fio_cli_helper.h +152 -0
- data/ext/iodine/fiobj.h +631 -0
- data/ext/iodine/fiobj_alloc.c +81 -0
- data/ext/iodine/fiobj_ary.c +290 -0
- data/ext/iodine/fiobj_generic.c +260 -0
- data/ext/iodine/fiobj_hash.c +447 -0
- data/ext/iodine/fiobj_io.c +58 -0
- data/ext/iodine/fiobj_json.c +779 -0
- data/ext/iodine/fiobj_misc.c +213 -0
- data/ext/iodine/fiobj_numbers.c +113 -0
- data/ext/iodine/fiobj_primitives.c +98 -0
- data/ext/iodine/fiobj_str.c +261 -0
- data/ext/iodine/fiobj_sym.c +213 -0
- data/ext/iodine/fiobj_tests.c +474 -0
- data/ext/iodine/fiobj_types.h +290 -0
- data/ext/iodine/http1.c +54 -36
- data/ext/iodine/http1_parser.c +143 -35
- data/ext/iodine/http1_parser.h +6 -3
- data/ext/iodine/http1_response.c +0 -1
- data/ext/iodine/http_response.c +1 -1
- data/ext/iodine/iodine.c +20 -4
- data/ext/iodine/iodine_protocol.c +5 -4
- data/ext/iodine/iodine_pubsub.c +1 -1
- data/ext/iodine/random.c +5 -5
- data/ext/iodine/sha1.c +5 -8
- data/ext/iodine/sha2.c +8 -11
- data/ext/iodine/sha2.h +3 -3
- data/ext/iodine/sock.c +29 -31
- data/ext/iodine/websocket_parser.h +428 -0
- data/ext/iodine/websockets.c +112 -377
- data/ext/iodine/xor-crypt.c +16 -12
- data/lib/iodine/version.rb +1 -1
- metadata +21 -3
- data/ext/iodine/empty.h +0 -26
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8b8c9242c96289b1e60cbf4e0ffe72c343ba468
|
4
|
+
data.tar.gz: 9334e2369ab360f6559f233ebb1be190232b4439
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b1ce71015afc887cb0c748fe106774ea722884d58407b5d1a5836b1b6f0c60f05439afbc0508435aa89619d85e05723a2f802431c07bd659bc3663d603c9756
|
7
|
+
data.tar.gz: 8b38c0ecefb0b83a9bd5667a5f7403967e1e9fbbc984b9df990976a8071499bf9ec3f9b67cb65bfcc27a9bc728cc09e6b9e84cfbfe32c0a1e536f457df338e6e
|
data/.gitignore
CHANGED
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.
|
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 ==
|
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
|
267
|
-
|
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...
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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 ([`
|
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
|
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.
|
data/SPEC-Websocket-Draft.md
CHANGED
@@ -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')
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
-
|
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
|
-
|
116
|
+
if (base64_len <= 0) {
|
117
|
+
target[0] = 0;
|
118
|
+
return 0;
|
119
|
+
}
|
144
120
|
int written = 0;
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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;
|