iodine 0.7.59 → 0.8.0

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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.clang-format +15 -0
  3. data/.rspec +1 -0
  4. data/.yardopts +1 -3
  5. data/CHANGELOG.md +132 -12
  6. data/Gemfile +5 -7
  7. data/LICENSE.txt +1 -1
  8. data/README.md +147 -671
  9. data/Rakefile +9 -16
  10. data/examples/chatroom.nru +9 -0
  11. data/examples/client +74 -0
  12. data/examples/config-hello.nru +9 -0
  13. data/examples/config-hello.ru +11 -0
  14. data/examples/config-hijack.ru +13 -0
  15. data/examples/config-shootout.ru +43 -0
  16. data/examples/config.nru +120 -0
  17. data/examples/config.rb +37 -0
  18. data/examples/config.ru +42 -53
  19. data/examples/fake +24 -0
  20. data/examples/hello_neorack +137 -0
  21. data/examples/routes.nru +29 -0
  22. data/examples/routes.ru +42 -0
  23. data/exe/iodine +218 -258
  24. data/ext/iodine/extconf.rb +27 -100
  25. data/ext/iodine/fio-stl.h +128336 -0
  26. data/ext/iodine/fio-stl.md +26396 -0
  27. data/ext/iodine/iodine.c +213 -1393
  28. data/ext/iodine/iodine.h +250 -55
  29. data/ext/iodine/iodine_arg_helper.h +329 -0
  30. data/ext/iodine/iodine_caller.h +234 -25
  31. data/ext/iodine/iodine_cli.h +434 -0
  32. data/ext/iodine/iodine_compression.h +312 -0
  33. data/ext/iodine/iodine_connection.h +3484 -34
  34. data/ext/iodine/iodine_core.h +363 -0
  35. data/ext/iodine/iodine_crypto.h +1441 -0
  36. data/ext/iodine/iodine_defer.h +225 -4
  37. data/ext/iodine/iodine_json.h +738 -4
  38. data/ext/iodine/iodine_listener.h +263 -0
  39. data/ext/iodine/iodine_log.h +297 -0
  40. data/ext/iodine/iodine_minimap.h +543 -0
  41. data/ext/iodine/iodine_mustache.h +461 -4
  42. data/ext/iodine/iodine_pubsub_eng.h +729 -0
  43. data/ext/iodine/iodine_pubsub_history.h +547 -0
  44. data/ext/iodine/iodine_pubsub_msg.h +228 -0
  45. data/ext/iodine/iodine_redis.h +322 -0
  46. data/ext/iodine/iodine_store.h +418 -16
  47. data/ext/iodine/iodine_threads.h +279 -0
  48. data/ext/iodine/iodine_tls.h +515 -6
  49. data/ext/iodine/iodine_utils.h +923 -0
  50. data/iodine.gemspec +37 -42
  51. data/lib/iodine/benchmark.rb +1690 -0
  52. data/lib/iodine/documentation.rb +3085 -0
  53. data/lib/iodine/version.rb +17 -1
  54. data/lib/iodine.rb +54 -255
  55. data/logo.svg +36 -0
  56. data/mustache.md +55 -0
  57. data/sig/iodine.rbs +4 -0
  58. metadata +58 -184
  59. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
  60. data/.github/workflows/ruby.yml +0 -42
  61. data/.gitignore +0 -20
  62. data/LIMITS.md +0 -41
  63. data/SPEC-PubSub-Draft.md +0 -159
  64. data/SPEC-WebSocket-Draft.md +0 -239
  65. data/bin/console +0 -22
  66. data/bin/info.md +0 -353
  67. data/bin/mustache_bench.rb +0 -100
  68. data/bin/poc/Gemfile.lock +0 -23
  69. data/bin/poc/README.md +0 -37
  70. data/bin/poc/config.ru +0 -66
  71. data/bin/poc/gemfile +0 -1
  72. data/bin/poc/www/index.html +0 -57
  73. data/examples/async_task.ru +0 -92
  74. data/examples/bates/README.md +0 -3
  75. data/examples/bates/config.ru +0 -342
  76. data/examples/bates/david+bold.pdf +0 -0
  77. data/examples/bates/public/drop-pdf.png +0 -0
  78. data/examples/bates/public/index.html +0 -600
  79. data/examples/echo.ru +0 -59
  80. data/examples/etag.ru +0 -16
  81. data/examples/hello.ru +0 -29
  82. data/examples/pubsub_engine.ru +0 -81
  83. data/examples/rack3.ru +0 -12
  84. data/examples/redis.ru +0 -70
  85. data/examples/shootout.ru +0 -73
  86. data/examples/sub-protocols.ru +0 -90
  87. data/examples/tcp_client.rb +0 -66
  88. data/examples/x-sendfile.ru +0 -14
  89. data/ext/iodine/fio.c +0 -11995
  90. data/ext/iodine/fio.h +0 -6371
  91. data/ext/iodine/fio_cli.c +0 -431
  92. data/ext/iodine/fio_cli.h +0 -189
  93. data/ext/iodine/fio_json_parser.h +0 -688
  94. data/ext/iodine/fio_siphash.c +0 -157
  95. data/ext/iodine/fio_siphash.h +0 -37
  96. data/ext/iodine/fio_tls.h +0 -129
  97. data/ext/iodine/fio_tls_missing.c +0 -649
  98. data/ext/iodine/fio_tls_openssl.c +0 -1106
  99. data/ext/iodine/fio_tmpfile.h +0 -50
  100. data/ext/iodine/fiobj.h +0 -44
  101. data/ext/iodine/fiobj4fio.h +0 -21
  102. data/ext/iodine/fiobj_ary.c +0 -333
  103. data/ext/iodine/fiobj_ary.h +0 -139
  104. data/ext/iodine/fiobj_data.c +0 -1187
  105. data/ext/iodine/fiobj_data.h +0 -167
  106. data/ext/iodine/fiobj_hash.c +0 -409
  107. data/ext/iodine/fiobj_hash.h +0 -176
  108. data/ext/iodine/fiobj_json.c +0 -622
  109. data/ext/iodine/fiobj_json.h +0 -68
  110. data/ext/iodine/fiobj_mem.h +0 -71
  111. data/ext/iodine/fiobj_mustache.c +0 -317
  112. data/ext/iodine/fiobj_mustache.h +0 -62
  113. data/ext/iodine/fiobj_numbers.c +0 -344
  114. data/ext/iodine/fiobj_numbers.h +0 -127
  115. data/ext/iodine/fiobj_str.c +0 -433
  116. data/ext/iodine/fiobj_str.h +0 -172
  117. data/ext/iodine/fiobject.c +0 -620
  118. data/ext/iodine/fiobject.h +0 -655
  119. data/ext/iodine/hpack.h +0 -1923
  120. data/ext/iodine/http.c +0 -2743
  121. data/ext/iodine/http.h +0 -1002
  122. data/ext/iodine/http1.c +0 -910
  123. data/ext/iodine/http1.h +0 -29
  124. data/ext/iodine/http1_parser.h +0 -878
  125. data/ext/iodine/http_internal.c +0 -1279
  126. data/ext/iodine/http_internal.h +0 -237
  127. data/ext/iodine/http_mime_parser.h +0 -350
  128. data/ext/iodine/iodine_caller.c +0 -230
  129. data/ext/iodine/iodine_connection.c +0 -941
  130. data/ext/iodine/iodine_defer.c +0 -420
  131. data/ext/iodine/iodine_fiobj2rb.h +0 -120
  132. data/ext/iodine/iodine_helpers.c +0 -282
  133. data/ext/iodine/iodine_helpers.h +0 -12
  134. data/ext/iodine/iodine_http.c +0 -1181
  135. data/ext/iodine/iodine_http.h +0 -23
  136. data/ext/iodine/iodine_json.c +0 -302
  137. data/ext/iodine/iodine_mustache.c +0 -567
  138. data/ext/iodine/iodine_pubsub.c +0 -580
  139. data/ext/iodine/iodine_pubsub.h +0 -26
  140. data/ext/iodine/iodine_rack_io.c +0 -273
  141. data/ext/iodine/iodine_rack_io.h +0 -20
  142. data/ext/iodine/iodine_store.c +0 -146
  143. data/ext/iodine/iodine_tcp.c +0 -346
  144. data/ext/iodine/iodine_tcp.h +0 -13
  145. data/ext/iodine/iodine_tls.c +0 -261
  146. data/ext/iodine/mustache_parser.h +0 -1546
  147. data/ext/iodine/redis_engine.c +0 -957
  148. data/ext/iodine/redis_engine.h +0 -79
  149. data/ext/iodine/resp_parser.h +0 -317
  150. data/ext/iodine/websocket_parser.h +0 -506
  151. data/ext/iodine/websockets.c +0 -743
  152. data/ext/iodine/websockets.h +0 -185
  153. data/lib/iodine/connection.rb +0 -61
  154. data/lib/iodine/json.rb +0 -42
  155. data/lib/iodine/mustache.rb +0 -113
  156. data/lib/iodine/pubsub.rb +0 -55
  157. data/lib/iodine/rack_utils.rb +0 -43
  158. data/lib/iodine/tls.rb +0 -16
  159. data/lib/rack/handler/iodine.rb +0 -33
  160. data/logo.png +0 -0
