protocol-zmtp 0.7.1 → 0.8.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: e17c453960d40efb63134468319f184e1751a1812a42e72b4b10de4f10c08f7a
4
- data.tar.gz: 3aa77f8983e7b737bc9c896d4ff7b0a476805e51d1bd2668d4399c4547beb2ca
3
+ metadata.gz: 5372fe6734b5faeded3069e3b510a7ff648458508c82e3a6c2b9d353ebd57179
4
+ data.tar.gz: b6e5cb324afac1deece4df0a0c8cc3aadbfa2ca3a31d29b38ece87dcea777c82
5
5
  SHA512:
6
- metadata.gz: c325ce9ad10043d55875304bf4267f5ef7ab286945126620fe43f0ec5eeedb228804ed1c6bb97a6dc135adc017124add12b56a1475bfb1ff37d74a0dc7536abc
7
- data.tar.gz: 1cdd5b8fd5abb3471c6e4147850b7e5d20c5bc47cec6fb4233c7e38845f8e5b8df2fd73463e618a128d07316c184facf678fa718674b8ba6e1cb70e9390310d5
6
+ metadata.gz: 21ec084b6bf898ed867c9c80700a3d9fad53146798d906a1cadeaed25f28745fa56d47f350e35e6af55e9ba5f7c8c7919342d2bba8e21d6068f585fb54105cc8
7
+ data.tar.gz: 90812f6950f8f558a9f33a7f57c2142c63b7c21bab6d4a5d2e788bdb5c9b26154a41b50dde097852e7bfab46e901d706c34d81b6acca0bfa5fd920bfcc655fe7
@@ -18,6 +18,11 @@ module Protocol
18
18
  #
19
19
  module Greeting
20
20
  SIZE = 64
21
+ # Bytes 0..10 cover the 10-byte signature plus the major-version
22
+ # byte at offset 10. ZMTP 2.0 peers only send an 11-byte signature
23
+ # phase (their full greeting is shorter than 64), so we must
24
+ # sniff the major version before committing to reading 64 bytes.
25
+ SIGNATURE_SIZE = 11
21
26
  SIGNATURE_START = 0xFF
22
27
  SIGNATURE_END = 0x7F
23
28
  VERSION_MAJOR = 3
@@ -41,6 +46,32 @@ module Protocol
41
46
  end
42
47
 
43
48
 
49
+ # Reads and decodes a ZMTP 3.x greeting from +io+, sniffing the
50
+ # major version after the first 11 bytes so a ZMTP 2.0 peer
51
+ # (which would never send the full 64 bytes) is detected and
52
+ # rejected without blocking forever on +read_exactly+.
53
+ #
54
+ # @param io [#read_exactly]
55
+ # @return [Hash] { major:, minor:, mechanism:, as_server: }
56
+ # @raise [Error] on invalid signature or unsupported version
57
+ #
58
+ def self.read_from(io)
59
+ sig = io.read_exactly(SIGNATURE_SIZE).b
60
+ major = sig.getbyte(10)
61
+
62
+ unless sig.getbyte(0) == SIGNATURE_START && sig.getbyte(9) == SIGNATURE_END
63
+ raise Error, "invalid greeting signature"
64
+ end
65
+
66
+ unless major >= 3
67
+ raise Error, "unsupported ZMTP revision 0x%02x (ZMTP/%d.x); need revision >= 3" %
68
+ [major, major == 1 ? 2 : major]
69
+ end
70
+
71
+ decode(sig + io.read_exactly(SIZE - SIGNATURE_SIZE))
72
+ end
73
+
74
+
44
75
  # Decodes a ZMTP greeting.
45
76
  #
46
77
  # @param data [String] 64-byte binary greeting
@@ -59,7 +90,7 @@ module Protocol
59
90
  minor = data.getbyte(11)
60
91
 
61
92
  unless major >= 3
62
- raise Error, "unsupported ZMTP version #{major}.#{minor} (need >= 3.0)"
93
+ raise Error, "unsupported ZMTP revision 0x%02x (need revision >= 3)" % major
63
94
  end
64
95
 
