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
data/lib/bindata/io.rb CHANGED
@@ -5,217 +5,10 @@ module BinData
5
5
  # interface for BinData objects to use when accessing the IO.
6
6
  module IO
7
7
 
8
- # Common operations for both Read and Write.
9
- module Common
10
- def initialize(io)
11
- if self.class === io
12
- raise ArgumentError, "io must not be a #{self.class}"
13
- end
14
-
15
- # wrap strings in a StringIO
16
- if io.respond_to?(:to_str)
17
- io = BinData::IO.create_string_io(io.to_str)
18
- end
19
-
20
- @raw_io = io
21
- @buffer_end_points = nil
22
-
23
- extend seekable? ? SeekableStream : UnSeekableStream
24
- stream_init
25
- end
26
-
27
- #-------------
28
- private
29
-
30
- def seekable?
31
- @raw_io.pos
32
- rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE, Errno::EINVAL
33
- nil
34
- end
35
-
36
- def seek(n)
37
- seek_raw(buffer_limited_n(n))
38
- end
39
-
40
- def buffer_limited_n(n)
41
- if @buffer_end_points
42
- if n.nil? || n > 0
43
- max = @buffer_end_points[1] - offset
44
- n = max if n.nil? || n > max
45
- else
46
- min = @buffer_end_points[0] - offset
47
- n = min if n < min
48
- end
49
- end
50
-
51
- n
52
- end
53
-
54
- def with_buffer_common(n)
55
- prev = @buffer_end_points
56
- if prev
57
- avail = prev[1] - offset
58
- n = avail if n > avail
59
- end
60
- @buffer_end_points = [offset, offset + n]
61
- begin
62
- yield(*@buffer_end_points)
63
- ensure
64
- @buffer_end_points = prev
65
- end
66
- end
67
-
68
- # Use #seek and #pos on seekable streams
69
- module SeekableStream
70
- # The number of bytes remaining in the input stream.
71
- def num_bytes_remaining
72
- start_mark = @raw_io.pos
73
- @raw_io.seek(0, ::IO::SEEK_END)
74
- end_mark = @raw_io.pos
75
-
76
- if @buffer_end_points
77
- if @buffer_end_points[1] < end_mark
78
- end_mark = @buffer_end_points[1]
79
- end
80
- end
81
-
82
- bytes_remaining = end_mark - start_mark
83
- @raw_io.seek(start_mark, ::IO::SEEK_SET)
84
-
85
- bytes_remaining
86
- end
87
-
88
- # All io calls in +block+ are rolled back after this
89
- # method completes.
90
- def with_readahead
91
- mark = @raw_io.pos
92
- begin
93
- yield
94
- ensure
95
- @raw_io.seek(mark, ::IO::SEEK_SET)
96
- end
97
- end
98
-
99
- #-----------
100
- private
101
-
102
- def stream_init
103
- @initial_pos = @raw_io.pos
104
- end
105
-
106
- def offset_raw
107
- @raw_io.pos - @initial_pos
108
- end
109
-
110
- def seek_raw(n)
111
- @raw_io.seek(n, ::IO::SEEK_CUR)
112
- end
113
-
114
- def read_raw(n)
115
- @raw_io.read(n)
116
- end
117
-
118
- def write_raw(data)
119
- @raw_io.write(data)
120
- end
121
- end
122
-
123
- # Manually keep track of offset for unseekable streams.
124
- module UnSeekableStream
125
- def offset_raw
126
- @offset
127
- end
128
-
129
- # The number of bytes remaining in the input stream.
130
- def num_bytes_remaining
131
- raise IOError, "stream is unseekable"
132
- end
133
-
134
- # All io calls in +block+ are rolled back after this
135
- # method completes.
136
- def with_readahead
137
- mark = @offset
138
- @read_data = ""
139
- @in_readahead = true
140
-
141
- class << self
142
- alias_method :read_raw_without_readahead, :read_raw
143
- alias_method :read_raw, :read_raw_with_readahead
144
- end
145
-
146
- begin
147
- yield
148
- ensure
149
- @offset = mark
150
- @in_readahead = false
151
- end
152
- end
153
-
154
- #-----------
155
- private
156
-
157
- def stream_init
158
- @offset = 0
159
- end
160
-
161
- def read_raw(n)
162
- data = @raw_io.read(n)
163
- @offset += data.size if data
164
- data
165
- end
166
-
167
- def read_raw_with_readahead(n)
168
- data = ""
169
-
170
- unless @read_data.empty? || @in_readahead
171
- bytes_to_consume = [n, @read_data.length].min
172
- data += @read_data.slice!(0, bytes_to_consume)
173
- n -= bytes_to_consume
174
-
175
- if @read_data.empty?
176
- class << self
177
- alias_method :read_raw, :read_raw_without_readahead
178
- end
179
- end
180
- end
181
-
182
- raw_data = @raw_io.read(n)
183
- data += raw_data if raw_data
184
-
185
- if @in_readahead
186
- @read_data += data
187
- end
188
-
189
- @offset += data.size
190
-
191
- data
192
- end
193
-
194
- def write_raw(data)
195
- @offset += data.size
196
- @raw_io.write(data)
197
- end
198
-
199
- def seek_raw(n)
200
- raise IOError, "stream is unseekable" if n < 0
201
-
202
- # NOTE: how do we seek on a writable stream?
203
-
204
- # skip over data in 8k blocks
205
- while n > 0
206
- bytes_to_read = [n, 8192].min
207
- read_raw(bytes_to_read)
208
- n -= bytes_to_read
209
- end
210
- end
211
- end
212
- end
213
-
214
8
  # Creates a StringIO around +str+.
