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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb02b0e08db21f4d0db4bb376a35759ca709ef80d175a306179c3710e8048e9d
4
- data.tar.gz: 41bfa63e9868e6a5d10c97e9751ebe227f8937be734d9f9bc5c618619cb1906c
3
+ metadata.gz: e18ec192a40dba5a208b0548d7c4f45091a95909ee9a56eb17782bccb85855c7
4
+ data.tar.gz: 343a94d01152a9f76b8876b2a85f29b4adebaed2dd0fcdee958d01877f9970da
5
5
  SHA512:
6
- metadata.gz: cf95700a644923f1598944501e10775de5ba0cfd277c375f3701c93e09a5e1928e495acea2e09eb5224f87143e55526f851852c2ba67d92409a5e0da62ec5266
7
- data.tar.gz: 9b299ca752fee2e1cba919d65bd7a377494e8067d111f27ac53c8dc2a35b3c9c3d0547386e38417656f58b746ea3dda6ea5b8292e1d9f9230736150561aad1b8
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OMQ
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -39,11 +39,7 @@ module OMQ
39
39
  #
40
40
  def to_body
41
41
  name_bytes = @name.b
42
- buf = IO::Buffer.new(1 + name_bytes.bytesize + @data.bytesize)
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
- buf = IO::Buffer.for(body)
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 = buf.get_string(1, name_len, Encoding::BINARY)
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
- buf = IO::Buffer.new(2 + context.bytesize)
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
- buf = IO::Buffer.for(@data)
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 = name.b
150
+ name_bytes = name.b
161
151
  value_bytes = value.b
162
- buf = IO::Buffer.new(1 + name_bytes.bytesize + 4 + value_bytes.bytesize)
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 = buf.get_value(:U8, offset)
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 = buf.get_string(offset, name_len, Encoding::BINARY)
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 = buf.get_value(:U32, offset)
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 = buf.get_string(offset, value_len, Encoding::BINARY)
181
+ value = data.byteslice(offset, value_len)
197
182
  offset += value_len
198
183
 
199
184
  result[name] = value
@@ -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 = @body.bytesize
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 |= FLAGS_LONG
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
- buf = IO::Buffer.new(2 + size)
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 [#read] must support read(n) returning exactly n bytes
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
- flags_byte = io.read_exactly(1)
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 = (flags & FLAGS_MORE) != 0
83
- long = (flags & FLAGS_LONG) != 0
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
- size_bytes = io.read_exactly(8)
88
- size_buf = IO::Buffer.for(size_bytes)
89
- size = size_buf.get_value(:U64, 0) # big-endian
90
- else
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 = IO::Buffer.new(SIZE)
37
- buf.clear
38
-
39
- # Signature
40
- buf.set_value(:U8, 0, SIGNATURE_START)
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
- buf = IO::Buffer.for(data.b)
52
+ data = data.b
68
53
 
69
- # Validate signature
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 = buf.get_value(:U8, 10)
76
- minor = buf.get_value(:U8, 11)
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 = buf.get_string(MECHANISM_OFFSET, MECHANISM_LENGTH, Encoding::BINARY)
83
- .delete("\x00")
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,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger