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/.gitignore
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private lib/**/*.rb - *.rdoc LICENCE
|
data/{BINARY_SPEC → BINARY.rdoc}
RENAMED
@@ -8,8 +8,8 @@ What a mouthful.
|
|
8
8
|
Let's try with an example:
|
9
9
|
|
10
10
|
class MyBinary < Arpie::Binary
|
11
|
-
|
12
|
-
|
11
|
+
uint8 :status
|
12
|
+
string :name, :sizeof => :uint8
|
13
13
|
end
|
14
14
|
|
15
15
|
Looks simple enough, doesn't it?
|
@@ -27,6 +27,15 @@ This works both ways, of course:
|
|
27
27
|
irb(main):006:0> a[0].to
|
28
28
|
=> "\001\005Arpie"
|
29
29
|
|
30
|
+
== Shorter notation for field definitions
|
31
|
+
|
32
|
+
Note that calling
|
33
|
+
|
34
|
+
field :name, :type
|
35
|
+
|
36
|
+
is equivalent to
|
37
|
+
|
38
|
+
type :name
|
30
39
|
|
31
40
|
== Usage within Arpie::Protocol
|
32
41
|
|
@@ -83,19 +92,19 @@ You can actually specify a field or virtual that was defined before the currentl
|
|
83
92
|
Example:
|
84
93
|
|
85
94
|
class Inner < Arpie::Binary
|
86
|
-
|
87
|
-
|
95
|
+
uint8 :sz
|
96
|
+
list :ls, :of => :uint8, :length => :sz
|
88
97
|
end
|
89
98
|
|
90
99
|
class Outer < Arpie::Binary
|
91
|
-
|
100
|
+
uint8 :totalsz
|
92
101
|
|
93
|
-
|
94
|
-
|
102
|
+
bytes :bytes, :length => :totalsz do
|
103
|
+
list :content, :of => Inner,
|
95
104
|
:length => :all
|
96
105
|
end
|
97
106
|
|
98
|
-
|
107
|
+
uint8 :end
|
99
108
|
end
|
100
109
|
|
101
110
|
Note that fields defined this way will NOT update their "length referral" - you will have to
|
@@ -105,26 +114,34 @@ do that manually.
|
|
105
114
|
This includes a prefixed "non-visible" field, which will be used to determine the
|
106
115
|
actual expected length of the data. Example:
|
107
116
|
|
108
|
-
|
117
|
+
bytes :blurbel, :sizeof => :lint16
|
109
118
|
|
110
119
|
Will expect a network-order short (16 bits), followed by the amout of bytes the short resolves to.
|
111
120
|
|
112
121
|
If the field type given in :sizeof requires additional parameters, you can pass them with
|
113
122
|
:sizeof_opts (just like with :list - :of).
|
114
123
|
|
124
|
+
=== :mod
|
125
|
+
Certain packed types (namely, numerics), allow for :mod, which will apply
|
126
|
+
a fixed modificator to the read/written value. Example:
|
127
|
+
|
128
|
+
string :test, :sizeof => :uint8, :sizeof_opts => { :mod => -1 }
|
129
|
+
|
130
|
+
This will always cut off the last character.
|
131
|
+
|
115
132
|
=== :list
|
116
133
|
|
117
134
|
A :list is an array of arbitary, same-type elements. The element type is given in the :list-specific
|
118
135
|
:of parameter:
|
119
136
|
|
120
|
-
|
137
|
+
list :my_list, :of => :lint16
|
121
138
|
|
122
139
|
This will complain of not being able to determine the size of the list - pass either a :sizeof,
|
123
140
|
or a :length parameter, described as above.
|
124
141
|
|
125
142
|
If your :of requires additional argument (a list of lists, for example), you can pass theses with :of_opts:
|
126
143
|
|
127
|
-
|
144
|
+
list :my_list_2, :sizeof => :uint8, :of => :string,
|
128
145
|
:of_opts => { :sizeof, :nint16 }
|
129
146
|
|
130
147
|
=== :bitfield
|
@@ -132,9 +149,9 @@ If your :of requires additional argument (a list of lists, for example), you can
|
|
132
149
|
The bitfield type unpacks one or more bytes into their bit values, for individual addressing:
|
133
150
|
|
134
151
|
class TestClass < Arpie::Binary
|
135
|
-
|
136
|
-
|
137
|
-
|
152
|
+
msg_bitfield :flags, :length => 8 do
|
153
|
+
bit :bool_1
|
154
|
+
bit :compound, :length => 7
|
138
155
|
# Take care not to leave any bits unmanaged - weird things happen otherwise.
|
139
156
|
end
|
140
157
|
end
|
@@ -155,9 +172,15 @@ This is pretty much all that you can do with it, for now.
|
|
155
172
|
The fixed type allows defining fixed strings that are always the same, both acting as a filler
|
156
173
|
and a safeguard (it will complain if it does not match):
|
157
174
|
|
158
|
-
|
175
|
+
fixed :blah, :value => "FIXED"
|
176
|
+
|
177
|
+
The alias Binary.static does this for you, but works slightly different:
|
178
|
+
|
179
|
+
static "aaa" # autogenerates a name with the assumption you don't want to access it
|
180
|
+
static :asdfg, "asdfg"
|
159
181
|
|
160
|
-
|
182
|
+
Fields declared with the "static" alias have a :default value already set, whereas
|
183
|
+
fields of the type :fixed do not.
|
161
184
|
|
162
185
|
== Nested Classes
|
163
186
|
|
@@ -165,11 +188,11 @@ Instead of pre-registered primitive data fiels you can pass in class names:
|
|
165
188
|
|
166
189
|
class Outer < Arpie::Binary
|
167
190
|
class Nested < Arpie::Binary
|
168
|
-
|
169
|
-
|
191
|
+
uint8 :a
|
192
|
+
uint8 :b
|
170
193
|
end
|
171
194
|
|
172
|
-
|
195
|
+
list :hah, :of => Nested, :sizeof => :uint8
|
173
196
|
end
|
174
197
|
|
175
198
|
== Inline Anonymous Classes
|
@@ -177,9 +200,9 @@ Instead of pre-registered primitive data fiels you can pass in class names:
|
|
177
200
|
Also, you can specify anonymous nested classes, which can be used to split data of the same type more fine-grainedly:
|
178
201
|
|
179
202
|
class TestClass < Arpie::Binary
|
180
|
-
|
181
|
-
|
182
|
-
|
203
|
+
bytes :outer, :length => 16 do
|
204
|
+
bytes :key1, :length => 8
|
205
|
+
bytes :key2, :length => 8
|
183
206
|
end
|
184
207
|
end
|
185
208
|
|
@@ -205,24 +228,24 @@ A virtual is a field definition that is not actually part of the binary data.
|
|
205
228
|
As you get to parse complex data structures, you might encounter the following case:
|
206
229
|
|
207
230
|
class TestClass < Arpie::Binary
|
208
|
-
|
209
|
-
|
231
|
+
uint8 :len_a
|
232
|
+
uint8 :len_b
|
210
233
|
|
211
234
|
field :middle, :something
|
212
235
|
|
213
|
-
|
236
|
+
list :matrix, :of => :uint8, :length => (value of :len_a * :len_b)
|
214
237
|
end
|
215
238
|
|
216
239
|
In this case, you will need to use a virtual attribute:
|
217
240
|
|
218
241
|
class TestClass < Arpie::Binary
|
219
|
-
|
220
|
-
|
242
|
+
uint8 :len_a
|
243
|
+
uint8 :len_b
|
221
244
|
|
222
245
|
field :middle, :something
|
223
246
|
|
224
247
|
virtual :v_len, :uint16 do |o| o.len_a * o.len_b end
|
225
|
-
|
248
|
+
list :hah, :of => Nested, :length => :v_len
|
226
249
|
|
227
250
|
pre_to do |o|
|
228
251
|
o.len_a = 4
|
@@ -235,6 +258,28 @@ virtual attributes are one-way - obviously they cannot be used to write out data
|
|
235
258
|
|
236
259
|
That is what the pre_to is for - it recalculates len_a and len_b to your specifications.
|
237
260
|
|
261
|
+
== Self-documenting Arpie::Binary
|
262
|
+
|
263
|
+
Every Arpie::Binary is self-documenting, as is this example:
|
264
|
+
|
265
|
+
class Doc < Arpie::Binary
|
266
|
+
describe "a document"
|
267
|
+
string :author, :sizeof => :uint16,
|
268
|
+
:description => "The author"
|
269
|
+
string :text, :sizeof => :uint16,
|
270
|
+
:description => "The document text"
|
271
|
+
end
|
272
|
+
|
273
|
+
puts Doc.describe
|
274
|
+
|
275
|
+
Will produce output like this:
|
276
|
+
|
277
|
+
Binary: a document
|
278
|
+
|
279
|
+
Fields: NAME TYPE WIDTH OF DESCRIPTION
|
280
|
+
author string uint16 The author
|
281
|
+
text string uint16 The document text
|
282
|
+
|
238
283
|
== hooks
|
239
284
|
|
240
285
|
Binary provides several hooks that can be used to mangle data in the transformation process.
|
data/Gemfile
ADDED
data/LICENCE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright Bernhard Stoeckner <le@e-ix.net> and contributors. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are
|
4
|
+
permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this list of
|
7
|
+
conditions and the following disclaimer.
|
8
|
+
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
10
|
+
of conditions and the following disclaimer in the documentation and/or other materials
|
11
|
+
provided with the distribution.
|
12
|
+
|
13
|
+
THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES,
|
14
|
+
INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
15
|
+
FOR A PARTICULAR PURPOSE.
|
data/PROTOCOLS.rdoc
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
== Writing custom Protocols
|
2
|
+
|
3
|
+
You can use arpies Protocol layer to write your custom protocol parser/emitters.
|
4
|
+
Consider the following, again very contrived, example. You have a linebased wire format,
|
5
|
+
which sends regular object updates in multiple lines, each holding a property to be updated.
|
6
|
+
What objects get updated is not relevant to this example.
|
7
|
+
|
8
|
+
For this example, we'll be using the SeparatorProtocol already contained in protocols.rb as
|
9
|
+
a base.
|
10
|
+
|
11
|
+
class AssembleExample < Arpie::Protocol
|
12
|
+
|
13
|
+
def from binary
|
14
|
+
# The wire format is simply a collection of lines
|
15
|
+
# where the first one is a number containing the
|
16
|
+
# # of lines to expect.
|
17
|
+
assemble! binary do |binaries, meta|
|
18
|
+
binaries.size >= 1 or incomplete!
|
19
|
+
binaries.size - 1 >= binaries[0].to_i or incomplete!
|
20
|
+
|
21
|
+
# Here, you can wrap all collected updates in
|
22
|
+
# whatever format you want it to be. We're just
|
23
|
+
# "joining" them to be a single array.
|
24
|
+
binaries.shift
|
25
|
+
binaries
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to object
|
30
|
+
yield object.size
|
31
|
+
object.each {|oo|
|
32
|
+
yield oo
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
p = Arpie::ProtocolChain.new(
|
38
|
+
AssembleExample.new,
|
39
|
+
Arpie::SeparatorProtocol.new
|
40
|
+
)
|
41
|
+
r, w = IO.pipe
|
42
|
+
|
43
|
+
p.write_message(w, %w{we want to be assembled})
|
44
|
+
|
45
|
+
p p.read_message(r)
|
46
|
+
# => ["we", "want", "to", "be", "assembled"]
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= What's this?
|
2
|
+
|
3
|
+
Arpie is a toolkit for handling binary data, network protocols, file formats, and
|
4
|
+
similar.
|
5
|
+
|
6
|
+
It provides:
|
7
|
+
|
8
|
+
- a DSL-like syntax for describing file formats (see {file:BINARY})
|
9
|
+
- stackable protocols that can be used to abstract layers of on-wire data (see {file:PROTOCOLS})
|
10
|
+
- some bits and glue to put it all together (for example, with eventmachine)
|
11
|
+
|
12
|
+
== Getting started
|
13
|
+
|
14
|
+
arpie is packaged up as a gem - just do <tt>gem install arpie</tt>
|
15
|
+
to get the newest version.
|
16
|
+
|
17
|
+
The latest source is available through git[https://github.com/elven/arpie].
|
data/Rakefile
CHANGED
@@ -1,116 +1,8 @@
|
|
1
|
-
|
2
|
-
require "
|
3
|
-
require
|
4
|
-
|
5
|
-
require "hanna/rdoctask"
|
6
|
-
rescue LoadError
|
7
|
-
require "rake/rdoctask"
|
8
|
-
end
|
9
|
-
require "fileutils"
|
10
|
-
include FileUtils
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'yard'
|
11
5
|
|
12
|
-
|
13
|
-
# Configuration
|
14
|
-
##############################################################################
|
15
|
-
NAME = "arpie"
|
16
|
-
VERS = "0.0.6"
|
17
|
-
CLEAN.include ["**/.*.sw?", "pkg", ".config", "rdoc", "coverage"]
|
18
|
-
RDOC_OPTS = ["--quiet", "--line-numbers", "--inline-source", '--title', \
|
19
|
-
"#{NAME}: A high-performing layered networking protocol framework. Simple to use, simple to extend.", \
|
20
|
-
'--main', 'README']
|
6
|
+
YARD::Rake::YardocTask.new
|
21
7
|
|
22
|
-
|
23
|
-
|
24
|
-
Rake::RDocTask.new do |rdoc|
|
25
|
-
rdoc.rdoc_dir = "rdoc"
|
26
|
-
rdoc.options += RDOC_OPTS
|
27
|
-
rdoc.rdoc_files.add DOCS + ["doc/*.rdoc", "lib/**/*.rb"]
|
28
|
-
end
|
29
|
-
|
30
|
-
desc "Packages up #{NAME}"
|
31
|
-
task :package => [:clean]
|
32
|
-
|
33
|
-
spec = Gem::Specification.new do |s|
|
34
|
-
s.name = NAME
|
35
|
-
s.rubyforge_project = "#{NAME}"
|
36
|
-
s.version = VERS
|
37
|
-
s.platform = Gem::Platform::RUBY
|
38
|
-
s.has_rdoc = true
|
39
|
-
s.extra_rdoc_files = DOCS + Dir["doc/*.rdoc"]
|
40
|
-
s.rdoc_options += RDOC_OPTS + ["--exclude", "^(examples|extras)\/"]
|
41
|
-
s.summary = "A high-performing layered networking protocol framework. Simple to use, simple to extend."
|
42
|
-
s.description = s.summary
|
43
|
-
s.author = "Bernhard Stoeckner"
|
44
|
-
s.email = "elven@swordcoast.net"
|
45
|
-
s.homepage = "http://#{NAME}.elv.es"
|
46
|
-
s.executables = []
|
47
|
-
s.required_ruby_version = ">= 1.8.4"
|
48
|
-
s.files = %w(COPYING README Rakefile) + Dir.glob("{bin,doc,spec,lib,tools,scripts,data}/**/*")
|
49
|
-
s.require_path = "lib"
|
50
|
-
s.bindir = "bin"
|
51
|
-
s.add_dependency('uuidtools', '>= 2.0.0')
|
52
|
-
end
|
53
|
-
|
54
|
-
Rake::GemPackageTask.new(spec) do |p|
|
55
|
-
p.need_tar = true
|
56
|
-
p.gem_spec = spec
|
57
|
-
end
|
58
|
-
|
59
|
-
desc "Install #{NAME} gem"
|
60
|
-
task :install do
|
61
|
-
sh %{rake package}
|
62
|
-
sh %{sudo gem1.8 install pkg/#{NAME}-#{VERS}}
|
63
|
-
end
|
64
|
-
|
65
|
-
desc "Regenerate proto classes"
|
66
|
-
task :protoc do
|
67
|
-
sh %{rprotoc --out=lib/arpie arpie.proto}
|
68
|
-
end
|
69
|
-
|
70
|
-
desc "Install #{NAME} gem without docs"
|
71
|
-
task :install_no_docs do
|
72
|
-
sh %{rake package}
|
73
|
-
sh %{sudo gem1.8 install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
|
74
|
-
end
|
75
|
-
|
76
|
-
desc "Uninstall #{NAME} gem"
|
77
|
-
task :uninstall => [:clean] do
|
78
|
-
sh %{sudo gem1.8 uninstall #{NAME}}
|
79
|
-
end
|
80
|
-
|
81
|
-
desc "Upload #{NAME} gem to rubyforge"
|
82
|
-
task :release => [:package] do
|
83
|
-
sh %{rubyforge login}
|
84
|
-
sh %{rubyforge add_release #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
|
85
|
-
sh %{rubyforge add_file #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
|
86
|
-
end
|
87
|
-
|
88
|
-
require "spec/rake/spectask"
|
89
|
-
|
90
|
-
desc "Run specs with coverage"
|
91
|
-
Spec::Rake::SpecTask.new("spec") do |t|
|
92
|
-
t.spec_files = FileList["spec/*_spec.rb"]
|
93
|
-
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
94
|
-
t.rcov_opts = File.read("spec/rcov.opts").split("\n")
|
95
|
-
t.rcov = true
|
96
|
-
end
|
97
|
-
|
98
|
-
desc "Run specs without coverage"
|
99
|
-
task :default => [:spec_no_cov]
|
100
|
-
Spec::Rake::SpecTask.new("spec_no_cov") do |t|
|
101
|
-
t.spec_files = FileList["spec/*_spec.rb"]
|
102
|
-
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
103
|
-
end
|
104
|
-
|
105
|
-
desc "Run rcov only"
|
106
|
-
Spec::Rake::SpecTask.new("rcov") do |t|
|
107
|
-
t.rcov_opts = File.read("spec/rcov.opts").split("\n")
|
108
|
-
t.spec_opts = File.read("spec/spec.opts").split("\n")
|
109
|
-
t.spec_files = FileList["spec/*_spec.rb"]
|
110
|
-
t.rcov = true
|
111
|
-
end
|
112
|
-
|
113
|
-
desc "check documentation coverage"
|
114
|
-
task :dcov do
|
115
|
-
sh "find lib -name '*.rb' | xargs dcov"
|
116
|
-
end
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
data/arpie.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/arpie/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Bernhard Stoeckner"]
|
6
|
+
gem.email = ["le@e-ix.net"]
|
7
|
+
gem.description = %q{Toolkit for handling binary data, network protocols, file formats, and similar}
|
8
|
+
gem.summary = gem.description
|
9
|
+
gem.homepage = "https://github.com/elven/arpie"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "arpie"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Arpie::VERSION
|
17
|
+
|
18
|
+
gem.post_install_message = "
|
19
|
+
You have installed arpie 0.1.0 or greater. This breaks
|
20
|
+
compatibility with previous versions (0.0.x).
|
21
|
+
|
22
|
+
Specifically, it removes all client/server code, and
|
23
|
+
XMLRPC integration, since EventMachine and similar does
|
24
|
+
all that in a much cleaner and more efficient way.
|
25
|
+
"
|
26
|
+
end
|