bindata 2.4.15 → 2.5.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.rdoc +11 -0
  3. data/README.md +6 -9
  4. data/bindata.gemspec +3 -3
  5. data/examples/list.rb +1 -1
  6. data/lib/bindata/alignment.rb +15 -7
  7. data/lib/bindata/array.rb +54 -54
  8. data/lib/bindata/base.rb +14 -25
  9. data/lib/bindata/base_primitive.rb +24 -20
  10. data/lib/bindata/bits.rb +5 -5
  11. data/lib/bindata/buffer.rb +89 -11
  12. data/lib/bindata/choice.rb +9 -6
  13. data/lib/bindata/count_bytes_remaining.rb +1 -1
  14. data/lib/bindata/delayed_io.rb +10 -10
  15. data/lib/bindata/dsl.rb +34 -32
  16. data/lib/bindata/float.rb +3 -3
  17. data/lib/bindata/framework.rb +8 -10
  18. data/lib/bindata/int.rb +9 -9
  19. data/lib/bindata/io.rb +276 -253
  20. data/lib/bindata/name.rb +1 -1
  21. data/lib/bindata/params.rb +9 -7
  22. data/lib/bindata/primitive.rb +3 -3
  23. data/lib/bindata/registry.rb +18 -18
  24. data/lib/bindata/rest.rb +1 -1
  25. data/lib/bindata/sanitize.rb +9 -16
  26. data/lib/bindata/section.rb +97 -0
  27. data/lib/bindata/skip.rb +140 -51
  28. data/lib/bindata/string.rb +9 -9
  29. data/lib/bindata/stringz.rb +12 -10
  30. data/lib/bindata/struct.rb +83 -66
  31. data/lib/bindata/trace.rb +35 -42
  32. data/lib/bindata/transform/brotli.rb +35 -0
  33. data/lib/bindata/transform/lz4.rb +35 -0
  34. data/lib/bindata/transform/lzma.rb +35 -0
  35. data/lib/bindata/transform/xor.rb +19 -0
  36. data/lib/bindata/transform/xz.rb +35 -0
  37. data/lib/bindata/transform/zlib.rb +33 -0
  38. data/lib/bindata/transform/zstd.rb +35 -0
  39. data/lib/bindata/uint8_array.rb +2 -2
  40. data/lib/bindata/version.rb +1 -1
  41. data/lib/bindata/virtual.rb +4 -7
  42. data/lib/bindata/warnings.rb +1 -1
  43. data/lib/bindata.rb +1 -0
  44. data/test/array_test.rb +10 -8
  45. data/test/buffer_test.rb +9 -0
  46. data/test/choice_test.rb +1 -1
  47. data/test/delayed_io_test.rb +16 -0
  48. data/test/io_test.rb +54 -246
  49. data/test/registry_test.rb +1 -1
  50. data/test/section_test.rb +111 -0
  51. data/test/skip_test.rb +55 -10
  52. data/test/string_test.rb +4 -4
  53. data/test/stringz_test.rb +8 -0
  54. data/test/struct_test.rb +87 -12
  55. data/test/system_test.rb +119 -1
  56. data/test/test_helper.rb +24 -13
  57. data/test/warnings_test.rb +12 -0
  58. metadata +17 -16
  59. data/lib/bindata/offset.rb +0 -94
  60. data/test/offset_test.rb +0 -100
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80617b407b7a7c2213a57dd816a4c34b144dc7bf84cfbedb021b4121f733d375
4
- data.tar.gz: 4a71111bd4ee444bd71bde4d2e1cf4242501618d4884b93f72eabdc7f0cd6ed6
3
+ metadata.gz: 6fb38e8b0ef072a6acb74e9a623307791ba9619423e445635bf949c6ede9a90a
4
+ data.tar.gz: 026e68ad13001b0ae2922bbe909882d5bec2c44c3380ab894cf73e3fbce8de61
5
5
  SHA512:
