bindata 0.11.1 → 1.0.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/ChangeLog CHANGED
@@ -1,9 +1,16 @@
1
1
  = BinData Changelog
2
2
 
3
- == Version 0.11.1 (2009-08-29)
3
+ == Version 1.0.0 (2009-09-13)
4
+
5
+ * Is now compatible with Ruby 1.9
6
+ * Added reference manual.
7
+ * Added #rel_offset to Base.
8
+ * Removed support for deprecated functionality.
9
+
10
+ == Version 0.11.1 (2009-08-28)
4
11
 
5
12
  * Allow wrapped types to work with struct's :onlyif parameter
6
- * Use Array#index instead of #find_index for compatibility with ruby 1.8.6
13
+ * Use Array#index instead of #find_index for compatibility with Ruby 1.8.6
7
14
  (patch courtesy of Joe Rozner).
8
15
 
9
16
  == Version 0.11.0 (2009-06-28)
@@ -20,7 +27,7 @@
20
27
  * Added debug_name
21
28
  * Added ability to trace reading
22
29
  * Primitives now behave as their value. Calling #value is no longer needed.
23
- * Renamed #to_s -> #to_binary_s to avoid confusion with ruby's #to_s.
30
+ * Renamed #to_s -> #to_binary_s to avoid confusion with Ruby's #to_s.
24
31
  * Added #assign as the generic way to assign values to objects.
25
32
  * Added :copy_on_change parameter to Choice.
26
33
  * Implement #offset for all objects.
@@ -75,7 +82,7 @@
75
82
  * Prevent warnings about method redefinition.
76
83
  * Allow Struct to masquerade as one of its fields.
77
84
  * Renamed String param :initial_length to :read_length.
78
- * BinData::Array now behaves more like the internal ruby array.
85
+ * BinData::Array now behaves more like the internal Ruby array.
79
86
 
80
87
  == Version 0.7.0 (2007-08-26)
81
88
 
data/NEWS CHANGED
@@ -1,3 +1,14 @@
1
+ = 1.0.0
2
+
3
+ Version 1.0.0 removes all deprecated features. If you are upgrading from a
4
+ version prior to 0.10.0 you will need to make changes to your code before it
5
+ will work with BinData 1.0.0 or later.
6
+
7
+ It is recommended to first upgrade to 0.11.1, and run your code with the -w
8
+ command line switch. This will turn on warning messages that should give you
9
+ hints about which parts of your code need changing. See the NEWS for 0.10.0
10
+ for details of the deprecated features.
11
+
1
12
  = 0.10.0
2
13
 
3
14
  There are several new features in this release. The major ones are:
data/README CHANGED
@@ -1,300 +1,1143 @@
1
- = BinData
1
+ Title: BinData Reference Manual
2
+
3
+ {:ruby: lang=ruby html_use_syntax=true}
4
+
5
+ # BinData
2
6
 
3
7
  A declarative way to read and write structured binary data.
4
8
 
5
- == What is it for?
9
+ ## What is it for?
6
10
 
7
11
  Do you ever find yourself writing code like this?
8
12
 
9
- io = File.open(...)
10
- len = io.read(2).unpack("v")
11
- name = io.read(len)
12
- width, height = io.read(8).unpack("VV")
13
- puts "Rectangle #{name} is #{width} x #{height}"
13
+ io = File.open(...)
14
+ len = io.read(2).unpack("v")[0]
15
+ name = io.read(len)
16
+ width, height = io.read(8).unpack("VV")
17
+ puts "Rectangle #{name} is #{width} x #{height}"
18
+ {:ruby}
14
19
 
15
20
  It's ugly, violates DRY and feels like you're writing Perl, not Ruby.
21
+
16
22
  There is a better way.
17
23
 
18
- class Rectangle < BinData::Record
19
- uint16le :len
20
- string :name, :read_length => :len
21
- uint32le :width
22
- uint32le :height
23
- end
24
+ class Rectangle < BinData::Record
25
+ endian :little
26
+ uint16 :len
27
+ string :name, :read_length => :len
28
+ uint32 :width
29
+ uint32 :height
30
+ end
24
31
 
25
- io = File.open(...)
26
- r = Rectangle.read(io)
27
- puts "Rectangle #{r.name} is #{r.width} x #{r.height}"
32
+ io = File.open(...)
33
+ r = Rectangle.read(io)
34
+ puts "Rectangle #{r.name} is #{r.width} x #{r.height}"
35
+ {:ruby}
28
36
 
29
37
  BinData makes it easy to specify the structure of the data you are
30
38
  manipulating.
31
39
 
32
40
  Read on for the tutorial, or go straight to the
