@@ -128,6 +128,9 @@ def is_eof(self):
128128 return self ._io .tell () >= self .size ()
129129
130130 def seek (self , n ):
131+ if n < 0 :
132+ raise InvalidArgumentError ("cannot seek to invalid position %d" % (n ,))
133+
131134 if self .bits_write_mode :
132135 self .write_align_to_byte ()
133136 else :
@@ -376,7 +379,7 @@ def read_bytes(self, n):
376379
377380 def _read_bytes_not_aligned (self , n ):
378381 if n < 0 :
379- raise ValueError (
382+ raise InvalidArgumentError (
380383 "requested invalid %d amount of bytes" %
381384 (n ,)
382385 )
@@ -404,9 +407,10 @@ def _read_bytes_not_aligned(self, n):
404407
405408 if not is_satisfiable :
406409 # noinspection PyUnboundLocalVariable
407- raise EOFError (
410+ raise EndOfStreamError (
408411 "requested %d bytes, but only %d bytes available" %
409- (n , num_bytes_available )
412+ (n , num_bytes_available ),
413+ n , num_bytes_available
410414 )
411415
412416 # noinspection PyUnboundLocalVariable
@@ -424,10 +428,7 @@ def read_bytes_term(self, term, include_term, consume_term, eos_error):
424428 c = self ._io .read (1 )
425429 if not c :
426430 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 )
431432
432433 return bytes (r )
433434
@@ -448,10 +449,7 @@ def read_bytes_term_multi(self, term, include_term, consume_term, eos_error):
448449 c = self ._io .read (unit_size )
449450 if len (c ) < unit_size :
450451 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 ))
455453
456454 r += c
457455 return bytes (r )
@@ -523,9 +521,10 @@ def _ensure_bytes_left_to_write(self, n, pos):
523521
524522 num_bytes_left = full_size - pos
525523 if n > num_bytes_left :
526- raise EOFError (
524+ raise EndOfStreamError (
527525 "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
529528 )
530529
531530 # region Integer numbers
@@ -739,14 +738,25 @@ def _write_bytes_not_aligned(self, buf):
739738
740739 def write_bytes_limit (self , buf , size , term , pad_byte ):
741740 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+
742756 self .write_bytes (buf )
743757 if n < size :
744758 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 ))
750760
751761 # endregion
752762
@@ -771,7 +781,7 @@ def process_xor_many(data, key):
771781 @staticmethod
772782 def process_rotate_left (data , amount , group_size ):
773783 if group_size != 1 :
774- raise Exception (
784+ raise NotImplementedError (
775785 "unable to rotate group of %d bytes yet" %
776786 (group_size ,)
777787 )
@@ -861,15 +871,55 @@ def _write_back(self, parent):
861871
862872
863873class 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`.
867883 """
868884 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 )
870886 self .src_path = src_path
871887
872888
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+
873923class UndecidedEndiannessError (KaitaiStructError ):
874924 """Error that occurs when default endianness should be decided with
875925 switch, but nothing matches (although using endianness expression
0 commit comments