6
- metadata.gz: 4de56ea71fa7e19e79d668eca56fd767a6dc781ce2184ef6d0373835ca73650d423cecd65c9066a1cd3e2055d66f87223688b78737da5630401cda06ec6ab5ee
7
- data.tar.gz: 33865f2dca175323ed73af86b52ec4c507b3b1ef1af6b4eeef103fbfe422607cb40476e558dda85e13ffad603ab6f8150a5798bdd1051a164dc54604ec7c5cf1
6
+ metadata.gz: 3a46d236c6206417f271782956e3aaa376f793ef9cc58f88de92577b10537a5cd2ce8cc8f2399e1a58d0bb1f1ab749d57f0ea6aa8532c78fd80dc0e998b3abb4
7
+ data.tar.gz: a1d2988f1bcfda8c3f1177338d34b5af0e7f62dd4eea55905ef3c49e5cb989a7b07e30d1b7d42606a4ca005bcfa3ef034829520b52df86ca9bafe25cb7f2bf1c
data/ChangeLog.rdoc CHANGED
@@ -1,5 +1,16 @@
1
1
  = BinData Changelog
2
2
 
3
+ == Version 2.5.0 (2024-02-16)
4
+
5
+ * Removed experimental :check_offset and :adjust_offset parameters.
6
+ * Ruby 2.5 is now required.
7
+ * Allow for nested tracing.
8
+ * Skip :until_valid is now fast for :asserted_value.
9
+ * Added Section - a way to transform the data stream.
10
+ * Added transforms for brotli, lz4, xor, zlib, zstd.
11
+ * Updated to current minitest
12
+ * Fixed typos. Thanks to Patrick Linnane.
13
+
3
14
  == Version 2.4.15 (2023-02-07)
4
15
 
5
16
  * Added ruby 2.4.0 requirement to gemspec. Thanks to theldoria.
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # What is BinData?
2
2
 
