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