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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Z85 with simple padding. This allows you to {#encode} input of any
2
4
  # length.
3
5
  #
@@ -29,7 +31,9 @@
29
31
  #
30
32
  # @see https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7
31
33
  class CZTop::Z85::Padded < CZTop::Z85
34
+
32
35
  class << self
36
+
33
37
  # Same as {Z85::Padded#encode}, but without the need to create an
34
38
  # instance first.
35
39
  #
@@ -41,6 +45,7 @@ class CZTop::Z85::Padded < CZTop::Z85
41
45
  default.encode(input)
42
46
  end
43
47
 
48
+
44
49
  # Same as {Z85::Padded#decode}, but without the need to create an
45
50
  # instance first.
46
51
  #
@@ -59,6 +64,7 @@ class CZTop::Z85::Padded < CZTop::Z85
59
64
  def default
60
65
  @default ||= CZTop::Z85::Padded.new
61
66
  end
67
+
62
68
  end
63
69
 
64
70
  # Encododes to Z85, with padding if needed.
@@ -72,14 +78,15 @@ class CZTop::Z85::Padded < CZTop::Z85
72
78
  padding_bytes = 4 - (input.bytesize % 4)
73
79
 
74
80
  # if 0, make it 4. we MUST append padding.
75
- padding_bytes = 4 if padding_bytes == 0
81
+ padding_bytes = 4 if padding_bytes.zero?
76
82
 
77
83
  # generate and append padding
78
- padding = [padding_bytes].pack("C") * padding_bytes
84
+ padding = [padding_bytes].pack('C') * padding_bytes
79
85
 
80
86
  super("#{input}#{padding}")
81
87
  end
82
88
 
89
+
83
90
  # Decodes from Z85 with padding.
84
91
  #
85
92
  # @param input [String] Z85 encoded data (including padding, or empty
@@ -88,11 +95,13 @@ class CZTop::Z85::Padded < CZTop::Z85
88
95
  # @raise [SystemCallError] if this fails
89
96
  def decode(input)
90
97
  return super if input.empty?
98
+
91
99
  decoded = super
92
100
 
93
101
  # last byte contains number of padding bytes
94
102
  padding_bytes = decoded.byteslice(-1).ord
95
103
 
96
- decoded.byteslice( 0 ... -padding_bytes )
104
+ decoded.byteslice(0...-padding_bytes)
97
105
  end
106
+
98
107
  end
@@ -1,18 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Can be used if you want to encode or decode data from one IO to another.
2
4
  # It'll do so until it hits EOF in the source IO.
3
5
  class CZTop::Z85::Pipe
6
+
4
7
  # @param source [IO] where to read data
5
8
  # @param sink [IO] where to write data
6
9
  # @param strategy [Strategy] algorithm to use (pass the class itself,
7
10
  # not an instance)
8
11
  def initialize(source, sink, strategy: Strategy::Parallel)
9
- @source, @sink = source, sink
10
- @strategy = strategy
12
+ @source = source
13
+ @sink = sink
14
+ @strategy = strategy
11
15
  @bin_bytes = 0 # processed binary data (non-Z85)
12
16
  end
13
17
 
14
18
  # @return [Integer] size of chunks read when encoding
15
- ENCODE_READ_SZ = (32 * 2**10) # 32 KiB (for full chunks read)
19
+ ENCODE_READ_SZ = (32 * (2**10)) # 32 KiB (for full chunks read)
16
20
 
17
21
  # @return [Integer] size of chunks read when decoding
18
22
  DECODE_READ_SZ = ENCODE_READ_SZ / 4 * 5
@@ -29,14 +33,15 @@ class CZTop::Z85::Pipe
29
33
  elsif prev_chunk && chunk.nil? # last chunk
30
34
  CZTop::Z85::Padded.encode(prev_chunk)
31
35
  elsif prev_chunk.nil? && chunk.nil?
32
- CZTop::Z85.encode("") # empty input
36
+ CZTop::Z85.encode('') # empty input
33
37
  else
34
- "" # very first chunk. don't encode anything yet...
38
+ '' # very first chunk. don't encode anything yet...
35
39
  end
36
40
  end.execute
37
- return @bin_bytes
41
+ @bin_bytes
38
42
  end
39
43
 
44
+
40
45
  # Decodes Z85 data from source and writes decoded data to sink. This is
41
46
  # done until EOF is hit on the source.
42
47
  #
@@ -48,19 +53,21 @@ class CZTop::Z85::Pipe
48
53
  elsif prev_chunk && chunk.nil?
49
54
  CZTop::Z85::Padded.decode(prev_chunk)
50
55
  elsif prev_chunk.nil? && chunk.nil?
51
- CZTop::Z85.decode("") # empty input
56
+ CZTop::Z85.decode('') # empty input
52
57
  else
53
- "" # very first chunk. don't decode anything yet...
58
+ '' # very first chunk. don't decode anything yet...
54
59
  end
55
60
  end.execute
56
- return @bin_bytes
61
+ @bin_bytes
57
62
  end
58
63
 
64
+
59
65
  # @abstract
60
66
  # Different encoding/decoding strategies (algorithms).
61
67
  #
62
68
  # This is mainly just for me to practice the GoF Strategy Pattern.
63
69
  class Strategy
70
+
64
71
  # @param source [IO] the source
65
72
  # @param sink [IO] the sink
66
73
  # @param read_sz [Integer] chunk size when reading from source
@@ -71,33 +78,41 @@ class CZTop::Z85::Pipe
71
78
  # the first time)
72
79
  # @yieldreturn [String] encoded/decoded chunk to write to sink
73
80
  def initialize(source, sink, read_sz, &xcode)
74
- @source = source
75
- @sink = sink
81
+ @source = source
82
+ @sink = sink
76
83
  @read_sz = read_sz
77
- @xcode = xcode
84
+ @xcode = xcode
78
85
  end
79
86
 
87
+
80
88
  # @abstract
81
89
  # Runs the algorithm.
82
90
  # @raise [void]
83
- def execute() raise NotImplementedError end
91
+ def execute
92
+ raise NotImplementedError
93
+ end
94
+
84
95
 
85
96
  # A single thread that is either reading input, encoding/decoding, or
86
97
  # writing output.
87
98
  class Sequential < Strategy
99
+
88
100
  # Runs the algorithm.
89
101
  # @raise [void]
90
102
  def execute
91
103
  previous_chunk = nil
92
104
  while true
93
105
  chunk = @source.read(@read_sz)
94
- @sink << @xcode.(chunk, previous_chunk)
106
+ @sink << @xcode.call(chunk, previous_chunk)
95
107
  break if chunk.nil?
108
+
96
109
  previous_chunk = chunk
97
110
  end
98
111
  end
112
+
99
113
  end
100
114
 
115
+
101
116
  # Uses three threads:
102
117
  #
103
118
  # 1. reads from source
@@ -108,6 +123,7 @@ class CZTop::Z85::Pipe
108
123
  # platforms such as Rubinius and JRuby (and multiple CPU cores).
109
124
  #
110
125
  class Parallel < Strategy
126
+
111
127
  # Initializes the 2 sized queues used.
112
128
  def initialize(*)
113
129
  super
@@ -126,6 +142,7 @@ class CZTop::Z85::Pipe
126
142
  # @sink
127
143
  end
128
144
 
145
+
129
146
  # Runs the algorithm.
130
147
  # @raise [void]
131
148
  def execute
@@ -140,12 +157,13 @@ class CZTop::Z85::Pipe
140
157
  # pushes a +nil+ into the queue.
141
158
  # @return [void]
142
159
  def read
143
- while chunk = @source.read(@read_sz)
160
+ while (chunk = @source.read(@read_sz))
144
161
  @source_queue << chunk
145
162
  end
146
163
  @source_queue << nil
147
164
  end
148
165
 
166
+
149
167
  # Pops all chunks from the source queue, encodes or decodes them,
150
168
  # and pushes the result into the sink queue. Then pushes a +nil+
151
169
  # into the queue.
@@ -157,21 +175,26 @@ class CZTop::Z85::Pipe
157
175
  chunk = @source_queue.pop
158
176
 
159
177
  # call @xcode for the trailing nil-chunk as well
160
- @sink_queue << @xcode.(chunk, previous_chunk)
178
+ @sink_queue << @xcode.call(chunk, previous_chunk)
161
179
 
162
180
  break if chunk.nil?
181
+
163
182
  previous_chunk = chunk
164
183
  end
165
184
  @sink_queue << nil
166
185
  end
167
186
 
187
+
168
188
  # Pops all chunks from the sink queue and writes them to the sink.
169
189
  # @return [void]
170
190
  def write
171
- while chunk = @sink_queue.pop
191
+ while (chunk = @sink_queue.pop)
172
192
  @sink << chunk
173
193
  end
174
194
  end
195
+
175
196
  end
197
+
176
198
  end
199
+
177
200
  end
data/lib/cztop/z85.rb CHANGED
@@ -1,13 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CZTop
2
4
  # Represents a CZMQ::FFI::Zarmour in Z85 mode.
3
5
  #
4
6
  # Use this class to encode to and from the Z85 encoding scheme.
5
7
  # @see http://rfc.zeromq.org/spec:32
6
8
  class Z85
9
+
7
10
  include HasFFIDelegate
8
11
  extend CZTop::HasFFIDelegate::ClassMethods
9
12
 
10
13
  class << self
14
+
11
15
  # Same as {Z85#encode}, but without the need to create an instance
12
16
  # first.
13
17
  #
@@ -20,6 +24,7 @@ module CZTop
20
24
  default.encode(input)
21
25
  end
22
26
 
27
+
23
28
  # Same as {Z85#decode}, but without the need to create an instance
24
29
  # first.
25
30
  #
@@ -39,6 +44,7 @@ module CZTop
39
44
  def default
40
45
  @default ||= Z85.new
41
46
  end
47
+
42
48
  end
43
49
 
44
50
  def initialize
@@ -46,6 +52,7 @@ module CZTop
46
52
  ffi_delegate.set_mode(CZMQ::FFI::Zarmour::MODE_Z85)
47
53
  end
48
54
 
55
+
49
56
  # Encodes to Z85.
50
57
  # @param input [String] possibly binary input data
51
58
  # @return [String] Z85 encoded data as ASCII string
@@ -53,15 +60,17 @@ module CZTop
53
60
  # remainder
54
61
  # @raise [SystemCallError] if this fails
55
62
  def encode(input)
56
- raise ArgumentError, "wrong input length" if input.bytesize % 4 > 0
63
+ raise ArgumentError, 'wrong input length' if (input.bytesize % 4).positive?
64
+
57
65
  input = input.dup.force_encoding(Encoding::BINARY)
58
- ptr = ffi_delegate.encode(input, input.bytesize)
66
+ ptr = ffi_delegate.encode(input, input.bytesize)
59
67
  raise_zmq_err if ptr.null?
60
68
  z85 = ptr.read_string
61
69
  z85.encode!(Encoding::ASCII)
62
- return z85
70
+ z85
63
71
  end
64
72
 
73
+
65
74
  # Decodes from Z85.
66
75
  # @param input [String] Z85 encoded data
67
76
  # @return [String] original data as binary string
@@ -69,11 +78,13 @@ module CZTop
69
78
  # remainder
70
79
  # @raise [SystemCallError] if this fails
71
80
  def decode(input)
72
- raise ArgumentError, "wrong input length" if input.bytesize % 5 > 0
81
+ return '' if input.empty?
82
+ raise ArgumentError, 'wrong input length' if (input.bytesize % 5).positive?
83
+
73
84
  zchunk = ffi_delegate.decode(input)
74
85
  raise_zmq_err if zchunk.null?
75
- decoded_string = zchunk.data.read_string(zchunk.size - 1)
76
- return decoded_string
86
+ zchunk.data.read_string(zchunk.size - 1)
77
87
  end
88
+
78
89
  end
79
90
  end
data/lib/cztop/zap.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CZTop
2
4
  # This module provides two classes useful when implementing your own ZAP
3
5
  # authentication handler or when directly communicating with one. Within
@@ -12,51 +14,58 @@ module CZTop
12
14
  # @note This is not needed to be able to use {CZTop::Authenticator}!
13
15
  # @see https://rfc.zeromq.org/spec:27/ZAP
14
16
  module ZAP
17
+
15
18
  # the endpoint a ZAP authenticator has bound to
16
- ENDPOINT = "inproc://zeromq.zap.01"
19
+ ENDPOINT = 'inproc://zeromq.zap.01'
17
20
 
18
21
  # the ZAP version supported by this code
19
- VERSION = "1.0"
22
+ VERSION = '1.0'
20
23
 
21
24
  # superclass for ZAP errors
22
25
  class Error < StandardError
23
26
  end
24
27
 
28
+
25
29
  # used when the response contains an unsupported version
26
30
  class VersionMismatch < Error
27
31
  end
28
32
 
33
+
29
34
  # security mechanisms mentioned in ZeroMQ RFC 27.
30
35
  module Mechanisms
31
- NULL = "NULL"
32
- PLAIN = "PLAIN"
33
- CURVE = "CURVE"
36
+
37
+ NULL = 'NULL'
38
+ PLAIN = 'PLAIN'
39
+ CURVE = 'CURVE'
40
+
34
41
  end
35
42
 
43
+
36
44
  # Represents a ZAP request.
37
45
  class Request
46
+
38
47
  # Crafts a new {Request} from a message.
39
48
  #
40
49
  # @param msg [CZTop::message] the message
41
50
  # @return [Request] the request
42
51
  # @raise [VersionMismatch] if the message contains an unsupported version
43
52
  def self.from_message(msg)
44
- version, # The version frame, which SHALL contain the three octets "1.0".
45
- request_id, # The request id, which MAY contain an opaque binary blob.
46
- domain, # The domain, which SHALL contain a string.
47
- address, # The address, the origin network IP address.
48
- identity, # The identity, the connection Identity, if any.
49
- mechanism, # The mechanism, which SHALL contain a string.
50
- *credentials = # The credentials, which SHALL be zero or more opaque frames.
51
- msg.to_a
53
+ version, # The version frame, which SHALL contain the three octets "1.0".
54
+ request_id, # The request id, which MAY contain an opaque binary blob.
55
+ domain, # The domain, which SHALL contain a string.
56
+ address, # The address, the origin network IP address.
57
+ identity, # The identity, the connection Identity, if any.
58
+ mechanism, # The mechanism, which SHALL contain a string.
59
+ *credentials = # The credentials, which SHALL be zero or more opaque frames.
60
+ msg.to_a
52
61
 
53
62
  raise VersionMismatch if version != VERSION
54
63
 
55
64
  new(domain, credentials, mechanism: mechanism).tap do |r|
56
- r.version = version
65
+ r.version = version
57
66
  r.request_id = request_id
58
- r.address = address
59
- r.identity = identity
67
+ r.address = address
68
+ r.identity = identity
60
69
  end
61
70
  end
62
71
 
@@ -89,22 +98,25 @@ module CZTop
89
98
  # @param credentials [Array<String>] the credentials of the user,
90
99
  # depending on the security mechanism used
91
100
  def initialize(domain, credentials = [], mechanism: Mechanisms::CURVE)
92
- @domain = domain
101
+ @domain = domain
93
102
  @credentials = credentials
94
- @mechanism = mechanism
95
- @version = VERSION
103
+ @mechanism = mechanism
104
+ @version = VERSION
96
105
  end
97
106
 
107
+
98
108
  # Creates a sendable message from this {Request}.
99
109
  # @return [CZTop::Message} this request packed into a message
100
110
  def to_msg
101
- fields = [ @version, @request_id, @domain, @address,
102
- @identity, @mechanism, @credentials].flatten.map(&:to_s)
111
+ fields = [@version, @request_id, @domain, @address,
112
+ @identity, @mechanism, @credentials].flatten.map(&:to_s)
103
113
 
104
114
  CZTop::Message.new(fields)
105
115
  end
116
+
106
117
  end
107
118
 
119
+
108
120
  # Represents a ZAP response.
109
121
  class Response
110
122
 
@@ -112,23 +124,27 @@ module CZTop
112
124
  class TemporaryError < Error
113
125
  end
114
126
 
127
+
115
128
  # used to indicate an internal error of the authenticator
116
129
  class InternalError < Error
117
130
  end
118
131
 
132
+
119
133
  # Status codes of ZAP responses.
120
134
  module StatusCodes
121
- SUCCESS = "200"
122
- TEMPORARY_ERROR = "300"
123
- AUTHENTICATION_FAILURE = "400"
124
- INTERNAL_ERROR = "500"
135
+
136
+ SUCCESS = '200'
137
+ TEMPORARY_ERROR = '300'
138
+ AUTHENTICATION_FAILURE = '400'
139
+ INTERNAL_ERROR = '500'
125
140
 
126
141
  ALL = [
127
142
  SUCCESS,
128
143
  TEMPORARY_ERROR,
129
144
  AUTHENTICATION_FAILURE,
130
145
  INTERNAL_ERROR
131
- ]
146
+ ].freeze
147
+
132
148
  end
