cztop 0.1.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +31 -0
- data/.yardopts +1 -0
- data/AUTHORS +1 -0
- data/CHANGES.md +3 -0
- data/Gemfile +10 -0
- data/Guardfile +61 -0
- data/LICENSE +5 -0
- data/Procfile +3 -0
- data/README.md +408 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/ci-scripts/install-deps +9 -0
- data/cztop.gemspec +36 -0
- data/examples/ruby_actor/actor.rb +100 -0
- data/examples/simple_req_rep/rep.rb +12 -0
- data/examples/simple_req_rep/req.rb +35 -0
- data/examples/taxi_system/.gitignore +2 -0
- data/examples/taxi_system/Makefile +2 -0
- data/examples/taxi_system/README.gsl +115 -0
- data/examples/taxi_system/README.md +276 -0
- data/examples/taxi_system/broker.rb +98 -0
- data/examples/taxi_system/client.rb +34 -0
- data/examples/taxi_system/generate_keys.rb +24 -0
- data/examples/taxi_system/start_broker.sh +2 -0
- data/examples/taxi_system/start_clients.sh +11 -0
- data/lib/cztop/actor.rb +308 -0
- data/lib/cztop/authenticator.rb +97 -0
- data/lib/cztop/beacon.rb +96 -0
- data/lib/cztop/certificate.rb +176 -0
- data/lib/cztop/config/comments.rb +66 -0
- data/lib/cztop/config/serialization.rb +82 -0
- data/lib/cztop/config/traversing.rb +157 -0
- data/lib/cztop/config.rb +119 -0
- data/lib/cztop/frame.rb +158 -0
- data/lib/cztop/has_ffi_delegate.rb +85 -0
- data/lib/cztop/message/frames.rb +74 -0
- data/lib/cztop/message.rb +191 -0
- data/lib/cztop/monitor.rb +102 -0
- data/lib/cztop/poller.rb +334 -0
- data/lib/cztop/polymorphic_zsock_methods.rb +24 -0
- data/lib/cztop/proxy.rb +149 -0
- data/lib/cztop/send_receive_methods.rb +35 -0
- data/lib/cztop/socket/types.rb +207 -0
- data/lib/cztop/socket.rb +106 -0
- data/lib/cztop/version.rb +3 -0
- data/lib/cztop/z85.rb +157 -0
- data/lib/cztop/zsock_options.rb +334 -0
- data/lib/cztop.rb +55 -0
- data/perf/README.md +79 -0
- data/perf/inproc_lat.rb +49 -0
- data/perf/inproc_thru.rb +42 -0
- data/perf/local_lat.rb +35 -0
- data/perf/remote_lat.rb +26 -0
- metadata +297 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module CZTop
|
2
|
+
|
3
|
+
# These are methods that can be used on a {Socket} as well as an {Actor}.
|
4
|
+
# @see http://api.zeromq.org/czmq3-0:zsock
|
5
|
+
module PolymorphicZsockMethods
|
6
|
+
# Sends a signal.
|
7
|
+
# @param status [Integer] signal (0-255)
|
8
|
+
def signal(status = 0)
|
9
|
+
::CZMQ::FFI::Zsock.signal(ffi_delegate, status)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Waits for a signal.
|
13
|
+
# @return [Integer] the received signal
|
14
|
+
def wait
|
15
|
+
::CZMQ::FFI::Zsock.wait(ffi_delegate)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set socket to use unbounded pipes (HWM=0); use this in cases when you are
|
19
|
+
# totally certain the message volume can fit in memory.
|
20
|
+
def set_unbounded
|
21
|
+
::CZMQ::FFI::Zsock.set_unbounded(ffi_delegate)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/cztop/proxy.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
module CZTop
|
2
|
+
# Steerable proxy which switches messages between a frontend and a backend
|
3
|
+
# socket.
|
4
|
+
#
|
5
|
+
# This is implemented using an {Actor}.
|
6
|
+
#
|
7
|
+
# @see http://api.zeromq.org/czmq3-0:zproxy
|
8
|
+
class Proxy
|
9
|
+
include ::CZMQ::FFI
|
10
|
+
|
11
|
+
# function pointer to the `zmonitor()` function
|
12
|
+
ZPROXY_FPTR = ::CZMQ::FFI.ffi_libraries.each do |dl|
|
13
|
+
fptr = dl.find_function("zproxy")
|
14
|
+
break fptr if fptr
|
15
|
+
end
|
16
|
+
raise LoadError, "couldn't find zproxy()" if ZPROXY_FPTR.nil?
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@actor = Actor.new(ZPROXY_FPTR)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Actor] the actor behind this proxy
|
23
|
+
attr_reader :actor
|
24
|
+
|
25
|
+
# Terminates the proxy.
|
26
|
+
# @return [void]
|
27
|
+
def terminate
|
28
|
+
@actor.terminate
|
29
|
+
end
|
30
|
+
|
31
|
+
# Enable verbose logging of commands and activity.
|
32
|
+
# @return [void]
|
33
|
+
def verbose!
|
34
|
+
@actor << "VERBOSE"
|
35
|
+
@actor.wait
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns a configurator object which you can use to configure the
|
39
|
+
# frontend socket.
|
40
|
+
# @return [Configurator] (memoized) frontend configurator
|
41
|
+
def frontend
|
42
|
+
@frontend ||= Configurator.new(self, :frontend)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a configurator object which you can use to configure the backend
|
46
|
+
# socket.
|
47
|
+
# @return [Configurator] (memoized) backend configurator
|
48
|
+
def backend
|
49
|
+
@backend ||= Configurator.new(self, :backend)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Captures all proxied messages and delivers them to a PULL socket bound
|
53
|
+
# to the specified endpoint.
|
54
|
+
# @note The PULL socket has to be bound before calling this method.
|
55
|
+
# @param endpoint [String] the endpoint to which the PULL socket is bound to
|
56
|
+
# @return [void]
|
57
|
+
def capture(endpoint)
|
58
|
+
@actor << ["CAPTURE", endpoint]
|
59
|
+
@actor.wait
|
60
|
+
end
|
61
|
+
|
62
|
+
# Pauses proxying of any messages.
|
63
|
+
# @note This causes any messages to be queued up and potentialy hit the
|
64
|
+
# high-water mark on the frontend or backend socket, causing messages to
|
65
|
+
# be dropped or writing applications to block.
|
66
|
+
# @return [void]
|
67
|
+
def pause
|
68
|
+
@actor << "PAUSE"
|
69
|
+
@actor.wait
|
70
|
+
end
|
71
|
+
|
72
|
+
# Resume proxying of messages.
|
73
|
+
# @note This is only needed after a call to {#pause}, not to start the
|
74
|
+
# proxy. Proxying starts as soon as the frontend and backend sockets are
|
75
|
+
# properly attached.
|
76
|
+
# @return [void]
|
77
|
+
def resume
|
78
|
+
@actor << "RESUME"
|
79
|
+
@actor.wait
|
80
|
+
end
|
81
|
+
|
82
|
+
# Used to configure the socket on one side of a {Proxy}.
|
83
|
+
class Configurator
|
84
|
+
# @return [Array<Symbol>] supported socket types
|
85
|
+
SOCKET_TYPES = %i[
|
86
|
+
PAIR PUB SUB REQ REP
|
87
|
+
DEALER ROUTER PULL PUSH
|
88
|
+
XPUB XSUB
|
89
|
+
]
|
90
|
+
|
91
|
+
# @param proxy [Proxy] the proxy instance
|
92
|
+
# @param side [Symbol] :frontend or :backend
|
93
|
+
def initialize(proxy, side)
|
94
|
+
@proxy = proxy
|
95
|
+
@side = case side
|
96
|
+
when :frontend then "FRONTEND"
|
97
|
+
when :backend then "BACKEND"
|
98
|
+
else raise ArgumentError, "invalid side: #{side.inspect}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Proxy] the proxy this {Configurator} works on
|
103
|
+
attr_reader :proxy
|
104
|
+
|
105
|
+
# @return [String] the side, either "FRONTEND" or "BACKEND"
|
106
|
+
attr_reader :side
|
107
|
+
|
108
|
+
# Creates and binds a serverish socket.
|
109
|
+
# @param socket_type [Symbol] one of {SOCKET_TYPES}
|
110
|
+
# @param endpoint [String] endpoint to bind to
|
111
|
+
# @raise [ArgumentError] if the given socket type is invalid
|
112
|
+
# @return [void]
|
113
|
+
def bind(socket_type, endpoint)
|
114
|
+
unless SOCKET_TYPES.include?(socket_type)
|
115
|
+
raise ArgumentError, "invalid socket type: #{socket_type}"
|
116
|
+
end
|
117
|
+
@proxy.actor << [ @side, socket_type.to_s, endpoint ]
|
118
|
+
@proxy.actor.wait
|
119
|
+
end
|
120
|
+
|
121
|
+
# Set ZAP domain for authentication.
|
122
|
+
# @param domain [String] the ZAP domain
|
123
|
+
def domain=(domain)
|
124
|
+
@proxy.actor << [ "DOMAIN", @side, domain ]
|
125
|
+
@proxy.actor.wait
|
126
|
+
end
|
127
|
+
|
128
|
+
# Configure PLAIN authentication on this socket.
|
129
|
+
# @note You'll have to use a {CZTop::Authenticator}.
|
130
|
+
def PLAIN_server!
|
131
|
+
@proxy.actor << [ "PLAIN", @side ]
|
132
|
+
@proxy.actor.wait
|
133
|
+
end
|
134
|
+
|
135
|
+
# Configure CURVE authentication on this socket.
|
136
|
+
# @note You'll have to use a {CZTop::Authenticator}.
|
137
|
+
# @param cert [Certificate] this server's certificate,
|
138
|
+
# so remote clients are able to authenticate this server
|
139
|
+
def CURVE_server!(cert)
|
140
|
+
public_key = cert.public_key
|
141
|
+
secret_key = cert.secret_key or
|
142
|
+
raise ArgumentError, "no secret key in certificate"
|
143
|
+
|
144
|
+
@proxy.actor << [ "CURVE", @side, public_key, secret_key ]
|
145
|
+
@proxy.actor.wait
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module CZTop
|
2
|
+
|
3
|
+
# These are methods that can be used on a {Socket} as well as an {Actor},
|
4
|
+
# but actually just pass through to methods of {Message} (which take
|
5
|
+
# a polymorphic reference, in Ruby as well as in C).
|
6
|
+
# @see http://api.zeromq.org/czmq3-0:zmsg
|
7
|
+
module SendReceiveMethods
|
8
|
+
# Sends a message.
|
9
|
+
#
|
10
|
+
# @param message [Message, String, Array<parts>] the message to send
|
11
|
+
# @raise [IO::EAGAINWaitWritable] if send timeout has been reached (see
|
12
|
+
# {ZsockOptions::OptionsAccessor#sndtimeo=})
|
13
|
+
# @raise [Interrupt, ArgumentError, SystemCallError] anything raised by
|
14
|
+
# {Message#send_to}
|
15
|
+
# @return [self]
|
16
|
+
# @see Message.coerce
|
17
|
+
# @see Message#send_to
|
18
|
+
def <<(message)
|
19
|
+
Message.coerce(message).send_to(self)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Receives a message.
|
24
|
+
#
|
25
|
+
# @return [Message]
|
26
|
+
# @raise [IO::EAGAINWaitReadable] if receive timeout has been reached (see
|
27
|
+
# {ZsockOptions::OptionsAccessor#rcvtimeo=})
|
28
|
+
# @raise [Interrupt, ArgumentError, SystemCallError] anything raised by
|
29
|
+
# {Message.receive_from}
|
30
|
+
# @see Message.receive_from
|
31
|
+
def receive
|
32
|
+
Message.receive_from(self)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
module CZTop
|
2
|
+
class Socket
|
3
|
+
# Socket types. Each constant in this namespace holds the type code used
|
4
|
+
# for the zsock_new() function.
|
5
|
+
module Types
|
6
|
+
PAIR = 0
|
7
|
+
PUB = 1
|
8
|
+
SUB = 2
|
9
|
+
REQ = 3
|
10
|
+
REP = 4
|
11
|
+
DEALER = 5
|
12
|
+
ROUTER = 6
|
13
|
+
PULL = 7
|
14
|
+
PUSH = 8
|
15
|
+
XPUB = 9
|
16
|
+
XSUB = 10
|
17
|
+
STREAM = 11
|
18
|
+
SERVER = 12
|
19
|
+
CLIENT = 13
|
20
|
+
end
|
21
|
+
|
22
|
+
# All the available type codes, mapped to their Symbol equivalent.
|
23
|
+
# @return [Hash<Integer, Symbol>]
|
24
|
+
TypeNames = Hash[
|
25
|
+
Types.constants.map { |name| i = Types.const_get(name); [ i, name ] }
|
26
|
+
].freeze
|
27
|
+
|
28
|
+
# @param type [Symbol, Integer] type from {Types} or like +:PUB+
|
29
|
+
# @return [REQ, REP, PUSH, PULL, ... ] the new socket
|
30
|
+
# @see Types
|
31
|
+
# @example Creating a socket by providing its type as a parameter
|
32
|
+
# my_sock = CZTop::Socket.new_by_type(:DEALER, "tcp://example.com:4000")
|
33
|
+
def self.new_by_type(type)
|
34
|
+
case type
|
35
|
+
when Integer
|
36
|
+
type_code = type
|
37
|
+
type_name = TypeNames[type_code] or
|
38
|
+
raise ArgumentError, "invalid type %p" % type
|
39
|
+
type_class = Socket.const_get(type_name)
|
40
|
+
when Symbol
|
41
|
+
type_code = Types.const_get(type)
|
42
|
+
type_class = Socket.const_get(type)
|
43
|
+
else
|
44
|
+
raise ArgumentError, "invalid socket type: %p" % type
|
45
|
+
end
|
46
|
+
ffi_delegate = Zsock.new(type_code)
|
47
|
+
sock = type_class.allocate
|
48
|
+
sock.attach_ffi_delegate(ffi_delegate)
|
49
|
+
sock
|
50
|
+
end
|
51
|
+
|
52
|
+
# Client socket for the ZeroMQ Client-Server Pattern.
|
53
|
+
# @see http://rfc.zeromq.org/spec:41
|
54
|
+
class CLIENT < Socket
|
55
|
+
# @param endpoints [String] endpoints to connect to
|
56
|
+
def initialize(endpoints = nil)
|
57
|
+
attach_ffi_delegate(Zsock.new_client(endpoints))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Server socket for the ZeroMQ Client-Server Pattern.
|
62
|
+
# @see http://rfc.zeromq.org/spec:41
|
63
|
+
class SERVER < Socket
|
64
|
+
# @param endpoints [String] endpoints to bind to
|
65
|
+
def initialize(endpoints = nil)
|
66
|
+
attach_ffi_delegate(Zsock.new_server(endpoints))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Request socket for the ZeroMQ Request-Reply Pattern.
|
71
|
+
# @see http://rfc.zeromq.org/spec:28
|
72
|
+
class REQ < Socket
|
73
|
+
# @param endpoints [String] endpoints to connect to
|
74
|
+
def initialize(endpoints = nil)
|
75
|
+
attach_ffi_delegate(Zsock.new_req(endpoints))
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Reply socket for the ZeroMQ Request-Reply Pattern.
|
80
|
+
# @see http://rfc.zeromq.org/spec:28
|
81
|
+
class REP < Socket
|
82
|
+
# @param endpoints [String] endpoints to bind to
|
83
|
+
def initialize(endpoints = nil)
|
84
|
+
attach_ffi_delegate(Zsock.new_rep(endpoints))
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Dealer socket for the ZeroMQ Request-Reply Pattern.
|
89
|
+
# @see http://rfc.zeromq.org/spec:28
|
90
|
+
class DEALER < Socket
|
91
|
+
# @param endpoints [String] endpoints to connect to
|
92
|
+
def initialize(endpoints = nil)
|
93
|
+
attach_ffi_delegate(Zsock.new_dealer(endpoints))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Router socket for the ZeroMQ Request-Reply Pattern.
|
98
|
+
# @see http://rfc.zeromq.org/spec:28
|
99
|
+
class ROUTER < Socket
|
100
|
+
# @param endpoints [String] endpoints to bind to
|
101
|
+
def initialize(endpoints = nil)
|
102
|
+
attach_ffi_delegate(Zsock.new_router(endpoints))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Send a message to a specific receiver. This is a shorthand for when
|
106
|
+
# you send a message to a specific receiver with no hops in between.
|
107
|
+
# @param receiver [String] receiving peer's socket identity
|
108
|
+
# @param message [Message] the message to send
|
109
|
+
# @note Do NOT use the message afterwards. It'll have been modified and
|
110
|
+
# destroyed.
|
111
|
+
def send_to(receiver, message)
|
112
|
+
message = Message.coerce(message)
|
113
|
+
message.prepend "" # separator frame
|
114
|
+
message.prepend receiver # receiver envelope
|
115
|
+
self << message
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Publish socket for the ZeroMQ Publish-Subscribe Pattern.
|
120
|
+
# @see http://rfc.zeromq.org/spec:29
|
121
|
+
class PUB < Socket
|
122
|
+
# @param endpoints [String] endpoints to bind to
|
123
|
+
def initialize(endpoints = nil)
|
124
|
+
attach_ffi_delegate(Zsock.new_pub(endpoints))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Subscribe socket for the ZeroMQ Publish-Subscribe Pattern.
|
129
|
+
# @see http://rfc.zeromq.org/spec:29
|
130
|
+
class SUB < Socket
|
131
|
+
# @param endpoints [String] endpoints to connect to
|
132
|
+
# @param subscription [String] what to subscribe to
|
133
|
+
def initialize(endpoints = nil, subscription = nil)
|
134
|
+
attach_ffi_delegate(Zsock.new_sub(endpoints, subscription))
|
135
|
+
end
|
136
|
+
|
137
|
+
# Subscribes to the given prefix string.
|
138
|
+
# @param prefix [String] prefix string to subscribe to
|
139
|
+
# @return [void]
|
140
|
+
def subscribe(prefix)
|
141
|
+
ffi_delegate.set_subscribe(prefix)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Unsubscribes from the given prefix.
|
145
|
+
# @param prefix [String] prefix string to unsubscribe from
|
146
|
+
# @return [void]
|
147
|
+
def unsubscribe(prefix)
|
148
|
+
ffi_delegate.set_unsubscribe(prefix)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Extended publish socket for the ZeroMQ Publish-Subscribe Pattern.
|
153
|
+
# @see http://rfc.zeromq.org/spec:29
|
154
|
+
class XPUB < Socket
|
155
|
+
# @param endpoints [String] endpoints to bind to
|
156
|
+
def initialize(endpoints = nil)
|
157
|
+
attach_ffi_delegate(Zsock.new_xpub(endpoints))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Extended subscribe socket for the ZeroMQ Publish-Subscribe Pattern.
|
162
|
+
# @see http://rfc.zeromq.org/spec:29
|
163
|
+
class XSUB < Socket
|
164
|
+
# @param endpoints [String] endpoints to connect to
|
165
|
+
def initialize(endpoints = nil)
|
166
|
+
attach_ffi_delegate(Zsock.new_xsub(endpoints))
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Push socket for the ZeroMQ Pipeline Pattern.
|
171
|
+
# @see http://rfc.zeromq.org/spec:30
|
172
|
+
class PUSH < Socket
|
173
|
+
# @param endpoints [String] endpoints to connect to
|
174
|
+
def initialize(endpoints = nil)
|
175
|
+
attach_ffi_delegate(Zsock.new_push(endpoints))
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Pull socket for the ZeroMQ Pipeline Pattern.
|
180
|
+
# @see http://rfc.zeromq.org/spec:30
|
181
|
+
class PULL < Socket
|
182
|
+
# @param endpoints [String] endpoints to bind to
|
183
|
+
def initialize(endpoints = nil)
|
184
|
+
attach_ffi_delegate(Zsock.new_pull(endpoints))
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Pair socket for inter-thread communication.
|
189
|
+
# @see http://rfc.zeromq.org/spec:31
|
190
|
+
class PAIR < Socket
|
191
|
+
# @param endpoints [String] endpoints to connect to
|
192
|
+
def initialize(endpoints = nil)
|
193
|
+
attach_ffi_delegate(Zsock.new_pair(endpoints))
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Stream socket for the native pattern over. This is useful when
|
198
|
+
# communicating with a non-ZMQ peer, done over TCP.
|
199
|
+
# @see http://api.zeromq.org/4-2:zmq-socket#toc16
|
200
|
+
class STREAM < Socket
|
201
|
+
# @param endpoints [String] endpoints to connect to
|
202
|
+
def initialize(endpoints = nil)
|
203
|
+
attach_ffi_delegate(Zsock.new_stream(endpoints))
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
data/lib/cztop/socket.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
module CZTop
|
2
|
+
# Represents a CZMQ::FFI::Zsock.
|
3
|
+
class Socket
|
4
|
+
include HasFFIDelegate
|
5
|
+
extend CZTop::HasFFIDelegate::ClassMethods
|
6
|
+
include ZsockOptions
|
7
|
+
include SendReceiveMethods
|
8
|
+
include PolymorphicZsockMethods
|
9
|
+
include CZMQ::FFI
|
10
|
+
|
11
|
+
# @!group CURVE Security
|
12
|
+
|
13
|
+
# Enables CURVE security and makes this socket a CURVE server.
|
14
|
+
# @param cert [Certificate] this server's certificate,
|
15
|
+
# so remote clients are able to authenticate this server
|
16
|
+
# @note You'll have to use a {CZTop::Authenticator}.
|
17
|
+
# @return [void]
|
18
|
+
def CURVE_server!(cert)
|
19
|
+
options.CURVE_server = true
|
20
|
+
cert.apply(self) # NOTE: desired: raises if no secret key in cert
|
21
|
+
end
|
22
|
+
|
23
|
+
# Enables CURVE security and makes this socket a CURVE client.
|
24
|
+
# @param client_cert [Certificate] client's certificate, to secure
|
25
|
+
# communication (and be authenticated by the server)
|
26
|
+
# @param server_cert [Certificate] the remote server's certificate, so
|
27
|
+
# this socket is able to authenticate the server
|
28
|
+
# @return [void]
|
29
|
+
# @raise [SecurityError] if the server's secret key is set in server_cert,
|
30
|
+
# which means it's not secret anymore
|
31
|
+
# @raise [SystemCallError] if there's no secret key in client_cert
|
32
|
+
def CURVE_client!(client_cert, server_cert)
|
33
|
+
if server_cert.secret_key
|
34
|
+
raise SecurityError, "server's secret key not secret"
|
35
|
+
end
|
36
|
+
|
37
|
+
client_cert.apply(self) # NOTE: desired: raises if no secret key in cert
|
38
|
+
options.CURVE_serverkey = server_cert.public_key
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!endgroup
|
42
|
+
|
43
|
+
# @return [String] last bound endpoint, if any
|
44
|
+
def last_endpoint
|
45
|
+
ffi_delegate.endpoint
|
46
|
+
end
|
47
|
+
|
48
|
+
# Connects to an endpoint.
|
49
|
+
# @param endpoint [String]
|
50
|
+
# @return [void]
|
51
|
+
# @raise [ArgumentError] if the endpoint is incorrect
|
52
|
+
def connect(endpoint)
|
53
|
+
rc = ffi_delegate.connect("%s", :string, endpoint)
|
54
|
+
raise ArgumentError, "incorrect endpoint: %p" % endpoint if rc == -1
|
55
|
+
end
|
56
|
+
|
57
|
+
# Disconnects from an endpoint.
|
58
|
+
# @param endpoint [String]
|
59
|
+
# @raise [ArgumentError] if the endpoint is incorrect
|
60
|
+
def disconnect(endpoint)
|
61
|
+
rc = ffi_delegate.disconnect("%s", :string, endpoint)
|
62
|
+
raise ArgumentError, "incorrect endpoint: %p" % endpoint if rc == -1
|
63
|
+
end
|
64
|
+
|
65
|
+
# Closes and destroys the native socket.
|
66
|
+
# @note Don't try to use it anymore afterwards.
|
67
|
+
def close
|
68
|
+
ffi_delegate.destroy
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Integer] last automatically selected, bound TCP port, if any
|
72
|
+
# @return [nil] if not bound to a TCP port yet
|
73
|
+
attr_reader :last_tcp_port
|
74
|
+
|
75
|
+
# Binds to an endpoint.
|
76
|
+
# @note When binding to an automatically selected TCP port, this will set
|
77
|
+
# {#last_tcp_port}.
|
78
|
+
# @param endpoint [String]
|
79
|
+
# @return [void]
|
80
|
+
# @raise [SystemCallError] in case of failure
|
81
|
+
def bind(endpoint)
|
82
|
+
rc = ffi_delegate.bind("%s", :string, endpoint)
|
83
|
+
raise_zmq_err("unable to bind to %p" % endpoint) if rc == -1
|
84
|
+
@last_tcp_port = rc if rc > 0
|
85
|
+
end
|
86
|
+
|
87
|
+
# Unbinds from an endpoint.
|
88
|
+
# @param endpoint [String]
|
89
|
+
# @return [void]
|
90
|
+
# @raise [ArgumentError] if the endpoint is incorrect
|
91
|
+
def unbind(endpoint)
|
92
|
+
rc = ffi_delegate.unbind("%s", :string, endpoint)
|
93
|
+
raise ArgumentError, "incorrect endpoint: %p" % endpoint if rc == -1
|
94
|
+
end
|
95
|
+
|
96
|
+
# Inspects this {Socket}.
|
97
|
+
# @return [String] shows class, native address, and {#last_endpoint}
|
98
|
+
def inspect
|
99
|
+
"#<%s:0x%x last_endpoint=%p>" % [
|
100
|
+
self.class,
|
101
|
+
to_ptr.address,
|
102
|
+
last_endpoint
|
103
|
+
]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/cztop/z85.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
module CZTop
|
2
|
+
# Represents a CZMQ::FFI::Zarmour in Z85 mode.
|
3
|
+
#
|
4
|
+
# Use this class to encode to and from the Z85 encoding algorithm.
|
5
|
+
# @see http://rfc.zeromq.org/spec:32
|
6
|
+
class Z85
|
7
|
+
include HasFFIDelegate
|
8
|
+
extend CZTop::HasFFIDelegate::ClassMethods
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
attach_ffi_delegate(CZMQ::FFI::Zarmour.new)
|
12
|
+
ffi_delegate.set_mode(:mode_z85)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Encodes to Z85.
|
16
|
+
# @param input [String] possibly binary input data
|
17
|
+
# @return [String] Z85 encoded data as ASCII string
|
18
|
+
# @raise [ArgumentError] if input length isn't divisible by 4 with no
|
19
|
+
# remainder
|
20
|
+
# @raise [SystemCallError] if this fails
|
21
|
+
def encode(input)
|
22
|
+
raise ArgumentError, "wrong input length" if input.bytesize % 4 > 0
|
23
|
+
input = input.dup.force_encoding(Encoding::BINARY)
|
24
|
+
ptr = ffi_delegate.encode(input, input.bytesize)
|
25
|
+
raise_zmq_err if ptr.null?
|
26
|
+
z85 = ptr.read_string
|
27
|
+
z85.encode!(Encoding::ASCII)
|
28
|
+
return z85
|
29
|
+
end
|
30
|
+
|
31
|
+
# Decodes from Z85.
|
32
|
+
# @param input [String] Z85 encoded data
|
33
|
+
# @return [String] original data as binary string
|
34
|
+
# @raise [ArgumentError] if input length isn't divisible by 5 with no
|
35
|
+
# remainder
|
36
|
+
# @raise [SystemCallError] if this fails
|
37
|
+
def decode(input)
|
38
|
+
raise ArgumentError, "wrong input length" if input.bytesize % 5 > 0
|
39
|
+
FFI::MemoryPointer.new(:size_t) do |size_ptr|
|
40
|
+
buffer_ptr = ffi_delegate.decode(input, size_ptr)
|
41
|
+
raise_zmq_err if buffer_ptr.null?
|
42
|
+
decoded_string = buffer_ptr.read_string(_size(size_ptr) - 1)
|
43
|
+
return decoded_string
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Gets correct size, depending on the platform.
|
50
|
+
# @return [Integer]
|
51
|
+
# @see https://github.com/ffi/ffi/issues/398
|
52
|
+
# @see https://github.com/ffi/ffi/issues/333
|
53
|
+
def _size(size_ptr)
|
54
|
+
if RUBY_ENGINE == "jruby"
|
55
|
+
# NOTE: JRuby FFI doesn't have #read_uint64, nor does it have
|
56
|
+
# Pointer::SIZE
|
57
|
+
return size_ptr.read_ulong_long
|
58
|
+
end
|
59
|
+
|
60
|
+
if ::FFI::Pointer::SIZE == 8 # 64 bit
|
61
|
+
size_ptr.read_uint64
|
62
|
+
else
|
63
|
+
size_ptr.read_uint32
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Z85 with simple padding. This allows you to {#encode} input of any
|
68
|
+
# length.
|
69
|
+
#
|
70
|
+
# = Encoding Procedure
|
71
|
+
#
|
72
|
+
# If the data to be encoded is empty (0 bytes), it is encoded to the empty
|
73
|
+
# string, just like in Z85.
|
74
|
+
#
|
75
|
+
# Otherwise, a length information is prepended and, if needed, padding (1,
|
76
|
+
# 2, or 3 NULL bytes) is appended to bring the resulting blob to
|
77
|
+
# a multiple of 4 bytes.
|
78
|
+
#
|
79
|
+
# The length information is encoded similarly to lengths of messages
|
80
|
+
# (frames) in ZMTP. Up to 127 bytes, the data's length is encoded with
|
81
|
+
# a single byte (specifically, with the 7 least significant bits in it).
|
82
|
+
#
|
83
|
+
# +--------+-------------------------------+------------+
|
84
|
+
# | length | data | padding |
|
85
|
+
# | 1 byte | up to 127 bytes | 0-3 bytes |
|
86
|
+
# +--------+-------------------------------+------------+
|
87
|
+
#
|
88
|
+
# If the data is 128 bytes or more, the most significant bit will be set
|
89
|
+
# to indicate that fact, and a 64 bit unsigned integer in network byte
|
90
|
+
# order is appended after this first byte to encode the length of the
|
91
|
+
# data. This means that up to 16EiB (exbibytes) can be encoded, which
|
92
|
+
# will be enough for the foreseeable future.
|
93
|
+
#
|
94
|
+
# +--------+-----------+----------------------------------+------------+
|
95
|
+
# | big? | length | data | padding |
|
96
|
+
# | 1 byte | 8 bytes | 128 bytes or much more | 0-3 bytes |
|
97
|
+
# +--------+-----------+----------------------------------+------------+
|
98
|
+
#
|
99
|
+
# The resulting blob is encoded using {CZTop::Z85#encode}.
|
100
|
+
# {CZTop::Z85#decode} does the inverse.
|
101
|
+
#
|
102
|
+
# @note Warning: This won't be compatible with other implementations of
|
103
|
+
# Z85. Only use this if you really need padding, like when you can't
|
104
|
+
# guarantee the input for {#encode} is always a multiple of 4 bytes.
|
105
|
+
#
|
106
|
+
class Padded < Z85
|
107
|
+
# Encododes to Z85, with padding if needed.
|
108
|
+
#
|
109
|
+
# If input isn't empty, 8 additional bytes for the encoded length will
|
110
|
+
# be prepended. If needed, 1 to 3 bytes of padding will be appended.
|
111
|
+
#
|
112
|
+
# If input is empty, returns the empty string.
|
113
|
+
#
|
114
|
+
# @param input [String] possibly binary input data
|
115
|
+
# @return [String] Z85 encoded data as ASCII string, including encoded
|
116
|
+
# length and padding
|
117
|
+
# @raise [SystemCallError] if this fails
|
118
|
+
def encode(input)
|
119
|
+
return super if input.empty?
|
120
|
+
length = input.bytesize
|
121
|
+
if length < 1<<7 # up to 127 bytes
|
122
|
+
encoded_length = [length].pack("C")
|
123
|
+
|
124
|
+
else # larger input
|
125
|
+
low = length & 0xFFFFFFFF
|
126
|
+
high = (length >> 32) & 0xFFFFFFFF
|
127
|
+
encoded_length = [ 1<<7, high, low ].pack("CNN")
|
128
|
+
end
|
129
|
+
padding = "\0" * ((4 - ((length+1) % 4)) % 4)
|
130
|
+
super("#{encoded_length}#{input}#{padding}")
|
131
|
+
end
|
132
|
+
|
133
|
+
# Decodes from Z85 with padding.
|
134
|
+
#
|
135
|
+
# @param input [String] Z85 encoded data (including encoded length and
|
136
|
+
# padding, or empty string)
|
137
|
+
# @return [String] original data as binary string
|
138
|
+
# @raise [ArgumentError] if input is invalid or truncated
|
139
|
+
# @raise [SystemCallError] if this fails
|
140
|
+
def decode(input)
|
141
|
+
return super if input.empty?
|
142
|
+
decoded = super
|
143
|
+
length = decoded.byteslice(0, 1).unpack("C")[0]
|
144
|
+
if (1<<7 & length).zero? # up to 127 bytes
|
145
|
+
decoded = decoded.byteslice(1, length) # extract payload
|
146
|
+
|
147
|
+
else # larger input
|
148
|
+
length = decoded.byteslice(1, 8).unpack("NN")
|
149
|
+
.inject(0) { |sum, i| (sum << 32) + i }
|
150
|
+
decoded = decoded.byteslice(9, length) # extract payload
|
151
|
+
end
|
152
|
+
raise ArgumentError, "input truncated" if decoded.bytesize < length
|
153
|
+
return decoded
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|