bindata 1.2.2 → 1.3.1

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.

Potentially problematic release.


This version of bindata might be problematic. Click here for more details.

Files changed (52) hide show
  1. data/ChangeLog +12 -0
  2. data/NEWS +53 -0
  3. data/Rakefile +2 -1
  4. data/examples/NBT.txt +149 -0
  5. data/examples/ip_address.rb +1 -2
  6. data/examples/list.rb +124 -0
  7. data/examples/nbt.rb +195 -0
  8. data/lib/bindata.rb +4 -3
  9. data/lib/bindata/alignment.rb +86 -0
  10. data/lib/bindata/array.rb +21 -29
  11. data/lib/bindata/base.rb +82 -81
  12. data/lib/bindata/base_primitive.rb +66 -48
  13. data/lib/bindata/choice.rb +18 -28
  14. data/lib/bindata/deprecated.rb +17 -0
  15. data/lib/bindata/dsl.rb +25 -15
  16. data/lib/bindata/int.rb +2 -2
  17. data/lib/bindata/io.rb +8 -6
  18. data/lib/bindata/offset.rb +91 -0
  19. data/lib/bindata/primitive.rb +22 -11
  20. data/lib/bindata/record.rb +40 -10
  21. data/lib/bindata/sanitize.rb +15 -30
  22. data/lib/bindata/string.rb +16 -17
  23. data/lib/bindata/stringz.rb +0 -1
  24. data/lib/bindata/struct.rb +17 -6
  25. data/lib/bindata/trace.rb +52 -0
  26. data/lib/bindata/wrapper.rb +28 -6
  27. data/manual.haml +56 -10
  28. data/manual.md +318 -113
  29. data/spec/alignment_spec.rb +61 -0
  30. data/spec/array_spec.rb +139 -178
  31. data/spec/base_primitive_spec.rb +86 -111
  32. data/spec/base_spec.rb +200 -172
  33. data/spec/bits_spec.rb +45 -53
  34. data/spec/choice_spec.rb +91 -87
  35. data/spec/deprecated_spec.rb +36 -14
  36. data/spec/float_spec.rb +16 -68
  37. data/spec/int_spec.rb +26 -27
  38. data/spec/io_spec.rb +105 -105
  39. data/spec/lazy_spec.rb +50 -50
  40. data/spec/primitive_spec.rb +36 -36
  41. data/spec/record_spec.rb +134 -134
  42. data/spec/registry_spec.rb +34 -38
  43. data/spec/rest_spec.rb +8 -11
  44. data/spec/skip_spec.rb +9 -17
  45. data/spec/spec_common.rb +4 -0
  46. data/spec/string_spec.rb +92 -115
  47. data/spec/stringz_spec.rb +41 -74
  48. data/spec/struct_spec.rb +132 -153
  49. data/spec/system_spec.rb +115 -60
  50. data/spec/wrapper_spec.rb +63 -31
  51. data/tasks/pkg.rake +1 -1
  52. metadata +15 -7
@@ -1,5 +1,5 @@
1
1
  # BinData -- Binary data manipulator.
2
- # Copyright (c) 2007 - 2010 Dion Mendel.
2
+ # Copyright (c) 2007 - 2011 Dion Mendel.
3
3
 
4
4
  require 'bindata/array'
5
5
  require 'bindata/bits'
@@ -15,6 +15,7 @@ require 'bindata/stringz'
15
15
  require 'bindata/struct'
16
16
  require 'bindata/trace'
17
17
  require 'bindata/wrapper'
18
+ require 'bindata/alignment'
18
19
  require 'bindata/deprecated'
19
20
 
20
21
  # = BinData
@@ -28,7 +29,7 @@ require 'bindata/deprecated'
28
29
  #
29
30
  # BinData is released under the same license as Ruby.
30
31
  #
31
- # Copyright (c) 2007 - 2010 Dion Mendel.
32
+ # Copyright (c) 2007 - 2011 Dion Mendel.
32
33
  module BinData
33
- VERSION = "1.2.2"
34
+ VERSION = "1.3.1"
34
35
  end
