rage-iodine 1.7.58

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  3. data/.github/workflows/ruby.yml +42 -0
  4. data/.gitignore +20 -0
  5. data/.rspec +2 -0
  6. data/.yardopts +8 -0
  7. data/CHANGELOG.md +1098 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/LIMITS.md +41 -0
  11. data/README.md +782 -0
  12. data/Rakefile +23 -0
  13. data/SPEC-PubSub-Draft.md +159 -0
  14. data/SPEC-WebSocket-Draft.md +239 -0
  15. data/bin/console +22 -0
  16. data/bin/info.md +353 -0
  17. data/bin/mustache_bench.rb +100 -0
  18. data/bin/poc/Gemfile.lock +23 -0
  19. data/bin/poc/README.md +37 -0
  20. data/bin/poc/config.ru +66 -0
  21. data/bin/poc/gemfile +1 -0
  22. data/bin/poc/www/index.html +57 -0
  23. data/examples/async_task.ru +92 -0
  24. data/examples/bates/README.md +3 -0
  25. data/examples/bates/config.ru +342 -0
  26. data/examples/bates/david+bold.pdf +0 -0
  27. data/examples/bates/public/drop-pdf.png +0 -0
  28. data/examples/bates/public/index.html +600 -0
  29. data/examples/config.ru +59 -0
  30. data/examples/echo.ru +59 -0
  31. data/examples/etag.ru +16 -0
  32. data/examples/hello.ru +29 -0
  33. data/examples/pubsub_engine.ru +81 -0
  34. data/examples/rack3.ru +12 -0
  35. data/examples/redis.ru +70 -0
  36. data/examples/shootout.ru +73 -0
  37. data/examples/sub-protocols.ru +90 -0
  38. data/examples/tcp_client.rb +66 -0
  39. data/examples/x-sendfile.ru +14 -0
  40. data/exe/iodine +280 -0
  41. data/ext/iodine/extconf.rb +110 -0
  42. data/ext/iodine/fio.c +12096 -0
  43. data/ext/iodine/fio.h +6390 -0
  44. data/ext/iodine/fio_cli.c +431 -0
  45. data/ext/iodine/fio_cli.h +189 -0
  46. data/ext/iodine/fio_json_parser.h +687 -0
  47. data/ext/iodine/fio_siphash.c +157 -0
  48. data/ext/iodine/fio_siphash.h +37 -0
  49. data/ext/iodine/fio_tls.h +129 -0
  50. data/ext/iodine/fio_tls_missing.c +649 -0
  51. data/ext/iodine/fio_tls_openssl.c +1056 -0
  52. data/ext/iodine/fio_tmpfile.h +50 -0
  53. data/ext/iodine/fiobj.h +44 -0
  54. data/ext/iodine/fiobj4fio.h +21 -0
  55. data/ext/iodine/fiobj_ary.c +333 -0
  56. data/ext/iodine/fiobj_ary.h +139 -0
  57. data/ext/iodine/fiobj_data.c +1185 -0
  58. data/ext/iodine/fiobj_data.h +167 -0
  59. data/ext/iodine/fiobj_hash.c +409 -0
  60. data/ext/iodine/fiobj_hash.h +176 -0
  61. data/ext/iodine/fiobj_json.c +622 -0
  62. data/ext/iodine/fiobj_json.h +68 -0
  63. data/ext/iodine/fiobj_mem.h +71 -0
  64. data/ext/iodine/fiobj_mustache.c +317 -0
  65. data/ext/iodine/fiobj_mustache.h +62 -0
  66. data/ext/iodine/fiobj_numbers.c +344 -0
  67. data/ext/iodine/fiobj_numbers.h +127 -0
  68. data/ext/iodine/fiobj_str.c +433 -0
  69. data/ext/iodine/fiobj_str.h +172 -0
  70. data/ext/iodine/fiobject.c +620 -0
  71. data/ext/iodine/fiobject.h +654 -0
  72. data/ext/iodine/hpack.h +1923 -0
  73. data/ext/iodine/http.c +2736 -0
  74. data/ext/iodine/http.h +1019 -0
  75. data/ext/iodine/http1.c +825 -0
  76. data/ext/iodine/http1.h +29 -0
  77. data/ext/iodine/http1_parser.h +1835 -0
  78. data/ext/iodine/http_internal.c +1279 -0
  79. data/ext/iodine/http_internal.h +248 -0
  80. data/ext/iodine/http_mime_parser.h +350 -0
  81. data/ext/iodine/iodine.c +1433 -0
  82. data/ext/iodine/iodine.h +64 -0
  83. data/ext/iodine/iodine_caller.c +218 -0
  84. data/ext/iodine/iodine_caller.h +27 -0
  85. data/ext/iodine/iodine_connection.c +941 -0
  86. data/ext/iodine/iodine_connection.h +55 -0
  87. data/ext/iodine/iodine_defer.c +420 -0
  88. data/ext/iodine/iodine_defer.h +6 -0
  89. data/ext/iodine/iodine_fiobj2rb.h +120 -0
  90. data/ext/iodine/iodine_helpers.c +282 -0
  91. data/ext/iodine/iodine_helpers.h +12 -0
  92. data/ext/iodine/iodine_http.c +1280 -0
  93. data/ext/iodine/iodine_http.h +23 -0
  94. data/ext/iodine/iodine_json.c +302 -0
  95. data/ext/iodine/iodine_json.h +6 -0
  96. data/ext/iodine/iodine_mustache.c +567 -0
  97. data/ext/iodine/iodine_mustache.h +6 -0
  98. data/ext/iodine/iodine_pubsub.c +580 -0
  99. data/ext/iodine/iodine_pubsub.h +26 -0
  100. data/ext/iodine/iodine_rack_io.c +273 -0
  101. data/ext/iodine/iodine_rack_io.h +20 -0
  102. data/ext/iodine/iodine_store.c +142 -0
  103. data/ext/iodine/iodine_store.h +20 -0
  104. data/ext/iodine/iodine_tcp.c +346 -0
  105. data/ext/iodine/iodine_tcp.h +13 -0
  106. data/ext/iodine/iodine_tls.c +261 -0
  107. data/ext/iodine/iodine_tls.h +13 -0
  108. data/ext/iodine/mustache_parser.h +1546 -0
  109. data/ext/iodine/redis_engine.c +957 -0
  110. data/ext/iodine/redis_engine.h +79 -0
  111. data/ext/iodine/resp_parser.h +317 -0
  112. data/ext/iodine/scheduler.c +173 -0
  113. data/ext/iodine/scheduler.h +6 -0
  114. data/ext/iodine/websocket_parser.h +506 -0
  115. data/ext/iodine/websockets.c +752 -0
  116. data/ext/iodine/websockets.h +185 -0
  117. data/iodine.gemspec +50 -0
  118. data/lib/iodine/connection.rb +61 -0
  119. data/lib/iodine/json.rb +42 -0
  120. data/lib/iodine/mustache.rb +113 -0
  121. data/lib/iodine/pubsub.rb +55 -0
  122. data/lib/iodine/rack_utils.rb +43 -0
  123. data/lib/iodine/tls.rb +16 -0
  124. data/lib/iodine/version.rb +3 -0
  125. data/lib/iodine.rb +274 -0
  126. data/lib/rack/handler/iodine.rb +33 -0
  127. data/logo.png +0 -0
  128. metadata +284 -0
