kaitai-struct 0.9 → 0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,621 +1,659 @@
1
- require 'stringio'
2
-
3
- module Kaitai
4
- module Struct
5
-
6
- VERSION = '0.9'
7
-
8
- ##
9
- # Common base class for all structured generated by Kaitai Struct.
10
- # Stores stream object that this object was parsed from in {#_io},
11
- # stores reference to parent structure in {#_parent} and root
12
- # structure in {#_root} and provides a few helper methods.
13
- class Struct
14
-
15
- def initialize(_io, _parent = nil, _root = self)
16
- @_io = _io
17
- @_parent = _parent
18
- @_root = _root
19
- end
20
-
21
- ##
22
- # Factory method to instantiate a Kaitai Struct-powered structure,
23
- # parsing it from a local file with a given filename.
24
- # @param filename [String] local file to parse
25
- def self.from_file(filename)
26
- self.new(Stream.open(filename))
27
- end
28
-
29
- ##
30
- # Implementation of {Object#inspect} to aid debugging (at the very
31
- # least, to aid exception raising) for KS-based classes. This one
32
- # uses a bit terser syntax than Ruby's default one, purposely skips
33
- # any internal fields (i.e. starting with `_`, such as `_io`,
34
- # `_parent` and `_root`) to reduce confusion, and does no
35
- # recursivity tracking (as proper general-purpose `inspect`
36
- # implementation should do) because there are no endless recursion
37
- # in KS-based classes by design (except for already mentioned
38
- # internal navigation variables).
39
- def inspect
40
- vars = []
41
- instance_variables.each { |nsym|
42
- nstr = nsym.to_s
43
-
44
- # skip all internal variables
45
- next if nstr[0..1] == '@_'
46
-
47
- # strip mandatory `@` at the beginning of the name for brevity
48
- nstr = nstr[1..-1]
49
-
50
- nvalue = instance_variable_get(nsym).inspect
51
-
52
- vars << "#{nstr}=#{nvalue}"
53
- }
54
-
55
- "#{self.class}(#{vars.join(' ')})"
56
- end
57
-
58
- attr_reader :_io, :_parent, :_root
59
- end
60
-
61
- ##
62
- # Kaitai::Struct::Stream is an implementation of
63
- # {https://github.com/kaitai-io/kaitai_struct/wiki/Kaitai-Struct-stream-API
64
- # Kaitai Struct stream API} for Ruby. It's implemented as a wrapper
65
- # for generic IO objects.
66
- #
67
- # It provides a wide variety of simple methods to read (parse) binary
68
- # representations of primitive types, such as integer and floating
69
- # point numbers, byte arrays and strings, and also provides stream
70
- # positioning / navigation methods with unified cross-language and
71
- # cross-toolkit semantics.
72
- #
73
- # Typically, end users won't access Kaitai Stream class manually, but
74
- # would describe a binary structure format using .ksy language and
75
- # then would use Kaitai Struct compiler to generate source code in
76
- # desired target language. That code, in turn, would use this class
77
- # and API to do the actual parsing job.
78
- class Stream
79
- ##
80
- # Unused since Kaitai Struct Compiler v0.9+ - compatibility with
81
- # older versions.
82
- #
83
- # Exception class for an error that occurs when some fixed content
84
- # was expected to appear, but actual data read was different.
85
- class UnexpectedDataError < Exception
86
- def initialize(actual, expected)
87
- super("Unexpected fixed contents: got #{Stream.format_hex(actual)}, was waiting for #{Stream.format_hex(expected)}")
88
- @actual = actual
89
- @expected = expected
90
- end
91
- end
92
-
93
-
94
- ##
95
- # Constructs new Kaitai Stream object.
96
- # @param arg [String, IO] if String, it will be used as byte array to read data from;
97
- # if IO, if will be used literally as source of data
98
- def initialize(arg)
99
- if arg.is_a?(String)
100
- @_io = StringIO.new(arg)
101
- elsif arg.is_a?(IO)
102
- @_io = arg
103
- else
104
- raise TypeError.new('can be initialized with IO or String only')
105
- end
106
- align_to_byte
107
- end
108
-
109
- ##
110
- # Convenience method to create a Kaitai Stream object, opening a
111
- # local file with a given filename.
112
- # @param filename [String] local file to open
113
- def self.open(filename)
114
- self.new(File.open(filename, 'rb:ASCII-8BIT'))
115
- end
116
-
117
- ##
118
- # Closes underlying IO object.
119
- def close
120
- @_io.close
121
- end
122
-
123
- # Test endianness of the platform
124
- @@big_endian = [0x0102].pack('s') == [0x0102].pack('n')
125
-
126
- # @!group Stream positioning
127
-
128
- ##
129
- # Check if stream pointer is at the end of stream.
130
- # @return [true, false] true if we are located at the end of the stream
131
- def eof?; @_io.eof? and @bits_left == 0; end
132
-
133
- ##
134
- # Set stream pointer to designated position.
135
- # @param x [Fixnum] new position (offset in bytes from the beginning of the stream)
136
- def seek(x); @_io.seek(x); end
137
-
138
- ##
139
- # Get current position of a stream pointer.
140
- # @return [Fixnum] pointer position, number of bytes from the beginning of the stream
141
- def pos; @_io.pos; end
142
-
143
- ##
144
- # Get total size of the stream in bytes.
145
- # @return [Fixnum] size of the stream in bytes
146
- def size; @_io.size; end
147
-
148
- # @!endgroup
149
-
150
- # @!group Integer numbers
151
-
152
- # ------------------------------------------------------------------------
153
- # Signed
154
- # ------------------------------------------------------------------------
155
-
156
- def read_s1
157
- read_bytes(1).unpack('c')[0]
158
- end
159
-
160
- # ........................................................................
161
- # Big-endian
162
- # ........................................................................
163
-
164
- def read_s2be
165
- to_signed(read_u2be, SIGN_MASK_16)
166
- end
167
-
168
- def read_s4be
169
- to_signed(read_u4be, SIGN_MASK_32)
170
- end
171
-
172
- if @@big_endian
173
- def read_s8be
174
- read_bytes(8).unpack('q')[0]
175
- end
176
- else
177
- def read_s8be
178
- to_signed(read_u8be, SIGN_MASK_64)
179
- end
180
- end
181
-
182
- # ........................................................................
183
- # Little-endian
184
- # ........................................................................
185
-
186
- def read_s2le
187
- to_signed(read_u2le, SIGN_MASK_16)
188
- end
189
-
190
- def read_s4le
191
- to_signed(read_u4le, SIGN_MASK_32)
192
- end
193
-
194
- unless @@big_endian
195
- def read_s8le
196
- read_bytes(8).unpack('q')[0]
197
- end
198
- else
199
- def read_s8le
200
- to_signed(read_u8le, SIGN_MASK_64)
201
- end
202
- end
203
-
204
- # ------------------------------------------------------------------------
205
- # Unsigned
206
- # ------------------------------------------------------------------------
207
-
208
- def read_u1
209
- read_bytes(1).unpack('C')[0]
210
- end
211
-
212
- # ........................................................................
213
- # Big-endian
214
- # ........................................................................
215
-
216
- def read_u2be
217
- read_bytes(2).unpack('n')[0]
218
- end
219
-
220
- def read_u4be
221
- read_bytes(4).unpack('N')[0]
222
- end
223
-
224
- if @@big_endian
225
- def read_u8be
226
- read_bytes(8).unpack('Q')[0]
227
- end
228
- else
229
- def read_u8be
230
- a, b = read_bytes(8).unpack('NN')
231
- (a << 32) + b
232
- end
233
- end
234
-
235
- # ........................................................................
236
- # Little-endian
237
- # ........................................................................
238
-
239
- def read_u2le
240
- read_bytes(2).unpack('v')[0]
241
- end
242
-
243
- def read_u4le
244
- read_bytes(4).unpack('V')[0]
245
- end
246
-
247
- unless @@big_endian
248
- def read_u8le
249
- read_bytes(8).unpack('Q')[0]
250
- end
251
- else
252
- def read_u8le
253
- a, b = read_bytes(8).unpack('VV')
254
- (b << 32) + a
255
- end
256
- end
257
-
258
- # @!endgroup
259
-
260
- # @!group Floating point numbers
261
-
262
- # ------------------------------------------------------------------------
263
- # Big-endian
264
- # ------------------------------------------------------------------------
265
-
266
- def read_f4be
267
- read_bytes(4).unpack('g')[0]
268
- end
269
-
270
- def read_f8be
271
- read_bytes(8).unpack('G')[0]
272
- end
273
-
274
- # ------------------------------------------------------------------------
275
- # Little-endian
276
- # ------------------------------------------------------------------------
277
-
278
- def read_f4le
279
- read_bytes(4).unpack('e')[0]
280
- end
281
-
282
- def read_f8le
283
- read_bytes(8).unpack('E')[0]
284
- end
285
-
286
- # @!endgroup
287
-
288
- # @!group Unaligned bit values
289
-
290
- def align_to_byte
291
- @bits_left = 0
292
- @bits = 0
293
- end
294
-
295
- def read_bits_int_be(n)
296
- bits_needed = n - @bits_left
297
- if bits_needed > 0
298
- # 1 bit => 1 byte
299
- # 8 bits => 1 byte
300
- # 9 bits => 2 bytes
301
- bytes_needed = ((bits_needed - 1) / 8) + 1
302
- buf = read_bytes(bytes_needed)
303
- buf.each_byte { |byte|
304
- @bits <<= 8
305
- @bits |= byte
306
- @bits_left += 8
307
- }
308
- end
309
-
310
- # raw mask with required number of 1s, starting from lowest bit
311
- mask = (1 << n) - 1
312
- # shift @bits to align the highest bits with the mask & derive reading result
313
- shift_bits = @bits_left - n
314
- res = (@bits >> shift_bits) & mask
315
- # clear top bits that we've just read => AND with 1s
316
- @bits_left -= n
317
- mask = (1 << @bits_left) - 1
318
- @bits &= mask
319
-
320
- res
321
- end
322
-
323
- # Unused since Kaitai Struct Compiler v0.9+ - compatibility with
324
- # older versions.
325
- def read_bits_int(n)
326
- read_bits_int_be(n)
327
- end
328
-
329
- def read_bits_int_le(n)
330
- bits_needed = n - @bits_left
331
- if bits_needed > 0
332
- # 1 bit => 1 byte
333
- # 8 bits => 1 byte
334
- # 9 bits => 2 bytes
335
- bytes_needed = ((bits_needed - 1) / 8) + 1
336
- buf = read_bytes(bytes_needed)
337
- buf.each_byte { |byte|
338
- @bits |= (byte << @bits_left)
339
- @bits_left += 8
340
- }
341
- end
342
-
343
- # raw mask with required number of 1s, starting from lowest bit
344
- mask = (1 << n) - 1
345
- # derive reading result
346
- res = @bits & mask
347
- # remove bottom bits that we've just read by shifting
348
- @bits >>= n
349
- @bits_left -= n
350
-
351
- res
352
- end
353
-
354
- # @!endgroup
355
-
356
- # @!group Byte arrays
357
-
358
- ##
359
- # Reads designated number of bytes from the stream.
360
- # @param n [Fixnum] number of bytes to read
361
- # @return [String] read bytes as byte array
362
- # @raise [EOFError] if there were less bytes than requested
363
- # available in the stream
364
- def read_bytes(n)
365
- r = @_io.read(n)
366
- if r
367
- rl = r.bytesize
368
- else
369
- rl = 0
370
- end
371
- raise EOFError.new("attempted to read #{n} bytes, got only #{rl}") if rl < n
372
- r
373
- end
374
-
375
- ##
376
- # Reads all the remaining bytes in a stream as byte array.
377
- # @return [String] all remaining bytes in a stream as byte array
378
- def read_bytes_full
379
- @_io.read
380
- end
381
-
382
- def read_bytes_term(term, include_term, consume_term, eos_error)
383
- r = ''
384
- loop {
385
- if @_io.eof?
386
- if eos_error
387
- raise EOFError.new("end of stream reached, but no terminator #{term} found")
388
- else
389
- return r
390
- end
391
- end
392
- c = @_io.getc
393
- if c.ord == term
394
- r << c if include_term
395
- @_io.seek(@_io.pos - 1) unless consume_term
396
- return r
397
- end
398
- r << c
399
- }
400
- end
401
-
402
- ##
403
- # Unused since Kaitai Struct Compiler v0.9+ - compatibility with
404
- # older versions.
405
- #
406
- # Reads next len bytes from the stream and ensures that they match
407
- # expected fixed byte array. If they differ, throws a
408
- # {UnexpectedDataError} runtime exception.
409
- # @param expected [String] contents to be expected
410
- # @return [String] read bytes as byte array, which are guaranteed to
411
- # equal to expected
412
- # @raise [UnexpectedDataError]
413
- def ensure_fixed_contents(expected)
414
- len = expected.bytesize
415
- actual = @_io.read(len)
416
- raise UnexpectedDataError.new(actual, expected) if actual != expected
417
- actual
418
- end
419
-
420
- def self.bytes_strip_right(bytes, pad_byte)
421
- new_len = bytes.length
422
- while new_len > 0 and bytes.getbyte(new_len - 1) == pad_byte
423
- new_len -= 1
424
- end
425
-
426
- bytes[0, new_len]
427
- end
428
-
429
- def self.bytes_terminate(bytes, term, include_term)
430
- new_len = 0
431
- max_len = bytes.length
432
- while bytes.getbyte(new_len) != term and new_len < max_len
433
- new_len += 1
434
- end
435
- new_len += 1 if include_term and new_len < max_len
436
- bytes[0, new_len]
437
- end
438
-
439
- # @!endgroup
440
-
441
- # @!group Byte array processing
442
-
443
- ##
444
- # Performs a XOR processing with given data, XORing every byte of
445
- # input with a single given value. Uses pure Ruby implementation suggested
446
- # by [Thomas Leitner](https://github.com/gettalong), borrowed from
447
- # https://github.com/fny/xorcist/blob/master/bin/benchmark
448
- # @param data [String] data to process
449
- # @param key [Fixnum] value to XOR with
450
- # @return [String] processed data
451
- def self.process_xor_one(data, key)
452
- out = data.dup
453
- i = 0
454
- max = data.length
455
- while i < max
456
- out.setbyte(i, data.getbyte(i) ^ key)
457
- i += 1
458
- end
459
- out
460
- end
461
-
462
- ##
463
- # Performs a XOR processing with given data, XORing every byte of
464
- # input with a key array, repeating key array many times, if
465
- # necessary (i.e. if data array is longer than key array).
466
- # Uses pure Ruby implementation suggested by
467
- # [Thomas Leitner](https://github.com/gettalong), borrowed from
468
- # https://github.com/fny/xorcist/blob/master/bin/benchmark
469
- # @param data [String] data to process
470
- # @param key [String] array of bytes to XOR with
471
- # @return [String] processed data
472
- def self.process_xor_many(data, key)
473
- out = data.dup
474
- kl = key.length
475
- ki = 0
476
- i = 0
477
- max = data.length
478
- while i < max
479
- out.setbyte(i, data.getbyte(i) ^ key.getbyte(ki))
480
- ki += 1
481
- ki = 0 if ki >= kl
482
- i += 1
483
- end
484
- out
485
- end
486
-
487
- ##
488
- # Performs a circular left rotation shift for a given buffer by a
489
- # given amount of bits, using groups of groupSize bytes each
490
- # time. Right circular rotation should be performed using this
491
- # procedure with corrected amount.
492
- # @param data [String] source data to process
493
- # @param amount [Fixnum] number of bits to shift by
494
- # @param group_size [Fixnum] number of bytes per group to shift
495
- # @return [String] copy of source array with requested shift applied
496
- def self.process_rotate_left(data, amount, group_size)
497
- raise NotImplementedError.new("unable to rotate group #{group_size} bytes yet") unless group_size == 1
498
-
499
- mask = group_size * 8 - 1
500
- anti_amount = -amount & mask
501
-
502
- # NB: actually, left bit shift (<<) in Ruby would have required
503
- # truncation to type_bits size (i.e. something like "& 0xff" for
504
- # group_size == 8), but we can skip this one, because later these
505
- # number would be packed with Array#pack, which will do truncation
506
- # anyway
507
-
508
- data.bytes.map { |x| (x << amount) | (x >> anti_amount) }.pack('C*')
509
- end
510
-
511
- # @!endgroup
512
-
513
- ##
514
- # Resolves value using enum: if the value is not found in the map,
515
- # we'll just use literal value per se.
516
- def self.resolve_enum(enum_map, value)
517
- enum_map[value] || value
518
- end
519
-
520
- # ========================================================================
521
-
522
- private
523
- SIGN_MASK_16 = (1 << (16 - 1))
524
- SIGN_MASK_32 = (1 << (32 - 1))
525
- SIGN_MASK_64 = (1 << (64 - 1))
526
-
527
- def to_signed(x, mask)
528
- (x & ~mask) - (x & mask)
529
- end
530
-
531
- def self.format_hex(arr)
532
- arr.unpack('H*')[0].gsub(/(..)/, '\1 ').chop
533
- end
534
- end
535
-
536
- ##
537
- # Common ancestor for all error originating from Kaitai Struct usage.
538
- # Stores KSY source path, pointing to an element supposedly guilty of
539
- # an error.
540
- class KaitaiStructError < Exception
541
- def initialize(msg, src_path)
542
- super("#{src_path}: #{msg}")
543
- @src_path = src_path
544
- end
545
- end
546
-
547
- ##
548
- # Error that occurs when default endianness should be decided with
549
- # a switch, but nothing matches (although using endianness expression
550
- # implies that there should be some positive result).
551
- class UndecidedEndiannessError < KaitaiStructError
552
- def initialize(src_path)
553
- super("unable to decide on endianness for a type", src_path)
554
- end
555
- end
556
-
557
- ##
558
- # Common ancestor for all validation failures. Stores pointer to
559
- # KaitaiStream IO object which was involved in an error.
560
- class ValidationFailedError < KaitaiStructError
561
- def initialize(msg, io, src_path)
562
- super("at pos #{io.pos}: validation failed: #{msg}", src_path)
563
- @io = io
564
- end
565
- end
566
-
567
- ##
568
- # Signals validation failure: we required "actual" value to be equal to
569
- # "expected", but it turned out that it's not.
570
- class ValidationNotEqualError < ValidationFailedError
571
- def initialize(expected, actual, io, src_path)
572
- super("not equal, expected #{expected.inspect}, but got #{actual.inspect}", io, src_path)
573
- @expected = expected
574
- @actual = actual
575
- end
576
- end
577
-
578
- ##
579
- # Signals validation failure: we required "actual" value to be greater
580
- # than or equal to "min", but it turned out that it's not.
581
- class ValidationLessThanError < ValidationFailedError
582
- def initialize(min, actual, io, src_path)
583
- super("not in range, min #{min.inspect}, but got #{actual.inspect}", io, src_path)
584
- @min = min
585
- @actual = actual
586
- end
587
- end
588
-
589
- ##
590
- # Signals validation failure: we required "actual" value to be less
591
- # than or equal to "max", but it turned out that it's not.
592
- class ValidationGreaterThanError < ValidationFailedError
593
- def initialize(max, actual, io, src_path)
594
- super("not in range, max #{max.inspect}, but got #{actual.inspect}", io, src_path)
595
- @max = max
596
- @actual = actual
597
- end
598
- end
599
-
600
- ##
601
- # Signals validation failure: we required "actual" value to be any of
602
- # the given list, but it turned out that it's not.
603
- class ValidationNotAnyOfError < ValidationFailedError
604
- def initialize(actual, io, src_path)
605
- super("not any of the list, got #{actual.inspect}", io, src_path)
606
- @actual = actual
607
- end
608
- end
609
-
610
- ##
611
- # Signals validation failure: we required "actual" value to match
612
- # the expression, but it turned out that it doesn't.
613
- class ValidationExprError < ValidationFailedError
614
- def initialize(actual, io, src_path)
615
- super("not matching the expression, got #{actual.inspect}", io, src_path)
616
- @actual = actual
617
- end
618
- end
619
-
620
- end
621
- end
1
+ require 'stringio'
2
+
3
+ module Kaitai
4
+ module Struct
5
+
6
+ VERSION = '0.10'
7
+
8
+ ##
9
+ # Common base class for all structured generated by Kaitai Struct.
10
+ # Stores stream object that this object was parsed from in {#_io},
11
+ # stores reference to parent structure in {#_parent} and root
12
+ # structure in {#_root} and provides a few helper methods.
13
+ class Struct
14
+
15
+ def initialize(_io, _parent = nil, _root = self)
16
+ @_io = _io
17
+ @_parent = _parent
18
+ @_root = _root
19
+ end
20
+
21
+ ##
22
+ # Factory method to instantiate a Kaitai Struct-powered structure,
23
+ # parsing it from a local file with a given filename.
24
+ # @param filename [String] local file to parse
25
+ def self.from_file(filename)
26
+ self.new(Stream.open(filename))
27
+ end
28
+
29
+ ##
30
+ # Implementation of {Object#inspect} to aid debugging (at the very
31
+ # least, to aid exception raising) for KS-based classes. This one
32
+ # uses a bit terser syntax than Ruby's default one, purposely skips
33
+ # any internal fields (i.e. starting with `_`, such as `_io`,
34
+ # `_parent` and `_root`) to reduce confusion, and does no
35
+ # recursivity tracking (as proper general-purpose `inspect`
36
+ # implementation should do) because there are no endless recursion
37
+ # in KS-based classes by design (except for already mentioned
38
+ # internal navigation variables).
39
+ def inspect
40
+ vars = []
41
+ instance_variables.each { |nsym|
42
+ nstr = nsym.to_s
43
+
44
+ # skip all internal variables
45
+ next if nstr[0..1] == '@_'
46
+
47
+ # strip mandatory `@` at the beginning of the name for brevity
48
+ nstr = nstr[1..-1]
49
+
50
+ nvalue = instance_variable_get(nsym).inspect
51
+
52
+ vars << "#{nstr}=#{nvalue}"
53
+ }
54
+
55
+ "#{self.class}(#{vars.join(' ')})"
56
+ end
57
+
58
+ attr_reader :_io, :_parent, :_root
59
+ end
60
+
61
+ ##
62
+ # Kaitai::Struct::Stream is an implementation of
63
+ # {Kaitai Stream API}[https://doc.kaitai.io/stream_api.html] for Ruby.
64
+ # It's implemented as a wrapper for generic IO objects.
65
+ #
66
+ # It provides a wide variety of simple methods to read (parse) binary
67
+ # representations of primitive types, such as integer and floating
68
+ # point numbers, byte arrays and strings, and also provides stream
69
+ # positioning / navigation methods with unified cross-language and
70
+ # cross-toolkit semantics.
71
+ #
72
+ # Typically, end users won't access Kaitai Stream class manually, but
73
+ # would describe a binary structure format using .ksy language and
74
+ # then would use Kaitai Struct compiler to generate source code in
75
+ # desired target language. That code, in turn, would use this class
76
+ # and API to do the actual parsing job.
77
+ class Stream
78
+ ##
79
+ # Unused since Kaitai Struct Compiler v0.9+ - compatibility with
80
+ # older versions.
81
+ #
82
+ # Exception class for an error that occurs when some fixed content
83
+ # was expected to appear, but actual data read was different.
84
+ class UnexpectedDataError < Exception
85
+ def initialize(actual, expected)
86
+ super("Unexpected fixed contents: got #{Stream.format_hex(actual)}, was waiting for #{Stream.format_hex(expected)}")
87
+ @actual = actual
88
+ @expected = expected
89
+ end
90
+ end
91
+
92
+
93
+ ##
94
+ # Constructs new Kaitai Stream object.
95
+ # @param arg [String, IO] if String, it will be used as byte array to read data from;
96
+ # if IO, if will be used literally as source of data
97
+ def initialize(arg)
98
+ if arg.is_a?(String)
99
+ @_io = StringIO.new(arg)
100
+ elsif arg.is_a?(IO)
101
+ @_io = arg
102
+ else
103
+ raise TypeError.new('can be initialized with IO or String only')
104
+ end
105
+ align_to_byte
106
+ end
107
+
108
+ ##
109
+ # Convenience method to create a Kaitai Stream object, opening a
110
+ # local file with a given filename.
111
+ # @param filename [String] local file to open
112
+ def self.open(filename)
113
+ self.new(File.open(filename, 'rb:ASCII-8BIT'))
114
+ end
115
+
116
+ ##
117
+ # Closes underlying IO object.
118
+ def close
119
+ @_io.close
120
+ end
121
+
122
+ # Test endianness of the platform
123
+ @@big_endian = [0x0102].pack('s') == [0x0102].pack('n')
124
+
125
+ # @!group Stream positioning
126
+
127
+ ##
128
+ # Check if stream pointer is at the end of stream.
129
+ # @return [true, false] true if we are located at the end of the stream
130
+ def eof?; @_io.eof? and @bits_left == 0; end
131
+
132
+ ##
133
+ # Set stream pointer to designated position.
134
+ # @param x [Fixnum] new position (offset in bytes from the beginning of the stream)
135
+ def seek(x); @_io.seek(x); end
136
+
137
+ ##
138
+ # Get current position of a stream pointer.
139
+ # @return [Fixnum] pointer position, number of bytes from the beginning of the stream
140
+ def pos; @_io.pos; end
141
+
142
+ ##
143
+ # Get total size of the stream in bytes.
144
+ # @return [Fixnum] size of the stream in bytes
145
+ def size; @_io.size; end
146
+
147
+ # @!endgroup
148
+
149
+ # @!group Integer numbers
150
+
151
+ # ------------------------------------------------------------------------
152
+ # Signed
153
+ # ------------------------------------------------------------------------
154
+
155
+ def read_s1
156
+ read_bytes(1).unpack('c')[0]
157
+ end
158
+
159
+ # ........................................................................
160
+ # Big-endian
161
+ # ........................................................................
162
+
163
+ def read_s2be
164
+ to_signed(read_u2be, SIGN_MASK_16)
165
+ end
166
+
167
+ def read_s4be
168
+ to_signed(read_u4be, SIGN_MASK_32)
169
+ end
170
+
171
+ if @@big_endian
172
+ def read_s8be
173
+ read_bytes(8).unpack('q')[0]
174
+ end
175
+ else
176
+ def read_s8be
177
+ to_signed(read_u8be, SIGN_MASK_64)
178
+ end
179
+ end
180
+
181
+ # ........................................................................
182
+ # Little-endian
183
+ # ........................................................................
184
+
185
+ def read_s2le
186
+ to_signed(read_u2le, SIGN_MASK_16)
187
+ end
188
+
189
+ def read_s4le
190
+ to_signed(read_u4le, SIGN_MASK_32)
191
+ end
192
+
193
+ unless @@big_endian
194
+ def read_s8le
195
+ read_bytes(8).unpack('q')[0]
196
+ end
197
+ else
198
+ def read_s8le
199
+ to_signed(read_u8le, SIGN_MASK_64)
200
+ end
201
+ end
202
+
203
+ # ------------------------------------------------------------------------
204
+ # Unsigned
205
+ # ------------------------------------------------------------------------
206
+
207
+ def read_u1
208
+ read_bytes(1).unpack('C')[0]
209
+ end
210
+
211
+ # ........................................................................
212
+ # Big-endian
213
+ # ........................................................................
214
+
215
+ def read_u2be
216
+ read_bytes(2).unpack('n')[0]
217
+ end
218
+
219
+ def read_u4be
220
+ read_bytes(4).unpack('N')[0]
221
+ end
222
+
223
+ if @@big_endian
224
+ def read_u8be
225
+ read_bytes(8).unpack('Q')[0]
226
+ end
227
+ else
228
+ def read_u8be
229
+ a, b = read_bytes(8).unpack('NN')
230
+ (a << 32) + b
231
+ end
232
+ end
233
+
234
+ # ........................................................................
235
+ # Little-endian
236
+ # ........................................................................
237
+
238
+ def read_u2le
239
+ read_bytes(2).unpack('v')[0]
240
+ end
241
+
242
+ def read_u4le
243
+ read_bytes(4).unpack('V')[0]
244
+ end
245
+
246
+ unless @@big_endian
247
+ def read_u8le
248
+ read_bytes(8).unpack('Q')[0]
249
+ end
250
+ else
251
+ def read_u8le
252
+ a, b = read_bytes(8).unpack('VV')
253
+ (b << 32) + a
254
+ end
255
+ end
256
+
257
+ # @!endgroup
258
+
259
+ # @!group Floating point numbers
260
+
261
+ # ------------------------------------------------------------------------
262
+ # Big-endian
263
+ # ------------------------------------------------------------------------
264
+
265
+ def read_f4be
266
+ read_bytes(4).unpack('g')[0]
267
+ end
268
+
269
+ def read_f8be
270
+ read_bytes(8).unpack('G')[0]
271
+ end
272
+
273
+ # ------------------------------------------------------------------------
274
+ # Little-endian
275
+ # ------------------------------------------------------------------------
276
+
277
+ def read_f4le
278
+ read_bytes(4).unpack('e')[0]
279
+ end
280
+
281
+ def read_f8le
282
+ read_bytes(8).unpack('E')[0]
283
+ end
284
+
285
+ # @!endgroup
286
+
287
+ # @!group Unaligned bit values
288
+
289
+ def align_to_byte
290
+ @bits_left = 0
291
+ @bits = 0
292
+ end
293
+
294
+ def read_bits_int_be(n)
295
+ res = 0
296
+
297
+ bits_needed = n - @bits_left
298
+ @bits_left = -bits_needed % 8
299
+
300
+ if bits_needed > 0
301
+ # 1 bit => 1 byte
302
+ # 8 bits => 1 byte
303
+ # 9 bits => 2 bytes
304
+ bytes_needed = ((bits_needed - 1) / 8) + 1 # `ceil(bits_needed / 8)`
305
+ buf = read_bytes(bytes_needed)
306
+ buf.each_byte { |byte|
307
+ res = res << 8 | byte
308
+ }
309
+
310
+ new_bits = res
311
+ res = res >> @bits_left | @bits << bits_needed
312
+ @bits = new_bits # will be masked at the end of the function
313
+ else
314
+ res = @bits >> -bits_needed # shift unneeded bits out
315
+ end
316
+
317
+ mask = (1 << @bits_left) - 1 # `@bits_left` is in range 0..7
318
+ @bits &= mask
319
+
320
+ res
321
+ end
322
+
323
+ # Unused since Kaitai Struct Compiler v0.9+ - compatibility with
324
+ # older versions.
325
+ def read_bits_int(n)
326
+ read_bits_int_be(n)
327
+ end
328
+
329
+ def read_bits_int_le(n)
330
+ res = 0
331
+ bits_needed = n - @bits_left
332
+
333
+ if bits_needed > 0 then
334
+ # 1 bit => 1 byte
335
+ # 8 bits => 1 byte
336
+ # 9 bits => 2 bytes
337
+ bytes_needed = ((bits_needed - 1) / 8) + 1 # `ceil(bits_needed / 8)`
338
+ buf = read_bytes(bytes_needed)
339
+ i = 0
340
+ buf.each_byte { |byte|
341
+ res |= byte << (i * 8)
342
+ i += 1
343
+ }
344
+
345
+ new_bits = res >> bits_needed
346
+ res = res << @bits_left | @bits
347
+ @bits = new_bits
348
+ else
349
+ res = @bits
350
+ @bits >>= n
351
+ end
352
+
353
+ @bits_left = -bits_needed % 8
354
+
355
+ mask = (1 << n) - 1 # no problem with this in Ruby (arbitrary precision integers)
356
+ res &= mask
357
+ return res
358
+ end
359
+
360
+ # @!endgroup
361
+
362
+ # @!group Byte arrays
363
+
364
+ ##
365
+ # Reads designated number of bytes from the stream.
366
+ # @param n [Fixnum] number of bytes to read
367
+ # @return [String] read bytes as byte array
368
+ # @raise [EOFError] if there were less bytes than requested
369
+ # available in the stream
370
+ def read_bytes(n)
371
+ r = @_io.read(n)
372
+ if r
373
+ rl = r.bytesize
374
+ else
375
+ rl = 0
376
+ end
377
+ raise EOFError.new("attempted to read #{n} bytes, got only #{rl}") if rl < n
378
+ r
379
+ end
380
+
381
+ ##
382
+ # Reads all the remaining bytes in a stream as byte array.
383
+ # @return [String] all remaining bytes in a stream as byte array
384
+ def read_bytes_full
385
+ @_io.read
386
+ end
387
+
388
+ def read_bytes_term(term, include_term, consume_term, eos_error)
389
+ r = ''
390
+ loop {
391
+ if @_io.eof?
392
+ if eos_error
393
+ raise EOFError.new("end of stream reached, but no terminator #{term} found")
394
+ else
395
+ return r
396
+ end
397
+ end
398
+ c = @_io.getc
399
+ if c.ord == term
400
+ r << c if include_term
401
+ @_io.seek(@_io.pos - 1) unless consume_term
402
+ return r
403
+ end
404
+ r << c
405
+ }
406
+ end
407
+
408
+ ##
409
+ # Unused since Kaitai Struct Compiler v0.9+ - compatibility with
410
+ # older versions.
411
+ #
412
+ # Reads next len bytes from the stream and ensures that they match
413
+ # expected fixed byte array. If they differ, throws a
414
+ # {UnexpectedDataError} runtime exception.
415
+ # @param expected [String] contents to be expected
416
+ # @return [String] read bytes as byte array, which are guaranteed to
417
+ # equal to expected
418
+ # @raise [UnexpectedDataError]
419
+ def ensure_fixed_contents(expected)
420
+ len = expected.bytesize
421
+ actual = @_io.read(len)
422
+ raise UnexpectedDataError.new(actual, expected) if actual != expected
423
+ actual
424
+ end
425
+
426
+ def self.bytes_strip_right(bytes, pad_byte)
427
+ new_len = bytes.length
428
+ while new_len > 0 and bytes.getbyte(new_len - 1) == pad_byte
429
+ new_len -= 1
430
+ end
431
+
432
+ bytes[0, new_len]
433
+ end
434
+
435
+ def self.bytes_terminate(bytes, term, include_term)
436
+ new_len = 0
437
+ max_len = bytes.length
438
+ while bytes.getbyte(new_len) != term and new_len < max_len
439
+ new_len += 1
440
+ end
441
+ new_len += 1 if include_term and new_len < max_len
442
+ bytes[0, new_len]
443
+ end
444
+
445
+ # @!endgroup
446
+
447
+ # @!group Byte array processing
448
+
449
+ ##
450
+ # Performs a XOR processing with given data, XORing every byte of
451
+ # input with a single given value. Uses pure Ruby implementation suggested
452
+ # by [Thomas Leitner](https://github.com/gettalong), borrowed from
453
+ # https://github.com/fny/xorcist/blob/master/bin/benchmark
454
+ # @param data [String] data to process
455
+ # @param key [Fixnum] value to XOR with
456
+ # @return [String] processed data
457
+ def self.process_xor_one(data, key)
458
+ out = data.dup
459
+ i = 0
460
+ max = data.length
461
+ while i < max
462
+ out.setbyte(i, data.getbyte(i) ^ key)
463
+ i += 1
464
+ end
465
+ out
466
+ end
467
+
468
+ ##
469
+ # Performs a XOR processing with given data, XORing every byte of
470
+ # input with a key array, repeating key array many times, if
471
+ # necessary (i.e. if data array is longer than key array).
472
+ # Uses pure Ruby implementation suggested by
473
+ # [Thomas Leitner](https://github.com/gettalong), borrowed from
474
+ # https://github.com/fny/xorcist/blob/master/bin/benchmark
475
+ # @param data [String] data to process
476
+ # @param key [String] array of bytes to XOR with
477
+ # @return [String] processed data
478
+ def self.process_xor_many(data, key)
479
+ out = data.dup
480
+ kl = key.length
481
+ ki = 0
482
+ i = 0
483
+ max = data.length
484
+ while i < max
485
+ out.setbyte(i, data.getbyte(i) ^ key.getbyte(ki))
486
+ ki += 1
487
+ ki = 0 if ki >= kl
488
+ i += 1
489
+ end
490
+ out
491
+ end
492
+
493
+ ##
494
+ # Performs a circular left rotation shift for a given buffer by a
495
+ # given amount of bits, using groups of groupSize bytes each
496
+ # time. Right circular rotation should be performed using this
497
+ # procedure with corrected amount.
498
+ # @param data [String] source data to process
499
+ # @param amount [Fixnum] number of bits to shift by
500
+ # @param group_size [Fixnum] number of bytes per group to shift
501
+ # @return [String] copy of source array with requested shift applied
502
+ def self.process_rotate_left(data, amount, group_size)
503
+ raise NotImplementedError.new("unable to rotate group #{group_size} bytes yet") unless group_size == 1
504
+
505
+ mask = group_size * 8 - 1
506
+ anti_amount = -amount & mask
507
+
508
+ # NB: actually, left bit shift (<<) in Ruby would have required
509
+ # truncation to type_bits size (i.e. something like "& 0xff" for
510
+ # group_size == 8), but we can skip this one, because later these
511
+ # number would be packed with Array#pack, which will do truncation
512
+ # anyway
513
+
514
+ data.bytes.map { |x| (x << amount) | (x >> anti_amount) }.pack('C*')
515
+ end
516
+
517
+ # @!endgroup
518
+
519
+ ##
520
+ # Resolves value using enum: if the value is not found in the map,
521
+ # we'll just use literal value per se.
522
+ def self.resolve_enum(enum_map, value)
523
+ enum_map[value] || value
524
+ end
525
+
526
+ # ========================================================================
527
+
528
+ private
529
+ SIGN_MASK_16 = (1 << (16 - 1))
530
+ SIGN_MASK_32 = (1 << (32 - 1))
531
+ SIGN_MASK_64 = (1 << (64 - 1))
532
+
533
+ def to_signed(x, mask)
534
+ (x & ~mask) - (x & mask)
535
+ end
536
+
537
+ def self.format_hex(bytes)
538
+ bytes.unpack('H*')[0].gsub(/(..)/, '\1 ').chop
539
+ end
540
+
541
+ ###
542
+ # Guess if the given args are most likely byte arrays.
543
+ # <p>
544
+ # There's no way to know for sure, but {@code Encoding::ASCII_8BIT} is a special encoding that is
545
+ # usually used for a byte array(/string), not a character string. For those reasons, that encoding
546
+ # is NOT planned to be allowed for human readable texts by KS in general as well.
547
+ # </p>
548
+ # @param args [...] Something to check.
549
+ # @see <a href="https://ruby-doc.org/core-3.0.0/Encoding.html">Encoding</a>
550
+ # @see <a href="https://github.com/kaitai-io/kaitai_struct/issues/116">List of supported encodings</a>
551
+ #
552
+ def self.is_byte_array?(*args)
553
+ args.all? { |arg| arg.is_a?(String) and (arg.encoding == Encoding::ASCII_8BIT) }
554
+ end
555
+
556
+ def self.inspect_values(*args)
557
+ reprs = args.map { |arg|
558
+ if Stream.is_byte_array?(arg)
559
+ "[#{Stream.format_hex(arg)}]"
560
+ else
561
+ arg.inspect
562
+ end
563
+ }
564
+ reprs.length == 1 ? reprs[0] : reprs
565
+ end
566
+ end
567
+
568
+ ##
569
+ # Common ancestor for all error originating from Kaitai Struct usage.
570
+ # Stores KSY source path, pointing to an element supposedly guilty of
571
+ # an error.
572
+ class KaitaiStructError < Exception
573
+ def initialize(msg, src_path)
574
+ super("#{src_path}: #{msg}")
575
+ @src_path = src_path
576
+ end
577
+ end
578
+
579
+ ##
580
+ # Error that occurs when default endianness should be decided with
581
+ # a switch, but nothing matches (although using endianness expression
582
+ # implies that there should be some positive result).
583
+ class UndecidedEndiannessError < KaitaiStructError
584
+ def initialize(src_path)
585
+ super("unable to decide on endianness for a type", src_path)
586
+ end
587
+ end
588
+
589
+ ##
590
+ # Common ancestor for all validation failures. Stores pointer to
591
+ # KaitaiStream IO object which was involved in an error.
592
+ class ValidationFailedError < KaitaiStructError
593
+ def initialize(msg, io, src_path)
594
+ super("at pos #{io.pos}: validation failed: #{msg}", src_path)
595
+ @io = io
596
+ end
597
+ end
598
+
599
+ ##
600
+ # Signals validation failure: we required "actual" value to be equal to
601
+ # "expected", but it turned out that it's not.
602
+ class ValidationNotEqualError < ValidationFailedError
603
+ def initialize(expected, actual, io, src_path)
604
+ expected_repr, actual_repr = Stream.inspect_values(expected, actual)
605
+ super("not equal, expected #{expected_repr}, but got #{actual_repr}", io, src_path)
606
+
607
+ @expected = expected
608
+ @actual = actual
609
+ end
610
+ end
611
+
612
+ ##
613
+ # Signals validation failure: we required "actual" value to be greater
614
+ # than or equal to "min", but it turned out that it's not.
615
+ class ValidationLessThanError < ValidationFailedError
616
+ def initialize(min, actual, io, src_path)
617
+ min_repr, actual_repr = Stream.inspect_values(min, actual)
618
+ super("not in range, min #{min_repr}, but got #{actual_repr}", io, src_path)
619
+ @min = min
620
+ @actual = actual
621
+ end
622
+ end
623
+
624
+ ##
625
+ # Signals validation failure: we required "actual" value to be less
626
+ # than or equal to "max", but it turned out that it's not.
627
+ class ValidationGreaterThanError < ValidationFailedError
628
+ def initialize(max, actual, io, src_path)
629
+ max_repr, actual_repr = Stream.inspect_values(max, actual)
630
+ super("not in range, max #{max_repr}, but got #{actual_repr}", io, src_path)
631
+ @max = max
632
+ @actual = actual
633
+ end
634
+ end
635
+
636
+ ##
637
+ # Signals validation failure: we required "actual" value to be any of
638
+ # the given list, but it turned out that it's not.
639
+ class ValidationNotAnyOfError < ValidationFailedError
640
+ def initialize(actual, io, src_path)
641
+ actual_repr = Stream.inspect_values(actual)
642
+ super("not any of the list, got #{actual_repr}", io, src_path)
643
+ @actual = actual
644
+ end
645
+ end
646
+
647
+ ##
648
+ # Signals validation failure: we required "actual" value to match
649
+ # the expression, but it turned out that it doesn't.
650
+ class ValidationExprError < ValidationFailedError
651
+ def initialize(actual, io, src_path)
652
+ actual_repr = Stream.inspect_values(actual)
653
+ super("not matching the expression, got #{actual_repr}", io, src_path)
654
+ @actual = actual
655
+ end
656
+ end
657
+
658
+ end
659
+ end