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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/coverage.yml +20 -0
  3. data/.github/workflows/draft_api.yml +27 -0
  4. data/.github/workflows/stable_api.yml +26 -0
  5. data/.rubocop.yml +175 -0
  6. data/CHANGES.md +9 -4
  7. data/Gemfile +3 -7
  8. data/README.md +19 -58
  9. data/ci/install-libczmq +22 -0
  10. data/ci/install-libzmq +22 -0
  11. data/cztop.gemspec +12 -13
  12. data/lib/cztop/actor.rb +55 -26
  13. data/lib/cztop/authenticator.rb +18 -9
  14. data/lib/cztop/beacon.rb +22 -10
  15. data/lib/cztop/cert_store.rb +8 -2
  16. data/lib/cztop/certificate.rb +47 -18
  17. data/lib/cztop/config/comments.rb +14 -3
  18. data/lib/cztop/config/serialization.rb +25 -5
  19. data/lib/cztop/config/traversing.rb +44 -13
  20. data/lib/cztop/config.rb +23 -9
  21. data/lib/cztop/frame.rb +23 -10
  22. data/lib/cztop/has_ffi_delegate.rb +11 -1
  23. data/lib/cztop/message/frames.rb +16 -2
  24. data/lib/cztop/message.rb +36 -22
  25. data/lib/cztop/metadata.rb +35 -24
  26. data/lib/cztop/monitor.rb +14 -5
  27. data/lib/cztop/poller/aggregated.rb +31 -15
  28. data/lib/cztop/poller/zmq.rb +25 -22
  29. data/lib/cztop/poller/zpoller.rb +18 -6
  30. data/lib/cztop/poller.rb +43 -18
  31. data/lib/cztop/polymorphic_zsock_methods.rb +6 -1
  32. data/lib/cztop/proxy.rb +34 -19
  33. data/lib/cztop/send_receive_methods.rb +5 -1
  34. data/lib/cztop/socket/types.rb +128 -22
  35. data/lib/cztop/socket.rb +23 -18
  36. data/lib/cztop/version.rb +5 -1
  37. data/lib/cztop/z85/padded.rb +12 -3
  38. data/lib/cztop/z85/pipe.rb +40 -17
  39. data/lib/cztop/z85.rb +17 -6
  40. data/lib/cztop/zap.rb +57 -32
  41. data/lib/cztop/zsock_options.rb +155 -122
  42. data/lib/cztop.rb +2 -1
  43. metadata +25 -90
  44. data/.gitlab-ci.yml +0 -32
  45. data/Guardfile +0 -61
  46. data/Procfile +0 -3
  47. 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 = true
64
- @mtx = Mutex.new
65
- @callback = callback || handler
66
- @callback = shim(@callback) unless @callback.is_a? ::FFI::Pointer
67
- ffi_delegate = Zactor.new(@callback, c_args)
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 if not @running
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 if not @running
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 if not @running
144
+ raise DeadActorError unless @running
145
+
133
146
  message = Message.coerce(message)
134
- raise ArgumentError, "use #terminate" if TERM == message[0]
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 if not @running
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 if not @running
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, "invalid handler" if !handler.respond_to?(:call)
226
+ raise ArgumentError, 'invalid handler' unless handler.respond_to?(:call)
204
227
 
205
- @handler_thread = nil
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
- begin
210
- @mtx.synchronize do
211
- @handler_thread = Thread.current
212
- @pipe = Socket::PAIR.from_ffi_delegate(pipe_delegate)
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 = "$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
@@ -1,16 +1,18 @@
1
- module CZTop
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("zauth")
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 << "VERBOSE"
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 << ["ALLOW", *addrs]
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 << ["DENY", *addrs]
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 << ["PLAIN", *filename]
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 << ["CURVE", directory]
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 << "GSSAPI"
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("zbeacon")
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 << "VERBOSE"
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("si", :string, "CONFIGURE", :int, port)
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, "data too long" if data.bytesize > MAX_BEACON_DATA
68
- @actor.send_picture("sbi", :string, "PUBLISH", :string, data,
69
- :int, data.bytesize, :int, interval)
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 << "SILENCE"
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("sb", :string, "SUBSCRIBE",
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("sb", :string, "SUBSCRIBE",
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 << "UNSUBSCRIBE"
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
@@ -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 = cert.public_key
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