@@ -0,0 +1,86 @@
1
+ require 'bindata/base_primitive'
2
+
3
+ module BinData
4
+ # Resets the stream alignment to the next byte. This is
5
+ # only useful when using bit-based primitives.
6
+ #
7
+ # class MyRec < BinData::Record
8
+ # bit4 :a
9
+ # resume_byte_alignment
10
+ # bit4 :b
11
+ # end
12
+ #
13
+ # MyRec.read("\x12\x34") #=> {"a" => 1, "b" => 3}
14
+ #
15
+ class ResumeByteAlignment < BinData::Base
16
+ register_self
17
+
18
+ def clear; end
19
+ def clear?; true; end
20
+ def assign(val); end
21
+ def snapshot; nil; end
22
+ def do_num_bytes; 0; end
23
+
24
+ def do_read(io)
25
+ io.reset_read_bits
26
+ end
27
+
28
+ def do_write(io)
29
+ io.flushbits
30
+ end
31
+ end
32
+
33
+ # A monkey patch to force byte-aligned primitives to
34
+ # become bit-aligned. This allows them to be used at
35
+ # non byte based boundaries.
36
+ #
37
+ # class BitString < BinData::String
38
+ # bit_aligned
39
+ # end
40
+ #
41
+ # class MyRecord < BinData::Record
42
+ # bit4 :preamble
43
+ # bit_string :str, :length => 2
44
+ # end
45
+ #
46
+ module BitAligned
47
+ class BitAlignedIO
48
+ def initialize(io)
49
+ @io = io
50
+ end
51
+ def readbytes(n)
52
+ n.times.inject("") do |bytes, i|
53
+ bytes + @io.readbits(8, :big).chr
54
+ end
55
+ end
56
+ def method_missing(sym, *args, &block)
57
+ @io.send(sym, *args, &block)
58
+ end
59
+ end
60
+
61
+ def read_and_return_value(io)
62
+ super(BitAlignedIO.new(io))
63
+ end
64
+
65
+ def do_num_bytes
66
+ super.to_f
67
+ end
68
+
69
+ def do_write(io)
70
+ value_to_binary_string(_value).each_byte { |v| io.writebits(v, 8, :big) }
71
+ end
72
+ end
73
+
74
+ class BasePrimitive < BinData::Base
75
+ def self.bit_aligned
76
+ include BitAligned
77
+ register_self
78
+ end
79
+ end
80
+
81
+ class Primitive < BinData::BasePrimitive
82
+ def self.bit_aligned
83
+ fail "'bit_aligned' is not needed for BinData::Primitives"
84
+ end
85
+ end
86
+ end
@@ -8,27 +8,22 @@ module BinData
8
8
  # data = "\x03\x04\x05\x06\x07\x08\x09"
9
9
  #
10
10
  # obj = BinData::Array.new(:type => :int8, :initial_length => 6)
11
- # obj.read(data)
12
- # obj.snapshot #=> [3, 4, 5, 6, 7, 8]
11
+ # obj.read(data) #=> [3, 4, 5, 6, 7, 8]
13
12
  #
14
13
  # obj = BinData::Array.new(:type => :int8,
15
14
  # :read_until => lambda { index == 1 })
16
- # obj.read(data)
17
- # obj.snapshot #=> [3, 4]
15
+ # obj.read(data) #=> [3, 4]
18
16
  #
19
17
  # obj = BinData::Array.new(:type => :int8,
20
18
  # :read_until => lambda { element >= 6 })
21
- # obj.read(data)
22
- # obj.snapshot #=> [3, 4, 5, 6]
19
+ # obj.read(data) #=> [3, 4, 5, 6]
23
20
  #
24
21
  # obj = BinData::Array.new(:type => :int8,
25
22
  # :read_until => lambda { array[index] + array[index - 1] == 13 })
26
- # obj.read(data)
27
- # obj.snapshot #=> [3, 4, 5, 6, 7]
23
+ # obj.read(data) #=> [3, 4, 5, 6, 7]
28
24
  #
29
25
  # obj = BinData::Array.new(:type => :int8, :read_until => :eof)
30
- # obj.read(data)
31
- # obj.snapshot #=> [3, 4, 5, 6, 7, 8, 9]
26
+ # obj.read(data) #=> [3, 4, 5, 6, 7, 8, 9]
32
27
  #
33
28
  # == Parameters
34
29
  #
@@ -78,13 +73,14 @@ module BinData
78
73
  end
79
74
  end