215
9
  def self.create_string_io(str = "")
216
- s = StringIO.new(str.dup.force_encoding(Encoding::BINARY))
217
- s.binmode
218
- s
10
+ bin_str = str.dup.force_encoding(Encoding::BINARY)
11
+ StringIO.new(bin_str).tap(&:binmode)
219
12
  end
220
13
 
221
14
  # Create a new IO Read wrapper around +io+. +io+ must provide #read,
@@ -236,10 +29,17 @@ module BinData
236
29
  # readbits(6), readbits(5) #=> [543210, a9876]
237
30
  #
238
31
  class Read
239
- include Common
240
-
241
32
  def initialize(io)
242
- super(io)
33
+ if self.class === io
34
+ raise ArgumentError, "io must not be a #{self.class}"
35
+ end
36
+
37
+ # wrap strings in a StringIO
38
+ if io.respond_to?(:to_str)
39
+ io = BinData::IO.create_string_io(io.to_str)
40
+ end
41
+
42
+ @io = RawIO.new(io)
243
43
 
244
44
  # bits when reading
245
45
  @rnbits = 0
@@ -247,25 +47,38 @@ module BinData
247
47
  @rendian = nil
248
48
  end
249
49
 
250
- # Sets a buffer of +n+ bytes on the io stream. Any reading or seeking
251
- # calls inside the +block+ will be contained within this buffer.
252
- def with_buffer(n)
253
- with_buffer_common(n) do
254
- yield
255
- read
256
- end
50
+ # Allow transforming data in the input stream.
51
+ # See +BinData::Buffer+ as an example.
52
+ #
53
+ # +io+ must be an instance of +Transform+.
54
+ #
55
+ # yields +self+ and +io+ to the given block
56
+ def transform(io)
57
+ reset_read_bits
58
+
59
+ saved = @io
60
+ @io = io.prepend_to_chain(@io)
61
+ yield(self, io)
62
+ io.after_read_transform
63
+ ensure
64
+ @io = saved
257
65
  end
258
66
 
259
- # Returns the current offset of the io stream. Offset will be rounded
260
- # up when reading bitfields.
261
- def offset
262
- offset_raw
67
+ # The number of bytes remaining in the io steam.
68
+ def num_bytes_remaining
69
+ @io.num_bytes_remaining
263
70
  end
264
71
 
265
72
  # Seek +n+ bytes from the current position in the io stream.
266
- def seekbytes(n)
73
+ def skipbytes(n)
267
74
  reset_read_bits
268
- seek(n)
75
+ @io.skip(n)
76
+ end
77
+
78
+ # Seek to an absolute offset within the io stream.
79
+ def seek_to_abs_offset(n)
80
+ reset_read_bits
81
+ @io.seek_abs(n)
269
82
  end
