cztop 0.14.1 → 1.1.0.pre1
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/.github/workflows/coverage.yml +20 -0
- data/.github/workflows/draft_api.yml +27 -0
- data/.github/workflows/stable_api.yml +26 -0
- data/.rubocop.yml +175 -0
- data/CHANGES.md +9 -4
- data/Gemfile +3 -7
- data/README.md +19 -58
- data/ci/install-libczmq +22 -0
- data/ci/install-libzmq +22 -0
- data/cztop.gemspec +12 -13
- data/lib/cztop/actor.rb +55 -26
- data/lib/cztop/authenticator.rb +18 -9
- data/lib/cztop/beacon.rb +22 -10
- data/lib/cztop/cert_store.rb +8 -2
- data/lib/cztop/certificate.rb +47 -18
- data/lib/cztop/config/comments.rb +14 -3
- data/lib/cztop/config/serialization.rb +25 -5
- data/lib/cztop/config/traversing.rb +44 -13
- data/lib/cztop/config.rb +23 -9
- data/lib/cztop/frame.rb +23 -10
- data/lib/cztop/has_ffi_delegate.rb +11 -1
- data/lib/cztop/message/frames.rb +16 -2
- data/lib/cztop/message.rb +36 -22
- data/lib/cztop/metadata.rb +35 -24
- data/lib/cztop/monitor.rb +14 -5
- data/lib/cztop/poller/aggregated.rb +31 -15
- data/lib/cztop/poller/zmq.rb +25 -22
- data/lib/cztop/poller/zpoller.rb +18 -6
- data/lib/cztop/poller.rb +43 -18
- data/lib/cztop/polymorphic_zsock_methods.rb +6 -1
- data/lib/cztop/proxy.rb +34 -19
- data/lib/cztop/send_receive_methods.rb +5 -1
- data/lib/cztop/socket/types.rb +128 -22
- data/lib/cztop/socket.rb +23 -18
- data/lib/cztop/version.rb +5 -1
- data/lib/cztop/z85/padded.rb +12 -3
- data/lib/cztop/z85/pipe.rb +40 -17
- data/lib/cztop/z85.rb +17 -6
- data/lib/cztop/zap.rb +57 -32
- data/lib/cztop/zsock_options.rb +155 -122
- data/lib/cztop.rb +2 -1
- metadata +25 -90
- data/.gitlab-ci.yml +0 -32
- data/Guardfile +0 -61
- data/Procfile +0 -3
- data/ci-scripts/install-deps +0 -8
data/lib/cztop/actor.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CZTop
|
2
4
|
# Represents a CZMQ::FFI::Zactor.
|
3
5
|
#
|
@@ -33,6 +35,7 @@ module CZTop
|
|
33
35
|
#
|
34
36
|
# @see http://api.zeromq.org/czmq3-0:zactor
|
35
37
|
class Actor
|
38
|
+
|
36
39
|
include HasFFIDelegate
|
37
40
|
extend CZTop::HasFFIDelegate::ClassMethods
|
38
41
|
include ZsockOptions
|
@@ -40,12 +43,15 @@ module CZTop
|
|
40
43
|
include PolymorphicZsockMethods
|
41
44
|
include ::CZMQ::FFI
|
42
45
|
|
46
|
+
|
43
47
|
# Raised when trying to interact with a terminated actor.
|
44
48
|
class DeadActorError < RuntimeError; end
|
45
49
|
|
50
|
+
|
46
51
|
# @return [Exception] the exception that crashed this actor, if any
|
47
52
|
attr_reader :exception
|
48
53
|
|
54
|
+
|
49
55
|
# Creates a new actor. Either pass a callback directly or a block. The
|
50
56
|
# block will be called for every received message.
|
51
57
|
#
|
@@ -60,15 +66,16 @@ module CZTop
|
|
60
66
|
# @yieldparam pipe [Socket::PAIR]
|
61
67
|
# @see #process_messages
|
62
68
|
def initialize(callback = nil, c_args = nil, &handler)
|
63
|
-
@running
|
64
|
-
@mtx
|
65
|
-
@callback
|
66
|
-
@callback
|
67
|
-
ffi_delegate
|
69
|
+
@running = true
|
70
|
+
@mtx = Mutex.new
|
71
|
+
@callback = callback || handler
|
72
|
+
@callback = shim(@callback) unless @callback.is_a? ::FFI::Pointer
|
73
|
+
ffi_delegate = Zactor.new(@callback, c_args)
|
68
74
|
attach_ffi_delegate(ffi_delegate)
|
69
|
-
options.sndtimeo = 20#ms # see #<<
|
75
|
+
options.sndtimeo = 20 # ms # see #<<
|
70
76
|
end
|
71
77
|
|
78
|
+
|
72
79
|
# Send a message to the actor.
|
73
80
|
# @param message [Object] message to send to the actor, see {Message.coerce}
|
74
81
|
# @return [self] so it's chainable
|
@@ -88,7 +95,8 @@ module CZTop
|
|
88
95
|
else
|
89
96
|
begin
|
90
97
|
@mtx.synchronize do
|
91
|
-
raise DeadActorError
|
98
|
+
raise DeadActorError unless @running
|
99
|
+
|
92
100
|
message.send_to(self)
|
93
101
|
end
|
94
102
|
rescue IO::EAGAINWaitWritable
|
@@ -109,19 +117,23 @@ module CZTop
|
|
109
117
|
retry
|
110
118
|
end
|
111
119
|
end
|
120
|
+
|
112
121
|
self
|
113
122
|
end
|
114
123
|
|
124
|
+
|
115
125
|
# Receive a message from the actor.
|
116
126
|
# @return [Message]
|
117
127
|
# @raise [DeadActorError] if actor is terminated
|
118
128
|
def receive
|
119
129
|
@mtx.synchronize do
|
120
|
-
raise DeadActorError
|
130
|
+
raise DeadActorError unless @running
|
131
|
+
|
121
132
|
super
|
122
133
|
end
|
123
134
|
end
|
124
135
|
|
136
|
+
|
125
137
|
# Same as {#<<}, but also waits for a response from the actor and returns
|
126
138
|
# it.
|
127
139
|
# @param message [Message] the request to the actor
|
@@ -129,9 +141,11 @@ module CZTop
|
|
129
141
|
# @raise [ArgumentError] if the message is "$TERM" (use {#terminate})
|
130
142
|
def request(message)
|
131
143
|
@mtx.synchronize do
|
132
|
-
raise DeadActorError
|
144
|
+
raise DeadActorError unless @running
|
145
|
+
|
133
146
|
message = Message.coerce(message)
|
134
|
-
raise ArgumentError,
|
147
|
+
raise ArgumentError, 'use #terminate' if TERM == message[0]
|
148
|
+
|
135
149
|
message.send_to(self)
|
136
150
|
Message.receive_from(self)
|
137
151
|
end
|
@@ -140,6 +154,7 @@ module CZTop
|
|
140
154
|
retry
|
141
155
|
end
|
142
156
|
|
157
|
+
|
143
158
|
# Sends a message according to a "picture".
|
144
159
|
# @see zsock_send() on http://api.zeromq.org/czmq3-0:zsock
|
145
160
|
# @note Mainly added for {Beacon}. If implemented there, it wouldn't be
|
@@ -151,11 +166,13 @@ module CZTop
|
|
151
166
|
# @return [void]
|
152
167
|
def send_picture(picture, *args)
|
153
168
|
@mtx.synchronize do
|
154
|
-
raise DeadActorError
|
169
|
+
raise DeadActorError unless @running
|
170
|
+
|
155
171
|
Zsock.send(ffi_delegate, picture, *args)
|
156
172
|
end
|
157
173
|
end
|
158
174
|
|
175
|
+
|
159
176
|
# Thread-safe {PolymorphicZsockMethods#wait}.
|
160
177
|
# @return [Integer]
|
161
178
|
def wait
|
@@ -164,12 +181,14 @@ module CZTop
|
|
164
181
|
end
|
165
182
|
end
|
166
183
|
|
184
|
+
|
167
185
|
# Tells the actor to terminate and waits for it. Idempotent.
|
168
186
|
# @return [Boolean] whether it died just now (+false+ if it was dead
|
169
187
|
# already)
|
170
188
|
def terminate
|
171
189
|
@mtx.synchronize do
|
172
|
-
return false
|
190
|
+
return false unless @running
|
191
|
+
|
173
192
|
Message.new(TERM).send_to(self)
|
174
193
|
await_handler_death
|
175
194
|
true
|
@@ -179,19 +198,23 @@ module CZTop
|
|
179
198
|
retry
|
180
199
|
end
|
181
200
|
|
201
|
+
|
182
202
|
# @return [Boolean] whether this actor is dead (terminated or crashed)
|
183
203
|
def dead?
|
184
204
|
!@running
|
185
205
|
end
|
186
206
|
|
207
|
+
|
187
208
|
# @return [Boolean] whether this actor has crashed
|
188
209
|
# @see #exception
|
189
210
|
def crashed?
|
190
211
|
!!@exception # if set, it has crashed
|
191
212
|
end
|
192
213
|
|
214
|
+
|
193
215
|
private
|
194
216
|
|
217
|
+
|
195
218
|
# Shims the given handler. The shim is used to do the handshake, to
|
196
219
|
# {#process_messages}, and ensure we're notified when the handler has
|
197
220
|
# terminated.
|
@@ -200,35 +223,37 @@ module CZTop
|
|
200
223
|
# @return [FFI::Function] the callback function to be passed to the zactor
|
201
224
|
# @raise [ArgumentError] if invalid handler given
|
202
225
|
def shim(handler)
|
203
|
-
raise ArgumentError,
|
226
|
+
raise ArgumentError, 'invalid handler' unless handler.respond_to?(:call)
|
204
227
|
|
205
|
-
@handler_thread
|
228
|
+
@handler_thread = nil
|
206
229
|
@handler_dead_signal = Queue.new # used for signaling
|
207
230
|
|
208
231
|
Zactor.fn do |pipe_delegate, _args|
|
209
|
-
|
210
|
-
@
|
211
|
-
|
212
|
-
|
213
|
-
@pipe.signal # handshake, so zactor_new() returns
|
214
|
-
end
|
215
|
-
process_messages(handler)
|
216
|
-
rescue Exception
|
217
|
-
@exception = $!
|
218
|
-
ensure
|
219
|
-
signal_shimmed_handler_death
|
232
|
+
@mtx.synchronize do
|
233
|
+
@handler_thread = Thread.current
|
234
|
+
@pipe = Socket::PAIR.from_ffi_delegate(pipe_delegate)
|
235
|
+
@pipe.signal # handshake, so zactor_new() returns
|
220
236
|
end
|
237
|
+
|
238
|
+
process_messages(handler)
|
239
|
+
rescue Exception
|
240
|
+
@exception = $ERROR_INFO
|
241
|
+
ensure
|
242
|
+
signal_shimmed_handler_death
|
221
243
|
end
|
222
244
|
end
|
223
245
|
|
246
|
+
|
224
247
|
# @return [Boolean] whether the handler is a Ruby object, like a simple
|
225
248
|
# block (as opposed to a FFI::Pointer to a C function)
|
226
249
|
def handler_shimmed?
|
227
250
|
!!@handler_thread # if it exists, it's shimmed
|
228
251
|
end
|
229
252
|
|
253
|
+
|
230
254
|
# the command which causes an actor handler to terminate
|
231
|
-
TERM =
|
255
|
+
TERM = '$TERM'
|
256
|
+
|
232
257
|
|
233
258
|
# Successively receive messages that were sent to the actor and
|
234
259
|
# yield them to the given handler to process them. The a pipe (a
|
@@ -256,12 +281,14 @@ module CZTop
|
|
256
281
|
end
|
257
282
|
end
|
258
283
|
|
284
|
+
|
259
285
|
# Receives the next message even across any interrupts.
|
260
286
|
# @return [Message] the next message
|
261
287
|
def next_message
|
262
288
|
@pipe.receive
|
263
289
|
end
|
264
290
|
|
291
|
+
|
265
292
|
# Creates a new thread that will signal the definitive termination of the
|
266
293
|
# Ruby handler.
|
267
294
|
#
|
@@ -286,6 +313,7 @@ module CZTop
|
|
286
313
|
end
|
287
314
|
end
|
288
315
|
|
316
|
+
|
289
317
|
# Waits for the C or Ruby handler to die.
|
290
318
|
# @return [void]
|
291
319
|
def await_handler_death
|
@@ -303,5 +331,6 @@ module CZTop
|
|
303
331
|
@running = false
|
304
332
|
end
|
305
333
|
end
|
334
|
+
|
306
335
|
end
|
307
336
|
end
|
data/lib/cztop/authenticator.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module CZTop
|
3
4
|
# Authentication for ZeroMQ security mechanisms.
|
4
5
|
#
|
5
6
|
# This is implemented using an {Actor}.
|
6
7
|
#
|
7
8
|
# @see http://api.zeromq.org/czmq3-0:zauth
|
8
9
|
class Authenticator
|
10
|
+
|
9
11
|
include ::CZMQ::FFI
|
10
12
|
|
11
13
|
# function pointer to the +zauth()+ function
|
12
14
|
ZAUTH_FPTR = ::CZMQ::FFI.ffi_libraries.each do |dl|
|
13
|
-
fptr = dl.find_function(
|
15
|
+
fptr = dl.find_function('zauth')
|
14
16
|
break fptr if fptr
|
15
17
|
end
|
16
18
|
raise LoadError, "couldn't find zauth()" if ZAUTH_FPTR.nil?
|
@@ -23,6 +25,7 @@ module CZTop
|
|
23
25
|
def initialize(cert_store = nil)
|
24
26
|
if cert_store
|
25
27
|
raise ArgumentError unless cert_store.is_a?(CertStore)
|
28
|
+
|
26
29
|
cert_store = cert_store.ffi_delegate
|
27
30
|
cert_store.__undef_finalizer # native object is now owned by zauth() actor
|
28
31
|
end
|
@@ -38,13 +41,15 @@ module CZTop
|
|
38
41
|
@actor.terminate
|
39
42
|
end
|
40
43
|
|
44
|
+
|
41
45
|
# Enable verbose logging of commands and activity.
|
42
46
|
# @return [void]
|
43
47
|
def verbose!
|
44
|
-
@actor <<
|
48
|
+
@actor << 'VERBOSE'
|
45
49
|
@actor.wait
|
46
50
|
end
|
47
51
|
|
52
|
+
|
48
53
|
# Add a list of IP addresses to the whitelist. For _NULL_, all clients
|
49
54
|
# from these addresses will be accepted. For _PLAIN_ and _CURVE_, they
|
50
55
|
# will be allowed to continue with authentication.
|
@@ -52,10 +57,11 @@ module CZTop
|
|
52
57
|
# @param addrs [String] IP address(es) to allow
|
53
58
|
# @return [void]
|
54
59
|
def allow(*addrs)
|
55
|
-
@actor << [
|
60
|
+
@actor << ['ALLOW', *addrs]
|
56
61
|
@actor.wait
|
57
62
|
end
|
58
63
|
|
64
|
+
|
59
65
|
# Add a list of IP addresses to the blacklist. For all security
|
60
66
|
# mechanisms, this rejects the connection without any further
|
61
67
|
# authentication. Use either a whitelist, or a blacklist, not not both. If
|
@@ -65,22 +71,23 @@ module CZTop
|
|
65
71
|
# @param addrs [String] IP address(es) to deny
|
66
72
|
# @return [void]
|
67
73
|
def deny(*addrs)
|
68
|
-
@actor << [
|
74
|
+
@actor << ['DENY', *addrs]
|
69
75
|
@actor.wait
|
70
76
|
end
|
71
77
|
|
78
|
+
|
72
79
|
# Configure PLAIN security mechanism using a plain-text password file. The
|
73
80
|
# password file will be reloaded automatically if modified externally.
|
74
81
|
#
|
75
82
|
# @param filename [String] path to the password file
|
76
83
|
# @return [void]
|
77
84
|
def plain(filename)
|
78
|
-
@actor << [
|
85
|
+
@actor << ['PLAIN', *filename]
|
79
86
|
@actor.wait
|
80
87
|
end
|
81
88
|
|
82
89
|
# used to allow any CURVE client
|
83
|
-
ALLOW_ANY =
|
90
|
+
ALLOW_ANY = '*'
|
84
91
|
|
85
92
|
# Configure CURVE authentication, using a directory that holds all public
|
86
93
|
# client certificates, i.e. their public keys. The certificates must have been
|
@@ -90,15 +97,17 @@ module CZTop
|
|
90
97
|
# @param directory [String] the directory to take the keys from
|
91
98
|
# @return [void]
|
92
99
|
def curve(directory = ALLOW_ANY)
|
93
|
-
@actor << [
|
100
|
+
@actor << ['CURVE', directory]
|
94
101
|
@actor.wait
|
95
102
|
end
|
96
103
|
|
104
|
+
|
97
105
|
# Configure GSSAPI authentication.
|
98
106
|
# @return [void]
|
99
107
|
def gssapi
|
100
|
-
@actor <<
|
108
|
+
@actor << 'GSSAPI'
|
101
109
|
@actor.wait
|
102
110
|
end
|
111
|
+
|
103
112
|
end
|
104
113
|
end
|
data/lib/cztop/beacon.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CZTop
|
2
4
|
# Used for LAN discovery and presence.
|
3
5
|
#
|
@@ -5,11 +7,12 @@ module CZTop
|
|
5
7
|
#
|
6
8
|
# @see http://api.zeromq.org/czmq3-0:zbeacon
|
7
9
|
class Beacon
|
10
|
+
|
8
11
|
include ::CZMQ::FFI
|
9
12
|
|
10
13
|
# function pointer to the +zbeacon()+ function
|
11
14
|
ZBEACON_FPTR = ::CZMQ::FFI.ffi_libraries.each do |dl|
|
12
|
-
fptr = dl.find_function(
|
15
|
+
fptr = dl.find_function('zbeacon')
|
13
16
|
break fptr if fptr
|
14
17
|
end
|
15
18
|
raise LoadError, "couldn't find zbeacon()" if ZBEACON_FPTR.nil?
|
@@ -28,12 +31,14 @@ module CZTop
|
|
28
31
|
@actor.terminate
|
29
32
|
end
|
30
33
|
|
34
|
+
|
31
35
|
# Enable verbose logging of commands and activity.
|
32
36
|
# @return [void]
|
33
37
|
def verbose!
|
34
|
-
@actor <<
|
38
|
+
@actor << 'VERBOSE'
|
35
39
|
end
|
36
40
|
|
41
|
+
|
37
42
|
# Run the beacon on the specified UDP port.
|
38
43
|
#
|
39
44
|
# @param port [Integer] port number to
|
@@ -43,7 +48,7 @@ module CZTop
|
|
43
48
|
# interrupted
|
44
49
|
# @raise [NotImplementedError] if the system doesn't support UDP broadcasts
|
45
50
|
def configure(port)
|
46
|
-
@actor.send_picture(
|
51
|
+
@actor.send_picture('si', :string, 'CONFIGURE', :int, port)
|
47
52
|
ptr = Zstr.recv(@actor)
|
48
53
|
|
49
54
|
# NULL if context terminated or interrupted
|
@@ -64,42 +69,49 @@ module CZTop
|
|
64
69
|
# @raise [ArgumentError] if data is longer than {MAX_BEACON_DATA} bytes
|
65
70
|
# @return [void]
|
66
71
|
def publish(data, interval)
|
67
|
-
raise ArgumentError,
|
68
|
-
|
69
|
-
|
72
|
+
raise ArgumentError, 'data too long' if data.bytesize > MAX_BEACON_DATA
|
73
|
+
|
74
|
+
@actor.send_picture('sbi', :string, 'PUBLISH', :string, data,
|
75
|
+
:int, data.bytesize, :int, interval)
|
70
76
|
end
|
71
77
|
|
78
|
+
|
72
79
|
# Stop broadcasting the beacon.
|
73
80
|
# @return [void]
|
74
81
|
def silence
|
75
|
-
@actor <<
|
82
|
+
@actor << 'SILENCE'
|
76
83
|
end
|
77
84
|
|
85
|
+
|
78
86
|
# Start listening to beacons from peers.
|
79
87
|
# @param filter [String] do a prefix match on received beacons
|
80
88
|
# @return [void]
|
81
89
|
def subscribe(filter)
|
82
|
-
@actor.send_picture(
|
90
|
+
@actor.send_picture('sb', :string, 'SUBSCRIBE',
|
83
91
|
:string, filter, :int, filter.bytesize)
|
84
92
|
end
|
85
93
|
|
94
|
+
|
86
95
|
# Just like {#subscribe}, but subscribe to all peer beacons.
|
87
96
|
# @return [void]
|
88
97
|
def listen
|
89
|
-
@actor.send_picture(
|
98
|
+
@actor.send_picture('sb', :string, 'SUBSCRIBE',
|
90
99
|
:string, nil, :int, 0)
|
91
100
|
end
|
92
101
|
|
102
|
+
|
93
103
|
# Stop listening to other peers.
|
94
104
|
# @return [void]
|
95
105
|
def unsubscribe
|
96
|
-
@actor <<
|
106
|
+
@actor << 'UNSUBSCRIBE'
|
97
107
|
end
|
98
108
|
|
109
|
+
|
99
110
|
# Receive next beacon from a peer.
|
100
111
|
# @return [Message] 2-frame message with ([ipaddr, data])
|
101
112
|
def receive
|
102
113
|
@actor.receive
|
103
114
|
end
|
115
|
+
|
104
116
|
end
|
105
117
|
end
|
data/lib/cztop/cert_store.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'set'
|
2
4
|
|
3
5
|
module CZTop
|
4
|
-
|
5
6
|
# A store for CURVE security certificates, either backed by files on disk or
|
6
7
|
# in-memory.
|
7
8
|
#
|
8
9
|
# @see http://api.zeromq.org/czmq3-0:zcertstore
|
9
10
|
class CertStore
|
11
|
+
|
10
12
|
include ::CZMQ::FFI
|
11
13
|
include HasFFIDelegate
|
12
14
|
extend CZTop::HasFFIDelegate::ClassMethods
|
@@ -21,6 +23,7 @@ module CZTop
|
|
21
23
|
attach_ffi_delegate(Zcertstore.new(location))
|
22
24
|
end
|
23
25
|
|
26
|
+
|
24
27
|
# Looks up a certificate in the store by its public key.
|
25
28
|
#
|
26
29
|
# @param pubkey [String] the public key in question, in Z85 format
|
@@ -29,9 +32,11 @@ module CZTop
|
|
29
32
|
def lookup(pubkey)
|
30
33
|
ptr = ffi_delegate.lookup(pubkey)
|
31
34
|
return nil if ptr.null?
|
35
|
+
|
32
36
|
Certificate.from_ffi_delegate(ptr)
|
33
37
|
end
|
34
38
|
|
39
|
+
|
35
40
|
# Inserts a new certificate into the store.
|
36
41
|
#
|
37
42
|
# @note The same public key must not be inserted more than once.
|
@@ -43,11 +48,12 @@ module CZTop
|
|
43
48
|
raise ArgumentError unless cert.is_a?(Certificate)
|
44
49
|
|
45
50
|
@_inserted_pubkeys ||= Set.new
|
46
|
-
pubkey
|
51
|
+
pubkey = cert.public_key
|
47
52
|
raise ArgumentError if @_inserted_pubkeys.include? pubkey
|
48
53
|
|
49
54
|
ffi_delegate.insert(cert.ffi_delegate)
|
50
55
|
@_inserted_pubkeys << pubkey
|
51
56
|
end
|
57
|
+
|
52
58
|
end
|
53
59
|
end
|