33
- download[http://rubyforge.org/frs/?group_id=3252] page.
41
+ [download](http://rubyforge.org/frs/?group_id=3252) page.
42
+
43
+ ## License
44
+
45
+ BinData is released under the same license as Ruby.
34
46
 
35
- == Syntax
47
+ Copyright &copy; 2007 - 2009 [Dion Mendel](mailto:dion@lostrealm.com)
48
+
49
+ ---------------------------------------------------------------------------
50
+
51
+ # Overview
36
52
 
37
53
  BinData declarations are easy to read. Here's an example.
38
54
 
39
- class MyFancyFormat < BinData::Record
40
- stringz :comment
41
- uint8 :count, :check_value => lambda { (value % 2) == 0 }
42
- array :some_ints, :type => :int32be, :initial_length => :count
43
- end
55
+ class MyFancyFormat < BinData::Record
56
+ stringz :comment
57
+ uint8 :num_ints, :check_value => lambda { value.even? }
58
+ array :some_ints, :type => :int32be, :initial_length => :num_ints
59
+ end
60
+ {:ruby}
44
61
 
45
- The structure of the data in this example is
46
- 1. A zero terminated string
47
- 2. An unsigned 8bit integer which must by even
48
- 3. A sequence of unsigned 32bit integers in big endian form, the total
49
- number of which is determined by the value of the 8bit integer.
62
+ This fancy format describes the following collection of data:
50
63
 
51
- The BinData declaration matches the english description closely. Just for
52
- fun, lets look at how we'd implement this using #pack and #unpack. Here's
53
- the writing code, have a go at the reading code.
64
+ 1. A zero terminated string
65
+ 2. An unsigned 8bit integer which must by even
66
+ 3. A sequence of unsigned 32bit integers in big endian form, the total
67
+ number of which is determined by the value of the 8bit integer.
54
68
 
55
- comment = "this is a comment"
56
- some_ints = [2, 3, 8, 9, 1, 8]
57
- File.open(...) do |io|
58
- io.write([comment, some_ints.size, *some_ints].pack("Z*CN*"))
59
- end
69
+ The BinData declaration matches the English description closely.
70
+ Compare the above declaration with the equivalent `#unpack` code to read
71
+ such a data record.
60
72
 
73
+ def read_fancy_format(io)
74
+ comment, num_ints, rest = io.read.unpack("Z*Ca*")
75
+ raise ArgumentError, "ints must be even" unless num_ints.even?
76
+ some_ints = rest.unpack("N#{num_ints}")
77
+ {:comment => comment, :num_ints => num_ints, :some_ints => *some_ints}
78
+ end
79
+ {:ruby}
61
80
 
62
- The general format of a BinData declaration is a class containing one or more
63
- fields.
81
+ The BinData declaration clearly shows the structure of the record. The
82
+ `#unpack` code makes this structure opaque.
64
83
 
65
- class MyName < BinData::Record
66
- type field_name, :param1 => "foo", :param2 => bar, ...
67
- ...
68
- end
84
+ The general usage of BinData is to declare a structured collection of
85
+ data as a user defined record. This record can be instantiated, read,
86
+ written and manipulated without the user having to be concerned with the
87
+ underlying binary representation of the data.
69
88
 
70
- *type* is the name of a supplied type (e.g. <tt>uint32be</tt>, +string+)
71
- or a user defined type. For user defined types, convert the class name
72
- from CamelCase to lowercase underscore_style.
89
+ ---------------------------------------------------------------------------
73
90
 
74
- *field_name* is the name by which you can access the data. Use either a
75
- String or a Symbol.
91
+ # Common Operations
76
92
 
77
- Each field may have *parameters* for how to process the data. The
78
- parameters are passed as a Hash using Symbols for keys.
93
+ There are operations common to all BinData types, including user defined
94
+ ones. These are summarised here.
79
95
 
80
- == Handling dependencies between fields
96
+ ## Reading and writing
81
97
 
82
- A common occurance in binary file formats is one field depending upon the
83
- value of another. e.g. A string preceded by it's length.
98
+ `::read(io)`
84
99
 
85
- As an example, let's assume a Pascal style string where the byte preceding
86
- the string contains the string's length.
100
+ : Creates a BinData object and reads its value from the given string
101
+ or `IO`. The newly created object is returned.
87
102
 
88
- # reading
89
- io = File.open(...)
90
- len = io.getc
91
- str = io.read(len)
92
- puts "string is " + str
103
+ str = BinData::Stringz::read("string1\0string2")
104
+ str.snapshot #=> "string1"
105
+ {:ruby}
93
106
 
94
- # writing
95
- io = File.open(...)
96
- str = "this is a string"
97
- io.putc(str.length)
98
- io.write(str)
107
+ `#read(io)`
99
108
 
100
- Here's how we'd implement the same example with BinData.
109
+ : Reads and assigns binary data read from `io`.
101
110
 
102
- class PascalString < BinData::Record
103
- uint8 :len, :value => lambda { data.length }
104
- string :data, :read_length => :len
105
- end
111
+ obj = BinData::Uint16be.new
112
+ obj.read("\022\064")
113
+ obj.value #=> 4660
114
+ {:ruby}
106
115
 
107
- # reading
108
- io = File.open(...)
109
- ps = PascalString.new
110
- ps.read(io)
111
- puts "string is " + ps.data
116
+ `#write(io)`
112
117
 
113
- # writing
114
- io = File.open(...)
115
- ps = PascalString.new
116
- ps.data = "this is a string"
117
- ps.write(io)
118
+ : Writes the binary representation of the object to `io`.
118
119
 
119
- This syntax needs explaining. Let's simplify by examining reading and
120
- writing separately.
120
+ File.open("...", "wb") do |io|
121
+ obj = BinData::Uint64be.new
122
+ obj.value = 568290145640170
123
+ obj.write(io)
124
+ end
125
+ {:ruby}
126
+
127
+ `#to_binary_s`
128
+
129
+ : Returns the binary representation of this object as a string.
130
+
131
+ obj = BinData::Uint16be.new
132
+ obj.assign(4660)
133
+ obj.to_binary_s #=> "\022\064"
134
+ {:ruby}
135
+
136
+ ## Manipulating
137
+
138
+ `#assign(value)`
139
+
140
+ : Assigns the given value to this object. `value` can be of the same
141
+ format as produced by `#snapshot`, or it can be a compatible data
142
+ object.
143
+
144
+ arr = BinData::Array.new(:type => :uint8)
145
+ arr.assign([1, 2, 3, 4])
146
+ arr.snapshot #=> [1, 2, 3, 4]
147
+ {:ruby}
148
+
149
+ `#clear`
150
+
151
+ : Resets this object to its initial state.
152
+
153
+ obj = BinData::Int32be.new(:initial_value => 42)
154
+ obj.assign(50)
155
+ obj.clear
156
+ obj.value #=> 42
157
+ {:ruby}
158
+
159
+ `#clear?`
160
+
161
+ : Returns whether this object is in its initial state.
162
+
163
+ arr = BinData::Array.new(:type => :uint16be, :initial_length => 5)
164
+ arr[3] = 42
165
+ arr.clear? #=> false
166
+
167
+ arr[3].clear
168
+ arr.clear? #=> true
169
+ {:ruby}
121
170
 
122
- class PascalStringReader < BinData::Record
123
- uint8 :len
124
- string :data, :read_length => :len
125
- end
171
+ ## Inspecting
126
172
 
127
- This states that when reading the string, the initial length of the string
128
- (and hence the number of bytes to read) is determined by the value of the
129
- +len+ field.
173
+ `#num_bytes`
130
174
 
131
- Note that <tt>:read_length => :len</tt> is syntactic sugar for
132
- <tt>:read_length => lambda { len }</tt>, but more on that later.
175
+ : Returns the number of bytes required for the binary representation
176
+ of this object.
133
177
 
134
- class PascalStringWriter < BinData::Record
135
- uint8 :len, :value => lambda { data.length }
136
- string :data
137
- end
178
+ arr = BinData::Array.new(:type => :uint16be, :initial_length => 5)
179
+ arr[0].num_bytes #=> 2
180
+ arr.num_bytes #=> 10
181
+ {:ruby}
138
182
 
139
- This states that the value of +len+ is always equal to the length of +data+.
140
- +len+ may not be manually modified.
183
+ `#snapshot`
141
184
 
142
- Combining these two definitions gives the definition for +PascalString+ as
143
- previously defined.
185
+ : Returns the value of this object as primitive Ruby objects
186
+ (numerics, strings, arrays and hashs). The output of `#snapshot`
187
+ may be useful for serialization or as a reduced memory usage
188
+ representation.
144
189
 
145
- Once thing to note with dependencies, is that a field can only depend on one
146
- before it. You can't have a string which has the characters first and the
147
- length afterwards.
190
+ obj = BinData::Uint8.new
191
+ obj.assign(3)
192
+ obj + 3 #=> 6
148
193
 
149
- == Predefined Types
194
+ obj.snapshot #=> 3
195
+ obj.snapshot.class #=> Fixnum
196
+ {:ruby}
150
197
 
151
- These are the predefined types. Custom types can be created by composing
152
- these types.
198
+ `#offset`
153
199
 
154
- BinData::String:: A sequence of bytes.
155
- BinData::Stringz:: A zero terminated sequence of bytes.
200
+ : Returns the offset of this object with respect to the most distant
201
+ ancestor structure it is contained within. This is most likely to
202
+ be used with arrays and records.
156
203
 
157
- BinData::Array:: A list of objects of the same type.
158
- BinData::Choice:: A choice between several objects.
159
- BinData::Struct:: An ordered collection of named objects.
204
+ class Tuple < BinData::Record
205
+ int8 :a
206
+ int8 :b
207
+ end
160
208
 
161
- BinData::Int8:: Signed 8 bit integer.
162
- BinData::Int16le:: Signed 16 bit integer (little endian).
163
- BinData::Int16be:: Signed 16 bit integer (big endian).
164
- BinData::Int32le:: Signed 32 bit integer (little endian).
165
- BinData::Int32be:: Signed 32 bit integer (big endian).
166
- BinData::Int64le:: Signed 64 bit integer (little endian).
167
- BinData::Int64be:: Signed 64 bit integer (big endian).
209
+ arr = BinData::Array.new(:type => :tuple, :initial_length => 3)
210
+ arr[2].b.offset #=> 5
211
+ {:ruby}
168
212
 
169
- BinData::Uint8:: Unsigned 8 bit integer.
170
- BinData::Uint16le:: Unsigned 16 bit integer (little endian).
171
- BinData::Uint16be:: Unsigned 16 bit integer (big endian).
172
- BinData::Uint32le:: Unsigned 32 bit integer (little endian).
173
- BinData::Uint32be:: Unsigned 32 bit integer (big endian).
174
- BinData::Uint64le:: Unsigned 64 bit integer (little endian).
175
- BinData::Uint64be:: Unsigned 64 bit integer (big endian).
213
+ `#rel_offset`
176
214
 
177
- BinData::Bit1:: 1 bit unsigned integer (big endian).
178
- BinData::Bit2:: 2 bit unsigned integer (big endian).
179
- ...
180
- BinData::Bit63:: 63 bit unsigned integer (big endian).
215
+ : Returns the offset of this object with respect to the parent
216
+ structure it is contained within. Compare this to `#offset`.
181
217
 
182
- BinData::Bit1le:: 1 bit unsigned integer (little endian).
183
- BinData::Bit2le:: 2 bit unsigned integer (little endian).
184
- ...
185
- BinData::Bit63le:: 63 bit unsigned integer (little endian).
218
+ class Tuple < BinData::Record
219
+ int8 :a
220
+ int8 :b
221
+ end
186
222
 
187
- BinData::FloatLe:: Single precision floating point number (little endian).
188
- BinData::FloatBe:: Single precision floating point number (big endian).
189
- BinData::DoubleLe:: Double precision floating point number (little endian).
190
- BinData::DoubleBe:: Double precision floating point number (big endian).
223
+ arr = BinData::Array.new(:type => :tuple, :initial_length => 3)
224
+ arr[2].b.rel_offset #=> 1
225
+ {:ruby}
191
226
 
192
- BinData::Rest:: Consumes the rest of the input stream.
227
+ `#inspect`
193
228
 
194
- == Parameters
229
+ : Returns a human readable representation of this object. This is a
230
+ shortcut to #snapshot.inspect.
195
231
 
196
- class PascalStringWriter < BinData::Record
197
- uint8 :len, :value => lambda { data.length }
198
- string :data
199
- end
232
+ ---------------------------------------------------------------------------
200
233
 
201
- Revisiting the Pascal string writer, we see that a field can take
202
- parameters. Parameters are passed as a Hash, where the key is a symbol.
203
- It should be noted that parameters are designed to be lazily evaluated,
204
- possibly multiple times. This means that any parameter value must not have
205
- side effects.
234
+ # Records
235
+
236
+ The general format of a BinData record declaration is a class containing
237
+ one or more fields.
238
+
239
+ class MyName < BinData::Record
240
+ type field_name, :param1 => "foo", :param2 => bar, ...
241
+ ...
242
+ end
243
+ {:ruby}
244
+
245
+ `type`
246
+ : is the name of a supplied type (e.g. `uint32be`, `string`, `array`)
247
+ or a user defined type. For user defined types, the class name is
248
+ converted from `CamelCase` to lowercased `underscore_style`.
249
+
250
+ `field_name`
251
+ : is the name by which you can access the data. Use either a
252
+ `String` or a `Symbol`.
253
+
254
+ Each field may have optional *parameters* for how to process the data.
255
+ The parameters are passed as a `Hash` with `Symbols` for keys.
256
+ Parameters are designed to be lazily evaluated, possibly multiple times.
257
+ This means that any parameter value must not have side effects.
206
258
 
207
259
  Here are some examples of legal values for parameters.
208
260
 
209
- * :param => 5
210
- * :param => lambda { 5 + 2 }
211
- * :param => lambda { foo + 2 }
212
- * :param => :foo
261
+ * `:param => 5`
262
+ * `:param => lambda { 5 + 2 }`
263
+ * `:param => lambda { foo + 2 }`
264
+ * `:param => :foo`
213
265
 
214
- The simplest case is when the value is a literal value, such as 5.
266
+ The simplest case is when the value is a literal value, such as `5`.
215
267
 
216
- If the value is not a literal, it is expected to be a lambda. The lambda
217
- will be evaluated in the context of the parent, in this case the parent is
218
- an instance of +PascalStringWriter+.
268
+ If the value is not a literal, it is expected to be a lambda. The
269
+ lambda will be evaluated in the context of the parent, in this case the
270
+ parent is an instance of `MyName`.
219
271
 
220
272
  If the value is a symbol, it is taken as syntactic sugar for a lambda
221
273
  containing the value of the symbol.
222
- e.g <tt>:param => :foo</tt> is <tt>:param => lambda { foo }</tt>
274
+ e.g `:param => :foo` is `:param => lambda { foo }`
275
+
276
+ ## Specifying default endian
277
+
278
+ The endianess of numeric types must be explicitly defined so that the
279
+ code produced is independent of architecture. However, explicitly
280
+ specifying the endian for each numeric field can result in a bloated
281
+ declaration that can be difficult to read.
282
+
283
+ class A < BinData::Record
284
+ int16be :a
285
+ int32be :b
286
+ int16le :c # <-- Note little endian!
287
+ int32be :d
288
+ float_be :e
289
+ array :f, :type => :uint32be
290
+ end
291
+ {:ruby}
292
+
293
+ The `endian` keyword can be used to set the default endian. This makes
294
+ the declaration easier to read. Any numeric field that doesn't use the
295
+ default endian can explicitly override it.
296
+
297
+ class A < BinData::Record
298
+ endian :big
299
+
300
+ int16 :a
301
+ int32 :b
302
+ int16le :c # <-- Note how this little endian now stands out
303
+ int32 :d
304
+ float :e
305
+ array :f, :type => :uint32
306
+ end
307
+ {:ruby}
308
+
309
+ The increase in clarity can be seen with the above example. The
310
+ `endian` keyword will cascade to nested types, as illustrated with the
311
+ array in the above example.
312
+
313
+ ## Optional fields
314
+
315
+ A record may contain optional fields. The optional state of a field is
316
+ decided by the `:onlyif` parameter. If the value of this parameter is
317
+ `false`, then the field will be as if it didn't exist in the record.
318
+
319
+ class RecordWithOptionalField < BinData::Record
320
+ ...
321
+ uint8 :comment_flag
322
+ string :comment, :length => 20, :onlyif => :has_comment?
323
+
324
+ def has_comment?
325
+ comment_flag.nonzero?
326
+ end
327
+ end
328
+ {:ruby}
329
+
330
+ In the above example, the `comment` field is only included in the record
331
+ if the value of the `comment_flag` field is non zero.
332
+
333
+ ## Handling dependencies between fields
334
+
335
+ A common occurence in binary file formats is one field depending upon
336
+ the value of another. e.g. A string preceded by its length.
337
+
338
+ As an example, let's assume a Pascal style string where the byte
339
+ preceding the string contains the string's length.
340
+
341
+ # reading
342
+ io = File.open(...)
343
+ len = io.getc
344
+ str = io.read(len)
345
+ puts "string is " + str
346
+
347
+ # writing
348
+ io = File.open(...)
349
+ str = "this is a string"
350
+ io.putc(str.length)
351
+ io.write(str)
352
+ {:ruby}
353
+
354
+ Here's how we'd implement the same example with BinData.
355
+
356
+ class PascalString < BinData::Record
357
+ uint8 :len, :value => lambda { data.length }
358
+ string :data, :read_length => :len
359
+ end
360
+
361
+ # reading
362
+ io = File.open(...)
363
+ ps = PascalString.new
364
+ ps.read(io)
365
+ puts "string is " + ps.data
366
+
367
+ # writing
368
+ io = File.open(...)
369
+ ps = PascalString.new
370
+ ps.data = "this is a string"
371
+ ps.write(io)
372
+ {:ruby}
373
+
374
+ This syntax needs explaining. Let's simplify by examining reading and
375
+ writing separately.
376
+
377
+ class PascalStringReader < BinData::Record
378
+ uint8 :len
379
+ string :data, :read_length => :len
380
+ end
381
+ {:ruby}
382
+
383
+ This states that when reading the string, the initial length of the
384
+ string (and hence the number of bytes to read) is determined by the
385
+ value of the `len` field.
386
+
387
+ Note that `:read_length => :len` is syntactic sugar for
388
+ `:read_length => lambda { len }`, as described previously.
389
+
390
+ class PascalStringWriter < BinData::Record
391
+ uint8 :len, :value => lambda { data.length }
392
+ string :data
393
+ end
394
+ {:ruby}
395
+
396
+ This states that the value of `len` is always equal to the length of
397
+ `data`. `len` may not be manually modified.
398
+
399
+ Combining these two definitions gives the definition for `PascalString`
400
+ as previously defined.
401
+
402
+ It is important to note with dependencies, that a field can only depend
403
+ on one before it. You can't have a string which has the characters
404
+ first and the length afterwards.
405
+
406
+ ---------------------------------------------------------------------------
407
+
408
+ # Primitive Types
409
+
410
+ BinData provides support for the most commonly used primitive types that
411
+ are used when working with binary data. Namely:
412
+
413
+ * fixed size strings
414
+ * zero terminated strings
415
+ * byte based integers - signed or unsigned, big or little endian and
416
+ of any size
417
+ * bit based integers - unsigned big or little endian integers of any
418
+ size
419
+ * floating point numbers - single or double precision floats in either
420
+ big or little endian
421
+
422
+ Primitives may be manipulated individually, but is more common to work
423
+ with them as part of a record.
424
+
425
+ Examples of individual usage:
426
+
427
+ int16 = BinData::Int16be.new
428
+ int16.value = 941
429
+ int16.to_binary_s #=> "\003\255"
430
+
431
+ fl = BinData::FloatBe.read("\100\055\370\124") #=> 2.71828174591064
432
+ fl.num_bytes #=> 4
433
+
434
+ fl * int16 #=> 2557.90320057996
435
+ {:ruby}
436
+
437
+ There are several parameters that are specific to primitives.
438
+
439
+ `:initial_value`
440
+
441
+ : This contains the initial value that the primitive will contain
442
+ after initialization. This is useful for setting default values.
443
+
444
+ obj = BinData::String.new(:initial_value => "hello ")
445
+ obj + "world" #=> "hello world"
446
+
447
+ obj.assign("good-bye " )
448
+ obj + "world" #=> "good-bye world"
449
+ {:ruby}
450
+
451
+ `:value`
452
+
453
+ : The primitive will always contain this value. Reading or assigning
454
+ will not change the value. This parameter is used to define
455
+ constants or dependent fields.
456
+
457
+ pi = BinData::FloatLe.new(:value => Math::PI)
458
+ pi.assign(3)
459
+ puts pi #=> 3.14159265358979
460
+ {:ruby}
461
+
462
+ `:check_value`
463
+
464
+ : When reading, will raise a `ValidityError` if the value read does
465
+ not match the value of this parameter.
466
+
467
+ obj = BinData::String.new(:check_value => lambda { /aaa/ =~ value })
468
+ obj.read("baaa!") #=> "baaa!"
469
+ obj.read("bbb") #=> raises ValidityError
470
+
471
+ obj = BinData::String.new(:check_value => "foo")
472
+ obj.read("foo") #=> "foo"
473
+ obj.read("bar") #=> raises ValidityError
474
+ {:ruby}
475
+
476
+ ## Numerics
477
+
478
+ There are three kinds of numeric types that are supported by BinData.
479
+
480
+ ### Byte based integers
481
+
482
+ These are the common integers that are used in most low level
483
+ programming languages (C, C++, Java etc). These integers can be signed
484
+ or unsigned. The endian must be specified so that the conversion is
485
+ independent of architecture. The bit size of these integers must be a
486
+ multiple of 8. Examples of byte based integers are:
487
+
488
+ `uint16be`
489
+ : unsigned 16 bit big endian integer
490
+
491
+ `int8`
492
+ : signed 8 bit integer
493
+
494
+ `int32le`
495
+ : signed 32 bit little endian integer
496
+
497
+ `uint40be`
498
+ : unsigned 40 bit big endian integer
499
+
500
+ The `be` | `le` suffix may be omitted if the `endian` keyword is in use.
501
+
502
+ ### Bit based integers
503
+
504
+ These unsigned integers are used to define bitfields in records.
505
+ Bitfields are big endian by default but little endian may be specified
506
+ explicitly. Little endian bitfields are rare, but do occur in older
507
+ file formats (e.g. The file allocation table for FAT12 filesystems is
508
+ stored as an array of 12bit little endian integers).
509
+
510
+ An array of bit based integers will be packed according to their endian.
511
+
512
+ In a record, adjacent bitfields will be packed according to their
513
+ endian. All other fields are byte aligned.
514
+
515
+ Examples of bit based integers are:
516
+
517
+ `bit1`
518
+ : 1 bit big endian integer (may be used as boolean)
519
+
520
+ `bit4_le`
521
+ : 4 bit little endian integer
522
+
523
+ `bit32`
524
+ : 32 bit big endian integer
525
+
526
+ The difference between byte and bit base integers of the same number of
527
+ bits (e.g. `uint8` vs `bit8`) is one of alignment.
528
+
529
+ This example is packed as 3 bytes
530
+
531
+ class A < BinData::Record
532
+ bit4 :a
533
+ uint8 :b
534
+ bit4 :c
535
+ end
536
+
537
+ Data is stored as: AAAA0000 BBBBBBBB CCCC0000
538
+ {:ruby}
539
+
540
+ Whereas this example is packed into only 2 bytes
541
+
542
+ class B < BinData::Record
543
+ bit4 :a
544
+ bit8 :b
545
+ bit4 :c
546
+ end
547
+
548
+ Data is stored as: AAAABBBB BBBBCCCC
549
+ {:ruby}
550
+
551
+ ### Floating point numbers
552
+
553
+ BinData supports 32 and 64 bit floating point numbers, in both big and
554
+ little endian format. These types are:
555
+
556
+ `float_le`
557
+ : single precision 32 bit little endian float
558
+
559
+ `float_be`
560
+ : single precision 32 bit big endian float
561
+
562
+ `double_le`
563
+ : double precision 64 bit little endian float
223
564
 
224
- == Saving Typing
565
+ `double_be`
566
+ : double precision 64 bit big endian float
225
567
 
226
- The endianess of numeric types must be explicitly defined so that the code
227
- produced is independent of architecture. Explicitly specifying the
228
- endianess of each numeric type can become tedious, so the following
229
- shortcut is provided.
568
+ The `_be` | `_le` suffix may be omitted if the `endian` keyword is in use.
230
569
 
231
- class A < BinData::Record
232
- endian :little
570
+ ### Example
233
571
 
234
- uint16 :a
235
- uint32 :b
236
- double :c
237
- uint32be :d
238
- array :e, :type => :int16
239
- end
572
+ Here is an example declaration for an Internet Protocol network packet.
240
573
 
241
- is equivalent to:
574
+ class IP_PDU < BinData::Record
575
+ endian :big
242
576
 
243
- class A < BinData::Record
244
- uint16le :a
245
- uint32le :b
246
- double_le :c
247
- uint32be :d
248
- array :e, :type => :int16le
249
- end
577
+ bit4 :version, :value => 4
578
+ bit4 :header_length
579
+ uint8 :tos
580
+ uint16 :total_length
581
+ uint16 :ident
582
+ bit3 :flags
583
+ bit13 :frag_offset
584
+ uint8 :ttl
585
+ uint8 :protocol
586
+ uint16 :checksum
587
+ uint32 :src_addr
588
+ uint32 :dest_addr
589
+ string :options, :read_length => :options_length_in_bytes
590
+ string :data, :read_length => lambda { total_length - header_length_in_bytes }
250
591
 
251
- Using the endian keyword improves the readability of the declaration as well
252
- as reducing the amount of typing necessary. Note that the endian keyword will
253
- cascade to nested types, as illustrated with the array in the above example.
592
+ def header_length_in_bytes
593
+ header_length * 4
594
+ end
254
595
 
255
- == Creating custom types
596
+ def options_length_in_bytes
597
+ header_length_in_bytes - 20
598
+ end
599
+ end
600
+ {:ruby}
256
601
 
257
- Custom types should be created by subclassing BinData::Record or
258
- BinData::Primitive. Ocassionally it may be useful to subclass
259
- BinData::BasePrimitive. Subclassing other classes may have unexpected results
260
- and is unsupported.
602
+ Three of the fields have parameters.
603
+ * The version field always has the value 4, as per the standard.
604
+ * The options field is read as a raw string, but not processed.
605
+ * The data field contains the payload of the packet. Its length is
606
+ calculated as the total length of the packet minus the length of
607
+ the header.
608
+
609
+ ## Strings
610
+
611
+ BinData supports two types of strings - fixed size and zero terminated.
612
+ Strings are treated as a sequence of 8bit bytes. This is the same as
613
+ strings in Ruby 1.8. The issue of character encoding is ignored by
614
+ BinData.
615
+
616
+ ### Fixed Sized Strings
617
+
618
+ Fixed sized strings may have a set length. If an assigned value is
619
+ shorter than this length, it will be padded to this length. If no
620
+ length is set, the length is taken to be the length of the assigned
621
+ value.
622
+
623
+ There are several parameters that are specific to fixed sized strings.
624
+
625
+ `:read_length`
626
+
627
+ : The length to use when reading a value.
628
+
629
+ obj = BinData::String.new(:read_length => 5)
630
+ obj.read("abcdefghij")
631
+ obj.value #=> "abcde"
632
+ {:ruby}
633
+
634
+ `:length`
635
+
636
+ : The fixed length of the string. If a shorter string is set, it
637
+ will be padded to this length. Longer strings will be truncated.
638
+
639
+ obj = BinData::String.new(:length => 6)
640
+ obj.read("abcdefghij")
641
+ obj.value #=> "abcdef"
642
+
643
+ obj = BinData::String.new(:length => 6)
644
+ obj.value = "abcd"
645
+ obj.value #=> "abcd\000\000"
646
+
647
+ obj = BinData::String.new(:length => 6)
648
+ obj.value = "abcdefghij"
649
+ obj.value #=> "abcdef"
650
+ {:ruby}
651
+
652
+ `:pad_char`
653
+
654
+ : The character to use when padding a string to a set length. Valid
655
+ values are `Integers` and `Strings` of length 1.
656
+ `"\0"` is the default.
657
+
658
+ obj = BinData::String.new(:length => 6, :pad_char => 'A')
659
+ obj.value = "abcd"
660
+ obj.value #=> "abcdAA"
661
+ obj.to_binary_s #=> "abcdAA"
662
+ {:ruby}
663
+
664
+ `:trim_padding`
665
+
666
+ : Boolean, default `false`. If set, the value of this string will
667
+ have all pad_chars trimmed from the end of the string. The value
668
+ will not be trimmed when writing.
669
+
670
+ obj = BinData::String.new(:length => 6, :trim_value => true)
671
+ obj.value = "abcd"
672
+ obj.value #=> "abcd"
673
+ obj.to_binary_s #=> "abcd\000\000"
674
+ {:ruby}
675
+
676
+ ### Zero Terminated Strings
677
+
678
+ These strings are modelled on the C style of string - a sequence of
679
+ bytes terminated by a null (`"\0"`) character.
680
+
681
+ obj = BinData::Stringz.new
682
+ obj.read("abcd\000efgh")
683
+ obj.value #=> "abcd"
684
+ obj.num_bytes #=> 5
685
+ obj.to_binary_s #=> "abcd\000"
686
+ {:ruby}
687
+
688
+ ## User Defined Primitive Types
689
+
690
+ Most user defined types will be Records, but occasionally we'd like to
691
+ create a custom type of primitive.
261
692
 
262
693
  Let us revisit the Pascal String example.
263
694
 
264
- class PascalString < BinData::Record
265
- uint8 :len, :value => lambda { data.length }
266
- string :data, :read_length => :len
267
- end
695
+ class PascalString < BinData::Record
696
+ uint8 :len, :value => lambda { data.length }
697
+ string :data, :read_length => :len
698
+ end
699
+ {:ruby}
700
+
701
+ We'd like to make `PascalString` a user defined type that behaves like a
702
+ `BinData::BasePrimitive` object so we can use `:initial_value` etc.
703
+ Here's an example usage of what we'd like:
704
+
705
+ class Favourites < BinData::Record
706
+ pascal_string :language, :initial_value => "ruby"
707
+ pascal_string :os, :initial_value => "unix"
708
+ end
709
+
710
+ f = Favourites.new
711
+ f.os = "freebsd"
712
+ f.to_binary_s #=> "\004ruby\007freebsd"
713
+ {:ruby}
714
+
715
+ We create this type of custom string by inheriting from
716
+ `BinData::Primitive` (instead of `BinData::Record`) and implementing the
717
+ `#get` and `#set` methods.
718
+
719
+ class PascalString < BinData::Primitive
720
+ uint8 :len, :value => lambda { data.length }
721
+ string :data, :read_length => :len
722
+
723
+ def get; self.data; end
724
+ def set(v) self.data = v; end
725
+ end
726
+ {:ruby}
727
+
728
+ ### Advanced User Defined Primitive Types
729
+
730
+ Sometimes a user defined primitive type can not easily be declaratively
731
+ defined. In this case you should inherit from `BinData::BasePrimitive`
732
+ and implement the following three methods:
733
+
734
+ * `value_to_binary_string(value)`
735
+ * `read_and_return_value(io)`
736
+ * `sensible_default()`
737
+
738
+ Here is an example of a big integer implementation.
739
+
740
+ # A custom big integer format. Binary format is:
741
+ # 1 byte : 0 for positive, non zero for negative
742
+ # x bytes : Little endian stream of 7 bit bytes representing the
743
+ # positive form of the integer. The upper bit of each byte
744
+ # is set when there are more bytes in the stream.
745
+ class BigInteger < BinData::BasePrimitive
746
+ def value_to_binary_string(value)
747
+ negative = (value < 0) ? 1 : 0
748
+ value = value.abs
749
+ bytes = [negative]
750
+ loop do
751
+ seven_bit_byte = value & 0x7f
752
+ value >>= 7
753
+ has_more = value.nonzero? ? 0x80 : 0
754
+ byte = has_more | seven_bit_byte
755
+ bytes.push(byte)
756
+
757
+ break if has_more.zero?
758
+ end
759
+
760
+ bytes.collect { |b| b.chr }.join
761
+ end
762
+
763
+ def read_and_return_value(io)
764
+ negative = read_uint8(io).nonzero?
765
+ value = 0
766
+ bit_shift = 0
767
+ loop do
768
+ byte = read_uint8(io)
769
+ has_more = byte & 0x80
770
+ seven_bit_byte = byte & 0x7f
771
+ value |= seven_bit_byte << bit_shift
772
+ bit_shift += 7
773
+
774
+ break if has_more.zero?
775
+ end
776
+
777
+ negative ? -value : value
778
+ end
779
+
780
+ def sensible_default
781
+ 0
782
+ end
783
+
784
+ def read_uint8(io)
785
+ io.readbytes(1).unpack("C").at(0)
786
+ end
787
+ end
788
+ {:ruby}
789
+
790
+ ---------------------------------------------------------------------------
791
+
792
+ # Arrays
793
+
794
+ A BinData array is a list of data objects of the same type. It behaves
795
+ much the same as the standard Ruby array, supporting most of the common
796
+ methods.
797
+
798
+ When instantiating an array, the type of object it contains must be
799
+ specified.
800
+
801
+ arr = BinData::Array.new(:type => :uint8)
802
+ arr[3] = 5
803
+ arr.snapshot #=> [0, 0, 0, 5]
804
+ {:ruby}
805
+
806
+ Parameters can be passed to this object with a slightly clumsy syntax.
807
+
808
+ arr = BinData::Array.new(:type => [:uint8, {:initial_value => :index}])
809
+ arr[3] = 5
810
+ arr.snapshot #=> [0, 1, 2, 5]
811
+ {:ruby}
812
+
813
+ There are two different parameters that specify the length of the array.
814
+
815
+ `:initial_length`
816
+
817
+ : Specifies the initial length of a newly instantiated array.
818
+ The array may grow as elements are inserted.
819
+
820
+ obj = BinData::Array.new(:type => :int8, :initial_length => 4)
821
+ obj.read("\002\003\004\005\006\007")
822
+ obj.snapshot #=> [2, 3, 4, 5]
823
+ {:ruby}
824
+
825
+ `:read_until`
826
+
827
+ : While reading, elements are read until this condition is true. This
828
+ is typically used to read an array until a sentinel value is found.
829
+ The variables `index`, `element` and `array` are made available to
830
+ any lambda assigned to this parameter. If the value of this
831
+ parameter is the symbol `:eof`, then the array will read as much
832
+ data from the stream as possible.
833
+
834
+ obj = BinData::Array.new(:type => :int8,
835
+ :read_until => lambda { index == 1 })
836
+ obj.read("\002\003\004\005\006\007")
837
+ obj.snapshot #=> [2, 3]
838
+
839
+ obj = BinData::Array.new(:type => :int8,
840
+ :read_until => lambda { element >= 3.5 })
841
+ obj.read("\002\003\004\005\006\007")
842
+ obj.snapshot #=> [2, 3, 4]
843
+
844
+ obj = BinData::Array.new(:type => :int8,
845
+ :read_until => lambda { array[index] + array[index - 1] == 9 })
846
+ obj.read("\002\003\004\005\006\007")
847
+ obj.snapshot #=> [2, 3, 4, 5]
268
848
 
269
- We'd like to make PascalString a custom type that behaves like a
270
- BinData::BasePrimitive object so we can use :initial_value etc. Here's an
271
- example usage of what we'd like:
849
+ obj = BinData::Array.new(:type => :int8, :read_until => :eof)
850
+ obj.read("\002\003\004\005\006\007")
851
+ obj.snapshot #=> [2, 3, 4, 5, 6, 7]
852
+ {:ruby}
272
853
 
273
- class Favourites < BinData::Record
274
- pascal_string :language, :initial_value => "ruby"
275
- pascal_string :os, :initial_value => "unix"
276
- end
854
+ ---------------------------------------------------------------------------
277
855
 
278
- f = Favourites.new
279
- f.os = "freebsd"
280
- f.to_binary_s #=> "\004ruby\007freebsd"
856
+ # Choices
281
857
 
282
- We create this type of custom string by inheriting from BinData::Primitive
283
- and implementing the #get and #set methods.
858
+ A Choice is a collection of data objects of which only one is active at
859
+ any particular time. Method calls will be delegated to the active
860
+ choice. The possible types of objects that a choice contains is
861
+ controlled by the `:choices` parameter, while the `:selection` parameter
862
+ specifies the active choice.
284
863
 
285
- class PascalString < BinData::Primitive
286
- uint8 :len, :value => lambda { data.length }
287
- string :data, :read_length => :len
864
+ `:choices`
288
865
 
289
- def get; self.data; end
290
- def set(v) self.data = v; end
291
- end
866
+ : Either an array or a hash specifying the possible data objects. The
867
+ format of the array/hash.values is a list of symbols representing
868
+ the data object type. If a choice is to have params passed to it,
869
+ then it should be provided as `[type_symbol, hash_params]`. An
870
+ implementation constraint is that the hash may not contain symbols
871
+ as keys.
292
872
 
293
- If the type we are creating represents a primitive value then inherit from
294
- BinData::Primitive, otherwise inherit from BinData::Record.
873
+ `:selection`
295
874
 
296
- == License
875
+ : An index/key into the `:choices` array/hash which specifies the
876
+ currently active choice.
297
877
 
298
- BinData is released under the same license as Ruby.
878
+ `:copy_on_change`
879
+
880
+ : If set to `true`, copy the value of the previous selection to the
881
+ current selection whenever the selection changes. Default is
882
+ `false`.
883
+
884
+ Examples
885
+
886
+ type1 = [:string, {:value => "Type1"}]
887
+ type2 = [:string, {:value => "Type2"}]
888
+
889
+ choices = {5 => type1, 17 => type2}
890
+ obj = BinData::Choice.new(:choices => choices, :selection => 5)
891
+ obj.value # => "Type1"
892
+
893
+ choices = [ type1, type2 ]
894
+ obj = BinData::Choice.new(:choices => choices, :selection => 1)
895
+ obj.value # => "Type2"
896
+
897
+ choices = [ nil, nil, nil, type1, nil, type2 ]
898
+ obj = BinData::Choice.new(:choices => choices, :selection => 3)
899
+ obj.value # => "Type1"
900
+
901
+ class MyNumber < BinData::Record
902
+ int8 :is_big_endian
903
+ choice :data, :choices => { true => :int32be, false => :int32le },
904
+ :selection => lambda { is_big_endian != 0 },
905
+ :copy_on_change => true
906
+ end
907
+
908
+ obj = MyNumber.new
909
+ obj.is_big_endian = 1
910
+ obj.data = 5
911
+ obj.to_binary_s #=> "\001\000\000\000\005"
912
+
913
+ obj.is_big_endian = 0
914
+ obj.to_binary_s #=> "\000\005\000\000\000"
915
+ {:ruby}
916
+
917
+ ---------------------------------------------------------------------------
918
+
919
+ # Advanced Topics
920
+
921
+ ## Wrappers
922
+
923
+ Sometimes you wish to create a new type that is simply an existing type
924
+ with some predefined parameters. Examples could be an array with a
925
+ specified type, or an integer with an initial value.
926
+
927
+ This can be achieved with a wrapper. A wrapper creates a new type based
928
+ on an existing type which has predefined parameters. These parameters
929
+ can of course be overridden at initialisation time.
930
+
931
+ Here we define an array that contains big endian 16 bit integers. The
932
+ array has a preferred initial length.
933
+
934
+ class IntArray < BinData::Wrapper
935
+ endian :big
936
+ array :type => :uint16, :initial_length => 5
937
+ end
938
+
939
+ arr = IntArray.new
940
+ arr.size #=> 5
941
+ {:ruby}
942
+
943
+ The initial length can be overridden at initialisation time.
944
+
945
+ arr = IntArray.new(:initial_length => 8)
946
+ arr.size #=> 8
947
+ {:ruby}
948
+
949
+ ## Parameterizing User Defined Types
950
+
951
+ All BinData types have parameters that allow the behaviour of an object
952
+ to be specified at initialization time. User defined types may also
953
+ specify parameters. There are two types of parameters: mandatory and
954
+ default.
955
+
956
+ ### Mandatory Parameters
957
+
958
+ Mandatory parameters must be specified when creating an instance of the
959
+ type. The `:type` parameter of `Array` is an example of a mandatory
960
+ type.
961
+
962
+ class IntArray < BinData::Wrapper
963
+ mandatory_parameter :half_count
964
+
965
+ array :type => :uint8, :initial_length => lambda { half_count * 2 }
966
+ end
967
+
968
+ arr = IntArray.new
969
+ #=> raises ArgumentError: parameter 'half_count' must be specified in IntArray
970
+
971
+ arr = IntArray.new(:half_count => lambda { 1 + 2 })
972
+ arr.snapshot #=> [0, 0, 0, 0, 0, 0]
973
+ {:ruby}
974
+
975
+ ### Default Parameters
976
+
977
+ Default parameters are optional. These parameters have a default value
978
+ that may be overridden when an instance of the type is created.
979
+
980
+ class Phrase < BinData::Primitive
981
+ default_parameter :number => "three"
982
+ default_parameter :adjective => "blind"
983
+ default_parameter :noun => "mice"
984
+
985
+ stringz :a, :initial_value => :number
986
+ stringz :b, :initial_value => :adjective
987
+ stringz :c, :initial_value => :noun
988
+
989
+ def get; "#{a} #{b} #{c}"; end
990
+ def set(v)
991
+ if /(.*) (.*) (.*)/ =~ v
992
+ self.a, self.b, self.c = $1, $2, $3
993
+ end
994
+ end
995
+ end
996
+
997
+ obj = Phrase.new(:number => "two", :adjective => "deaf")
998
+ obj.to_s #=> "two deaf mice"
999
+ {:ruby}
1000
+
1001
+ ## Debugging
1002
+
1003
+ BinData includes several features to make it easier to debug
1004
+ declarations.
1005
+
1006
+ ### Tracing
1007
+
1008
+ BinData has the ability to trace the results of reading a data
1009
+ structure.
1010
+
1011
+ class A < BinData::Record
1012
+ int8 :a
1013
+ bit4 :b
1014
+ bit2 :c
1015
+ array :d, :initial_length => 6, :type => :bit1
1016
+ end
1017
+
1018
+ BinData::trace_reading do
1019
+ A.read("\373\225\220")
1020
+ end
1021
+ {:ruby}
1022
+
1023
+ Results in the following being written to `STDERR`.
1024
+
1025
+ obj.a => -5
1026
+ obj.b => 9
1027
+ obj.c => 1
1028
+ obj.d[0] => 0
1029
+ obj.d[1] => 1
1030
+ obj.d[2] => 1
1031
+ obj.d[3] => 0
1032
+ obj.d[4] => 0
1033
+ obj.d[5] => 1
1034
+ {:ruby}
1035
+
1036
+ ### Rest
1037
+
1038
+ The rest keyword will consume the input stream from the current position
1039
+ to the end of the stream.
1040
+
1041
+ class A < BinData::Record
1042
+ string :a, :read_length => 5
1043
+ rest :rest
1044
+ end
1045
+
1046
+ obj = A.read("abcdefghij")
1047
+ obj.a #=> "abcde"
1048
+ obj.rest #=" "fghij"
1049
+ {:ruby}
1050
+
1051
+ ### Hidden fields
1052
+
1053
+ The typical way to view the contents of a BinData record is to call
1054
+ `#snapshot` or `#inspect`. This gives all fields and their values. The
1055
+ `hide` keyword can be used to prevent certain fields from appearing in
1056
+ this output. This removes clutter and allows the developer to focus on
1057
+ what they are currently interested in.
1058
+
1059
+ class Testing < BinData::Record
1060
+ hide :a, :b
1061
+ string :a, :read_length => 10
1062
+ string :b, :read_length => 10
1063
+ string :c, :read_length => 10
1064
+ end
1065
+
1066
+ obj = Testing.read(("a" * 10) + ("b" * 10) + ("c" * 10))
1067
+ obj.snapshot #=> {"c"=>"cccccccccc"}
1068
+ obj.to_binary_s #=> "aaaaaaaaaabbbbbbbbbbcccccccccc"
1069
+ {:ruby}
1070
+
1071
+ ---------------------------------------------------------------------------
1072
+
1073
+ # Alternatives
1074
+
1075
+ There are several alternatives to BinData. Below is a comparison
1076
+ between BinData and its alternatives.
1077
+
1078
+ The short form is that BinData is the best choice for most cases. If
1079
+ decoding / encoding speed is very important and the binary formats are
1080
+ simple then BitStruct may be a good choice. (Though if speed is
1081
+ important, perhaps you should investigate a language other than Ruby.)
1082
+
1083
+ ### [BitStruct](http://rubyforge.org/projects/bit-struct)
1084
+
1085
+ BitStruct is the most complete of all the alternatives. It is
1086
+ declarative and supports all the same primitive types as BinData. In
1087
+ addition it includes a self documenting feature to make it easy to write
1088
+ reports.
1089
+
1090
+ The major limitation of BitStruct is that it does not support variable
1091
+ length fields and dependent fields. The simple PascalString example
1092
+ used previously is not possible with BitStruct. This limitation is due
1093
+ to the design choice to favour speed over flexibility.
1094
+
1095
+ Most non trivial file formats rely on dependent and variable length
1096
+ fields. It is difficult to use BitStruct with these formats as code
1097
+ must be written to explicitly handle the dependencies.
1098
+
1099
+ BitStruct does not currently support little endian bit fields, or
1100
+ bitfields that span more than 2 bytes. BitStruct is actively maintained
1101
+ so these limitations may be removed in a future release.
1102
+
1103
+ If speed is important and you are only dealing with simple binary data
1104
+ types then BitStruct is a good choice. For non trivial data types,
1105
+ BinData is the better choice.
1106
+
1107
+ ### [BinaryParse](http://rubyforge.org/projects/binaryparse)
1108
+
1109
+ BinaryParse is a declarative style packer / unpacker. It provides the
1110
+ same primitives as Ruby's `#pack`, with the addition of date and time.
1111
+ Like BitStruct, it doesn't provide dependent or variable length fields.
1112
+
1113
+ ### [BinStruct](http://rubyforge.org/projects/metafuzz)
1114
+
1115
+ BinStruct is an imperative approach to unpacking binary data. It does
1116
+ provide some declarative style syntax sugar. It provides support for
1117
+ the most common primitive types, as well as arbitrary length bitfields.
1118
+
1119
+ It's main focus is as a binary fuzzer, rather than as a generic decoding
1120
+ / encoding library.
1121
+
1122
+ ### [Packable](http://github.com/marcandre/packable/tree/master)
1123
+
1124
+ Packable makes it much nicer to use Ruby's `#pack` and `#unpack`
1125
+ methods. Instead of having to remember that, for example `"n"` is the
1126
+ code to pack a 16 bit big endian integer, packable provides many
1127
+ convenient shortcuts. In the case of `"n"`, `{:bytes => 2, :endian => :big}`
1128
+ may be used instead.
1129
+
1130
+ Using Packable improves the readability of `#pack` and `#unpack`
1131
+ methods, but explicitly calls to `#pack` and `#unpack` aren't as
1132
+ readable as a declarative approach.
1133
+
1134
+ ### [Bitpack](http://rubyforge.org/projects/bitpack)
1135
+
1136
+ Bitpack provides methods to extract big endian integers of arbitrary bit
1137
+ length from an octet stream.
1138
+
1139
+ The extraction code is written in `C`, so if speed is important and bit
1140
+ manipulation is all the functionality you require then this may be an
1141
+ alternative.
299
1142
 
300
- Copyright (c) 2007 - 2009 Dion Mendel
1143
+ ---------------------------------------------------------------------------