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/.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
|