protocol-zmtp 0.4.0 → 0.5.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: f6f3ddb84f7c69bf776e615da15797cb815eaf663f357cf16cff60c744153bc8
4
- data.tar.gz: fd410e2dc417e623254be530230e88fc3e428f62413d6465ee88465c82444b10
3
+ metadata.gz: a29e580def88791398cee267152d16bd7e187a518bd409f2e8a2acb1e364cab1
4
+ data.tar.gz: a27e2d1335fe8e0376abf7d17e3b9e4ed2d82e3c98d9dd70733327d789144576
5
5
  SHA512:
6
- metadata.gz: 0c84020c35c81c9b9609541019cb7433956bfed7a2bf8fd4ea615ad7560b9aecec5086efbab733576309ff7cb2febb4f5c98bef2388f55a056bcf38c78a0633d
7
- data.tar.gz: 7251f30d057e56f97e6ffa3eb95af5727d7763a04829b3c93580e36998b1aa2591cdc0152ff4d53e7764ea80f75113585513d537147c3de238b498d50f25d15c
6
+ metadata.gz: 92318c07232337b3b108e5a98b4735c293e15810a8b41b7d1abe046f7f0ec7ed602072a91baa2ba6a6394ab05a4d260768e01a4302652ac96ca9ed61c9ad621d
7
+ data.tar.gz: 5e5fe073b34866af67c8b0e820afb95d37b2404cab2a986b26da24d70bb30fa809afef47a27c303b396f83a3f94c3f29da9e9a95f54f4413f83605b9d5fef8b1
@@ -23,11 +23,13 @@ module Protocol
23
23
  # @return [String] command data (binary)
24
24
  attr_reader :data
25
25
 
26
+ EMPTY_DATA = "".b.freeze
27
+
26
28
  # @param name [String] command name
27
29
  # @param data [String] command data
28
- def initialize(name, data = "".b)
30
+ def initialize(name, data = EMPTY_DATA)
29
31
  @name = name
30
- @data = data.b
32
+ @data = data.encoding == Encoding::BINARY ? data : data.b
31
33
  end
32
34
 
33
35
 
@@ -35,8 +37,9 @@ module Protocol
35
37
  #
36
38
  # @return [String] binary body (name-length + name + data)
37
39
  def to_body
38
- name_bytes = @name.b
39
- name_bytes.bytesize.chr.b + name_bytes + @data
40
+ name_bytes = @name.encoding == Encoding::BINARY ? @name : @name.b
41
+ buf = String.new(capacity: 1 + name_bytes.bytesize + @data.bytesize, encoding: Encoding::BINARY)
42
+ buf << Frame::FLAG_BYTES[name_bytes.bytesize] << name_bytes << @data
40
43
  end
41
44
 
42
45
 
@@ -19,6 +19,12 @@ module Protocol
19
19
  # Short frame: 1-byte size, max body 255 bytes.
20
20
  SHORT_MAX = 255
21
21
 
22
+ # Pre-computed single-byte flag strings (avoids Integer#chr + String#b per frame).
23
+ FLAG_BYTES = Array.new(256) { |i| i.chr.b.freeze }.freeze
24
+
25
+ # Frozen empty binary string for zero-length frame bodies.
26
+ EMPTY_BODY = "".b.freeze
27
+
22
28
 
23
29
  # @return [String] frame body (binary)
24
30
  attr_reader :body
@@ -49,9 +55,9 @@ module Protocol
49
55
  flags |= FLAGS_COMMAND if @command
50
56
 
51
57
  if size > SHORT_MAX
52
- (flags | FLAGS_LONG).chr.b + [size].pack("Q>") + @body
58
+ FLAG_BYTES[flags | FLAGS_LONG] + [size].pack("Q>") + @body
53
59
  else
54
- flags.chr.b + size.chr.b + @body
60
+ FLAG_BYTES[flags] + FLAG_BYTES[size] + @body
55
61
  end
56
62
  end
57
63
 
@@ -64,9 +70,20 @@ module Protocol
64
70
  # @return [String] frozen binary wire representation
65
71
  #
66
72
  def self.encode_message(parts)
67
- buf = +""
68
- parts.each_with_index do |part, i|
69
- buf << new(part, more: i < parts.size - 1).to_wire
73
+ buf = String.new(encoding: Encoding::BINARY)
74
+ last = parts.size - 1
75
+ i = 0
76
+ while i < parts.size
77
+ body = parts[i]
78
+ body = body.b unless body.encoding == Encoding::BINARY
79
+ size = body.bytesize
80
+ flags = i < last ? FLAGS_MORE : 0
81
+ if size > SHORT_MAX
82
+ buf << FLAG_BYTES[flags | FLAGS_LONG] << [size].pack("Q>") << body
83
+ else
84
+ buf << FLAG_BYTES[flags] << FLAG_BYTES[size] << body
85
+ end
86
+ i += 1
70
87
  end
71
88
  buf.freeze
72
89
  end
@@ -95,7 +112,7 @@ module Protocol
95
112
  raise Error, "frame size #{size} exceeds max_message_size #{max_message_size}"
96
113
  end
97
114
 
