omq 0.2.0 → 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 +8 -0
- 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
- 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,13 @@
|
|
|
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
|
+
|
|
3
11
|
## 0.2.0 — 2026-03-26
|
|
4
12
|
|
|
5
13
|
### Changed
|
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,
|