cztop 0.14.1 → 1.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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