98
- body = size > 0 ? io.read_exactly(size) : "".b
115
+ body = size > 0 ? io.read_exactly(size) : EMPTY_BODY
99
116
 
100
117
  new(body, more: more, command: command)
101
118
  end
@@ -95,9 +95,11 @@ module Protocol
95
95
  # @param parts [Array<String>] message frames
96
96
  # @return [void]
97
97
  def send_message(parts)
98
- @mutex.synchronize do
99
- write_frames(parts)
100
- @io.flush
98
+ with_deferred_cancel do
99
+ @mutex.synchronize do
100
+ write_frames(parts)
101
+ @io.flush
102
+ end
101
103
  end
102
104
  end
103
105
 
@@ -108,8 +110,10 @@ module Protocol
108
110
  # @param parts [Array<String>] message frames
109
111
  # @return [void]
110
112
  def write_message(parts)
111
- @mutex.synchronize do
112
- write_frames(parts)
113
+ with_deferred_cancel do
114
+ @mutex.synchronize do
115
+ write_frames(parts)
116
+ end
113
117
  end
114
118
  end
115
119
 
@@ -124,12 +128,14 @@ module Protocol
124
128
  # multi-frame message
125
129
  # @return [void]
126
130
  def write_messages(messages)
127
- @mutex.synchronize do
128
- i = 0
129
- n = messages.size
130
- while i < n
131
- write_frames(messages[i])
132
- i += 1
131
+ with_deferred_cancel do
132
+ @mutex.synchronize do
133
+ i = 0
134
+ n = messages.size
135
+ while i < n
136
+ write_frames(messages[i])
137
+ i += 1
138
+ end
133
139
  end
134
140
  end
135
141
  end
@@ -141,8 +147,10 @@ module Protocol
141
147
  # @param wire_bytes [String] ZMTP wire-format bytes
142
148
  # @return [void]
143
149
  def write_wire(wire_bytes)
144
- @mutex.synchronize do
145
- @io.write(wire_bytes)
150
+ with_deferred_cancel do
151
+ @mutex.synchronize do
152
+ @io.write(wire_bytes)
153
+ end
146
154
  end
147
155
  end
148
156
 
@@ -191,13 +199,15 @@ module Protocol
191
199
  # @param command [Codec::Command]
192
200
  # @return [void]
193
201
  def send_command(command)
194
- @mutex.synchronize do
195
- if @mechanism.encrypted?
196
- @io.write(@mechanism.encrypt(command.to_body, command: true))
197
- else
198
- @io.write(command.to_frame.to_wire)
202
+ with_deferred_cancel do
203
+ @mutex.synchronize do
204
+ if @mechanism.encrypted?
205
+ @io.write(@mechanism.encrypt(command.to_body, command: true))
206
+ else
207
+ @io.write(command.to_frame.to_wire)
208
+ end
209
+ @io.flush
199
210
  end
200
- @io.flush
201
211
  end
202
212
  end
203
213
 
@@ -266,6 +276,26 @@ module Protocol
266
276
 
267
277
  private
268
278
 
279
+ # Defers task cancellation around a block of wire writes so the
280
+ # peer never sees a half-written frame. Without this, an
281
+ # +Async::Cancel+ arriving between the header write and the body
282
+ # write (the unencrypted path issues two separate +@io.write+
283
+ # calls per frame) would desync the peer's framer
284
+ # unrecoverably.
285
+ #
286
+ # When called outside an Async task (test fixtures, blocking
287
+ # callers), the block runs directly -- there is no task to defer
288
+ # on. Cancellation arriving from inside the block (peer
289
+ # disconnect raising +EPIPE+/+EOFError+) propagates normally.
290
+ def with_deferred_cancel
291
+ if defined?(Async::Task) && (task = Async::Task.current?)
292
+ task.defer_cancel { yield }
293
+ else
294
+ yield
295
+ end
296
+ end
297
+
298
+
269
299
  # Writes message parts as ZMTP frames, encrypting if needed.
270
300
  #
271
301
  # For the unencrypted path, writes the frame header and body
@@ -276,13 +306,17 @@ module Protocol
276
306
  def write_frames(parts)
277
307
  encrypted = @mechanism.encrypted?
278
308
  buf = @header_buf
309
+ last = parts.size - 1
279
310
 
280
- parts.each_with_index do |part, i|
281
- more = i < parts.size - 1
311
+ i = 0
312
+ while i < parts.size
313
+ part = parts[i]
314
+ more = i < last
282
315
  if encrypted
283
- @io.write(@mechanism.encrypt(part.b, more: more))
316
+ body = part.encoding == Encoding::BINARY ? part : part.b
317
+ @io.write(@mechanism.encrypt(body, more: more))
284
318
  else
285
- body = part.b
319
+ body = part.encoding == Encoding::BINARY ? part : part.b
286
320
  size = body.bytesize
287
321
  flags = more ? Codec::Frame::FLAGS_MORE : 0
288
322
  buf.clear
@@ -294,6 +328,7 @@ module Protocol
294
328
  @io.write(buf)
295
329
  @io.write(body)
296
330
  end
331
+ i += 1
297
332
  end
298
333
  end
299
334
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Protocol
4
4
  module ZMTP
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.1"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-zmtp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger