protocol-zmtp 0.9.0 → 0.10.2
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/lib/protocol/zmtp/codec/command.rb +5 -11
- data/lib/protocol/zmtp/codec/frame.rb +1 -2
- data/lib/protocol/zmtp/connection.rb +68 -37
- data/lib/protocol/zmtp/mechanism/curve.rb +17 -35
- data/lib/protocol/zmtp/mechanism/null.rb +8 -22
- data/lib/protocol/zmtp/mechanism/plain.rb +14 -31
- data/lib/protocol/zmtp/peer_info.rb +14 -3
- data/lib/protocol/zmtp/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a99947180a584a86331021fac44327f3bde68cc44c50fe6264fdb7e6018cf3af
|
|
4
|
+
data.tar.gz: 00c1b152a3763603452a42b0af92455ab02cb864d55b4cd331d0dba26396f65b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6e6997a555bc1ed27bd68f51935cfc0b68bf4b7b3f5055a47b2de44f63561116b309be5d933df1ffc9e8a62d72deac7a3ed918b9b323fcb41584934a8b76a684
|
|
7
|
+
data.tar.gz: 5a495e661e2e4c829a6e33d5cb3d98a0976c4606efb676f17201a7184a82753405816d7f66057154b95a2af78c3385d960e1d0a319674945454eb2218f8c81fd
|
|
@@ -70,20 +70,14 @@ module Protocol
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
# Builds a READY command with Socket-Type, Identity,
|
|
74
|
-
#
|
|
75
|
-
#
|
|
73
|
+
# Builds a READY command with Socket-Type, Identity, and any
|
|
74
|
+
# extra properties supplied by upper layers (e.g. an extension
|
|
75
|
+
# that injects +X-QoS+ or +X-Compression+).
|
|
76
76
|
#
|
|
77
|
-
# @param
|
|
78
|
-
# @param qos_hash [String] supported hash algorithms in preference order (e.g. "xXsS")
|
|
79
|
-
# @param metadata [Hash{String => String}] additional READY properties
|
|
77
|
+
# @param metadata [Hash{String => String}, nil] additional READY properties
|
|
80
78
|
# @return [Command]
|
|
81
|
-
def self.ready(socket_type:, identity: "",
|
|
79
|
+
def self.ready(socket_type:, identity: "", metadata: nil)
|
|
82
80
|
props = { "Socket-Type" => socket_type, "Identity" => identity }
|
|
83
|
-
if qos > 0
|
|
84
|
-
props["X-QoS"] = qos.to_s
|
|
85
|
-
props["X-QoS-Hash"] = qos_hash unless qos_hash.empty?
|
|
86
|
-
end
|
|
87
81
|
props.merge!(metadata) if metadata && !metadata.empty?
|
|
88
82
|
new("READY", encode_properties(props))
|
|
89
83
|
end
|
|
@@ -56,7 +56,6 @@ module Protocol
|
|
|
56
56
|
|
|
57
57
|
while i < parts.size
|
|
58
58
|
body = parts[i]
|
|
59
|
-
body = body.b unless body.encoding == Encoding::BINARY
|
|
60
59
|
size = body.bytesize
|
|
61
60
|
flags = i < last ? FLAGS_MORE : 0
|
|
62
61
|
|
|
@@ -142,7 +141,7 @@ module Protocol
|
|
|
142
141
|
# @param more [Boolean] more frames follow
|
|
143
142
|
# @param command [Boolean] this is a command frame
|
|
144
143
|
def initialize(body, more: false, command: false)
|
|
145
|
-
@body = body
|
|
144
|
+
@body = body
|
|
146
145
|
@more = more
|
|
147
146
|
@command = command
|
|
148
147
|
end
|
|
@@ -20,16 +20,14 @@ module Protocol
|
|
|
20
20
|
attr_reader :peer_identity
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
# @return [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# @return [String] peer's supported hash algorithms in preference order
|
|
28
|
-
attr_reader :peer_qos_hash
|
|
23
|
+
# @return [Object, nil] peer's CURVE long-term public key post-handshake
|
|
24
|
+
# (+crypto::PublicKey+ when the mechanism is CURVE; +nil+ for NULL).
|
|
25
|
+
attr_reader :peer_public_key
|
|
29
26
|
|
|
30
27
|
|
|
31
28
|
# @return [Hash{String => String}, nil] full peer READY property hash
|
|
32
|
-
# (set after a successful handshake; nil before)
|
|
29
|
+
# (set after a successful handshake; nil before).
|
|
30
|
+
# Upper layers extract their own X-* properties from here.
|
|
33
31
|
attr_reader :peer_properties
|
|
34
32
|
|
|
35
33
|
|
|
@@ -56,8 +54,12 @@ module Protocol
|
|
|
56
54
|
# @param as_server [Boolean] whether we are the server side
|
|
57
55
|
# @param mechanism [Mechanism::Null, Mechanism::Curve] security mechanism
|
|
58
56
|
# @param max_message_size [Integer, nil] max frame size in bytes, nil = unlimited
|
|
57
|
+
# @param opts [Hash{String => String}] extra READY properties to
|
|
58
|
+
# advertise (e.g. +"X-QoS" => "1"+). Upper-layer extensions use
|
|
59
|
+
# this to inject their own negotiated properties without the
|
|
60
|
+
# codec needing to know about them.
|
|
59
61
|
def initialize(io, socket_type:, identity: "", as_server: false,
|
|
60
|
-
mechanism: nil, max_message_size: nil,
|
|
62
|
+
mechanism: nil, max_message_size: nil, **opts)
|
|
61
63
|
@io = io
|
|
62
64
|
@socket_type = socket_type
|
|
63
65
|
@identity = identity
|
|
@@ -65,13 +67,11 @@ module Protocol
|
|
|
65
67
|
@mechanism = mechanism || Mechanism::Null.new
|
|
66
68
|
@peer_socket_type = nil
|
|
67
69
|
@peer_identity = nil
|
|
68
|
-
@
|
|
69
|
-
@peer_qos_hash = nil
|
|
70
|
+
@peer_public_key = nil
|
|
70
71
|
@peer_properties = nil
|
|
71
72
|
@peer_major = nil
|
|
72
73
|
@peer_minor = nil
|
|
73
|
-
@
|
|
74
|
-
@qos_hash = qos_hash
|
|
74
|
+
@metadata = opts.empty? ? nil : opts.transform_keys(&:to_s)
|
|
75
75
|
@mutex = Mutex.new
|
|
76
76
|
@max_message_size = max_message_size
|
|
77
77
|
@last_received_at = nil
|
|
@@ -80,6 +80,7 @@ module Protocol
|
|
|
80
80
|
# writes in place so the per-message 2-or-9 byte String allocation
|
|
81
81
|
# in write_frames disappears on the hot send path.
|
|
82
82
|
@header_buf = String.new(capacity: 9, encoding: Encoding::BINARY)
|
|
83
|
+
@frame_buf = String.new(capacity: 257, encoding: Encoding::BINARY)
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
|
|
@@ -92,13 +93,11 @@ module Protocol
|
|
|
92
93
|
as_server: @as_server,
|
|
93
94
|
socket_type: @socket_type,
|
|
94
95
|
identity: @identity,
|
|
95
|
-
|
|
96
|
-
qos_hash: @qos_hash
|
|
96
|
+
metadata: @metadata
|
|
97
97
|
|
|
98
98
|
@peer_socket_type = result[:peer_socket_type]
|
|
99
99
|
@peer_identity = result[:peer_identity]
|
|
100
|
-
@
|
|
101
|
-
@peer_qos_hash = result[:peer_qos_hash] || ""
|
|
100
|
+
@peer_public_key = result[:peer_public_key]
|
|
102
101
|
@peer_properties = result[:peer_properties]
|
|
103
102
|
@peer_major = result[:peer_major]
|
|
104
103
|
@peer_minor = result[:peer_minor]
|
|
@@ -114,6 +113,17 @@ module Protocol
|
|
|
114
113
|
end
|
|
115
114
|
|
|
116
115
|
|
|
116
|
+
# Returns a {PeerInfo} value bundling the peer's CURVE public key
|
|
117
|
+
# and identity for use as a stable per-peer key (frozen, hash-usable).
|
|
118
|
+
# Nil before the handshake has completed.
|
|
119
|
+
#
|
|
120
|
+
# @return [PeerInfo, nil]
|
|
121
|
+
def peer_info
|
|
122
|
+
return nil unless @peer_socket_type
|
|
123
|
+
PeerInfo.new(public_key: @peer_public_key, identity: @peer_identity)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
|
|
117
127
|
# Sends a multi-frame message (write + flush).
|
|
118
128
|
#
|
|
119
129
|
# @param parts [Array<String>] message frames
|
|
@@ -179,6 +189,25 @@ module Protocol
|
|
|
179
189
|
end
|
|
180
190
|
|
|
181
191
|
|
|
192
|
+
# Writes multiple pre-encoded wire byte strings under a single
|
|
193
|
+
# mutex acquisition.
|
|
194
|
+
#
|
|
195
|
+
# @param wire_strings [Array<String>]
|
|
196
|
+
# @return [void]
|
|
197
|
+
def write_wire_batch(wire_strings)
|
|
198
|
+
with_deferred_cancel do
|
|
199
|
+
@mutex.synchronize do
|
|
200
|
+
i = 0
|
|
201
|
+
n = wire_strings.size
|
|
202
|
+
while i < n
|
|
203
|
+
@io.write(wire_strings[i])
|
|
204
|
+
i += 1
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
|
|
182
211
|
# Returns true if the ZMTP mechanism encrypts at the frame level
|
|
183
212
|
# (e.g. CURVE, BLAKE3ZMQ).
|
|
184
213
|
#
|
|
@@ -214,11 +243,11 @@ module Protocol
|
|
|
214
243
|
next
|
|
215
244
|
end
|
|
216
245
|
|
|
217
|
-
frames << frame.body
|
|
246
|
+
frames << frame.body
|
|
218
247
|
break unless frame.more?
|
|
219
248
|
end
|
|
220
249
|
|
|
221
|
-
frames
|
|
250
|
+
frames
|
|
222
251
|
end
|
|
223
252
|
|
|
224
253
|
|
|
@@ -307,11 +336,10 @@ module Protocol
|
|
|
307
336
|
private
|
|
308
337
|
|
|
309
338
|
# Defers task cancellation around a block of wire writes so the
|
|
310
|
-
# peer never sees a half-written frame
|
|
311
|
-
# +Async::Cancel+ arriving between
|
|
312
|
-
#
|
|
313
|
-
#
|
|
314
|
-
# unrecoverably.
|
|
339
|
+
# peer never sees a half-written frame or partial multipart
|
|
340
|
+
# message. Without this, an +Async::Cancel+ arriving between
|
|
341
|
+
# successive frames (or between header and body writes for long
|
|
342
|
+
# frames) would desync the peer's framer unrecoverably.
|
|
315
343
|
#
|
|
316
344
|
# When called outside an Async task (test fixtures, blocking
|
|
317
345
|
# callers), the block runs directly -- there is no task to defer
|
|
@@ -328,15 +356,16 @@ module Protocol
|
|
|
328
356
|
|
|
329
357
|
# Writes message parts as ZMTP frames, encrypting if needed.
|
|
330
358
|
#
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
# the dominant allocation per send.
|
|
359
|
+
# Short frames (body <= 255 B) combine the 2-byte header and
|
|
360
|
+
# body into a reusable buffer for a single +@io.write+, halving
|
|
361
|
+
# the per-frame mutex overhead in io-stream. Long frames write
|
|
362
|
+
# header and body separately to avoid copying the body.
|
|
336
363
|
def write_frames(parts)
|
|
337
|
-
encrypted
|
|
338
|
-
buf
|
|
339
|
-
|
|
364
|
+
encrypted = @mechanism.encrypted?
|
|
365
|
+
buf = @header_buf
|
|
366
|
+
fbuf = @frame_buf
|
|
367
|
+
flag_bytes = Codec::Frame::FLAG_BYTES
|
|
368
|
+
last = parts.size - 1
|
|
340
369
|
|
|
341
370
|
i = 0
|
|
342
371
|
|
|
@@ -352,16 +381,18 @@ module Protocol
|
|
|
352
381
|
size = body.bytesize
|
|
353
382
|
flags = more ? Codec::Frame::FLAGS_MORE : 0
|
|
354
383
|
|
|
355
|
-
buf.clear
|
|
356
|
-
|
|
357
384
|
if size > Codec::Frame::SHORT_MAX
|
|
385
|
+
buf.clear
|
|
358
386
|
[flags | Codec::Frame::FLAGS_LONG, size].pack("CQ>", buffer: buf)
|
|
387
|
+
@io.write(buf)
|
|
388
|
+
@io.write(body)
|
|
359
389
|
else
|
|
360
|
-
|
|
390
|
+
fbuf.clear
|
|
391
|
+
fbuf << flag_bytes[flags]
|
|
392
|
+
fbuf << flag_bytes[size]
|
|
393
|
+
fbuf << body
|
|
394
|
+
@io.write(fbuf)
|
|
361
395
|
end
|
|
362
|
-
|
|
363
|
-
@io.write(buf)
|
|
364
|
-
@io.write(body)
|
|
365
396
|
end
|
|
366
397
|
|
|
367
398
|
i += 1
|
|
@@ -23,10 +23,6 @@ module Protocol
|
|
|
23
23
|
class Curve
|
|
24
24
|
MECHANISM_NAME = "CURVE"
|
|
25
25
|
|
|
26
|
-
# Extra READY/INITIATE properties merged in by extensions
|
|
27
|
-
# (e.g. ZMTP-Zstd's X-Compression). nil = none.
|
|
28
|
-
attr_accessor :metadata
|
|
29
|
-
|
|
30
26
|
|
|
31
27
|
# Nonce prefixes.
|
|
32
28
|
NONCE_PREFIX_HELLO = "CurveZMQHELLO---"
|
|
@@ -100,10 +96,9 @@ module Protocol
|
|
|
100
96
|
end
|
|
101
97
|
end
|
|
102
98
|
|
|
103
|
-
@session_box
|
|
104
|
-
@send_nonce
|
|
105
|
-
@recv_nonce
|
|
106
|
-
@metadata = nil
|
|
99
|
+
@session_box = nil
|
|
100
|
+
@send_nonce = 0
|
|
101
|
+
@recv_nonce = -1
|
|
107
102
|
end
|
|
108
103
|
|
|
109
104
|
|
|
@@ -139,15 +134,14 @@ module Protocol
|
|
|
139
134
|
# @param as_server [Boolean] ignored -- uses the value from #initialize
|
|
140
135
|
# @param socket_type [String] our socket type name
|
|
141
136
|
# @param identity [String] our identity
|
|
142
|
-
# @param
|
|
143
|
-
# @
|
|
144
|
-
# @return [Hash] { peer_socket_type:, peer_identity:, peer_qos:, peer_qos_hash: }
|
|
137
|
+
# @param metadata [Hash{String => String}, nil] extra READY properties
|
|
138
|
+
# @return [Hash] { peer_socket_type:, peer_identity:, peer_properties: }
|
|
145
139
|
# @raise [Error] on handshake failure
|
|
146
|
-
def handshake!(io, as_server:, socket_type:, identity:,
|
|
140
|
+
def handshake!(io, as_server:, socket_type:, identity:, metadata: nil)
|
|
147
141
|
if @as_server
|
|
148
|
-
server_handshake!(io, socket_type:, identity:,
|
|
142
|
+
server_handshake!(io, socket_type:, identity:, metadata:)
|
|
149
143
|
else
|
|
150
|
-
client_handshake!(io, socket_type:, identity:,
|
|
144
|
+
client_handshake!(io, socket_type:, identity:, metadata:)
|
|
151
145
|
end
|
|
152
146
|
end
|
|
153
147
|
|
|
@@ -227,7 +221,7 @@ module Protocol
|
|
|
227
221
|
# Client-side handshake
|
|
228
222
|
# ----------------------------------------------------------------
|
|
229
223
|
|
|
230
|
-
def client_handshake!(io, socket_type:, identity:,
|
|
224
|
+
def client_handshake!(io, socket_type:, identity:, metadata: nil)
|
|
231
225
|
cn_secret = @crypto::PrivateKey.generate
|
|
232
226
|
cn_public = cn_secret.public_key
|
|
233
227
|
|
|
@@ -288,18 +282,14 @@ module Protocol
|
|
|
288
282
|
vouch = @crypto::Box.new(sn_public, @permanent_secret).encrypt(vouch_nonce, vouch_plaintext)
|
|
289
283
|
|
|
290
284
|
props = { "Socket-Type" => socket_type, "Identity" => identity }
|
|
291
|
-
if
|
|
292
|
-
|
|
293
|
-
props["X-QoS-Hash"] = qos_hash unless qos_hash.empty?
|
|
294
|
-
end
|
|
295
|
-
props.merge!(@metadata) if @metadata
|
|
296
|
-
metadata = Codec::Command.encode_properties(props)
|
|
285
|
+
props.merge!(metadata) if metadata && !metadata.empty?
|
|
286
|
+
metadata_bytes = Codec::Command.encode_properties(props)
|
|
297
287
|
|
|
298
288
|
initiate_box_plaintext = "".b
|
|
299
289
|
initiate_box_plaintext << @permanent_public.to_s
|
|
300
290
|
initiate_box_plaintext << vouch_nonce.byteslice(8, 16)
|
|
301
291
|
initiate_box_plaintext << vouch
|
|
302
|
-
initiate_box_plaintext <<
|
|
292
|
+
initiate_box_plaintext << metadata_bytes
|
|
303
293
|
|
|
304
294
|
init_short_nonce = [1].pack("Q>")
|
|
305
295
|
init_nonce = NONCE_PREFIX_INITIATE + init_short_nonce
|
|
@@ -336,8 +326,6 @@ module Protocol
|
|
|
336
326
|
props = Codec::Command.decode_properties(r_plaintext)
|
|
337
327
|
peer_socket_type = props["Socket-Type"]
|
|
338
328
|
peer_identity = props["Identity"] || ""
|
|
339
|
-
peer_qos = (props["X-QoS"] || "0").to_i
|
|
340
|
-
peer_qos_hash = props["X-QoS-Hash"] || ""
|
|
341
329
|
|
|
342
330
|
@session_box = session
|
|
343
331
|
@send_nonce = 1
|
|
@@ -347,8 +335,7 @@ module Protocol
|
|
|
347
335
|
{
|
|
348
336
|
peer_socket_type: peer_socket_type,
|
|
349
337
|
peer_identity: peer_identity,
|
|
350
|
-
|
|
351
|
-
peer_qos_hash: peer_qos_hash,
|
|
338
|
+
peer_public_key: @server_public,
|
|
352
339
|
peer_properties: props,
|
|
353
340
|
peer_major: @peer_major,
|
|
354
341
|
peer_minor: @peer_minor,
|
|
@@ -360,7 +347,7 @@ module Protocol
|
|
|
360
347
|
# Server-side handshake
|
|
361
348
|
# ----------------------------------------------------------------
|
|
362
349
|
|
|
363
|
-
def server_handshake!(io, socket_type:, identity:,
|
|
350
|
+
def server_handshake!(io, socket_type:, identity:, metadata: nil)
|
|
364
351
|
io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: true))
|
|
365
352
|
io.flush
|
|
366
353
|
peer_greeting = Codec::Greeting.read_from(io)
|
|
@@ -478,7 +465,7 @@ module Protocol
|
|
|
478
465
|
end
|
|
479
466
|
|
|
480
467
|
if @authenticator
|
|
481
|
-
peer = PeerInfo.new(public_key: client_permanent)
|
|
468
|
+
peer = PeerInfo.new(public_key: client_permanent, identity: "")
|
|
482
469
|
unless @authenticator.call(peer)
|
|
483
470
|
send_error(io, "client key not authorized")
|
|
484
471
|
raise Error, "client key not authorized"
|
|
@@ -488,11 +475,7 @@ module Protocol
|
|
|
488
475
|
|
|
489
476
|
# --- READY ---
|
|
490
477
|
ready_props = { "Socket-Type" => socket_type, "Identity" => identity }
|
|
491
|
-
if
|
|
492
|
-
ready_props["X-QoS"] = qos.to_s
|
|
493
|
-
ready_props["X-QoS-Hash"] = qos_hash unless qos_hash.empty?
|
|
494
|
-
end
|
|
495
|
-
ready_props.merge!(@metadata) if @metadata
|
|
478
|
+
ready_props.merge!(metadata) if metadata && !metadata.empty?
|
|
496
479
|
ready_metadata = Codec::Command.encode_properties(ready_props)
|
|
497
480
|
|
|
498
481
|
r_short_nonce = [1].pack("Q>")
|
|
@@ -517,8 +500,7 @@ module Protocol
|
|
|
517
500
|
{
|
|
518
501
|
peer_socket_type: props["Socket-Type"],
|
|
519
502
|
peer_identity: props["Identity"] || "",
|
|
520
|
-
|
|
521
|
-
peer_qos_hash: props["X-QoS-Hash"] || "",
|
|
503
|
+
peer_public_key: client_permanent,
|
|
522
504
|
peer_properties: props,
|
|
523
505
|
peer_major: @peer_major,
|
|
524
506
|
peer_minor: @peer_minor,
|
|
@@ -11,16 +11,6 @@ module Protocol
|
|
|
11
11
|
class Null
|
|
12
12
|
MECHANISM_NAME = "NULL"
|
|
13
13
|
|
|
14
|
-
# Extra READY properties an upper layer (e.g. an OMQ extension)
|
|
15
|
-
# wants this side to advertise. Mutated before #handshake!.
|
|
16
|
-
# @return [Hash{String => String}]
|
|
17
|
-
attr_accessor :metadata
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
@metadata = nil
|
|
22
|
-
end
|
|
23
|
-
|
|
24
14
|
|
|
25
15
|
# Performs the full NULL handshake over +io+.
|
|
26
16
|
#
|
|
@@ -32,10 +22,11 @@ module Protocol
|
|
|
32
22
|
# @param as_server [Boolean]
|
|
33
23
|
# @param socket_type [String]
|
|
34
24
|
# @param identity [String]
|
|
35
|
-
# @
|
|
25
|
+
# @param metadata [Hash{String => String}, nil] extra READY properties
|
|
26
|
+
# @return [Hash] { peer_socket_type:, peer_identity:, peer_public_key:, peer_properties:, peer_major:, peer_minor: }
|
|
36
27
|
# @raise [Error]
|
|
37
|
-
def handshake!(io, as_server:, socket_type:, identity:,
|
|
38
|
-
io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server:
|
|
28
|
+
def handshake!(io, as_server:, socket_type:, identity:, metadata: nil)
|
|
29
|
+
io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: false))
|
|
39
30
|
io.flush
|
|
40
31
|
|
|
41
32
|
peer_greeting = Codec::Greeting.read_from(io)
|
|
@@ -45,11 +36,9 @@ module Protocol
|
|
|
45
36
|
end
|
|
46
37
|
|
|
47
38
|
ready_cmd = Codec::Command.ready(
|
|
48
|
-
socket_type:
|
|
49
|
-
identity:
|
|
50
|
-
|
|
51
|
-
qos_hash: qos_hash,
|
|
52
|
-
metadata: @metadata,
|
|
39
|
+
socket_type: socket_type,
|
|
40
|
+
identity: identity,
|
|
41
|
+
metadata: metadata,
|
|
53
42
|
)
|
|
54
43
|
io.write(ready_cmd.to_frame.to_wire)
|
|
55
44
|
io.flush
|
|
@@ -67,8 +56,6 @@ module Protocol
|
|
|
67
56
|
props = peer_cmd.properties
|
|
68
57
|
peer_socket_type = props["Socket-Type"]
|
|
69
58
|
peer_identity = props["Identity"] || ""
|
|
70
|
-
peer_qos = (props["X-QoS"] || "0").to_i
|
|
71
|
-
peer_qos_hash = props["X-QoS-Hash"] || ""
|
|
72
59
|
|
|
73
60
|
unless peer_socket_type
|
|
74
61
|
raise Error, "peer READY missing Socket-Type"
|
|
@@ -77,8 +64,7 @@ module Protocol
|
|
|
77
64
|
{
|
|
78
65
|
peer_socket_type: peer_socket_type,
|
|
79
66
|
peer_identity: peer_identity,
|
|
80
|
-
|
|
81
|
-
peer_qos_hash: peer_qos_hash,
|
|
67
|
+
peer_public_key: nil,
|
|
82
68
|
peer_properties: props,
|
|
83
69
|
peer_major: peer_greeting[:major],
|
|
84
70
|
peer_minor: peer_greeting[:minor],
|
|
@@ -16,22 +16,15 @@ module Protocol
|
|
|
16
16
|
MECHANISM_NAME = "PLAIN"
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
# Extra READY/INITIATE properties an upper layer wants this side to
|
|
20
|
-
# advertise. Mutated before #handshake!.
|
|
21
|
-
# @return [Hash{String => String}]
|
|
22
|
-
attr_accessor :metadata
|
|
23
|
-
|
|
24
|
-
|
|
25
19
|
# @param username [String] client username (max 255 bytes)
|
|
26
20
|
# @param password [String] client password (max 255 bytes)
|
|
27
21
|
# @param authenticator [#call, nil] server-side credential verifier;
|
|
28
22
|
# called as +authenticator.call(username, password)+ and must return
|
|
29
23
|
# truthy to accept the connection. When +nil+, all credentials pass.
|
|
30
24
|
def initialize(username: "", password: "", authenticator: nil)
|
|
31
|
-
@username
|
|
32
|
-
@password
|
|
33
|
-
@authenticator
|
|
34
|
-
@metadata = nil
|
|
25
|
+
@username = username
|
|
26
|
+
@password = password
|
|
27
|
+
@authenticator = authenticator
|
|
35
28
|
end
|
|
36
29
|
|
|
37
30
|
|
|
@@ -41,9 +34,10 @@ module Protocol
|
|
|
41
34
|
# @param as_server [Boolean]
|
|
42
35
|
# @param socket_type [String]
|
|
43
36
|
# @param identity [String]
|
|
44
|
-
# @
|
|
37
|
+
# @param metadata [Hash{String => String}, nil] extra READY properties
|
|
38
|
+
# @return [Hash] { peer_socket_type:, peer_identity:, peer_properties: }
|
|
45
39
|
# @raise [Error]
|
|
46
|
-
def handshake!(io, as_server:, socket_type:, identity:,
|
|
40
|
+
def handshake!(io, as_server:, socket_type:, identity:, metadata: nil)
|
|
47
41
|
io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: as_server))
|
|
48
42
|
io.flush
|
|
49
43
|
|
|
@@ -54,11 +48,9 @@ module Protocol
|
|
|
54
48
|
end
|
|
55
49
|
|
|
56
50
|
if as_server
|
|
57
|
-
server_handshake! io, socket_type: socket_type, identity: identity,
|
|
58
|
-
qos: qos, qos_hash: qos_hash
|
|
51
|
+
server_handshake! io, socket_type: socket_type, identity: identity, metadata: metadata
|
|
59
52
|
else
|
|
60
|
-
client_handshake! io, socket_type: socket_type, identity: identity,
|
|
61
|
-
qos: qos, qos_hash: qos_hash
|
|
53
|
+
client_handshake! io, socket_type: socket_type, identity: identity, metadata: metadata
|
|
62
54
|
end
|
|
63
55
|
end
|
|
64
56
|
|
|
@@ -72,7 +64,7 @@ module Protocol
|
|
|
72
64
|
private
|
|
73
65
|
|
|
74
66
|
|
|
75
|
-
def client_handshake!(io, socket_type:, identity:,
|
|
67
|
+
def client_handshake!(io, socket_type:, identity:, metadata: nil)
|
|
76
68
|
send_command(io, hello_command)
|
|
77
69
|
|
|
78
70
|
cmd = read_command(io)
|
|
@@ -80,15 +72,9 @@ module Protocol
|
|
|
80
72
|
|
|
81
73
|
props = {
|
|
82
74
|
"Socket-Type" => socket_type,
|
|
83
|
-
"Identity" => identity
|
|
75
|
+
"Identity" => identity,
|
|
84
76
|
}
|
|
85
|
-
|
|
86
|
-
if qos > 0
|
|
87
|
-
props["X-QoS"] = qos.to_s
|
|
88
|
-
props["X-QoS-Hash"] = qos_hash unless qos_hash.empty?
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
props.merge!(@metadata) if @metadata && !@metadata.empty?
|
|
77
|
+
props.merge!(metadata) if metadata && !metadata.empty?
|
|
92
78
|
initiate = Codec::Command.new("INITIATE", Codec::Command.encode_properties(props))
|
|
93
79
|
send_command(io, initiate)
|
|
94
80
|
|
|
@@ -99,7 +85,7 @@ module Protocol
|
|
|
99
85
|
end
|
|
100
86
|
|
|
101
87
|
|
|
102
|
-
def server_handshake!(io, socket_type:, identity:,
|
|
88
|
+
def server_handshake!(io, socket_type:, identity:, metadata: nil)
|
|
103
89
|
cmd = read_command(io)
|
|
104
90
|
raise Error, "expected HELLO, got #{cmd.name}" unless cmd.name == "HELLO"
|
|
105
91
|
|
|
@@ -116,8 +102,7 @@ module Protocol
|
|
|
116
102
|
|
|
117
103
|
peer_info = extract_peer_info(cmd)
|
|
118
104
|
|
|
119
|
-
ready = Codec::Command.ready socket_type: socket_type, identity: identity,
|
|
120
|
-
qos: qos, qos_hash: qos_hash, metadata: @metadata
|
|
105
|
+
ready = Codec::Command.ready socket_type: socket_type, identity: identity, metadata: metadata
|
|
121
106
|
send_command io, ready
|
|
122
107
|
|
|
123
108
|
peer_info
|
|
@@ -165,9 +150,7 @@ module Protocol
|
|
|
165
150
|
{
|
|
166
151
|
peer_socket_type: peer_socket_type,
|
|
167
152
|
peer_identity: props["Identity"] || "",
|
|
168
|
-
|
|
169
|
-
peer_qos_hash: props["X-QoS-Hash"] || "",
|
|
170
|
-
peer_properties: props
|
|
153
|
+
peer_properties: props,
|
|
171
154
|
}
|
|
172
155
|
end
|
|
173
156
|
|
|
@@ -2,8 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Protocol
|
|
4
4
|
module ZMTP
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
|
|
5
|
+
# Post-handshake identity of the peer on a {Connection}.
|
|
6
|
+
#
|
|
7
|
+
# - +public_key+ is the peer's CURVE long-term +crypto::PublicKey+,
|
|
8
|
+
# or +nil+ when the mechanism does not authenticate a key (e.g. NULL).
|
|
9
|
+
# - +identity+ is the peer's +ZMQ_IDENTITY+ string, or +""+ when no
|
|
10
|
+
# identity was advertised in the READY metadata.
|
|
11
|
+
#
|
|
12
|
+
# Used as a peer key by upper layers (e.g. omq-qos levels 2/3): the
|
|
13
|
+
# whole value object is frozen and equality-comparable, so it can be
|
|
14
|
+
# stored directly in a Hash without picking one anchor over the other.
|
|
15
|
+
#
|
|
16
|
+
# Also passed to CURVE authenticators during the handshake (at that
|
|
17
|
+
# point +identity+ is +""+, since it arrives post-auth).
|
|
18
|
+
PeerInfo = Data.define(:public_key, :identity)
|
|
8
19
|
end
|
|
9
20
|
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
|
+
version: 0.10.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -53,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
requirements: []
|
|
56
|
-
rubygems_version: 4.0.
|
|
56
|
+
rubygems_version: 4.0.10
|
|
57
57
|
specification_version: 4
|
|
58
58
|
summary: ZMTP 3.1 wire protocol codec and connection
|
|
59
59
|
test_files: []
|