arpie 0.0.6 → 0.1.0
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.
- data/.gitignore +4 -0
- data/.yardopts +1 -0
- data/{BINARY_SPEC → BINARY.rdoc} +73 -28
- data/Gemfile +8 -0
- data/LICENCE +15 -0
- data/PROTOCOLS.rdoc +46 -0
- data/README.rdoc +17 -0
- data/Rakefile +6 -114
- data/arpie.gemspec +26 -0
- data/examples/em.rb +18 -0
- data/lib/arpie.rb +2 -4
- data/lib/arpie/binary.rb +18 -4
- data/lib/arpie/binary/bytes_type.rb +7 -2
- data/lib/arpie/binary/fixed_type.rb +11 -4
- data/lib/arpie/binary/list_type.rb +2 -0
- data/lib/arpie/binary/pack_type.rb +20 -10
- data/lib/arpie/em.rb +48 -0
- data/lib/arpie/error.rb +2 -4
- data/lib/arpie/protocol.rb +7 -23
- data/lib/arpie/version.rb +3 -0
- data/spec/binary_spec.rb +113 -8
- data/spec/bytes_binary_type_spec.rb +16 -3
- data/spec/em_spec.rb +27 -0
- data/spec/examples_spec.rb +11 -0
- data/spec/fixed_binary_type_spec.rb +2 -2
- data/spec/list_binary_type_spec.rb +9 -0
- data/spec/protocol_merge_and_split_spec.rb +3 -3
- data/spec/protocol_spec.rb +20 -43
- data/spec/spec_helper.rb +13 -12
- metadata +74 -83
- data/COPYING +0 -15
- data/README +0 -167
- data/lib/arpie/client.rb +0 -195
- data/lib/arpie/proxy.rb +0 -143
- data/lib/arpie/server.rb +0 -157
- data/lib/arpie/xmlrpc.rb +0 -108
- data/spec/client_server_spec.rb +0 -107
- data/tools/benchmark.rb +0 -52
- data/tools/protocol_benchmark.rb +0 -42
data/examples/em.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'arpie'
|
3
|
+
require 'eventmachine'
|
4
|
+
|
5
|
+
EM::run {
|
6
|
+
EM::start_server "127.0.0.1", 51210, Arpie::EventMachine::ArpieProtocol,
|
7
|
+
Arpie::SeparatorProtocol.new do |c|
|
8
|
+
def c.receive message
|
9
|
+
puts message.reverse
|
10
|
+
EM::stop_event_loop
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
EM::connect "127.0.0.1", 51210, Arpie::EventMachine::ArpieProtocol,
|
15
|
+
Arpie::SeparatorProtocol.new do |c|
|
16
|
+
c.send "hi"
|
17
|
+
end
|
18
|
+
}
|
data/lib/arpie.rb
CHANGED
data/lib/arpie/binary.rb
CHANGED
@@ -50,7 +50,7 @@ module Arpie
|
|
50
50
|
@@description ||= {}
|
51
51
|
@@hooks ||= {}
|
52
52
|
|
53
|
-
|
53
|
+
# @private
|
54
54
|
@@anonymous ||= {}
|
55
55
|
def self.__anonymous
|
56
56
|
@@anonymous[self]
|
@@ -58,7 +58,6 @@ module Arpie
|
|
58
58
|
def self.__anonymous= x
|
59
59
|
@@anonymous[self] = x
|
60
60
|
end
|
61
|
-
#:startdoc:
|
62
61
|
|
63
62
|
def initialize
|
64
63
|
@fields = {}
|
@@ -229,6 +228,17 @@ module Arpie
|
|
229
228
|
end
|
230
229
|
end
|
231
230
|
|
231
|
+
# You can use the field type as a class method to create new fields,
|
232
|
+
# as if you would call Binary.field with the appropriate parameters.
|
233
|
+
#
|
234
|
+
# Examples:
|
235
|
+
# uint8 :nameof, :default => 1
|
236
|
+
# fixed :fixedValue, :value => [1,2,3].pack("CCC")
|
237
|
+
# list :ls, :of => :uint8, :sizeof => :nameof
|
238
|
+
def self.method_missing meth, *va, &block
|
239
|
+
self.field(va.shift, meth, *va, &block)
|
240
|
+
end
|
241
|
+
|
232
242
|
# Specify that this Binary has a field of type +type+.
|
233
243
|
# See the class documentation for usage.
|
234
244
|
def self.field name, type = nil, opts = {}, &block
|
@@ -331,7 +341,7 @@ module Arpie
|
|
331
341
|
# a complete Binary.
|
332
342
|
def self.from binary, opts = {}
|
333
343
|
@@fields[self] ||= []
|
334
|
-
binary = * self.call_hooks(:pre_from, binary)
|
344
|
+
binary, * = * self.call_hooks(:pre_from, binary)
|
335
345
|
|
336
346
|
consumed_bytes = 0
|
337
347
|
obj = new
|
@@ -373,7 +383,7 @@ module Arpie
|
|
373
383
|
object.nil? and raise ArgumentError, "cannot #to nil"
|
374
384
|
@@fields[self] ||= []
|
375
385
|
r = []
|
376
|
-
object = * self.call_hooks(:pre_to, object)
|
386
|
+
object, * = * self.call_hooks(:pre_to, object)
|
377
387
|
|
378
388
|
@@fields[self].each {|field| # name, klass, kopts, inline_handler|
|
379
389
|
field.opts[:object] = object
|
@@ -435,5 +445,9 @@ module Arpie
|
|
435
445
|
def self.post_from &handler
|
436
446
|
self.add_hook(:post_from, handler)
|
437
447
|
end
|
448
|
+
|
449
|
+
private
|
450
|
+
def self.to_ary *a
|
451
|
+
end
|
438
452
|
end
|
439
453
|
end
|
@@ -29,7 +29,12 @@ module Arpie
|
|
29
29
|
elsif opts[:length]
|
30
30
|
len = case opts[:length]
|
31
31
|
when :all
|
32
|
-
|
32
|
+
if @pack_string == "Z"
|
33
|
+
npos = binary.index("\000") or raise EIncomplete
|
34
|
+
npos + 1
|
35
|
+
else
|
36
|
+
binary.size
|
37
|
+
end
|
33
38
|
when Symbol
|
34
39
|
opts[:object].send(opts[:length])
|
35
40
|
else
|
@@ -74,7 +79,7 @@ module Arpie
|
|
74
79
|
Binary.register_type(BytesBinaryType.new("a", :length => 1), :char)
|
75
80
|
Binary.register_type(BytesBinaryType.new("a"), :bytes)
|
76
81
|
Binary.register_type(BytesBinaryType.new("A"), :string)
|
77
|
-
Binary.register_type(BytesBinaryType.new("Z"), :nstring)
|
82
|
+
Binary.register_type(BytesBinaryType.new("Z", :length => :all), :nstring)
|
78
83
|
|
79
84
|
Binary.register_type(BytesBinaryType.new("M"), :quoted_printable)
|
80
85
|
Binary.register_type(BytesBinaryType.new("m"), :base64)
|
@@ -7,6 +7,7 @@ module Arpie
|
|
7
7
|
def from binary, opts
|
8
8
|
opts[:value] or raise ArgumentError, "Requires option :value"
|
9
9
|
sz = opts[:value].size
|
10
|
+
binary.size >= sz or incomplete!
|
10
11
|
existing = binary.unpack("a#{sz}")[0]
|
11
12
|
existing == opts[:value] or bogon! nil, ":fixed did not match data in packet"
|
12
13
|
|
@@ -23,13 +24,19 @@ module Arpie
|
|
23
24
|
class Binary
|
24
25
|
# Create a static field.
|
25
26
|
# This is an alias for:
|
26
|
-
# field :name, :fixed, :value => content
|
27
|
+
# field :name, :fixed, :value => content, :default => content
|
27
28
|
#
|
28
29
|
# If no name is specified, it is assumed that the user will
|
29
30
|
# never want to access it and a suitable one is autogenerated.
|
30
|
-
def self.static
|
31
|
-
|
32
|
-
|
31
|
+
def self.static name_or_content, content = nil
|
32
|
+
if content.nil?
|
33
|
+
name = "static-%x" % [name_or_content.hash]
|
34
|
+
content = name_or_content
|
35
|
+
else
|
36
|
+
name = name_or_content
|
37
|
+
end
|
38
|
+
|
39
|
+
field name, :fixed, :value => content, :default => content
|
33
40
|
end
|
34
41
|
|
35
42
|
# An alias for +static+.
|
@@ -14,16 +14,26 @@ module Arpie
|
|
14
14
|
count = 1 if count == 0
|
15
15
|
|
16
16
|
length += case directive
|
17
|
-
when 'A', 'a', 'C', 'c', 'Z', 'x'
|
18
|
-
|
19
|
-
when '
|
20
|
-
|
21
|
-
when '
|
22
|
-
|
23
|
-
when '
|
24
|
-
|
25
|
-
when '
|
26
|
-
|
17
|
+
when 'A', 'a', 'C', 'c', 'Z', 'x'
|
18
|
+
count
|
19
|
+
when 'B', 'b'
|
20
|
+
(count / 8.0).ceil
|
21
|
+
when 'D', 'd', 'E', 'G'
|
22
|
+
count * 8
|
23
|
+
when 'e', 'F', 'f', 'g'
|
24
|
+
count * 4
|
25
|
+
when 'H', 'h'
|
26
|
+
(count / 2.0).ceil
|
27
|
+
when 'I', 'i', 'L', 'l', 'N', 'V'
|
28
|
+
count * 4
|
29
|
+
when 'n', 'S', 's', 'v'
|
30
|
+
count * 2
|
31
|
+
when 'Q', 'q'
|
32
|
+
count * 8
|
33
|
+
when 'X'
|
34
|
+
count * -1
|
35
|
+
else
|
36
|
+
raise ArgumentError, "#{directive} is not supported"
|
27
37
|
end
|
28
38
|
end
|
29
39
|
|
data/lib/arpie/em.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Arpie
|
2
|
+
module EventMachine
|
3
|
+
# A EventMachine protocol implementing a simple protocol
|
4
|
+
# chain handler. To use, simply include it in your EM
|
5
|
+
# Connection moduleas you would any other EM protocol.
|
6
|
+
#
|
7
|
+
# The module expects a list of protocols given along with
|
8
|
+
# the module initializer:
|
9
|
+
# EM::start_server host, port, ArpieProtocol,
|
10
|
+
# Arpie::MarshalProtocol.new, Arpie::SizedProtocol.new
|
11
|
+
#
|
12
|
+
# To receive messages, override <tt>receive(message)</tt>, which will
|
13
|
+
# be called once for each message decoded with the given
|
14
|
+
# protocols.
|
15
|
+
#
|
16
|
+
# To send messages back over the same connection, simply call
|
17
|
+
# <tt>send(message)</tt>.
|
18
|
+
module ArpieProtocol
|
19
|
+
attr_reader :chain
|
20
|
+
|
21
|
+
def initialize *protocols
|
22
|
+
@chain = Arpie::ProtocolChain.new(*protocols)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Receive a message. Override this in your implemention.
|
26
|
+
def receive message
|
27
|
+
end
|
28
|
+
|
29
|
+
def receive_data data
|
30
|
+
begin
|
31
|
+
for msg in @chain.from(data)
|
32
|
+
receive msg
|
33
|
+
end
|
34
|
+
rescue Arpie::EIncomplete
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Send a message, encoding it with the given
|
40
|
+
# protocols.
|
41
|
+
def send message
|
42
|
+
for msg in @chain.to(message)
|
43
|
+
send_data(msg)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end # module EventMachine
|
48
|
+
end # module Arpie
|
data/lib/arpie/error.rb
CHANGED
@@ -6,18 +6,16 @@ module Arpie
|
|
6
6
|
class StreamError < IOError ; end
|
7
7
|
# Raised by arpie when a Protocol needs more data to parse a packet.
|
8
8
|
# Usually only of relevance to the programmer when using Protocol#from directly.
|
9
|
-
class EIncomplete <
|
9
|
+
class EIncomplete < Errno::EAGAIN ; end
|
10
10
|
|
11
|
-
#
|
11
|
+
# @private
|
12
12
|
# Used internally by arpie.
|
13
|
-
class ETryAgain < RuntimeError ; end
|
14
13
|
class YieldResult < RuntimeError
|
15
14
|
attr_reader :result
|
16
15
|
def initialize result
|
17
16
|
@result = result
|
18
17
|
end
|
19
18
|
end
|
20
|
-
# :startdoc:
|
21
19
|
|
22
20
|
# Call this if you think the stream has been corrupted, or
|
23
21
|
# non-protocol data arrived.
|
data/lib/arpie/protocol.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'shellwords'
|
2
|
+
require 'psych' if RUBY_VERSION =~ /^1.9/
|
2
3
|
require 'yaml'
|
3
4
|
require 'zlib'
|
4
5
|
|
5
6
|
module Arpie
|
6
7
|
MTU = 1024
|
7
|
-
# A RPC call. You need to wrap all calls sent over RPC protocols in this.
|
8
|
-
class RPCall < Struct.new(:ns, :meth, :argv, :uuid); end
|
9
8
|
|
10
9
|
# A ProtocolChain wraps one or more Protocols to provide a parser
|
11
10
|
# list, into which io data can be fed and parsed packets received; and
|
@@ -22,10 +21,6 @@ module Arpie
|
|
22
21
|
# A buffer holding all parsed, but unreturned messages.
|
23
22
|
attr_reader :messages
|
24
23
|
|
25
|
-
# The endpoint class of this Protocol.
|
26
|
-
# Defaults to Arpie::Endpoint
|
27
|
-
attr_accessor :endpoint_class
|
28
|
-
|
29
24
|
# Create a new Chain. Supply an Array of Protocol
|
30
25
|
# instances, where the leftmost is the innermost.
|
31
26
|
#
|
@@ -39,8 +34,6 @@ module Arpie
|
|
39
34
|
"The outermost protocol needs to be able to " +
|
40
35
|
"separate messages in a stream (#{protocols.inspect} does not)."
|
41
36
|
|
42
|
-
@endpoint_class = Arpie::Endpoint
|
43
|
-
|
44
37
|
@chain = protocols
|
45
38
|
@buffer = ""
|
46
39
|
@messages = []
|
@@ -163,7 +156,7 @@ module Arpie
|
|
163
156
|
# Write +message+ to +io+.
|
164
157
|
def write_message io, *messages
|
165
158
|
binary = messages.map {|m| to(m)}
|
166
|
-
io.write(binary)
|
159
|
+
io.write(binary.flatten.join(""))
|
167
160
|
end
|
168
161
|
|
169
162
|
def reset
|
@@ -180,12 +173,12 @@ module Arpie
|
|
180
173
|
# message separation within a stream.
|
181
174
|
CAN_SEPARATE_MESSAGES = false
|
182
175
|
|
183
|
-
#
|
176
|
+
# @private
|
184
177
|
# The stowbuffer hash used by assemble! No need to touch this, usually.
|
185
178
|
attr_reader :stowbuffer
|
179
|
+
# @private
|
186
180
|
# The meta-information hash used by assemble! No need to touch this, usually.
|
187
181
|
attr_reader :metabuffer
|
188
|
-
# :startdoc:
|
189
182
|
|
190
183
|
# Convert obj to on-the-wire format.
|
191
184
|
def to obj
|
@@ -212,12 +205,6 @@ module Arpie
|
|
212
205
|
0
|
213
206
|
end
|
214
207
|
|
215
|
-
# Call this within Protocol#from to reparse the current
|
216
|
-
# message.
|
217
|
-
def again!
|
218
|
-
raise ETryAgain
|
219
|
-
end
|
220
|
-
|
221
208
|
# Stow away a message in this protocols buffer for later reassembly.
|
222
209
|
# Optional argument: a token if you are planning to reassemble multiple
|
223
210
|
# interleaved/fragmented message streams.
|
@@ -338,16 +325,13 @@ module Arpie
|
|
338
325
|
# A protocol which encodes objects into YAML representation.
|
339
326
|
# Messages are arbitary yaml-encodable objects.
|
340
327
|
class YAMLProtocol < Protocol
|
341
|
-
CAN_SEPARATE_MESSAGES = true
|
342
|
-
|
343
328
|
def to object
|
344
|
-
yield YAML.dump(object)
|
329
|
+
yield YAML.dump(object)
|
345
330
|
end
|
346
331
|
|
347
332
|
def from binary
|
348
|
-
|
349
|
-
|
350
|
-
4 + index
|
333
|
+
yield YAML.load(binary)
|
334
|
+
binary.size
|
351
335
|
end
|
352
336
|
end
|
353
337
|
|
data/spec/binary_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe Arpie::Binary do
|
4
4
|
|
5
5
|
describe "empty:" do subject {
|
6
6
|
[
|
@@ -9,7 +9,7 @@ describe "Binary" do
|
|
9
9
|
""
|
10
10
|
]
|
11
11
|
}
|
12
|
-
|
12
|
+
include_examples "Binary Tests"
|
13
13
|
end
|
14
14
|
|
15
15
|
describe "basic sanity:" do subject {
|
@@ -24,7 +24,7 @@ describe "Binary" do
|
|
24
24
|
]
|
25
25
|
}
|
26
26
|
|
27
|
-
|
27
|
+
include_examples "Binary Tests with data"
|
28
28
|
end
|
29
29
|
|
30
30
|
describe ".virtual:" do subject {
|
@@ -32,18 +32,26 @@ describe "Binary" do
|
|
32
32
|
Class.new(Binary) do
|
33
33
|
field :a, :uint8
|
34
34
|
field :b, :uint8
|
35
|
+
|
35
36
|
virtual :c, :uint8 do |o| o.a * o.b end
|
37
|
+
|
38
|
+
v :d, :uint8 do |o| 1 + o.c end
|
36
39
|
end,
|
37
40
|
[1, 2].pack("CC")
|
38
41
|
]
|
39
42
|
}
|
40
43
|
|
41
|
-
it_should_behave_like "Binary Tests with data"
|
42
|
-
|
43
44
|
it "evaluates the block given" do
|
44
45
|
b, co = @c.from(@d)
|
45
46
|
b.c.should == b.a * b.b
|
46
47
|
end
|
48
|
+
|
49
|
+
it "evaluates nested virtuals" do
|
50
|
+
b, co = @c.from(@d)
|
51
|
+
b.d.should == b.c + 1
|
52
|
+
end
|
53
|
+
|
54
|
+
include_examples "Binary Tests with data"
|
47
55
|
end
|
48
56
|
|
49
57
|
describe ".aliases:" do subject {
|
@@ -58,7 +66,7 @@ describe "Binary" do
|
|
58
66
|
]
|
59
67
|
}
|
60
68
|
|
61
|
-
|
69
|
+
include_examples "Binary Tests with data"
|
62
70
|
end
|
63
71
|
|
64
72
|
describe ":default:" do subject {
|
@@ -72,8 +80,6 @@ describe "Binary" do
|
|
72
80
|
]
|
73
81
|
}
|
74
82
|
|
75
|
-
it_should_behave_like "Binary Tests with data"
|
76
|
-
|
77
83
|
it "does not use the default when a value was read" do
|
78
84
|
b, con = @c.from([1, 2].pack("CC"))
|
79
85
|
b.a.should == 1
|
@@ -86,7 +92,106 @@ describe "Binary" do
|
|
86
92
|
b.b.should == 5
|
87
93
|
end
|
88
94
|
|
95
|
+
it "uses the default for new binaries" do
|
96
|
+
new = @c.new
|
97
|
+
new.b.should == 5
|
98
|
+
end
|
99
|
+
|
100
|
+
include_examples "Binary Tests with data"
|
89
101
|
end
|
90
102
|
|
103
|
+
describe ":fixed:" do subject {
|
104
|
+
[
|
105
|
+
Class.new(Binary) do
|
106
|
+
field :a, :fixed, :value => "abc"
|
107
|
+
end,
|
108
|
+
["abc"].pack("a*")
|
109
|
+
]
|
110
|
+
}
|
111
|
+
|
112
|
+
it "does not use the default for new binaries when using :fixed" do
|
113
|
+
@c.new.a.should == nil
|
114
|
+
end
|
91
115
|
|
116
|
+
it "fails on parsing invalid values" do
|
117
|
+
proc { @c.from("abd") }.should raise_error Arpie::StreamError
|
118
|
+
end
|
119
|
+
|
120
|
+
it "fails on setting invalid values" do
|
121
|
+
proc {
|
122
|
+
c = @c.new
|
123
|
+
c.a = "abd"
|
124
|
+
c.to
|
125
|
+
}.should raise_error Arpie::StreamError
|
126
|
+
end
|
127
|
+
|
128
|
+
include_examples "Binary Tests with data"
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "static:" do subject {
|
132
|
+
[
|
133
|
+
Class.new(Binary) do
|
134
|
+
static :a, "abc"
|
135
|
+
end,
|
136
|
+
["abc"].pack("a*")
|
137
|
+
]
|
138
|
+
}
|
139
|
+
|
140
|
+
it "uses the default for new binaries" do
|
141
|
+
new = @c.new.to.should == "abc"
|
142
|
+
end
|
143
|
+
|
144
|
+
it "fails on parsing invalid values" do
|
145
|
+
proc { @c.from("abd") }.should raise_error Arpie::StreamError
|
146
|
+
end
|
147
|
+
|
148
|
+
it "fails on setting invalid values" do
|
149
|
+
proc {
|
150
|
+
c = @c.new
|
151
|
+
c.a = "abd"
|
152
|
+
c.to
|
153
|
+
}.should raise_error Arpie::StreamError
|
154
|
+
end
|
155
|
+
|
156
|
+
include_examples "Binary Tests with data"
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "mod:" do
|
160
|
+
specify {
|
161
|
+
klass = Class.new(Binary) do
|
162
|
+
uint8 :int, :mod => 1
|
163
|
+
string :test, :sizeof => :uint8, :sizeof_opts => { :mod => -1 }
|
164
|
+
end
|
165
|
+
|
166
|
+
b, read = klass.from("\x01\x05abcde")
|
167
|
+
b.int.should == 2
|
168
|
+
b.test.should == "abcd"
|
169
|
+
read.should == 6
|
170
|
+
}
|
171
|
+
end
|
172
|
+
|
173
|
+
describe "short notation" do
|
174
|
+
describe "invalid fields" do
|
175
|
+
specify do
|
176
|
+
proc {
|
177
|
+
Class.new(Binary) do
|
178
|
+
invalid :xy
|
179
|
+
end
|
180
|
+
}.should raise_error ArgumentError
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "simple types" do
|
185
|
+
subject {
|
186
|
+
[
|
187
|
+
Class.new(Binary) do
|
188
|
+
bytes :a, :length => 5
|
189
|
+
end,
|
190
|
+
["abc01"].pack("a5")
|
191
|
+
]
|
192
|
+
}
|
193
|
+
|
194
|
+
include_examples "Binary Tests with data"
|
195
|
+
end
|
196
|
+
end
|
92
197
|
end
|