3
- [![Version ](https://img.shields.io/gem/v/bindata.svg) ](https://rubygems.org/gems/bindata)
4
- [![Github CI ](https://github.com/dmendel/bindata/actions/workflows/ci.yml/badge.svg) ](https://github.com/dmendel/bindata/actions/workflows/ci.yml)
5
- [![Coverage ](https://img.shields.io/coveralls/dmendel/bindata.svg) ](https://coveralls.io/r/dmendel/bindata)
3
+ [![Github CI](https://github.com/dmendel/bindata/actions/workflows/ci.yml/badge.svg)](https://github.com/dmendel/bindata/actions/workflows/ci.yml)
4
+ [![Version](https://img.shields.io/gem/v/bindata.svg)](https://rubygems.org/gems/bindata)
5
+ [![Downloads](https://img.shields.io/gem/dt/bindata.svg)](https://rubygems.org/gems/bindata)
6
+ [![Coverage](https://img.shields.io/coveralls/dmendel/bindata.svg)](https://coveralls.io/r/dmendel/bindata)
6
7
 
7
8
  Do you ever find yourself writing code like this?
8
9
 
@@ -14,7 +15,7 @@ width, height = io.read(8).unpack("VV")
14
15
  puts "Rectangle #{name} is #{width} x #{height}"
15
16
  ```
16
17
 
17
- It’s ugly, violates DRY and feels like you’re writing Perl, not Ruby.
18
+ It’s ugly, violates DRY and doesn't feel like Ruby.
18
19
 
19
20
  There is a better way. Here’s how you’d write the above using BinData.
20
21
 
@@ -47,13 +48,9 @@ for dependent and variable length fields is built in.
47
48
 
48
49
  $ gem install bindata
49
50
 
50
- or if running ruby 1.8
51
-
52
- $ gem install bindata -v '~> 1.8.0'
53
-
54
51
  # Documentation
55
52
 
56
- [Read the wiki](http://github.com/dmendel/bindata/wiki).
53
+ [BinData manual](http://github.com/dmendel/bindata/wiki).
57
54
 
58
55
  # Contact
59
56
 
data/bindata.gemspec CHANGED
@@ -18,11 +18,11 @@ Gem::Specification.new do |s|
18
18
  end
19
19
  end
20
20
  s.license = 'BSD-2-Clause'
21
- s.required_ruby_version = ">= 2.4.0"
21
+ s.required_ruby_version = ">= 2.5.0"
22
22
 
23
23
  s.add_development_dependency('rake')
24
- s.add_development_dependency('minitest', "> 5.0.0", "< 5.12.0")
25
- s.add_development_dependency('coveralls')
24
+ s.add_development_dependency('minitest', "> 5.0.0")
25
+ s.add_development_dependency('simplecov')
26
26
  s.description = <<-END.gsub(/^ +/, "")
27
27
  BinData is a declarative way to read and write binary file formats.
28
28
 
data/examples/list.rb CHANGED
@@ -35,7 +35,7 @@ require 'bindata'
35
35
  # end
36
36
  # end
37
37
  #
38
- # Notice how we get stuck on attemping to write a declaration for
38
+ # Notice how we get stuck on attempting to write a declaration for
39
39
  # the contents of the list. We can't determine if the list item is
40
40
  # an atom or list because we haven't read it yet. It appears that
41
41
  # we can't proceed.
@@ -19,11 +19,11 @@ module BinData
19
19
  def do_num_bytes; 0; end
20
20
 
21
21
  def do_read(io)
22
- io.reset_read_bits
22
+ io.readbytes(0)
23
23
  end
24
24
 
25
25
  def do_write(io)
26
- io.flushbits
26
+ io.writebytes("")
27
27
  end
28
28
  end
29
29
 
@@ -45,18 +45,26 @@ module BinData
45
45
  def initialize(io)
46
46
  @io = io
47
47
  end
48
+
49
+ def binary_string(str)
50
+ str.to_s.dup.force_encoding(Encoding::BINARY)
51
+ end
52
+
48
53
  def readbytes(n)
49
- n.times.inject("") do |bytes, _|
50
- bytes += @io.readbits(8, :big).chr
54
+ n.times.inject(binary_string("")) do |bytes, _|
55
+ bytes + @io.readbits(8, :big).chr
51
56
  end
52
57
  end
58
+ def writebytes(str)
59
+ str.each_byte { |v| @io.writebits(v, 8, :big) }
60
+ end
53
61
  end
54
62
 
55
63
  def bit_aligned?
56
64
  true
57
65
  end
58
66
 
59
- def read_and_return_value(io)
67
+ def do_read(io)
60
68
  super(BitAlignedIO.new(io))
61
69
  end
62
70
 
@@ -65,7 +73,7 @@ module BinData
65
73
  end
66
74
 
67
75
  def do_write(io)
68
- value_to_binary_string(_value).each_byte { |v| io.writebits(v, 8, :big) }
76
+ super(BitAlignedIO.new(io))
69
77
  end
70
78
  end
71
79
 
@@ -74,6 +82,6 @@ module BinData
74
82
  end
75
83
 
76
84
  def Primitive.bit_aligned
77
- fail "'bit_aligned' is not needed for BinData::Primitives"
85
+ fail "'bit_aligned' is not supported for BinData::Primitives"
78
86
  end
79
87
  end
data/lib/bindata/array.rb CHANGED
@@ -72,18 +72,18 @@ module BinData
72
72
  end
73
73
 
74
74
  def initialize_instance
75
- @element_list = nil
75
+ @elements = nil
76
76
  end
77
77
 
78
78
  def clear?
79
- @element_list.nil? || elements.all?(&:clear?)
79
+ @elements.nil? || elements.all?(&:clear?)
80
80
  end
81
81
 
82
82
  def assign(array)
83
83
  return if self.equal?(array) # prevent self assignment
84
84
  raise ArgumentError, "can't set a nil value for #{debug_name}" if array.nil?
85
85
 
86
- @element_list = []
86
+ @elements = []
87
87
  concat(array)
88
88
  end
89
89
 
@@ -220,23 +220,23 @@ module BinData
220
220
  elements.each { |el| yield el }
221
221
  end
222
222
 
223
- def debug_name_of(child) #:nodoc:
223
+ def debug_name_of(child) # :nodoc:
224
224
  index = find_index_of(child)
225
225
  "#{debug_name}[#{index}]"
226
226
  end
227
227
 
228
- def offset_of(child) #:nodoc:
228
+ def offset_of(child) # :nodoc:
229
229
  index = find_index_of(child)
230
230
  sum = sum_num_bytes_below_index(index)
231
231
 
232
232
  child.bit_aligned? ? sum.floor : sum.ceil
233
233
  end
234
234
 
235
- def do_write(io) #:nodoc:
235
+ def do_write(io) # :nodoc:
236
236
  elements.each { |el| el.do_write(io) }
237
237
  end
238
238
 
239
- def do_num_bytes #:nodoc:
239
+ def do_num_bytes # :nodoc:
240
240
  sum_num_bytes_for_all_elements
241
241
  end
242
242
 
@@ -251,7 +251,7 @@ module BinData
251
251
  end
252
252
 
253
253
  def elements
254
- @element_list ||= []
254
+ @elements ||= []
255
255
  end
256
256
 
257
257
  def append_new_element
@@ -279,66 +279,66 @@ module BinData
279
279
  end
280
280
  end
281
281
  end
282
- end
283
282
 
284
- class ArrayArgProcessor < BaseArgProcessor
285
- def sanitize_parameters!(obj_class, params) #:nodoc:
286
- # ensure one of :initial_length and :read_until exists
287
- unless params.has_at_least_one_of?(:initial_length, :read_until)
288
- params[:initial_length] = 0
283
+ # Logic for the :read_until parameter
284
+ module ReadUntilPlugin
285
+ def do_read(io)
286
+ loop do
287
+ element = append_new_element
288
+ element.do_read(io)
289
+ variables = { index: self.length - 1, element: self.last, array: self }
290
+ break if eval_parameter(:read_until, variables)
291
+ end
289
292
  end
290
-
291
- params.warn_replacement_parameter(:length, :initial_length)
292
- params.warn_replacement_parameter(:read_length, :initial_length)
293
- params.must_be_integer(:initial_length)
294
-
295
- params.merge!(obj_class.dsl_params)
296
- params.sanitize_object_prototype(:type)
297
293
  end
298
- end
299
294
 
300
- # Logic for the :read_until parameter
301
- module ReadUntilPlugin
302
- def do_read(io)
303
- loop do
304
- element = append_new_element
305
- element.do_read(io)
306
- variables = { index: self.length - 1, element: self.last, array: self }
307
- break if eval_parameter(:read_until, variables)
295
+ # Logic for the read_until: :eof parameter
296
+ module ReadUntilEOFPlugin
297
+ def do_read(io)
298
+ loop do
299
+ element = append_new_element
300
+ begin
301
+ element.do_read(io)
302
+ rescue EOFError, IOError
303
+ elements.pop
304
+ break
305
+ end
306
+ end
308
307
  end
309
308
  end
310
- end
311
309
 
312
- # Logic for the read_until: :eof parameter
313
- module ReadUntilEOFPlugin
314
- def do_read(io)
315
- loop do
316
- element = append_new_element
317
- begin
318
- element.do_read(io)
319
- rescue EOFError, IOError
320
- elements.pop
321
- break
310
+ # Logic for the :initial_length parameter
311
+ module InitialLengthPlugin
312
+ def do_read(io)
313
+ elements.each { |el| el.do_read(io) }
314
+ end
315
+
316
+ def elements
317
+ if @elements.nil?
318
+ @elements = []
319
+ eval_parameter(:initial_length).times do
320
+ @elements << new_element
321
+ end
322
322
  end
323
+
324
+ @elements
323
325
  end
324
326
  end
325
327
  end
326
328
 
327
- # Logic for the :initial_length parameter
328
- module InitialLengthPlugin
329
- def do_read(io)
330
- elements.each { |el| el.do_read(io) }
331
- end
332
-
333
- def elements
334
- if @element_list.nil?
335
- @element_list = []
336
- eval_parameter(:initial_length).times do
337
- @element_list << new_element
338
- end
329
+ class ArrayArgProcessor < BaseArgProcessor
330
+ def sanitize_parameters!(obj_class, params) # :nodoc:
331
+ # ensure one of :initial_length and :read_until exists
332
+ unless params.has_at_least_one_of?(:initial_length, :read_until)
333
+ params[:initial_length] = 0
339
334
  end
340
335
 
341
- @element_list
336
+ params.warn_replacement_parameter(:length, :initial_length)
337
+ params.warn_replacement_parameter(:read_length, :initial_length)
338
+ params.must_be_integer(:initial_length)
339
+
340
+ params.merge!(obj_class.dsl_params)
341
+ params.sanitize_object_prototype(:type)
342
342
  end
343
343
  end
344
344
  end
data/lib/bindata/base.rb CHANGED
@@ -17,7 +17,7 @@ module BinData
17
17
  # Instantiates this class and reads from +io+, returning the newly
18
18
  # created data object. +args+ will be used when instantiating.
19
19
  def read(io, *args, &block)
20
- obj = self.new(*args)
20
+ obj = new(*args)
21
21
  obj.read(io, &block)
22
22
  obj
23
23
  end
@@ -48,7 +48,7 @@ module BinData
48
48
  end
49
49
 
50
50
  # Registers all subclasses of this class for use
51
- def register_subclasses #:nodoc:
51
+ def register_subclasses # :nodoc:
52
52
  singleton_class.send(:undef_method, :inherited)
53
53
  define_singleton_method(:inherited) do |subclass|
54
54
  RegisteredClasses.register(subclass.name, subclass)
@@ -90,6 +90,8 @@ module BinData
90
90
 
91
91
  # Creates a new data object based on this instance.
92
92
  #
93
+ # This implements the prototype design pattern.
94
+ #
93
95
  # All parameters will be be duplicated. Use this method
94
96
  # when creating multiple objects with the same parameters.
95
97
  def new(value = nil, parent = nil)
@@ -117,8 +119,8 @@ module BinData
117
119
  end
118
120
 
119
121
  # Returns a lazy evaluator for this object.
120
- def lazy_evaluator #:nodoc:
121
- @lazy ||= LazyEvaluator.new(self)
122
+ def lazy_evaluator # :nodoc:
123
+ @lazy_evaluator ||= LazyEvaluator.new(self)
122
124
  end
123
125
 
124
126
  # Returns the parameter referenced by +key+.
@@ -177,7 +179,7 @@ module BinData
177
179
 
178
180
  # Returns the hexadecimal string representation of this data object.
179
181
  def to_hex(&block)
180
- to_binary_s(&block).unpack('H*')[0]
182
+ to_binary_s(&block).unpack1('H*')
181
183
  end
182
184
 
183
185
  # Return a human readable representation of this data object.
@@ -191,7 +193,7 @@ module BinData
191
193
  end
192
194
 
193
195
  # Work with Ruby's pretty-printer library.
194
- def pretty_print(pp) #:nodoc:
196
+ def pretty_print(pp) # :nodoc:
195
197
  pp.pp(snapshot)
196
198
  end
197
199
 
@@ -202,40 +204,28 @@ module BinData
202
204
 
203
205
  # Returns a user friendly name of this object for debugging purposes.
204
206
  def debug_name
205
- if @parent
206
- @parent.debug_name_of(self)
207
- else
208
- "obj"
209
- end
207
+ @parent ? @parent.debug_name_of(self) : 'obj'
210
208
  end
211
209
 
212
210
  # Returns the offset (in bytes) of this object with respect to its most
213
211
  # distant ancestor.
214
212
  def abs_offset
215
- if @parent
216
- @parent.abs_offset + @parent.offset_of(self)
217
- else
218
- 0
219
- end
213
+ @parent ? @parent.abs_offset + @parent.offset_of(self) : 0
220
214
  end
221
215
 
222
216
  # Returns the offset (in bytes) of this object with respect to its parent.
223
217
  def rel_offset
224
- if @parent
225
- @parent.offset_of(self)
226
- else
227
- 0
228
- end
218
+ @parent ? @parent.offset_of(self) : 0
229
219
  end
230
220
 
231
- def ==(other) #:nodoc:
221
+ def ==(other) # :nodoc:
232
222
  # double dispatch
233
223
  other == snapshot
234
224
  end
235
225
 
236
226
  # A version of +respond_to?+ used by the lazy evaluator. It doesn't
237
227
  # reinvoke the evaluator so as to avoid infinite evaluation loops.
238
- def safe_respond_to?(symbol, include_private = false) #:nodoc:
228
+ def safe_respond_to?(symbol, include_private = false) # :nodoc:
239
229
  base_respond_to?(symbol, include_private)
240
230
  end
241
231
 
@@ -329,7 +319,6 @@ module BinData
329
319
  # Performs sanity checks on the given parameters.
330
320
  # This method converts the parameters to the form expected
331
321
  # by the data object.
332
- def sanitize_parameters!(obj_class, obj_params)
333
- end
322
+ def sanitize_parameters!(obj_class, obj_params); end
334
323
  end
335
324
  end
@@ -65,7 +65,7 @@ module BinData
65
65
  @value = nil
66
66
  end
67
67
 
68
- def clear? #:nodoc:
68
+ def clear? # :nodoc:
69
69
  @value.nil?
70
70
  end
71
71
 
@@ -73,13 +73,7 @@ module BinData
73
73
  raise ArgumentError, "can't set a nil value for #{debug_name}" if val.nil?
74
74
 
75
75
  raw_val = val.respond_to?(:snapshot) ? val.snapshot : val
76
- @value =
77
- begin
78
- raw_val.dup
79
- rescue TypeError
80
- # can't dup Fixnums
81
- raw_val
82
- end
76
+ @value = raw_val.dup
83
77
  end
84
78
 
85
79
  def snapshot
@@ -94,18 +88,19 @@ module BinData
94
88
  assign(val)
95
89
  end
96
90
 
97
- def respond_to?(symbol, include_private = false) #:nodoc:
91
+ def respond_to_missing?(symbol, include_all = false) # :nodoc:
98
92
  child = snapshot
99
- child.respond_to?(symbol, include_private) || super
93
+ child.respond_to?(symbol, include_all) || super
100
94
  end
101
95
 
102
- def method_missing(symbol, *args, &block) #:nodoc:
96
+ def method_missing(symbol, *args, &block) # :nodoc:
103
97
  child = snapshot
104
98
  if child.respond_to?(symbol)
105
- self.class.class_eval \
106
- "def #{symbol}(*args, &block);" \
107
- " snapshot.#{symbol}(*args, &block);" \
108
- "end"
99
+ self.class.class_eval <<-END, __FILE__, __LINE__ + 1
100
+ def #{symbol}(*args, &block) # def clamp(*args, &block)
101
+ snapshot.#{symbol}(*args, &block) # snapshot.clamp(*args, &block)
102
+ end # end
103
+ END
109
104
  child.__send__(symbol, *args, &block)
110
105
  else
111
106
  super
@@ -125,15 +120,15 @@ module BinData
125
120
  snapshot.hash
126
121
  end
127
122
 
128
- def do_read(io) #:nodoc:
123
+ def do_read(io) # :nodoc:
129
124
  @value = read_and_return_value(io)
130
125
  end
131
126
 
132
- def do_write(io) #:nodoc:
127
+ def do_write(io) # :nodoc:
133
128
  io.writebytes(value_to_binary_string(_value))
134
129
  end
135
130
 
136
- def do_num_bytes #:nodoc:
131
+ def do_num_bytes # :nodoc:
137
132
  value_to_binary_string(_value).length
138
133
  end
139
134
 
@@ -172,7 +167,7 @@ module BinData
172
167
  assert!
173
168
  end
174
169
 
175
- def do_read(io) #:nodoc:
170
+ def do_read(io) # :nodoc:
176
171
  super(io)
177
172
  assert!
178
173
  end
@@ -205,7 +200,16 @@ module BinData
205
200
  reading? ? @value : eval_parameter(:asserted_value)
206
201
  end
207
202
 
208
- def do_read(io) #:nodoc:
203
+ # The asserted value as a binary string.
204
+ #
205
+ # Rationale: while reading, +#to_binary_s+ will use the
206
+ # value read in, rather than the +:asserted_value+.
207
+ # This feature is used by Skip.
208
+ def asserted_binary_s
209
+ value_to_binary_string(eval_parameter(:asserted_value))
210
+ end
211
+
212
+ def do_read(io) # :nodoc:
209
213
  super(io)
210
214
  assert!
211
215
  end
data/lib/bindata/bits.rb CHANGED
@@ -5,7 +5,7 @@ module BinData
5
5
  # Defines a number of classes that contain a bit based integer.
6
6
  # The integer is defined by endian and number of bits.
7
7
 
8
- module BitField #:nodoc: all
8
+ module BitField # :nodoc: all
9
9
  @@mutex = Mutex.new
10
10
 
11
11
  class << self
@@ -156,10 +156,10 @@ module BinData
156
156
 
157
157
  # Create classes for dynamic bitfields
158
158
  {
159
- "Bit" => :big,
160
- "BitLe" => :little,
161
- "Sbit" => [:big, :signed],
162
- "SbitLe" => [:little, :signed],
159
+ 'Bit' => :big,
160
+ 'BitLe' => :little,
161
+ 'Sbit' => [:big, :signed],
162
+ 'SbitLe' => [:little, :signed]
163
163
  }.each_pair { |name, args| BitField.define_class(name, :nbits, *args) }
164
164
 
165
165
  # Create classes on demand
@@ -41,7 +41,7 @@ module BinData
41
41
  # end
42
42
  # end
43
43
  # end
44
- #
44
+ #
45
45
  #
46
46
  # == Parameters
47
47
  #
@@ -80,29 +80,107 @@ module BinData
80
80
  @type.snapshot
81
81
  end
82
82
 
83
- def respond_to?(symbol, include_private = false) #:nodoc:
84
- @type.respond_to?(symbol, include_private) || super
83
+ def respond_to_missing?(symbol, include_all = false) # :nodoc:
84
+ @type.respond_to?(symbol, include_all) || super
85
85
  end
86
86
 
87
- def method_missing(symbol, *args, &block) #:nodoc:
87
+ def method_missing(symbol, *args, &block) # :nodoc:
88
88
  @type.__send__(symbol, *args, &block)
89
89
  end
90
90
 
91
- def do_read(io) #:nodoc:
92
- io.with_buffer(eval_parameter(:length)) do
93
- @type.do_read(io)
91
+ def do_read(io) # :nodoc:
92
+ buf_len = eval_parameter(:length)
93
+ io.transform(BufferIO.new(buf_len)) do |transformed_io, _|
94
+ @type.do_read(transformed_io)
94
95
  end
95
96
  end
96
97
 
97
- def do_write(io) #:nodoc:
98
- io.with_buffer(eval_parameter(:length)) do
99
- @type.do_write(io)
98
+ def do_write(io) # :nodoc:
99
+ buf_len = eval_parameter(:length)
100
+ io.transform(BufferIO.new(buf_len)) do |transformed_io, _|
101
+ @type.do_write(transformed_io)
100
102
  end
101
103
  end
102
104
 
103
- def do_num_bytes #:nodoc:
105
+ def do_num_bytes # :nodoc:
104
106
  eval_parameter(:length)
105
107
  end
108
+
109
+ # Transforms the IO stream to restrict access inside
110
+ # a buffer of specified length.
111
+ class BufferIO < IO::Transform
112
+ def initialize(length)
113
+ super()
114
+ @bytes_remaining = length
115
+ end
116
+
117
+ def before_transform
118
+ @buf_start = offset
119
+ @buf_end = @buf_start + @bytes_remaining
120
+ end
121
+
122
+ def num_bytes_remaining
123
+ [@bytes_remaining, super].min
124
+ rescue IOError
125
+ @bytes_remaining
126
+ end
127
+
128
+ def skip(n)
129
+ nbytes = buffer_limited_n(n)
130
+ @bytes_remaining -= nbytes
131
+
132
+ chain_skip(nbytes)
133
+ end
134
+
135
+ def seek_abs(n)
136
+ if n < @buf_start || n >= @buf_end
137
+ raise IOError, "can not seek to abs_offset outside of buffer"
138
+ end
139
+
140
+ @bytes_remaining -= (n - offset)
141
+ chain_seek_abs(n)
142
+ end
143
+
144
+ def read(n)
145
+ nbytes = buffer_limited_n(n)
146
+ @bytes_remaining -= nbytes
147
+
148
+ chain_read(nbytes)
149
+ end
150
+
151
+ def write(data)
152
+ nbytes = buffer_limited_n(data.size)
153
+ @bytes_remaining -= nbytes
154
+ if nbytes < data.size
155
+ data = data[0, nbytes]
156
+ end
157
+
158
+ chain_write(data)
159
+ end
160
+
161
+ def after_read_transform
162
+ read(nil)
163
+ end
164
+
165
+ def after_write_transform
166
+ write("\x00" * @bytes_remaining)
167
+ end
168
+
169
+ def buffer_limited_n(n)
170
+ if n.nil?
171
+ @bytes_remaining
172
+ elsif n.positive?
173
+ limit = @bytes_remaining
174
+ n > limit ? limit : n
175
+ # uncomment if we decide to allow backwards skipping
176
+ # elsif n.negative?
177
+ # limit = @bytes_remaining + @buf_start - @buf_end
178
+ # n < limit ? limit : n
179
+ else
180
+ 0
181
+ end
182
+ end
183
+ end
106
184
  end
107
185
 
108
186
  class BufferArgProcessor < BaseArgProcessor