cztop 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +15 -0
- data/README.md +5 -0
- data/exe/z85decode +31 -0
- data/exe/z85encode +31 -0
- data/lib/cztop.rb +2 -0
- data/lib/cztop/poller.rb +58 -56
- data/lib/cztop/socket/types.rb +4 -1
- data/lib/cztop/version.rb +1 -1
- data/lib/cztop/z85.rb +34 -90
- data/lib/cztop/z85/padded.rb +98 -0
- data/lib/cztop/z85/pipe.rb +178 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38df565064ee6d04ad3eeaaf668e68574cb8e0cc
|
4
|
+
data.tar.gz: 62eb8513064a9546d4c04c57411314ffb8bdc162
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 028dbf962ccf069d64048d12107fcc322e66a5553c34e2c8081e15319e602eac13c88ca903a0290bb9fbd6dda22c398ea8e4e16d99fa01f0fab6cf049110df13
|
7
|
+
data.tar.gz: 7f6f6cdf10066b9a8ae4c26f5b3d0025da04a1a6aeb0caec8bc6ab550a16955057162543ac83e0ec0fd2535923637c5beafb6acf1a6247228492f97531862dc4
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
0.2.0 (xx/01/2016)
|
2
|
+
-----
|
3
|
+
* simplify CZTop::Z85::Padded
|
4
|
+
* no length encoding
|
5
|
+
* padding similar to PKCS#7
|
6
|
+
* add utilities `z85encode` and `z85decode`
|
7
|
+
* add CZTop::Z85::Pipe
|
8
|
+
* CZTop::SUB#subscribe: subscribe to everything if no parameter given
|
9
|
+
|
10
|
+
0.1.1 (23/01/2016)
|
11
|
+
-----
|
12
|
+
* add support for Ruby 2.0
|
13
|
+
* improve documentation
|
14
|
+
* fix require()s in examples
|
15
|
+
|
1
16
|
0.1.0 (23/01/2016)
|
2
17
|
-----
|
3
18
|
* first release
|
data/README.md
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
[![Inline docs](http://inch-ci.org/github/paddor/cztop.svg?branch=master&style=shields)](http://inch-ci.org/github/paddor/cztop)
|
4
4
|
[![Dependency Status](https://gemnasium.com/paddor/cztop.svg)](https://gemnasium.com/paddor/cztop)
|
5
5
|
[![Coverage Status](https://coveralls.io/repos/paddor/cztop/badge.svg?branch=master&service=github)](https://coveralls.io/github/paddor/cztop?branch=master)
|
6
|
+
[![ISC License](https://img.shields.io/badge/license-ISC_License-blue.svg)](LICENSE)
|
6
7
|
|
7
8
|
# CZTop
|
8
9
|
|
@@ -389,6 +390,10 @@ $
|
|
389
390
|
* cannot handle CLIENT/SERVER sockets
|
390
391
|
* there good timer gems for Ruby
|
391
392
|
* Poller can be used to embed in an existing event loop (Celluloid), or make your own trivial one
|
393
|
+
* [x] provide `z85encode` and `z85decode` utilities
|
394
|
+
* can be used in a pipeline (limited memory usage)
|
395
|
+
* reusable interface: `Z85::Pipe`
|
396
|
+
|
392
397
|
|
393
398
|
## Contributing
|
394
399
|
|
data/exe/z85decode
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'cztop'
|
3
|
+
require 'optparse'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
options = { strategy: CZTop::Z85::Pipe::Strategy::Sequential }
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: #$0 [options]"
|
9
|
+
|
10
|
+
opts.on("-p", "--parallel", "read, decode, and write in parallel " +
|
11
|
+
"using 3 threads") do
|
12
|
+
options[:strategy] = CZTop::Z85::Pipe::Strategy::Parallel
|
13
|
+
end
|
14
|
+
opts.on("-v", "--verbose", "print some statistics afterwards") do
|
15
|
+
options[:verbose] = true
|
16
|
+
end
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
pipe = CZTop::Z85::Pipe.new(STDIN, STDOUT, strategy: options[:strategy])
|
20
|
+
bytes_decoded = nil
|
21
|
+
tms = Benchmark.measure { bytes_decoded = pipe.decode }
|
22
|
+
exit unless options[:verbose]
|
23
|
+
|
24
|
+
if tms.real < 0.1
|
25
|
+
warn "#{$0}: decoding took %.2f us." % (tms.real * 1_000_000)
|
26
|
+
else
|
27
|
+
warn "#{$0}: decoding took %.3f seconds." % tms.real
|
28
|
+
end
|
29
|
+
|
30
|
+
throughput = (bytes_decoded * 8 / tms.real) / 1_000_000
|
31
|
+
warn "#{$0}: mean throughput: %.3f [Mb/s]" % throughput
|
data/exe/z85encode
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'cztop'
|
3
|
+
require 'optparse'
|
4
|
+
require 'benchmark'
|
5
|
+
|
6
|
+
options = { strategy: CZTop::Z85::Pipe::Strategy::Sequential }
|
7
|
+
OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: #$0 [options]"
|
9
|
+
|
10
|
+
opts.on("-p", "--parallel", "read, encode, and write in parallel " +
|
11
|
+
"using 3 threads") do
|
12
|
+
options[:strategy] = CZTop::Z85::Pipe::Strategy::Parallel
|
13
|
+
end
|
14
|
+
opts.on("-v", "--verbose", "print some statistics afterwards") do
|
15
|
+
options[:verbose] = true
|
16
|
+
end
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
pipe = CZTop::Z85::Pipe.new(STDIN, STDOUT, strategy: options[:strategy])
|
20
|
+
bytes_encoded = nil
|
21
|
+
tms = Benchmark.measure { bytes_encoded = pipe.encode }
|
22
|
+
exit unless options[:verbose]
|
23
|
+
|
24
|
+
if tms.real < 0.1
|
25
|
+
warn "#{$0}: encoding took %.2f us." % (tms.real * 1_000_000)
|
26
|
+
else
|
27
|
+
warn "#{$0}: encoding took %.3f seconds." % tms.real
|
28
|
+
end
|
29
|
+
|
30
|
+
throughput = (bytes_encoded * 8 / tms.real) / 1_000_000
|
31
|
+
warn "#{$0}: mean throughput: %.3f [Mb/s]" % throughput
|
data/lib/cztop.rb
CHANGED
@@ -31,6 +31,8 @@ require_relative 'cztop/config/traversing'
|
|
31
31
|
require_relative 'cztop/config/serialization'
|
32
32
|
require_relative 'cztop/message/frames'
|
33
33
|
require_relative 'cztop/socket/types'
|
34
|
+
require_relative 'cztop/z85/padded'
|
35
|
+
require_relative 'cztop/z85/pipe'
|
34
36
|
|
35
37
|
|
36
38
|
# make Ctrl-C work in case a low-level call hangs
|
data/lib/cztop/poller.rb
CHANGED
@@ -30,57 +30,7 @@ module CZTop
|
|
30
30
|
# ugly.
|
31
31
|
#
|
32
32
|
class Poller
|
33
|
-
|
34
|
-
module ZMQ
|
35
|
-
|
36
|
-
POLL = 1
|
37
|
-
POLLIN = 1
|
38
|
-
POLLOUT = 2
|
39
|
-
POLLERR = 4
|
40
|
-
|
41
|
-
extend ::FFI::Library
|
42
|
-
lib_name = 'libzmq'
|
43
|
-
lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
|
44
|
-
.map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
|
45
|
-
ffi_lib lib_paths + [lib_name]
|
46
|
-
|
47
|
-
# Represents a struct of type +zmq_pollitem_t+.
|
48
|
-
class PollItem < FFI::Struct
|
49
|
-
##
|
50
|
-
# shamelessly taken from https://github.com/mtortonesi/ruby-czmq-ffi
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
FD_TYPE = if FFI::Platform::IS_WINDOWS && FFI::Platform::ADDRESS_SIZE == 64
|
55
|
-
# On Windows, zmq.h defines fd as a SOCKET, which is 64 bits on x64.
|
56
|
-
:uint64
|
57
|
-
else
|
58
|
-
:int
|
59
|
-
end
|
60
|
-
|
61
|
-
layout :socket, :pointer,
|
62
|
-
:fd, FD_TYPE,
|
63
|
-
:events, :short,
|
64
|
-
:revents, :short
|
65
|
-
|
66
|
-
# @return [Boolean] whether the socket is readable
|
67
|
-
def readable?
|
68
|
-
(self[:revents] & POLLIN) > 0
|
69
|
-
end
|
70
|
-
|
71
|
-
# @return [Boolean] whether the socket is writable
|
72
|
-
def writable?
|
73
|
-
(self[:revents] & POLLOUT) > 0
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
opts = {
|
78
|
-
blocking: true # only necessary on MRI to deal with the GIL.
|
79
|
-
}
|
80
|
-
|
81
|
-
#ZMQ_EXPORT int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout);
|
82
|
-
attach_function :poll, :zmq_poll, [:pointer, :int, :long], :int, **opts
|
83
|
-
end
|
33
|
+
include ::CZMQ::FFI
|
84
34
|
|
85
35
|
# @param readers [Socket, Actor] sockets to poll for input
|
86
36
|
def initialize(*readers)
|
@@ -108,7 +58,7 @@ module CZTop
|
|
108
58
|
# @raise [ArgumentError] if it's not a socket
|
109
59
|
def add_reader(socket)
|
110
60
|
raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
|
111
|
-
ptr =
|
61
|
+
ptr = Zsock.resolve(socket) # get low-level handle
|
112
62
|
@readers[ptr.to_i] = socket
|
113
63
|
@rebuild_needed = true
|
114
64
|
end
|
@@ -120,7 +70,7 @@ module CZTop
|
|
120
70
|
# @raise [ArgumentError] if it's not a socket
|
121
71
|
def remove_reader(socket)
|
122
72
|
raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
|
123
|
-
ptr =
|
73
|
+
ptr = Zsock.resolve(socket) # get low-level handle
|
124
74
|
@readers.delete(ptr.to_i) and @rebuild_needed = true
|
125
75
|
end
|
126
76
|
|
@@ -130,7 +80,7 @@ module CZTop
|
|
130
80
|
# @raise [ArgumentError] if it's not a socket
|
131
81
|
def add_writer(socket)
|
132
82
|
raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
|
133
|
-
ptr =
|
83
|
+
ptr = Zsock.resolve(socket) # get low-level handle
|
134
84
|
@writers[ptr.to_i] = socket
|
135
85
|
@rebuild_needed = true
|
136
86
|
end
|
@@ -142,7 +92,7 @@ module CZTop
|
|
142
92
|
# @raise [ArgumentError] if it's not a socket
|
143
93
|
def remove_writer(socket)
|
144
94
|
raise ArgumentError unless socket.is_a?(Socket) || socket.is_a?(Actor)
|
145
|
-
ptr =
|
95
|
+
ptr = Zsock.resolve(socket) # get low-level handle
|
146
96
|
@writers.delete(ptr.to_i) and @rebuild_needed = true
|
147
97
|
end
|
148
98
|
|
@@ -212,13 +162,65 @@ module CZTop
|
|
212
162
|
# @return [ZMQ::PollItem] a new item for
|
213
163
|
def new_item(address, socket, events)
|
214
164
|
item = ZMQ::PollItem.new(address)
|
215
|
-
item[:socket] =
|
165
|
+
item[:socket] = Zsock.resolve(socket)
|
216
166
|
item[:fd] = 0
|
217
167
|
item[:events] = events
|
218
168
|
item[:revents] = 0
|
219
169
|
item
|
220
170
|
end
|
221
171
|
|
172
|
+
# CZTop's interface to the low-level +zmq_poll()+ function.
|
173
|
+
module ZMQ
|
174
|
+
|
175
|
+
POLL = 1
|
176
|
+
POLLIN = 1
|
177
|
+
POLLOUT = 2
|
178
|
+
POLLERR = 4
|
179
|
+
|
180
|
+
extend ::FFI::Library
|
181
|
+
lib_name = 'libzmq'
|
182
|
+
lib_paths = ['/usr/local/lib', '/opt/local/lib', '/usr/lib64']
|
183
|
+
.map { |path| "#{path}/#{lib_name}.#{::FFI::Platform::LIBSUFFIX}" }
|
184
|
+
ffi_lib lib_paths + [lib_name]
|
185
|
+
|
186
|
+
# Represents a struct of type +zmq_pollitem_t+.
|
187
|
+
class PollItem < FFI::Struct
|
188
|
+
##
|
189
|
+
# shamelessly taken from https://github.com/mtortonesi/ruby-czmq-ffi
|
190
|
+
#
|
191
|
+
|
192
|
+
|
193
|
+
FD_TYPE = if FFI::Platform::IS_WINDOWS && FFI::Platform::ADDRESS_SIZE == 64
|
194
|
+
# On Windows, zmq.h defines fd as a SOCKET, which is 64 bits on x64.
|
195
|
+
:uint64
|
196
|
+
else
|
197
|
+
:int
|
198
|
+
end
|
199
|
+
|
200
|
+
layout :socket, :pointer,
|
201
|
+
:fd, FD_TYPE,
|
202
|
+
:events, :short,
|
203
|
+
:revents, :short
|
204
|
+
|
205
|
+
# @return [Boolean] whether the socket is readable
|
206
|
+
def readable?
|
207
|
+
(self[:revents] & POLLIN) > 0
|
208
|
+
end
|
209
|
+
|
210
|
+
# @return [Boolean] whether the socket is writable
|
211
|
+
def writable?
|
212
|
+
(self[:revents] & POLLOUT) > 0
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
opts = {
|
217
|
+
blocking: true # only necessary on MRI to deal with the GIL.
|
218
|
+
}
|
219
|
+
|
220
|
+
#ZMQ_EXPORT int zmq_poll (zmq_pollitem_t *items, int nitems, long timeout);
|
221
|
+
attach_function :poll, :zmq_poll, [:pointer, :int, :long], :int, **opts
|
222
|
+
end
|
223
|
+
|
222
224
|
# This is the trivial poller based on zpoller. It only supports polling
|
223
225
|
# for reading, but it also supports doing that on CLIENT/SERVER sockets,
|
224
226
|
# which is useful for {CZTop::Poller}.
|
data/lib/cztop/socket/types.rb
CHANGED
@@ -134,10 +134,13 @@ module CZTop
|
|
134
134
|
attach_ffi_delegate(Zsock.new_sub(endpoints, subscription))
|
135
135
|
end
|
136
136
|
|
137
|
+
# @return [String] subscription prefix to subscribe to everything
|
138
|
+
EVERYTHING = ""
|
139
|
+
|
137
140
|
# Subscribes to the given prefix string.
|
138
141
|
# @param prefix [String] prefix string to subscribe to
|
139
142
|
# @return [void]
|
140
|
-
def subscribe(prefix)
|
143
|
+
def subscribe(prefix = EVERYTHING)
|
141
144
|
ffi_delegate.set_subscribe(prefix)
|
142
145
|
end
|
143
146
|
|
data/lib/cztop/version.rb
CHANGED
data/lib/cztop/z85.rb
CHANGED
@@ -7,6 +7,40 @@ module CZTop
|
|
7
7
|
include HasFFIDelegate
|
8
8
|
extend CZTop::HasFFIDelegate::ClassMethods
|
9
9
|
|
10
|
+
class << self
|
11
|
+
# Same as {Z85#encode}, but without the need to create an instance
|
12
|
+
# first.
|
13
|
+
#
|
14
|
+
# @param input [String] possibly binary input data
|
15
|
+
# @return [String] Z85 encoded data as ASCII string
|
16
|
+
# @raise [ArgumentError] if input length isn't divisible by 4 with no
|
17
|
+
# remainder
|
18
|
+
# @raise [SystemCallError] if this fails
|
19
|
+
def encode(input)
|
20
|
+
default.encode(input)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Same as {Z85#decode}, but without the need to create an instance
|
24
|
+
# first.
|
25
|
+
#
|
26
|
+
# @param input [String] Z85 encoded data
|
27
|
+
# @return [String] original data as binary string
|
28
|
+
# @raise [ArgumentError] if input length isn't divisible by 5 with no
|
29
|
+
# remainder
|
30
|
+
# @raise [SystemCallError] if this fails
|
31
|
+
def decode(input)
|
32
|
+
default.decode(input)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Default instance of {Z85}.
|
38
|
+
# @return [Z85] memoized default instance
|
39
|
+
def default
|
40
|
+
@default ||= Z85.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
10
44
|
def initialize
|
11
45
|
attach_ffi_delegate(CZMQ::FFI::Zarmour.new)
|
12
46
|
ffi_delegate.set_mode(:mode_z85)
|
@@ -63,95 +97,5 @@ module CZTop
|
|
63
97
|
size_ptr.read_uint32
|
64
98
|
end
|
65
99
|
end
|
66
|
-
|
67
|
-
# Z85 with simple padding. This allows you to {#encode} input of any
|
68
|
-
# length.
|
69
|
-
#
|
70
|
-
# = Encoding Procedure
|
71
|
-
#
|
72
|
-
# If the data to be encoded is empty (0 bytes), it is encoded to the empty
|
73
|
-
# string, just like in Z85.
|
74
|
-
#
|
75
|
-
# Otherwise, a length information is prepended and, if needed, padding (1,
|
76
|
-
# 2, or 3 NULL bytes) is appended to bring the resulting blob to
|
77
|
-
# a multiple of 4 bytes.
|
78
|
-
#
|
79
|
-
# The length information is encoded similarly to lengths of messages
|
80
|
-
# (frames) in ZMTP. Up to 127 bytes, the data's length is encoded with
|
81
|
-
# a single byte (specifically, with the 7 least significant bits in it).
|
82
|
-
#
|
83
|
-
# +--------+-------------------------------+------------+
|
84
|
-
# | length | data | padding |
|
85
|
-
# | 1 byte | up to 127 bytes | 0-3 bytes |
|
86
|
-
# +--------+-------------------------------+------------+
|
87
|
-
#
|
88
|
-
# If the data is 128 bytes or more, the most significant bit will be set
|
89
|
-
# to indicate that fact, and a 64 bit unsigned integer in network byte
|
90
|
-
# order is appended after this first byte to encode the length of the
|
91
|
-
# data. This means that up to 16EiB (exbibytes) can be encoded, which
|
92
|
-
# will be enough for the foreseeable future.
|
93
|
-
#
|
94
|
-
# +--------+-----------+----------------------------------+------------+
|
95
|
-
# | big? | length | data | padding |
|
96
|
-
# | 1 byte | 8 bytes | 128 bytes or much more | 0-3 bytes |
|
97
|
-
# +--------+-----------+----------------------------------+------------+
|
98
|
-
#
|
99
|
-
# The resulting blob is encoded using {CZTop::Z85#encode}.
|
100
|
-
# {CZTop::Z85#decode} does the inverse.
|
101
|
-
#
|
102
|
-
# @note Warning: This won't be compatible with other implementations of
|
103
|
-
# Z85. Only use this if you really need padding, like when you can't
|
104
|
-
# guarantee the input for {#encode} is always a multiple of 4 bytes.
|
105
|
-
#
|
106
|
-
class Padded < Z85
|
107
|
-
# Encododes to Z85, with padding if needed.
|
108
|
-
#
|
109
|
-
# If input isn't empty, 8 additional bytes for the encoded length will
|
110
|
-
# be prepended. If needed, 1 to 3 bytes of padding will be appended.
|
111
|
-
#
|
112
|
-
# If input is empty, returns the empty string.
|
113
|
-
#
|
114
|
-
# @param input [String] possibly binary input data
|
115
|
-
# @return [String] Z85 encoded data as ASCII string, including encoded
|
116
|
-
# length and padding
|
117
|
-
# @raise [SystemCallError] if this fails
|
118
|
-
def encode(input)
|
119
|
-
return super if input.empty?
|
120
|
-
length = input.bytesize
|
121
|
-
if length < 1<<7 # up to 127 bytes
|
122
|
-
encoded_length = [length].pack("C")
|
123
|
-
|
124
|
-
else # larger input
|
125
|
-
low = length & 0xFFFFFFFF
|
126
|
-
high = (length >> 32) & 0xFFFFFFFF
|
127
|
-
encoded_length = [ 1<<7, high, low ].pack("CNN")
|
128
|
-
end
|
129
|
-
padding = "\0" * ((4 - ((length+1) % 4)) % 4)
|
130
|
-
super("#{encoded_length}#{input}#{padding}")
|
131
|
-
end
|
132
|
-
|
133
|
-
# Decodes from Z85 with padding.
|
134
|
-
#
|
135
|
-
# @param input [String] Z85 encoded data (including encoded length and
|
136
|
-
# padding, or empty string)
|
137
|
-
# @return [String] original data as binary string
|
138
|
-
# @raise [ArgumentError] if input is invalid or truncated
|
139
|
-
# @raise [SystemCallError] if this fails
|
140
|
-
def decode(input)
|
141
|
-
return super if input.empty?
|
142
|
-
decoded = super
|
143
|
-
length = decoded.byteslice(0, 1).unpack("C")[0]
|
144
|
-
if (1<<7 & length).zero? # up to 127 bytes
|
145
|
-
decoded = decoded.byteslice(1, length) # extract payload
|
146
|
-
|
147
|
-
else # larger input
|
148
|
-
length = decoded.byteslice(1, 8).unpack("NN")
|
149
|
-
.inject(0) { |sum, i| (sum << 32) + i }
|
150
|
-
decoded = decoded.byteslice(9, length) # extract payload
|
151
|
-
end
|
152
|
-
raise ArgumentError, "input truncated" if decoded.bytesize < length
|
153
|
-
return decoded
|
154
|
-
end
|
155
|
-
end
|
156
100
|
end
|
157
101
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# Z85 with simple padding. This allows you to {#encode} input of any
|
2
|
+
# length.
|
3
|
+
#
|
4
|
+
# = Padding Scheme
|
5
|
+
#
|
6
|
+
# If the data to be encoded is empty (0 bytes), it is encoded to the empty
|
7
|
+
# string, just like in Z85.
|
8
|
+
#
|
9
|
+
# Otherwise, a small padding sequence of 1 to 4 (identical) bytes is
|
10
|
+
# appended to the binary data. Its last byte denotes the number of padding
|
11
|
+
# bytes. This padding is done even if the length of the binary input is
|
12
|
+
# a multiple of 4 bytes. This is similar to PKCS#7 padding.
|
13
|
+
#
|
14
|
+
# +----------------------------------------+------------+
|
15
|
+
# | binary data | padding |
|
16
|
+
# | any number of bytes | 1-4 bytes |
|
17
|
+
# +----------------------------------------+------------+
|
18
|
+
#
|
19
|
+
# The resulting blob is encoded using {CZTop::Z85#encode}.
|
20
|
+
#
|
21
|
+
# When decoding, {CZTop::Z85#decode} does the inverse. After decoding, it
|
22
|
+
# checks the last byte to determine the number of bytes of padding used,
|
23
|
+
# and chops those off.
|
24
|
+
#
|
25
|
+
# @note Warning: Z85 doesn't have a standardized padding procedure. So
|
26
|
+
# other implementations won't automatically recognize and chop off the
|
27
|
+
# padding. Only use this if you really need padding, like when you can't
|
28
|
+
# guarantee the input for {#encode} is always a multiple of 4 bytes.
|
29
|
+
#
|
30
|
+
# @see https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7
|
31
|
+
class CZTop::Z85::Padded < CZTop::Z85
|
32
|
+
class << self
|
33
|
+
# Same as {Z85::Padded#encode}, but without the need to create an
|
34
|
+
# instance first.
|
35
|
+
#
|
36
|
+
# @param input [String] possibly binary input data
|
37
|
+
# @return [String] Z85 encoded data as ASCII string, including encoded
|
38
|
+
# length and padding
|
39
|
+
# @raise [SystemCallError] if this fails
|
40
|
+
def encode(input)
|
41
|
+
default.encode(input)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Same as {Z85::Padded#decode}, but without the need to create an
|
45
|
+
# instance first.
|
46
|
+
#
|
47
|
+
# @param input [String] Z85 encoded data (including padding, or empty
|
48
|
+
# string)
|
49
|
+
# @return [String] original data as binary string
|
50
|
+
# @raise [SystemCallError] if this fails
|
51
|
+
def decode(input)
|
52
|
+
default.decode(input)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Default instance of {Z85::Padded}.
|
58
|
+
# @return [Z85::Padded] memoized default instance
|
59
|
+
def default
|
60
|
+
@default ||= CZTop::Z85::Padded.new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Encododes to Z85, with padding if needed.
|
65
|
+
#
|
66
|
+
# @param input [String] possibly binary input data
|
67
|
+
# @return [String] Z85 encoded data as ASCII string, including padding
|
68
|
+
# @raise [SystemCallError] if this fails
|
69
|
+
def encode(input)
|
70
|
+
return super if input.empty?
|
71
|
+
|
72
|
+
padding_bytes = 4 - (input.bytesize % 4)
|
73
|
+
|
74
|
+
# if 0, make it 4. we MUST append padding.
|
75
|
+
padding_bytes = 4 if padding_bytes == 0
|
76
|
+
|
77
|
+
# generate and append padding
|
78
|
+
padding = [padding_bytes].pack("C") * padding_bytes
|
79
|
+
|
80
|
+
super("#{input}#{padding}")
|
81
|
+
end
|
82
|
+
|
83
|
+
# Decodes from Z85 with padding.
|
84
|
+
#
|
85
|
+
# @param input [String] Z85 encoded data (including padding, or empty
|
86
|
+
# string)
|
87
|
+
# @return [String] original data as binary string
|
88
|
+
# @raise [SystemCallError] if this fails
|
89
|
+
def decode(input)
|
90
|
+
return super if input.empty?
|
91
|
+
decoded = super
|
92
|
+
|
93
|
+
# last byte contains number of padding bytes
|
94
|
+
padding_bytes = decoded.byteslice(-1).ord
|
95
|
+
|
96
|
+
decoded.byteslice( 0 ... -padding_bytes )
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Can be used if you want to encode or decode data from one IO to another.
|
2
|
+
# It'll do so until it hits EOF in the source IO.
|
3
|
+
class CZTop::Z85::Pipe
|
4
|
+
# @param source [IO] where to read data
|
5
|
+
# @param sink [IO] where to write data
|
6
|
+
# @param strategy [Strategy] algorithm to use (pass the class itself,
|
7
|
+
# not an instance)
|
8
|
+
def initialize(source, sink, strategy: Strategy::Parallel)
|
9
|
+
@source, @sink = source, sink
|
10
|
+
@strategy = strategy
|
11
|
+
@bin_bytes = 0 # processed binary data (non-Z85)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Integer] size of chunks read when encoding
|
15
|
+
ENCODE_READ_SZ = (32 * 2**10) # 32 KiB (for full chunks read)
|
16
|
+
|
17
|
+
# @return [Integer] size of chunks read when decoding
|
18
|
+
DECODE_READ_SZ = ENCODE_READ_SZ / 4 * 5
|
19
|
+
|
20
|
+
# Encodes data from source and writes Z85 data to sink. This is done
|
21
|
+
# until EOF is hit on the source.
|
22
|
+
#
|
23
|
+
# @return [Integer] number of bytes read (binary data)
|
24
|
+
def encode
|
25
|
+
padded = false
|
26
|
+
@strategy.new(@source, @sink, ENCODE_READ_SZ) do |chunk, prev_chunk|
|
27
|
+
@bin_bytes += chunk.bytesize if chunk
|
28
|
+
if prev_chunk && chunk
|
29
|
+
CZTop::Z85.encode(prev_chunk)
|
30
|
+
elsif prev_chunk && chunk.nil? # last chunk
|
31
|
+
CZTop::Z85::Padded.encode(prev_chunk)
|
32
|
+
elsif prev_chunk.nil? && chunk.nil?
|
33
|
+
CZTop::Z85.encode("") # empty input
|
34
|
+
else
|
35
|
+
"" # very first chunk. don't encode anything yet...
|
36
|
+
end
|
37
|
+
end.execute
|
38
|
+
return @bin_bytes
|
39
|
+
end
|
40
|
+
|
41
|
+
# Decodes Z85 data from source and writes decoded data to sink. This is
|
42
|
+
# done until EOF is hit on the source.
|
43
|
+
#
|
44
|
+
# @return [Integer] number of bytes written (binary data)
|
45
|
+
def decode
|
46
|
+
@strategy.new(@source, @sink, DECODE_READ_SZ) do |chunk, prev_chunk|
|
47
|
+
if prev_chunk && chunk
|
48
|
+
CZTop::Z85.decode(prev_chunk)
|
49
|
+
elsif prev_chunk && chunk.nil?
|
50
|
+
CZTop::Z85::Padded.decode(prev_chunk)
|
51
|
+
elsif prev_chunk.nil? && chunk.nil?
|
52
|
+
CZTop::Z85.decode("") # empty input
|
53
|
+
else
|
54
|
+
"" # very first chunk. don't decode anything yet...
|
55
|
+
end
|
56
|
+
end.execute
|
57
|
+
return @bin_bytes
|
58
|
+
end
|
59
|
+
|
60
|
+
# @abstract
|
61
|
+
# Different encoding/decoding strategies (algorithms).
|
62
|
+
#
|
63
|
+
# This is mainly just for me to practice the GoF Strategy Pattern.
|
64
|
+
class Strategy
|
65
|
+
# @param source [IO] the source
|
66
|
+
# @param sink [IO] the sink
|
67
|
+
# @param read_sz [Integer] chunk size when reading from source
|
68
|
+
# @param xcode [Proc] block to encode or decode data
|
69
|
+
# @yieldparam chunk [String, nil] current chunk (or +nil+ after the
|
70
|
+
# last one)
|
71
|
+
# @yieldparam prev_chunk [String, nil] previous chunk (or +nil+ for
|
72
|
+
# the first time)
|
73
|
+
# @yieldreturn [String] encoded/decoded chunk to write to sink
|
74
|
+
def initialize(source, sink, read_sz, &xcode)
|
75
|
+
@source = source
|
76
|
+
@sink = sink
|
77
|
+
@read_sz = read_sz
|
78
|
+
@xcode = xcode
|
79
|
+
end
|
80
|
+
|
81
|
+
# @abstract
|
82
|
+
# Runs the algorithm.
|
83
|
+
# @raise [void]
|
84
|
+
def execute() raise NotImplementedError end
|
85
|
+
|
86
|
+
# A single thread that is either reading input, encoding/decoding, or
|
87
|
+
# writing output.
|
88
|
+
class Sequential < Strategy
|
89
|
+
# Runs the algorithm.
|
90
|
+
# @raise [void]
|
91
|
+
def execute
|
92
|
+
previous_chunk = nil
|
93
|
+
while true
|
94
|
+
chunk = @source.read(@read_sz)
|
95
|
+
@sink << @xcode.(chunk, previous_chunk)
|
96
|
+
break if chunk.nil?
|
97
|
+
previous_chunk = chunk
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Uses three threads:
|
103
|
+
#
|
104
|
+
# 1. reads from source
|
105
|
+
# 2. encodes/decodes
|
106
|
+
# 3. thread that writes to sink
|
107
|
+
#
|
108
|
+
# This might give a performance increase on truly parallel
|
109
|
+
# platforms such as Rubinius and JRuby (and multiple CPU cores).
|
110
|
+
#
|
111
|
+
class Parallel < Strategy
|
112
|
+
# Initializes the 2 sized queues used.
|
113
|
+
def initialize(*)
|
114
|
+
super
|
115
|
+
# @source
|
116
|
+
# |
|
117
|
+
# V
|
118
|
+
@source_queue = SizedQueue.new(20) # limit memory usage
|
119
|
+
# |
|
120
|
+
# V
|
121
|
+
# xcode
|
122
|
+
# |
|
123
|
+
# V
|
124
|
+
@sink_queue = SizedQueue.new(20) # limit memory usage
|
125
|
+
# |
|
126
|
+
# V
|
127
|
+
# @sink
|
128
|
+
end
|
129
|
+
|
130
|
+
# Runs the algorithm.
|
131
|
+
# @raise [void]
|
132
|
+
def execute
|
133
|
+
Thread.new { read }
|
134
|
+
Thread.new { xcode }
|
135
|
+
write
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
# Reads all chunks and pushes them into the source queue. Then
|
141
|
+
# pushes a +nil+ into the queue.
|
142
|
+
# @return [void]
|
143
|
+
def read
|
144
|
+
while chunk = @source.read(@read_sz)
|
145
|
+
@source_queue << chunk
|
146
|
+
end
|
147
|
+
@source_queue << nil
|
148
|
+
end
|
149
|
+
|
150
|
+
# Pops all chunks from the source queue, encodes or decodes them,
|
151
|
+
# and pushes the result into the sink queue. Then pushes a +nil+
|
152
|
+
# into the queue.
|
153
|
+
# @return [void]
|
154
|
+
def xcode
|
155
|
+
# Encode all but the last chunk with pure Z85.
|
156
|
+
previous_chunk = nil
|
157
|
+
while true
|
158
|
+
chunk = @source_queue.pop
|
159
|
+
|
160
|
+
# call @xcode for the trailing nil-chunk as well
|
161
|
+
@sink_queue << @xcode.(chunk, previous_chunk)
|
162
|
+
|
163
|
+
break if chunk.nil?
|
164
|
+
previous_chunk = chunk
|
165
|
+
end
|
166
|
+
@sink_queue << nil
|
167
|
+
end
|
168
|
+
|
169
|
+
# Pops all chunks from the sink queue and writes them to the sink.
|
170
|
+
# @return [void]
|
171
|
+
def write
|
172
|
+
while chunk = @sink_queue.pop
|
173
|
+
@sink << chunk
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cztop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrik Wenger
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: czmq-ffi-gen
|
@@ -209,7 +209,9 @@ dependencies:
|
|
209
209
|
description:
|
210
210
|
email:
|
211
211
|
- paddor@gmail.com
|
212
|
-
executables:
|
212
|
+
executables:
|
213
|
+
- z85decode
|
214
|
+
- z85encode
|
213
215
|
extensions: []
|
214
216
|
extra_rdoc_files: []
|
215
217
|
files:
|
@@ -241,6 +243,8 @@ files:
|
|
241
243
|
- examples/taxi_system/generate_keys.rb
|
242
244
|
- examples/taxi_system/start_broker.sh
|
243
245
|
- examples/taxi_system/start_clients.sh
|
246
|
+
- exe/z85decode
|
247
|
+
- exe/z85encode
|
244
248
|
- lib/cztop.rb
|
245
249
|
- lib/cztop/actor.rb
|
246
250
|
- lib/cztop/authenticator.rb
|
@@ -263,6 +267,8 @@ files:
|
|
263
267
|
- lib/cztop/socket/types.rb
|
264
268
|
- lib/cztop/version.rb
|
265
269
|
- lib/cztop/z85.rb
|
270
|
+
- lib/cztop/z85/padded.rb
|
271
|
+
- lib/cztop/z85/pipe.rb
|
266
272
|
- lib/cztop/zsock_options.rb
|
267
273
|
- perf/README.md
|
268
274
|
- perf/inproc_lat.rb
|