kaitai-struct 0.9 → 0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +21 -21
- data/README.md +44 -41
- data/lib/kaitai/struct/struct.rb +659 -621
- metadata +10 -9
- data/.gitignore +0 -1
- data/kaitai-struct.gemspec +0 -31
data/lib/kaitai/struct/struct.rb
CHANGED
@@ -1,621 +1,659 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
module Kaitai
|
4
|
-
module Struct
|
5
|
-
|
6
|
-
VERSION = '0.
|
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://
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
# would
|
75
|
-
#
|
76
|
-
#
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
#
|
96
|
-
#
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
#
|
111
|
-
# local file
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
#
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
#
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
#
|
153
|
-
#
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
#
|
161
|
-
#
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
#
|
183
|
-
#
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
#
|
205
|
-
#
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
#
|
213
|
-
#
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
a
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
#
|
236
|
-
#
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
#
|
263
|
-
#
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
#
|
275
|
-
#
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
@
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
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
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
#
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
#
|
361
|
-
|
362
|
-
#
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
r
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
end
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
#
|
410
|
-
#
|
411
|
-
#
|
412
|
-
#
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
#
|
446
|
-
|
447
|
-
#
|
448
|
-
|
449
|
-
|
450
|
-
#
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
#
|
470
|
-
#
|
471
|
-
#
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
#
|
495
|
-
#
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
#
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
#
|
549
|
-
# a
|
550
|
-
#
|
551
|
-
|
552
|
-
def
|
553
|
-
|
554
|
-
end
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
#
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
end
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
#
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
end
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
#
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
end
|
587
|
-
end
|
588
|
-
|
589
|
-
##
|
590
|
-
#
|
591
|
-
#
|
592
|
-
class
|
593
|
-
def initialize(
|
594
|
-
super("
|
595
|
-
@
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
#
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
super("not
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
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
|