iodine 0.7.60 → 0.8.1

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