@@ -0,0 +1,3085 @@
1
+ # unless defined?(Iodine)
2
+
3
+ # Iodine is a high performance HTTP / WebSocket server and Evented Network Tool Library.
4
+ #
5
+ # In essence, Iodine is a partial Ruby port for the [facil.io](http://facil.io) C library.
6
+ #
7
+ # Here is a simple text echo server using Iodine (see full list at {Iodine::Connection}):
8
+ #
9
+ #
10
+ # require 'iodine'
11
+ #
12
+ # # Define the protocol for our service
13
+ # module EchoProtocol
14
+ #
15
+ # # Called when a new connection is opened.
16
+ # def on_open(client)
17
+ # # Write a welcome message
18
+ # puts "MY LOG: Client #{client.object_id} opened";
19
+ # client.write "Echo server running on Iodine #{Iodine::VERSION}.\r\n"
20
+ # end
21
+ #
22
+ # # this is called for incoming data - note data might be fragmented.
23
+ # def on_message(client, data)
24
+ # # write the data we received
25
+ # client.write "echo: #{data}"
26
+ # # close the connection when the time comes
27
+ # client.close if data =~ /^bye[\n\r]/i
28
+ # end
29
+ #
30
+ # # called if the connection is still open and the server is shutting down.
31
+ # def on_shutdown(client)
32
+ # # write the data we received
33
+ # client.write "Server going away\r\n"
34
+ # end
35
+ #
36
+ # # Called once all calls to `write` have been sent on the wire.
37
+ # def on_drained(client)
38
+ # puts "MY LOG: Client #{client.object_id} outgoing buffer now empty";
39
+ # end
40
+ #
41
+ # # Called when `ping` time has elapsed with no data transferred on the wire.
42
+ # def on_timeout(client)
43
+ # puts "MY LOG: Client #{client.object_id} timing out";
44
+ # # Write something to reset timeout.
45
+ # client.write "Server message: are you there?\r\n"
46
+ # end
47
+ #
48
+ # # Called when the connection is closed.
49
+ # def on_close(client)
50
+ # # Log closure
51
+ # puts "MY LOG: Client #{client.object_id} closed";
52
+ # end
53
+ #
54
+ # # We can use a singleton pattern,
55
+ # # as the Protocol object doesn't contain any per-connection data.
56
+ # extend self
57
+ # end
58
+ #
59
+ # # create the service instance, the block returns a connection handler.
60
+ # Iodine.listen(handler: EchoProtocol, service: :tcp)
61
+ # # start the service
62
+ # Iodine.threads = 1
63
+ # Iodine.start
64
+ #
65
+ #
66
+ #
67
+ # Methods for setting up and starting {Iodine} include {start}, {threads}, {threads=}, {workers} and {workers=}.
68
+ #
69
+ # Methods for setting startup / operational callbacks include {on_state}.
70
+ #
71
+ # Methods for asynchronous execution include {run}, {async} and {run_after}.
72
+ #
73
+ # Methods for application wide pub/sub include {subscribe}, {unsubscribe} and {publish}. Connection specific pub/sub methods are documented in the {Iodine::Connection} class).
74
+ #
75
+ # Methods for TCP/IP, Unix Sockets and HTTP connections include {listen} and {Iodine::Connection.new}.
76
+ #
77
+ # Note that the HTTP server supports both TCP/IP and Unix Sockets as well as SSE / WebSockets extensions.
78
+ #
79
+ # Iodine doesn't call {Iodine::Utils.monkey_patch} automatically, but doing so will improve Rack's performance.
80
+ #
81
+ # Please read the {file:README.md} file for an introduction to Iodine.
82
+ #
83
+ # == TLS Backend Selection
84
+ #
85
+ # Iodine supports two TLS backends:
86
+ #
87
+ # 1. *Embedded TLS 1.3* (`:iodine`) - A lightweight, dependency-free TLS 1.3
88
+ # implementation built into facil.io. Always available, no external libraries required.
89
+ #
90
+ # 2. *OpenSSL* (`:openssl`) - Uses the system's OpenSSL library. Requires
91
+ # OpenSSL version 3.0 or higher. Used as default when available.
92
+ #
93
+ # === Runtime Selection (Recommended)
94
+ #
95
+ # Set the global default TLS backend at runtime:
96
+ #
97
+ # # Switch to embedded TLS 1.3
98
+ # Iodine::TLS.default = :iodine
99
+ #
100
+ # # Switch to OpenSSL
101
+ # Iodine::TLS.default = :openssl
102
+ #
103
+ # === Environment Variable
104
+ #
105
+ # Set before starting iodine:
106
+ #
107
+ # IODINE_MTLS=1 iodine
108
+ #
109
+ # === Command Line
110
+ #
111
+ # iodine -mtls
112
+ #
113
+ # === Compile-time Default
114
+ #
115
+ # Force embedded TLS as the compile-time default:
116
+ #
117
+ # IODINE_USE_EMBEDDED_TLS=1 gem install iodine
118
+ #
119
+ # === Checking Available Backends
120
+ #
121
+ # Iodine::TLS.default # => :openssl or :iodine (current default)
122
+ # Iodine::TLS.default = :iodine # Set the default
123
+ # Iodine::TLS::OPENSSL_AVAILABLE # => true if OpenSSL is compiled in
124
+ # Iodine::TLS::EMBEDDED_AVAILABLE # => true (always available)
125
+ # Iodine::TLS::SUPPORTED # => true (always - at least embedded is available)
126
+ #
127
+ # *Note*: The embedded TLS 1.3 implementation has not been independently audited.
128
+ #
129
+ module Iodine
130
+ # Returns the number of worker threads per worker process.
131
+ #
132
+ # Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc').
133
+ #
134
+ # @return [Integer] the number of threads (defaults to -4 if not explicitly configured)
135
+ def self.threads; end
136
+
137
+ # Sets the number of worker threads per worker process.
138
+ #
139
+ # Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc').
140
+ #
141
+ # @param threads [Integer] the number of threads
142
+ # @return [Integer] the current CLI value for threads (may differ from input if set failed)
143
+ # @raise [TypeError] if threads is not a Fixnum (only when called from master process with non-nil value)
144
+ # @note Can only be set in the master process before starting the reactor.
145
+ # @note If called from a worker process or with `nil`, logs an error but does not raise an exception.
146
+ # The return value will be the current (unchanged) CLI value.
147
+ def self.threads=(threads); end
148
+
149
+ # Returns the number of worker processes.
150
+ #
151
+ # Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc').
152
+ # A value of 0 means single-process mode (no forking).
153
+ #
154
+ # @return [Integer] the number of workers (defaults to -2 if not explicitly configured)
155
+ def self.workers; end
156
+
157
+ # Sets the number of worker processes.
158
+ #
159
+ # Negative values signify fractions of CPU core count (-1 ~= all CPU cores, -2 ~= half CPU cores, etc').
160
+ # Set to 0 for single-process mode, or > 0 for cluster mode.
161
+ #
162
+ # @param workers [Integer] the number of worker processes
163
+ # @return [Integer] the current CLI value for workers (may differ from input if set failed)
164
+ # @raise [TypeError] if workers is not a Fixnum (only when called from master process with non-nil value)
165
+ # @note Can only be set in the master process before starting the reactor.
166
+ # @note If called from a worker process or with `nil`, logs an error but does not raise an exception.
167
+ # The return value will be the current (unchanged) CLI value.
168
+ def self.workers=(workers); end
169
+
170
+ # Starts the Iodine IO reactor.
171
+ #
172
+ # This is the main entry point that begins the event loop. It:
173
+ # - Attaches the async thread pool
174
+ # - Sets rack.multithread/rack.multiprocess environment values
175
+ # - Logs startup information (version, workers, threads)
176
+ # - Starts the IO event loop (blocking until stopped)
177
+ #
178
+ # @return [self]
179
+ #
180
+ # @example
181
+ # Iodine.listen(url: "http://0.0.0.0:3000", handler: MyApp)
182
+ # Iodine.start
183
+ def self.start; end
184
+
185
+ # Stops the current process' IO reactor.
186
+ #
187
+ # If this is a worker process, the process will exit and if Iodine is running
188
+ # in cluster mode, a new worker will be spawned by the master process.
189
+ #
190
+ # @return [self]
191
+ def self.stop; end
192
+
193
+ # Returns `true` if this is the master (root) process.
194
+ #
195
+ # In cluster mode, the master process spawns and monitors worker processes.
196
+ # In single-process mode, the process is both master and worker.
197
+ #
198
+ # @return [Boolean]
199
+ def self.master?; end
200
+
201
+ # Returns `true` if this is a worker process.
202
+ #
203
+ # Worker processes handle actual client connections and requests.
204
+ # In single-process mode, the process is both master and worker.
205
+ #
206
+ # @return [Boolean]
207
+ def self.worker?; end
208
+
209
+ # Returns `true` if the IO reactor is currently running.
210
+ #
211
+ # @return [Boolean]
212
+ def self.running?; end
213
+
214
+ # Returns the server's secret as a 64-byte binary string.
215
+ #
216
+ # The secret is used for cryptographic operations like session signing,
217
+ # CSRF tokens, and other security-sensitive operations. It's derived
218
+ # from the secret key set via `Iodine.secret=` or auto-generated.
219
+ #
220
+ # If no secret was previously set, Iodine will use the environment variable `SECRET` if available.
221
+ # Otherwise, a random secret is produced.
222
+ #
223
+ # @return [String] 64-byte binary String containing the server secret
224
+ #
225
+ # @note It is recommended to set the secret as a Hex encoded String environment variable named `SECRET` before starting Ruby.
226
+ def self.secret; end
227
+
228
+ # Sets a new server secret based on the provided key.
229
+ #
230
+ # The key is hashed/expanded to produce the internal 512-bit secret.
231
+ # This should be set before starting the server for consistent
232
+ # cryptographic operations across restarts.
233
+ #
234
+ # @param secret_to_be_hashed [String] the secret key
235
+ # @return [String] the new 64-byte server secret
236
+ # @raise [TypeError] if secret_to_be_hashed is not a String
237
+ def self.secret=(secret_to_be_hashed); end
238
+
239
+ # Returns the current graceful shutdown timeout in milliseconds.
240
+ #
241
+ # During graceful shutdown, the server waits for active connections
242
+ # to complete before forcing termination.
243
+ #
244
+ # @return [Integer] timeout in milliseconds
245
+ def self.shutdown_timeout; end
246
+
247
+ # Sets the graceful shutdown timeout in milliseconds.
248
+ #
249
+ # Maximum allowed value is 5 minutes (300,000 ms).
250
+ #
251
+ # @param milliseconds [Integer] timeout in milliseconds
252
+ # @return [Integer] the new timeout value
253
+ # @raise [TypeError] if milliseconds is not a Fixnum
254
+ # @raise [RangeError] if milliseconds exceeds 5 minutes
255
+ #
256
+ # @example
257
+ # Iodine.shutdown_timeout = 30000 # 30 seconds
258
+ def self.shutdown_timeout=(milliseconds); end
259
+
260
+ # Listens on the `url` to connections requesting `service`.
261
+ #
262
+ # The `handler` handles the following events and supports the following callbacks:
263
+ #
264
+ # | | |
265
+ # |---|---|
266
+ # | `on_http(client)` | Called when an HTTP request arrives. |
267
+ # | `on_authenticate_websocket(client)` | Called when a WebSocket Upgrade request is detected, **must** return `true` or the request is denied. |
268
+ # | `on_open(client)` | Called when a WebSocket or `raw`/`tcp`connection had been established. |
269
+ # | `on_message(client, data)` | Called when a WebSocket message arrives. For `raw`/`tcp` connections, `data` contains the part of the byte stream that was read by Iodine. |
270
+ # | `on_drained(client)` | Called when all the calls to `client.write` have been completed (the outgoing buffer has been sent). |
271
+ # | `on_timeout(client)` | Called when `ping` time has elapsed with no data was exchanged on the wire. |
272
+ # | `on_shutdown(client)` | Called if the connection is still open and the server is shutting down. |
273
+ # | `on_close(client)` | Called when the connection is closed |
274
+ # | `on_authenticate_sse(client)` | Called when an Event Source (SSE) request is detected, **must** return `true` or the request is denied. |
275
+ # | `on_eventsource_reconnect(client)` | Called when an Event Source (SSE) request contains re-connection data. |
276
+ # | `on_eventsource(client, event)` | Called when an Event Source (SSE) event is received (client mode). |
277
+ # | `on_authenticate(client)` | **MAY** be defined instead of `on_authenticate_websocket` and `on_authenticate_sse` to unify the logic of both authentication callbacks. |
278
+ #
279
+ # @param options [Hash] the options for the listener
280
+ # @option options [String, nil] :url The address to listen at (or `nil`).
281
+ # @option options [Object] :handler The handler (callback) object / NeoRack application / Rack application.
282
+ # @option options [Symbol, String, nil] :service The service to be listening to (`http`, `https`, `ws`, `tcp`, `unix`). Can be provided as part of the `url`'s `scheme`.
283
+ # @option options [Iodine::TLS, nil] :tls An Iodine::TLS instance.
284
+ # @option options [Symbol, nil] :tls_io TLS backend selection: `:iodine` (embedded TLS 1.3), `:openssl` (OpenSSL >= 3.0), or `nil` (compile-time default).
285
+ # @option options [String, nil] :public (HTTP only) Public folder for static file service.
286
+ # @option options [Integer, nil] :max_age (HTTP only) The default maximum age for caching (when the etag header is provided).
287
+ # @option options [Integer, nil] :max_header_size (HTTP only) The maximum size for HTTP headers.
288
+ # @option options [Integer, nil] :max_line_len (HTTP only) The maximum size for a single HTTP header.
289
+ # @option options [Integer, nil] :max_body_size (HTTP only) The maximum size for an HTTP payload.
290
+ # @option options [Integer, nil] :max_msg_size (WebSocket only) The maximum size for a WebSocket message.
291
+ # @option options [Integer, nil] :timeout (HTTP only) `keep-alive` timeout in seconds (0..255).
292
+ # @option options [Integer, nil] :ping Connection timeout (WebSocket / `raw` / `tcp`) in seconds (0..255). A class or module handler's `TIMEOUT` constant (0..255) overrides it.
293
+ # @option options [Boolean, nil] :log (HTTP only) If `true`, logs `http` requests (default: `false`, unless the `--log` CLI flag is set).
294
+ # @option options [Boolean, nil] :dynamic_deflate (HTTP only) If `true`, dynamically deflates non-streaming HTTP responses (default: `true`, unless the `--no-dynamic-deflate` CLI flag is set).
295
+ # @option options [Boolean, nil] :websocket_deflate (WebSocket only) If `true`, enables permessage-deflate (default: `true`, unless the `--no-websocket-deflate` CLI flag is set).
296
+ # @return [Iodine::Listener] the listener object
297
+ #
298
+ # @note Either a `handler` or a `block` (Rack App) **must** be provided.
299
+ #
300
+ # @example HTTP server
301
+ # Iodine.listen(url: "http://0.0.0.0:3000", handler: MyApp)
302
+ #
303
+ # @example WebSocket server with TLS
304
+ # Iodine.listen(url: "wss://0.0.0.0:3000", handler: MyWSHandler, tls_io: :iodine)
305
+ def self.listen(**options); end
306
+
307
+ # Sets a block of code to run when Iodine's core state is updated.
308
+ #
309
+ # @param state [Symbol] event the state event for which the block should run (see list).
310
+ #
311
+ # The state event Symbol can be any of the following:
312
+ #
313
+ # | | |
314
+ # |---|---|
315
+ # | `:pre_start` | the block will be called once before starting up the IO reactor. |
316
+ # | `:before_fork` | the block will be called before each time the IO reactor forks a new worker. |
317
+ # | `:after_fork` | the block will be called after each fork (both in parent and workers). |
318
+ # | `:enter_child` | the block will be called by a worker process right after forking. |
319
+ # | `:enter_master` | the block will be called by the master process after spawning a worker (after forking). |
320
+ # | `:start` | the block will be called every time a *worker* process starts. In single process mode, the master process is also a worker. |
321
+ # | `:idle` | the block will be called every time the IO reactor enters an idling state (on all processes). |
322
+ # | `:parent_crush` | the block will be called by each worker the moment it detects the master process crashed. |
323
+ # | `:child_crush` | the block will be called by the parent (master) after a worker process crashed. |
324
+ # | `:shutdown` | the block will be called before starting the shutdown sequence. |
325
+ # | `:stop` | the block will be called just before stopping iodine (both on child and parent processes). |
326
+ # | `:exit` | the block will be called during iodine library cleanup, similar to a Ruby `at_exit` callback. |
327
+ #
328
+ # @return [Proc] the block
329
+ #
330
+ # @note Code runs in both the parent and the child.
331
+ #
332
+ # @example
333
+ # Iodine.on_state(:start) { puts "Worker started!" }
334
+ def self.on_state(state, &block); end
335
+
336
+ # Runs a block of code in the main IO thread (adds the code to the event queue).
337
+ #
338
+ # Always returns the block of code to executed (Proc object).
339
+ #
340
+ # Code will be executed only while Iodine is running (after {Iodine.start}).
341
+ #
342
+ # Code blocks that where scheduled to run before Iodine enters cluster mode will run on all child processes.
343
+ #
344
+ # The code will always run in single-threaded mode, running on the main IO thread used by Iodine.
345
+ #
346
+ # @return [Proc] the block
347
+ #
348
+ # @note Blocking code MUST NOT be used in the block passed to this method,
349
+ # or Iodine's IO will hang while waiting for the blocking code to finish.
350
+ # @note Method also accepts a method object if passed as a parameter.
351
+ def self.run(&block); end
352
+
353
+ # Runs a block of code in a worker thread (adds the code to the event queue).
354
+ #
355
+ # Always returns the block of code to executed (Proc object).
356
+ #
357
+ # Code will be executed only while Iodine is running (after {Iodine.start}).
358
+ #
359
+ # Code blocks that where scheduled to run before Iodine enters cluster mode will run on all child processes.
360
+ #
361
+ # The code may run in parallel with other calls to `run` or `async`, as it will run in one of the worker threads.
362
+ #
363
+ # @return [Proc] the block
364
+ #
365
+ # @note Method also accepts a method object if passed as a parameter.
366
+ def self.async(&block); end
367
+
368
+ # Runs the required block after the specified number of milliseconds have passed.
369
+ #
370
+ # The block will run in the main IO thread (adds the code to the event queue).
371
+ #
372
+ # Time is counted only once Iodine started running (using {Iodine.start}).
373
+ #
374
+ # @param milliseconds [Integer] the number of milliseconds between event repetitions.
375
+ # @param repetitions [Integer] the number of event repetitions. Defaults to 1 (performed once). Set to 0 for never ending.
376
+ # @return [Proc] a copy of the block object
377
+ #
378
+ # @note The event will repeat itself until the number of repetitions had been depleted or until the event returns `false`.
379
+ #
380
+ # @example Run once after 1 second
381
+ # Iodine.run_after(1000) { puts "1 second passed!" }
382
+ #
383
+ # @example Run every 5 seconds forever
384
+ # Iodine.run_after(5000, 0) { puts "tick" }
385
+ def self.run_after(milliseconds, repetitions = 1, &block); end
386
+
387
+ # Publishes a message to a combination of a channel (String) and filter (number).
388
+ #
389
+ # @param channel [String, nil] the channel name to publish to.
390
+ # @param filter [Integer] the filter to publish to (Number < 32,767).
391
+ # @param message [String] the actual message to publish.
392
+ # @param engine [Iodine::PubSub::Engine, nil] the pub/sub engine to use (if not the default one).
393
+ # @return [Boolean] `true` on success
394
+ #
395
+ # @example
396
+ # Iodine.publish("channel_name", 0, "payload")
397
+ # Iodine.publish(channel: "name", message: "payload")
398
+ def self.publish(channel = nil, message = nil, filter = 0, engine = nil); end
399
+
400
+ # Subscribes to a combination of a channel (String) and filter (number).
401
+ #
402
+ # @param channel [String, nil] the subscription's channel name.
403
+ # @param filter [Integer] the subscription's filter (Number < 32,767).
404
+ # @param since [Integer] replay cached messages since this timestamp in milliseconds
405
+ # (requires {Iodine::PubSub::History.cache} to be called first).
406
+ # @param callback [Proc, nil] an **optional** object that answers to call and accepts a single argument (the message structure).
407
+ # @return [Iodine] returns the Iodine module (self). For a handle that can be cancelled or re-targeted, use {Iodine::PubSub::Subscription} directly.
408
+ #
409
+ # @note Either a proc or a block **must** be provided for global subscriptions.
410
+ # @note **One subscription per channel per context.** A "context" is either a specific
411
+ # IO object (e.g., a WebSocket connection) or the global context (no IO). Calling
412
+ # `subscribe` on the same channel within the same context silently replaces any
413
+ # previously registered callback for that channel — you do **not** get two callbacks.
414
+ # To route a published message to multiple handlers, use multiple IO contexts
415
+ # (e.g., multiple WebSocket connections each with their own subscription).
416
+ #
417
+ # @example Basic subscription
418
+ # Iodine.subscribe("name") { |msg| puts msg.message }
419
+ # Iodine.subscribe(channel: "name", filter: 1) { |msg| puts msg.message }
420
+ #
421
+ # @example Second subscribe replaces the first in the same context
422
+ # Iodine.subscribe("ch") { |msg| puts "first" } # registered
423
+ # Iodine.subscribe("ch") { |msg| puts "second" } # replaces the first
424
+ # Iodine.publish("ch", 0, "hello") # only "second" is printed
425
+ #
426
+ # @example Subscription with history replay
427
+ # # First enable the memory cache
428
+ # Iodine::PubSub::History.cache
429
+ # # Subscribe and replay messages from the last 60 seconds
430
+ # since_ms = (Time.now.to_i - 60) * 1000
431
+ # Iodine.subscribe(channel: "chat", since: since_ms) { |msg| puts msg.message }
432
+ def self.subscribe(channel = nil, filter = 0, since = 0, &callback); end
433
+
434
+ # Un-Subscribes from a combination of a channel (String) and filter (number).
435
+ #
436
+ # @param channel [String, nil] the subscription's channel name.
437
+ # @param filter [Integer] the subscription's filter (Number < 32,767).
438
+ # @return [Boolean] `true` on success
439
+ #
440
+ # @example
441
+ # Iodine.unsubscribe("name")
442
+ # Iodine.unsubscribe(channel: "name", filter: 1)
443
+ def self.unsubscribe(channel = nil, filter = 0); end
444
+
445
+ # Adds an `on_http` implementation to `handler` that will route requests to their proper REST/CRUD callback.
446
+ #
447
+ # @param handler [Object] the handler object to modify
448
+ # @return [Object] the modified handler
449
+ #
450
+ # @see Iodine::Connection::ResourceHandler
451
+ def self.make_resource(handler); end
452
+
453
+ #######################
454
+
455
+ # {Iodine::Logger} routes Ruby log messages through the facil.io C STL
456
+ # logging system (FIO_LOG_*), sharing the C core's stderr sink, level
457
+ # prefixes and the global log level ({Iodine::Logger.level}).
458
+ #
459
+ # Each argument is logged as its own message. Arguments that answer `call`
460
+ # (e.g. a Proc or lambda) are invoked and their return value is logged.
461
+ # If a block is given, it is called last and its return value is logged
462
+ # (Rack SPEC compatible logger behavior). Nothing is evaluated while the
463
+ # level is silenced by the current verbosity (lazy evaluation).
464
+ #
465
+ # Rack applications receive this module as `env['rack.logger']`.
466
+ #
467
+ # Log levels (constants, e.g. {Iodine::Logger::DEBUG}):
468
+ # - 0: NONE - No logging
469
+ # - 1: FATAL - Fatal errors only
470
+ # - 2: ERROR - Errors and above
471
+ # - 3: WARN - Warnings and above
472
+ # - 4: INFO - Info and above (default)
473
+ # - 5: DEBUG - Debug and above
474
+ #
475
+ # @example
476
+ # Iodine::Logger.level = :debug
477
+ # Iodine::Logger.debug "connected to:", -> { expensive_host_lookup }
478
+ # Iodine::Logger.info { "lazy message" }
479
+ # Iodine::Logger << "info level message"
480
+ module Logger
481
+ # Logs each message at fatal level (level 1).
482
+ #
483
+ # @param messages [Array<Object>] the messages (converted with #to_s);
484
+ # callable arguments are invoked and their return value logged
485
+ # @yield called last; the return value is logged
486
+ # @return [nil]
487
+ def self.fatal(*messages, &block); end
488
+
489
+ # Logs each message at error level (level 2).
490
+ #
491
+ # @param messages [Array<Object>] the messages (converted with #to_s);
492
+ # callable arguments are invoked and their return value logged
493
+ # @yield called last; the return value is logged
494
+ # @return [nil]
495
+ def self.error(*messages, &block); end
496
+
497
+ # Logs each message at warning level (level 3).
498
+ #
499
+ # @param messages [Array<Object>] the messages (converted with #to_s);
500
+ # callable arguments are invoked and their return value logged
501
+ # @yield called last; the return value is logged
502
+ # @return [nil]
503
+ def self.warn(*messages, &block); end
504
+
505
+ # Logs each message at info level (level 4).
506
+ #
507
+ # @param messages [Array<Object>] the messages (converted with #to_s);
508
+ # callable arguments are invoked and their return value logged
509
+ # @yield called last; the return value is logged
510
+ # @return [nil]
511
+ def self.info(*messages, &block); end
512
+
513
+ # Logs each message at debug level (level 5).
514
+ #
515
+ # @param messages [Array<Object>] the messages (converted with #to_s);
516
+ # callable arguments are invoked and their return value logged
517
+ # @yield called last; the return value is logged
518
+ # @return [nil]
519
+ def self.debug(*messages, &block); end
520
+
521
+ # Alias for {info} - logs the message at info level (level 4).
522
+ #
523
+ # @param message [Object] the message (converted with #to_s)
524
+ # @return [nil]
525
+ def self.<<(message); end
526
+
527
+ # Returns the current logging level (the process-wide C STL log level).
528
+ #
529
+ # @return [Integer] the current log level (0-5)
530
+ def self.level; end
531
+
532
+ # Sets the current logging level (the process-wide C STL log level).
533
+ #
534
+ # @param level [Integer, Symbol] the log level: 0-5, or one of
535
+ # `:none`, `:fatal`, `:error`, `:warn`, `:info`, `:debug`
536
+ # @return [Integer, Symbol] the given level
537
+ # @raise [TypeError] if level is not an Integer or Symbol
538
+ # @raise [ArgumentError] if the level value is unknown or out of range
539
+ #
540
+ # @example
541
+ # Iodine::Logger.level = Iodine::Logger::DEBUG
542
+ # Iodine::Logger.level = :warn
543
+ def self.level=(level); end
544
+ end
545
+
546
+ #######################
547
+
548
+ # {Iodine::JSON} exposes the {Iodine::Connection#write} fallback behavior when called with non-String objects.
549
+ #
550
+ # The fallback behavior is similar to (though faster than) calling:
551
+ #
552
+ # client.write(Iodine::JSON.stringify(data))
553
+ #
554
+ # If you want to work with JSON, consider using the `oj` gem.
555
+ #
556
+ # This API is mostly to test for Iodine JSON input/output errors and reflects what the C layer sees.
557
+ #
558
+ # ## Performance
559
+ #
560
+ # Performance... could be better.
561
+ #
562
+ # Converting Ruby objects into a JSON String (Strinfigying) should be fast even though the String data is copied twice, once to C and then to Ruby.
563
+ #
564
+ # However, Converting a JSON object into Ruby Objects is currently slow and it is better to use the `oj` gem or even the Ruby builtin parser.
565
+ #
566
+ # The reason is simple - the implementation is designed to create C objects (C Hash Maps, C Arrays, etc'), not Ruby objects. When converting from a String to Ruby Objects, the data is copied twice, once to C and then to Ruby.
567
+ #
568
+ # This especially effects parsing, where more objects are allocated, whereas {Iodine::JSON.stringify} only (re)copies the String data which is a single continuous block of memory.
569
+ #
570
+ # That's why {Iodine::JSON.stringify} is significantly faster than the Ruby `object.to_json` approach, yet slower than `JSON.parse(json_string)`.
571
+ #
572
+ # Performance depends on architecture and compiler used. Please measure:
573
+ #
574
+ # require 'iodine/benchmark'
575
+ # Iodine::Benchmark.json
576
+ module JSON
577
+ # Accepts a JSON String and returns a Ruby object.
578
+ #
579
+ # @param json_string [String] the JSON string to parse
580
+ # @return [Object] the parsed Ruby object
581
+ def self.parse(json_string); end
582
+
583
+ # Accepts a Ruby object and returns a JSON String.
584
+ #
585
+ # @param ruby_object [Object] the Ruby object to stringify
586
+ # @return [String] the JSON string
587
+ def self.stringify(ruby_object); end
588
+
589
+ # Accepts a Ruby object and returns a prettier JSON String.
590
+ #
591
+ # @param ruby_object [Object] the Ruby object to stringify
592
+ # @return [String] the formatted JSON string
593
+ def self.beautify(ruby_object); end
594
+ end
595
+
596
+ #######################
597
+
598
+ # {Iodine::Mustache} is a lighter implementation of the mustache template rendering gem, with a focus on a few minor security details:
599
+ #
600
+ # 1. HTML escaping is more aggressive, increasing XSS protection. Read why at: [wonko.com/post/html-escaping](https://wonko.com/post/html-escaping).
601
+ #
602
+ # 2. Dot notation is tested in whole as well as in part (i.e. `user.name.first` will be tested as is, than the couplet `user`, `name.first` and than as each `user`, `name` , `first`), allowing for the Hash data to contain keys with dots while still supporting dot notation shortcuts.
603
+ #
604
+ # 3. Less logic: i.e., lambdas / procs do not automatically invoke a re-rendering... I'd remove them completely as unsafe, but for now there's that.
605
+ #
606
+ # 4. Improved Protection against Endless Recursion: i.e., Partial templates reference themselves when recursively nested (instead of being recursively re-loaded); and Partial's context is limited to their starting point's context (cannot access parent context).
607
+ #
608
+ # It wasn't designed specifically for speed or performance... but it ended up being significantly faster.
609
+ #
610
+ # ## Usage
611
+ #
612
+ # This approach to Mustache templates may require more forethought when designing either the template or the context's data format, however it should force implementations to be more secure and performance aware.
613
+ #
614
+ # Approach:
615
+ #
616
+ # require 'iodine'
617
+ # # One-off rendering of (possibly dynamic) template:
618
+ # result = Iodine::Mustache.render(template: "{{foo}}", ctx: {foo: "bar"}) # => "bar"
619
+ # # caching of parsed template data for multiple render operations:
620
+ # view = Iodine::Mustache.new(file: "./views/foo.mustache", template: "{{foo}}")
621
+ # results = Array.new(100) {|i| view.render(foo: "bar#{i}") } # => ["bar0", "bar1", ...]
622
+ #
623
+ # ## Performance
624
+ #
625
+ # Performance may differ according to architecture and compiler used. Please measure:
626
+ #
627
+ # require 'iodine/benchmark'
628
+ # Iodine::Benchmark.mustache
629
+ #
630
+ # **Note**: the benchmark requires the `mustache` gem, which could be installed using:
631
+ #
632
+ # gem install mustache
633
+ #
634
+ # See also {file:./mustache.md}.
635
+ class Mustache
636
+ # Loads a template file and compiles it into a flattened instruction tree.
637
+ #
638
+ # Iodine::Mustache.new(file = nil, template = nil, on_yaml = nil, &block)
639
+ #
640
+ # @param file [String] a file name for the mustache template.
641
+ # @param template [String] the content of the mustache template - if `nil` or missing, `file` content will be loaded from disk.
642
+ # @param on_yaml [Proc] (**optional**) accepts a YAML front-matter String.
643
+ # @param block [Block] to be used as an implicit `on_yaml` (if missing).
644
+ # @return [Iodine::Mustache] returns an Iodine::Mustache object with the provided template ready for rendering.
645
+ #
646
+ # @note Either the file or template argument (or both) must be provided.
647
+ def initialize(file = nil, template = nil, on_yaml = nil, &block); end
648
+
649
+ # Renders the template given at initialization with the provided context.
650
+ #
651
+ # m.render(ctx)
652
+ #
653
+ # @param ctx [Hash] the top level context for the template data.
654
+ # @return [String] returns a String containing the rendered template.
655
+ def render(ctx = nil); end
656
+
657
+ # Loads a template file and renders it into a String.
658
+ #
659
+ # Iodine::Mustache.render(file = nil, template = nil, ctx = nil, on_yaml = nil)
660
+ #
661
+ # @param file [String] a file name for the mustache template.
662
+ # @param template [String] the content of the mustache template - if `nil` or missing, `file` content will be loaded from disk.
663
+ # @param ctx [Hash] the top level context for the template data.
664
+ # @param on_yaml [Proc] (**optional**) accepts a YAML front-matter String.
665
+ # @param block [Block] to be used as an implicit \p on_yaml (if missing).
666
+ # @return [String] returns a String containing the rendered template.
667
+ #
668
+ # @note Either the file or template argument (or both) must be provided.
669
+ def self.render(file = nil, template = nil, ctx = nil, on_yaml = nil, &block); end
670
+ end
671
+
672
+ #######################
673
+
674
+ # These are some utility, unescaping and decoding helpers provided by Iodine.
675
+ #
676
+ # These **should** be faster then their common Ruby / Rack alternative, but this may depend on your own machine and Ruby version.
677
+ #
678
+ # require 'iodine/benchmark'
679
+ # Iodine::Benchmark.utils
680
+ #
681
+ # After testing the performance, consider calling {Iodine::Utils.monkey_patch} if using Rack.
682
+ module Utils
683
+ # Encodes a String using percent encoding (i.e., URI encoding).
684
+ #
685
+ # @param str [String] the string to encode
686
+ # @return [String] the encoded string
687
+ def self.escape_path(str); end
688
+
689
+ # Encodes a String in place using percent encoding (i.e., URI encoding).
690
+ #
691
+ # @param str [String] the string to encode in place
692
+ # @return [String] the encoded string
693
+ def self.escape_path!(str); end
694
+
695
+ # Decodes percent encoding, including the `%uxxxx` JavaScript extension.
696
+ #
697
+ # @param str [String] the string to decode
698
+ # @param encoding [String, Encoding, nil] target string encoding (default: UTF-8)
699
+ # @return [String] the decoded string
700
+ def self.unescape_path(str, encoding = nil); end
701
+
702
+ # Decodes percent encoding in place, including the `%uxxxx` JavaScript.
703
+ #
704
+ # @param str [String] the string to decode in place
705
+ # @return [String] the decoded string
706
+ def self.unescape_path!(str); end
707
+
708
+ # Encodes a String using percent encoding (i.e., URI encoding).
709
+ #
710
+ # @param str [String] the string to encode
711
+ # @return [String] the encoded string
712
+ def self.escape(str); end
713
+
714
+ # Encodes a String using percent encoding (i.e., URI encoding).
715
+ #
716
+ # @param str [String] the string to encode in place
717
+ # @return [String] the encoded string
718
+ def self.escape!(str); end
719
+
720
+ # Decodes percent encoding, including the `%uxxxx` JavaScript extension and converting `+` to spaces.
721
+ #
722
+ # @param str [String] the string to decode
723
+ # @param encoding [String, Encoding, nil] target string encoding (default: UTF-8)
724
+ # @return [String] the decoded string
725
+ def self.unescape(str, encoding = nil); end
726
+
727
+ # Decodes percent encoding in place, including the `%uxxxx` JavaScript extension and converting `+` to spaces.
728
+ #
729
+ # @param str [String] the string to decode in place
730
+ # @return [String] the decoded string
731
+ def self.unescape!(str); end
732
+
733
+ # Escapes String using HTML escape encoding.
734
+ #
735
+ # @param str [String] the string to escape
736
+ # @return [String] the escaped string
737
+ #
738
+ # @note This function may significantly increase the number of escaped characters when compared to the native implementation.
739
+ def self.escape_html(str); end
740
+
741
+ # Escapes String in place using HTML escape encoding.
742
+ #
743
+ # @param str [String] the string to escape in place
744
+ # @return [String] the escaped string
745
+ #
746
+ # @note This function may significantly increase the number of escaped characters when compared to the native implementation.
747
+ def self.escape_html!(str); end
748
+
749
+ # Decodes an HTML escaped String.
750
+ #
751
+ # @param str [String] the string to decode
752
+ # @param encoding [String, Encoding, nil] target string encoding (default: UTF-8)
753
+ # @return [String] the decoded string
754
+ def self.unescape_html(str, encoding = nil); end
755
+
756
+ # Decodes an HTML escaped String in place.
757
+ #
758
+ # @param str [String] the string to decode in place
759
+ # @return [String] the decoded string
760
+ def self.unescape_html!(str); end
761
+
762
+ # Takes a Time object and returns a String conforming to RFC 2109.
763
+ #
764
+ # @param time [Time] the time object
765
+ # @return [String] the formatted time string
766
+ def self.rfc2109(time); end
767
+
768
+ # Takes a Time object and returns a String conforming to RFC 2822.
769
+ #
770
+ # @param time [Time] the time object
771
+ # @return [String] the formatted time string
772
+ def self.rfc2822(time); end
773
+
774
+ # Takes a Time object and returns a String conforming to RFC 7231.
775
+ #
776
+ # @param time [Time] the time object
777
+ # @return [String] the formatted time string
778
+ def self.time2str(time); end
779
+
780
+ # Securely compares two String objects to see if they are equal.
781
+ #
782
+ # Designed to be secure against timing attacks when both String objects are of the same length.
783
+ #
784
+ # @param a [String] the first string
785
+ # @param b [String] the second string
786
+ # @return [Boolean] `true` if equal, `false` otherwise
787
+ def self.secure_compare(a, b); end
788
+
789
+ # Adds the `Iodine::Utils` methods that are similar to the `Rack::Utils` module to the module(s) passed as arguments.
790
+ #
791
+ # If no modules were passed to the `monkey_patch` method, `Rack::Utils` will be monkey patched.
792
+ #
793
+ # @param to_patch [Module] the module(s) to patch
794
+ # @return [self]
795
+ def self.monkey_patch(to_patch = Rack::Utils); end
796
+
797
+ # Returns a String with the requested length of random bytes.
798
+ #
799
+ # Uses high entropy seeding auto-cycles, but isn't a tested cryptographic CPRNG.
800
+ #
801
+ # @param bytes [Integer] the number of random bytes (default: 16)
802
+ # @return [String] a String containing the requested length of random bytes
803
+ def self.random(bytes = 16); end
804
+
805
+ # Generates a UUID using either random `secret` String, a random `info` String, both or none (a deterministic UUID), depending on the number parameters supplied.
806
+ #
807
+ # @param secret [String, nil] (**optional**) Adds salt to the UUID randomized number.
808
+ # @param info [String, nil] (**optional**) Adds salt to the UUID randomized number.
809
+ # @return [String] in UUID format
810
+ def self.uuid(secret = nil, info = nil); end
811
+
812
+ # Returns new Timed-One-Time-Password (TOTP), in Google format (6 digit Number with a 30 second validity).
813
+ #
814
+ # @param secret [String] the secret OTP for which the time based (T)OTP should be created.
815
+ # @param offset [Integer] (**optional**) the number of "steps" backwards in time. Allows for testing of expired TOTPs.
816
+ # @param interval [Integer] (**optional**) time window in seconds (default: 30).
817
+ # @return [Integer] returns a 6 digit Number based on the secret provided and the current time.
818
+ #
819
+ # @example
820
+ # code = Iodine::Utils.totp(secret: my_secret)
821
+ # code = Iodine::Utils.totp(secret: my_secret, offset: -1) # previous window
822
+ def self.totp(secret:, offset: 0, interval: 30); end
823
+
824
+ # Generates a new TOTP secret suitable for Google Authenticator.
825
+ #
826
+ # The secret uses random bytes from the system CSPRNG (falling back to the
827
+ # facil.io PRNG, like {secure_random}), encoded in Base32 (uppercase, no
828
+ # padding) for compatibility with authenticator apps.
829
+ #
830
+ # @param len [Integer] (**optional**) Length of the secret in bytes (default: 20, range: 10-64)
831
+ # @return [String] Base32 encoded secret suitable for QR codes and authenticator apps
832
+ #
833
+ # @example
834
+ # secret = Iodine::Utils.totp_secret
835
+ # secret = Iodine::Utils.totp_secret(len: 32)
836
+ def self.totp_secret(len: 20); end
837
+
838
+ # Verifies a TOTP code against a secret with time window tolerance.
839
+ #
840
+ # The window parameter specifies how many intervals to check on either side
841
+ # of the current time. For example, window: 1 checks current ± 1 interval.
842
+ #
843
+ # @param code [Integer] the TOTP code to verify
844
+ # @param secret [String] the shared secret key (raw bytes or Base32 decoded)
845
+ # @param window [Integer] (**optional**) number of intervals to check on each side (default: 1, range: 0-10)
846
+ # @param interval [Integer] (**optional**) time window in seconds (default: 30)
847
+ # @return [Boolean] `true` if the code is valid, `false` otherwise
848
+ #
849
+ # @example
850
+ # valid = Iodine::Utils.totp_verify(code: user_code, secret: my_secret)
851
+ # valid = Iodine::Utils.totp_verify(code: user_code, secret: my_secret, window: 2)
852
+ def self.totp_verify(code:, secret:, window: 1, interval: 30); end
853
+
854
+ # Generates 16 unique bytes of Poly1305-MAC, encoding them as a Base64 encoded String.
855
+ #
856
+ # @param secret [String] The secret used for the Hash based Message Authentication Code (MAC). Only the first 32 bytes are used.
857
+ # @param msg [String] The message to authenticate.
858
+ # @return [String] Base64 encoded String.
859
+ def self.hmac128(secret, msg); end
860
+
861
+ # Generates 20 unique bytes of SHA1-HMAC, encoding them as a Base64 encoded String.
862
+ #
863
+ # This is the equivalent of calling `OpenSSL::HMAC.base64digest "SHA1", secret, msg`
864
+ #
865
+ # @param secret [String] The secret used for the Hash based Message Authentication Code (MAC).
866
+ # @param msg [String] The message to authenticate.
867
+ # @return [String] Base64 encoded String.
868
+ def self.hmac160(secret, msg); end
869
+
870
+ # Generates 32 unique bytes of SHA256-HMAC, encoding them as a Base64 encoded String.
871
+ #
872
+ # This is the equivalent of calling `OpenSSL::HMAC.base64digest "SHA256", secret, msg`
873
+ #
874
+ # @param secret [String] The secret used for the Hash based Message Authentication Code (MAC).
875
+ # @param msg [String] The message to authenticate.
876
+ # @return [String] Base64 encoded String.
877
+ def self.hmac256(secret, msg); end
878
+
879
+ # Generates 64 unique bytes of SHA512-HMAC, encoding them as a Base64 encoded String.
880
+ #
881
+ # This is the equivalent of calling `OpenSSL::HMAC.base64digest "SHA512", secret, msg`
882
+ #
883
+ # @param secret [String] The secret used for the Hash based Message Authentication Code (MAC).
884
+ # @param msg [String] The message to authenticate.
885
+ # @return [String] Base64 encoded String.
886
+ def self.hmac512(secret, msg); end
887
+
888
+ # Computes SHA-256 hash of the data.
889
+ #
890
+ # @param data [String] the data to hash
891
+ # @return [String] 32-byte binary hash
892
+ def self.sha256(data); end
893
+
894
+ # Computes SHA-512 hash of the data.
895
+ #
896
+ # @param data [String] the data to hash
897
+ # @return [String] 64-byte binary hash
898
+ def self.sha512(data); end
899
+
900
+ # Computes SHA3-256 hash of the data.
901
+ #
902
+ # @param data [String] the data to hash
903
+ # @return [String] 32-byte binary hash
904
+ def self.sha3_256(data); end
905
+
906
+ # Computes SHA3-512 hash of the data.
907
+ #
908
+ # @param data [String] the data to hash
909
+ # @return [String] 64-byte binary hash
910
+ def self.sha3_512(data); end
911
+
912
+ # Computes BLAKE2b hash of the data.
913
+ #
914
+ # @param data [String] the data to hash
915
+ # @param key [String, nil] optional key for keyed hashing
916
+ # @param len [Integer] output length in bytes (1-64, default: 64)
917
+ # @return [String] binary hash of specified length
918
+ def self.blake2b(data, key: nil, len: 64); end
919
+
920
+ # Computes BLAKE2s hash of the data.
921
+ #
922
+ # @param data [String] the data to hash
923
+ # @param key [String, nil] optional key for keyed hashing
924
+ # @param len [Integer] output length in bytes (1-32, default: 32)
925
+ # @return [String] binary hash of specified length
926
+ def self.blake2s(data, key: nil, len: 32); end
927
+
928
+ # Computes SHA3-224 hash of the data.
929
+ #
930
+ # @param data [String] the data to hash
931
+ # @return [String] 28-byte binary hash digest
932
+ #
933
+ # @example
934
+ # hash = Iodine::Utils.sha3_224("hello world")
935
+ # # => 28-byte binary string
936
+ def self.sha3_224(data); end
937
+
938
+ # Computes SHA3-384 hash of the data.
939
+ #
940
+ # @param data [String] the data to hash
941
+ # @return [String] 48-byte binary hash digest
942
+ #
943
+ # @example
944
+ # hash = Iodine::Utils.sha3_384("hello world")
945
+ # # => 48-byte binary string
946
+ def self.sha3_384(data); end
947
+
948
+ # Computes SHAKE128 extendable-output function (XOF).
949
+ #
950
+ # SHAKE128 is a variable-length hash function from the SHA-3 family.
951
+ # Unlike fixed-output hash functions, XOFs can produce output of any desired length.
952
+ #
953
+ # @param data [String] the data to hash
954
+ # @param length [Integer] output length in bytes (default: 32)
955
+ # @return [String] binary string of specified length
956
+ # @raise [ArgumentError] if length is less than 1 or greater than 268435455
957
+ #
958
+ # @example
959
+ # # Default 32-byte output
960
+ # hash = Iodine::Utils.shake128("hello world")
961
+ #
962
+ # # Custom length output
963
+ # hash = Iodine::Utils.shake128("hello world", length: 64)
964
+ def self.shake128(data, length: 32); end
965
+
966
+ # Computes SHAKE256 extendable-output function (XOF).
967
+ #
968
+ # SHAKE256 is a variable-length hash function from the SHA-3 family.
969
+ # Unlike fixed-output hash functions, XOFs can produce output of any desired length.
970
+ # SHAKE256 provides a higher security margin than SHAKE128.
971
+ #
972
+ # @param data [String] the data to hash
973
+ # @param length [Integer] output length in bytes (default: 64)
974
+ # @return [String] binary string of specified length
975
+ # @raise [ArgumentError] if length is less than 1 or greater than 268435455
976
+ #
977
+ # @example
978
+ # # Default 64-byte output
979
+ # hash = Iodine::Utils.shake256("hello world")
980
+ #
981
+ # # Custom length output
982
+ # hash = Iodine::Utils.shake256("hello world", length: 128)
983
+ def self.shake256(data, length: 64); end
984
+
985
+ # Computes SHA-1 hash of the data.
986
+ #
987
+ # @param data [String] the data to hash
988
+ # @return [String] 20-byte binary hash digest
989
+ #
990
+ # @note SHA-1 is considered cryptographically weak and should not be used for
991
+ # security-sensitive applications. Use only for protocol compatibility
992
+ # (e.g., TOTP/HOTP, Git, WebSocket handshakes) where SHA-1 is required.
993
+ #
994
+ # @example
995
+ # hash = Iodine::Utils.sha1("hello world")
996
+ # # => 20-byte binary string
997
+ def self.sha1(data); end
998
+
999
+ # Computes CRC32 checksum (ITU-T V.42 / ISO 3309 / gzip polynomial 0xEDB88320).
1000
+ #
1001
+ # Uses a slicing-by-8 algorithm for high throughput. This is the standard CRC32
1002
+ # used by gzip, zlib, and Ethernet — NOT the Castagnoli (CRC32-C) variant.
1003
+ #
1004
+ # Supports incremental computation: pass the previous CRC value as `initial_crc`
1005
+ # to continue a checksum across multiple buffers.
1006
+ #
1007
+ # @param data [String] the data to checksum
1008
+ # @param initial_crc [Integer] starting CRC value for incremental computation (default: 0)
1009
+ # @return [Integer] 32-bit CRC32 checksum as an unsigned integer
1010
+ #
1011
+ # @example Basic usage
1012
+ # checksum = Iodine::Utils.crc32("Hello, World!")
1013
+ #
1014
+ # @example Incremental computation over multiple buffers
1015
+ # crc = Iodine::Utils.crc32("Hello, ")
1016
+ # crc = Iodine::Utils.crc32("World!", initial_crc: crc)
1017
+ # # Same result as: Iodine::Utils.crc32("Hello, World!")
1018
+ def self.crc32(data, initial_crc: 0); end
1019
+
1020
+ # Computes a fast non-cryptographic hash using facil.io's Risky Hash.
1021
+ #
1022
+ # Risky Hash is optimized for speed and is suitable for hash tables,
1023
+ # checksums, data partitioning, and other non-security applications.
1024
+ #
1025
+ # @param data [String] the data to hash
1026
+ # @param seed [Integer] optional 64-bit seed value (default: 0)
1027
+ # @return [Integer] 64-bit hash value
1028
+ #
1029
+ # @note This hash function is NOT cryptographically secure. Do not use
1030
+ # for security purposes such as password hashing or message authentication.
1031
+ #
1032
+ # @example
1033
+ # # Basic usage
1034
+ # hash = Iodine::Utils.risky_hash("hello world")
1035
+ #
1036
+ # # With custom seed for different hash distributions
1037
+ # hash = Iodine::Utils.risky_hash("hello world", seed: 12345)
1038
+ def self.risky_hash(data, seed: 0); end
1039
+
1040
+ # Computes a 256-bit (32-byte) non-cryptographic hash using Risky Hash.
1041
+ #
1042
+ # This is a fast, high-quality hash function suitable for hash tables,
1043
+ # checksums, data deduplication, and other non-security applications.
1044
+ # The output is deterministic and consistent across platforms.
1045
+ #
1046
+ # @param data [String] the data to hash
1047
+ # @return [String] 32-byte binary hash digest
1048
+ #
1049
+ # @note This hash function is NOT cryptographically secure. Do not use
1050
+ # for security purposes such as password hashing or message authentication.
1051
+ #
1052
+ # @example
1053
+ # hash = Iodine::Utils.risky256("hello world")
1054
+ # puts hash.bytesize # => 32
1055
+ def self.risky256(data); end
1056
+
1057
+ # Computes a 512-bit (64-byte) non-cryptographic hash using Risky Hash.
1058
+ #
1059
+ # This is a SHAKE-style extension of risky256: the first 256 bits of
1060
+ # the output are identical to risky256 (truncation-safe). The second
1061
+ # 256 bits extend the hash without increasing collision resistance.
1062
+ #
1063
+ # @param data [String] the data to hash
1064
+ # @return [String] 64-byte binary hash digest
1065
+ #
1066
+ # @note This hash function is NOT cryptographically secure. Do not use
1067
+ # for security purposes such as password hashing or message authentication.
1068
+ #
1069
+ # @example
1070
+ # hash = Iodine::Utils.risky512("hello world")
1071
+ # puts hash.bytesize # => 64
1072
+ #
1073
+ # # First 32 bytes match risky256
1074
+ # hash[0, 32] == Iodine::Utils.risky256("hello world") # => true
1075
+ def self.risky512(data); end
1076
+
1077
+ # Computes a 256-bit (32-byte) keyed HMAC using Risky Hash.
1078
+ #
1079
+ # Uses risky256 as the underlying hash with a 64-byte block size.
1080
+ # If key_len > 64, the key is hashed first with risky256.
1081
+ #
1082
+ # @param key [String] the secret key for authentication
1083
+ # @param data [String] the data to authenticate
1084
+ # @return [String] 32-byte binary HMAC digest
1085
+ #
1086
+ # @note While this provides message authentication, risky256 is not a
1087
+ # cryptographic hash. For security-critical applications, use hmac256
1088
+ # (SHA-256) or hmac512 (SHA-512) instead.
1089
+ #
1090
+ # @example
1091
+ # mac = Iodine::Utils.risky256_hmac("secret-key", "message to authenticate")
1092
+ # puts mac.bytesize # => 32
1093
+ def self.risky256_hmac(key, data); end
1094
+
1095
+ # Computes a 512-bit (64-byte) keyed HMAC using Risky Hash.
1096
+ #
1097
+ # Uses risky512 as the underlying hash with a 64-byte block size.
1098
+ # If key_len > 64, the key is hashed first with risky512.
1099
+ #
1100
+ # @param key [String] the secret key for authentication
1101
+ # @param data [String] the data to authenticate
1102
+ # @return [String] 64-byte binary HMAC digest
1103
+ #
1104
+ # @note While this provides message authentication, risky512 is not a
1105
+ # cryptographic hash. For security-critical applications, use hmac256
1106
+ # (SHA-256) or hmac512 (SHA-512) instead.
1107
+ #
1108
+ # @example
1109
+ # mac = Iodine::Utils.risky512_hmac("secret-key", "message to authenticate")
1110
+ # puts mac.bytesize # => 64
1111
+ def self.risky512_hmac(key, data); end
1112
+
1113
+ # Generates cryptographically secure random bytes using the system CSPRNG.
1114
+ #
1115
+ # Use it for keys, nonces, salts, and other security-sensitive values.
1116
+ #
1117
+ # The system CSPRNG is `arc4random_buf` on BSD/macOS, `BCryptGenRandom` on
1118
+ # Windows, and `getrandom` (or `/dev/urandom`) on Linux. If the system
1119
+ # CSPRNG fails, this method falls back to the facil.io PRNG instead of
1120
+ # raising.
1121
+ #
1122
+ # @param bytes [Integer] number of random bytes to generate (default: 32)
1123
+ # @return [String] binary string containing the requested random bytes
1124
+ # @raise [RangeError] if bytes count is out of range
1125
+ #
1126
+ # @example
1127
+ # # Generate a 32-byte key (default)
1128
+ # key = Iodine::Utils.secure_random
1129
+ #
1130
+ # # Generate a 16-byte nonce
1131
+ # nonce = Iodine::Utils.secure_random(bytes: 16)
1132
+ #
1133
+ # # Generate a 64-byte seed
1134
+ # seed = Iodine::Utils.secure_random(bytes: 64)
1135
+ def self.secure_random(bytes: 32); end
1136
+ end
1137
+
1138
+ #######################
1139
+
1140
+ # This is the namespace for all pub/sub related classes and constants.
1141
+ module PubSub
1142
+ # Returns the current default PubSub engine.
1143
+ #
1144
+ # @return [Iodine::PubSub::Engine] the current default engine
1145
+ def self.default; end
1146
+
1147
+ # Sets the default PubSub engine for all publish operations.
1148
+ #
1149
+ # @param engine [Iodine::PubSub::Engine, nil] the engine to set as default (or nil for CLUSTER)
1150
+ # @return [Iodine::PubSub::Engine] the new default engine
1151
+ #
1152
+ # @example
1153
+ # Iodine::PubSub.default = Iodine::PubSub::Engine::RESP3.new("redis://localhost:6379/")
1154
+ def self.default=(engine); end
1155
+
1156
+ # Iodine::PubSub::Message class instances are passed to subscription callbacks.
1157
+ #
1158
+ # This allows subscription callbacks to get information about the pub/sub event.
1159
+ class Message
1160
+ # Returns the event's `id` property - a (mostly) random numerical value.
1161
+ # @return [Integer]
1162
+ def id; end
1163
+
1164
+ # Returns the event's `channel` property - the event's target channel.
1165
+ # @return [String, nil]
1166
+ def channel; end
1167
+ alias event channel
1168
+
1169
+ # Returns the event's `filter` property - the event's numerical channel filter.
1170
+ # @return [Integer]
1171
+ def filter; end
1172
+
1173
+ # Returns the event's `message` property - the event's payload (same as {to_s}).
1174
+ # @return [String]
1175
+ def message; end
1176
+ alias msg message
1177
+ alias data message
1178
+
1179
+ # Returns the event's `published` property - the event's published timestamp in milliseconds since epoch.
1180
+ # @return [Integer]
1181
+ def published; end
1182
+
1183
+ # Returns the event's `message` property - the event's payload (same as {message}).
1184
+ # @return [String]
1185
+ def to_s; end
1186
+
1187
+ # Sets the event's `id` property.
1188
+ # @param value [Integer]
1189
+ def id=(value); end
1190
+
1191
+ # Sets the event's `channel` property.
1192
+ # @param value [String]
1193
+ def channel=(value); end
1194
+
1195
+ # Sets the event's `filter` property.
1196
+ # @param value [Integer]
1197
+ def filter=(value); end
1198
+
1199
+ # Sets the event's `message` property.
1200
+ # @param value [String]
1201
+ def message=(value); end
1202
+
1203
+ # Sets the event's `published` property.
1204
+ # @param value [Integer]
1205
+ def published=(value); end
1206
+ end
1207
+
1208
+ # Iodine::PubSub::Engine class instances are used as the base class for all pub/sub engines.
1209
+ #
1210
+ # The Engine class makes it easy to use leverage Iodine's pub/sub system using external services.
1211
+ #
1212
+ # Engine child classes SHOULD override the #subscribe, #unsubscribe and #publish in order to perform this actions using the backend service (i.e. using Redis).
1213
+ #
1214
+ # Once an Engine instance receives a message from its backend service, it should forward the message to the Iodine distribution layer one of the other engines. i.e.:
1215
+ #
1216
+ # Iodine.publish channel: "subscribed_channel", message: "the_message", engine: Iodine::PubSub::Engine::CLUSTER
1217
+ #
1218
+ # Iodine comes with two built-in engines:
1219
+ #
1220
+ # * {Iodine::PubSub::Engine::CLUSTER} will distribute messages to all subscribers in the process cluster.
1221
+ #
1222
+ # * {Iodine::PubSub::Engine::PROCESS} will distribute messages to all subscribers sharing the same process.
1223
+ class Engine
1224
+ # When implementing your own initialization class, remember to call `super` to attach the engine to Iodine.
1225
+ def initialize; end
1226
+
1227
+ # Please OVERRIDE(!) – called by iodine when a message was published to the engine (using `Iodine.publish(engine: MY_ENGINE, ...)).
1228
+ #
1229
+ # @param msg [Iodine::PubSub::Message] the message to publish
1230
+ def publish(msg); end
1231
+
1232
+ # Please OVERRIDE(!) – called by iodine when a named channel was subscribed to.
1233
+ #
1234
+ # @param channel [String] the channel name
1235
+ def subscribe(channel); end
1236
+
1237
+ # Please OVERRIDE(!) – called by iodine when a pattern channel was subscribed to.
1238
+ #
1239
+ # @param pattern [String] the pattern
1240
+ def psubscribe(pattern); end
1241
+
1242
+ # Please OVERRIDE(!) – called by iodine when a named channel was unsubscribed to.
1243
+ #
1244
+ # @param channel [String] the channel name
1245
+ def unsubscribe(channel); end
1246
+
1247
+ # Please OVERRIDE(!) – called by iodine when a pattern channel was unsubscribed to.
1248
+ #
1249
+ # @param pattern [String] the pattern
1250
+ def punsubscribe(pattern); end
1251
+
1252
+ # Please OVERRIDE(!) – called by iodine when a the engine is detached from iodine.
1253
+ #
1254
+ # This happens when the engine object went out of scope and should be collected by the GC (Garbage Collector).
1255
+ def on_cleanup; end
1256
+
1257
+ # This engine publishes the message to all subscribers within the local machine (master process + all worker processes).
1258
+ LOCAL = Engine.new
1259
+ # This engine publishes the message to all subscribers across the entire cluster (all machines). This is the default engine.
1260
+ CLUSTER = Engine.new
1261
+
1262
+ # RESP3 Pub/Sub engine for distributed messaging across multiple server instances.
1263
+ #
1264
+ # Provides both Pub/Sub functionality and direct Valkey/Redis command execution.
1265
+ #
1266
+ # @example Basic usage
1267
+ # resp3 = Iodine::PubSub::Engine::RESP3.new("redis://localhost:6379/", ping: 50)
1268
+ # Iodine::PubSub.default = resp3
1269
+ #
1270
+ # @example With authentication
1271
+ # resp3 = Iodine::PubSub::Engine::RESP3.new("redis://user:password@host:6379/0")
1272
+ #
1273
+ # @example Sending Valkey/Redis commands
1274
+ # resp3.cmd("SET", "key", "value") { |result| puts result }
1275
+ # resp3.cmd("GET", "key") { |value| puts value }
1276
+ # resp3.cmd("KEYS", "*") { |keys| p keys }
1277
+ class RESP3 < Engine
1278
+ # Creates a new RESP3 Pub/Sub engine.
1279
+ #
1280
+ # @param url [String] RESP3-compatible server URL (e.g., "redis://localhost:6379/")
1281
+ # @param ping [Integer] Ping interval in seconds (0-255, 0 = default of 30 seconds)
1282
+ # @return [Iodine::PubSub::Engine::RESP3]
1283
+ #
1284
+ # URL formats supported:
1285
+ # - "redis://host:port"
1286
+ # - "redis://user:password@host:port/db"
1287
+ # - "host:port"
1288
+ # - "host" (default port 6379)
1289
+ #
1290
+ # @example
1291
+ # resp3 = Iodine::PubSub::Engine::RESP3.new("redis://localhost:6379/")
1292
+ # resp3 = Iodine::PubSub::Engine::RESP3.new("redis://secret@host:6379/", ping: 60)
1293
+ def initialize(url, ping: 0); end
1294
+
1295
+ # Returns the RESP3 connection state.
1296
+ #
1297
+ # @return [Symbol] `:connecting`, `:connected`, or `:error`
1298
+ def connection_state; end
1299
+
1300
+ # Sends a Redis command and optionally receives the response via callback.
1301
+ #
1302
+ # @param args [Array<String, Integer, Float, Symbol, Boolean, nil>] Command and arguments
1303
+ # @yield [result] Called with the Redis response
1304
+ # @yieldparam result [Object] The parsed Redis response
1305
+ # @return [Boolean] true on success, false on error
1306
+ #
1307
+ # @example
1308
+ # redis.cmd("SET", "key", "value") { |r| puts "SET result: #{r}" }
1309
+ # redis.cmd("GET", "key") { |value| puts "Value: #{value}" }
1310
+ # redis.cmd("KEYS", "*") { |keys| p keys }
1311
+ # redis.cmd("INCR", "counter") { |new_val| puts new_val }
1312
+ #
1313
+ # @note Do NOT use SUBSCRIBE/PSUBSCRIBE/UNSUBSCRIBE/PUNSUBSCRIBE commands.
1314
+ # These are handled internally by the pub/sub system.
1315
+ def cmd(*args, &block); end
1316
+ end
1317
+ end
1318
+
1319
+ # Iodine::PubSub::Subscription wraps an independent, non-IO-bound pub/sub
1320
+ # subscription handle.
1321
+ #
1322
+ # Unlike {Iodine.subscribe} — which allows only **one callback per channel
1323
+ # per global context** (a second call silently replaces the first) — each
1324
+ # `Subscription` object is its own independent context. You can create as
1325
+ # many `Subscription` objects as you like for the same channel and each
1326
+ # will receive every published message independently.
1327
+ #
1328
+ # The subscription **MUST** be explicitly cancelled or will remain until the process ends.
1329
+ #
1330
+ # A Iodine::PubSub::Subscription instance is never garbage collected unless subscription is cancelled using {#cancel}.
1331
+ #
1332
+ # @example Basic usage (lives forever)
1333
+ # Iodine::PubSub::Subscription.new("chat") { |msg| puts msg.message }
1334
+ #
1335
+ # @example Multiple independent subscriptions to the same channel
1336
+ # sub1 = Iodine::PubSub::Subscription.new("news") { |msg| puts "Handler 1: #{msg.message}" }
1337
+ # sub2 = Iodine::PubSub::Subscription.new("news") { |msg| puts "Handler 2: #{msg.message}" }
1338
+ # # Both sub1 and sub2 receive every message published to "news"
1339
+ #
1340
+ # @example With keyword arguments and filter
1341
+ # sub = Iodine::PubSub::Subscription.new(channel: "events", filter: 42) { |msg| ... }
1342
+ #
1343
+ # @example Live handler replacement
1344
+ # sub = Iodine::PubSub::Subscription.new("ch") { |msg| puts "old" }
1345
+ # sub.handler = proc { |msg| puts "new" } # future messages go here
1346
+ #
1347
+ # @example Early cancellation
1348
+ # sub = Iodine::PubSub::Subscription.new("ch") { |msg| ... }
1349
+ # sub.cancel # stop receiving messages immediately
1350
+ # sub.active? # => false
1351
+ class Subscription
1352
+ # Creates a new independent pub/sub subscription.
1353
+ #
1354
+ # Each `Subscription.new` call creates its own independent context,
1355
+ # so multiple subscriptions to the same channel all receive messages.
1356
+ #
1357
+ # @param channel [String] the channel name to subscribe to
1358
+ # @param filter [Integer] optional numerical filter (default: 0, max: 32767)
1359
+ # @param since [Integer] optional replay-since timestamp in milliseconds
1360
+ # (requires {Iodine::PubSub::History.cache} to be enabled)
1361
+ # @yield [msg] required message handler block
1362
+ # @yieldparam msg [Iodine::PubSub::Message] the received message
1363
+ # @return [Iodine::PubSub::Subscription] the new subscription
1364
+ # @raise [ArgumentError] if no block is given
1365
+ # @raise [RangeError] if filter is out of range
1366
+ #
1367
+ # @example
1368
+ # sub = Iodine::PubSub::Subscription.new("channel") { |msg| puts msg.message }
1369
+ # sub = Iodine::PubSub::Subscription.new(channel: "ch", filter: 0) { |msg| ... }
1370
+ def initialize(channel, filter: 0, since: 0, &block); end
1371
+
1372
+ # Returns the current message handler proc.
1373
+ #
1374
+ # @return [Proc, nil] the handler proc, or nil if the subscription was
1375
+ # cancelled and the handler cleared
1376
+ def handler; end
1377
+
1378
+ # Replaces the message handler proc.
1379
+ #
1380
+ # Future messages will be dispatched to the new proc. The swap takes
1381
+ # effect immediately — any message dispatched after this call will use
1382
+ # the new handler.
1383
+ #
1384
+ # @param new_handler [Proc] the new handler (must respond to `#call`)
1385
+ # @return [Proc] the new handler
1386
+ # @raise [ArgumentError] if new_handler does not respond to `call`
1387
+ def handler=(new_handler); end
1388
+
1389
+ # Returns `true` if the subscription is still active.
1390
+ #
1391
+ # A subscription becomes inactive after {#cancel} is called or after
1392
+ # the object is garbage collected.
1393
+ #
1394
+ # @return [Boolean]
1395
+ def active?; end
1396
+
1397
+ # Cancels the subscription, stopping future message delivery.
1398
+ #
1399
+ # Idempotent — safe to call multiple times. After cancellation, no
1400
+ # further messages will be delivered. The subscription is also
1401
+ # automatically cancelled when the object is garbage collected.
1402
+ #
1403
+ # @return [self]
1404
+ def cancel; end
1405
+ end
1406
+
1407
+ # Iodine::PubSub::History module provides message history and replay support.
1408
+ #
1409
+ # When history is enabled, published messages are cached in memory (up to a
1410
+ # configurable size limit). Subscribers can then request replay of missed
1411
+ # messages by providing a `since` timestamp when subscribing.
1412
+ #
1413
+ # @example Enable memory cache with default 256MB limit
1414
+ # Iodine::PubSub::History.cache
1415
+ #
1416
+ # @example Enable memory cache with custom size
1417
+ # Iodine::PubSub::History.cache(size_limit: 128 * 1024 * 1024) # 128MB
1418
+ #
1419
+ # @example Subscribe with history replay
1420
+ # # Get messages from the last 60 seconds
1421
+ # since_ms = (Time.now.to_i - 60) * 1000
1422
+ # Iodine.subscribe(channel: "chat", since: since_ms) do |msg|
1423
+ # puts "Message: #{msg.message}"
1424
+ # end
1425
+ module History
1426
+ # Enables the built-in in-memory history cache.
1427
+ #
1428
+ # The memory cache has the highest priority (255) so it will be tried
1429
+ # first for replay requests, providing the fastest possible replay.
1430
+ #
1431
+ # @param size_limit [Integer] Maximum cache size in bytes (default: 256MB)
1432
+ # @return [Boolean] true on success
1433
+ #
1434
+ # @example
1435
+ # Iodine::PubSub::History.cache(size_limit: 128 * 1024 * 1024)
1436
+ def self.cache(size_limit: 256 * 1024 * 1024); end
1437
+
1438
+ # Returns true if the built-in memory cache is enabled.
1439
+ #
1440
+ # @return [Boolean]
1441
+ def self.cache?; end
1442
+
1443
+ # Base class for custom history managers.
1444
+ #
1445
+ # Subclass this to implement custom history storage backends (e.g., Redis,
1446
+ # database, file-based storage).
1447
+ #
1448
+ # @example Custom history manager
1449
+ # class MyHistoryManager < Iodine::PubSub::History::Manager
1450
+ # def push(message)
1451
+ # # Store message in your backend
1452
+ # @storage[message.channel] ||= []
1453
+ # @storage[message.channel] << message
1454
+ # true
1455
+ # end
1456
+ #
1457
+ # def replay(channel:, filter:, since:)
1458
+ # # Return array of messages since timestamp
1459
+ # (@storage[channel] || []).select { |m| m.published >= since }
1460
+ # end
1461
+ #
1462
+ # def oldest(channel:, filter:)
1463
+ # # Return oldest available timestamp
1464
+ # @storage[channel]&.first&.published || (2**64 - 1)
1465
+ # end
1466
+ # end
1467
+ class Manager
1468
+ # Creates and attaches a new history manager.
1469
+ #
1470
+ # @param priority [Integer] Manager priority (0-255, default: 128).
1471
+ # Higher priority managers are tried first for replay requests.
1472
+ # @return [self]
1473
+ def initialize(priority: 128); end
1474
+
1475
+ # Detaches the history manager from the pubsub system.
1476
+ #
1477
+ # @return [self]
1478
+ def detach; end
1479
+
1480
+ # Returns true if the manager is attached.
1481
+ #
1482
+ # @return [Boolean]
1483
+ def attached?; end
1484
+
1485
+ # Override this to store a message in history.
1486
+ #
1487
+ # @param message [Iodine::PubSub::Message] the message to store
1488
+ # @return [Boolean] true on success, false on error
1489
+ def push(message); end
1490
+
1491
+ # Override this to replay messages since a timestamp.
1492
+ #
1493
+ # @param channel [String, nil] the channel name
1494
+ # @param filter [Integer] the filter value
1495
+ # @param since [Integer] timestamp in milliseconds since epoch
1496
+ # @return [Array<Iodine::PubSub::Message>] array of messages to replay
1497
+ def replay(channel:, filter:, since:); end
1498
+
1499
+ # Override this to get the oldest available timestamp for a channel.
1500
+ #
1501
+ # @param channel [String, nil] the channel name
1502
+ # @param filter [Integer] the filter value
1503
+ # @return [Integer] oldest timestamp in milliseconds, or 2^64-1 if no history
1504
+ def oldest(channel:, filter:); end
1505
+
1506
+ # Called when the manager is detached for cleanup.
1507
+ def on_cleanup; end
1508
+ end
1509
+ end
1510
+ end
1511
+
1512
+ #######################
1513
+
1514
+ # This is a listener object, returned from {Iodine.listen}.
1515
+ #
1516
+ # **Note**: calling {Iodine::Listener.new} would raise an exception. Use {Iodine.listen} instead.
1517
+ class Listener
1518
+ # Sets the handler for new connections.
1519
+ #
1520
+ # @param url [String, nil] should be `nil` unless listening for HTTP connections, in which case `url`, if set, will be used for routing the `path` part of the URL requested to the specified handler.
1521
+ # @param handler [Object] should contain a valid {Iodine::Connection::Handler} for incoming connections.
1522
+ # @return [self]
1523
+ #
1524
+ # Routing with HTTP `url` values will follow the following rules:
1525
+ #
1526
+ # - Routing follows the best-match approach regardless of ordering. If the best match is the root URL `'/'`, the default handler will be used.
1527
+ #
1528
+ # - Partial URL matches are only valid if the `/` character is the one following the partial match. For example: setting `"/user"` will match `"/user"` and all `"/user/*"` paths but not `"/user*"`
1529
+ #
1530
+ # - The {Iodine::Connection#path} property will NOT contain the removed path prefix, but will always contain and start with the `/` character. For example: matching the path `"/user/new"` with the route for `"/user"` will set the {Iodine::Connection#path} property to `'/new'`.
1531
+ #
1532
+ # @note When `handler` is `false` instead of `nil`, the handler for the route will be replace with the default handler. However, the {Iodine::Connection#path} property for the HTTP handler will behave as if a route match has occurred, removing the route's prefix.
1533
+ def map(url = nil, handler = nil); end
1534
+ end
1535
+
1536
+ #######################
1537
+
1538
+ # {Iodine::Connection} is the connection instance class used for all connection callbacks to control the state of the connection.
1539
+ #
1540
+ # The {Iodine::Connection} class offers methods that control the different aspects of a Connection's state:
1541
+ #
1542
+ # - HTTP related methods include the {#status}, {#method}, {#path}, {#query}, {#headers}, {#cookie}, {#each_cookie}, {#read}, {#env} (Rack), and more methods.
1543
+ #
1544
+ # - WebSocket / TCP related methods include the {#open?}, {#write}, {#pending} and {#close} methods.
1545
+ #
1546
+ # - Pub/Sub related methods include the {#subscribe}, {#unsubscribe}, and {#publish} methods.
1547
+ #
1548
+ # - Connection specific data can be stored / accessed using {#[]}, {#[]=}, {#each}, {#store_count}, {#store_capa}, {#store_reserve} methods.
1549
+ #
1550
+ class Connection
1551
+ # Creates a new outgoing client connection.
1552
+ #
1553
+ # @param url [String] The URL to connect to (required). Supports `tcp://`, `tcps://`, `http://`, `https://`, `ws://`, `wss://` schemes.
1554
+ # @param handler [Object] The handler object implementing connection callbacks (see {Handler}).
1555
+ # @param tls [Iodine::TLS, Boolean] TLS context or `true` for auto-generated TLS.
1556
+ # @param tls_io [Symbol, nil] TLS backend selection: `:iodine` (embedded TLS 1.3), `:openssl` (OpenSSL >= 3.0), or `nil` (compile-time default). Only effective when TLS is enabled.
1557
+ # @param headers [Hash] (HTTP/WebSocket only) Request headers to send.
1558
+ # @param cookies [Hash] (HTTP/WebSocket only) Cookies to send.
1559
+ # @param body [String] (HTTP only) Request body.
1560
+ # @param method [String] (HTTP only) HTTP method (defaults to GET).
1561
+ # @return [Iodine::Connection] The new connection object.
1562
+ #
1563
+ # @example WebSocket client with embedded TLS
1564
+ # Iodine::Connection.new("wss://example.com/socket", handler: MyHandler, tls_io: :iodine)
1565
+ #
1566
+ # @example WebSocket client with OpenSSL
1567
+ # Iodine::Connection.new("wss://example.com/socket", handler: MyHandler, tls_io: :openssl)
1568
+ def initialize(url, handler: nil, tls: nil, tls_io: nil, headers: nil, cookies: nil, body: nil, method: nil); end
1569
+
1570
+ # Unless `env` is manually set, Iodine will attempt to create a default `env` object that will follow the Rack Server specification for HTTP connections.
1571
+ #
1572
+ # Otherwise, the `env` attribute is usually a Hash object set by the client and may be used as an alternative to the Connection data storage (see {#[]} and {#[]=}).
1573
+ # @return [Hash]
1574
+ attr_accessor :env
1575
+
1576
+ # The `handler` attribute (see {Iodine::Connection::Handler}) - this may be the Rack application or a [NeoRack](https://github.com/boazsegev/neorack) application.
1577
+ # @return [Object]
1578
+ attr_accessor :handler
1579
+
1580
+ # For HTTP connections, the `status` value is the HTTP status value that will be sent in the response.
1581
+ # @return [Integer]
1582
+ attr_accessor :status
1583
+
1584
+ # For HTTP connections, the `method` value is the HTTP method value received by the client.
1585
+ # @return [String]
1586
+ attr_accessor :method
1587
+
1588
+ # For HTTP connections, the `path` value is the HTTP URL's path String received by the client. When routing, this does **NOT** include the route's prefix (see {Iodine::Listener#map}).
1589
+ # @return [String]
1590
+ attr_accessor :path
1591
+
1592
+ # For HTTP connections, the `opath` value is the HTTP URL's original path String received by the client.
1593
+ # @return [String]
1594
+ attr_accessor :opath
1595
+
1596
+ # For HTTP connections, the `query` value is the HTTP URL's query String (if received by the client).
1597
+ # @return [String, nil]
1598
+ attr_accessor :query
1599
+
1600
+ # For HTTP connections, the `version` String value stated by the HTTP client.
1601
+ # @return [String]
1602
+ attr_accessor :version
1603
+
1604
+ # For HTTP connections, sets all incoming request headers in the Connection instance storage ({#[]}) and returns `self`.
1605
+ #
1606
+ # Used to overcome lazy parsing of incoming headers. i.e.:
1607
+ #
1608
+ # # prints all incoming headers, assuming NeoRack guidelines were followed:
1609
+ # client.headers.each do |key, value|
1610
+ # if(value.is_a? Array)
1611
+ # value.each {|v| puts "#{key}: #{v}" }
1612
+ # else
1613
+ # puts "#{key}: #{value}"
1614
+ # end
1615
+ # end
1616
+ #
1617
+ # @return [self]
1618
+ attr_accessor :headers
1619
+
1620
+ # (HTTP Only) Returns the length of the `body` payload.
1621
+ # @return [Integer]
1622
+ attr_reader :length
1623
+
1624
+ # @!group Key-Data Store
1625
+
1626
+ # Returns a the value of a key previously set.
1627
+ #
1628
+ # If the Connection is an HTTP connection and `key` is a Header String, the value of the header (if received) will be returned.
1629
+ #
1630
+ # If no value was set for the key (or no Header received), `nil` will be returned.
1631
+ #
1632
+ # @param key [Object] the key to look up
1633
+ # @return [Object, nil] the value or nil
1634
+ def [](key); end
1635
+
1636
+ # Sets a key value pair.
1637
+ #
1638
+ # @param key [Object] the key
1639
+ # @param value [Object] the value
1640
+ # @return [Object] the value
1641
+ def []=(key, value); end
1642
+
1643
+ # Iterates over the stored key value pairs.
1644
+ #
1645
+ # If the Connection is an HTTP connection, `each` will also iterate over any header previously accessed.
1646
+ #
1647
+ # @yield [key, value] the key-value pairs
1648
+ # @return [self]
1649
+ def each(&block); end
1650
+
1651
+ # Returns the number of key value pairs stored.
1652
+ # @return [Integer]
1653
+ def store_count; end
1654
+
1655
+ # Returns the theoretical capacity of the key value pair storage map.
1656
+ # @return [Integer]
1657
+ def store_capa; end
1658
+
1659
+ # Reserved a theoretical storage capacity in the key value pair storage map.
1660
+ #
1661
+ # @param number_of_items [Integer] the number of items to reserve
1662
+ # @return [self]
1663
+ #
1664
+ # @note The capacity is theoretical only and some extra memory is required to allow for possible (partial) hash collisions.
1665
+ def store_reserve(number_of_items); end
1666
+
1667
+ # @!group Connection State
1668
+
1669
+ # Returns `true` if the connection appears to be open (no known issues).
1670
+ #
1671
+ # (HTTP Only) Returns `true` only if upgraded and open (WebSocket is open).
1672
+ # @return [Boolean]
1673
+ def open?; end
1674
+
1675
+ # (HTTP Only) Returns `false` if headers can still be sent. Otherwise returns `true`.
1676
+ # @return [Boolean]
1677
+ def headers_sent?; end
1678
+
1679
+ # Returns `true` only if the Connection object is still valid (data can be sent).
1680
+ # @return [Boolean]
1681
+ def valid?; end
1682
+
1683
+ # Returns true only of the connection is a WebSocket connection.
1684
+ # @return [Boolean]
1685
+ def websocket?; end
1686
+
1687
+ # Returns true only of the connection is an SSE connection.
1688
+ # @return [Boolean]
1689
+ def sse?; end
1690
+
1691
+ # Returns true only of the connection is an HTTP connection (not upgraded).
1692
+ # @return [Boolean]
1693
+ def http?; end
1694
+
1695
+ # Returns true if the connection uses TLS (e.g., HTTPS or WSS).
1696
+ #
1697
+ # Rack applications see the same fact as `env['rack.url_scheme'] == 'https'`.
1698
+ # @return [Boolean]
1699
+ def tls?; end
1700
+
1701
+ # Returns the number of bytes that need to be sent before the next `on_drained` callback is called.
1702
+ # @return [Integer]
1703
+ def pending; end
1704
+
1705
+ # Schedules the connection to be closed.
1706
+ # @return [self]
1707
+ def close; end
1708
+
1709
+ # @!group Writing to the Connection
1710
+
1711
+ # Sets a response header and returns `true`.
1712
+ #
1713
+ # If headers have already been sent, or if either `write` or `finish` has been previously called, returns `false` (doing nothing).
1714
+ #
1715
+ # Header `name` should be a lowercase String. This will be enforced by Iodine, converting header names to lower case and converting Symbols to Strings.
1716
+ #
1717
+ # `value` must be either a `String`, an `Array` of Strings, or `nil`. Iodine also accepts Symbols and certain Numbers (small numbers).
1718
+ #
1719
+ # **If `value` is `nil`**:
1720
+ # - Performs a non-guaranteed best attempt at removing any existing headers named `name` and returns `false`.
1721
+ #
1722
+ # **If `value` is a `String`**:
1723
+ # - A response header with the given `name` is added to the response and set to `value`. If `name` already exists, multiple headers will be sent.
1724
+ # - Iodine is backwards compatible with Rack 2, treating newline characters as an array of `String`s. Eventually this will be removed.
1725
+ #
1726
+ # **If `value` is an `Array` of `String`s**:
1727
+ # - The method behaves as if called multiple times with the same `name`, once for each element of `value` (except `nil` values are ignored).
1728
+ #
1729
+ # @param name [String] the header name (lowercase)
1730
+ # @param values [String, Array<String>, nil] the header value(s)
1731
+ # @return [Boolean] `true` on success, `false` if headers already sent
1732
+ def write_header(name, values); end
1733
+
1734
+ # Writes data to the connection asynchronously.
1735
+ #
1736
+ # If `data` **should* be a String. However, if `data` is not a String, Iodine will attempt to convert it to a JSON String, allowing Hashes, Arrays and other native Ruby objects to be sent over the wire. Note that `data` **must** be a native Ruby Object, or it could not be converted to a JSON String.
1737
+ #
1738
+ # For WebSocket connections: if `data` is a UTF-8 encoded String, message will be sent as `text`, otherwise message will be sent as `binary`.
1739
+ #
1740
+ # For SSE connections: this is similar to calling `write_sse(nil, nil, data)`.
1741
+ #
1742
+ # @param data [String, Object] the data to write
1743
+ # @return [Boolean] `false` on error / fail and `true` if the event was scheduled to be sent to the client
1744
+ def write(data); end
1745
+
1746
+ # Writes data to EventSource client connections asynchronously.
1747
+ #
1748
+ # The `id` and `event` elements **must** be UTF-8 encoded Strings.
1749
+ #
1750
+ # If `data` **should* be a String. However, if `data` is not a String, Iodine will attempt to convert it to a JSON String, allowing Hashes, Arrays and other native Ruby objects to be sent over the wire. Note that `data` **must** be a native Ruby Object, or it could not be converted to a JSON String.
1751
+ #
1752
+ # An empty `data` String or a missing `data` argument will cause `write_sse` to return false.
1753
+ #
1754
+ # @param id [String, nil] the event ID
1755
+ # @param event [String, nil] the event type
1756
+ # @param data [String, Object] the event data
1757
+ # @return [Boolean] `false` on error / fail and `true` if the event was scheduled to be sent to the client
1758
+ def write_sse(id, event, data); end
1759
+
1760
+ # Closes the connection after writing `data`, or (HTTP Only) finishes the HTTP response.
1761
+ #
1762
+ # @param data [String, nil] optional data to write before closing
1763
+ # @return [self]
1764
+ #
1765
+ # @note For HTTP connections the `"content-length"` header is automatically written if no previous calls to `write` were called.
1766
+ def finish(data = nil); end
1767
+
1768
+ # @!group Reading the HTTP Body / Payload
1769
+
1770
+ # (HTTP Only) Returns an ASCII-8 String with the next line in the Body (same as {::IO#gets} where only `limit` is allowed).
1771
+ #
1772
+ # @param limit [Integer, nil] maximum number of bytes to read
1773
+ # @return [String, nil] the line or nil if at end
1774
+ def gets(limit = nil); end
1775
+
1776
+ # (HTTP Only) Returns an ASCII-8 String with (part of) the Body (see {::IO#read}).
1777
+ #
1778
+ # @param maxlen [Integer, nil] maximum number of bytes to read
1779
+ # @param out_string [String, nil] optional buffer to read into
1780
+ # @return [String, nil] the data or nil if at end
1781
+ def read(maxlen = nil, out_string = nil); end
1782
+
1783
+ # (HTTP Only) Sets the body's new read position. Negative values start at the end of the body. `nil` returns current position.
1784
+ #
1785
+ # @param pos [Integer, nil] the new position
1786
+ # @return [Integer] the current position
1787
+ def seek(pos = nil); end
1788
+
1789
+ # (HTTP Only) Releases the body's resources, freeing the in-memory buffer and closing any temporary file.
1790
+ #
1791
+ # Subsequent body reads will return `nil` and `length` will return `0`. The connection itself remains open.
1792
+ #
1793
+ # @return [Boolean, nil] `true` if body data was available to close, `false` if the body was empty or already closed, or `nil` when there is no HTTP request
1794
+ def close_body; end
1795
+
1796
+ # @!group HTTP Cookies
1797
+
1798
+ # (HTTP Only) Returns an ASCII-8 String with the value of the named cookie.
1799
+ #
1800
+ # @param name [String, Symbol] the cookie name
1801
+ # @return [String, nil] the cookie value or nil
1802
+ def cookie(name); end
1803
+
1804
+ # (HTTP Only) Sets the value of the named cookie and returns `true` upon success.
1805
+ #
1806
+ # If `headers_sent?` returns `true`, calling this method may raise an exceptions.
1807
+ #
1808
+ # If `value` is `nil`, the cookie will be deleted. If `:max_age` is 0 (default), cookie will be session cookie and will be deleted by the browser at its discretion.
1809
+ #
1810
+ # This should behave similar to calling `write_header`, except that the cookie can be read using the {#cookie} method.
1811
+ #
1812
+ # @param name [String, Symbol] the cookie name
1813
+ # @param value [String, nil] the cookie value (nil to delete)
1814
+ # @param max_age [Integer] max age in seconds (0 for session cookie)
1815
+ # @param domain [String, nil] the cookie domain
1816
+ # @param path [String, nil] the cookie path
1817
+ # @param same_site [Symbol, nil] `:default`, `:none`, `:lax`, or `:strict`
1818
+ # @param secure [Boolean] if true, cookie only sent over HTTPS
1819
+ # @param http_only [Boolean] if true, cookie not accessible via JavaScript
1820
+ # @param partitioned [Boolean] if true, cookie is partitioned
1821
+ # @return [Boolean] `true` on success
1822
+ #
1823
+ # @example
1824
+ # set_cookie(name: "MyCookie", value: "My non-secret data", domain: "localhost", max_age: 1_728_000)
1825
+ #
1826
+ # @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
1827
+ def set_cookie(name, value = nil, max_age = 0, domain = nil, path = nil, same_site = nil, secure = false,
1828
+ http_only = false, partitioned = false)
1829
+ end
1830
+
1831
+ # Calls `block` for each name-value cookie pair.
1832
+ #
1833
+ # @yield [name, value] the cookie name and value
1834
+ # @return [self]
1835
+ def each_cookie(&block); end
1836
+
1837
+ # @!group Pub/Sub
1838
+
1839
+ # Publishes a message to a combination of a channel (String) and filter (number).
1840
+ #
1841
+ # @param channel [String, nil] the channel name to publish to
1842
+ # @param filter [Integer] the filter to publish to (Number < 32,767)
1843
+ # @param message [String] the actual message to publish
1844
+ # @param engine [Iodine::PubSub::Engine, nil] the pub/sub engine to use (if not the default one)
1845
+ # @return [Boolean] `true` on success
1846
+ #
1847
+ # @example
1848
+ # Iodine::Connection.publish("channel_name", 0, "payload")
1849
+ # Iodine::Connection.publish(channel: "name", message: "payload")
1850
+ def self.publish(channel = nil, filter = 0, message = nil, engine = nil); end
1851
+
1852
+ # Publishes a message to a combination of a channel (String) and filter (number).
1853
+ #
1854
+ # **Note**: events published by a specific client are sent to "**everyone else**" and ignored by the client itself.
1855
+ #
1856
+ # **Note**: if `engine` was specified and it isn't an Iodine engine, message **may** be delivered to the client calling `publish` as well.
1857
+ #
1858
+ # To publish events to everyone, including the client itself, use the global {Iodine.publish} method or the {.publish} class method.
1859
+ #
1860
+ # @param channel [String, nil] the channel name to publish to
1861
+ # @param filter [Integer] the filter to publish to (Number < 32,767)
1862
+ # @param message [String] the actual message to publish
1863
+ # @param engine [Iodine::PubSub::Engine, nil] the pub/sub engine to use (if not the default one)
1864
+ # @return [Boolean] `true` on success
1865
+ #
1866
+ # @example
1867
+ # client.publish("channel_name", 0, "payload")
1868
+ # client.publish(channel: "name", message: "payload")
1869
+ def publish(channel = nil, filter = 0, message = nil, engine = nil); end
1870
+
1871
+ # Subscribes to a combination of a channel (String) and filter (number).
1872
+ #
1873
+ # @param channel [String, nil] the subscription's channel name
1874
+ # @param filter [Integer] the subscription's filter (Number < 32,767)
1875
+ # @param since [Integer] replay cached messages since this timestamp in milliseconds
1876
+ # (requires {Iodine::PubSub::History.cache} to be called first)
1877
+ # @param callback [Proc, nil] an **optional** object that answers to call and accepts a single argument (the message structure)
1878
+ # @return [Proc, nil] the callback
1879
+ #
1880
+ # @note Either a proc or a block MUST be provided for global subscriptions.
1881
+ #
1882
+ # @example Basic subscription
1883
+ # client.subscribe("name") { |msg| puts msg.message }
1884
+ # client.subscribe(channel: "name", filter: 1) { |msg| puts msg.message }
1885
+ #
1886
+ # @example Subscription with history replay
1887
+ # since_ms = (Time.now.to_i - 60) * 1000
1888
+ # client.subscribe(channel: "chat", since: since_ms) { |msg| puts msg.message }
1889
+ def subscribe(channel = nil, filter = 0, since = 0, &callback); end
1890
+
1891
+ # Subscribes to a combination of a channel (String) and filter (number).
1892
+ #
1893
+ # @param channel [String, nil] the subscription's channel name
1894
+ # @param filter [Integer] the subscription's filter (Number < 32,767)
1895
+ # @param since [Integer] replay cached messages since this timestamp in milliseconds
1896
+ # (requires {Iodine::PubSub::History.cache} to be called first)
1897
+ # @param callback [Proc, nil] an **optional** object that answers to call and accepts a single argument (the message structure)
1898
+ # @return [Proc, nil] the callback
1899
+ #
1900
+ # @note Either a proc or a block MUST be provided for global subscriptions.
1901
+ #
1902
+ # @example
1903
+ # Iodine::Connection.subscribe("name") { |msg| puts msg.message }
1904
+ def self.subscribe(channel = nil, filter = 0, since = 0, &callback); end
1905
+
1906
+ # Un-Subscribes from a combination of a channel (String) and filter (number).
1907
+ #
1908
+ # @param channel [String, nil] the subscription's channel name
1909
+ # @param filter [Integer] the subscription's filter (Number < 32,767)
1910
+ # @return [Boolean] `true` on success
1911
+ #
1912
+ # @example
1913
+ # client.unsubscribe("name")
1914
+ # client.unsubscribe(channel: "name", filter: 1)
1915
+ def unsubscribe(channel = nil, filter = 0); end
1916
+
1917
+ # Un-Subscribes from a combination of a channel (String) and filter (number).
1918
+ #
1919
+ # @param channel [String, nil] the subscription's channel name
1920
+ # @param filter [Integer] the subscription's filter (Number < 32,767)
1921
+ # @return [Boolean] `true` on success
1922
+ #
1923
+ # @example
1924
+ # Iodine::Connection.unsubscribe("name")
1925
+ def self.unsubscribe(channel = nil, filter = 0); end
1926
+
1927
+ # @!group Misc
1928
+
1929
+ # Returns the raw peer IP address.
1930
+ # @return [String]
1931
+ def peer_addr; end
1932
+
1933
+ # Returns the published peer address (may differ with proxies).
1934
+ # @return [String]
1935
+ def from; end
1936
+
1937
+ # Returns the peer's leaf certificate, if the peer presented one.
1938
+ #
1939
+ # Returns `nil` when unavailable (not a TLS connection, handshake
1940
+ # incomplete, or the peer sent no certificate). Never raises.
1941
+ #
1942
+ # @return [Iodine::TLS::Certificate, nil] the peer's leaf certificate
1943
+ #
1944
+ # @example Client certificate authorization
1945
+ # def on_http(client)
1946
+ # cert = client.certificate
1947
+ # return forbidden unless cert&.verified?
1948
+ # user = Users.find_by(fingerprint: cert.fingerprint)
1949
+ # # ...
1950
+ # end
1951
+ def certificate; end
1952
+
1953
+ # Yields the peer's certificate chain, leaf certificate first.
1954
+ #
1955
+ # Allocates one {Iodine::TLS::Certificate} per certificate (they are
1956
+ # snapshots, safe to keep). Returns an Enumerator when no block is given.
1957
+ #
1958
+ # @yieldparam cert [Iodine::TLS::Certificate] a certificate in the chain
1959
+ # @return [Enumerator, self]
1960
+ def each_certificate; end
1961
+
1962
+ # Hijacks the connection from the Server and returns an IO object.
1963
+ #
1964
+ # This method MAY be used to implement a full hijack when providing compatibility with the Rack specification.
1965
+ #
1966
+ # @return [IO] for current underlying socket connection
1967
+ def rack_hijack; end
1968
+
1969
+ #######################
1970
+
1971
+ # This is an example handler for documentation purposes **only**.
1972
+ #
1973
+ # This module is designed to help you author your own Handler for {Iodine.listen} and {Iodine::Connection.new}.
1974
+ #
1975
+ # A handler implementation is expected to implement at least one of the callbacks defined for this example Handler. Any missing callbacks will be defined by Iodine using smart defaults (where possible).
1976
+ #
1977
+ # **Note**: This module isn't implemented by Iodine, but is for documentation and guidance only.
1978
+ module Handler
1979
+ # @!group Callbacks
1980
+
1981
+ # Used for [NeoRack](https://github.com/boazsegev/neorack) application authoring, when receiving HTTP requests.
1982
+ #
1983
+ # [NeoRack](https://github.com/boazsegev/neorack) allows both Async and CGI style Web Applications, making it easier to stream data and do more with a single thread.
1984
+ #
1985
+ # @param event [Iodine::Connection] the connection/event object
1986
+ def self.on_http(event); end
1987
+
1988
+ # Used for [NeoRack](https://github.com/boazsegev/neorack) application authoring, when an HTTP request handling had finished.
1989
+ #
1990
+ # @param event [Iodine::Connection] the connection/event object
1991
+ def self.on_finish(event); end
1992
+
1993
+ # Used for [Rack](https://github.com/rack/rack) application authoring, when receiving HTTP requests.
1994
+ #
1995
+ # [Rack](https://github.com/rack/rack) is the classical server interface and it is supported by Iodine with minimal extensions.
1996
+ #
1997
+ # @param env [Hash] the Rack environment hash
1998
+ # @return [Array] the Rack response [status, headers, body]
1999
+ def self.call(env); end
2000
+
2001
+ # When present, a missing `on_authenticate_sse` or missing `on_authenticate_websocket` will route to this callback, allowing authentication logic to be merged in one method.
2002
+ #
2003
+ # **Must** return `true` for the connection to be allowed. **Any other return value will cause the connection to be refused** (and disconnected).
2004
+ #
2005
+ # By default, if `on_message` or `on_open` are defined, than `on_authenticate` returns `true`. Otherwise, `on_authenticate` returns `false`.
2006
+ #
2007
+ # @param connection [Iodine::Connection] the connection object
2008
+ # @return [Boolean] `true` to allow, `false` to deny
2009
+ def self.on_authenticate(connection); end
2010
+
2011
+ # Called when an Event Source (SSE) request is detected, to authenticate and possibly set the identity of the user.
2012
+ #
2013
+ # By default, if `on_open` is defined, than `on_authenticate_sse` returns `true`. Otherwise, `on_authenticate` returns `false`.
2014
+ #
2015
+ # @param connection [Iodine::Connection] the connection object
2016
+ # @return [Boolean] `true` to allow, `false` to deny
2017
+ def self.on_authenticate_sse(connection); end
2018
+
2019
+ # Called when a WebSocket Upgrade request is detected, to authenticate and possibly set the identity of the user.
2020
+ #
2021
+ # By default, if `on_message` or `on_open` are defined, than `on_authenticate_websocket` returns `true`. Otherwise, `on_authenticate` returns `false`.
2022
+ #
2023
+ # @param connection [Iodine::Connection] the connection object
2024
+ # @return [Boolean] `true` to allow, `false` to deny
2025
+ def self.on_authenticate_websocket(connection); end
2026
+
2027
+ # Called when a long-running connection opens. This will be called for Event Source, WebSocket, TCP/IP and Raw Unix Socket connections.
2028
+ #
2029
+ # @param connection [Iodine::Connection] the connection object
2030
+ def self.on_open(connection); end
2031
+
2032
+ # Called when incoming data arrives and its behavior depends on the underlying protocol.
2033
+ #
2034
+ # For TCP/IP and Raw Unix connections, the data is a Binary encoded String with some (or all) of the data available in the incoming socket buffer.
2035
+ #
2036
+ # For WebSockets, the `data` element will contain a String with the WebSocket Message.
2037
+ #
2038
+ # If the WebSocket message is in text, the String encoding will be UTF-8.
2039
+ #
2040
+ # If the WebSocket message is binary, the String encoding will be Binary (ASCII).
2041
+ #
2042
+ # @param connection [Iodine::Connection] the connection object
2043
+ # @param data [String] the received data
2044
+ def self.on_message(connection, data); end
2045
+
2046
+ # Called when all calls to {Iodine::Connection#write} have been handled and the outgoing buffer is now empty.
2047
+ #
2048
+ # @param connection [Iodine::Connection] the connection object
2049
+ def self.on_drained(connection); end
2050
+
2051
+ # Called when timeout has been reached for a WebSocket / TCP/IP / Raw Unix Socket connection.
2052
+ #
2053
+ # @param connection [Iodine::Connection] the connection object
2054
+ def self.on_timeout(connection); end
2055
+
2056
+ # Called when the worker that manages this connection (or the root process, in non-cluster mode) starts shutting down.
2057
+ #
2058
+ # @param connection [Iodine::Connection] the connection object
2059
+ def self.on_shutdown(connection); end
2060
+
2061
+ # Called when the connection was closed (WebSocket / TCP/IP / Raw Unix Socket).
2062
+ #
2063
+ # @param connection [Iodine::Connection] the connection object
2064
+ def self.on_close(connection); end
2065
+
2066
+ # Called when an Event Source (SSE) event has been received (when acting as an Event Source client).
2067
+ #
2068
+ # @param connection [Iodine::Connection] the connection object
2069
+ # @param message [Iodine::PubSub::Message] the SSE message
2070
+ def self.on_eventsource(connection, message); end
2071
+
2072
+ # Called an Event Source (SSE) client send a re-connection request with the ID of the last message received.
2073
+ #
2074
+ # @param connection [Iodine::Connection] the connection object
2075
+ # @param last_message_id [String] the last message ID
2076
+ def self.on_eventsource_reconnect(connection, last_message_id); end
2077
+ end
2078
+
2079
+ # This is an example REST/CRUD resource handler for documentation purposes **only**.
2080
+ #
2081
+ # This module is designed to help you author your own Resource Handler for {Iodine.make_resource}.
2082
+ #
2083
+ # A resource handler implementation is expected to implement all the callbacks. Any missing callbacks will result in a 404 error.
2084
+ #
2085
+ # **Note**: This module isn't implemented by Iodine, it is for documentation and guidance only.
2086
+ module ResourceHandler
2087
+ # @!group Callbacks
2088
+
2089
+ # Called for `"GET /"`.
2090
+ # @param e [Iodine::Connection] the connection object
2091
+ def self.index(e) = e.finish('Show Index')
2092
+
2093
+ # Called for `"QUERY /*"`, re-routes to `index` if missing – assumes either a search or index filtering.
2094
+ # @param e [Iodine::Connection] the connection object
2095
+ def self.query(e) = self.index(e)
2096
+
2097
+ # Called for `"GET /(id)"`, where `id` can be assumed to be: `e.path[1..-1]`.
2098
+ # @param e [Iodine::Connection] the connection object
2099
+ def self.show(e) = e.finish("Show Item: #{e.path[1..-1]}")
2100
+
2101
+ # Called for `"GET /new"`, expecting a response containing a form for posting a new element.
2102
+ # @param e [Iodine::Connection] the connection object
2103
+ def self.new(e) = e.finish('New Item Form.')
2104
+
2105
+ # Called for `"GET /(id)/edit"`, expecting a response containing a form for editing the existing element with `id` being `e.path.split('/')[1]`.
2106
+ # @param e [Iodine::Connection] the connection object
2107
+ def self.edit(e) = e.finish("Edit Item: #{e.path[1..-1]}")
2108
+
2109
+ # Called for `"PATCH /"`, `"PUT /"`, `"POST /"`, `"PATCH /new"`, `"PUT /new"`, `"POST /new"` (`new` is optional).
2110
+ # @param e [Iodine::Connection] the connection object
2111
+ def self.create(e) = e.finish('Create a new Item.')
2112
+
2113
+ # Called for `"PATCH /(id)"`, `"PUT /(id)"`, `"POST /(id)"`, where `id` can be assumed to be: `e.path[1..-1]`.
2114
+ # @param e [Iodine::Connection] the connection object
2115
+ def self.update(e) = e.finish("Update Item: #{e.path[1..-1]}")
2116
+
2117
+ # Called for `"DELETE /(id)"`, where `id` can be assumed to be: `e.path[1..-1]`.
2118
+ # @param e [Iodine::Connection] the connection object
2119
+ def self.delete(e) = e.finish("Delete Item: #{e.path[1..-1]}")
2120
+ end
2121
+ end
2122
+
2123
+ #######################
2124
+
2125
+ # Used to setup a TLS contexts for connections (incoming / outgoing).
2126
+ #
2127
+ # == TLS Backend Constants
2128
+ #
2129
+ # The following constants provide information about available TLS backends:
2130
+ #
2131
+ # - `Iodine::TLS::SUPPORTED` - Always `true` (embedded TLS always available).
2132
+ # - `Iodine::TLS::OPENSSL_AVAILABLE` - Returns `true` if OpenSSL backend is compiled in.
2133
+ # - `Iodine::TLS::EMBEDDED_AVAILABLE` - Returns `true` (embedded TLS 1.3 is always available).
2134
+ #
2135
+ # @example Checking available backends
2136
+ # puts "Default TLS backend: #{Iodine::TLS.default}"
2137
+ # puts "OpenSSL available: #{Iodine::TLS::OPENSSL_AVAILABLE}"
2138
+ # puts "Embedded TLS available: #{Iodine::TLS::EMBEDDED_AVAILABLE}"
2139
+ #
2140
+ class TLS
2141
+ # @return [Boolean] `true` (TLS is always supported - at least embedded is available).
2142
+ SUPPORTED = true
2143
+
2144
+ # @return [Boolean] `true` if OpenSSL backend is compiled in and available.
2145
+ OPENSSL_AVAILABLE = true
2146
+
2147
+ # @return [Boolean] `true` (embedded TLS 1.3 is always available).
2148
+ EMBEDDED_AVAILABLE = true
2149
+
2150
+ # Returns the current default TLS backend.
2151
+ #
2152
+ # @return [Symbol] `:openssl` or `:iodine` indicating the current default backend
2153
+ def self.default; end
2154
+
2155
+ # Sets the default TLS backend.
2156
+ #
2157
+ # @param backend [Symbol] `:iodine` (embedded TLS 1.3) or `:openssl`
2158
+ # @return [Symbol] the new default backend
2159
+ # @raise [TypeError] if backend is not a Symbol
2160
+ # @raise [ArgumentError] if backend is not `:iodine` or `:openssl`
2161
+ # @raise [RuntimeError] if `:openssl` is requested but not available
2162
+ #
2163
+ # @example
2164
+ # Iodine::TLS.default = :iodine # Use embedded TLS 1.3
2165
+ # Iodine::TLS.default = :openssl # Use OpenSSL
2166
+ def self.default=(backend); end
2167
+
2168
+ # Assigns the TLS context a public certificate, allowing remote parties to validate the connection's identity.
2169
+ #
2170
+ # A self signed certificate is automatically created if the `name` argument is specified and either (or both) of the `cert` (public certificate) or `key` (private key) arguments are missing.
2171
+ #
2172
+ # Some implementations allow servers to have more than a single certificate, which will be selected using the SNI extension. I believe the existing OpenSSL implementation supports this option (untested).
2173
+ #
2174
+ # Iodine::TLS#add_cert(name = nil,
2175
+ # cert = nil,
2176
+ # key = nil,
2177
+ # password = nil)
2178
+ #
2179
+ # Certificates and keys should be String objects leading to a PEM file.
2180
+ #
2181
+ # @param name [String, nil] the server name (for SNI)
2182
+ # @param cert [String, nil] path to the public certificate PEM file
2183
+ # @param key [String, nil] path to the private key PEM file
2184
+ # @param password [String, nil] password for the private key
2185
+ # @return [self]
2186
+ #
2187
+ # @example
2188
+ # tls = Iodine::TLS.new
2189
+ # tls.add_cert name: "example.com"
2190
+ # tls.add_cert cert: "my_cert.pem", key: "my_key.pem"
2191
+ # tls.add_cert cert: "my_cert.pem", key: "my_key.pem", password: ENV['TLS_PASS']
2192
+ #
2193
+ # @note Since TLS setup is crucial for security, an initialization error will result in Iodine crashing with an error message. This is expected behavior.
2194
+ def add_cert(name = nil, cert = nil, key = nil, password = nil); end
2195
+
2196
+ # Adds a public certificate (or certificate bundle) to the "trust" list, enabling peer (client) certificate authentication / authorization.
2197
+ #
2198
+ # Once the trust list is non-empty, servers using this TLS context request a certificate from connecting clients and verify its chain against the trusted certificates. The peer's certificate chain can then be reviewed by the application using {Iodine::Connection#certificate} and {Iodine::Connection#each_certificate}.
2199
+ #
2200
+ # @param path [String, nil] path to a PEM file with one or more trusted certificates (`nil` for the system's trust registry).
2201
+ # @return [Iodine::TLS] returns itself, so calls may be chained.
2202
+ #
2203
+ # @example
2204
+ # tls = Iodine::TLS.new
2205
+ # tls.add_cert(cert: "server.pem", key: "server.key")
2206
+ # tls.trust("clients-ca.pem")
2207
+ def trust(path = nil); end
2208
+
2209
+ # An immutable snapshot of an X.509 certificate.
2210
+ #
2211
+ # Instances are created by Iodine (see {Iodine::Connection#certificate} and {Iodine::Connection#each_certificate}) and cannot be constructed from Ruby code.
2212
+ class Certificate
2213
+ # @return [String] the raw DER data (binary).
2214
+ attr_reader :der
2215
+ # @return [String] the certificate's serial number (binary).
2216
+ attr_reader :serial
2217
+ # @return [String] the Subject Distinguished Name (raw DER, binary).
2218
+ attr_reader :subject
2219
+ # @return [String] the Issuer Distinguished Name (raw DER, binary).
2220
+ attr_reader :issuer
2221
+ # @return [String, nil] the Subject Common Name, if present.
2222
+ attr_reader :cn
2223
+ # @return [String] the signature value (binary).
2224
+ attr_reader :signature
2225
+ # @return [String, nil] the first DNS Subject Alternative Name, if present.
2226
+ attr_reader :san_dns
2227
+ # @return [String, nil] the first IP Subject Alternative Name (binary, 4 or 16 bytes), if present.
2228
+ attr_reader :san_ip
2229
+ # @return [Time] beginning of the validity period.
2230
+ attr_reader :not_before
2231
+ # @return [Time] end of the validity period.
2232
+ attr_reader :not_after
2233
+ # @return [String] the SHA-256 fingerprint of the DER data (32 bytes, binary).
2234
+ attr_reader :fingerprint
2235
+ # @return [Symbol] the public key algorithm (`:rsa`, `:ecdsa_p256`, `:ecdsa_p384`, `:ed25519` or `:unknown`).
2236
+ attr_reader :key_algo
2237
+ # @return [Symbol] the signature algorithm (i.e., `:rsa_pkcs1_sha256`, `:rsa_pss_sha384`, `:ecdsa_sha256`, `:ed25519` or `:unknown`).
2238
+ attr_reader :signature_algo
2239
+ # @return [Integer, nil] the Key Usage extension bitmask, if the extension is present.
2240
+ attr_reader :key_usage
2241
+ # @return [Integer] the certificate version (0 = v1, 1 = v2, 2 = v3).
2242
+ attr_reader :version
2243
+ # @return [Integer] the certificate's position in the peer's chain (0 = leaf).
2244
+ attr_reader :chain_index
2245
+ # @return [Boolean] `true` if the TLS backend verified this certificate's chain against the trust store.
2246
+ def verified?; end
2247
+ # @return [Boolean] `true` if the certificate is a CA (Basic Constraints).
2248
+ def ca?; end
2249
+ end
2250
+ end
2251
+
2252
+ #######################
2253
+
2254
+ # Used internally by Iodine.
2255
+ class Base
2256
+ # Prints the number of object withheld from the GC (for debugging).
2257
+ def self.print_debug; end
2258
+
2259
+ # Sets Iodine's cache limit for frozen strings, limited to 65,535 items.
2260
+ #
2261
+ # @param new_limit [Integer] the new cache limit
2262
+ def self.cache_limit=(new_limit); end
2263
+
2264
+ # Returns Iodine's cache limit for frozen strings.
2265
+ #
2266
+ # @return [Integer] the current cache limit
2267
+ def self.cache_limit; end
2268
+
2269
+ # Parses and manages CLI input.
2270
+ module CLI
2271
+ # Parses CLI input.
2272
+ #
2273
+ # If `required` is true and CLI detects an error, program will exit with an appropriate exit message and a list of valid CLI options.
2274
+ #
2275
+ # @param required [Boolean] whether to exit on error
2276
+ def parse(required = false); end
2277
+
2278
+ # Returns a CLI option's value.
2279
+ #
2280
+ # @param key [String] the option key
2281
+ # @return [String, nil] the option value
2282
+ def [](key); end
2283
+
2284
+ # Sets a CLI option's value.
2285
+ #
2286
+ # @param key [String] the option key
2287
+ # @param value [String] the option value
2288
+ def []=(key, value); end
2289
+ end
2290
+
2291
+ # A small Hash implementation used internally to bridge C data structures with Ruby.
2292
+ #
2293
+ # This implementation would normally run slower when called and executed exclusively from within the Ruby layer,
2294
+ # as it is optimized for C callbacks and is used to bridge Ruby and C data structures.
2295
+ #
2296
+ # To benchmark the C performance (excluding the Ruby overhead), call {MiniMap.cbench}.
2297
+ #
2298
+ # It is also possible to benchmark the Ruby performance vs. the Ruby Hash using the `Iodine::Benchmark` module.
2299
+ module MiniMap
2300
+ # Returns the value associated with @key.
2301
+ #
2302
+ # @param key [Object] the key
2303
+ # @return [Object, nil] the value
2304
+ def [](key); end
2305
+
2306
+ # Sets the @value associated with @key and returns it.
2307
+ #
2308
+ # @param key [Object] the key
2309
+ # @param value [Object] the value
2310
+ # @return [Object] the value
2311
+ def []=(key, value); end
2312
+
2313
+ # Runs block for each key-value pair. Returns the number of times the block was executed.
2314
+ #
2315
+ # @yield [key, value] the key-value pairs
2316
+ # @return [Integer] the number of iterations
2317
+ #
2318
+ # @example
2319
+ # m = Iodine::Base::MiniMap.new
2320
+ # m[:go] = "home"
2321
+ # m.each {|key, value| puts "#{key} => #{value}"}
2322
+ def each(&block); end
2323
+
2324
+ # Returns the number of key-value pairs stored in the Hash.
2325
+ # @return [Integer]
2326
+ def count; end
2327
+
2328
+ # Empties the map. Returns self.
2329
+ # @return [self]
2330
+ def clear; end
2331
+
2332
+ # Returns the maximum number of theoretical items the Hash can contain.
2333
+ #
2334
+ # @return [Integer]
2335
+ #
2336
+ # @note Capacity increase will likely happen sooner and this number deals more with pre-allocated memory for the objects than with actual capacity.
2337
+ def capa; end
2338
+
2339
+ # Reserves room in the Hash Map for at least the @capacity requested.
2340
+ #
2341
+ # @param capacity [Integer] the capacity to reserve
2342
+ # @see #capa
2343
+ def reserve(capacity); end
2344
+
2345
+ # Prints a JSON representation of the map (if possible).
2346
+ # @return [String]
2347
+ def to_s; end
2348
+
2349
+ # Benchmarks MiniMap v.s Hash performance when called from within the C layer.
2350
+ #
2351
+ # Numbers printed out are in time units. Lower is better.
2352
+ #
2353
+ # @param number_of_items [Integer] the number of items to benchmark
2354
+ # @return [String]
2355
+ def self.cbench(number_of_items); end
2356
+ end
2357
+
2358
+ # This is an empty web application that does nothing.
2359
+ #
2360
+ # Iodine provides default implementations for any missing callback (which is all of them in this example). Thus, this app does nothing aside of refusing WebSocket / SSE connections and returning HTTP error 404.
2361
+ #
2362
+ # This app is used internally when a static file service is requested without a handler. i.e.:
2363
+ #
2364
+ # ```sh
2365
+ # iodine -www ./public_folder
2366
+ # ```
2367
+ #
2368
+ # Which results in a `WARNING: no App found` but no error.
2369
+ #
2370
+ module App404
2371
+ end
2372
+
2373
+ # Provides access to data compression and decompression algorithms.
2374
+ #
2375
+ # This module contains submodules for:
2376
+ # - {Deflate} - Raw DEFLATE compression (RFC 1951) - no headers
2377
+ # - {Gzip} - GZIP compression (RFC 1952) - deflate with gzip wrapper
2378
+ # - {Brotli} - Brotli compression (RFC 7932) - modern, high-ratio compression
2379
+ #
2380
+ # All compression methods accept a binary String and return a binary String.
2381
+ # Decompression methods will raise {RuntimeError} if the input data is
2382
+ # corrupt or truncated.
2383
+ #
2384
+ # @note This module is under `Iodine::Base` as the API may change between versions.
2385
+ #
2386
+ # @example Deflate round-trip
2387
+ # compressed = Iodine::Base::Compression::Deflate.compress("Hello, World!" * 100)
2388
+ # original = Iodine::Base::Compression::Deflate.decompress(compressed)
2389
+ #
2390
+ # @example Gzip for HTTP responses
2391
+ # body = Iodine::Base::Compression::Gzip.compress(response_body, level: 6)
2392
+ #
2393
+ # @example Brotli for static assets
2394
+ # compressed = Iodine::Base::Compression::Brotli.compress(File.read("app.js"), quality: 4)
2395
+ module Compression
2396
+ # Raw DEFLATE compression (RFC 1951).
2397
+ #
2398
+ # Produces raw deflate data without any headers or trailers.
2399
+ # Suitable for protocols that manage their own framing (e.g., WebSocket
2400
+ # permessage-deflate) or when you need the smallest possible output.
2401
+ #
2402
+ # For HTTP responses, prefer {Gzip} which adds the standard gzip wrapper.
2403
+ module Deflate
2404
+ # Compresses data using raw DEFLATE.
2405
+ #
2406
+ # @param data [String] Data to compress
2407
+ # @param level [Integer] Compression level (0-9, default: 3 for lower server CPU cost).
2408
+ # 0 = no compression (store), 1-3 = fast, 4-6 = balanced, 7-9 = best compression.
2409
+ # @return [String] Compressed data (binary string)
2410
+ # @raise [ArgumentError] if level is not between 0 and 9
2411
+ # @raise [RuntimeError] if compression fails
2412
+ #
2413
+ # @example
2414
+ # compressed = Iodine::Base::Compression::Deflate.compress("Hello, World!", level: 6)
2415
+ def self.compress(data, level: 3); end
2416
+
2417
+ # Decompresses raw DEFLATE data.
2418
+ #
2419
+ # @param data [String] Compressed data (raw DEFLATE, no gzip/zlib headers)
2420
+ # @param max_output [Integer] Maximum output bytes (default: 64 MiB); set
2421
+ # lower for untrusted input, higher for trusted large streams.
2422
+ # @return [String] Decompressed data (binary string)
2423
+ # @raise [ArgumentError] if output exceeds max_output
2424
+ # @raise [RuntimeError] if data is corrupt or truncated
2425
+ #
2426
+ # @example
2427
+ # original = Iodine::Base::Compression::Deflate.decompress(compressed)
2428
+ def self.decompress(data, max_output: 64 * 1024 * 1024); end
2429
+ end
2430
+
2431
+ # GZIP compression (RFC 1952).
2432
+ #
2433
+ # Produces standard gzip-formatted data with headers and CRC32 trailer.
2434
+ # This is the format used by HTTP `Content-Encoding: gzip` and the `gzip`
2435
+ # command-line tool. Compatible with `Zlib::GzipReader` in Ruby stdlib.
2436
+ #
2437
+ # Internally uses the same DEFLATE algorithm as {Deflate} but wraps the
2438
+ # output with the 18-byte gzip header and trailer.
2439
+ module Gzip
2440
+ # Compresses data using GZIP format.
2441
+ #
2442
+ # @param data [String] Data to compress
2443
+ # @param level [Integer] Compression level (0-9, default: 3 for lower server CPU cost).
2444
+ # 0 = no compression (store), 1-3 = fast, 4-6 = balanced, 7-9 = best compression.
2445
+ # @return [String] GZIP-compressed data (binary string)
2446
+ # @raise [ArgumentError] if level is not between 0 and 9
2447
+ # @raise [RuntimeError] if compression fails
2448
+ #
2449
+ # @example
2450
+ # gzipped = Iodine::Base::Compression::Gzip.compress("Hello, World!", level: 6)
2451
+ def self.compress(data, level: 3); end
2452
+
2453
+ # Decompresses GZIP data.
2454
+ #
2455
+ # @param data [String] GZIP-compressed data
2456
+ # @param max_output [Integer] Maximum output bytes (default: 64 MiB); set
2457
+ # lower for untrusted input, higher for trusted large streams.
2458
+ # @return [String] Decompressed data (binary string)
2459
+ # @raise [ArgumentError] if output exceeds max_output
2460
+ # @raise [RuntimeError] if data is corrupt or truncated
2461
+ #
2462
+ # @example
2463
+ # original = Iodine::Base::Compression::Gzip.decompress(gzipped)
2464
+ def self.decompress(data, max_output: 64 * 1024 * 1024); end
2465
+ end
2466
+
2467
+ # Brotli compression (RFC 7932).
2468
+ #
2469
+ # Brotli is a modern compression algorithm developed by Google that typically
2470
+ # achieves better compression ratios than DEFLATE/gzip, especially for text
2471
+ # content like HTML, CSS, and JavaScript. It is widely supported by browsers
2472
+ # via `Content-Encoding: br`.
2473
+ #
2474
+ # @note The quality parameter ranges from 1-6 (not 0-11 as in the reference
2475
+ # Brotli library). Quality 3 balances CPU and size; 5-6 use slower
2476
+ # hash-chain matching and context modeling.
2477
+ module Brotli
2478
+ # Compresses data using Brotli.
2479
+ #
2480
+ # @param data [String] Data to compress
2481
+ # @param quality [Integer] Compression quality (1-6, default: 3).
2482
+ # 1 = fastest (greedy), 5-6 = higher compression at greater CPU cost.
2483
+ # @return [String] Brotli-compressed data (binary string)
2484
+ # @raise [ArgumentError] if quality is not between 1 and 6
2485
+ # @raise [RuntimeError] if compression fails
2486
+ #
2487
+ # @example
2488
+ # compressed = Iodine::Base::Compression::Brotli.compress("Hello, World!", quality: 4)
2489
+ def self.compress(data, quality: 3); end
2490
+
2491
+ # Decompresses Brotli data.
2492
+ #
2493
+ # @param data [String] Brotli-compressed data
2494
+ # @param max_output [Integer] Maximum output bytes (default: 64 MiB); set
2495
+ # lower for untrusted input, higher for trusted large streams.
2496
+ # @return [String] Decompressed data (binary string)
2497
+ # @raise [ArgumentError] if output exceeds max_output
2498
+ # @raise [RuntimeError] if data is corrupt or truncated
2499
+ #
2500
+ # @example
2501
+ # original = Iodine::Base::Compression::Brotli.decompress(compressed)
2502
+ def self.decompress(data, max_output: 64 * 1024 * 1024); end
2503
+ end
2504
+ end
2505
+
2506
+ # Provides access to modern cryptographic primitives.
2507
+ #
2508
+ # This module contains submodules for:
2509
+ # - {AES128GCM} - AEAD symmetric encryption with AES-128 (12-byte nonce)
2510
+ # - {AES256GCM} - AEAD symmetric encryption with AES-256 (12-byte nonce)
2511
+ # - {ChaCha20Poly1305} - AEAD symmetric encryption (12-byte nonce)
2512
+ # - {XChaCha20Poly1305} - AEAD symmetric encryption (24-byte nonce, safe for random)
2513
+ # - {Ed25519} - Digital signatures (with key conversion to X25519)
2514
+ # - {X25519} - Key exchange and public-key encryption (ECIES with ChaCha20, AES-128, or AES-256)
2515
+ # - {HKDF} - Key derivation (RFC 5869)
2516
+ # - {X25519MLKEM768} - Post-quantum hybrid key encapsulation (X25519 + ML-KEM-768)
2517
+ #
2518
+ # @note This module is under `Iodine::Base` as the API may change between versions.
2519
+ module Crypto
2520
+ # ChaCha20-Poly1305 AEAD encryption with 12-byte nonce.
2521
+ #
2522
+ # @note The 12-byte nonce requires careful management to avoid reuse.
2523
+ # Consider using {XChaCha20Poly1305} if you need random nonces.
2524
+ module ChaCha20Poly1305
2525
+ # Encrypts data using ChaCha20-Poly1305 AEAD.
2526
+ #
2527
+ # @param data [String] Plaintext to encrypt
2528
+ # @param key [String] 32-byte encryption key
2529
+ # @param nonce [String] 12-byte nonce (must be unique per key)
2530
+ # @param ad [String, nil] Optional additional authenticated data
2531
+ # @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
2532
+ # @raise [ArgumentError] if key is not 32 bytes or nonce is not 12 bytes
2533
+ #
2534
+ # @example
2535
+ # key = Iodine::Utils.random(32)
2536
+ # nonce = Iodine::Utils.random(12)
2537
+ # ciphertext, mac = Iodine::Base::Crypto::ChaCha20Poly1305.encrypt("secret", key: key, nonce: nonce)
2538
+ def self.encrypt(data, key:, nonce:, ad: nil); end
2539
+
2540
+ # Decrypts data using ChaCha20-Poly1305 AEAD.
2541
+ #
2542
+ # @param data [String] Ciphertext to decrypt
2543
+ # @param mac [String] 16-byte authentication tag
2544
+ # @param key [String] 32-byte encryption key
2545
+ # @param nonce [String] 12-byte nonce
2546
+ # @param ad [String, nil] Optional additional authenticated data
2547
+ # @return [String] Decrypted plaintext
2548
+ # @raise [ArgumentError] if key, nonce, or mac have incorrect sizes
2549
+ # @raise [RuntimeError] if authentication fails
2550
+ #
2551
+ # @example
2552
+ # plaintext = Iodine::Base::Crypto::ChaCha20Poly1305.decrypt(data, mac: mac, key: key, nonce: nonce)
2553
+ def self.decrypt(data, mac:, key:, nonce:, ad: nil); end
2554
+ end
2555
+
2556
+ # AES-128-GCM AEAD encryption with 12-byte nonce.
2557
+ #
2558
+ # AES-128-GCM provides authenticated encryption using the AES block cipher
2559
+ # with 128-bit keys in Galois/Counter Mode. It is widely supported and
2560
+ # offers excellent performance on hardware with AES-NI instructions.
2561
+ #
2562
+ # @note The 12-byte nonce requires careful management to avoid reuse.
2563
+ # Never use the same nonce twice with the same key.
2564
+ module AES128GCM
2565
+ # Encrypts data using AES-128-GCM AEAD.
2566
+ #
2567
+ # @param data [String] Plaintext to encrypt
2568
+ # @param key [String] 16-byte encryption key
2569
+ # @param nonce [String] 12-byte nonce (must be unique per key)
2570
+ # @param ad [String, nil] Optional additional authenticated data
2571
+ # @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
2572
+ # @raise [ArgumentError] if key is not 16 bytes or nonce is not 12 bytes
2573
+ #
2574
+ # @example
2575
+ # key = Iodine::Utils.random(16)
2576
+ # nonce = Iodine::Utils.random(12)
2577
+ # ciphertext, mac = Iodine::Base::Crypto::AES128GCM.encrypt("secret", key: key, nonce: nonce)
2578
+ def self.encrypt(data, key:, nonce:, ad: nil); end
2579
+
2580
+ # Decrypts data using AES-128-GCM AEAD.
2581
+ #
2582
+ # @param data [String] Ciphertext to decrypt
2583
+ # @param mac [String] 16-byte authentication tag
2584
+ # @param key [String] 16-byte encryption key
2585
+ # @param nonce [String] 12-byte nonce
2586
+ # @param ad [String, nil] Optional additional authenticated data
2587
+ # @return [String] Decrypted plaintext
2588
+ # @raise [ArgumentError] if key, nonce, or mac have incorrect sizes
2589
+ # @raise [RuntimeError] if authentication fails
2590
+ #
2591
+ # @example
2592
+ # plaintext = Iodine::Base::Crypto::AES128GCM.decrypt(data, mac: mac, key: key, nonce: nonce)
2593
+ def self.decrypt(data, mac:, key:, nonce:, ad: nil); end
2594
+ end
2595
+
2596
+ # AES-256-GCM AEAD encryption with 12-byte nonce.
2597
+ #
2598
+ # AES-256-GCM provides authenticated encryption using the AES block cipher
2599
+ # with 256-bit keys in Galois/Counter Mode. It offers a higher security margin
2600
+ # than AES-128-GCM and is recommended for long-term security requirements.
2601
+ #
2602
+ # @note The 12-byte nonce requires careful management to avoid reuse.
2603
+ # Never use the same nonce twice with the same key.
2604
+ module AES256GCM
2605
+ # Encrypts data using AES-256-GCM AEAD.
2606
+ #
2607
+ # @param data [String] Plaintext to encrypt
2608
+ # @param key [String] 32-byte encryption key
2609
+ # @param nonce [String] 12-byte nonce (must be unique per key)
2610
+ # @param ad [String, nil] Optional additional authenticated data
2611
+ # @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
2612
+ # @raise [ArgumentError] if key is not 32 bytes or nonce is not 12 bytes
2613
+ #
2614
+ # @example
2615
+ # key = Iodine::Utils.random(32)
2616
+ # nonce = Iodine::Utils.random(12)
2617
+ # ciphertext, mac = Iodine::Base::Crypto::AES256GCM.encrypt("secret", key: key, nonce: nonce)
2618
+ def self.encrypt(data, key:, nonce:, ad: nil); end
2619
+
2620
+ # Decrypts data using AES-256-GCM AEAD.
2621
+ #
2622
+ # @param data [String] Ciphertext to decrypt
2623
+ # @param mac [String] 16-byte authentication tag
2624
+ # @param key [String] 32-byte encryption key
2625
+ # @param nonce [String] 12-byte nonce
2626
+ # @param ad [String, nil] Optional additional authenticated data
2627
+ # @return [String] Decrypted plaintext
2628
+ # @raise [ArgumentError] if key, nonce, or mac have incorrect sizes
2629
+ # @raise [RuntimeError] if authentication fails
2630
+ #
2631
+ # @example
2632
+ # plaintext = Iodine::Base::Crypto::AES256GCM.decrypt(data, mac: mac, key: key, nonce: nonce)
2633
+ def self.decrypt(data, mac:, key:, nonce:, ad: nil); end
2634
+ end
2635
+
2636
+ # XChaCha20-Poly1305 AEAD encryption with 24-byte nonce.
2637
+ #
2638
+ # The extended 24-byte nonce makes it safe to use randomly generated nonces
2639
+ # without risk of collision, unlike the 12-byte nonce in ChaCha20-Poly1305.
2640
+ module XChaCha20Poly1305
2641
+ # Encrypts data using XChaCha20-Poly1305 AEAD.
2642
+ #
2643
+ # @param data [String] Plaintext to encrypt
2644
+ # @param key [String] 32-byte encryption key
2645
+ # @param nonce [String] 24-byte nonce (safe for random generation)
2646
+ # @param ad [String, nil] Optional additional authenticated data
2647
+ # @return [Array<String, String>] [ciphertext, mac] where mac is 16 bytes
2648
+ # @raise [ArgumentError] if key is not 32 bytes or nonce is not 24 bytes
2649
+ #
2650
+ # @example
2651
+ # key = Iodine::Utils.random(32)
2652
+ # nonce = Iodine::Utils.random(24) # Safe to use random nonce!
2653
+ # ciphertext, mac = Iodine::Base::Crypto::XChaCha20Poly1305.encrypt("secret", key: key, nonce: nonce)
2654
+ def self.encrypt(data, key:, nonce:, ad: nil); end
2655
+
2656
+ # Decrypts data using XChaCha20-Poly1305 AEAD.
2657
+ #
2658
+ # @param data [String] Ciphertext to decrypt
2659
+ # @param mac [String] 16-byte authentication tag
2660
+ # @param key [String] 32-byte encryption key
2661
+ # @param nonce [String] 24-byte nonce
2662
+ # @param ad [String, nil] Optional additional authenticated data
2663
+ # @return [String] Decrypted plaintext
2664
+ # @raise [ArgumentError] if key, nonce, or mac have incorrect sizes
2665
+ # @raise [RuntimeError] if authentication fails
2666
+ #
2667
+ # @example
2668
+ # plaintext = Iodine::Base::Crypto::XChaCha20Poly1305.decrypt(data, mac: mac, key: key, nonce: nonce)
2669
+ def self.decrypt(data, mac:, key:, nonce:, ad: nil); end
2670
+ end
2671
+
2672
+ # Ed25519 digital signatures (128-bit security level).
2673
+ #
2674
+ # Ed25519 provides fast, secure digital signatures suitable for
2675
+ # authentication, message signing, and other cryptographic protocols.
2676
+ module Ed25519
2677
+ # Generates a new Ed25519 key pair.
2678
+ #
2679
+ # @return [Array<String, String>] [secret_key, public_key] both 32 bytes
2680
+ #
2681
+ # @example
2682
+ # secret_key, public_key = Iodine::Base::Crypto::Ed25519.keypair
2683
+ def self.keypair; end
2684
+
2685
+ # Derives the public key from an Ed25519 secret key.
2686
+ #
2687
+ # @param secret_key [String] 32-byte secret key
2688
+ # @return [String] 32-byte public key
2689
+ # @raise [ArgumentError] if secret_key is not 32 bytes
2690
+ def self.public_key(secret_key:); end
2691
+
2692
+ # Signs a message using Ed25519.
2693
+ #
2694
+ # @param data [String] Message to sign
2695
+ # @param secret_key [String] 32-byte secret key
2696
+ # @param public_key [String] 32-byte public key
2697
+ # @return [String] 64-byte signature
2698
+ # @raise [ArgumentError] if keys have incorrect sizes
2699
+ #
2700
+ # @example
2701
+ # signature = Iodine::Base::Crypto::Ed25519.sign("data", secret_key: sk, public_key: pk)
2702
+ def self.sign(data, secret_key:, public_key:); end
2703
+
2704
+ # Verifies an Ed25519 signature.
2705
+ #
2706
+ # @param data [String] Original message
2707
+ # @param signature [String] 64-byte signature
2708
+ # @param public_key [String] 32-byte public key
2709
+ # @return [Boolean] `true` if valid, `false` otherwise
2710
+ # @raise [ArgumentError] if signature or public_key have incorrect sizes
2711
+ #
2712
+ # @example
2713
+ # valid = Iodine::Base::Crypto::Ed25519.verify("data", signature, public_key: pk)
2714
+ def self.verify(data, signature, public_key:); end
2715
+
2716
+ # Converts an Ed25519 secret key to an X25519 secret key.
2717
+ #
2718
+ # This allows using the same keypair for both signing (Ed25519) and
2719
+ # encryption (X25519), reducing key management complexity.
2720
+ #
2721
+ # @param ed_secret_key [String] 32-byte Ed25519 secret key
2722
+ # @return [String] 32-byte X25519 secret key
2723
+ # @raise [ArgumentError] if ed_secret_key is not 32 bytes
2724
+ #
2725
+ # @example
2726
+ # # Generate Ed25519 keypair for signing
2727
+ # ed_sk, ed_pk = Iodine::Base::Crypto::Ed25519.keypair
2728
+ #
2729
+ # # Convert to X25519 for encryption
2730
+ # x_sk = Iodine::Base::Crypto::Ed25519.to_x25519_secret(ed_secret_key: ed_sk)
2731
+ # x_pk = Iodine::Base::Crypto::Ed25519.to_x25519_public(ed_public_key: ed_pk)
2732
+ #
2733
+ # # Now use x_sk and x_pk for X25519 encryption
2734
+ def self.to_x25519_secret(ed_secret_key:); end
2735
+
2736
+ # Converts an Ed25519 public key to an X25519 public key.
2737
+ #
2738
+ # This allows encrypting to someone who has only shared their Ed25519
2739
+ # signing public key, without requiring a separate X25519 public key.
2740
+ #
2741
+ # @param ed_public_key [String] 32-byte Ed25519 public key
2742
+ # @return [String] 32-byte X25519 public key
2743
+ # @raise [ArgumentError] if ed_public_key is not 32 bytes
2744
+ #
2745
+ # @example
2746
+ # # Alice has Bob's Ed25519 public key (for signature verification)
2747
+ # # She can derive his X25519 public key to encrypt a message
2748
+ # bob_x_pk = Iodine::Base::Crypto::Ed25519.to_x25519_public(ed_public_key: bob_ed_pk)
2749
+ # ciphertext = Iodine::Base::Crypto::X25519.encrypt("secret", recipient_pk: bob_x_pk)
2750
+ def self.to_x25519_public(ed_public_key:); end
2751
+ end
2752
+
2753
+ # X25519 key exchange (ECDH) and public-key encryption (ECIES).
2754
+ #
2755
+ # X25519 provides secure key agreement and can be combined with
2756
+ # symmetric encryption for public-key encryption without prior key exchange.
2757
+ module X25519
2758
+ # Generates a new X25519 key pair.
2759
+ #
2760
+ # @return [Array<String, String>] [secret_key, public_key] both 32 bytes
2761
+ #
2762
+ # @example
2763
+ # secret_key, public_key = Iodine::Base::Crypto::X25519.keypair
2764
+ def self.keypair; end
2765
+
2766
+ # Derives the public key from an X25519 secret key.
2767
+ #
2768
+ # @param secret_key [String] 32-byte secret key
2769
+ # @return [String] 32-byte public key
2770
+ # @raise [ArgumentError] if secret_key is not 32 bytes
2771
+ def self.public_key(secret_key:); end
2772
+
2773
+ # Computes a shared secret using X25519 (ECDH).
2774
+ #
2775
+ # Both parties compute the same shared secret:
2776
+ # shared = X25519(my_secret, their_public)
2777
+ #
2778
+ # @param secret_key [String] 32-byte own secret key
2779
+ # @param their_public [String] 32-byte other party's public key
2780
+ # @return [String] 32-byte shared secret
2781
+ # @raise [ArgumentError] if keys have incorrect sizes
2782
+ # @raise [RuntimeError] if key exchange fails (e.g., low-order point)
2783
+ #
2784
+ # @example
2785
+ # # Alice and Bob each generate keypairs
2786
+ # alice_sk, alice_pk = Iodine::Base::Crypto::X25519.keypair
2787
+ # bob_sk, bob_pk = Iodine::Base::Crypto::X25519.keypair
2788
+ #
2789
+ # # They exchange public keys and compute the same shared secret
2790
+ # alice_shared = Iodine::Base::Crypto::X25519.shared_secret(secret_key: alice_sk, their_public: bob_pk)
2791
+ # bob_shared = Iodine::Base::Crypto::X25519.shared_secret(secret_key: bob_sk, their_public: alice_pk)
2792
+ # # alice_shared == bob_shared
2793
+ def self.shared_secret(secret_key:, their_public:); end
2794
+
2795
+ # Encrypts a message using X25519 public-key encryption (ECIES).
2796
+ #
2797
+ # Uses ephemeral key agreement + ChaCha20-Poly1305 for authenticated
2798
+ # encryption. Only the recipient with the matching secret key can decrypt.
2799
+ #
2800
+ # @param data [String] Plaintext to encrypt
2801
+ # @param recipient_pk [String] 32-byte recipient's public key
2802
+ # @return [String] Ciphertext (message.length + 48 bytes overhead)
2803
+ # @raise [ArgumentError] if recipient_pk is not 32 bytes
2804
+ # @raise [RuntimeError] if encryption fails
2805
+ #
2806
+ # @example
2807
+ # # Bob encrypts a message for Alice
2808
+ # ciphertext = Iodine::Base::Crypto::X25519.encrypt("secret message", recipient_pk: alice_pk)
2809
+ def self.encrypt(data, recipient_pk:); end
2810
+
2811
+ # Decrypts a message using X25519 public-key encryption (ECIES).
2812
+ #
2813
+ # @param data [String] Ciphertext from X25519.encrypt
2814
+ # @param secret_key [String] 32-byte recipient's secret key
2815
+ # @return [String] Decrypted plaintext
2816
+ # @raise [ArgumentError] if secret_key is not 32 bytes or data is too short
2817
+ # @raise [RuntimeError] if decryption fails (authentication error)
2818
+ #
2819
+ # @example
2820
+ # # Alice decrypts the message
2821
+ # plaintext = Iodine::Base::Crypto::X25519.decrypt(data, secret_key: alice_sk)
2822
+ def self.decrypt(data, secret_key:); end
2823
+
2824
+ # Encrypts a message using X25519 ECIES with AES-128-GCM.
2825
+ #
2826
+ # Uses ephemeral key agreement + AES-128-GCM for authenticated encryption.
2827
+ # Only the recipient with the matching secret key can decrypt.
2828
+ # The shared secret is derived using HKDF before use as the AES key.
2829
+ #
2830
+ # @param data [String] Plaintext to encrypt
2831
+ # @param recipient_pk [String] 32-byte recipient's public key
2832
+ # @return [String] Ciphertext (message.length + 48 bytes overhead)
2833
+ # @raise [ArgumentError] if recipient_pk is not 32 bytes
2834
+ # @raise [RuntimeError] if encryption fails
2835
+ #
2836
+ # @example
2837
+ # # Bob encrypts a message for Alice using AES-128-GCM
2838
+ # ciphertext = Iodine::Base::Crypto::X25519.encrypt_aes128("secret message", recipient_pk: alice_pk)
2839
+ def self.encrypt_aes128(data, recipient_pk:); end
2840
+
2841
+ # Decrypts a message using X25519 ECIES with AES-128-GCM.
2842
+ #
2843
+ # @param data [String] Ciphertext from X25519.encrypt_aes128
2844
+ # @param secret_key [String] 32-byte recipient's secret key
2845
+ # @return [String] Decrypted plaintext
2846
+ # @raise [ArgumentError] if secret_key is not 32 bytes or data is too short
2847
+ # @raise [RuntimeError] if decryption fails (authentication error)
2848
+ #
2849
+ # @example
2850
+ # # Alice decrypts the message
2851
+ # plaintext = Iodine::Base::Crypto::X25519.decrypt_aes128(data, secret_key: alice_sk)
2852
+ def self.decrypt_aes128(data, secret_key:); end
2853
+
2854
+ # Encrypts a message using X25519 ECIES with AES-256-GCM.
2855
+ #
2856
+ # Uses ephemeral key agreement + AES-256-GCM for authenticated encryption.
2857
+ # Only the recipient with the matching secret key can decrypt.
2858
+ # The shared secret is derived using HKDF before use as the AES key.
2859
+ #
2860
+ # @param data [String] Plaintext to encrypt
2861
+ # @param recipient_pk [String] 32-byte recipient's public key
2862
+ # @return [String] Ciphertext (message.length + 48 bytes overhead)
2863
+ # @raise [ArgumentError] if recipient_pk is not 32 bytes
2864
+ # @raise [RuntimeError] if encryption fails
2865
+ #
2866
+ # @example
2867
+ # # Bob encrypts a message for Alice using AES-256-GCM
2868
+ # ciphertext = Iodine::Base::Crypto::X25519.encrypt_aes256("secret message", recipient_pk: alice_pk)
2869
+ def self.encrypt_aes256(data, recipient_pk:); end
2870
+
2871
+ # Decrypts a message using X25519 ECIES with AES-256-GCM.
2872
+ #
2873
+ # @param data [String] Ciphertext from X25519.encrypt_aes256
2874
+ # @param secret_key [String] 32-byte recipient's secret key
2875
+ # @return [String] Decrypted plaintext
2876
+ # @raise [ArgumentError] if secret_key is not 32 bytes or data is too short
2877
+ # @raise [RuntimeError] if decryption fails (authentication error)
2878
+ #
2879
+ # @example
2880
+ # # Alice decrypts the message
2881
+ # plaintext = Iodine::Base::Crypto::X25519.decrypt_aes256(data, secret_key: alice_sk)
2882
+ def self.decrypt_aes256(data, secret_key:); end
2883
+ end
2884
+
2885
+ # HKDF key derivation (RFC 5869).
2886
+ #
2887
+ # HKDF is a simple and secure key derivation function based on HMAC.
2888
+ # It can be used to derive multiple keys from a single source of keying material.
2889
+ module HKDF
2890
+ # Derives keying material using HKDF (RFC 5869).
2891
+ #
2892
+ # @param ikm [String] Input keying material
2893
+ # @param salt [String, nil] Optional salt (random value)
2894
+ # @param info [String, nil] Optional context/application info
2895
+ # @param length [Integer] Desired output length (default: 32)
2896
+ # @param sha384 [Boolean] Use SHA-384 instead of SHA-256 (default: false)
2897
+ # @return [String] Derived key material
2898
+ # @raise [ArgumentError] if length is out of range (max 8160 bytes for SHA-256; max 12240 bytes for SHA-384 when `sha384: true`)
2899
+ #
2900
+ # @example
2901
+ # # Derive a 32-byte key from a password
2902
+ # key = Iodine::Base::Crypto::HKDF.derive(ikm: password, salt: salt, info: "encryption key", length: 32)
2903
+ #
2904
+ # @example
2905
+ # # Derive multiple keys from shared secret
2906
+ # shared = X25519.shared_secret(...)
2907
+ # enc_key = HKDF.derive(ikm: shared, info: "encryption", length: 32)
2908
+ # mac_key = HKDF.derive(ikm: shared, info: "authentication", length: 32)
2909
+ def self.derive(ikm:, salt: nil, info: nil, length: 32, sha384: false); end
2910
+ end
2911
+
2912
+ # X25519+ML-KEM-768 Post-Quantum Hybrid Key Encapsulation Mechanism.
2913
+ #
2914
+ # X25519MLKEM768 combines classical X25519 elliptic curve Diffie-Hellman with
2915
+ # ML-KEM-768 (formerly known as Kyber), a post-quantum lattice-based KEM.
2916
+ # This hybrid approach provides security against both classical and quantum
2917
+ # computer attacks.
2918
+ #
2919
+ # The hybrid construction ensures that even if one algorithm is broken,
2920
+ # the other still provides security. This is the recommended approach for
2921
+ # transitioning to post-quantum cryptography.
2922
+ #
2923
+ # Key sizes:
2924
+ # - Secret key: 2432 bytes (ML-KEM-768 sk + X25519 sk)
2925
+ # - Public key: 1216 bytes (ML-KEM-768 pk + X25519 pk)
2926
+ # - Data: 1120 bytes (ML-KEM-768 ct + X25519 ephemeral pk)
2927
+ # - Shared secret: 64 bytes (ML-KEM-768 ss || X25519 ss)
2928
+ #
2929
+ # @note This is a Key Encapsulation Mechanism (KEM), not direct encryption.
2930
+ # Use the shared secret with a symmetric cipher (e.g., AES-256-GCM or
2931
+ # ChaCha20-Poly1305) to encrypt actual data.
2932
+ #
2933
+ # @example Complete key exchange workflow
2934
+ # # Recipient generates a keypair
2935
+ # secret_key, public_key = Iodine::Base::Crypto::X25519MLKEM768.keypair
2936
+ #
2937
+ # # Sender encapsulates a shared secret using recipient's public key
2938
+ # data, sender_shared = Iodine::Base::Crypto::X25519MLKEM768.encapsulate(public_key: public_key)
2939
+ #
2940
+ # # Recipient decapsulates to obtain the same shared secret
2941
+ # recipient_shared = Iodine::Base::Crypto::X25519MLKEM768.decapsulate(
2942
+ # data: data,
2943
+ # secret_key: secret_key
2944
+ # )
2945
+ #
2946
+ # # sender_shared == recipient_shared (64 bytes)
2947
+ # # Now both parties can use the shared secret for symmetric encryption
2948
+ module X25519MLKEM768
2949
+ # Generates a new X25519+ML-KEM-768 hybrid key encapsulation keypair.
2950
+ #
2951
+ # @return [Array<String, String>] [secret_key, public_key]
2952
+ # - secret_key: 2432-byte binary string
2953
+ # - public_key: 1216-byte binary string
2954
+ # @raise [RuntimeError] if key generation fails
2955
+ #
2956
+ # @note Post-quantum secure. The keypair combines classical X25519 with
2957
+ # ML-KEM-768 (Kyber) for protection against both classical and quantum attacks.
2958
+ #
2959
+ # @example
2960
+ # secret_key, public_key = Iodine::Base::Crypto::X25519MLKEM768.keypair
2961
+ # # secret_key.bytesize => 2432
2962
+ # # public_key.bytesize => 1216
2963
+ def self.keypair; end
2964
+
2965
+ # Encapsulates a shared secret for the given public key.
2966
+ #
2967
+ # The sender uses this method to generate a shared secret and data.
2968
+ # The data is sent to the recipient, who can decapsulate it using
2969
+ # their secret key to obtain the same shared secret.
2970
+ #
2971
+ # @param public_key [String] 1216-byte recipient's public key
2972
+ # @return [Array<String, String>] [data, shared_secret]
2973
+ # - data: 1120-byte binary string to send to recipient
2974
+ # - shared_secret: 64-byte binary string for symmetric encryption
2975
+ # @raise [ArgumentError] if public_key is not 1216 bytes
2976
+ # @raise [RuntimeError] if encapsulation fails
2977
+ #
2978
+ # @note Send the data to the recipient who can decapsulate it to
2979
+ # obtain the same shared secret. The shared secret should be used with
2980
+ # a symmetric cipher for actual data encryption.
2981
+ #
2982
+ # @example
2983
+ # # Sender encapsulates a shared secret
2984
+ # data, shared_secret = Iodine::Base::Crypto::X25519MLKEM768.encapsulate(
2985
+ # public_key: recipient_public_key
2986
+ # )
2987
+ # # Send data to recipient
2988
+ # # Use shared_secret with AES-256-GCM or ChaCha20-Poly1305
2989
+ def self.encapsulate(public_key:); end
2990
+
2991
+ # Decapsulates a shared secret using your secret key.
2992
+ #
2993
+ # The recipient uses this method to recover the shared secret from the
2994
+ # data sent by the sender.
2995
+ #
2996
+ # @param data [String] 1120-byte encapsulated data from encapsulate
2997
+ # @param secret_key [String] 2432-byte secret key from keypair
2998
+ # @return [String] 64-byte shared secret (same as encapsulate returned)
2999
+ # @raise [ArgumentError] if data is not 1120 bytes or secret_key is not 2432 bytes
3000
+ # @raise [RuntimeError] if decapsulation fails (invalid key or data)
3001
+ #
3002
+ # @example
3003
+ # # Recipient decapsulates the shared secret
3004
+ # shared_secret = Iodine::Base::Crypto::X25519MLKEM768.decapsulate(
3005
+ # data: received_ciphertext,
3006
+ # secret_key: my_secret_key
3007
+ # )
3008
+ # # shared_secret matches what the sender obtained from encapsulate
3009
+ def self.decapsulate(data:, secret_key:); end
3010
+ end
3011
+
3012
+ # Argon2 memory-hard password hashing (RFC 9106).
3013
+ #
3014
+ # Argon2id is the recommended default variant. It provides resistance
3015
+ # against both side-channel and GPU cracking attacks.
3016
+ #
3017
+ # @note Use `Iodine::Utils.secure_compare` to compare hashes in constant time.
3018
+ #
3019
+ # @example Hash a password with default parameters (Argon2id, t=3, m=64 MiB)
3020
+ # hash = Iodine::Base::Crypto::Argon2.hash(password, salt: salt)
3021
+ #
3022
+ # @example Use OWASP-recommended minimum settings for Argon2id
3023
+ # hash = Iodine::Base::Crypto::Argon2.hash(
3024
+ # password,
3025
+ # salt: salt,
3026
+ # t_cost: 3,
3027
+ # m_cost: 64 * 1024, # KiB
3028
+ # parallelism: 1,
3029
+ # outlen: 32
3030
+ # )
3031
+ module Argon2
3032
+ # Hashes a password using Argon2.
3033
+ #
3034
+ # @param password [String] The password to hash.
3035
+ # @param salt [String] A unique salt (at least 8 bytes recommended).
3036
+ # @param t_cost [Integer] Time cost / iterations (default: 3).
3037
+ # @param m_cost [Integer] Memory cost in KiB (default: 65536 = 64 MiB).
3038
+ # @param parallelism [Integer] Parallelism lanes (default: 1).
3039
+ # @param outlen [Integer] Output length in bytes (default: 32, minimum: 4).
3040
+ # @param type [Symbol] Argon2 variant: :d, :i, or :id (default: :id).
3041
+ # @param secret [String, nil] Optional secret key.
3042
+ # @param ad [String, nil] Optional associated data.
3043
+ # @return [String] Binary hash of the requested length.
3044
+ # @raise [ArgumentError] if parameters are invalid.
3045
+ # @raise [RuntimeError] if hashing fails.
3046
+ #
3047
+ # @example
3048
+ # hash = Iodine::Base::Crypto::Argon2.hash("password", salt: SecureRandom.random_bytes(16))
3049
+ def self.hash(password, salt:, t_cost: 3, m_cost: 64 * 1024, parallelism: 1, outlen: 32, type: :id, secret: nil, ad: nil); end
3050
+ end
3051
+
3052
+ # Lyra2 memory-hard password hashing.
3053
+ #
3054
+ # Lyra2 was a finalist in the Password Hashing Competition. It uses
3055
+ # Blake2b as the underlying sponge function and offers a tunable
3056
+ # time-memory trade-off.
3057
+ #
3058
+ # @note Use `Iodine::Utils.secure_compare` to compare hashes in constant time.
3059
+ #
3060
+ # @example Hash a password with default parameters
3061
+ # hash = Iodine::Base::Crypto::Lyra2.hash(password, salt: salt)
3062
+ module Lyra2
3063
+ # Hashes a password using Lyra2.
3064
+ #
3065
+ # @param password [String] The password to hash.
3066
+ # @param salt [String] A unique salt.
3067
+ # @param t_cost [Integer] Time cost / rounds (default: 1).
3068
+ # @param m_cost [Integer] Memory cost in rows (default: 1000, minimum: 3).
3069
+ # @param outlen [Integer] Output length in bytes (default: 32).
3070
+ # @param n_cols [Integer] Number of columns (default: 256).
3071
+ # @return [String] Binary hash of the requested length.
3072
+ # @raise [ArgumentError] if parameters are invalid.
3073
+ # @raise [RuntimeError] if hashing fails.
3074
+ #
3075
+ # @example
3076
+ # hash = Iodine::Base::Crypto::Lyra2.hash("password", salt: SecureRandom.random_bytes(16))
3077
+ def self.hash(password, salt:, t_cost: 1, m_cost: 1000, outlen: 32, n_cols: 256); end
3078
+ end
3079
+ end
3080
+ end
3081
+
3082
+ # class Error < StandardError; end
3083
+ end
3084
+
3085
+ # end # defined?(Iodine)