80
75
 
81
- def initialize(parameters = {}, parent = nil)
82
- super
83
-
84
- @element_list = nil
76
+ def initialize_shared_instance
85
77
  @element_prototype = get_parameter(:type)
86
78
  end
87
79
 
80
+ def initialize_instance
81
+ @element_list = nil
82
+ end
83
+
88
84
  def clear?
89
85
  @element_list.nil? or elements.all? { |el| el.clear? }
90
86
  end
@@ -247,7 +243,7 @@ module BinData
247
243
  end
248
244
 
249
245
  def do_num_bytes #:nodoc:
250
- sum_num_bytes_for_all_elements.ceil
246
+ sum_num_bytes_for_all_elements
251
247
  end
252
248
 
253
249
  #---------------
@@ -261,13 +257,7 @@ module BinData
261
257
  end
262
258
 
263
259
  def to_storage_formats(els)
264
- els.collect { |el| to_storage_format(el) }
265
- end
266
-
267
- def to_storage_format(obj)
268
- element = new_element
269
- element.assign(obj)
270
- element
260
+ els.collect { |el| new_element(el) }
271
261
  end
272
262
 
273
263
  def read_until(io)
@@ -318,8 +308,8 @@ module BinData
318
308
  element
319
309
  end
320
310
 
321
- def new_element
322
- @element_prototype.instantiate(self)
311
+ def new_element(value = nil)
312
+ @element_prototype.instantiate(value, self)
323
313
  end
324
314
 
325
315
  def sum_num_bytes_for_all_elements
@@ -327,13 +317,15 @@ module BinData
327
317
  end
328
318
 
329
319
  def sum_num_bytes_below_index(index)
330
- sum = 0
331
- (0...index).each do |i|
320
+ (0...index).inject(0) do |sum, i|
332
321
  nbytes = elements[i].do_num_bytes
333
- sum = (nbytes.is_a?(Integer) ? sum.ceil : sum) + nbytes
334
- end
335
322
 
336
- sum
323
+ if nbytes.is_a?(Integer)
324
+ (sum + nbytes).ceil
325
+ else
326
+ sum + nbytes
327
+ end
328
+ end
337
329
  end
338
330
  end
339
331
  end
@@ -1,5 +1,6 @@
1
1
  require 'bindata/io'
2
2
  require 'bindata/lazy'
3
+ require 'bindata/offset'
3
4
  require 'bindata/params'
4
5
  require 'bindata/registry'
5
6
  require 'bindata/sanitize'
@@ -8,39 +9,49 @@ module BinData
8
9
  # Error raised when unexpected results occur when reading data from IO.
9
10
  class ValidityError < StandardError ; end
10
11
 
12
+ # ArgExtractors take the arguments to BinData::Base.new and
13
+ # separate them into [value, parameters, parent].
14
+ class BaseArgExtractor
15
+ def self.extract(the_class, the_args)
16
+ args = the_args.dup
17
+ value = parameters = parent = nil
18
+
19
+ if args.length > 1 and args.last.is_a? BinData::Base
20
+ parent = args.pop
21
+ end
22
+
23
+ if args.length > 0 and args.last.is_a? Hash
24
+ parameters = args.pop
25
+ end
26
+
27
+ if args.length > 0
28
+ value = args.pop
29
+ end
30
+
31
+ parameters ||= {}
32
+
33
+ return [value, parameters, parent]
34
+ end
35
+ end
36
+
11
37
  # This is the abstract base class for all data objects.
12
- #
13
- # == Parameters
14
- #
15
- # Parameters may be provided at initialisation to control the behaviour of
16
- # an object. These parameters are:
17
- #
18
- # [<tt>:check_offset</tt>] Raise an error if the current IO offset doesn't
19
- # meet this criteria. A boolean return indicates
20
- # success or failure. Any other return is compared
21
- # to the current offset. The variable +offset+
22
- # is made available to any lambda assigned to
23
- # this parameter. This parameter is only checked
24
- # before reading.
25
- # [<tt>:adjust_offset</tt>] Ensures that the current IO offset is at this
26
- # position before reading. This is like
27
- # <tt>:check_offset</tt>, except that it will
28
- # adjust the IO offset instead of raising an error.
29
38
  class Base
30
39
  include AcceptedParametersMixin
