protocol-zmtp 0.9.0 → 0.10.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: edfecc19ca6c0befbc5835da0fbfc1fc364007207f10e6f1de64c733d48f2336
4
- data.tar.gz: d6616b661ee77e73191ed6f33e0a3c5b73b04e6b3fe8e8e5cac30067ae75bb42
3
+ metadata.gz: 303ee019ebede5cddca93226c136233c19b7ec6e54cad7ef0deba5f5a5f6e347
4
+ data.tar.gz: 229f46c1a06f1fe7d02a3b1a518fee8958786959fa0b43fe6587d47392c9d602
5
5
  SHA512:
6
- metadata.gz: 4520a2435a1490fcfb7aebd070f58d10e97a05da685a3c36a47417db2e82b8d7ef48d0c5d19ee6c7db99c7b5da552cd4fec2f061aee3fd9e55908d7d30cf301d
7
- data.tar.gz: d8c6f26fe3197a25f32e5c5a6668d53eb8cecf109dbe084b00c63b2fcb396cbe05dfd37c9b01ecbc39a960f35ce56ca70fa02c49761e9e0065246ffffeb36437
6
+ metadata.gz: 38345cf8abc4d1ed198a90abe714d15dd5cc8877fd22ba25f672c9ff4c02a36e9c7754a6c3998fc3b0cf9dd8972e54595be4c953c4e18d0acecaf3363b5971d2
7
+ data.tar.gz: 50c74c72d1926d1bd3756cf130d57039337f75f34c47d701b7b7a936e9f3d9b57e9f203e6b841b454b9de5d99624ada8239dbfb6e2f8cf8285ae991aa94b9828
@@ -70,20 +70,14 @@ module Protocol
70
70
  end
71
71
 
72
72
 
73
- # Builds a READY command with Socket-Type, Identity, optional X-QoS,
74
- # and any extra properties supplied by upper layers (e.g. an
75
- # extension that injects an `X-Compression` property).
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 qos [Integer] QoS level (0 = omitted)
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: "", qos: 0, qos_hash: "", metadata: nil)
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.encoding == Encoding::BINARY ? body : body.b
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 [Integer] peer's QoS level (from READY handshake, 0 if absent)
24
- attr_reader :peer_qos
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, qos: 0, qos_hash: "")
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
- @peer_qos = nil
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
- @qos = qos
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
@@ -92,13 +92,11 @@ module Protocol
92
92
  as_server: @as_server,
93
93
  socket_type: @socket_type,
94
94
  identity: @identity,
95
- qos: @qos,
96
- qos_hash: @qos_hash
95
+ metadata: @metadata
97
96
 
98
97
  @peer_socket_type = result[:peer_socket_type]
99
98
  @peer_identity = result[:peer_identity]
100
- @peer_qos = result[:peer_qos] || 0
101
- @peer_qos_hash = result[:peer_qos_hash] || ""
99
+ @peer_public_key = result[:peer_public_key]
102
100
  @peer_properties = result[:peer_properties]
103
101
  @peer_major = result[:peer_major]
104
102
  @peer_minor = result[:peer_minor]
@@ -114,6 +112,17 @@ module Protocol
114
112
  end
115
113
 
116
114
 
115
+ # Returns a {PeerInfo} value bundling the peer's CURVE public key
116
+ # and identity for use as a stable per-peer key (frozen, hash-usable).
117
+ # Nil before the handshake has completed.
118
+ #
119
+ # @return [PeerInfo, nil]
120
+ def peer_info
121
+ return nil unless @peer_socket_type
122
+ PeerInfo.new(public_key: @peer_public_key, identity: @peer_identity)
123
+ end
124
+
125
+
117
126
  # Sends a multi-frame message (write + flush).
118
127
  #
119
128
  # @param parts [Array<String>] message frames
@@ -214,11 +223,11 @@ module Protocol
214
223
  next
215
224
  end
216
225
 
217
- frames << frame.body.freeze
226
+ frames << frame.body
218
227
  break unless frame.more?
219
228
  end
220
229
 
221
- frames.freeze
230
+ frames
222
231
  end
223
232
 
224
233
 
@@ -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 = nil
104
- @send_nonce = 0
105
- @recv_nonce = -1
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 qos [Integer] QoS level
143
- # @param qos_hash [String] supported hash algorithms
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:, qos: 0, qos_hash: "")
140
+ def handshake!(io, as_server:, socket_type:, identity:, metadata: nil)
147
141
  if @as_server
148
- server_handshake!(io, socket_type:, identity:, qos:, qos_hash:)
142
+ server_handshake!(io, socket_type:, identity:, metadata:)
149
143
  else
150
- client_handshake!(io, socket_type:, identity:, qos:, qos_hash:)
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:, qos: 0, qos_hash: "")
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 qos > 0
292
- props["X-QoS"] = qos.to_s
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 << metadata
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
- peer_qos: peer_qos,
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:, qos: 0, qos_hash: "")
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 qos > 0
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
- peer_qos: (props["X-QoS"] || "0").to_i,
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,9 +22,10 @@ module Protocol
32
22
  # @param as_server [Boolean]
33
23
  # @param socket_type [String]
34
24
  # @param identity [String]
35
- # @return [Hash] { peer_socket_type:, peer_identity:, peer_qos:, peer_qos_hash:, peer_properties:, peer_major:, peer_minor: }
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:, qos: 0, qos_hash: "")
28
+ def handshake!(io, as_server:, socket_type:, identity:, metadata: nil)
38
29
  io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: as_server))
39
30
  io.flush
40
31
 
@@ -45,11 +36,9 @@ module Protocol
45
36
  end
46
37
 
47
38
  ready_cmd = Codec::Command.ready(
48
- socket_type: socket_type,
49
- identity: identity,
50
- qos: qos,
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
- peer_qos: peer_qos,
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 = username
32
- @password = password
33
- @authenticator = 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
- # @return [Hash] { peer_socket_type:, peer_identity: }
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:, qos: 0, qos_hash: "")
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:, qos: 0, qos_hash: "")
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:, qos: 0, qos_hash: "")
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
- peer_qos: (props["X-QoS"] || "0").to_i,
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
- # Context passed to an authenticator during authentication.
6
- # +public_key+ is a +crypto::PublicKey+ instance.
7
- PeerInfo = Data.define(:public_key)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Protocol
4
4
  module ZMTP
5
- VERSION = "0.9.0"
5
+ VERSION = "0.10.0"
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.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger