kaitai-struct 0.9 → 0.11
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.
- checksums.yaml +4 -4
- data/LICENSE +21 -21
- data/README.md +44 -41
- data/lib/kaitai/struct/struct.rb +893 -621
- metadata +11 -11
- data/.gitignore +0 -1
- data/kaitai-struct.gemspec +0 -31
data/lib/kaitai/struct/struct.rb
CHANGED
@@ -1,621 +1,893 @@
|
|
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 =
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
#
|
119
|
-
def
|
120
|
-
|
121
|
-
end
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
end
|
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
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
mask = (1 << n) - 1
|
345
|
-
|
346
|
-
res
|
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
|
-
|
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
|
-
|
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
|
-
end
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
#
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
#
|
569
|
-
#
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
##
|
601
|
-
#
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Kaitai
|
4
|
+
module Struct
|
5
|
+
|
6
|
+
VERSION = '0.11'
|
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 = nil)
|
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
|
+
# @deprecated Unused since Kaitai Struct compiler 0.9. It
|
80
|
+
# is only available for backward compatibility and will be
|
81
|
+
# removed in the future. KSC 0.9 and later versions use
|
82
|
+
# {ValidationNotEqualError} instead.
|
83
|
+
#
|
84
|
+
# Exception class for an error that occurs when some fixed content
|
85
|
+
# was expected to appear, but actual data read was different.
|
86
|
+
class UnexpectedDataError < Exception
|
87
|
+
def initialize(actual, expected)
|
88
|
+
super("Unexpected fixed contents: got #{Internal.format_hex(actual)}, " \
|
89
|
+
"was waiting for #{Internal.format_hex(expected)}")
|
90
|
+
@actual = actual
|
91
|
+
@expected = expected
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Module#deprecate_constant was added in Ruby 2.3, see
|
96
|
+
# https://rubyreferences.github.io/rubychanges/evolution.html#modules-and-classes
|
97
|
+
deprecate_constant :UnexpectedDataError if respond_to?(:deprecate_constant)
|
98
|
+
|
99
|
+
##
|
100
|
+
# Constructs new Kaitai Stream object.
|
101
|
+
# @param arg [String, IO, StringIO, SubIO] if String, it will be used as byte
|
102
|
+
# array to read data from; if IO (or StringIO, or SubIO), if will be used literally
|
103
|
+
# as the source of data
|
104
|
+
def initialize(arg)
|
105
|
+
if arg.is_a?(String)
|
106
|
+
@_io = StringIO.new(arg)
|
107
|
+
elsif arg.is_a?(IO) or arg.is_a?(StringIO) or arg.is_a?(SubIO)
|
108
|
+
@_io = arg
|
109
|
+
else
|
110
|
+
raise TypeError.new('can be initialized with IO, StringIO, SubIO or String only')
|
111
|
+
end
|
112
|
+
align_to_byte
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Convenience method to create a Kaitai Stream object, opening a
|
117
|
+
# local file with a given filename.
|
118
|
+
# @param filename [String] local file to open
|
119
|
+
def self.open(filename)
|
120
|
+
self.new(File.open(filename, 'rb:ASCII-8BIT'))
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Closes underlying IO object.
|
125
|
+
def close
|
126
|
+
# NOTE: `unless @_io.closed?` is only needed in Ruby 2.2 and below. Ruby 2.3
|
127
|
+
# and later versions no longer raise `IOError: closed stream` when
|
128
|
+
# `StringIO#close` is called a second time, see
|
129
|
+
# https://github.com/ruby/ruby/commit/2e02f2dfd2dab936e7cd9a68d46bd910c5d184e5
|
130
|
+
@_io.close unless @_io.closed?
|
131
|
+
end
|
132
|
+
|
133
|
+
# @!group Stream positioning
|
134
|
+
|
135
|
+
##
|
136
|
+
# Check if stream pointer is at the end of stream.
|
137
|
+
# @return [true, false] true if we are located at the end of the stream
|
138
|
+
def eof?; @_io.eof? and @bits_left == 0; end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Set stream pointer to designated position.
|
142
|
+
# @param x [Fixnum] new position (offset in bytes from the beginning of the stream)
|
143
|
+
def seek(x); @_io.seek(x); end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Get current position of a stream pointer.
|
147
|
+
# @return [Fixnum] pointer position, number of bytes from the beginning of the stream
|
148
|
+
def pos; @_io.pos; end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Get total size of the stream in bytes.
|
152
|
+
# @return [Fixnum] size of the stream in bytes
|
153
|
+
def size; @_io.size; end
|
154
|
+
|
155
|
+
# @!endgroup
|
156
|
+
|
157
|
+
# @!group Integer numbers
|
158
|
+
|
159
|
+
# ------------------------------------------------------------------------
|
160
|
+
# Signed
|
161
|
+
# ------------------------------------------------------------------------
|
162
|
+
|
163
|
+
def read_s1
|
164
|
+
read_bytes(1).unpack('c')[0]
|
165
|
+
end
|
166
|
+
|
167
|
+
# ........................................................................
|
168
|
+
# Big-endian
|
169
|
+
# ........................................................................
|
170
|
+
|
171
|
+
def read_s2be
|
172
|
+
read_bytes(2).unpack('s>')[0]
|
173
|
+
end
|
174
|
+
|
175
|
+
def read_s4be
|
176
|
+
read_bytes(4).unpack('l>')[0]
|
177
|
+
end
|
178
|
+
|
179
|
+
def read_s8be
|
180
|
+
read_bytes(8).unpack('q>')[0]
|
181
|
+
end
|
182
|
+
|
183
|
+
# ........................................................................
|
184
|
+
# Little-endian
|
185
|
+
# ........................................................................
|
186
|
+
|
187
|
+
def read_s2le
|
188
|
+
read_bytes(2).unpack('s<')[0]
|
189
|
+
end
|
190
|
+
|
191
|
+
def read_s4le
|
192
|
+
read_bytes(4).unpack('l<')[0]
|
193
|
+
end
|
194
|
+
|
195
|
+
def read_s8le
|
196
|
+
read_bytes(8).unpack('q<')[0]
|
197
|
+
end
|
198
|
+
|
199
|
+
# ------------------------------------------------------------------------
|
200
|
+
# Unsigned
|
201
|
+
# ------------------------------------------------------------------------
|
202
|
+
|
203
|
+
def read_u1
|
204
|
+
read_bytes(1).unpack('C')[0]
|
205
|
+
end
|
206
|
+
|
207
|
+
# ........................................................................
|
208
|
+
# Big-endian
|
209
|
+
# ........................................................................
|
210
|
+
|
211
|
+
def read_u2be
|
212
|
+
read_bytes(2).unpack('S>')[0]
|
213
|
+
end
|
214
|
+
|
215
|
+
def read_u4be
|
216
|
+
read_bytes(4).unpack('L>')[0]
|
217
|
+
end
|
218
|
+
|
219
|
+
def read_u8be
|
220
|
+
read_bytes(8).unpack('Q>')[0]
|
221
|
+
end
|
222
|
+
|
223
|
+
# ........................................................................
|
224
|
+
# Little-endian
|
225
|
+
# ........................................................................
|
226
|
+
|
227
|
+
def read_u2le
|
228
|
+
read_bytes(2).unpack('S<')[0]
|
229
|
+
end
|
230
|
+
|
231
|
+
def read_u4le
|
232
|
+
read_bytes(4).unpack('L<')[0]
|
233
|
+
end
|
234
|
+
|
235
|
+
def read_u8le
|
236
|
+
read_bytes(8).unpack('Q<')[0]
|
237
|
+
end
|
238
|
+
|
239
|
+
# @!endgroup
|
240
|
+
|
241
|
+
# @!group Floating point numbers
|
242
|
+
|
243
|
+
# ------------------------------------------------------------------------
|
244
|
+
# Big-endian
|
245
|
+
# ------------------------------------------------------------------------
|
246
|
+
|
247
|
+
def read_f4be
|
248
|
+
read_bytes(4).unpack('g')[0]
|
249
|
+
end
|
250
|
+
|
251
|
+
def read_f8be
|
252
|
+
read_bytes(8).unpack('G')[0]
|
253
|
+
end
|
254
|
+
|
255
|
+
# ------------------------------------------------------------------------
|
256
|
+
# Little-endian
|
257
|
+
# ------------------------------------------------------------------------
|
258
|
+
|
259
|
+
def read_f4le
|
260
|
+
read_bytes(4).unpack('e')[0]
|
261
|
+
end
|
262
|
+
|
263
|
+
def read_f8le
|
264
|
+
read_bytes(8).unpack('E')[0]
|
265
|
+
end
|
266
|
+
|
267
|
+
# @!endgroup
|
268
|
+
|
269
|
+
# @!group Unaligned bit values
|
270
|
+
|
271
|
+
def align_to_byte
|
272
|
+
@bits_left = 0
|
273
|
+
@bits = 0
|
274
|
+
end
|
275
|
+
|
276
|
+
def read_bits_int_be(n)
|
277
|
+
res = 0
|
278
|
+
|
279
|
+
bits_needed = n - @bits_left
|
280
|
+
@bits_left = -bits_needed % 8
|
281
|
+
|
282
|
+
if bits_needed > 0
|
283
|
+
# 1 bit => 1 byte
|
284
|
+
# 8 bits => 1 byte
|
285
|
+
# 9 bits => 2 bytes
|
286
|
+
bytes_needed = ((bits_needed - 1) / 8) + 1 # `ceil(bits_needed / 8)`
|
287
|
+
buf = read_bytes(bytes_needed)
|
288
|
+
buf.each_byte { |byte|
|
289
|
+
res = res << 8 | byte
|
290
|
+
}
|
291
|
+
|
292
|
+
new_bits = res
|
293
|
+
res = res >> @bits_left | @bits << bits_needed
|
294
|
+
@bits = new_bits # will be masked at the end of the function
|
295
|
+
else
|
296
|
+
res = @bits >> -bits_needed # shift unneeded bits out
|
297
|
+
end
|
298
|
+
|
299
|
+
mask = (1 << @bits_left) - 1 # `@bits_left` is in range 0..7
|
300
|
+
@bits &= mask
|
301
|
+
|
302
|
+
res
|
303
|
+
end
|
304
|
+
|
305
|
+
##
|
306
|
+
# @deprecated Unused since Kaitai Struct compiler 0.9. It
|
307
|
+
# is only available for backward compatibility and will be
|
308
|
+
# removed in the future. KSC 0.9 and later versions use
|
309
|
+
# {#read_bits_int_be} instead.
|
310
|
+
def read_bits_int(n)
|
311
|
+
Internal.warn_deprecated(
|
312
|
+
'method Stream#read_bits_int is deprecated since 0.9, ' \
|
313
|
+
'use Stream#read_bits_int_be instead'
|
314
|
+
)
|
315
|
+
read_bits_int_be(n)
|
316
|
+
end
|
317
|
+
|
318
|
+
def read_bits_int_le(n)
|
319
|
+
res = 0
|
320
|
+
bits_needed = n - @bits_left
|
321
|
+
|
322
|
+
if bits_needed > 0
|
323
|
+
# 1 bit => 1 byte
|
324
|
+
# 8 bits => 1 byte
|
325
|
+
# 9 bits => 2 bytes
|
326
|
+
bytes_needed = ((bits_needed - 1) / 8) + 1 # `ceil(bits_needed / 8)`
|
327
|
+
buf = read_bytes(bytes_needed)
|
328
|
+
i = 0
|
329
|
+
buf.each_byte { |byte|
|
330
|
+
res |= byte << (i * 8)
|
331
|
+
i += 1
|
332
|
+
}
|
333
|
+
|
334
|
+
new_bits = res >> bits_needed
|
335
|
+
res = res << @bits_left | @bits
|
336
|
+
@bits = new_bits
|
337
|
+
else
|
338
|
+
res = @bits
|
339
|
+
@bits >>= n
|
340
|
+
end
|
341
|
+
|
342
|
+
@bits_left = -bits_needed % 8
|
343
|
+
|
344
|
+
mask = (1 << n) - 1 # no problem with this in Ruby (arbitrary precision integers)
|
345
|
+
res &= mask
|
346
|
+
return res
|
347
|
+
end
|
348
|
+
|
349
|
+
# @!endgroup
|
350
|
+
|
351
|
+
# @!group Byte arrays
|
352
|
+
|
353
|
+
##
|
354
|
+
# Reads designated number of bytes from the stream.
|
355
|
+
# @param n [Fixnum] number of bytes to read
|
356
|
+
# @return [String] read bytes as byte array
|
357
|
+
# @raise [EOFError] if there were less bytes than requested
|
358
|
+
# available in the stream
|
359
|
+
def read_bytes(n)
|
360
|
+
if n.nil?
|
361
|
+
# This `read(0)` call is only used to raise `IOError: not opened for reading`
|
362
|
+
# if the stream is closed. This ensures identical behavior to the `substream`
|
363
|
+
# method.
|
364
|
+
@_io.read(0)
|
365
|
+
raise TypeError.new('no implicit conversion from nil to integer')
|
366
|
+
end
|
367
|
+
|
368
|
+
r = @_io.read(n)
|
369
|
+
rl = r ? r.bytesize : 0
|
370
|
+
n = n.to_int
|
371
|
+
if rl < n
|
372
|
+
begin
|
373
|
+
@_io.seek(@_io.pos - rl)
|
374
|
+
rescue Errno::ESPIPE
|
375
|
+
# We have a non-seekable stream, so we can't go back to the
|
376
|
+
# previous position - that's fine.
|
377
|
+
end
|
378
|
+
raise EOFError.new("attempted to read #{n} bytes, got only #{rl}")
|
379
|
+
end
|
380
|
+
r
|
381
|
+
end
|
382
|
+
|
383
|
+
##
|
384
|
+
# Reads all the remaining bytes in a stream as byte array.
|
385
|
+
# @return [String] all remaining bytes in a stream as byte array
|
386
|
+
def read_bytes_full
|
387
|
+
@_io.read
|
388
|
+
end
|
389
|
+
|
390
|
+
def read_bytes_term(term, include_term, consume_term, eos_error)
|
391
|
+
term_byte = term.chr
|
392
|
+
r = String.new
|
393
|
+
loop {
|
394
|
+
c = @_io.getc
|
395
|
+
if c.nil?
|
396
|
+
if eos_error
|
397
|
+
raise EOFError.new("end of stream reached, but no terminator #{term} found")
|
398
|
+
end
|
399
|
+
|
400
|
+
return r
|
401
|
+
end
|
402
|
+
if c == term_byte
|
403
|
+
r << c if include_term
|
404
|
+
@_io.seek(@_io.pos - 1) unless consume_term
|
405
|
+
return r
|
406
|
+
end
|
407
|
+
r << c
|
408
|
+
}
|
409
|
+
end
|
410
|
+
|
411
|
+
def read_bytes_term_multi(term, include_term, consume_term, eos_error)
|
412
|
+
unit_size = term.bytesize
|
413
|
+
r = String.new
|
414
|
+
loop {
|
415
|
+
c = @_io.read(unit_size) || ''
|
416
|
+
if c.bytesize < unit_size
|
417
|
+
if eos_error
|
418
|
+
raise EOFError.new("end of stream reached, but no terminator #{term} found")
|
419
|
+
end
|
420
|
+
|
421
|
+
r << c
|
422
|
+
return r
|
423
|
+
end
|
424
|
+
if c == term
|
425
|
+
r << c if include_term
|
426
|
+
@_io.seek(@_io.pos - unit_size) unless consume_term
|
427
|
+
return r
|
428
|
+
end
|
429
|
+
r << c
|
430
|
+
}
|
431
|
+
end
|
432
|
+
|
433
|
+
##
|
434
|
+
# @deprecated Unused since Kaitai Struct compiler 0.9. It
|
435
|
+
# is only available for backward compatibility and will be
|
436
|
+
# removed in the future. KSC 0.9 and later versions raise
|
437
|
+
# {ValidationNotEqualError} instead.
|
438
|
+
#
|
439
|
+
# Reads next len bytes from the stream and ensures that they match
|
440
|
+
# expected fixed byte array. If they differ, throws a
|
441
|
+
# {UnexpectedDataError} runtime exception.
|
442
|
+
# @param expected [String] contents to be expected
|
443
|
+
# @return [String] read bytes as byte array, which are guaranteed to
|
444
|
+
# equal to expected
|
445
|
+
# @raise [UnexpectedDataError]
|
446
|
+
def ensure_fixed_contents(expected)
|
447
|
+
Internal.warn_deprecated(
|
448
|
+
'method Stream#ensure_fixed_contents is deprecated since 0.9, ' \
|
449
|
+
'explicitly raise ValidationNotEqualError from an `if` statement instead'
|
450
|
+
)
|
451
|
+
len = expected.bytesize
|
452
|
+
actual = @_io.read(len)
|
453
|
+
raise UnexpectedDataError.new(actual, expected) if actual != expected
|
454
|
+
actual
|
455
|
+
end
|
456
|
+
|
457
|
+
def self.bytes_strip_right(bytes, pad_byte)
|
458
|
+
new_len = bytes.length
|
459
|
+
while new_len > 0 and bytes.getbyte(new_len - 1) == pad_byte
|
460
|
+
new_len -= 1
|
461
|
+
end
|
462
|
+
|
463
|
+
bytes[0, new_len]
|
464
|
+
end
|
465
|
+
|
466
|
+
def self.bytes_terminate(bytes, term, include_term)
|
467
|
+
term_index = bytes.index(term.chr)
|
468
|
+
if term_index.nil?
|
469
|
+
bytes.dup
|
470
|
+
else
|
471
|
+
bytes[0, term_index + (include_term ? 1 : 0)]
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
def self.bytes_terminate_multi(bytes, term, include_term)
|
476
|
+
unit_size = term.bytesize
|
477
|
+
search_index = bytes.index(term)
|
478
|
+
loop {
|
479
|
+
if search_index.nil?
|
480
|
+
return bytes.dup
|
481
|
+
end
|
482
|
+
mod = search_index % unit_size
|
483
|
+
if mod == 0
|
484
|
+
return bytes[0, search_index + (include_term ? unit_size : 0)]
|
485
|
+
end
|
486
|
+
search_index = bytes.index(term, search_index + (unit_size - mod))
|
487
|
+
}
|
488
|
+
end
|
489
|
+
|
490
|
+
# @!endgroup
|
491
|
+
|
492
|
+
# @!group Byte array processing
|
493
|
+
|
494
|
+
##
|
495
|
+
# Performs a XOR processing with given data, XORing every byte of
|
496
|
+
# input with a single given value. Uses pure Ruby implementation suggested
|
497
|
+
# by [Thomas Leitner](https://github.com/gettalong), borrowed from
|
498
|
+
# https://github.com/fny/xorcist/blob/master/bin/benchmark
|
499
|
+
# @param data [String] data to process
|
500
|
+
# @param key [Fixnum] value to XOR with
|
501
|
+
# @return [String] processed data
|
502
|
+
def self.process_xor_one(data, key)
|
503
|
+
out = data.dup
|
504
|
+
i = 0
|
505
|
+
max = data.length
|
506
|
+
while i < max
|
507
|
+
out.setbyte(i, data.getbyte(i) ^ key)
|
508
|
+
i += 1
|
509
|
+
end
|
510
|
+
out
|
511
|
+
end
|
512
|
+
|
513
|
+
##
|
514
|
+
# Performs a XOR processing with given data, XORing every byte of
|
515
|
+
# input with a key array, repeating key array many times, if
|
516
|
+
# necessary (i.e. if data array is longer than key array).
|
517
|
+
# Uses pure Ruby implementation suggested by
|
518
|
+
# [Thomas Leitner](https://github.com/gettalong), borrowed from
|
519
|
+
# https://github.com/fny/xorcist/blob/master/bin/benchmark
|
520
|
+
# @param data [String] data to process
|
521
|
+
# @param key [String] array of bytes to XOR with
|
522
|
+
# @return [String] processed data
|
523
|
+
def self.process_xor_many(data, key)
|
524
|
+
out = data.dup
|
525
|
+
kl = key.length
|
526
|
+
ki = 0
|
527
|
+
i = 0
|
528
|
+
max = data.length
|
529
|
+
while i < max
|
530
|
+
out.setbyte(i, data.getbyte(i) ^ key.getbyte(ki))
|
531
|
+
ki += 1
|
532
|
+
ki = 0 if ki >= kl
|
533
|
+
i += 1
|
534
|
+
end
|
535
|
+
out
|
536
|
+
end
|
537
|
+
|
538
|
+
##
|
539
|
+
# Performs a circular left rotation shift for a given buffer by a
|
540
|
+
# given amount of bits, using groups of groupSize bytes each
|
541
|
+
# time. Right circular rotation should be performed using this
|
542
|
+
# procedure with corrected amount.
|
543
|
+
# @param data [String] source data to process
|
544
|
+
# @param amount [Fixnum] number of bits to shift by
|
545
|
+
# @param group_size [Fixnum] number of bytes per group to shift
|
546
|
+
# @return [String] copy of source array with requested shift applied
|
547
|
+
def self.process_rotate_left(data, amount, group_size)
|
548
|
+
raise NotImplementedError.new("unable to rotate group #{group_size} bytes yet") unless group_size == 1
|
549
|
+
|
550
|
+
mask = group_size * 8 - 1
|
551
|
+
anti_amount = -amount & mask
|
552
|
+
|
553
|
+
# NB: actually, left bit shift (<<) in Ruby would have required
|
554
|
+
# truncation to type_bits size (i.e. something like "& 0xff" for
|
555
|
+
# group_size == 8), but we can skip this one, because later these
|
556
|
+
# number would be packed with Array#pack, which will do truncation
|
557
|
+
# anyway
|
558
|
+
|
559
|
+
data.bytes.map { |x| (x << amount) | (x >> anti_amount) }.pack('C*')
|
560
|
+
end
|
561
|
+
|
562
|
+
# @!endgroup
|
563
|
+
|
564
|
+
##
|
565
|
+
# Reserves next n bytes from current stream as a
|
566
|
+
# Kaitai::Struct::Stream substream. Substream has its own pointer
|
567
|
+
# and addressing in the range of [0, n) bytes. This stream's pointer
|
568
|
+
# is advanced to the position right after this substream.
|
569
|
+
# @param n [Fixnum] number of bytes to reserve for a substream
|
570
|
+
# @return [Stream] substream covering n bytes from the current
|
571
|
+
# position
|
572
|
+
def substream(n)
|
573
|
+
raise IOError.new('not opened for reading') if @_io.closed?
|
574
|
+
|
575
|
+
n = Internal.num2long(n)
|
576
|
+
raise ArgumentError.new("negative length #{n} given") if n < 0
|
577
|
+
|
578
|
+
rl = [0, @_io.size - @_io.pos].max
|
579
|
+
raise EOFError.new("attempted to read #{n} bytes, got only #{rl}") if rl < n
|
580
|
+
|
581
|
+
sub = Stream.new(SubIO.new(@_io, @_io.pos, n))
|
582
|
+
@_io.seek(@_io.pos + n)
|
583
|
+
sub
|
584
|
+
end
|
585
|
+
|
586
|
+
##
|
587
|
+
# Resolves value using enum: if the value is not found in the map,
|
588
|
+
# we'll just use literal value per se.
|
589
|
+
def self.resolve_enum(enum_map, value)
|
590
|
+
enum_map[value] || value
|
591
|
+
end
|
592
|
+
end
|
593
|
+
|
594
|
+
##
|
595
|
+
# Substream IO implementation: a IO object which wraps existing IO object
|
596
|
+
# and provides similar byte/bytes reading functionality, but only for a
|
597
|
+
# limited set of bytes starting from specified offset and spanning up to
|
598
|
+
# specified length.
|
599
|
+
class SubIO
|
600
|
+
##
|
601
|
+
# Parent IO object that this substream is projecting data from.
|
602
|
+
attr_reader :parent_io
|
603
|
+
|
604
|
+
##
|
605
|
+
# Offset of start of substream in coordinates of parent stream. In
|
606
|
+
# coordinates of substream itself start will be always 0.
|
607
|
+
attr_reader :parent_start
|
608
|
+
|
609
|
+
##
|
610
|
+
# Size of substream in bytes.
|
611
|
+
attr_reader :size
|
612
|
+
|
613
|
+
##
|
614
|
+
# Current position in a substream. Independent from a position in a
|
615
|
+
# parent IO.
|
616
|
+
attr_reader :pos
|
617
|
+
|
618
|
+
def initialize(parent_io, parent_start, size)
|
619
|
+
@parent_io = parent_io
|
620
|
+
@parent_start = parent_start
|
621
|
+
@size = size
|
622
|
+
@pos = 0
|
623
|
+
@closed = false
|
624
|
+
end
|
625
|
+
|
626
|
+
def eof?
|
627
|
+
raise IOError.new('not opened for reading') if @closed
|
628
|
+
|
629
|
+
@pos >= @size
|
630
|
+
end
|
631
|
+
|
632
|
+
def seek(offset, whence = IO::SEEK_SET)
|
633
|
+
raise ArgumentError.new('only IO::SEEK_SET is supported by SubIO#seek') unless whence == IO::SEEK_SET
|
634
|
+
|
635
|
+
offset = Internal.num2long(offset)
|
636
|
+
raise IOError.new('closed stream') if @closed
|
637
|
+
raise Errno::EINVAL if offset < 0
|
638
|
+
@pos = offset.to_int
|
639
|
+
return 0
|
640
|
+
end
|
641
|
+
|
642
|
+
def getc
|
643
|
+
raise IOError.new('not opened for reading') if @closed
|
644
|
+
|
645
|
+
return nil if @pos >= @size
|
646
|
+
|
647
|
+
# remember position in parent IO
|
648
|
+
old_pos = @parent_io.pos
|
649
|
+
@parent_io.seek(@parent_start + @pos)
|
650
|
+
begin
|
651
|
+
res = @parent_io.getc
|
652
|
+
@pos += 1
|
653
|
+
ensure
|
654
|
+
# restore position in parent IO
|
655
|
+
@parent_io.seek(old_pos)
|
656
|
+
end
|
657
|
+
|
658
|
+
res
|
659
|
+
end
|
660
|
+
|
661
|
+
def read(len = nil)
|
662
|
+
raise IOError.new('not opened for reading') if @closed
|
663
|
+
|
664
|
+
# read until the end of substream
|
665
|
+
if len.nil?
|
666
|
+
len = @size - @pos
|
667
|
+
return String.new if len <= 0
|
668
|
+
elsif len.respond_to?(:to_int)
|
669
|
+
len = len.to_int
|
670
|
+
# special case for requesting exactly 0 bytes
|
671
|
+
return String.new if len == 0
|
672
|
+
|
673
|
+
if len > 0
|
674
|
+
# cap intent to read if going beyond substream boundary
|
675
|
+
left = @size - @pos
|
676
|
+
|
677
|
+
# if actually requested reading and we're beyond the boundary, return nil
|
678
|
+
return nil if left <= 0
|
679
|
+
|
680
|
+
# otherwise, still return something, but less than requested
|
681
|
+
len = left if len > left
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
# remember position in parent IO
|
686
|
+
old_pos = @parent_io.pos
|
687
|
+
|
688
|
+
@parent_io.seek(@parent_start + @pos)
|
689
|
+
begin
|
690
|
+
res = @parent_io.read(len)
|
691
|
+
read_len = res.bytesize
|
692
|
+
@pos += read_len
|
693
|
+
ensure
|
694
|
+
# restore position in parent IO
|
695
|
+
@parent_io.seek(old_pos)
|
696
|
+
end
|
697
|
+
|
698
|
+
res
|
699
|
+
end
|
700
|
+
|
701
|
+
def close
|
702
|
+
@closed = true
|
703
|
+
nil
|
704
|
+
end
|
705
|
+
|
706
|
+
def closed?
|
707
|
+
@closed
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
##
|
712
|
+
# Common ancestor for all error originating from Kaitai Struct usage.
|
713
|
+
# Stores KSY source path, pointing to an element supposedly guilty of
|
714
|
+
# an error.
|
715
|
+
class KaitaiStructError < StandardError
|
716
|
+
def initialize(msg, src_path)
|
717
|
+
super("#{src_path}: #{msg}")
|
718
|
+
@src_path = src_path
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
##
|
723
|
+
# Error that occurs when default endianness should be decided with
|
724
|
+
# a switch, but nothing matches (although using endianness expression
|
725
|
+
# implies that there should be some positive result).
|
726
|
+
class UndecidedEndiannessError < KaitaiStructError
|
727
|
+
def initialize(src_path)
|
728
|
+
super("unable to decide on endianness for a type", src_path)
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
##
|
733
|
+
# Common ancestor for all validation failures. Stores pointer to
|
734
|
+
# KaitaiStream IO object which was involved in an error.
|
735
|
+
class ValidationFailedError < KaitaiStructError
|
736
|
+
def initialize(msg, io, src_path)
|
737
|
+
super("at pos #{io.pos}: validation failed: #{msg}", src_path)
|
738
|
+
@io = io
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
##
|
743
|
+
# Signals validation failure: we required "actual" value to be equal to
|
744
|
+
# "expected", but it turned out that it's not.
|
745
|
+
class ValidationNotEqualError < ValidationFailedError
|
746
|
+
def initialize(expected, actual, io, src_path)
|
747
|
+
expected_repr, actual_repr = Internal.inspect_values(expected, actual)
|
748
|
+
super("not equal, expected #{expected_repr}, but got #{actual_repr}", io, src_path)
|
749
|
+
|
750
|
+
@expected = expected
|
751
|
+
@actual = actual
|
752
|
+
end
|
753
|
+
end
|
754
|
+
|
755
|
+
##
|
756
|
+
# Signals validation failure: we required "actual" value to be greater
|
757
|
+
# than or equal to "min", but it turned out that it's not.
|
758
|
+
class ValidationLessThanError < ValidationFailedError
|
759
|
+
def initialize(min, actual, io, src_path)
|
760
|
+
min_repr, actual_repr = Internal.inspect_values(min, actual)
|
761
|
+
super("not in range, min #{min_repr}, but got #{actual_repr}", io, src_path)
|
762
|
+
@min = min
|
763
|
+
@actual = actual
|
764
|
+
end
|
765
|
+
end
|
766
|
+
|
767
|
+
##
|
768
|
+
# Signals validation failure: we required "actual" value to be less
|
769
|
+
# than or equal to "max", but it turned out that it's not.
|
770
|
+
class ValidationGreaterThanError < ValidationFailedError
|
771
|
+
def initialize(max, actual, io, src_path)
|
772
|
+
max_repr, actual_repr = Internal.inspect_values(max, actual)
|
773
|
+
super("not in range, max #{max_repr}, but got #{actual_repr}", io, src_path)
|
774
|
+
@max = max
|
775
|
+
@actual = actual
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
##
|
780
|
+
# Signals validation failure: we required "actual" value to be any of
|
781
|
+
# the given list, but it turned out that it's not.
|
782
|
+
class ValidationNotAnyOfError < ValidationFailedError
|
783
|
+
def initialize(actual, io, src_path)
|
784
|
+
actual_repr = Internal.inspect_values(actual)
|
785
|
+
super("not any of the list, got #{actual_repr}", io, src_path)
|
786
|
+
@actual = actual
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
##
|
791
|
+
# Signals validation failure: we required "actual" value to be in
|
792
|
+
# the enum, but it turned out that it's not.
|
793
|
+
class ValidationNotInEnumError < ValidationFailedError
|
794
|
+
def initialize(actual, io, src_path)
|
795
|
+
actual_repr = Internal.inspect_values(actual)
|
796
|
+
super("not in the enum, got #{actual_repr}", io, src_path)
|
797
|
+
@actual = actual
|
798
|
+
end
|
799
|
+
end
|
800
|
+
|
801
|
+
##
|
802
|
+
# Signals validation failure: we required "actual" value to match
|
803
|
+
# the expression, but it turned out that it doesn't.
|
804
|
+
class ValidationExprError < ValidationFailedError
|
805
|
+
def initialize(actual, io, src_path)
|
806
|
+
actual_repr = Internal.inspect_values(actual)
|
807
|
+
super("not matching the expression, got #{actual_repr}", io, src_path)
|
808
|
+
@actual = actual
|
809
|
+
end
|
810
|
+
end
|
811
|
+
|
812
|
+
##
|
813
|
+
# \Internal implementation helpers.
|
814
|
+
module Internal
|
815
|
+
##
|
816
|
+
# This method reproduces the behavior of the +rb_num2long+ function:
|
817
|
+
# https://github.com/ruby/ruby/blob/d2930f8e7a5db8a7337fa43370940381b420cc3e/numeric.c#L3195-L3221
|
818
|
+
def self.num2long(val)
|
819
|
+
val_as_int = val.to_int
|
820
|
+
rescue NoMethodError
|
821
|
+
raise TypeError.new('no implicit conversion from nil to integer') if val.nil?
|
822
|
+
|
823
|
+
val_as_human =
|
824
|
+
case val
|
825
|
+
when true, false
|
826
|
+
val.to_s
|
827
|
+
else
|
828
|
+
val.class
|
829
|
+
end
|
830
|
+
raise TypeError.new("no implicit conversion of #{val_as_human} into Integer")
|
831
|
+
else
|
832
|
+
val_as_int
|
833
|
+
end
|
834
|
+
|
835
|
+
def self.format_hex(bytes)
|
836
|
+
bytes.unpack('H*')[0].gsub(/(..)/, '\1 ').chop
|
837
|
+
end
|
838
|
+
|
839
|
+
###
|
840
|
+
# Guess if the given args are most likely byte arrays.
|
841
|
+
# <p>
|
842
|
+
# There's no way to know for sure, but {@code Encoding::ASCII_8BIT} is a special encoding that is
|
843
|
+
# usually used for a byte array(/string), not a character string. For those reasons, that encoding
|
844
|
+
# is NOT planned to be allowed for human readable texts by KS in general as well.
|
845
|
+
# </p>
|
846
|
+
# @param args [...] Something to check.
|
847
|
+
# @see <a href="https://ruby-doc.org/core-3.0.0/Encoding.html">Encoding</a>
|
848
|
+
# @see <a href="https://github.com/kaitai-io/kaitai_struct/issues/116">List of supported encodings</a>
|
849
|
+
#
|
850
|
+
def self.is_byte_array?(*args)
|
851
|
+
args.all? { |arg| arg.is_a?(String) and (arg.encoding == Encoding::ASCII_8BIT) }
|
852
|
+
end
|
853
|
+
|
854
|
+
private_class_method :is_byte_array?
|
855
|
+
|
856
|
+
def self.inspect_values(*args)
|
857
|
+
reprs = args.map { |arg|
|
858
|
+
if is_byte_array?(arg)
|
859
|
+
"[#{format_hex(arg)}]"
|
860
|
+
else
|
861
|
+
arg.inspect
|
862
|
+
end
|
863
|
+
}
|
864
|
+
reprs.length == 1 ? reprs[0] : reprs
|
865
|
+
end
|
866
|
+
|
867
|
+
# The `uplevel` keyword argument of Kernel#warn was added in Ruby 2.5,
|
868
|
+
# see https://rubyreferences.github.io/rubychanges/2.5.html#warn-uplevel-keyword-argument
|
869
|
+
#
|
870
|
+
# The `category` keyword argument of Kernel#warn was added in Ruby 3.0,
|
871
|
+
# see https://rubyreferences.github.io/rubychanges/3.0.html#warningwarn-category-keyword-argument
|
872
|
+
#
|
873
|
+
# NOTE: `.dup` is needed in Ruby 1.9, otherwise `RuntimeError: can't modify frozen String` occurs
|
874
|
+
WARN_SUPPORTS_UPLEVEL = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.5.0')
|
875
|
+
WARN_SUPPORTS_CATEGORY = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('3.0.0')
|
876
|
+
private_constant :WARN_SUPPORTS_UPLEVEL
|
877
|
+
private_constant :WARN_SUPPORTS_CATEGORY
|
878
|
+
|
879
|
+
def self.warn_deprecated(msg)
|
880
|
+
if WARN_SUPPORTS_CATEGORY
|
881
|
+
warn(msg, uplevel: 2, category: :deprecated)
|
882
|
+
elsif WARN_SUPPORTS_UPLEVEL
|
883
|
+
warn(msg, uplevel: 2)
|
884
|
+
else
|
885
|
+
warn(msg)
|
886
|
+
end
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
private_constant :Internal
|
891
|
+
|
892
|
+
end
|
893
|
+
end
|