omq 0.6.5 → 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.
@@ -35,8 +35,9 @@ module OMQ
35
35
  identity = SecureRandom.bytes(5) if identity.nil? || identity.empty?
36
36
  @connections_by_identity[identity] = connection
37
37
 
38
- task = @engine.start_recv_pump(connection, @recv_queue,
39
- transform: ->(msg) { [identity, *msg] })
38
+ task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
39
+ [identity, *msg]
40
+ end
40
41
  @tasks << task if task
41
42
 
42
43
  start_send_pump unless @send_pump_started
@@ -34,8 +34,9 @@ module OMQ
34
34
  routing_id = SecureRandom.bytes(4)
35
35
  @connections_by_routing_id[routing_id] = connection
36
36
 
37
- task = @engine.start_recv_pump(connection, @recv_queue,
38
- transform: ->(msg) { [routing_id, *msg] })
37
+ task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
38
+ [routing_id, *msg]
39
+ end
39
40
  @tasks << task if task
40
41
 
41
42
  start_send_pump unless @send_pump_started
data/lib/omq/zmtp.rb CHANGED
@@ -1,13 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "protocol/zmtp"
4
+ require "io/stream"
5
+
3
6
  module OMQ
4
7
  # ZMTP 3.1 protocol internals.
5
8
  #
6
- # These classes implement the wire protocol, transports, and routing
7
- # strategies. They are not part of the public API.
9
+ # The wire protocol (codec, connection, mechanisms) lives in the
10
+ # protocol-zmtp gem. This module re-exports those classes under the
11
+ # OMQ::ZMTP namespace and adds the transport/routing/engine layers.
8
12
  #
9
13
  module ZMTP
10
- require "io/stream"
14
+ # Re-export protocol-zmtp classes
15
+ Codec = Protocol::ZMTP::Codec
16
+ Connection = Protocol::ZMTP::Connection
17
+ ProtocolError = Protocol::ZMTP::Error
18
+ VALID_PEERS = Protocol::ZMTP::VALID_PEERS
19
+
20
+ module Mechanism
21
+ Null = Protocol::ZMTP::Mechanism::Null
22
+ Curve = Protocol::ZMTP::Mechanism::Curve if defined?(Protocol::ZMTP::Mechanism::Curve)
23
+ end
11
24
 
12
25
  # Errors raised when a peer disconnects or resets the connection.