31
-
32
- optional_parameters :check_offset, :adjust_offset
33
- optional_parameter :onlyif # Used by Struct
34
- mutually_exclusive_parameters :check_offset, :adjust_offset
40
+ include CheckOrAdjustOffsetMixin
35
41
 
36
42
  class << self
37
43
 
38
44
  # Instantiates this class and reads from +io+, returning the newly
39
45
  # created data object.
40
46
  def read(io)
41
- data = self.new
42
- data.read(io)
43
- data
47
+ obj = self.new
48
+ obj.read(io)
49
+ obj
50
+ end
51
+
52
+ # The arg extractor for this class.
53
+ def arg_extractor
54
+ BaseArgExtractor
44
55
  end
45
56
 
46
57
  # The name of this class as used by Records, Arrays etc.
@@ -71,19 +82,42 @@ module BinData
71
82
 
72
83
  # Creates a new data object.
73
84
  #
85
+ # Args are optional, but if present, must be in the following order.
86
+ #
87
+ # +value+ is a value that is +assign+ed immediately after initialization.
88
+ #
74
89
  # +parameters+ is a hash containing symbol keys. Some parameters may
75
90
  # reference callable objects (methods or procs).
76
91
  #
77
92
  # +parent+ is the parent data object (e.g. struct, array, choice) this
78
93
  # object resides under.
79
- def initialize(parameters = {}, parent = nil)
94
+ def initialize(*args)
95
+ value, parameters, parent = extract_args(args)
80
96
  @params = Sanitizer.sanitize(parameters, self.class)
81
- @parent = parent
82
97
 
83
- prepare_for_read_with_offset
98
+ add_methods_for_check_or_adjust_offset
99
+
100
+ @parent = parent if parent
101
+ initialize_shared_instance
102
+ initialize_instance
103
+ assign(value) if value
84
104
  end
85
105
 
86
- attr_reader :parent
106
+ attr_accessor :parent
107
+ protected :parent=
108
+
109
+ # Creates a new data object based on this instance.
110
+ #
111
+ # All parameters will be be duplicated. Use this method
112
+ # when creating multiple objects with the same parameters.
113
+ def new(value = nil, parent = nil)
114
+ obj = clone
115
+ obj.parent = parent if parent
116
+ obj.initialize_instance
117
+ obj.assign(value) if value
118
+
119
+ obj
120
+ end
87
121
 
88
122
  # Returns the result of evaluating the parameter identified by +key+.
89
123
  #
@@ -202,6 +236,10 @@ module BinData
202
236
  #---------------
203
237
  private
204
238
 
239
+ def extract_args(the_args)
240
+ self.class.arg_extractor.extract(self.class, the_args)
241
+ end
242
+
205
243
  def furthest_ancestor
206
244
  if parent.nil?
207
245
  self
@@ -212,58 +250,6 @@ module BinData
212
250
  end
213
251
  end
214
252
 
215
- def prepare_for_read_with_offset
216
- if has_parameter?(:check_offset) or has_parameter?(:adjust_offset)
217
- class << self
218
- alias_method :do_read_without_offset, :do_read
219
- alias_method :do_read, :do_read_with_offset
220
- public :do_read # Ruby 1.9.2 bug. Should be protected
221
- end
222
- end
223
- end
224
-
225
- def do_read_with_offset(io) #:nodoc:
226
- check_or_adjust_offset(io)
227
- do_read_without_offset(io)
228
- end
229
-
230
- def check_or_adjust_offset(io)
231
- if has_parameter?(:check_offset)
232
- check_offset(io)
233
- elsif has_parameter?(:adjust_offset)
234
- adjust_offset(io)
235
- end
236
- end
237
-
238
- def check_offset(io)
239
- actual_offset = io.offset
240
- expected = eval_parameter(:check_offset, :offset => actual_offset)
241
-
242
- if not expected
243
- raise ValidityError, "offset not as expected for #{debug_name}"
244
- elsif actual_offset != expected and expected != true
245
- raise ValidityError,
246
- "offset is '#{actual_offset}' but " +
247
- "expected '#{expected}' for #{debug_name}"
248
- end
249
- end
250
-
251
- def adjust_offset(io)
252
- actual_offset = io.offset
253
- expected = eval_parameter(:adjust_offset)
254
- if actual_offset != expected
255
- begin
256
- seek = expected - actual_offset
257
- io.seekbytes(seek)
258
- warn "adjusting stream position by #{seek} bytes" if $VERBOSE
259
- rescue
260
- raise ValidityError,
261
- "offset is '#{actual_offset}' but couldn't seek to " +
262
- "expected '#{expected}' for #{debug_name}"
263
- end
264
- end
265
- end
266
-
267
253
  ###########################################################################
