protocol-zmtp 0.8.0 → 0.9.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edfecc19ca6c0befbc5835da0fbfc1fc364007207f10e6f1de64c733d48f2336
|
|
4
|
+
data.tar.gz: d6616b661ee77e73191ed6f33e0a3c5b73b04e6b3fe8e8e5cac30067ae75bb42
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4520a2435a1490fcfb7aebd070f58d10e97a05da685a3c36a47417db2e82b8d7ef48d0c5d19ee6c7db99c7b5da552cd4fec2f061aee3fd9e55908d7d30cf301d
|
|
7
|
+
data.tar.gz: d8c6f26fe3197a25f32e5c5a6668d53eb8cecf109dbe084b00c63b2fcb396cbe05dfd37c9b01ecbc39a960f35ce56ca70fa02c49761e9e0065246ffffeb36437
|
|
@@ -130,9 +130,9 @@ module Protocol
|
|
|
130
130
|
# @param ttl [Numeric] time-to-live in seconds (sent as deciseconds)
|
|
131
131
|
# @param context [String] optional context bytes (up to 16 bytes)
|
|
132
132
|
# @return [Command]
|
|
133
|
-
def self.ping(ttl: 0, context:
|
|
133
|
+
def self.ping(ttl: 0, context: EMPTY_BINARY)
|
|
134
134
|
ttl_ds = (ttl * 10).to_i
|
|
135
|
-
new("PING", [ttl_ds].pack("n") + context.b)
|
|
135
|
+
new("PING", [ttl_ds].pack("n") + (context.encoding == Encoding::BINARY ? context : context.b))
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
|
|
@@ -140,8 +140,8 @@ module Protocol
|
|
|
140
140
|
#
|
|
141
141
|
# @param context [String] context bytes echoed from the PING
|
|
142
142
|
# @return [Command]
|
|
143
|
-
def self.pong(context:
|
|
144
|
-
new("PONG", context.b)
|
|
143
|
+
def self.pong(context: EMPTY_BINARY)
|
|
144
|
+
new("PONG", context.encoding == Encoding::BINARY ? context : context.b)
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
|
|
@@ -150,7 +150,7 @@ module Protocol
|
|
|
150
150
|
# @return [Array(Numeric, String)] [ttl_seconds, context_bytes]
|
|
151
151
|
def ping_ttl_and_context
|
|
152
152
|
ttl_ds = @data.unpack1("n")
|
|
153
|
-
context = @data.bytesize > 2 ? @data.byteslice(2..) :
|
|
153
|
+
context = @data.bytesize > 2 ? @data.byteslice(2..) : EMPTY_BINARY
|
|
154
154
|
[ttl_ds / 10.0, context]
|
|
155
155
|
end
|
|
156
156
|
|
|
@@ -28,42 +28,6 @@ module Protocol
|
|
|
28
28
|
FLAG_BYTES = Array.new(256) { |i| i.chr.b.freeze }.freeze
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
# @return [String] frame body (binary)
|
|
32
|
-
attr_reader :body
|
|
33
|
-
|
|
34
|
-
# @param body [String] frame body
|
|
35
|
-
# @param more [Boolean] more frames follow
|
|
36
|
-
# @param command [Boolean] this is a command frame
|
|
37
|
-
def initialize(body, more: false, command: false)
|
|
38
|
-
@body = body.b
|
|
39
|
-
@more = more
|
|
40
|
-
@command = command
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
# @return [Boolean] true if more frames follow in this message
|
|
45
|
-
def more? = @more
|
|
46
|
-
|
|
47
|
-
# @return [Boolean] true if this is a command frame
|
|
48
|
-
def command? = @command
|
|
49
|
-
|
|
50
|
-
# Encodes to wire bytes.
|
|
51
|
-
#
|
|
52
|
-
# @return [String] binary wire representation (flags + size + body)
|
|
53
|
-
def to_wire
|
|
54
|
-
size = @body.bytesize
|
|
55
|
-
flags = 0
|
|
56
|
-
flags |= FLAGS_MORE if @more
|
|
57
|
-
flags |= FLAGS_COMMAND if @command
|
|
58
|
-
|
|
59
|
-
if size > SHORT_MAX
|
|
60
|
-
FLAG_BYTES[flags | FLAGS_LONG] + [size].pack("Q>") + @body
|
|
61
|
-
else
|
|
62
|
-
FLAG_BYTES[flags] + FLAG_BYTES[size] + @body
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
|
|
67
31
|
# Encodes a multi-part message into a single wire-format string.
|
|
68
32
|
# The result can be written to multiple connections without
|
|
69
33
|
# re-encoding each time (useful for fan-out patterns like PUB).
|
|
@@ -72,71 +36,152 @@ module Protocol
|
|
|
72
36
|
# @return [String] frozen binary wire representation
|
|
73
37
|
#
|
|
74
38
|
def self.encode_message(parts)
|
|
75
|
-
|
|
39
|
+
if parts.size == 1
|
|
40
|
+
s = parts.first.bytesize
|
|
41
|
+
wire = s > SHORT_MAX ? 9 + s : 2 + s
|
|
42
|
+
else
|
|
43
|
+
wire = 0
|
|
44
|
+
j = 0
|
|
45
|
+
|
|
46
|
+
while j < parts.size
|
|
47
|
+
s = parts[j].bytesize
|
|
48
|
+
wire += s > SHORT_MAX ? 9 + s : 2 + s
|
|
49
|
+
j += 1
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
buf = String.new(capacity: wire, encoding: Encoding::BINARY)
|
|
76
54
|
last = parts.size - 1
|
|
77
55
|
i = 0
|
|
56
|
+
|
|
78
57
|
while i < parts.size
|
|
79
58
|
body = parts[i]
|
|
80
59
|
body = body.b unless body.encoding == Encoding::BINARY
|
|
81
60
|
size = body.bytesize
|
|
82
61
|
flags = i < last ? FLAGS_MORE : 0
|
|
62
|
+
|
|
83
63
|
if size > SHORT_MAX
|
|
84
|
-
buf << FLAG_BYTES[flags | FLAGS_LONG]
|
|
64
|
+
buf << FLAG_BYTES[flags | FLAGS_LONG]
|
|
65
|
+
buf << [size].pack("Q>")
|
|
66
|
+
buf << body
|
|
85
67
|
else
|
|
86
|
-
buf << FLAG_BYTES[flags]
|
|
68
|
+
buf << FLAG_BYTES[flags]
|
|
69
|
+
buf << FLAG_BYTES[size]
|
|
70
|
+
buf << body
|
|
87
71
|
end
|
|
72
|
+
|
|
88
73
|
i += 1
|
|
89
74
|
end
|
|
75
|
+
|
|
90
76
|
buf.freeze
|
|
91
77
|
end
|
|
92
78
|
|
|
93
79
|
|
|
94
80
|
# Reads one frame from an IO-like object.
|
|
95
81
|
#
|
|
96
|
-
#
|
|
82
|
+
# Uses #peek to buffer just enough header bytes (2 for short frames,
|
|
83
|
+
# 9 for long), then drains header + body in a single #read_exactly.
|
|
84
|
+
# This is 2 calls for both short and long frames, vs the naive 3 for
|
|
85
|
+
# long. A speculative read_exactly(9) would be unsafe: a <7-byte
|
|
86
|
+
# short frame at idle would hang waiting for bytes that never arrive,
|
|
87
|
+
# or consume bytes from the next frame on a mixed stream.
|
|
88
|
+
#
|
|
89
|
+
# @param io [#peek, #read_exactly]
|
|
97
90
|
# @return [Frame]
|
|
98
91
|
# @raise [Error] on invalid frame
|
|
99
92
|
# @raise [EOFError] if the connection is closed
|
|
100
93
|
def self.read_from(io, max_message_size: nil)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
buf = io.peek do |b|
|
|
95
|
+
next false if b.bytesize < 2
|
|
96
|
+
(b.getbyte(0) & FLAGS_LONG) == 0 || b.bytesize >= 9
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
raise EOFError, "Stream finished before reading frame header" if buf.bytesize < 2
|
|
107
100
|
|
|
101
|
+
flags = buf.getbyte(0)
|
|
108
102
|
more = (flags & FLAGS_MORE) != 0
|
|
109
103
|
long = (flags & FLAGS_LONG) != 0
|
|
110
104
|
command = (flags & FLAGS_COMMAND) != 0
|
|
111
|
-
|
|
105
|
+
|
|
106
|
+
if long
|
|
107
|
+
raise EOFError, "Stream finished before reading long frame size" if buf.bytesize < 9
|
|
108
|
+
|
|
109
|
+
size = (buf.getbyte(1) << 56) |
|
|
110
|
+
(buf.getbyte(2) << 48) |
|
|
111
|
+
(buf.getbyte(3) << 40) |
|
|
112
|
+
(buf.getbyte(4) << 32) |
|
|
113
|
+
(buf.getbyte(5) << 24) |
|
|
114
|
+
(buf.getbyte(6) << 16) |
|
|
115
|
+
(buf.getbyte(7) << 8) |
|
|
116
|
+
buf.getbyte(8)
|
|
117
|
+
header_size = 9
|
|
118
|
+
else
|
|
119
|
+
size = buf.getbyte(1)
|
|
120
|
+
header_size = 2
|
|
121
|
+
end
|
|
112
122
|
|
|
113
123
|
if max_message_size && size > max_message_size
|
|
114
124
|
raise Error, "frame size #{size} exceeds max_message_size #{max_message_size}"
|
|
115
125
|
end
|
|
116
126
|
|
|
117
|
-
|
|
127
|
+
if size.zero?
|
|
128
|
+
io.read_exactly(header_size)
|
|
129
|
+
return new(EMPTY_BINARY, more: more, command: command)
|
|
130
|
+
end
|
|
118
131
|
|
|
119
|
-
|
|
132
|
+
wire = io.read_exactly(header_size + size)
|
|
133
|
+
new(wire.byteslice(header_size, size), more: more, command: command)
|
|
120
134
|
end
|
|
121
135
|
|
|
122
136
|
|
|
123
|
-
#
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
# @param
|
|
128
|
-
# @param
|
|
129
|
-
# @
|
|
137
|
+
# @return [String] frame body (binary)
|
|
138
|
+
attr_reader :body
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# @param body [String] frame body
|
|
142
|
+
# @param more [Boolean] more frames follow
|
|
143
|
+
# @param command [Boolean] this is a command frame
|
|
144
|
+
def initialize(body, more: false, command: false)
|
|
145
|
+
@body = body.encoding == Encoding::BINARY ? body : body.b
|
|
146
|
+
@more = more
|
|
147
|
+
@command = command
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
# @return [Boolean] true if more frames follow in this message
|
|
152
|
+
def more?
|
|
153
|
+
@more
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# @return [Boolean] true if this is a command frame
|
|
158
|
+
def command?
|
|
159
|
+
@command
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# Encodes to wire bytes.
|
|
130
164
|
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
165
|
+
# @return [String] binary wire representation (flags + size + body)
|
|
166
|
+
def to_wire
|
|
167
|
+
size = @body.bytesize
|
|
168
|
+
flags = 0
|
|
169
|
+
flags |= FLAGS_MORE if @more
|
|
170
|
+
flags |= FLAGS_COMMAND if @command
|
|
171
|
+
|
|
172
|
+
if size > SHORT_MAX
|
|
173
|
+
buf = String.new(capacity: 9 + size, encoding: Encoding::BINARY)
|
|
174
|
+
buf << FLAG_BYTES[flags | FLAGS_LONG]
|
|
175
|
+
buf << [size].pack("Q>")
|
|
176
|
+
buf << @body
|
|
177
|
+
else
|
|
178
|
+
buf = String.new(capacity: 2 + size, encoding: Encoding::BINARY)
|
|
179
|
+
buf << FLAG_BYTES[flags]
|
|
180
|
+
buf << FLAG_BYTES[size]
|
|
181
|
+
buf << @body
|
|
182
|
+
end
|
|
139
183
|
end
|
|
184
|
+
|
|
140
185
|
end
|
|
141
186
|
end
|
|
142
187
|
end
|
|
@@ -15,33 +15,42 @@ module Protocol
|
|
|
15
15
|
# @return [String] peer's socket type (from READY handshake)
|
|
16
16
|
attr_reader :peer_socket_type
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
# @return [String] peer's identity (from READY handshake)
|
|
19
20
|
attr_reader :peer_identity
|
|
20
21
|
|
|
22
|
+
|
|
21
23
|
# @return [Integer] peer's QoS level (from READY handshake, 0 if absent)
|
|
22
24
|
attr_reader :peer_qos
|
|
23
25
|
|
|
26
|
+
|
|
24
27
|
# @return [String] peer's supported hash algorithms in preference order
|
|
25
28
|
attr_reader :peer_qos_hash
|
|
26
29
|
|
|
30
|
+
|
|
27
31
|
# @return [Hash{String => String}, nil] full peer READY property hash
|
|
28
32
|
# (set after a successful handshake; nil before)
|
|
29
33
|
attr_reader :peer_properties
|
|
30
34
|
|
|
35
|
+
|
|
31
36
|
# @return [Integer, nil] peer ZMTP major version (from greeting)
|
|
32
37
|
attr_reader :peer_major
|
|
33
38
|
|
|
39
|
+
|
|
34
40
|
# @return [Integer, nil] peer ZMTP minor version (from greeting);
|
|
35
41
|
# 0 for ZMTP 3.0 peers, 1 for ZMTP 3.1+
|
|
36
42
|
attr_reader :peer_minor
|
|
37
43
|
|
|
38
|
-
|
|
44
|
+
|
|
45
|
+
# @return [Object] transport IO (#peek, #read_exactly, #write, #flush, #close)
|
|
39
46
|
attr_reader :io
|
|
40
47
|
|
|
48
|
+
|
|
41
49
|
# @return [Float, nil] monotonic timestamp of last received frame
|
|
42
50
|
attr_reader :last_received_at
|
|
43
51
|
|
|
44
|
-
|
|
52
|
+
|
|
53
|
+
# @param io [#peek, #read_exactly, #write, #flush, #close] transport IO
|
|
45
54
|
# @param socket_type [String] our socket type name (e.g. "REQ")
|
|
46
55
|
# @param identity [String] our identity
|
|
47
56
|
# @param as_server [Boolean] whether we are the server side
|
|
@@ -79,14 +88,12 @@ module Protocol
|
|
|
79
88
|
# @return [void]
|
|
80
89
|
# @raise [Error] on handshake failure
|
|
81
90
|
def handshake!
|
|
82
|
-
result = @mechanism.handshake!
|
|
83
|
-
@io,
|
|
91
|
+
result = @mechanism.handshake! @io,
|
|
84
92
|
as_server: @as_server,
|
|
85
93
|
socket_type: @socket_type,
|
|
86
94
|
identity: @identity,
|
|
87
95
|
qos: @qos,
|
|
88
|
-
qos_hash: @qos_hash
|
|
89
|
-
)
|
|
96
|
+
qos_hash: @qos_hash
|
|
90
97
|
|
|
91
98
|
@peer_socket_type = result[:peer_socket_type]
|
|
92
99
|
@peer_identity = result[:peer_identity]
|
|
@@ -198,15 +205,19 @@ module Protocol
|
|
|
198
205
|
# @raise [EOFError] if connection is closed
|
|
199
206
|
def receive_message
|
|
200
207
|
frames = []
|
|
208
|
+
|
|
201
209
|
loop do
|
|
202
210
|
frame = read_frame
|
|
211
|
+
|
|
203
212
|
if frame.command?
|
|
204
213
|
yield frame if block_given?
|
|
205
214
|
next
|
|
206
215
|
end
|
|
216
|
+
|
|
207
217
|
frames << frame.body.freeze
|
|
208
218
|
break unless frame.more?
|
|
209
219
|
end
|
|
220
|
+
|
|
210
221
|
frames.freeze
|
|
211
222
|
end
|
|
212
223
|
|
|
@@ -244,6 +255,7 @@ module Protocol
|
|
|
244
255
|
close
|
|
245
256
|
raise
|
|
246
257
|
end
|
|
258
|
+
|
|
247
259
|
touch_heartbeat
|
|
248
260
|
|
|
249
261
|
frame = @mechanism.decrypt(frame) if @mechanism.encrypted?
|
|
@@ -259,6 +271,7 @@ module Protocol
|
|
|
259
271
|
next
|
|
260
272
|
end
|
|
261
273
|
end
|
|
274
|
+
|
|
262
275
|
return frame
|
|
263
276
|
end
|
|
264
277
|
end
|
|
@@ -326,9 +339,11 @@ module Protocol
|
|
|
326
339
|
last = parts.size - 1
|
|
327
340
|
|
|
328
341
|
i = 0
|
|
342
|
+
|
|
329
343
|
while i < parts.size
|
|
330
344
|
part = parts[i]
|
|
331
345
|
more = i < last
|
|
346
|
+
|
|
332
347
|
if encrypted
|
|
333
348
|
body = part.encoding == Encoding::BINARY ? part : part.b
|
|
334
349
|
@io.write(@mechanism.encrypt(body, more: more))
|
|
@@ -336,18 +351,23 @@ module Protocol
|
|
|
336
351
|
body = part.encoding == Encoding::BINARY ? part : part.b
|
|
337
352
|
size = body.bytesize
|
|
338
353
|
flags = more ? Codec::Frame::FLAGS_MORE : 0
|
|
354
|
+
|
|
339
355
|
buf.clear
|
|
356
|
+
|
|
340
357
|
if size > Codec::Frame::SHORT_MAX
|
|
341
358
|
[flags | Codec::Frame::FLAGS_LONG, size].pack("CQ>", buffer: buf)
|
|
342
359
|
else
|
|
343
360
|
[flags, size].pack("CC", buffer: buf)
|
|
344
361
|
end
|
|
362
|
+
|
|
345
363
|
@io.write(buf)
|
|
346
364
|
@io.write(body)
|
|
347
365
|
end
|
|
366
|
+
|
|
348
367
|
i += 1
|
|
349
368
|
end
|
|
350
369
|
end
|
|
370
|
+
|
|
351
371
|
end
|
|
352
372
|
end
|
|
353
373
|
end
|