133
149
 
134
150
  include StatusCodes
@@ -160,16 +176,16 @@ module CZTop
160
176
  when INTERNAL_ERROR
161
177
  raise InternalError, status_text
162
178
  else
163
- raise InternalError, "invalid status code"
179
+ raise InternalError, 'invalid status code'
164
180
  end
165
181
 
166
182
  new(status_code).tap do |r|
167
- r.version = version
168
- r.request_id = request_id
183
+ r.version = version
184
+ r.request_id = request_id
169
185
  r.status_code = status_code
170
186
  r.status_text = status_text
171
- r.user_id = user_id
172
- r.meta_data = meta_data
187
+ r.user_id = user_id
188
+ r.meta_data = meta_data
173
189
  end
174
190
  end
175
191
 
@@ -198,30 +214,37 @@ module CZTop
198
214
  def initialize(status_code)
199
215
  @status_code = status_code.to_s
200
216
  raise ArgumentError unless ALL.include?(@status_code)
201
- @version = VERSION
217
+
218
+ @version = VERSION
202
219
  end
203
220
 
221
+
204
222
  # @return [Boolean] whether the authentication was successful
205
223
  def success?
206
224
  @status_code == SUCCESS
207
225
  end
208
226
 
227
+
209
228
  # Returns the user ID, if authentication was successful.
210
229
  # @return [String] the user ID of the authenticated user
211
230
  # @return [nil] if authentication was unsuccessful
212
231
  def user_id
213
232
  return nil unless success?
233
+
214
234
  @user_id
215
235
  end
216
236
 
237
+
217
238
  # Returns the meta data, if authentication was successful.
218
239
  # @return [String] the meta data for the authenticated user
219
240
  # @return [nil] if authentication was unsuccessful
220
241
  def meta_data
221
242
  return nil unless success?
243
+
222
244
  @meta_data
223
245
  end
224
246
 
247
+
225
248
  # Creates a sendable message from this {Response}.
226
249
  # @return [CZTop::Message} this request packed into a message
227
250
  def to_msg
@@ -229,6 +252,8 @@ module CZTop
229
252
  @status_text, @user_id, @meta_data].map(&:to_s)
230
253
  CZTop::Message.new(fields)
231
254
  end
255
+
232
256
  end
257
+
233
258
  end
234
259
  end