Skip to content

Commit 7ba8e81

Browse files
authored
chore: handle fragmented messages with empty payload (#158)
1 parent 3b02853 commit 7ba8e81

File tree

5 files changed

+38
-7
lines changed

5 files changed

+38
-7
lines changed

pyais/exceptions.py

+5
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ class NonPrintableCharacterException(AISBaseException):
3838

3939
class TagBlockNotInitializedException(Exception):
4040
"""The TagBlock is not initialized"""
41+
42+
43+
class MissingPayloadException(AISBaseException):
44+
"""Valid NMEA Message without payload"""
45+
pass

pyais/messages.py

+3-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from pyais.constants import TalkerID, NavigationStatus, ManeuverIndicator, EpfdType, ShipType, NavAid, StationType, \
1313
TransmitMode, StationIntervals, TurnRate
1414
from pyais.exceptions import InvalidNMEAMessageException, TagBlockNotInitializedException, UnknownMessageException, UnknownPartNoException, \
15-
InvalidDataTypeException
15+
InvalidDataTypeException, MissingPayloadException
1616
from pyais.util import checksum, decode_into_bit_array, compute_checksum, get_itdma_comm_state, get_sotdma_comm_state, int_to_bin, str_to_bin, \
1717
encode_ascii_6, from_bytes, from_bytes_signed, decode_bin_as_ascii6, get_int, chk_to_int, coerce_val, \
1818
bits2bytes, bytes2bits, b64encode_str
@@ -500,9 +500,6 @@ def __init__(self, raw: bytes) -> None:
500500
except Exception as err:
501501
raise InvalidNMEAMessageException(raw) from err
502502

503-
if not len(payload):
504-
raise InvalidNMEAMessageException("Invalid empty payload")
505-
506503
if len(payload) > MAX_PAYLOAD_LEN:
507504
raise InvalidNMEAMessageException("AIS payload too large")
508505

@@ -601,6 +598,8 @@ def decode(self) -> "ANY_MESSAGE":
601598
>>> nmea = NMEAMessage(b"!AIVDO,1,1,,,B>qc:003wk?8mP=18D3Q3wgTiT;T,0*13").decode()
602599
MessageType18(msg_type=18, ...)
603600
"""
601+
if not self.payload:
602+
raise MissingPayloadException(self.raw.decode())
604603
try:
605604
return MSG_CLASS[self.ais_id].from_bitarray(self.bit_array)
606605
except KeyError as e:

tests/test_decode.py

+25
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
)
4949
from pyais.stream import ByteStream
5050
from pyais.util import b64encode_str, bits2bytes, bytes2bits, decode_into_bit_array
51+
from pyais.exceptions import MissingPayloadException
5152

5253

5354
def ensure_type_for_msg_dict(msg_dict: typing.Dict[str, typing.Any]) -> None:
@@ -1743,3 +1744,27 @@ def test_that_decode_nmea_and_ais_works_with_proprietary_messages(self):
17431744
self.assertEqual(decoded.msg_type, 1)
17441745
self.assertEqual(decoded.mmsi, 538090443)
17451746
self.assertEqual(decoded.speed, 10.9)
1747+
1748+
def test_that_decode_works_for_fragmented_messages_with_empty_payloads(self):
1749+
"""Issue: https://github.com/M0r13n/pyais/issues/157"""
1750+
# WHEN decoding a fragmented message where the second message has an empty payload.
1751+
decoded = decode(
1752+
b"!AIVDM,2,1,0,A,8@2R5Ph0GhRbUqe?n>KS?wvlFR06EuOwiOl?wnSwe7wvlOwwsAwwnSGmwvwt,0*4E",
1753+
b"!AIVDM,2,2,0,A,,0*16",
1754+
)
1755+
# THEN the message is decoded without an error
1756+
# Verified against https://www.aggsoft.com/ais-decoder.htm
1757+
self.assertEqual(decoded.msg_type, 8)
1758+
self.assertEqual(decoded.repeat, 1)
1759+
self.assertEqual(decoded.mmsi, 2655619)
1760+
self.assertEqual(decoded.data, b'\x08\xaa\x97\x9bO\xd8\xe6\xe3?\xff\xb4Z \x06W\xd7\xff\xc5\xfd\x0f\xffh\xff\xb4\x7f\xfe\xd1\xff\xff\xed\x1f\xff\xda5\xf5\xff\xef\xfc')
1761+
1762+
def test_decode_with_empty_payload(self):
1763+
"""Variation of test_that_decode_works_for_fragmented_messages_with_empty_payloads"""
1764+
# WHEN decoding message without payload an exception is raised
1765+
with self.assertRaises(MissingPayloadException) as err:
1766+
_ = decode(
1767+
b"!AIVDM,1,1,0,A,,0*16",
1768+
)
1769+
1770+
self.assertEqual(str(err.exception), '!AIVDM,1,1,0,A,,0*16')

tests/test_decode_raw.py

-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ def should_raise(msg):
4444
should_raise(",1,1,,A,403Ovl@000Htt<tSF0l4Q@100`Pq,0*28")
4545
should_raise("!AIVDM,,1,,A,403Ovl@000Htt<tSF0l4Q@100`Pq,0*28")
4646
should_raise("!AIVDM,1,,,A,403Ovl@000Htt<tSF0l4Q@100`Pq,0*28")
47-
should_raise("!AIVDM,1,1,,A,,0*28")
4847

4948
should_raise("!AIVDM,11111111111111,1,,A,403Ovl@000Htt<tSF0l4Q@100`Pq,0*28")
5049
should_raise("!AIVDM,1,11111111111111111111,,A,403Ovl@000Htt<tSF0l4Q@100`Pq,0*28")

tests/test_file_stream.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import unittest
44
from unittest.case import skip
55

6-
from pyais.exceptions import UnknownMessageException
6+
from pyais.exceptions import UnknownMessageException, MissingPayloadException
77
from pyais.messages import GatehouseSentence, NMEAMessage
88
from pyais.stream import FileReaderStream, IterMessages
99

@@ -180,7 +180,10 @@ def test_marine_traffic_sample(self):
180180

181181
with FileReaderStream(nmea_file) as stream:
182182
for msg in stream:
183-
assert msg.decode()
183+
try:
184+
assert msg.decode()
185+
except MissingPayloadException:
186+
assert msg.raw.startswith(b'!AIVDM,1,1,,')
184187

185188
def test_mixed_content(self):
186189
"""Test that the file reader handles mixed content. That means, that is is able to handle

0 commit comments

Comments
 (0)