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