@@ -128,6 +128,9 @@ def is_eof(self):
128
128
return self ._io .tell () >= self .size ()
129
129
130
130
def seek (self , n ):
131
+ if n < 0 :
132
+ raise InvalidArgumentError ("cannot seek to invalid position %d" % (n ,))
133
+
131
134
if self .bits_write_mode :
132
135
self .write_align_to_byte ()
133
136
else :
@@ -376,7 +379,7 @@ def read_bytes(self, n):
376
379
377
380
def _read_bytes_not_aligned (self , n ):
378
381
if n < 0 :
379
- raise ValueError (
382
+ raise InvalidArgumentError (
380
383
"requested invalid %d amount of bytes" %
381
384
(n ,)
382
385
)
@@ -404,9 +407,10 @@ def _read_bytes_not_aligned(self, n):
404
407
405
408
if not is_satisfiable :
406
409
# noinspection PyUnboundLocalVariable
407
- raise EOFError (
410
+ raise EndOfStreamError (
408
411
"requested %d bytes, but only %d bytes available" %
409
- (n , num_bytes_available )
412
+ (n , num_bytes_available ),
413
+ n , num_bytes_available
410
414
)
411
415
412
416
# noinspection PyUnboundLocalVariable
@@ -424,10 +428,7 @@ def read_bytes_term(self, term, include_term, consume_term, eos_error):
424
428
c = self ._io .read (1 )
425
429
if not c :
426
430
if eos_error :
427
- raise Exception (
428
- "end of stream reached, but no terminator %d found" %
429
- (term ,)
430
- )
431
+ raise NoTerminatorFoundError (term_byte , 0 )
431
432
432
433
return bytes (r )
433
434
@@ -448,10 +449,7 @@ def read_bytes_term_multi(self, term, include_term, consume_term, eos_error):
448
449
c = self ._io .read (unit_size )
449
450
if len (c ) < unit_size :
450
451
if eos_error :
451
- raise Exception (
452
- "end of stream reached, but no terminator %s found" %
453
- (repr (term ),)
454
- )
452
+ raise NoTerminatorFoundError (term , len (c ))
455
453
456
454
r += c
457
455
return bytes (r )
@@ -523,9 +521,10 @@ def _ensure_bytes_left_to_write(self, n, pos):
523
521
524
522
num_bytes_left = full_size - pos
525
523
if n > num_bytes_left :
526
- raise EOFError (
524
+ raise EndOfStreamError (
527
525
"requested to write %d bytes, but only %d bytes left in the stream" %
528
- (n , num_bytes_left )
526
+ (n , num_bytes_left ),
527
+ n , num_bytes_left
529
528
)
530
529
531
530
# region Integer numbers
@@ -739,14 +738,25 @@ def _write_bytes_not_aligned(self, buf):
739
738
740
739
def write_bytes_limit (self , buf , size , term , pad_byte ):
741
740
n = len (buf )
741
+ # Strictly speaking, this assertion is redundant because it is already
742
+ # done in the corresponding _check() method in the generated code, but
743
+ # it seems to make sense to include it here anyway so that this method
744
+ # itself does something reasonable for every set of arguments.
745
+ #
746
+ # However, it should never be `false` when operated correctly (and in
747
+ # this case, assigning inconsistent values to fields of a KS-generated
748
+ # object is considered correct operation if the user application calls
749
+ # the corresponding _check(), which we know would raise an error and
750
+ # thus the code should not reach _write() and this method at all). So
751
+ # it's by design that this throws AssertionError, not any specific
752
+ # error, because it's not intended to be caught in user applications,
753
+ # but avoided by calling all _check() methods correctly.
754
+ assert n <= size , "writing %d bytes, but %d bytes were given" % (size , n )
755
+
742
756
self .write_bytes (buf )
743
757
if n < size :
744
758
self .write_u1 (term )
745
- pad_len = size - n - 1
746
- for _ in range (pad_len ):
747
- self .write_u1 (pad_byte )
748
- elif n > size :
749
- raise ValueError ("writing %d bytes, but %d bytes were given" % (size , n ))
759
+ self .write_bytes (KaitaiStream .byte_from_int (pad_byte ) * (size - n - 1 ))
750
760
751
761
# endregion
752
762
@@ -771,7 +781,7 @@ def process_xor_many(data, key):
771
781
@staticmethod
772
782
def process_rotate_left (data , amount , group_size ):
773
783
if group_size != 1 :
774
- raise Exception (
784
+ raise NotImplementedError (
775
785
"unable to rotate group of %d bytes yet" %
776
786
(group_size ,)
777
787
)
@@ -861,15 +871,55 @@ def _write_back(self, parent):
861
871
862
872
863
873
class KaitaiStructError (Exception ):
864
- """Common ancestor for all error originating from Kaitai Struct usage.
865
- Stores KSY source path, pointing to an element supposedly guilty of
866
- an error.
874
+ """Common ancestor for all errors originating from correct Kaitai Struct
875
+ usage (i.e. errors that indicate a problem with user input, not errors
876
+ indicating incorrect usage that are not meant to be caught but fixed in the
877
+ application code). Use this exception type in the `except` clause if you
878
+ want to handle all parse errors and serialization errors.
879
+
880
+ If available, the `src_path` attribute will contain the KSY source path
881
+ pointing to the element where the error occurred. If it is not available,
882
+ `src_path` will be `None`.
867
883
"""
868
884
def __init__ (self , msg , src_path ):
869
- super (KaitaiStructError , self ).__init__ ("%s: %s" % ( src_path , msg ) )
885
+ super (KaitaiStructError , self ).__init__ (( "" if src_path is None else src_path + ": " ) + msg )
870
886
self .src_path = src_path
871
887
872
888
889
+ class InvalidArgumentError (KaitaiStructError , ValueError ):
890
+ """Indicates that an invalid argument value was received (like `ValueError`),
891
+ but used in places where this might indicate invalid user input and
892
+ therefore represents a parse error or serialization error.
893
+ """
894
+ def __init__ (self , msg ):
895
+ super (InvalidArgumentError , self ).__init__ (msg , None )
896
+
897
+
898
+ class EndOfStreamError (KaitaiStructError , EOFError ):
899
+ """Read or write beyond end of stream. Provides the `bytes_needed` (number
900
+ of bytes requested to read or write) and `bytes_available` (number of bytes
901
+ remaining in the stream) attributes.
902
+ """
903
+ def __init__ (self , msg , bytes_needed , bytes_available ):
904
+ super (EndOfStreamError , self ).__init__ (msg , None )
905
+ self .bytes_needed = bytes_needed
906
+ self .bytes_available = bytes_available
907
+
908
+
909
+ class NoTerminatorFoundError (EndOfStreamError ):
910
+ """Special type of `EndOfStreamError` that occurs when end of stream is
911
+ reached before the required terminator is found. If you want to tolerate a
912
+ missing terminator, you can specify `eos-error: false` in the KSY
913
+ specification, in which case the end of stream will be considered a valid
914
+ end of field and this error will no longer be raised.
915
+
916
+ The `term` attribute contains a `bytes` object with the searched terminator.
917
+ """
918
+ def __init__ (self , term , bytes_available ):
919
+ super (NoTerminatorFoundError , self ).__init__ ("end of stream reached, but no terminator %r found" % (term ,), len (term ), bytes_available )
920
+ self .term = term
921
+
922
+
873
923
class UndecidedEndiannessError (KaitaiStructError ):
874
924
"""Error that occurs when default endianness should be decided with
875
925
switch, but nothing matches (although using endianness expression
0 commit comments