270
83
 
271
84
  # Reads exactly +n+ bytes from +io+.
@@ -311,7 +124,7 @@ module BinData
311
124
  private
312
125
 
313
126
  def read(n = nil)
314
- str = read_raw(buffer_limited_n(n))
127
+ str = @io.read(n)
315
128
  if n
316
129
  raise EOFError, "End of file reached" if str.nil?
317
130
  raise IOError, "data truncated" if str.size < n
@@ -332,7 +145,7 @@ module BinData
332
145
  end
333
146
 
334
147
  def accumulate_big_endian_bits
335
- byte = read(1).unpack('C').at(0) & 0xff
148
+ byte = read(1).unpack1('C') & 0xff
336
149
  @rval = (@rval << 8) | byte
337
150
  @rnbits += 8
338
151
  end
@@ -350,7 +163,7 @@ module BinData
350
163
  end
351
164
 
352
165
  def accumulate_little_endian_bits
353
- byte = read(1).unpack('C').at(0) & 0xff
166
+ byte = read(1).unpack1('C') & 0xff
354
167
  @rval = @rval | (byte << @rnbits)
355
168
  @rnbits += 8
356
169
  end
@@ -368,36 +181,46 @@ module BinData
368
181
  #
369
182
  # See IO::Read for more information.
370
183
  class Write
371
- include Common
372
184
  def initialize(io)
373
- super(io)
185
+ if self.class === io
186
+ raise ArgumentError, "io must not be a #{self.class}"
187
+ end
188
+
189
+ # wrap strings in a StringIO
190
+ if io.respond_to?(:to_str)
191
+ io = BinData::IO.create_string_io(io.to_str)
192
+ end
193
+
194
+ @io = RawIO.new(io)
374
195
 
375
196
  @wnbits = 0
376
197
  @wval = 0
377
198
  @wendian = nil
378
199
  end
379
200
 
380
- # Sets a buffer of +n+ bytes on the io stream. Any writes inside the
381
- # +block+ will be contained within this buffer. If less than +n+ bytes
382
- # are written inside the block, the remainder will be padded with '\0'
383
- # bytes.
384
- def with_buffer(n)
385
- with_buffer_common(n) do |_buf_start, buf_end|
386
- yield
387
- write("\0" * (buf_end - offset))
388
- end
389
- end
201
+ # Allow transforming data in the output stream.
202
+ # See +BinData::Buffer+ as an example.
203
+ #
204
+ # +io+ must be an instance of +Transform+.
205
+ #
206
+ # yields +self+ and +io+ to the given block
207
+ def transform(io)
208
+ flushbits
390
209
 
391
- # Returns the current offset of the io stream. Offset will be rounded
392
- # up when writing bitfields.
393
- def offset
394
- offset_raw + (@wnbits > 0 ? 1 : 0)
210
+ saved = @io
211
+ @io = io.prepend_to_chain(@io)
212
+ yield(self, io)
213
+ io.after_write_transform
214
+ ensure
215
+ @io = saved
395
216
  end
396
217
 
397
- # Seek +n+ bytes from the current position in the io stream.
398
- def seekbytes(n)
218
+ # Seek to an absolute offset within the io stream.
219
+ def seek_to_abs_offset(n)
220
+ raise IOError, "stream is unseekable" unless @io.seekable?
221
+
399
222
  flushbits
400
- seek(n)
223
+ @io.seek_abs(n)
401
224
  end
402
225
 
403
226
  # Writes the given string of bytes to the io stream.
@@ -438,12 +261,7 @@ module BinData
438
261
  private
439
262
 
440
263
  def write(data)
441
- n = buffer_limited_n(data.size)
442
- if n < data.size
443
- data = data[0, n]
444
- end
445
-
446
- write_raw(data)
264
+ @io.write(data)
447
265
  end
448
266
 
449
267
  def write_big_endian_bits(val, nbits)
@@ -492,5 +310,210 @@ module BinData
492
310
  (1 << nbits) - 1
493
311
  end
494
312
  end
