bindata 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of bindata might be problematic. Click here for more details.
- data/BSDL +22 -0
- data/COPYING +2 -2
- data/ChangeLog +12 -0
- data/NEWS +28 -0
- data/examples/list.rb +7 -7
- data/examples/nbt.rb +21 -38
- data/lib/bindata.rb +1 -1
- data/lib/bindata/alignment.rb +1 -4
- data/lib/bindata/array.rb +8 -4
- data/lib/bindata/base.rb +20 -17
- data/lib/bindata/base_primitive.rb +1 -0
- data/lib/bindata/bits.rb +0 -1
- data/lib/bindata/choice.rb +20 -42
- data/lib/bindata/deprecated.rb +5 -8
- data/lib/bindata/dsl.rb +167 -170
- data/lib/bindata/float.rb +0 -4
- data/lib/bindata/int.rb +1 -6
- data/lib/bindata/lazy.rb +8 -9
- data/lib/bindata/primitive.rb +4 -4
- data/lib/bindata/record.rb +36 -42
- data/lib/bindata/registry.rb +5 -4
- data/lib/bindata/rest.rb +0 -2
- data/lib/bindata/sanitize.rb +180 -194
- data/lib/bindata/skip.rb +0 -2
- data/lib/bindata/string.rb +3 -3
- data/lib/bindata/stringz.rb +2 -2
- data/lib/bindata/struct.rb +104 -47
- data/lib/bindata/wrapper.rb +22 -9
- data/manual.haml +74 -41
- data/manual.md +223 -123
- data/spec/array_spec.rb +20 -0
- data/spec/base_primitive_spec.rb +10 -0
- data/spec/choice_spec.rb +20 -6
- data/spec/deprecated_spec.rb +8 -0
- data/spec/example.rb +0 -2
- data/spec/primitive_spec.rb +19 -3
- data/spec/record_spec.rb +23 -7
- data/spec/registry_spec.rb +12 -13
- data/spec/string_spec.rb +39 -0
- data/spec/struct_spec.rb +35 -0
- data/spec/wrapper_spec.rb +10 -0
- data/tasks/rspec.rake +1 -1
- metadata +6 -6
- data/GPL +0 -339
data/manual.md
CHANGED
@@ -40,6 +40,8 @@ manipulating.
|
|
40
40
|
It supports all the common datatypes that are found in structured binary
|
41
41
|
data. Support for dependent and variable length fields is built in.
|
42
42
|
|
43
|
+
Last updated: 2011-06-14
|
44
|
+
|
43
45
|
## License
|
44
46
|
|
45
47
|
BinData is released under the same license as Ruby.
|
@@ -698,9 +700,11 @@ Three of the fields have parameters.
|
|
698
700
|
## Strings
|
699
701
|
|
700
702
|
BinData supports two types of strings - fixed size and zero terminated.
|
701
|
-
Strings are treated as a sequence of 8bit bytes. This is the
|
702
|
-
strings in Ruby 1.8.
|
703
|
-
|
703
|
+
Strings are treated internally as a sequence of 8bit bytes. This is the
|
704
|
+
same as strings in Ruby 1.8. BinData fully supports Ruby 1.9 string
|
705
|
+
encodings. See this [FAQ
|
706
|
+
entry](#im_using_ruby_19_how_do_i_use_string_encodings_with_bindata) for
|
707
|
+
details.
|
704
708
|
|
705
709
|
### Fixed Sized Strings
|
706
710
|
|
@@ -834,6 +838,9 @@ and implement the following three methods:
|
|
834
838
|
|
835
839
|
: The ruby value that a clear object should return.
|
836
840
|
|
841
|
+
If you wish to access parameters from inside these methods, you can
|
842
|
+
use `eval_parameter(key)`.
|
843
|
+
|
837
844
|
Here is an example of a big integer implementation.
|
838
845
|
|
839
846
|
# A custom big integer format. Binary format is:
|
@@ -842,7 +849,6 @@ Here is an example of a big integer implementation.
|
|
842
849
|
# positive form of the integer. The upper bit of each byte
|
843
850
|
# is set when there are more bytes in the stream.
|
844
851
|
class BigInteger < BinData::BasePrimitive
|
845
|
-
register_self
|
846
852
|
|
847
853
|
def value_to_binary_string(value)
|
848
854
|
negative = (value < 0) ? 1 : 0
|
@@ -1105,6 +1111,169 @@ Examples
|
|
1105
1111
|
|
1106
1112
|
# Advanced Topics
|
1107
1113
|
|
1114
|
+
## Debugging
|
1115
|
+
|
1116
|
+
BinData includes several features to make it easier to debug
|
1117
|
+
declarations.
|
1118
|
+
|
1119
|
+
### Tracing
|
1120
|
+
|
1121
|
+
BinData has the ability to trace the results of reading a data
|
1122
|
+
structure.
|
1123
|
+
|
1124
|
+
class A < BinData::Record
|
1125
|
+
int8 :a
|
1126
|
+
bit4 :b
|
1127
|
+
bit2 :c
|
1128
|
+
array :d, :initial_length => 6, :type => :bit1
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
BinData::trace_reading do
|
1132
|
+
A.read("\373\225\220")
|
1133
|
+
end
|
1134
|
+
{:ruby}
|
1135
|
+
|
1136
|
+
Results in the following being written to `STDERR`.
|
1137
|
+
|
1138
|
+
obj.a => -5
|
1139
|
+
obj.b => 9
|
1140
|
+
obj.c => 1
|
1141
|
+
obj.d[0] => 0
|
1142
|
+
obj.d[1] => 1
|
1143
|
+
obj.d[2] => 1
|
1144
|
+
obj.d[3] => 0
|
1145
|
+
obj.d[4] => 0
|
1146
|
+
obj.d[5] => 1
|
1147
|
+
{:ruby}
|
1148
|
+
|
1149
|
+
### Rest
|
1150
|
+
|
1151
|
+
The rest keyword will consume the input stream from the current position
|
1152
|
+
to the end of the stream.
|
1153
|
+
|
1154
|
+
class A < BinData::Record
|
1155
|
+
string :a, :read_length => 5
|
1156
|
+
rest :rest
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
obj = A.read("abcdefghij")
|
1160
|
+
obj.a #=> "abcde"
|
1161
|
+
obj.rest #=" "fghij"
|
1162
|
+
{:ruby}
|
1163
|
+
|
1164
|
+
### Hidden fields
|
1165
|
+
|
1166
|
+
The typical way to view the contents of a BinData record is to call
|
1167
|
+
`#snapshot` or `#inspect`. This gives all fields and their values. The
|
1168
|
+
`hide` keyword can be used to prevent certain fields from appearing in
|
1169
|
+
this output. This removes clutter and allows the developer to focus on
|
1170
|
+
what they are currently interested in.
|
1171
|
+
|
1172
|
+
class Testing < BinData::Record
|
1173
|
+
hide :a, :b
|
1174
|
+
string :a, :read_length => 10
|
1175
|
+
string :b, :read_length => 10
|
1176
|
+
string :c, :read_length => 10
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
obj = Testing.read(("a" * 10) + ("b" * 10) + ("c" * 10))
|
1180
|
+
obj.snapshot #=> {"c"=>"cccccccccc"}
|
1181
|
+
obj.to_binary_s #=> "aaaaaaaaaabbbbbbbbbbcccccccccc"
|
1182
|
+
{:ruby}
|
1183
|
+
|
1184
|
+
## Parameterizing User Defined Types
|
1185
|
+
|
1186
|
+
All BinData types have parameters that allow the behaviour of an object
|
1187
|
+
to be specified at initialization time. User defined types may also
|
1188
|
+
specify parameters. There are two types of parameters: mandatory and
|
1189
|
+
default.
|
1190
|
+
|
1191
|
+
### Mandatory Parameters
|
1192
|
+
|
1193
|
+
Mandatory parameters must be specified when creating an instance of the
|
1194
|
+
type.
|
1195
|
+
|
1196
|
+
class Polygon < BinData::Record
|
1197
|
+
mandatory_parameter :num_edges
|
1198
|
+
|
1199
|
+
uint8 :num, :value => lambda { vertices.length }
|
1200
|
+
array :vertices, :initial_length => :num_edges do
|
1201
|
+
int8 :x
|
1202
|
+
int8 :y
|
1203
|
+
end
|
1204
|
+
end
|
1205
|
+
|
1206
|
+
triangle = Polygon.new
|
1207
|
+
#=> raises ArgumentError: parameter 'num_edges' must be specified in Polygon
|
1208
|
+
|
1209
|
+
triangle = Polygon.new(:num_edges => 3)
|
1210
|
+
triangle.snapshot #=> {"num" => 3, "vertices" =>
|
1211
|
+
[{"x"=>0, "y"=>0}, {"x"=>0, "y"=>0}, {"x"=>0, "y"=>0}]}
|
1212
|
+
{:ruby}
|
1213
|
+
|
1214
|
+
### Default Parameters
|
1215
|
+
|
1216
|
+
Default parameters are optional. These parameters have a default value
|
1217
|
+
that may be overridden when an instance of the type is created.
|
1218
|
+
|
1219
|
+
class Phrase < BinData::Primitive
|
1220
|
+
default_parameter :number => "three"
|
1221
|
+
default_parameter :adjective => "blind"
|
1222
|
+
default_parameter :noun => "mice"
|
1223
|
+
|
1224
|
+
stringz :a, :initial_value => :number
|
1225
|
+
stringz :b, :initial_value => :adjective
|
1226
|
+
stringz :c, :initial_value => :noun
|
1227
|
+
|
1228
|
+
def get; "#{a} #{b} #{c}"; end
|
1229
|
+
def set(v)
|
1230
|
+
if /(.*) (.*) (.*)/ =~ v
|
1231
|
+
self.a, self.b, self.c = $1, $2, $3
|
1232
|
+
end
|
1233
|
+
end
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
obj = Phrase.new(:number => "two", :adjective => "deaf")
|
1237
|
+
obj.to_s #=> "two deaf mice"
|
1238
|
+
{:ruby}
|
1239
|
+
|
1240
|
+
## Extending existing Types
|
1241
|
+
|
1242
|
+
Sometimes you wish to create a new type that is simply an existing type
|
1243
|
+
with some predefined parameters. Examples could be an array with a
|
1244
|
+
specified type, or an integer with an initial value.
|
1245
|
+
|
1246
|
+
This can be achieved by subclassing the existing type and providing
|
1247
|
+
default parameters. These parameters can of course be overridden at
|
1248
|
+
initialisation time.
|
1249
|
+
|
1250
|
+
Here we define an array that contains big endian 16 bit integers. The
|
1251
|
+
array has a preferred initial length.
|
1252
|
+
|
1253
|
+
class IntArray < BinData::Array
|
1254
|
+
default_parameters :type => :uint16be, :initial_length => 5
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
arr = IntArray.new
|
1258
|
+
arr.size #=> 5
|
1259
|
+
{:ruby}
|
1260
|
+
|
1261
|
+
The initial length can be overridden at initialisation time.
|
1262
|
+
|
1263
|
+
arr = IntArray.new(:initial_length => 8)
|
1264
|
+
arr.size #=> 8
|
1265
|
+
{:ruby}
|
1266
|
+
|
1267
|
+
We can also use the block form syntax:
|
1268
|
+
|
1269
|
+
class IntArray < BinData::Array
|
1270
|
+
endian :big
|
1271
|
+
default_parameter :initial_length => 5
|
1272
|
+
|
1273
|
+
uint16
|
1274
|
+
end
|
1275
|
+
{:ruby}
|
1276
|
+
|
1108
1277
|
## Skipping over unused data
|
1109
1278
|
|
1110
1279
|
Some structures contain binary data that is irrelevant to your purposes.
|
@@ -1176,155 +1345,86 @@ versions of `string` and `int16le`.
|
|
1176
1345
|
c.to_binary_s #=> "\377\377\377\377\377"
|
1177
1346
|
{:ruby}
|
1178
1347
|
|
1179
|
-
|
1180
|
-
|
1181
|
-
Sometimes you wish to create a new type that is simply an existing type
|
1182
|
-
with some predefined parameters. Examples could be an array with a
|
1183
|
-
specified type, or an integer with an initial value.
|
1184
|
-
|
1185
|
-
This can be achieved with a wrapper. A wrapper creates a new type based
|
1186
|
-
on an existing type which has predefined parameters. These parameters
|
1187
|
-
can of course be overridden at initialisation time.
|
1188
|
-
|
1189
|
-
Here we define an array that contains big endian 16 bit integers. The
|
1190
|
-
array has a preferred initial length.
|
1191
|
-
|
1192
|
-
class IntArray < BinData::Wrapper
|
1193
|
-
endian :big
|
1194
|
-
array :type => :uint16, :initial_length => 5
|
1195
|
-
end
|
1196
|
-
|
1197
|
-
arr = IntArray.new
|
1198
|
-
arr.size #=> 5
|
1199
|
-
{:ruby}
|
1200
|
-
|
1201
|
-
The initial length can be overridden at initialisation time.
|
1202
|
-
|
1203
|
-
arr = IntArray.new(:initial_length => 8)
|
1204
|
-
arr.size #=> 8
|
1205
|
-
{:ruby}
|
1206
|
-
|
1207
|
-
## Parameterizing User Defined Types
|
1348
|
+
---------------------------------------------------------------------------
|
1208
1349
|
|
1209
|
-
|
1210
|
-
to be specified at initialization time. User defined types may also
|
1211
|
-
specify parameters. There are two types of parameters: mandatory and
|
1212
|
-
default.
|
1350
|
+
# FAQ
|
1213
1351
|
|
1214
|
-
|
1352
|
+
## I'm using Ruby 1.9. How do I use string encodings with BinData?
|
1215
1353
|
|
1216
|
-
|
1217
|
-
|
1218
|
-
type.
|
1354
|
+
BinData will internally use 8bit binary strings to represent the data.
|
1355
|
+
You do not need to worry about converting between encodings.
|
1219
1356
|
|
1220
|
-
|
1221
|
-
|
1357
|
+
If you wish BinData to present string data in a specific encoding, you
|
1358
|
+
can override `#snapshot` as illustrated below:
|
1222
1359
|
|
1223
|
-
|
1360
|
+
class UTF8String < BinData::String
|
1361
|
+
def snapshot
|
1362
|
+
super.force_encoding('UTF-8')
|
1363
|
+
end
|
1224
1364
|
end
|
1225
1365
|
|
1226
|
-
|
1227
|
-
|
1228
|
-
|
1229
|
-
arr = IntArray.new(:byte_count => 12)
|
1230
|
-
arr.snapshot #=> [0, 0, 0, 0, 0, 0]
|
1366
|
+
str = UTF8String.new("\xC3\x85\xC3\x84\xC3\x96")
|
1367
|
+
str #=> "ÅÄÖ"
|
1368
|
+
str.to_binary_s #=> "\xC3\x85\xC3\x84\xC3\x96"
|
1231
1369
|
{:ruby}
|
1232
1370
|
|
1233
|
-
|
1234
|
-
|
1235
|
-
Default parameters are optional. These parameters have a default value
|
1236
|
-
that may be overridden when an instance of the type is created.
|
1237
|
-
|
1238
|
-
class Phrase < BinData::Primitive
|
1239
|
-
default_parameter :number => "three"
|
1240
|
-
default_parameter :adjective => "blind"
|
1241
|
-
default_parameter :noun => "mice"
|
1371
|
+
## How do I speed up initialization?
|
1242
1372
|
|
1243
|
-
|
1244
|
-
stringz :b, :initial_value => :adjective
|
1245
|
-
stringz :c, :initial_value => :noun
|
1373
|
+
I'm doing this and it's slow.
|
1246
1374
|
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
self.a, self.b, self.c = $1, $2, $3
|
1251
|
-
end
|
1252
|
-
end
|
1375
|
+
999.times do |i|
|
1376
|
+
foo = Foo.new(:bar => "baz")
|
1377
|
+
...
|
1253
1378
|
end
|
1254
|
-
|
1255
|
-
obj = Phrase.new(:number => "two", :adjective => "deaf")
|
1256
|
-
obj.to_s #=> "two deaf mice"
|
1257
1379
|
{:ruby}
|
1258
1380
|
|
1259
|
-
|
1260
|
-
|
1261
|
-
BinData includes several features to make it easier to debug
|
1262
|
-
declarations.
|
1263
|
-
|
1264
|
-
### Tracing
|
1265
|
-
|
1266
|
-
BinData has the ability to trace the results of reading a data
|
1267
|
-
structure.
|
1381
|
+
BinData is optimized to be declarative. For imperative use, the
|
1382
|
+
above naïve approach will be slow. Below are faster alternatives.
|
1268
1383
|
|
1269
|
-
|
1270
|
-
|
1271
|
-
bit4 :b
|
1272
|
-
bit2 :c
|
1273
|
-
array :d, :initial_length => 6, :type => :bit1
|
1274
|
-
end
|
1384
|
+
The fastest approach is to reuse objects by calling `#clear` instead of
|
1385
|
+
instantiating more objects.
|
1275
1386
|
|
1276
|
-
|
1277
|
-
|
1387
|
+
foo = Foo.new(:bar => "baz")
|
1388
|
+
999.times do
|
1389
|
+
foo.clear
|
1390
|
+
...
|
1278
1391
|
end
|
1279
1392
|
{:ruby}
|
1280
1393
|
|
1281
|
-
|
1394
|
+
If you can't reuse objects, then consider the prototype pattern.
|
1282
1395
|
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
obj.d[2] => 1
|
1289
|
-
obj.d[3] => 0
|
1290
|
-
obj.d[4] => 0
|
1291
|
-
obj.d[5] => 1
|
1396
|
+
prototype = Foo.new(:bar => "baz")
|
1397
|
+
999.times do
|
1398
|
+
foo = prototype.new
|
1399
|
+
...
|
1400
|
+
end
|
1292
1401
|
{:ruby}
|
1293
1402
|
|
1294
|
-
|
1403
|
+
The prefered approach is to be declarative.
|
1295
1404
|
|
1296
|
-
|
1297
|
-
|
1405
|
+
class FooList < BinData::Array
|
1406
|
+
default_parameter :initial_length => 999
|
1298
1407
|
|
1299
|
-
|
1300
|
-
string :a, :read_length => 5
|
1301
|
-
rest :rest
|
1408
|
+
foo :bar => "baz"
|
1302
1409
|
end
|
1303
1410
|
|
1304
|
-
|
1305
|
-
|
1306
|
-
obj.rest #=" "fghij"
|
1411
|
+
array = FooList.new
|
1412
|
+
array.each { ... }
|
1307
1413
|
{:ruby}
|
1308
1414
|
|
1309
|
-
|
1310
|
-
|
1311
|
-
The typical way to view the contents of a BinData record is to call
|
1312
|
-
`#snapshot` or `#inspect`. This gives all fields and their values. The
|
1313
|
-
`hide` keyword can be used to prevent certain fields from appearing in
|
1314
|
-
this output. This removes clutter and allows the developer to focus on
|
1315
|
-
what they are currently interested in.
|
1415
|
+
## How do I model this complex nested format?
|
1316
1416
|
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1417
|
+
A common pattern in file formats and network protocols is
|
1418
|
+
[type-length-value](http://en.wikipedia.org/wiki/Type-length-value). The
|
1419
|
+
`type` field specifies how to interpret the `value`. This gives a way to
|
1420
|
+
dynamically structure the data format. An example is the TCP/IP protocol
|
1421
|
+
suite. An IP datagram can contain a nested TCP, UDP or other packet type as
|
1422
|
+
decided by the `protocol` field.
|
1323
1423
|
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1424
|
+
Modelling this structure can be difficult when the nesting is recursive, e.g.
|
1425
|
+
IP tunneling. Here is an example of the simplest possible recursive TLV structure,
|
1426
|
+
a [list that can contains atoms or other
|
1427
|
+
lists](http://bindata.rubyforge.org/svn/trunk/examples/list.rb).
|
1328
1428
|
|
1329
1429
|
---------------------------------------------------------------------------
|
1330
1430
|
|
data/spec/array_spec.rb
CHANGED
@@ -296,3 +296,23 @@ describe BinData::Array, "nested within an Array" do
|
|
296
296
|
subject.should == [ [4], [5, 6], [7, 8, 9] ]
|
297
297
|
end
|
298
298
|
end
|
299
|
+
|
300
|
+
describe BinData::Array, "subclassed" do
|
301
|
+
class IntArray < BinData::Array
|
302
|
+
endian :big
|
303
|
+
default_parameter :initial_element_value => 0
|
304
|
+
|
305
|
+
uint16 :initial_value => :initial_element_value
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should forward parameters" do
|
309
|
+
subject = IntArray.new(:initial_length => 7)
|
310
|
+
subject.length.should == 7
|
311
|
+
end
|
312
|
+
|
313
|
+
it "should be able to override default parameters" do
|
314
|
+
subject = IntArray.new(:initial_length => 3, :initial_element_value => 5)
|
315
|
+
subject.to_binary_s.should == "\x00\x05\x00\x05\x00\x05"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
data/spec/base_primitive_spec.rb
CHANGED
@@ -5,6 +5,16 @@ require File.expand_path(File.join(File.dirname(__FILE__), "example"))
|
|
5
5
|
require 'bindata/base_primitive'
|
6
6
|
require 'bindata/io'
|
7
7
|
|
8
|
+
describe BinData::BasePrimitive do
|
9
|
+
let(:r) { BinData::RegisteredClasses }
|
10
|
+
|
11
|
+
it "should not be registered" do
|
12
|
+
lambda {
|
13
|
+
r.lookup("BasePrimitive")
|
14
|
+
}.should raise_error(BinData::UnRegisteredTypeError)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
8
18
|
describe BinData::BasePrimitive, "all subclasses" do
|
9
19
|
class SubClassOfBasePrimitive < BinData::BasePrimitive
|
10
20
|
expose_methods_for_testing
|