268
254
  # To be implemented by subclasses
269
255
 
@@ -272,6 +258,20 @@ module BinData
272
258
  def self.sanitize_parameters!(parameters, sanitizer) #:nodoc:
273
259
  end
274
260
 
261
+ # Initializes the state of the object. All instance variables that
262
+ # are used by the object must be initialized here.
263
+ def initialize_instance
264
+ end
265
+
266
+ # Initialises state that is shared by objects with the same parameters.
267
+ #
268
+ # This should only be used when optimising for performance. Instance
269
+ # variables set here, and changes to the singleton class will be shared
270
+ # between all objects that are initialized with the same parameters.
271
+ # This method is called only once for a particular set of parameters.
272
+ def initialize_shared_instance
273
+ end
274
+
275
275
  # Resets the internal state to that of a newly created object.
276
276
  def clear
277
277
  raise NotImplementedError
@@ -282,7 +282,7 @@ module BinData
282
282
  raise NotImplementedError
283
283
  end
284
284
 
285
- # Assigns the value of +val+ to this data object. Note that +val+ will
285
+ # Assigns the value of +val+ to this data object. Note that +val+ must
286
286
  # always be deep copied to ensure no aliasing problems can occur.
287
287
  def assign(val)
288
288
  raise NotImplementedError
@@ -322,6 +322,7 @@ module BinData
322
322
 
323
323
  # Set visibility requirements of methods to implement
324
324
  public :clear, :clear?, :assign, :snapshot, :debug_name_of, :offset_of
325
+ protected :initialize_instance, :initialize_shared_instance
325
326
  protected :do_read, :do_write, :do_num_bytes
326
327
 
327
328
  # End To be implemented by subclasses
@@ -1,5 +1,4 @@
1
1
  require 'bindata/base'
2
- require 'bindata/trace'
3
2
 
4
3
  module BinData
5
4
  # A BinData::BasePrimitive object is a container for a value that has a
@@ -10,16 +9,16 @@ module BinData
10
9
  # require 'bindata'
11
10
  #
12
11
  # obj = BinData::Uint8.new(:initial_value => 42)
13
- # obj.value #=> 42
14
- # obj.value = 5
15
- # obj.value #=> 5
12
+ # obj #=> 42
13
+ # obj.assign(5)
14
+ # obj #=> 5
16
15
  # obj.clear
17
- # obj.value #=> 42
16
+ # obj #=> 42
18
17
  #
19
18
  # obj = BinData::Uint8.new(:value => 42)
20
- # obj.value #=> 42
21
- # obj.value = 5
22
- # obj.value #=> 42
19
+ # obj #=> 42
20
+ # obj.assign(5)
21
+ # obj #=> 42
23
22
  #
24
23
  # obj = BinData::Uint8.new(:check_value => 3)
25
24
  # obj.read("\005") #=> BinData::ValidityError: value is '5' but expected '3'
@@ -50,9 +49,26 @@ module BinData
50
49
  optional_parameters :initial_value, :value, :check_value
51
50
  mutually_exclusive_parameters :initial_value, :value
52
51
 
53
- def initialize(parameters = {}, parent = nil)
54
- super
52
+ def initialize_shared_instance
53
+ if has_parameter?(:check_value)
54
+ class << self
55
+ alias_method :do_read_without_check_value, :do_read
56
+ alias_method :do_read, :do_read_with_check_value
57
+ end
58
+ end
59
+ if has_parameter?(:value)
60
+ class << self
61
+ alias_method :_value, :_value_with_value
62
+ end
63
+ end
64
+ if has_parameter?(:initial_value)
65
+ class << self
66
+ alias_method :_value, :_value_with_initial_value
67
+ end
68
+ end
69
+ end
55
70
 
71
+ def initialize_instance
56
72
  @value = nil
57
73
  end
58
74
 
