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 +4 -4
- data/lib/protocol/zmtp/codec/greeting.rb +32 -1
- data/lib/protocol/zmtp/codec/subscription.rb +69 -0
- data/lib/protocol/zmtp/codec.rb +1 -0
- data/lib/protocol/zmtp/connection.rb +11 -0
- data/lib/protocol/zmtp/mechanism/curve.rb +10 -2
- data/lib/protocol/zmtp/mechanism/null.rb +4 -3
- data/lib/protocol/zmtp/mechanism/plain.rb +1 -2
- data/lib/protocol/zmtp/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5372fe6734b5faeded3069e3b510a7ff648458508c82e3a6c2b9d353ebd57179
|
|
4
|
+
data.tar.gz: b6e5cb324afac1deece4df0a0c8cc3aadbfa2ca3a31d29b38ece87dcea777c82
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
data/lib/protocol/zmtp/codec.rb
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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]}"
|
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.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
|