cztop 1.0.0 → 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/{main.yml → stable_api.yml} +6 -6
- data/.rubocop.yml +175 -0
- data/CHANGES.md +8 -1
- data/Gemfile +5 -0
- data/README.md +3 -1
- data/ci/install-libczmq +22 -0
- data/ci/install-libzmq +22 -0
- data/cztop.gemspec +3 -2
- 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 +28 -10
- data/.ruby-version +0 -1
data/lib/cztop/z85/pipe.rb
CHANGED
@@ -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
|
10
|
-
@
|
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(
|
36
|
+
CZTop::Z85.encode('') # empty input
|
33
37
|
else
|
34
|
-
|
38
|
+
'' # very first chunk. don't encode anything yet...
|
35
39
|
end
|
36
40
|
end.execute
|
37
|
-
|
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(
|
56
|
+
CZTop::Z85.decode('') # empty input
|
52
57
|
else
|
53
|
-
|
58
|
+
'' # very first chunk. don't decode anything yet...
|
54
59
|
end
|
55
60
|
end.execute
|
56
|
-
|
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
|
75
|
-
@sink
|
81
|
+
@source = source
|
82
|
+
@sink = sink
|
76
83
|
@read_sz = read_sz
|
77
|
-
@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
|
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,
|
63
|
+
raise ArgumentError, 'wrong input length' if (input.bytesize % 4).positive?
|
64
|
+
|
57
65
|
input = input.dup.force_encoding(Encoding::BINARY)
|
58
|
-
ptr
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
19
|
+
ENDPOINT = 'inproc://zeromq.zap.01'
|
17
20
|
|
18
21
|
# the ZAP version supported by this code
|
19
|
-
VERSION =
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
65
|
+
r.version = version
|
57
66
|
r.request_id = request_id
|
58
|
-
r.address
|
59
|
-
r.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
|
101
|
+
@domain = domain
|
93
102
|
@credentials = credentials
|
94
|
-
@mechanism
|
95
|
-
@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 = [
|
102
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
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,
|
179
|
+
raise InternalError, 'invalid status code'
|
164
180
|
end
|
165
181
|
|
166
182
|
new(status_code).tap do |r|
|
167
|
-
r.version
|
168
|
-
r.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
|
172
|
-
r.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
|
-
|
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
|