@@ -83,27 +99,28 @@ module BinData
83
99
  end
84
100
 
85
101
  def value
86
- # TODO: warn "#value is deprecated, use #snapshot instead"
87
102
  snapshot
88
103
  end
89
-
90
- def value=(val)
91
- # TODO: warn "#value= is deprecated, use #assign instead"
92
- assign(val)
93
- end
104
+ alias_method :value=, :assign
94
105
 
95
106
  def respond_to?(symbol, include_private = false) #:nodoc:
96
- value.respond_to?(symbol, include_private) || super
107
+ child = snapshot
108
+ child.respond_to?(symbol, include_private) || super
97
109
  end
98
110
 
99
111
  def method_missing(symbol, *args, &block) #:nodoc:
100
- if value.respond_to?(symbol)
101
- value.__send__(symbol, *args, &block)
112
+ child = snapshot
113
+ if child.respond_to?(symbol)
114
+ child.__send__(symbol, *args, &block)
102
115
  else
103
116
  super
104
117
  end
105
118
  end
106
119
 
120
+ def <=>(other)
121
+ snapshot <=> other
122
+ end
123
+
107
124
  def eql?(other)
108
125
  # double dispatch
109
126
  other.eql?(snapshot)
@@ -114,13 +131,13 @@ module BinData
114
131
  end
115
132
 
116
133
  def do_read(io) #:nodoc:
117
- @value = read_and_return_value(io)
118
-
119
- trace_value
134
+ @value = read_and_return_value(io)
135
+ hook_after_do_read
136
+ end
120
137
 
121
- if has_parameter?(:check_value)
122
- check_value(value)
123
- end
138
+ def do_read_with_check_value(io) #:nodoc:
139
+ do_read_without_check_value(io)
140
+ check_value(snapshot)
124
141
  end
125
142
 
126
143
  def do_write(io) #:nodoc:
@@ -134,12 +151,7 @@ module BinData
134
151
  #---------------
135
152
  private
136
153
 
137
- def trace_value
138
- BinData::trace_message do |tracer|
139
- value_string = _value.inspect
140
- tracer.trace_obj(debug_name, value_string)
141
- end
142
- end
154
+ def hook_after_do_read; end
143
155
 
144
156
  def check_value(current_value)
145
157
  expected = eval_parameter(:check_value, :value => current_value)
@@ -153,28 +165,34 @@ module BinData
153
165
  end
154
166
  end
155
167
 
156
- # The unmodified value of this data object. Note that #value calls this
157
- # method. This indirection is so that #value can be overridden in
158
- # subclasses to modify the value.
168
+ # The unmodified value of this data object. Note that #snapshot calls this
169
+ # method. This indirection is so that #snapshot can be overridden in
170
+ # subclasses to modify the presentation value.
171
+ #
172
+ # Table of possible preconditions and expected outcome
173
+ # 1. :value and !reading? -> :value
174
+ # 2. :value and reading? -> @value
175
+ # 3. :initial_value and clear? -> :initial_value
176
+ # 4. :initial_value and !clear? -> @value
177
+ # 5. clear? -> sensible_default
178
+ # 6. !clear? -> @value
179
+
159
180
  def _value
160
- # Table of possible preconditions and expected outcome
161
- # 1. :value and !reading? -> :value
162
- # 2. :value and reading? -> @value
163
- # 3. :initial_value and clear? -> :initial_value
164
- # 4. :initial_value and !clear? -> @value
165
- # 5. clear? -> sensible_default
166
- # 6. !clear? -> @value
167
-
168
- if has_parameter?(:value) and not reading?
169
- # rule 1 above
170
- eval_parameter(:value)
181
+ @value || sensible_default()
182
+ end
183
+
184
+ def _value_with_value #:nodoc:
185
+ if reading?
186
+ @value
171
187
  else
172
- # combining all other rules gives this simplified expression
173
- @value || eval_parameter(:value) ||
174
- eval_parameter(:initial_value) || sensible_default()
188
+ eval_parameter(:value)
175
189
  end
176
190
  end
177
191
 
192
+ def _value_with_initial_value #:nodoc:
193
+ @value || eval_parameter(:initial_value)
194
+ end
195
+
178
196
  ###########################################################################
179
197
  # To be implemented by subclasses
180
198