omq 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/lib/omq/socket.rb +0 -5
- data/lib/omq/version.rb +1 -1
- data/lib/omq/zmtp/codec/command.rb +12 -27
- data/lib/omq/zmtp/codec/frame.rb +12 -27
- data/lib/omq/zmtp/codec/greeting.rb +11 -29
- data/lib/omq/zmtp/engine.rb +1 -21
- data/lib/omq/zmtp/options.rb +2 -10
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e18ec192a40dba5a208b0548d7c4f45091a95909ee9a56eb17782bccb85855c7
|
|
4
|
+
data.tar.gz: 343a94d01152a9f76b8876b2a85f29b4adebaed2dd0fcdee958d01877f9970da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 581b7119d33deedca75f3dd4d2701a0955ad4b4434d258ce368f1b61f5c25c3b4891e210007f331e0a051043cdf0936f1f1f7d1d9dabc5b25d4d4b79deb823f6
|
|
7
|
+
data.tar.gz: 999d27f8c87edb499219d3d2927d0d67bea4a6171231e40a251348839343eca17ff9ab335735ec1585e04baa96f0ee5ff3f481128c113bb3a8f03d900f5cbfd0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 — 2026-03-26
|
|
4
|
+
|
|
5
|
+
### Improved
|
|
6
|
+
|
|
7
|
+
- Replace `IO::Buffer` with `pack`/`unpack1`/`getbyte`/`byteslice` in
|
|
8
|
+
frame, command, and greeting codecs — up to 68% higher throughput for
|
|
9
|
+
large messages, 21% lower TCP latency
|
|
10
|
+
|
|
11
|
+
## 0.2.0 — 2026-03-26
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `mechanism` option now holds the mechanism instance directly
|
|
16
|
+
(`Mechanism::Null.new` by default). For CURVE, use
|
|
17
|
+
`OMQ::Curve.server(pub, sec)` or `OMQ::Curve.client(pub, sec, server_key: k)`.
|
|
18
|
+
- Removed `curve_server`, `curve_server_key`, `curve_public_key`,
|
|
19
|
+
`curve_secret_key`, `curve_authenticator` socket options
|
|
20
|
+
|
|
3
21
|
## 0.1.1 — 2026-03-26
|
|
4
22
|
|
|
5
23
|
### Fixed
|
data/lib/omq/socket.rb
CHANGED
|
@@ -31,11 +31,6 @@ module OMQ
|
|
|
31
31
|
heartbeat_timeout heartbeat_timeout=
|
|
32
32
|
max_message_size max_message_size=
|
|
33
33
|
mechanism mechanism=
|
|
34
|
-
curve_server curve_server=
|
|
35
|
-
curve_server_key curve_server_key=
|
|
36
|
-
curve_public_key curve_public_key=
|
|
37
|
-
curve_secret_key curve_secret_key=
|
|
38
|
-
curve_authenticator curve_authenticator=
|
|
39
34
|
].each do |method|
|
|
40
35
|
define_method(method) { |*args| @options.public_send(method, *args) }
|
|
41
36
|
end
|
data/lib/omq/version.rb
CHANGED
|
@@ -39,11 +39,7 @@ module OMQ
|
|
|
39
39
|
#
|
|
40
40
|
def to_body
|
|
41
41
|
name_bytes = @name.b
|
|
42
|
-
|
|
43
|
-
buf.set_value(:U8, 0, name_bytes.bytesize)
|
|
44
|
-
buf.set_string(name_bytes, 1)
|
|
45
|
-
buf.set_string(@data, 1 + name_bytes.bytesize)
|
|
46
|
-
buf.get_string(0, buf.size, Encoding::BINARY)
|
|
42
|
+
name_bytes.bytesize.chr.b + name_bytes + @data
|
|
47
43
|
end
|
|
48
44
|
|
|
49
45
|
# Encodes as a complete command Frame.
|
|
@@ -64,12 +60,11 @@ module OMQ
|
|
|
64
60
|
body = body.b
|
|
65
61
|
raise ProtocolError, "command body too short" if body.bytesize < 1
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
name_len = buf.get_value(:U8, 0)
|
|
63
|
+
name_len = body.getbyte(0)
|
|
69
64
|
|
|
70
65
|
raise ProtocolError, "command name truncated" if body.bytesize < 1 + name_len
|
|
71
66
|
|
|
72
|
-
name =
|
|
67
|
+
name = body.byteslice(1, name_len)
|
|
73
68
|
data = body.byteslice(1 + name_len..)
|
|
74
69
|
new(name, data)
|
|
75
70
|
end
|
|
@@ -113,12 +108,8 @@ module OMQ
|
|
|
113
108
|
# @return [Command]
|
|
114
109
|
#
|
|
115
110
|
def self.ping(ttl: 0, context: "".b)
|
|
116
|
-
# TTL is encoded as 2-byte big-endian value in tenths of a second
|
|
117
111
|
ttl_ds = (ttl * 10).to_i
|
|
118
|
-
|
|
119
|
-
buf.set_value(:U16, 0, ttl_ds)
|
|
120
|
-
buf.set_string(context.b, 2) if context.bytesize > 0
|
|
121
|
-
new("PING", buf.get_string(0, buf.size, Encoding::BINARY))
|
|
112
|
+
new("PING", [ttl_ds].pack("n") + context.b)
|
|
122
113
|
end
|
|
123
114
|
|
|
124
115
|
# Builds a PONG command.
|
|
@@ -135,8 +126,7 @@ module OMQ
|
|
|
135
126
|
# @return [Array(Numeric, String)] [ttl_seconds, context_bytes]
|
|
136
127
|
#
|
|
137
128
|
def ping_ttl_and_context
|
|
138
|
-
|
|
139
|
-
ttl_ds = buf.get_value(:U16, 0)
|
|
129
|
+
ttl_ds = @data.unpack1("n")
|
|
140
130
|
context = @data.bytesize > 2 ? @data.byteslice(2..) : "".b
|
|
141
131
|
[ttl_ds / 10.0, context]
|
|
142
132
|
end
|
|
@@ -157,17 +147,13 @@ module OMQ
|
|
|
157
147
|
#
|
|
158
148
|
def self.encode_properties(props)
|
|
159
149
|
parts = props.map do |name, value|
|
|
160
|
-
name_bytes
|
|
150
|
+
name_bytes = name.b
|
|
161
151
|
value_bytes = value.b
|
|
162
|
-
|
|
163
|
-
buf.set_value(:U8, 0, name_bytes.bytesize)
|
|
164
|
-
buf.set_string(name_bytes, 1)
|
|
165
|
-
buf.set_value(:U32, 1 + name_bytes.bytesize, value_bytes.bytesize) # big-endian
|
|
166
|
-
buf.set_string(value_bytes, 1 + name_bytes.bytesize + 4)
|
|
167
|
-
buf.get_string(0, buf.size, Encoding::BINARY)
|
|
152
|
+
name_bytes.bytesize.chr.b + name_bytes + [value_bytes.bytesize].pack("N") + value_bytes
|
|
168
153
|
end
|
|
169
154
|
parts.join
|
|
170
155
|
end
|
|
156
|
+
|
|
171
157
|
# Decodes a ZMTP property list from binary data.
|
|
172
158
|
#
|
|
173
159
|
# @param data [String] binary property list
|
|
@@ -176,24 +162,23 @@ module OMQ
|
|
|
176
162
|
#
|
|
177
163
|
def self.decode_properties(data)
|
|
178
164
|
result = {}
|
|
179
|
-
buf = IO::Buffer.for(data)
|
|
180
165
|
offset = 0
|
|
181
166
|
|
|
182
167
|
while offset < data.bytesize
|
|
183
168
|
raise ProtocolError, "property name truncated" if offset + 1 > data.bytesize
|
|
184
|
-
name_len =
|
|
169
|
+
name_len = data.getbyte(offset)
|
|
185
170
|
offset += 1
|
|
186
171
|
|
|
187
172
|
raise ProtocolError, "property name truncated" if offset + name_len > data.bytesize
|
|
188
|
-
name =
|
|
173
|
+
name = data.byteslice(offset, name_len)
|
|
189
174
|
offset += name_len
|
|
190
175
|
|
|
191
176
|
raise ProtocolError, "property value length truncated" if offset + 4 > data.bytesize
|
|
192
|
-
value_len =
|
|
177
|
+
value_len = data.byteslice(offset, 4).unpack1("N")
|
|
193
178
|
offset += 4
|
|
194
179
|
|
|
195
180
|
raise ProtocolError, "property value truncated" if offset + value_len > data.bytesize
|
|
196
|
-
value =
|
|
181
|
+
value = data.byteslice(offset, value_len)
|
|
197
182
|
offset += value_len
|
|
198
183
|
|
|
199
184
|
result[name] = value
|
data/lib/omq/zmtp/codec/frame.rb
CHANGED
|
@@ -46,52 +46,37 @@ module OMQ
|
|
|
46
46
|
# @return [String] binary wire representation (flags + size + body)
|
|
47
47
|
#
|
|
48
48
|
def to_wire
|
|
49
|
-
size
|
|
49
|
+
size = @body.bytesize
|
|
50
50
|
flags = 0
|
|
51
51
|
flags |= FLAGS_MORE if @more
|
|
52
52
|
flags |= FLAGS_COMMAND if @command
|
|
53
53
|
|
|
54
54
|
if size > SHORT_MAX
|
|
55
|
-
flags
|
|
56
|
-
buf = IO::Buffer.new(9 + size)
|
|
57
|
-
buf.set_value(:U8, 0, flags)
|
|
58
|
-
buf.set_value(:U64, 1, size) # big-endian
|
|
59
|
-
buf.set_string(@body, 9)
|
|
60
|
-
buf.get_string(0, 9 + size, Encoding::BINARY)
|
|
55
|
+
(flags | FLAGS_LONG).chr.b + [size].pack("Q>") + @body
|
|
61
56
|
else
|
|
62
|
-
|
|
63
|
-
buf.set_value(:U8, 0, flags)
|
|
64
|
-
buf.set_value(:U8, 1, size)
|
|
65
|
-
buf.set_string(@body, 2)
|
|
66
|
-
buf.get_string(0, 2 + size, Encoding::BINARY)
|
|
57
|
+
flags.chr.b + size.chr.b + @body
|
|
67
58
|
end
|
|
68
59
|
end
|
|
69
60
|
|
|
70
61
|
# Reads one frame from an IO-like object.
|
|
71
62
|
#
|
|
72
|
-
# @param io [#
|
|
63
|
+
# @param io [#read_exactly] must support read_exactly(n)
|
|
73
64
|
# @return [Frame]
|
|
74
65
|
# @raise [ProtocolError] on invalid frame
|
|
75
66
|
# @raise [EOFError] if the connection is closed
|
|
76
67
|
#
|
|
77
68
|
def self.read_from(io)
|
|
78
|
-
|
|
79
|
-
flags_buf = IO::Buffer.for(flags_byte)
|
|
80
|
-
flags = flags_buf.get_value(:U8, 0)
|
|
69
|
+
flags = io.read_exactly(1).getbyte(0)
|
|
81
70
|
|
|
82
|
-
more
|
|
83
|
-
long
|
|
71
|
+
more = (flags & FLAGS_MORE) != 0
|
|
72
|
+
long = (flags & FLAGS_LONG) != 0
|
|
84
73
|
command = (flags & FLAGS_COMMAND) != 0
|
|
85
74
|
|
|
86
|
-
if long
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
size_byte = io.read_exactly(1)
|
|
92
|
-
size_buf = IO::Buffer.for(size_byte)
|
|
93
|
-
size = size_buf.get_value(:U8, 0)
|
|
94
|
-
end
|
|
75
|
+
size = if long
|
|
76
|
+
io.read_exactly(8).unpack1("Q>")
|
|
77
|
+
else
|
|
78
|
+
io.read_exactly(1).getbyte(0)
|
|
79
|
+
end
|
|
95
80
|
|
|
96
81
|
body = size > 0 ? io.read_exactly(size) : "".b
|
|
97
82
|
|
|
@@ -33,26 +33,11 @@ module OMQ
|
|
|
33
33
|
# @return [String] 64-byte binary greeting
|
|
34
34
|
#
|
|
35
35
|
def self.encode(mechanism: "NULL", as_server: false)
|
|
36
|
-
buf =
|
|
37
|
-
buf.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
buf
|
|
41
|
-
# bytes 1-8 are already 0x00
|
|
42
|
-
buf.set_value(:U8, 9, SIGNATURE_END)
|
|
43
|
-
|
|
44
|
-
# Version
|
|
45
|
-
buf.set_value(:U8, 10, VERSION_MAJOR)
|
|
46
|
-
buf.set_value(:U8, 11, VERSION_MINOR)
|
|
47
|
-
|
|
48
|
-
# Mechanism (null-padded)
|
|
49
|
-
buf.set_string(mechanism.b, MECHANISM_OFFSET)
|
|
50
|
-
|
|
51
|
-
# As-server flag
|
|
52
|
-
buf.set_value(:U8, AS_SERVER_OFFSET, as_server ? 1 : 0)
|
|
53
|
-
|
|
54
|
-
# Filler bytes 33-63 are already 0x00
|
|
55
|
-
buf.get_string(0, SIZE, Encoding::BINARY)
|
|
36
|
+
buf = "\xFF".b + ("\x00" * 8) + "\x7F".b
|
|
37
|
+
buf << [VERSION_MAJOR, VERSION_MINOR].pack("CC")
|
|
38
|
+
buf << mechanism.b.ljust(MECHANISM_LENGTH, "\x00")
|
|
39
|
+
buf << (as_server ? "\x01" : "\x00")
|
|
40
|
+
buf << ("\x00" * 31)
|
|
56
41
|
end
|
|
57
42
|
|
|
58
43
|
# Decodes a ZMTP greeting.
|
|
@@ -64,24 +49,21 @@ module OMQ
|
|
|
64
49
|
def self.decode(data)
|
|
65
50
|
raise ProtocolError, "greeting too short (#{data.bytesize} bytes)" if data.bytesize < SIZE
|
|
66
51
|
|
|
67
|
-
|
|
52
|
+
data = data.b
|
|
68
53
|
|
|
69
|
-
|
|
70
|
-
unless buf.get_value(:U8, 0) == SIGNATURE_START &&
|
|
71
|
-
buf.get_value(:U8, 9) == SIGNATURE_END
|
|
54
|
+
unless data.getbyte(0) == SIGNATURE_START && data.getbyte(9) == SIGNATURE_END
|
|
72
55
|
raise ProtocolError, "invalid greeting signature"
|
|
73
56
|
end
|
|
74
57
|
|
|
75
|
-
major =
|
|
76
|
-
minor =
|
|
58
|
+
major = data.getbyte(10)
|
|
59
|
+
minor = data.getbyte(11)
|
|
77
60
|
|
|
78
61
|
unless major >= 3
|
|
79
62
|
raise ProtocolError, "unsupported ZMTP version #{major}.#{minor} (need >= 3.0)"
|
|
80
63
|
end
|
|
81
64
|
|
|
82
|
-
mechanism =
|
|
83
|
-
|
|
84
|
-
as_server = buf.get_value(:U8, AS_SERVER_OFFSET) == 1
|
|
65
|
+
mechanism = data.byteslice(MECHANISM_OFFSET, MECHANISM_LENGTH).delete("\x00")
|
|
66
|
+
as_server = data.getbyte(AS_SERVER_OFFSET) == 1
|
|
85
67
|
|
|
86
68
|
{
|
|
87
69
|
major: major,
|
data/lib/omq/zmtp/engine.rb
CHANGED
|
@@ -260,7 +260,7 @@ module OMQ
|
|
|
260
260
|
socket_type: @socket_type.to_s,
|
|
261
261
|
identity: @options.identity,
|
|
262
262
|
as_server: as_server,
|
|
263
|
-
mechanism:
|
|
263
|
+
mechanism: @options.mechanism,
|
|
264
264
|
heartbeat_interval: @options.heartbeat_interval,
|
|
265
265
|
heartbeat_ttl: @options.heartbeat_ttl,
|
|
266
266
|
heartbeat_timeout: @options.heartbeat_timeout,
|
|
@@ -305,26 +305,6 @@ module OMQ
|
|
|
305
305
|
end
|
|
306
306
|
|
|
307
307
|
|
|
308
|
-
def build_mechanism
|
|
309
|
-
case @options.mechanism
|
|
310
|
-
when :null
|
|
311
|
-
Mechanism::Null.new
|
|
312
|
-
when :curve
|
|
313
|
-
unless defined?(Mechanism::Curve)
|
|
314
|
-
raise LoadError, "require 'omq/curve' to use CURVE security"
|
|
315
|
-
end
|
|
316
|
-
Mechanism::Curve.new(
|
|
317
|
-
server_key: @options.curve_server_key,
|
|
318
|
-
public_key: @options.curve_public_key,
|
|
319
|
-
secret_key: @options.curve_secret_key,
|
|
320
|
-
as_server: @options.curve_server,
|
|
321
|
-
authenticator: @options.curve_authenticator,
|
|
322
|
-
)
|
|
323
|
-
else
|
|
324
|
-
raise ArgumentError, "unknown mechanism: #{@options.mechanism}"
|
|
325
|
-
end
|
|
326
|
-
end
|
|
327
|
-
|
|
328
308
|
def transport_for(endpoint)
|
|
329
309
|
case endpoint
|
|
330
310
|
when /\Atcp:\/\// then Transport::TCP
|
data/lib/omq/zmtp/options.rb
CHANGED
|
@@ -25,12 +25,7 @@ module OMQ
|
|
|
25
25
|
@heartbeat_ttl = nil # seconds, nil = use heartbeat_interval
|
|
26
26
|
@heartbeat_timeout = nil # seconds, nil = use heartbeat_interval
|
|
27
27
|
@max_message_size = nil # bytes, nil = unlimited
|
|
28
|
-
@mechanism =
|
|
29
|
-
@curve_server = false
|
|
30
|
-
@curve_server_key = nil # 32-byte binary (server's permanent public key)
|
|
31
|
-
@curve_public_key = nil # 32-byte binary (our permanent public key)
|
|
32
|
-
@curve_secret_key = nil # 32-byte binary (our permanent secret key)
|
|
33
|
-
@curve_authenticator = nil # nil = allow all, Set = allowlist, #call = custom
|
|
28
|
+
@mechanism = Mechanism::Null.new
|
|
34
29
|
end
|
|
35
30
|
|
|
36
31
|
attr_accessor :send_hwm, :recv_hwm,
|
|
@@ -40,10 +35,7 @@ module OMQ
|
|
|
40
35
|
:reconnect_interval,
|
|
41
36
|
:heartbeat_interval, :heartbeat_ttl, :heartbeat_timeout,
|
|
42
37
|
:max_message_size,
|
|
43
|
-
:mechanism
|
|
44
|
-
:curve_server, :curve_server_key,
|
|
45
|
-
:curve_public_key, :curve_secret_key,
|
|
46
|
-
:curve_authenticator
|
|
38
|
+
:mechanism
|
|
47
39
|
|
|
48
40
|
alias_method :router_mandatory?, :router_mandatory
|
|
49
41
|
alias_method :recv_timeout, :read_timeout
|