13
26
  CONNECTION_LOST = [
@@ -32,24 +45,14 @@ module OMQ
32
45
  end
33
46
  end
34
47
 
35
- # Constants
36
- require_relative "zmtp/valid_peers"
37
-
38
- # Codec
39
- require_relative "zmtp/codec"
40
-
41
48
  # Transport
42
49
  require_relative "zmtp/transport/inproc"
43
50
  require_relative "zmtp/transport/tcp"
44
51
  require_relative "zmtp/transport/ipc"
45
52
 
46
- # Mechanisms
47
- require_relative "zmtp/mechanism/null"
48
-
49
53
  # Core
50
54
  require_relative "zmtp/reactor"
51
55
  require_relative "zmtp/options"
52
- require_relative "zmtp/connection"
53
56
  require_relative "zmtp/routing"
54
57
  require_relative "zmtp/routing/round_robin"
55
58
  require_relative "zmtp/routing/fan_out"
data/lib/omq.rb CHANGED
@@ -17,6 +17,7 @@ module OMQ
17
17
  #
18
18
  class SocketDeadError < RuntimeError; end
19
19
  end
20
+
20
21
  require_relative "omq/zmtp"
21
22
  require_relative "omq/socket"
22
23
  require_relative "omq/req_rep"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.5
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -9,6 +9,20 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: protocol-zmtp
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: async
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -83,13 +97,7 @@ files:
83
97
  - lib/omq/socket.rb
84
98
  - lib/omq/version.rb
85
99
  - lib/omq/zmtp.rb
86
- - lib/omq/zmtp/codec.rb
87
- - lib/omq/zmtp/codec/command.rb
88
- - lib/omq/zmtp/codec/frame.rb
89
- - lib/omq/zmtp/codec/greeting.rb
90
- - lib/omq/zmtp/connection.rb
91
100
  - lib/omq/zmtp/engine.rb
92
- - lib/omq/zmtp/mechanism/null.rb
93
101
  - lib/omq/zmtp/options.rb
94
102
  - lib/omq/zmtp/reactor.rb
95
103
  - lib/omq/zmtp/readable.rb
@@ -119,7 +127,6 @@ files:
119
127
  - lib/omq/zmtp/transport/inproc.rb
120
128
  - lib/omq/zmtp/transport/ipc.rb
121
129
  - lib/omq/zmtp/transport/tcp.rb
122
- - lib/omq/zmtp/valid_peers.rb
123
130
  - lib/omq/zmtp/writable.rb
124
131
  homepage: https://github.com/zeromq/omq
125
132
  licenses:
@@ -1,210 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Codec
6
- # ZMTP command encode/decode.
7
- #
8
- # Command frame body format:
9
- # 1 byte: command name length
10
- # N bytes: command name
11
- # remaining: command data
12
- #
13
- # READY command data = property list:
14
- # 1 byte: property name length
15
- # N bytes: property name
16
- # 4 bytes: property value length (big-endian)
17
- # N bytes: property value
18
- #
19
- class Command
20
- # @return [String] command name (e.g. "READY", "SUBSCRIBE")
21
- #
22
- attr_reader :name
23
-
24
- # @return [String] command data (binary)
25
- #
26
- attr_reader :data
27
-
28
- # @param name [String] command name
29
- # @param data [String] command data
30
- #
31
- def initialize(name, data = "".b)
32
- @name = name
33
- @data = data.b
34
- end
35
-
36
- # Encodes as a command frame body.
37
- #
38
- # @return [String] binary body (name-length + name + data)
39
- #
40
- def to_body
41
- name_bytes = @name.b
42
- name_bytes.bytesize.chr.b + name_bytes + @data
43
- end
44
-
45
- # Encodes as a complete command Frame.
46
- #
47
- # @return [Frame]
48
- #
49
- def to_frame
50
- Frame.new(to_body, command: true)
51
- end
52
-
53
- # Decodes a command from a frame body.
54
- #
55
- # @param body [String] binary frame body
56
- # @return [Command]
57
- # @raise [ProtocolError] on malformed command
58
- #
59
- def self.from_body(body)
60
- body = body.b
61
- raise ProtocolError, "command body too short" if body.bytesize < 1
62
-
63
- name_len = body.getbyte(0)
64
-
65
- raise ProtocolError, "command name truncated" if body.bytesize < 1 + name_len
66
-
67
- name = body.byteslice(1, name_len)
68
- data = body.byteslice(1 + name_len..)
69
- new(name, data)
70
- end
71
-
72
- # Builds a READY command with Socket-Type and Identity properties.
73
- #
74
- # @param socket_type [String] e.g. "REQ", "REP", "PAIR"
75
- # @param identity [String] peer identity (can be empty)
76
- # @return [Command]
77
- #
78
- def self.ready(socket_type:, identity: "")
79
- props = encode_properties(
80
- "Socket-Type" => socket_type,
81
- "Identity" => identity,
82
- )
83
- new("READY", props)
84
- end
85
-
86
- # Builds a SUBSCRIBE command.
87
- #
88
- # @param prefix [String] subscription prefix
89
- # @return [Command]
90
- #
91
- def self.subscribe(prefix)
92
- new("SUBSCRIBE", prefix.b)
93
- end
94
-
95
- # Builds a CANCEL command (unsubscribe).
96
- #
97
- # @param prefix [String] subscription prefix to cancel
98
- # @return [Command]
99
- #
100
- def self.cancel(prefix)
101
- new("CANCEL", prefix.b)
102
- end
103
-
104
- # Builds a JOIN command (RADIO/DISH group subscription).
105
- #
106
- # @param group [String] group name
107
- # @return [Command]
108
- #
109
- def self.join(group)
110
- new("JOIN", group.b)
111
- end
112
-
113
- # Builds a LEAVE command (RADIO/DISH group unsubscription).
114
- #
115
- # @param group [String] group name
116
- # @return [Command]
117
- #
118
- def self.leave(group)
119
- new("LEAVE", group.b)
120
- end
121
-
122
- # Builds a PING command.
123
- #
124
- # @param ttl [Numeric] time-to-live in seconds (sent as deciseconds)
125
- # @param context [String] optional context bytes (up to 16 bytes)
126
- # @return [Command]
127
- #
128
- def self.ping(ttl: 0, context: "".b)
129
- ttl_ds = (ttl * 10).to_i
130
- new("PING", [ttl_ds].pack("n") + context.b)
131
- end
132
-
133
- # Builds a PONG command.
134
- #
135
- # @param context [String] context bytes from the PING
136
- # @return [Command]
137
- #
138
- def self.pong(context: "".b)
139
- new("PONG", context.b)
140
- end
141
-
142
- # Extracts TTL (in seconds) and context from a PING command's data.
143
- #
144
- # @return [Array(Numeric, String)] [ttl_seconds, context_bytes]
145
- #
146
- def ping_ttl_and_context
147
- ttl_ds = @data.unpack1("n")
148
- context = @data.bytesize > 2 ? @data.byteslice(2..) : "".b
149
- [ttl_ds / 10.0, context]
150
- end
151
-
152
- # Parses READY command data as a property list.
153
- #
154
- # @return [Hash<String, String>] property name => value
155
- # @raise [ProtocolError] on malformed properties
156
- #
157
- def properties
158
- self.class.decode_properties(@data)
159
- end
160
-
161
- # Encodes a hash of properties into ZMTP property list format.
162
- #
163
- # @param props [Hash<String, String>]
164
- # @return [String] binary property list
165
- #
166
- def self.encode_properties(props)
167
- parts = props.map do |name, value|
168
- name_bytes = name.b
169
- value_bytes = value.b
170
- name_bytes.bytesize.chr.b + name_bytes + [value_bytes.bytesize].pack("N") + value_bytes
171
- end
172
- parts.join
173
- end
174
-
175
- # Decodes a ZMTP property list from binary data.
176
- #
177
- # @param data [String] binary property list
178
- # @return [Hash<String, String>] property name => value
179
- # @raise [ProtocolError] on malformed properties
180
- #
181
- def self.decode_properties(data)
182
- result = {}
183
- offset = 0
184
-
185
- while offset < data.bytesize
186
- raise ProtocolError, "property name truncated" if offset + 1 > data.bytesize
187
- name_len = data.getbyte(offset)
188
- offset += 1
189
-
190
- raise ProtocolError, "property name truncated" if offset + name_len > data.bytesize
191
- name = data.byteslice(offset, name_len)
192
- offset += name_len
193
-
194
- raise ProtocolError, "property value length truncated" if offset + 4 > data.bytesize
195
- value_len = data.byteslice(offset, 4).unpack1("N")
196
- offset += 4
197
-
198
- raise ProtocolError, "property value truncated" if offset + value_len > data.bytesize
199
- value = data.byteslice(offset, value_len)
200
- offset += value_len
201
-
202
- result[name] = value
203
- end
204
-
205
- result
206
- end
207
- end
208
- end
209
- end
210
- end
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Codec
6
- # ZMTP frame encode/decode.
7
- #
8
- # Wire format:
9
- # Byte 0: flags (bit 0=MORE, bit 1=LONG, bit 2=COMMAND)
10
- # Next 1-8: size (1-byte if short, 8-byte big-endian if LONG)
11
- # Next N: body
12
- #
13
- class Frame
14
- FLAGS_MORE = 0x01
15
- FLAGS_LONG = 0x02
16
- FLAGS_COMMAND = 0x04
17
-
18
- # Short frame: 1-byte size, max body 255 bytes.
19
- #
20
- SHORT_MAX = 255
21
-
22
- # @return [String] frame body (binary)
23
- #
24
- attr_reader :body
25
-
26
- # @param body [String] frame body
27
- # @param more [Boolean] more frames follow
28
- # @param command [Boolean] this is a command frame
29
- #
30
- def initialize(body, more: false, command: false)
31
- @body = body.b
32
- @more = more
33
- @command = command
34
- end
35
-
36
- # @return [Boolean] true if more frames follow in this message
37
- #
38
- def more? = @more
39
-
40
- # @return [Boolean] true if this is a command frame
41
- #
42
- def command? = @command
43
-
44
- # Encodes to wire bytes.
45
- #
46
- # @return [String] binary wire representation (flags + size + body)
47
- #
48
- def to_wire
49
- size = @body.bytesize
50
- flags = 0
51
- flags |= FLAGS_MORE if @more
52
- flags |= FLAGS_COMMAND if @command
53
-
54
- if size > SHORT_MAX
55
- (flags | FLAGS_LONG).chr.b + [size].pack("Q>") + @body
56
- else
57
- flags.chr.b + size.chr.b + @body
58
- end
59
- end
60
-
61
- # Reads one frame from an IO-like object.
62
- #
63
- # @param io [#read_exactly] must support read_exactly(n)
64
- # @return [Frame]
65
- # @raise [ProtocolError] on invalid frame
66
- # @raise [EOFError] if the connection is closed
67
- #
68
- def self.read_from(io)
69
- flags = io.read_exactly(1).getbyte(0)
70
-
71
- more = (flags & FLAGS_MORE) != 0
72
- long = (flags & FLAGS_LONG) != 0
73
- command = (flags & FLAGS_COMMAND) != 0
74
-
75
- size = if long
76
- io.read_exactly(8).unpack1("Q>")
77
- else
78
- io.read_exactly(1).getbyte(0)
79
- end
80
-
81
- body = size > 0 ? io.read_exactly(size) : "".b
82
-
83
- new(body, more: more, command: command)
84
- end
85
-
86
- end
87
- end
88
- end
89
- end
@@ -1,78 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- module Codec
6
- # ZMTP 3.1 greeting encode/decode.
7
- #
8
- # The greeting is always exactly 64 bytes:
9
- # Offset Bytes Field
10
- # 0 1 0xFF (signature start)
11
- # 1-8 8 0x00 padding
12
- # 9 1 0x7F (signature end)
13
- # 10 1 major version
14
- # 11 1 minor version
15
- # 12-31 20 mechanism (null-padded ASCII)
16
- # 32 1 as-server flag (0x00 or 0x01)
17
- # 33-63 31 filler (0x00)
18
- #
19
- module Greeting
20
- SIZE = 64
21
- SIGNATURE_START = 0xFF
22
- SIGNATURE_END = 0x7F
23
- VERSION_MAJOR = 3
24
- VERSION_MINOR = 1
25
- MECHANISM_OFFSET = 12
26
- MECHANISM_LENGTH = 20
27
- AS_SERVER_OFFSET = 32
28
-
29
- # Encodes a ZMTP 3.1 greeting.
30
- #
31
- # @param mechanism [String] security mechanism name (e.g. "NULL")
32
- # @param as_server [Boolean] whether this peer is the server
33
- # @return [String] 64-byte binary greeting
34
- #
35
- def self.encode(mechanism: "NULL", as_server: false)
36
- buf = "\xFF".b + ("\x00" * 8) + "\x7F".b
37
- buf << [VERSION_MAJOR, VERSION_MINOR].pack("CC")
38
- buf << mechanism.b.ljust(MECHANISM_LENGTH, "\x00")
39
- buf << (as_server ? "\x01" : "\x00")
40
- buf << ("\x00" * 31)
41
- end
42
-
43
- # Decodes a ZMTP greeting.
44
- #
45
- # @param data [String] 64-byte binary greeting
46
- # @return [Hash] { major:, minor:, mechanism:, as_server: }
47
- # @raise [ProtocolError] on invalid greeting
48
- #
49
- def self.decode(data)
50
- raise ProtocolError, "greeting too short (#{data.bytesize} bytes)" if data.bytesize < SIZE
51
-
52
- data = data.b
53
-
54
- unless data.getbyte(0) == SIGNATURE_START && data.getbyte(9) == SIGNATURE_END
55
- raise ProtocolError, "invalid greeting signature"
56
- end
57
-
58
- major = data.getbyte(10)
59
- minor = data.getbyte(11)
60
-
61
- unless major >= 3
62
- raise ProtocolError, "unsupported ZMTP version #{major}.#{minor} (need >= 3.0)"
63
- end
64
-
65
- mechanism = data.byteslice(MECHANISM_OFFSET, MECHANISM_LENGTH).delete("\x00")
66
- as_server = data.getbyte(AS_SERVER_OFFSET) == 1
67
-
68
- {
69
- major: major,
70
- minor: minor,
71
- mechanism: mechanism,
72
- as_server: as_server,
73
- }
74
- end
75
- end
76
- end
77
- end
78
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OMQ
4
- module ZMTP
5
- # ZMTP 3.1 wire protocol codec.
6
- #
7
- module Codec
8
- end
9
-
10
- # Raised on ZMTP protocol violations.
11
- #
12
- class ProtocolError < RuntimeError; end
13
- end
14
- end
15
-
16
- require_relative "codec/greeting"
17
- require_relative "codec/frame"
18
- require_relative "codec/command"