65
96
  mechanism = data.byteslice(MECHANISM_OFFSET, MECHANISM_LENGTH).delete("\x00")
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protocol
4
+ module ZMTP
5
+ module Codec
6
+
7
+ # ZMTP subscription encoding.
8
+ #
9
+ # Two wire formats exist and both are in active use:
10
+ #
11
+ # * **Message form (ZMTP 3.0 legacy, RFC 23).** A regular data
12
+ # frame whose body is `\x01` + prefix (subscribe) or `\x00` +
13
+ # prefix (cancel). libzmq, JeroMQ, pyzmq, CZMQ, NetMQ all send
14
+ # subscriptions in this form by default, and all accept it.
15
+ #
16
+ # * **Command form (ZMTP 3.1, RFC 37).** A COMMAND-flagged frame
17
+ # whose body is a Command named "SUBSCRIBE" or "CANCEL" with
18
+ # the prefix as the command data.
19
+ #
20
+ # Interop requires sending the message form (understood by every
21
+ # ZMTP 3.0+ peer) and accepting both forms on the receiving side.
22
+ #
23
+ module Subscription
24
+ FLAG_SUBSCRIBE = "\x01".b.freeze
25
+ FLAG_CANCEL = "\x00".b.freeze
26
+
27
+ module_function
28
+
29
+ # Builds the body of a subscription message in the legacy
30
+ # message form.
31
+ #
32
+ # @param prefix [String] topic prefix
33
+ # @param cancel [Boolean] true to build an unsubscribe
34
+ # @return [String] binary frame body
35
+ def body(prefix, cancel: false)
36
+ flag = cancel ? FLAG_CANCEL : FLAG_SUBSCRIBE
37
+ (flag + prefix.b).b
38
+ end
39
+
40
+
41
+ # Attempts to parse a frame as a subscription. Accepts both the
42
+ # legacy message form and the ZMTP 3.1 command form.
43
+ #
44
+ # @param frame [Frame]
45
+ # @return [Array(Symbol, String), nil] `[:subscribe, prefix]`,
46
+ # `[:cancel, prefix]`, or `nil` if the frame is not a
47
+ # subscription
48
+ def parse(frame)
49
+ if frame.command?
50
+ cmd = Command.from_body(frame.body)
51
+ case cmd.name
52
+ when "SUBSCRIBE" then [:subscribe, cmd.data]
53
+ when "CANCEL" then [:cancel, cmd.data]
54
+ end
55
+ else
56
+ body = frame.body
57
+ return nil if body.empty?
58
+
59
+ prefix = body.byteslice(1..) || "".b
60
+ case body.getbyte(0)
61
+ when 0x01 then [:subscribe, prefix]
62
+ when 0x00 then [:cancel, prefix]
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -11,3 +11,4 @@ end
11
11
  require_relative "codec/greeting"
12
12
  require_relative "codec/frame"
13
13
  require_relative "codec/command"
14
+ require_relative "codec/subscription"
@@ -28,6 +28,13 @@ module Protocol
28
28
  # (set after a successful handshake; nil before)
29
29
  attr_reader :peer_properties
30
30
 
31
+ # @return [Integer, nil] peer ZMTP major version (from greeting)
32
+ attr_reader :peer_major
33
+
34
+ # @return [Integer, nil] peer ZMTP minor version (from greeting);
35
+ # 0 for ZMTP 3.0 peers, 1 for ZMTP 3.1+
36
+ attr_reader :peer_minor
37
+
31
38
  # @return [Object] transport IO (#read_exactly, #write, #flush, #close)
32
39
  attr_reader :io
33
40
 
@@ -52,6 +59,8 @@ module Protocol
52
59
  @peer_qos = nil
53
60
  @peer_qos_hash = nil
54
61
  @peer_properties = nil
62
+ @peer_major = nil
63
+ @peer_minor = nil
55
64
  @qos = qos
56
65
  @qos_hash = qos_hash
57
66
  @mutex = Mutex.new
@@ -84,6 +93,8 @@ module Protocol
84
93
  @peer_qos = result[:peer_qos] || 0
85
94
  @peer_qos_hash = result[:peer_qos_hash] || ""
86
95
  @peer_properties = result[:peer_properties]
96
+ @peer_major = result[:peer_major]
97
+ @peer_minor = result[:peer_minor]
87
98
 
88
99
  unless @peer_socket_type
89
100
  raise Error, "peer READY missing Socket-Type"
@@ -233,10 +233,12 @@ module Protocol
233
233
 
234
234
  io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: false))
235
235
  io.flush
236
- peer_greeting = Codec::Greeting.decode(io.read_exactly(Codec::Greeting::SIZE))
236
+ peer_greeting = Codec::Greeting.read_from(io)
237
237
  unless peer_greeting[:mechanism] == MECHANISM_NAME
