bindata 2.4.15 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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