kaitai-struct 0.10 → 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 +1 -1
- data/README.md +2 -2
- data/lib/kaitai/struct/struct.rb +347 -113
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd4bb9d0b8c7befee55e055dd8eb8aba8e13f08074600f3ed2a49e1b32f11ebc
|
4
|
+
data.tar.gz: 24fe1b2e85d6682c71afb4fd80824634b9113453e3fb80736c12c8051c09c5d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9f88733d357dbe3962c34437bf775cfd0d9a4c3f48dbc9c88be7a08606c9d4ec8a2adfb8e61804b61bb28ff10cb5cd1178f7ef0397edf0d6d2703e1846ce2afe
|
7
|
+
data.tar.gz: 53e735516abfafc05f0218b93cabfeece0978a3d41aec1afb04c21d89dc76085cb83ecfd5b13bc0d41fe2d8dfec67a4c7acb96e283c62e0fa0b27cded490b48b
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -11,8 +11,8 @@ formats, network stream packet formats, etc.
|
|
11
11
|
|
12
12
|
Further reading:
|
13
13
|
|
14
|
-
* [About Kaitai Struct](
|
15
|
-
* [About API implemented in this library](
|
14
|
+
* [About Kaitai Struct](https://kaitai.io/)
|
15
|
+
* [About API implemented in this library](https://doc.kaitai.io/stream_api.html)
|
16
16
|
|
17
17
|
## Installing
|
18
18
|
|
data/lib/kaitai/struct/struct.rb
CHANGED
@@ -3,7 +3,7 @@ require 'stringio'
|
|
3
3
|
module Kaitai
|
4
4
|
module Struct
|
5
5
|
|
6
|
-
VERSION = '0.
|
6
|
+
VERSION = '0.11'
|
7
7
|
|
8
8
|
##
|
9
9
|
# Common base class for all structured generated by Kaitai Struct.
|
@@ -12,7 +12,7 @@ VERSION = '0.10'
|
|
12
12
|
# structure in {#_root} and provides a few helper methods.
|
13
13
|
class Struct
|
14
14
|
|
15
|
-
def initialize(_io, _parent = nil, _root =
|
15
|
+
def initialize(_io, _parent = nil, _root = nil)
|
16
16
|
@_io = _io
|
17
17
|
@_parent = _parent
|
18
18
|
@_root = _root
|
@@ -76,31 +76,38 @@ end
|
|
76
76
|
# and API to do the actual parsing job.
|
77
77
|
class Stream
|
78
78
|
##
|
79
|
-
# Unused since Kaitai Struct
|
80
|
-
#
|
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.
|
81
83
|
#
|
82
84
|
# Exception class for an error that occurs when some fixed content
|
83
85
|
# was expected to appear, but actual data read was different.
|
84
86
|
class UnexpectedDataError < Exception
|
85
87
|
def initialize(actual, expected)
|
86
|
-
super("Unexpected fixed contents: got #{
|
88
|
+
super("Unexpected fixed contents: got #{Internal.format_hex(actual)}, " \
|
89
|
+
"was waiting for #{Internal.format_hex(expected)}")
|
87
90
|
@actual = actual
|
88
91
|
@expected = expected
|
89
92
|
end
|
90
93
|
end
|
91
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)
|
92
98
|
|
93
99
|
##
|
94
100
|
# Constructs new Kaitai Stream object.
|
95
|
-
# @param arg [String, IO] if String, it will be used as byte
|
96
|
-
# if IO, if will be used literally
|
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
|
97
104
|
def initialize(arg)
|
98
105
|
if arg.is_a?(String)
|
99
106
|
@_io = StringIO.new(arg)
|
100
|
-
elsif arg.is_a?(IO)
|
107
|
+
elsif arg.is_a?(IO) or arg.is_a?(StringIO) or arg.is_a?(SubIO)
|
101
108
|
@_io = arg
|
102
109
|
else
|
103
|
-
raise TypeError.new('can be initialized with IO or String only')
|
110
|
+
raise TypeError.new('can be initialized with IO, StringIO, SubIO or String only')
|
104
111
|
end
|
105
112
|
align_to_byte
|
106
113
|
end
|
@@ -116,12 +123,13 @@ class Stream
|
|
116
123
|
##
|
117
124
|
# Closes underlying IO object.
|
118
125
|
def close
|
119
|
-
@_io.
|
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?
|
120
131
|
end
|
121
132
|
|
122
|
-
# Test endianness of the platform
|
123
|
-
@@big_endian = [0x0102].pack('s') == [0x0102].pack('n')
|
124
|
-
|
125
133
|
# @!group Stream positioning
|
126
134
|
|
127
135
|
##
|
@@ -161,21 +169,15 @@ class Stream
|
|
161
169
|
# ........................................................................
|
162
170
|
|
163
171
|
def read_s2be
|
164
|
-
|
172
|
+
read_bytes(2).unpack('s>')[0]
|
165
173
|
end
|
166
174
|
|
167
175
|
def read_s4be
|
168
|
-
|
176
|
+
read_bytes(4).unpack('l>')[0]
|
169
177
|
end
|
170
178
|
|
171
|
-
|
172
|
-
|
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
|
+
def read_s8be
|
180
|
+
read_bytes(8).unpack('q>')[0]
|
179
181
|
end
|
180
182
|
|
181
183
|
# ........................................................................
|
@@ -183,21 +185,15 @@ class Stream
|
|
183
185
|
# ........................................................................
|
184
186
|
|
185
187
|
def read_s2le
|
186
|
-
|
188
|
+
read_bytes(2).unpack('s<')[0]
|
187
189
|
end
|
188
190
|
|
189
191
|
def read_s4le
|
190
|
-
|
192
|
+
read_bytes(4).unpack('l<')[0]
|
191
193
|
end
|
192
194
|
|
193
|
-
|
194
|
-
|
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
|
195
|
+
def read_s8le
|
196
|
+
read_bytes(8).unpack('q<')[0]
|
201
197
|
end
|
202
198
|
|
203
199
|
# ------------------------------------------------------------------------
|
@@ -213,22 +209,15 @@ class Stream
|
|
213
209
|
# ........................................................................
|
214
210
|
|
215
211
|
def read_u2be
|
216
|
-
read_bytes(2).unpack('
|
212
|
+
read_bytes(2).unpack('S>')[0]
|
217
213
|
end
|
218
214
|
|
219
215
|
def read_u4be
|
220
|
-
read_bytes(4).unpack('
|
216
|
+
read_bytes(4).unpack('L>')[0]
|
221
217
|
end
|
222
218
|
|
223
|
-
|
224
|
-
|
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
|
219
|
+
def read_u8be
|
220
|
+
read_bytes(8).unpack('Q>')[0]
|
232
221
|
end
|
233
222
|
|
234
223
|
# ........................................................................
|
@@ -236,22 +225,15 @@ class Stream
|
|
236
225
|
# ........................................................................
|
237
226
|
|
238
227
|
def read_u2le
|
239
|
-
read_bytes(2).unpack('
|
228
|
+
read_bytes(2).unpack('S<')[0]
|
240
229
|
end
|
241
230
|
|
242
231
|
def read_u4le
|
243
|
-
read_bytes(4).unpack('
|
232
|
+
read_bytes(4).unpack('L<')[0]
|
244
233
|
end
|
245
234
|
|
246
|
-
|
247
|
-
|
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
|
235
|
+
def read_u8le
|
236
|
+
read_bytes(8).unpack('Q<')[0]
|
255
237
|
end
|
256
238
|
|
257
239
|
# @!endgroup
|
@@ -320,9 +302,16 @@ class Stream
|
|
320
302
|
res
|
321
303
|
end
|
322
304
|
|
323
|
-
|
324
|
-
#
|
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.
|
325
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
|
+
)
|
326
315
|
read_bits_int_be(n)
|
327
316
|
end
|
328
317
|
|
@@ -330,7 +319,7 @@ class Stream
|
|
330
319
|
res = 0
|
331
320
|
bits_needed = n - @bits_left
|
332
321
|
|
333
|
-
if bits_needed > 0
|
322
|
+
if bits_needed > 0
|
334
323
|
# 1 bit => 1 byte
|
335
324
|
# 8 bits => 1 byte
|
336
325
|
# 9 bits => 2 bytes
|
@@ -368,13 +357,26 @@ class Stream
|
|
368
357
|
# @raise [EOFError] if there were less bytes than requested
|
369
358
|
# available in the stream
|
370
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
|
+
|
371
368
|
r = @_io.read(n)
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
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}")
|
376
379
|
end
|
377
|
-
raise EOFError.new("attempted to read #{n} bytes, got only #{rl}") if rl < n
|
378
380
|
r
|
379
381
|
end
|
380
382
|
|
@@ -386,17 +388,18 @@ class Stream
|
|
386
388
|
end
|
387
389
|
|
388
390
|
def read_bytes_term(term, include_term, consume_term, eos_error)
|
389
|
-
|
391
|
+
term_byte = term.chr
|
392
|
+
r = String.new
|
390
393
|
loop {
|
391
|
-
|
394
|
+
c = @_io.getc
|
395
|
+
if c.nil?
|
392
396
|
if eos_error
|
393
397
|
raise EOFError.new("end of stream reached, but no terminator #{term} found")
|
394
|
-
else
|
395
|
-
return r
|
396
398
|
end
|
399
|
+
|
400
|
+
return r
|
397
401
|
end
|
398
|
-
c
|
399
|
-
if c.ord == term
|
402
|
+
if c == term_byte
|
400
403
|
r << c if include_term
|
401
404
|
@_io.seek(@_io.pos - 1) unless consume_term
|
402
405
|
return r
|
@@ -405,9 +408,33 @@ class Stream
|
|
405
408
|
}
|
406
409
|
end
|
407
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
|
+
|
408
433
|
##
|
409
|
-
# Unused since Kaitai Struct
|
410
|
-
#
|
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.
|
411
438
|
#
|
412
439
|
# Reads next len bytes from the stream and ensures that they match
|
413
440
|
# expected fixed byte array. If they differ, throws a
|
@@ -417,6 +444,10 @@ class Stream
|
|
417
444
|
# equal to expected
|
418
445
|
# @raise [UnexpectedDataError]
|
419
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
|
+
)
|
420
451
|
len = expected.bytesize
|
421
452
|
actual = @_io.read(len)
|
422
453
|
raise UnexpectedDataError.new(actual, expected) if actual != expected
|
@@ -433,13 +464,27 @@ class Stream
|
|
433
464
|
end
|
434
465
|
|
435
466
|
def self.bytes_terminate(bytes, term, include_term)
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
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)]
|
440
472
|
end
|
441
|
-
|
442
|
-
|
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
|
+
}
|
443
488
|
end
|
444
489
|
|
445
490
|
# @!endgroup
|
@@ -516,52 +561,150 @@ class Stream
|
|
516
561
|
|
517
562
|
# @!endgroup
|
518
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
|
+
|
519
586
|
##
|
520
587
|
# Resolves value using enum: if the value is not found in the map,
|
521
588
|
# we'll just use literal value per se.
|
522
589
|
def self.resolve_enum(enum_map, value)
|
523
590
|
enum_map[value] || value
|
524
591
|
end
|
592
|
+
end
|
525
593
|
|
526
|
-
|
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
|
527
612
|
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
613
|
+
##
|
614
|
+
# Current position in a substream. Independent from a position in a
|
615
|
+
# parent IO.
|
616
|
+
attr_reader :pos
|
532
617
|
|
533
|
-
def
|
534
|
-
|
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
|
535
624
|
end
|
536
625
|
|
537
|
-
def
|
538
|
-
|
626
|
+
def eof?
|
627
|
+
raise IOError.new('not opened for reading') if @closed
|
628
|
+
|
629
|
+
@pos >= @size
|
539
630
|
end
|
540
631
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
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) }
|
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
|
554
640
|
end
|
555
641
|
|
556
|
-
def
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
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
|
562
682
|
end
|
563
|
-
|
564
|
-
|
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
|
565
708
|
end
|
566
709
|
end
|
567
710
|
|
@@ -569,7 +712,7 @@ end
|
|
569
712
|
# Common ancestor for all error originating from Kaitai Struct usage.
|
570
713
|
# Stores KSY source path, pointing to an element supposedly guilty of
|
571
714
|
# an error.
|
572
|
-
class KaitaiStructError <
|
715
|
+
class KaitaiStructError < StandardError
|
573
716
|
def initialize(msg, src_path)
|
574
717
|
super("#{src_path}: #{msg}")
|
575
718
|
@src_path = src_path
|
@@ -601,7 +744,7 @@ end
|
|
601
744
|
# "expected", but it turned out that it's not.
|
602
745
|
class ValidationNotEqualError < ValidationFailedError
|
603
746
|
def initialize(expected, actual, io, src_path)
|
604
|
-
expected_repr, actual_repr =
|
747
|
+
expected_repr, actual_repr = Internal.inspect_values(expected, actual)
|
605
748
|
super("not equal, expected #{expected_repr}, but got #{actual_repr}", io, src_path)
|
606
749
|
|
607
750
|
@expected = expected
|
@@ -614,7 +757,7 @@ end
|
|
614
757
|
# than or equal to "min", but it turned out that it's not.
|
615
758
|
class ValidationLessThanError < ValidationFailedError
|
616
759
|
def initialize(min, actual, io, src_path)
|
617
|
-
min_repr, actual_repr =
|
760
|
+
min_repr, actual_repr = Internal.inspect_values(min, actual)
|
618
761
|
super("not in range, min #{min_repr}, but got #{actual_repr}", io, src_path)
|
619
762
|
@min = min
|
620
763
|
@actual = actual
|
@@ -626,7 +769,7 @@ end
|
|
626
769
|
# than or equal to "max", but it turned out that it's not.
|
627
770
|
class ValidationGreaterThanError < ValidationFailedError
|
628
771
|
def initialize(max, actual, io, src_path)
|
629
|
-
max_repr, actual_repr =
|
772
|
+
max_repr, actual_repr = Internal.inspect_values(max, actual)
|
630
773
|
super("not in range, max #{max_repr}, but got #{actual_repr}", io, src_path)
|
631
774
|
@max = max
|
632
775
|
@actual = actual
|
@@ -638,22 +781,113 @@ end
|
|
638
781
|
# the given list, but it turned out that it's not.
|
639
782
|
class ValidationNotAnyOfError < ValidationFailedError
|
640
783
|
def initialize(actual, io, src_path)
|
641
|
-
actual_repr =
|
784
|
+
actual_repr = Internal.inspect_values(actual)
|
642
785
|
super("not any of the list, got #{actual_repr}", io, src_path)
|
643
786
|
@actual = actual
|
644
787
|
end
|
645
788
|
end
|
646
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
|
+
|
647
801
|
##
|
648
802
|
# Signals validation failure: we required "actual" value to match
|
649
803
|
# the expression, but it turned out that it doesn't.
|
650
804
|
class ValidationExprError < ValidationFailedError
|
651
805
|
def initialize(actual, io, src_path)
|
652
|
-
actual_repr =
|
806
|
+
actual_repr = Internal.inspect_values(actual)
|
653
807
|
super("not matching the expression, got #{actual_repr}", io, src_path)
|
654
808
|
@actual = actual
|
655
809
|
end
|
656
810
|
end
|
657
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
|
+
|
658
892
|
end
|
659
893
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kaitai-struct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.11'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikhail Yakshin
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-09-10 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: |
|
14
13
|
Kaitai Struct is a declarative language used for describe various binary data structures, laid out in files or in memory: i.e. binary file formats, network stream packet formats, etc.
|
@@ -24,14 +23,15 @@ files:
|
|
24
23
|
- LICENSE
|
25
24
|
- README.md
|
26
25
|
- lib/kaitai/struct/struct.rb
|
27
|
-
homepage:
|
26
|
+
homepage: https://kaitai.io/
|
28
27
|
licenses:
|
29
28
|
- MIT
|
30
29
|
metadata:
|
31
30
|
bug_tracker_uri: https://github.com/kaitai-io/kaitai_struct_ruby_runtime/issues
|
32
|
-
|
31
|
+
documentation_uri: https://www.rubydoc.info/gems/kaitai-struct
|
32
|
+
homepage_uri: https://kaitai.io/
|
33
33
|
source_code_uri: https://github.com/kaitai-io/kaitai_struct_ruby_runtime
|
34
|
-
|
34
|
+
rubygems_mfa_required: 'true'
|
35
35
|
rdoc_options: []
|
36
36
|
require_paths:
|
37
37
|
- lib
|
@@ -39,15 +39,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
39
|
requirements:
|
40
40
|
- - ">="
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
42
|
+
version: 1.9.3
|
43
43
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '0'
|
48
48
|
requirements: []
|
49
|
-
rubygems_version: 3.
|
50
|
-
signing_key:
|
49
|
+
rubygems_version: 3.7.2
|
51
50
|
specification_version: 4
|
52
51
|
summary: 'Kaitai Struct: runtime library for Ruby'
|
53
52
|
test_files: []
|