@@ -0,0 +1,346 @@
1
+ #include "iodine.h"
2
+ #include <ruby/encoding.h>
3
+ #include <ruby/io.h>
4
+
5
+ #define FIO_INCLUDE_STR
6
+ #include "fio.h"
7
+
8
+ /* *****************************************************************************
9
+ Static stuff
10
+ ***************************************************************************** */
11
+ static ID call_id;
12
+ static ID on_closed_id;
13
+ static rb_encoding *IodineBinaryEncoding;
14
+
15
+ static VALUE port_id;
16
+ static VALUE address_id;
17
+ static VALUE handler_id;
18
+ static VALUE timeout_id;
19
+
20
+ /* *****************************************************************************
21
+ Raw TCP/IP Protocol
22
+ ***************************************************************************** */
23
+
24
+ #define IODINE_MAX_READ 8192
25
+
26
+ typedef struct {
27
+ fio_protocol_s p;
28
+ VALUE io;
29
+ } iodine_protocol_s;
30
+
31
+ typedef struct {
32
+ VALUE io;
33
+ ssize_t len;
34
+ char buffer[IODINE_MAX_READ];
35
+ } iodine_buffer_s;
36
+
37
+ /**
38
+ * Converts an iodine_buffer_s pointer to a Ruby string.
39
+ */
40
+ static void *iodine_tcp_on_data_in_GIL(void *b_) {
41
+ iodine_buffer_s *b = b_;
42
+ if (!b) {
43
+ FIO_LOG_FATAL("(iodine->tcp/ip->on_data->GIL) WTF?!\n");
44
+ exit(-1);
45
+ }
46
+ VALUE data = IodineStore.add(rb_str_new(b->buffer, b->len));
47
+ rb_enc_associate(data, IodineBinaryEncoding);
48
+ iodine_connection_fire_event(b->io, IODINE_CONNECTION_ON_MESSAGE, data);
49
+ IodineStore.remove(data);
50
+ return NULL;
51
+ // return (void *)IodineStore.add(rb_usascii_str_new((const char *)b->buffer,
52
+ // b->len));
53
+ }
54
+
55
+ /** Called when a data is available, but will not run concurrently */
56
+ static void iodine_tcp_on_data(intptr_t uuid, fio_protocol_s *protocol) {
57
+ iodine_buffer_s buffer;
58
+ buffer.len = fio_read(uuid, buffer.buffer, IODINE_MAX_READ);
59
+ if (buffer.len <= 0) {
60
+ return;
61
+ }
62
+ buffer.io = ((iodine_protocol_s *)protocol)->io;
63
+ IodineCaller.enterGVL(iodine_tcp_on_data_in_GIL, &buffer);
64
+ if (buffer.len == IODINE_MAX_READ) {
65
+ fio_force_event(uuid, FIO_EVENT_ON_DATA);
66
+ }
67
+ }
68
+
69
+ /** called when the socket is ready to be written to. */
70
+ static void iodine_tcp_on_ready(intptr_t uuid, fio_protocol_s *protocol) {
71
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
72
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_DRAINED, Qnil);
73
+ (void)uuid;
74
+ }
75
+
76
+ /**
77
+ * Called when the server is shutting down, immediately before closing the
78
+ * connection.
79
+ */
80
+ static uint8_t iodine_tcp_on_shutdown(intptr_t uuid, fio_protocol_s *protocol) {
81
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
82
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_SHUTDOWN, Qnil);
83
+ return 0;
84
+ (void)uuid;
85
+ }
86
+
87
+ /** Called when the connection was closed, but will not run concurrently */
88
+
89
+ static void iodine_tcp_on_close(intptr_t uuid, fio_protocol_s *protocol) {
90
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
91
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_CLOSE, Qnil);
92
+ free(p);
93
+ (void)uuid;
94
+ }
95
+
96
+ /** called when a connection's timeout was reached */
97
+ static void iodine_tcp_ping(intptr_t uuid, fio_protocol_s *protocol) {
98
+ iodine_protocol_s *p = (iodine_protocol_s *)protocol;
99
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_PING, Qnil);
100
+ (void)uuid;
101
+ }
102
+
103
+ /** fio_listen callback, called when a connection opens */
104
+ static void iodine_tcp_on_open(intptr_t uuid, void *udata) {
105
+ if (!fio_is_valid(uuid))
106
+ return;
107
+ VALUE handler = IodineCaller.call((VALUE)udata, call_id);
108
+ IodineStore.add(handler);
109
+ iodine_tcp_attch_uuid(uuid, handler);
110
+ IodineStore.remove(handler);
111
+ }
112
+
113
+ /** called when the listening socket is destroyed */
114
+ static void iodine_tcp_on_finish(intptr_t uuid, void *udata) {
115
+ IodineStore.remove((VALUE)udata);
116
+ (void)uuid;
117
+ }
118
+
119
+ /**
120
+ * The `on_connect` callback should either call `fio_attach` or close the
121
+ * connection.
122
+ */
123
+ static void iodine_tcp_on_connect(intptr_t uuid, void *udata) {
124
+ iodine_tcp_attch_uuid(uuid, (VALUE)udata);
125
+ IodineStore.remove((VALUE)udata);
126
+ }
127
+
128
+ /**
129
+ * The `on_fail` is called when a socket fails to connect. The old sock UUID
130
+ * is passed along.
131
+ */
132
+ static void iodine_tcp_on_fail(intptr_t uuid, void *udata) {
133
+ IodineStore.remove((VALUE)udata);
134
+ }
135
+
136
+ /* *****************************************************************************
137
+ The Ruby API implementation
138
+ ***************************************************************************** */
139
+
140
+ // clang-format off
141
+ /**
142
+ The {listen} method instructs iodine to listen to incoming connections using either TCP/IP or Unix sockets.
143
+
144
+ The method accepts a single Hash argument with the following optional keys:
145
+
146
+ :port :: The port to listen to, deafults to nil (using a Unix socket)
147
+ :address :: The address to listen to, which could be a Unix Socket path as well as an IPv4 / IPv6 address. Deafults to 0.0.0.0 (or the IPv6 equivelant).
148
+ :handler :: An object that answers the `call` method (i.e., a Proc).
149
+
150
+ The method also accepts an optional block.
151
+
152
+ Either a block or the :handler key MUST be present.
153
+
154
+ The handler Proc (or object) should return a connection callback object that supports the following callbacks (see also {Iodine::Connection}):
155
+
156
+ on_open(client) :: called after a connection was established
157
+ on_message(client, data) :: called when incoming data is available. Data may be fragmented.
158
+ on_drained(client) :: called when all the pending `client.write` events have been processed (see {Iodine::Connection#pending}).
159
+ ping(client) :: called whenever a timeout has occured (see {Iodine::Connection#timeout=}).
160
+ on_shutdown(client) :: called if the server is shutting down. This is called before the connection is closed.
161
+ on_close(client) :: called when the connection with the client was closed.
162
+
163
+ The `client` argument is an {Iodine::Connection} instance that represents the connection / the client.
164
+
165
+ Here's a telnet based chat-room example:
166
+
167
+ require 'iodine'
168
+ # define the protocol for our service
169
+ module ChatHandler
170
+ def self.on_open(client)
171
+ # Set a connection timeout
172
+ client.timeout = 10
173
+ # subscribe to the chat channel.
174
+ client.subscribe :chat
175
+ # Write a welcome message
176
+ client.publish :chat, "new member entered the chat\r\n"
177
+ end
178
+ # this is called for incoming data - note data might be fragmented.
179
+ def self.on_message(client, data)
180
+ # publish the data we received
181
+ client.publish :chat, data
182
+ # close the connection when the time comes
183
+ client.close if data =~ /^bye[\n\r]/
184
+ end
185
+ # called whenever timeout occurs.
186
+ def self.ping(client)
187
+ client.write "System: quite, isn't it...?\r\n"
188
+ end
189
+ # called if the connection is still open and the server is shutting down.
190
+ def self.on_shutdown(client)
191
+ # write the data we received
192
+ client.write "Chat server going away. Try again later.\r\n"
193
+ end
194
+ # returns the callback object (self).
195
+ def self.call
196
+ self
197
+ end
198
+ end
199
+ # we use can both the `handler` keyword or a block, anything that answers #call.
200
+ Iodine.listen(port: "3000", handler: ChatHandler)
201
+ # start the service
202
+ Iodine.threads = 1
203
+ Iodine.start
204
+
205
+
206
+
207
+ Returns the handler object used.
208
+ */
209
+ intptr_t iodine_tcp_listen(iodine_connection_args_s args) {
210
+ // clang-format on
211
+ IodineStore.add(args.handler);
212
+ #ifdef __MINGW32__
213
+ return fio_listen(.port = args.port.data, .address = args.address.data,
214
+ .on_open = iodine_tcp_on_open,
215
+ .on_finish = iodine_tcp_on_finish,
216
+ .udata = (void *)args.handler);
217
+ #else
218
+ return fio_listen(.port = args.port.data, .address = args.address.data,
219
+ .on_open = iodine_tcp_on_open,
220
+ .on_finish = iodine_tcp_on_finish, .tls = args.tls,
221
+ .udata = (void *)args.handler);
222
+ #endif
223
+ }
224
+
225
+ // clang-format off
226
+ /**
227
+ The {connect} method instructs iodine to connect to a server using either TCP/IP or Unix sockets.
228
+
229
+ The method accepts a single Hash argument with the following optional keys:
230
+
231
+ :port :: The port to listen to, deafults to 0 (using a Unix socket)
232
+ :address :: The address to listen to, which could be a Unix Socket path as well as an IPv4 / IPv6 address. Deafults to 0.0.0.0 (or the IPv6 equivelant).
233
+ :handler :: A connection callback object that supports the following same callbacks listen in the {listen} method's documentation.
234
+ :timeout :: An integer timeout for connection establishment (doen't effect the new connection's timeout. Should be in the rand of 0..255.
235
+ :tls :: An {Iodine::TLS} object (optional) for secure connections.
236
+
237
+ The method also accepts an optional block.
238
+
239
+ Either a block or the :handler key MUST be present.
240
+
241
+ If the connection fails, only the `on_close` callback will be called (with a `nil` client).
242
+
243
+ Returns the handler object used.
244
+ */
245
+ intptr_t iodine_tcp_connect(iodine_connection_args_s args){
246
+ // clang-format on
247
+ IodineStore.add(args.handler);
248
+ #ifdef __MINGW32__
249
+ return fio_connect(.port = args.port.data, .address = args.address.data,
250
+ .on_connect = iodine_tcp_on_connect,
251
+ .on_fail = iodine_tcp_on_fail, .timeout = args.ping,
252
+ .udata = (void *)args.handler);
253
+ #else
254
+ return fio_connect(.port = args.port.data, .address = args.address.data,
255
+ .on_connect = iodine_tcp_on_connect, .tls = args.tls,
256
+ .on_fail = iodine_tcp_on_fail, .timeout = args.ping,
257
+ .udata = (void *)args.handler);
258
+ #endif
259
+ }
260
+
261
+ // clang-format off
262
+ /**
263
+ The {attach_fd} method instructs iodine to attach a socket to the server using it's numerical file descriptor.
264
+
265
+ This is faster than attaching a Ruby IO object since it allows iodine to directly call the system's read/write methods. However, this doesn't support TLS/SSL connections.
266
+
267
+ This method requires two objects, a file descriptor (`fd`) and a callback object.
268
+
269
+ See {listen} for details about the callback object.
270
+
271
+ Returns the callback object (handler) used.
272
+ */
273
+ static VALUE iodine_tcp_attach_fd(VALUE self, VALUE fd, VALUE handler) {
274
+ // clang-format on
275
+ Check_Type(fd, T_FIXNUM);
276
+ if (handler == Qnil || handler == Qfalse || handler == Qtrue) {
277
+ rb_raise(rb_eArgError, "A callback object must be provided.");
278
+ }
279
+ IodineStore.add(handler);
280
+ int other = dup(NUM2INT(fd));
281
+ if (other == -1) {
282
+ rb_raise(rb_eIOError, "invalid fd.");
283
+ }
284
+ intptr_t uuid = fio_fd2uuid(other);
285
+ iodine_tcp_attch_uuid(uuid, handler);
286
+ IodineStore.remove(handler);
287
+ return handler;
288
+ (void)self;
289
+ }
290
+
291
+ /* *****************************************************************************
292
+ Add the Ruby API methods to the Iodine object
293
+ ***************************************************************************** */
294
+
295
+ void iodine_init_tcp_connections(void) {
296
+ call_id = rb_intern2("call", 4);
297
+ port_id = IodineStore.add(rb_id2sym(rb_intern("port")));
298
+ address_id = IodineStore.add(rb_id2sym(rb_intern("address")));
299
+ handler_id = IodineStore.add(rb_id2sym(rb_intern("handler")));
300
+ timeout_id = IodineStore.add(rb_id2sym(rb_intern("timeout")));
301
+ on_closed_id = rb_intern("on_closed");
302
+
303
+ IodineBinaryEncoding = rb_enc_find("binary");
304
+
305
+ rb_define_module_function(IodineModule, "attach_fd", iodine_tcp_attach_fd, 2);
306
+ }
307
+
308
+ /* *****************************************************************************
309
+ Allow uuid attachment
310
+ ***************************************************************************** */
311
+
312
+ /** assigns a protocol and IO object to a handler */
313
+ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler) {
314
+ FIO_LOG_DEBUG("Iodine attaching handler %p to uuid %p", (void *)handler,
315
+ (void *)uuid);
316
+ if (handler == Qnil || handler == Qfalse || handler == Qtrue) {
317
+ fio_close(uuid);
318
+ return;
319
+ }
320
+ /* temporary, in case `iodine_connection_new` invokes the GC */
321
+ iodine_protocol_s *p = malloc(sizeof(*p));
322
+ FIO_ASSERT_ALLOC(p);
323
+ *p = (iodine_protocol_s){
324
+ .p =
325
+ {
326
+ .on_data = iodine_tcp_on_data,
327
+ .on_ready = NULL /* set only after the on_open callback */,
328
+ .on_shutdown = iodine_tcp_on_shutdown,
329
+ .on_close = iodine_tcp_on_close,
330
+ .ping = iodine_tcp_ping,
331
+ },
332
+ .io = iodine_connection_new(.type = IODINE_CONNECTION_RAW, .uuid = uuid,
333
+ .arg = p, .handler = handler),
334
+ };
335
+ /* clear away (remember the connection object manages these concerns) */
336
+ fio_attach(uuid, &p->p);
337
+ if (fio_is_valid(uuid)) {
338
+ iodine_connection_fire_event(p->io, IODINE_CONNECTION_ON_OPEN, Qnil);
339
+ p->p.on_ready = iodine_tcp_on_ready;
340
+ fio_force_event(uuid, FIO_EVENT_ON_READY);
341
+ } else {
342
+ FIO_LOG_DEBUG(
343
+ "Iodine couldn't attach handler %p to uuid %p - invalid uuid.",
344
+ (void *)handler, (void *)uuid);
345
+ }
346
+ }
@@ -0,0 +1,13 @@
1
+ #ifndef H_IODINE_RAW_TCP_IP_H
2
+ #define H_IODINE_RAW_TCP_IP_H
3
+
4
+ #include "ruby.h"
5
+
6
+ #include "iodine.h"
7
+
8
+ void iodine_init_tcp_connections(void);
9
+ void iodine_tcp_attch_uuid(intptr_t uuid, VALUE handler);
10
+ intptr_t iodine_tcp_listen(iodine_connection_args_s args);
11
+ intptr_t iodine_tcp_connect(iodine_connection_args_s args);
12
+
13
+ #endif
@@ -0,0 +1,261 @@
1
+ #ifdef __MINGW32__
2
+ // make pedantic compiler happy
3
+ typedef struct {
4
+ int bogus;
5
+ } bogus_s;
6
+
7
+ #else
8
+ #include <ruby.h>
9
+
10
+ #include <fio.h>
11
+ #include <fio_tls.h>
12
+ #include <iodine.h>
13
+
14
+ static VALUE server_name_sym = Qnil, certificate_sym = Qnil,
15
+ private_key_sym = Qnil, password_sym = Qnil;
16
+ VALUE IodineTLSClass;
17
+ /* *****************************************************************************
18
+ C <=> Ruby Data allocation
19
+ ***************************************************************************** */
20
+
21
+ static size_t iodine_tls_data_size(const void *c_) {
22
+ return sizeof(fio_tls_s *);
23
+ (void)c_;
24
+ }
25
+
26
+ static void iodine_tls_data_free(void *c_) { fio_tls_destroy(c_); }
27
+
28
+ static const rb_data_type_t iodine_tls_data_type = {
29
+ .wrap_struct_name = "IodineTLSData",
30
+ .function =
31
+ {
32
+ .dmark = NULL,
33
+ .dfree = iodine_tls_data_free,
34
+ .dsize = iodine_tls_data_size,
35
+ },
36
+ .data = NULL,
37
+ // .flags = RUBY_TYPED_FREE_IMMEDIATELY,
38
+ };
39
+
40
+ /* Iodine::PubSub::Engine.allocate */
41
+ static VALUE iodine_tls_data_alloc_c(VALUE klass) {
42
+ fio_tls_s *tls = fio_tls_new(NULL, NULL, NULL, NULL);
43
+ FIO_ASSERT_ALLOC(tls);
44
+ return TypedData_Wrap_Struct(klass, &iodine_tls_data_type, tls);
45
+ }
46
+
47
+ /* *****************************************************************************
48
+ ALPN selection callback
49
+ ***************************************************************************** */
50
+
51
+ FIO_FUNC void iodine_tls_alpn_cb(intptr_t uuid, void *udata, void *block_) {
52
+ if (!fio_is_valid(uuid)) {
53
+ FIO_LOG_DEBUG("ALPN callback called for invalid connetion. SSL/TLS error?");
54
+ return;
55
+ }
56
+ VALUE new_handler = IodineCaller.call((VALUE)block_, iodine_call_id);
57
+ if (!new_handler || new_handler == Qnil || new_handler == Qtrue ||
58
+ new_handler == Qfalse) {
59
+ fio_close(uuid);
60
+ return;
61
+ }
62
+
63
+ iodine_tcp_attch_uuid(uuid, new_handler);
64
+
65
+ (void)udata; /* we can't use `udata`, since it's different in HTTP vs. TCP */
66
+ }
67
+
68
+ /* *****************************************************************************
69
+ C API
70
+ ***************************************************************************** */
71
+
72
+ fio_tls_s *iodine_tls2c(VALUE self) {
73
+ fio_tls_s *c = NULL;
74
+ if (self == Qnil || self == Qfalse)
75
+ return NULL;
76
+ TypedData_Get_Struct(self, fio_tls_s, &iodine_tls_data_type, c);
77
+ if (!c) {
78
+ rb_raise(rb_eTypeError, "Iodine::TLS error - not an Iodine::TLS object?");
79
+ }
80
+ return c;
81
+ }
82
+
83
+ /* *****************************************************************************
84
+ Ruby API
85
+ ***************************************************************************** */
86
+
87
+ /**
88
+ Assigns the TLS context a public sertificate, allowing remote parties to
89
+ validate the connection's identity.
90
+
91
+ A self signed certificate is automatically created if the `server_name` argument
92
+ is specified and either (or both) of the `certificate` or `private_ket`
93
+ arguments are missing.
94
+
95
+ Some implementations allow servers to have more than a single certificate, which
96
+ will be selected using the SNI extension. I believe the existing OpenSSL
97
+ implementation supports this option (untested).
98
+
99
+ Iodine::TLS#use_certificate(server_name,
100
+ certificate = nil,
101
+ private_key = nil,
102
+ password = nil)
103
+
104
+ Certificates and keys should be String objects leading to a PEM file.
105
+
106
+ This method also accepts named arguments. i.e.:
107
+
108
+ tls = Iodine::TLS.new
109
+ tls.use_certificate server_name: "example.com"
110
+ tls.use_certificate certificate: "my_cert.pem", private_key: "my_key.pem"
111
+
112
+ Since TLS setup is crucial for security, a missing file will result in Iodine
113
+ crashing with an error message. This is expected behavior.
114
+
115
+ */
116
+ static VALUE iodine_tls_use_certificate(int argc, VALUE *argv, VALUE self) {
117
+ VALUE server_name = Qnil, certificate = Qnil, private_key = Qnil,
118
+ password = Qnil;
119
+ if (argc == 1 && RB_TYPE_P(argv[0], T_HASH)) {
120
+ /* named arguments */
121
+ server_name = rb_hash_aref(argv[0], server_name_sym);
122
+ certificate = rb_hash_aref(argv[0], certificate_sym);
123
+ private_key = rb_hash_aref(argv[0], private_key_sym);
124
+ password = rb_hash_aref(argv[0], password_sym);
125
+ } else {
126
+ /* regular arguments */
127
+ switch (argc) {
128
+ case 4: /* overflow */
129
+ password = argv[3];
130
+ Check_Type(password, T_STRING);
131
+ case 3: /* overflow */
132
+ private_key = argv[1];
133
+ Check_Type(private_key, T_STRING);
134
+ case 2: /* overflow */
135
+ certificate = argv[2];
136
+ Check_Type(certificate, T_STRING);
137
+ case 1: /* overflow */
138
+ server_name = argv[0];
139
+ Check_Type(server_name, T_STRING);
140
+ break;
141
+ default:
142
+ rb_raise(rb_eArgError, "expecting 1..4 arguments or named arguments.");
143
+ return self;
144
+ }
145
+ }
146
+
147
+ fio_tls_s *t = iodine_tls2c(self);
148
+ char *srvname = (server_name == Qnil ? NULL : StringValueCStr(server_name));
149
+ char *pubcert = (certificate == Qnil ? NULL : StringValueCStr(certificate));
150
+ char *prvkey = (private_key == Qnil ? NULL : StringValueCStr(private_key));
151
+ char *pass = (password == Qnil ? NULL : StringValueCStr(password));
152
+
153
+ fio_tls_cert_add(t, srvname, pubcert, prvkey, pass);
154
+ return self;
155
+ }
156
+
157
+ /**
158
+ Adds a certificate PEM file to the list of trusted certificates and enforces
159
+ peer verification.
160
+
161
+ This is extremely important when using {Iodine::TLS} for client connections,
162
+ since adding the target server's
163
+
164
+ It is enough to add the Certificate Authority's (CA) certificate, there's no
165
+ need to add each client or server certificate.
166
+
167
+ When {trust} is used on a server TLS, only trusted clients will be allowed to
168
+ connect.
169
+
170
+ Since TLS setup is crucial for security, a missing file will result in Iodine
171
+ crashing with an error message. This is expected behavior.
172
+ */
173
+ static VALUE iodine_tls_trust(VALUE self, VALUE certificate) {
174
+ Check_Type(certificate, T_STRING);
175
+ fio_tls_s *t = iodine_tls2c(self);
176
+ char *pubcert =
177
+ (certificate == Qnil ? NULL : IODINE_RSTRINFO(certificate).data);
178
+ fio_tls_trust(t, pubcert);
179
+ return self;
180
+ }
181
+
182
+ /**
183
+ Adds an ALPN protocol callback for the named protocol, the required block must
184
+ return the handler for that protocol.
185
+
186
+ The first protocol added will be the default protocol in cases where ALPN
187
+ failed.
188
+
189
+ i.e.:
190
+
191
+ tls.on_protocol("http/1.1") { HTTPConnection.new }
192
+
193
+ When implementing TLS clients, this identifies the protocol(s) that should be
194
+ requested by the client.
195
+
196
+ When implementing TLS servers, this identifies the protocol(s) offered by the
197
+ server.
198
+
199
+ More than a single protocol can be set, but iodine doesn't offer, at this
200
+ moment, a way to handle these changes or to detect which protocol was selected
201
+ except by assigning a different callback per protocol.
202
+
203
+ This is implemented using the ALPN extension to TLS.
204
+ */
205
+ static VALUE iodine_tls_alpn(VALUE self, VALUE protocol_name) {
206
+ Check_Type(protocol_name, T_STRING);
207
+ rb_need_block();
208
+ fio_tls_s *t = iodine_tls2c(self);
209
+ char *prname =
210
+ (protocol_name == Qnil ? NULL : IODINE_RSTRINFO(protocol_name).data);
211
+ VALUE block = IodineStore.add(rb_block_proc());
212
+ fio_tls_alpn_add(t, prname, iodine_tls_alpn_cb, (void *)block,
213
+ (void (*)(void *))IodineStore.remove);
214
+ return self;
215
+ }
216
+
217
+ /**
218
+ Creates a new {Iodine::TLS} object and calles the {#use_certificate} method with
219
+ the supplied arguments.
220
+ */
221
+ static VALUE iodine_tls_new(int argc, VALUE *argv, VALUE self) {
222
+ if (argc) {
223
+ iodine_tls_use_certificate(argc, argv, self);
224
+ }
225
+ return self;
226
+ }
227
+
228
+ /* *****************************************************************************
229
+ Initialize Iodine::TLS
230
+ ***************************************************************************** */
231
+ #define IODINE_MAKE_SYM(name) \
232
+ do { \
233
+ name##_sym = rb_id2sym(rb_intern(#name)); \
234
+ rb_global_variable(&name##_sym); \
235
+ } while (0)
236
+
237
+ void iodine_init_tls(void) {
238
+
239
+ IODINE_MAKE_SYM(server_name);
240
+ IODINE_MAKE_SYM(certificate);
241
+ IODINE_MAKE_SYM(private_key);
242
+ IODINE_MAKE_SYM(password);
243
+
244
+ IodineTLSClass = rb_define_class_under(IodineModule, "TLS", rb_cObject);
245
+ rb_define_alloc_func(IodineTLSClass, iodine_tls_data_alloc_c);
246
+ rb_define_method(IodineTLSClass, "initialize", iodine_tls_new, -1);
247
+ rb_define_method(IodineTLSClass, "use_certificate",
248
+ iodine_tls_use_certificate, -1);
249
+ rb_define_method(IodineTLSClass, "trust", iodine_tls_trust, 1);
250
+ rb_define_method(IodineTLSClass, "on_protocol", iodine_tls_alpn, 1);
251
+
252
+ #if HAVE_OPENSSL
253
+ rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qtrue);
254
+ #elif HAVE_BEARSSL
255
+ rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse);
256
+ #else
257
+ rb_const_set(IodineTLSClass, rb_intern("SUPPORTED"), Qfalse);
258
+ #endif
259
+ }
260
+ #undef IODINE_MAKE_SYM
261
+ #endif
@@ -0,0 +1,13 @@
1
+ #ifndef H_IODINE_TLS_H
2
+ #define H_IODINE_TLS_H
3
+
4
+ #include "iodine.h"
5
+
6
+ #include "fio_tls.h"
7
+
8
+ void iodine_init_tls(void);
9
+ fio_tls_s *iodine_tls2c(VALUE self);
10
+
11
+ extern VALUE IodineTLSClass;
12
+
13
+ #endif