Repository: M0r13n/pyais

Issue:
ValueError: invalid literal for int() with base 16: b''
Maybe this is not as intended?

I am parsing a file of all sorts of bad data and decoding it; I handle the exceptions; however this seems like a bad way to handle a missing checksum?

!AIVDM,1,1,,A,100u3FP04r28t0<WcshcQI<H0H79,0

`Traceback (most recent call last):
  File "pyais_test.py", line 14, in <module>
    decoded = pyais.decode(i)
  File "c:\Python38\lib\site-packages\pyais\decode.py", line 34, in decode
    nmea = _assemble_messages(*parts)
  File "c:\Python38\lib\site-packages\pyais\decode.py", line 13, in _assemble_messages
    nmea = NMEAMessage(msg)
  File "c:\Python38\lib\site-packages\pyais\messages.py", line 201, in __init__
    self.checksum = int(checksum[2:], 16)
ValueError: invalid literal for int() with base 16: b''`

All the best

Comments:

Patch:
diff --git a/pyais/messages.py b/pyais/messages.py
index 3cdd34d..6154e5a 100644
--- a/pyais/messages.py
+++ b/pyais/messages.py
@@ -11,7 +11,7 @@ from pyais.constants import TalkerID, NavigationStatus, ManeuverIndicator, EpfdT
 from pyais.exceptions import InvalidNMEAMessageException, UnknownMessageException, UnknownPartNoException, \
     InvalidDataTypeException
 from pyais.util import decode_into_bit_array, compute_checksum, int_to_bin, str_to_bin, \
-    encode_ascii_6, from_bytes, int_to_bytes, from_bytes_signed, decode_bin_as_ascii6, get_int
+    encode_ascii_6, from_bytes, int_to_bytes, from_bytes_signed, decode_bin_as_ascii6, get_int, chk_to_int
 
 NMEA_VALUE = typing.Union[str, float, int, bool, bytes]
 
@@ -195,10 +195,12 @@ class NMEAMessage(object):
         self.channel: str = channel.decode('ascii')
         # Decoded message payload as byte string
         self.payload: bytes = payload
+
+        fill, check = chk_to_int(checksum)
         # Fill bits (0 to 5)
-        self.fill_bits: int = int(chr(checksum[0]))
+        self.fill_bits: int = fill
         # Message Checksum (hex value)
-        self.checksum = int(checksum[2:], 16)
+        self.checksum = check
 
         # Finally decode bytes into bits
         self.bit_array: bitarray = decode_into_bit_array(self.payload, self.fill_bits)
diff --git a/pyais/util.py b/pyais/util.py
index 846c89d..06147bc 100644
--- a/pyais/util.py
+++ b/pyais/util.py
@@ -229,3 +229,20 @@ def str_to_bin(val: str, width: int) -> bitarray:
         out += bitarray(txt)
 
     return out
+
+
+def chk_to_int(chk_str: bytes) -> typing.Tuple[int, int]:
+    """
+    Converts a checksum string to a tuple of (fillbits, checksum).
+    >>> chk_to_int(b"0*1B")
+    (0, 27)
+    """
+    if not len(chk_str):
+        return 0, -1
+
+    fill_bits: int = int(chr(chk_str[0]))
+    try:
+        checksum = int(chk_str[2:], 16)
+    except (IndexError, ValueError):
+        checksum = -1
+    return fill_bits, checksum


Test patch:
diff --git a/tests/test_nmea.py b/tests/test_nmea.py
index bacc0fb..e274ed4 100644
--- a/tests/test_nmea.py
+++ b/tests/test_nmea.py
@@ -5,6 +5,7 @@ from bitarray import bitarray
 
 from pyais.exceptions import InvalidNMEAMessageException
 from pyais.messages import NMEAMessage
+from pyais.util import chk_to_int
 
 
 class TestNMEA(unittest.TestCase):
@@ -173,3 +174,25 @@ class TestNMEA(unittest.TestCase):
 
         with self.assertRaises(TypeError):
             _ = msg[1:3]
+
+    def test_missing_checksum(self):
+        msg = b"!AIVDM,1,1,,A,100u3FP04r28t0<WcshcQI<H0H79,0"
+        NMEAMessage(msg)
+
+    def test_chk_to_int_with_valid_checksum(self):
+        self.assertEqual(chk_to_int(b"0*1B"), (0, 27))
+        self.assertEqual(chk_to_int(b"0*FF"), (0, 255))
+        self.assertEqual(chk_to_int(b"0*00"), (0, 0))
+
+    def test_chk_to_int_with_fill_bits(self):
+        self.assertEqual(chk_to_int(b"1*1B"), (1, 27))
+        self.assertEqual(chk_to_int(b"5*1B"), (5, 27))
+
+    def test_chk_to_int_with_missing_checksum(self):
+        self.assertEqual(chk_to_int(b"1"), (1, -1))
+        self.assertEqual(chk_to_int(b"5*"), (5, -1))
+
+    def test_chk_to_int_with_missing_fill_bits(self):
+        self.assertEqual(chk_to_int(b""), (0, -1))
+        with self.assertRaises(ValueError):
+            self.assertEqual(chk_to_int(b"*1B"), (0, 24))