238
238
  raise Error, "expected CURVE mechanism, got #{peer_greeting[:mechanism]}"
239
239
  end
240
+ @peer_major = peer_greeting[:major]
241
+ @peer_minor = peer_greeting[:minor]
240
242
 
241
243
 
242
244
  # --- HELLO ---
@@ -348,6 +350,8 @@ module Protocol
348
350
  peer_qos: peer_qos,
349
351
  peer_qos_hash: peer_qos_hash,
350
352
  peer_properties: props,
353
+ peer_major: @peer_major,
354
+ peer_minor: @peer_minor,
351
355
  }
352
356
  end
353
357
 
@@ -359,10 +363,12 @@ module Protocol
359
363
  def server_handshake!(io, socket_type:, identity:, qos: 0, qos_hash: "")
360
364
  io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: true))
361
365
  io.flush
362
- peer_greeting = Codec::Greeting.decode(io.read_exactly(Codec::Greeting::SIZE))
366
+ peer_greeting = Codec::Greeting.read_from(io)
363
367
  unless peer_greeting[:mechanism] == MECHANISM_NAME
364
368
  raise Error, "expected CURVE mechanism, got #{peer_greeting[:mechanism]}"
365
369
  end
370
+ @peer_major = peer_greeting[:major]
371
+ @peer_minor = peer_greeting[:minor]
366
372
 
367
373
 
368
374
  # --- Read HELLO ---
@@ -514,6 +520,8 @@ module Protocol
514
520
  peer_qos: (props["X-QoS"] || "0").to_i,
515
521
  peer_qos_hash: props["X-QoS-Hash"] || "",
516
522
  peer_properties: props,
523
+ peer_major: @peer_major,
524
+ peer_minor: @peer_minor,
517
525
  }
518
526
  end
519
527
 
@@ -32,14 +32,13 @@ module Protocol
32
32
  # @param as_server [Boolean]
33
33
  # @param socket_type [String]
34
34
  # @param identity [String]
35
- # @return [Hash] { peer_socket_type:, peer_identity:, peer_qos:, peer_qos_hash:, peer_properties: }
35
+ # @return [Hash] { peer_socket_type:, peer_identity:, peer_qos:, peer_qos_hash:, peer_properties:, peer_major:, peer_minor: }
36
36
  # @raise [Error]
37
37
  def handshake!(io, as_server:, socket_type:, identity:, qos: 0, qos_hash: "")
38
38
  io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: as_server))
39
39
  io.flush
40
40
 
41
- greeting_data = io.read_exactly(Codec::Greeting::SIZE)
42
- peer_greeting = Codec::Greeting.decode(greeting_data)
41
+ peer_greeting = Codec::Greeting.read_from(io)
43
42
 
44
43
  unless peer_greeting[:mechanism] == MECHANISM_NAME
45
44
  raise Error, "unsupported mechanism: #{peer_greeting[:mechanism]}"
@@ -81,6 +80,8 @@ module Protocol
81
80
  peer_qos: peer_qos,
82
81
  peer_qos_hash: peer_qos_hash,
83
82
  peer_properties: props,
83
+ peer_major: peer_greeting[:major],
84
+ peer_minor: peer_greeting[:minor],
84
85
  }
85
86
  end
86
87
 
@@ -47,8 +47,7 @@ module Protocol
47
47
  io.write(Codec::Greeting.encode(mechanism: MECHANISM_NAME, as_server: as_server))
48
48
  io.flush
49
49
 
50
- greeting_data = io.read_exactly(Codec::Greeting::SIZE)
51
- peer_greeting = Codec::Greeting.decode(greeting_data)
50
+ peer_greeting = Codec::Greeting.read_from(io)
52
51
 
53
52
  unless peer_greeting[:mechanism] == MECHANISM_NAME
54
53
  raise Error, "unsupported mechanism: #{peer_greeting[:mechanism]}"
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Protocol
4
4
  module ZMTP
5
- VERSION = "0.7.1"
5
+ VERSION = "0.8.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.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -25,6 +25,7 @@ files:
25
25
  - lib/protocol/zmtp/codec/command.rb
26
26
  - lib/protocol/zmtp/codec/frame.rb
27
27
  - lib/protocol/zmtp/codec/greeting.rb
28
+ - lib/protocol/zmtp/codec/subscription.rb
28
29
  - lib/protocol/zmtp/connection.rb
29
30
  - lib/protocol/zmtp/error.rb
30
31
  - lib/protocol/zmtp/mechanism/curve.rb