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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +2 -2
  4. data/lib/kaitai/struct/struct.rb +347 -113
  5. metadata +8 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cbdd2b873476556b609d64f78e10c3ef35ea5ede048ac5ee57a03a24f523bfac
4
- data.tar.gz: db41f46711b11efcf12a982e282181ae20d25831f408b7a3e7e668791f14701b
3
+ metadata.gz: dd4bb9d0b8c7befee55e055dd8eb8aba8e13f08074600f3ed2a49e1b32f11ebc
4
+ data.tar.gz: 24fe1b2e85d6682c71afb4fd80824634b9113453e3fb80736c12c8051c09c5d9
5
5
  SHA512:
6
- metadata.gz: ea4b2780d4efa7c645175957059073f6506ee5e39d2525b62f7a5de41816d0115dde2980948ab47e8994b1a7fc97cb94e8fab7692882244fc7d9b58b18b3e8b3
7
- data.tar.gz: 99cec16629be56c07b1df24f4a091fbffdd6240341dc0130ef6a5d04195a91c37d6238c4991ba3782667514cf25ae7746aa2fdd3aa307c34352c0dd0698ea4c9
6
+ metadata.gz: 9f88733d357dbe3962c34437bf775cfd0d9a4c3f48dbc9c88be7a08606c9d4ec8a2adfb8e61804b61bb28ff10cb5cd1178f7ef0397edf0d6d2703e1846ce2afe
7
+ data.tar.gz: 53e735516abfafc05f0218b93cabfeece0978a3d41aec1afb04c21d89dc76085cb83ecfd5b13bc0d41fe2d8dfec67a4c7acb96e283c62e0fa0b27cded490b48b
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2015-2022 Kaitai Project
3
+ Copyright (c) 2015-2025 Kaitai Project
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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](http://kaitai.io/)
15
- * [About API implemented in this library](http://doc.kaitai.io/stream_api.html)
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
 
@@ -3,7 +3,7 @@ require 'stringio'
3
3
  module Kaitai
4
4
  module Struct
5
5
 
6
- VERSION = '0.10'
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 = self)
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 Compiler v0.9+ - compatibility with
80
- # older versions.
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 #{Stream.format_hex(actual)}, was waiting for #{Stream.format_hex(expected)}")
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 array to read data from;
96
- # if IO, if will be used literally as source of data
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.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?
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
- to_signed(read_u2be, SIGN_MASK_16)
172
+ read_bytes(2).unpack('s>')[0]
165
173
  end
166
174
 
167
175
  def read_s4be
168
- to_signed(read_u4be, SIGN_MASK_32)
176
+ read_bytes(4).unpack('l>')[0]
169
177
  end
170
178
 
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
+ 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
- to_signed(read_u2le, SIGN_MASK_16)
188
+ read_bytes(2).unpack('s<')[0]
187
189
  end
188
190
 
189
191
  def read_s4le
190
- to_signed(read_u4le, SIGN_MASK_32)
192
+ read_bytes(4).unpack('l<')[0]
191
193
  end
192
194
 
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
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('n')[0]
212
+ read_bytes(2).unpack('S>')[0]
217
213
  end
218
214
 
219
215
  def read_u4be
220
- read_bytes(4).unpack('N')[0]
216
+ read_bytes(4).unpack('L>')[0]
221
217
  end
222
218
 
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
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('v')[0]
228
+ read_bytes(2).unpack('S<')[0]
240
229
  end
241
230
 
242
231
  def read_u4le
243
- read_bytes(4).unpack('V')[0]
232
+ read_bytes(4).unpack('L<')[0]
244
233
  end
245
234
 
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
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
- # Unused since Kaitai Struct Compiler v0.9+ - compatibility with
324
- # older versions.
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 then
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
- if r
373
- rl = r.bytesize
374
- else
375
- rl = 0
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
- r = ''
391
+ term_byte = term.chr
392
+ r = String.new
390
393
  loop {
391
- if @_io.eof?
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 = @_io.getc
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 Compiler v0.9+ - compatibility with
410
- # older versions.
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
- new_len = 0
437
- max_len = bytes.length
438
- while bytes.getbyte(new_len) != term and new_len < max_len
439
- new_len += 1
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
- new_len += 1 if include_term and new_len < max_len
442
- bytes[0, new_len]
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
- private
529
- SIGN_MASK_16 = (1 << (16 - 1))
530
- SIGN_MASK_32 = (1 << (32 - 1))
531
- SIGN_MASK_64 = (1 << (64 - 1))
613
+ ##
614
+ # Current position in a substream. Independent from a position in a
615
+ # parent IO.
616
+ attr_reader :pos
532
617
 
533
- def to_signed(x, mask)
534
- (x & ~mask) - (x & mask)
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 self.format_hex(bytes)
538
- bytes.unpack('H*')[0].gsub(/(..)/, '\1 ').chop
626
+ def eof?
627
+ raise IOError.new('not opened for reading') if @closed
628
+
629
+ @pos >= @size
539
630
  end
540
631
 
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) }
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 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
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
- reprs.length == 1 ? reprs[0] : reprs
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 < Exception
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 = Stream.inspect_values(expected, actual)
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 = Stream.inspect_values(min, actual)
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 = Stream.inspect_values(max, actual)
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 = Stream.inspect_values(actual)
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 = Stream.inspect_values(actual)
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.10'
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: 2022-07-09 00:00:00.000000000 Z
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: http://kaitai.io
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
- homepage_uri: http://kaitai.io
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
- post_install_message:
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: '0'
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.3.17
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: []