kaitai-struct 0.9 → 0.10

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.
@@ -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