313
+
314
+ # API used to access the raw data stream.
315
+ class RawIO
316
+ def initialize(io)
317
+ @io = io
318
+ @pos = 0
319
+
320
+ if is_seekable?(io)
321
+ @initial_pos = io.pos
322
+ else
323
+ singleton_class.prepend(UnSeekableIO)
324
+ end
325
+ end
326
+
327
+ def is_seekable?(io)
328
+ io.pos
329
+ rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE, Errno::EINVAL
330
+ nil
331
+ end
332
+
333
+ def seekable?
334
+ true
335
+ end
336
+
337
+ def num_bytes_remaining
338
+ start_mark = @io.pos
339
+ @io.seek(0, ::IO::SEEK_END)
340
+ end_mark = @io.pos
341
+ @io.seek(start_mark, ::IO::SEEK_SET)
342
+
343
+ end_mark - start_mark
344
+ end
345
+
346
+ def offset
347
+ @pos
348
+ end
349
+
350
+ def skip(n)
351
+ raise IOError, "can not skip backwards" if n.negative?
352
+ @io.seek(n, ::IO::SEEK_CUR)
353
+ @pos += n
354
+ end
355
+
356
+ def seek_abs(n)
357
+ @io.seek(n + @initial_pos, ::IO::SEEK_SET)
358
+ @pos = n
359
+ end
360
+
361
+ def read(n)
362
+ @io.read(n).tap { |data| @pos += (data&.size || 0) }
363
+ end
364
+
365
+ def write(data)
366
+ @io.write(data)
367
+ end
368
+ end
369
+
370
+ # An IO stream may be transformed before processing.
371
+ # e.g. encoding, compression, buffered.
372
+ #
373
+ # Multiple transforms can be chained together.
374
+ #
375
+ # To create a new transform layer, subclass +Transform+.
376
+ # Override the public methods +#read+ and +#write+ at a minimum.
377
+ # Additionally the hook, +#before_transform+, +#after_read_transform+
378
+ # and +#after_write_transform+ are available as well.
379
+ #
380
+ # IMPORTANT! If your transform changes the size of the underlying
381
+ # data stream (e.g. compression), then call
382
+ # +::transform_changes_stream_length!+ in your subclass.
383
+ class Transform
384
+ class << self
385
+ # Indicates that this transform changes the length of the
386
+ # underlying data. e.g. performs compression or error correction
387
+ def transform_changes_stream_length!
388
+ prepend(UnSeekableIO)
389
+ end
390
+ end
391
+
392
+ def initialize
393
+ @chain_io = nil
394
+ end
395
+
396
+ # Initialises this transform.
397
+ #
398
+ # Called before any IO operations.
399
+ def before_transform; end
400
+
401
+ # Flushes the input stream.
402
+ #
403
+ # Called after the final read operation.
404
+ def after_read_transform; end
405
+
406
+ # Flushes the output stream.
407
+ #
408
+ # Called after the final write operation.
409
+ def after_write_transform; end
410
+
411
+ # Prepends this transform to the given +chain+.
412
+ #
413
+ # Returns self (the new head of chain).
414
+ def prepend_to_chain(chain)
415
+ @chain_io = chain
416
+ before_transform
417
+ self
418
+ end
419
+
420
+ # Is the IO seekable?
421
+ def seekable?
422
+ @chain_io.seekable?
423
+ end
424
+
425
+ # How many bytes are available for reading?
426
+ def num_bytes_remaining
427
+ chain_num_bytes_remaining
428
+ end
429
+
430
+ # The current offset within the stream.
431
+ def offset
432
+ chain_offset
433
+ end
434
+
435
+ # Skips forward +n+ bytes in the input stream.
436
+ def skip(n)
437
+ chain_skip(n)
438
+ end
439
+
440
+ # Seeks to the given absolute position.
441
+ def seek_abs(n)
442
+ chain_seek_abs(n)
443
+ end
444
+
445
+ # Reads +n+ bytes from the stream.
446
+ def read(n)
447
+ chain_read(n)
448
+ end
449
+
450
+ # Writes +data+ to the stream.
451
+ def write(data)
452
+ chain_write(data)
453
+ end
454
+
455
+ #-------------
456
+ private
457
+
458
+ def create_empty_binary_string
459
+ "".force_encoding(Encoding::BINARY)
460
+ end
461
+
462
+ def chain_seekable?
463
+ @chain_io.seekable?
464
+ end
465
+
466
+ def chain_num_bytes_remaining
467
+ @chain_io.num_bytes_remaining
468
+ end
469
+
470
+ def chain_offset
471
+ @chain_io.offset
472
+ end
473
+
474
+ def chain_skip(n)
475
+ @chain_io.skip(n)
476
+ end
477
+
478
+ def chain_seek_abs(n)
479
+ @chain_io.seek_abs(n)
480
+ end
481
+
482
+ def chain_read(n)
483
+ @chain_io.read(n)
484
+ end
485
+
486
+ def chain_write(data)
487
+ @chain_io.write(data)
488
+ end
489
+ end
490
+
491
+ # A module to be prepended to +RawIO+ or +Transform+ when the data
492
+ # stream is not seekable. This is either due to underlying stream
493
+ # being unseekable or the transform changes the number of bytes.
494
+ module UnSeekableIO
495
+ def seekable?
496
+ false
497
+ end
498
+
499
+ def num_bytes_remaining
500
+ raise IOError, "stream is unseekable"
501
+ end
502
+
503
+ def skip(n)
504
+ raise IOError, "can not skip backwards" if n.negative?
505
+
506
+ # skip over data in 8k blocks
507
+ while n > 0
508
+ bytes_to_read = [n, 8192].min
509
+ read(bytes_to_read)
510
+ n -= bytes_to_read
511
+ end
512
+ end
513
+
514
+ def seek_abs(n)
515
+ skip(n - offset)
516
+ end
517
+ end
495
518
  end
496
519
  end
data/lib/bindata/name.rb CHANGED
@@ -13,7 +13,7 @@ module BinData
13
13
  # </pre></code>
14
14
  module RegisterNamePlugin
15
15
 
16
- def self.included(base) #:nodoc:
16
+ def self.included(base) # :nodoc:
17
17
  # The registered name may be provided explicitly.
18
18
  base.optional_parameter :name
19
19
  end
@@ -27,7 +27,7 @@ module BinData
27
27
  alias optional_parameter optional_parameters
28
28
  alias default_parameter default_parameters
29
29
 
30
- def accepted_parameters #:nodoc:
30
+ def accepted_parameters # :nodoc:
31
31
  @accepted_parameters ||= begin
32
32
  ancestor_params = superclass.respond_to?(:accepted_parameters) ?
33
33
  superclass.accepted_parameters : nil
@@ -114,13 +114,15 @@ module BinData
114
114
  end
115
115
  end
116
116
 
117
- def self.invalid_parameter_names
118
- @invalid_names ||= begin
119
- all_names = LazyEvaluator.instance_methods(true)
120
- allowed_names = [:name, :type]
121
- invalid_names = (all_names - allowed_names).uniq
117
+ class << self
118
+ def invalid_parameter_names
119
+ @invalid_parameter_names ||= begin
120
+ all_names = LazyEvaluator.instance_methods(true)
121
+ allowed_names = [:name, :type]
122
+ invalid_names = (all_names - allowed_names).uniq
122
123
 
123
- Hash[*invalid_names.collect { |key| [key.to_sym, true] }.flatten]
124
+ Hash[*invalid_names.collect { |key| [key.to_sym, true] }.flatten]
125
+ end
124
126
  end
125
127
  end
126
128
  end
@@ -73,11 +73,11 @@ module BinData
73
73
  @struct = BinData::Struct.new(get_parameter(:struct_params), self)
74
74
  end
75
75
 
76
- def respond_to?(symbol, include_private = false) #:nodoc:
76
+ def respond_to?(symbol, include_private = false) # :nodoc:
77
77
  @struct.respond_to?(symbol, include_private) || super
78
78
  end
79
79
 
80
- def method_missing(symbol, *args, &block) #:nodoc:
80
+ def method_missing(symbol, *args, &block) # :nodoc:
81
81
  if @struct.respond_to?(symbol)
82
82
  @struct.__send__(symbol, *args, &block)
83
83
  else
@@ -91,7 +91,7 @@ module BinData
91
91
  @value = get
92
92
  end
93
93
 
94
- def debug_name_of(child) #:nodoc:
94
+ def debug_name_of(child) # :nodoc:
95
95
  debug_name + "-internal-"
96
96
  end
97
97