Skip to content

Commit

Permalink
Raise a meaningful exception if 'decode_msg' is used improperly (#38)
Browse files Browse the repository at this point in the history
  • Loading branch information
M0r13n committed Sep 11, 2021
1 parent 8e96f16 commit 5527830
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python: ['3.7']
python: ['3.7', '3.8', '3.9']
os: ['ubuntu-latest']
steps:
- uses: actions/checkout@master
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
====================
pyais CHANGELOG
====================
-------------------------------------------------------------------------------
Version 1.6.2 2 May 2021
-------------------------------------------------------------------------------

* Improves `decode_msg` by adding meaningful error messages

-------------------------------------------------------------------------------
Version 1.6.0 2 May 2021
-------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion pyais/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


__license__ = 'MIT'
__version__ = '1.6.1'
__version__ = '1.6.2'

__all__ = (
'decode_msg',
Expand Down
26 changes: 21 additions & 5 deletions pyais/decode.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from functools import partial
from typing import Any, Dict, Union
from typing import Any, Dict, Union, List

import bitarray # type: ignore
import bitarray

from pyais import messages
from pyais.constants import (
Expand All @@ -14,7 +14,7 @@
StationIntervals,
NavAid
)
from pyais.exceptions import UnknownMessageException
from pyais.exceptions import UnknownMessageException, MissingMultipartMessageException, TooManyMessagesException
from pyais.util import get_int, encode_bin_as_ascii6, get_mmsi


Expand Down Expand Up @@ -715,8 +715,24 @@ def decode_msg(*args: Union[str, bytes]) -> Dict[str, Any]:
# Make everything bytes
message_as_bytes = tuple(msg.encode('utf-8') if isinstance(msg, str) else msg for msg in args)

# Create temporary messages
temp = [messages.NMEAMessage(m) for m in message_as_bytes]
# Convert bytes into NMEAMessage and remember fragment_count and fragment_numbers
temp: List[messages.NMEAMessage] = []
frags: List[int] = []
frag_cnt: int = 1
for msg in message_as_bytes:
nmea = messages.NMEAMessage(msg)
temp.append(nmea)
frags.append(nmea.fragment_number)
frag_cnt = nmea.fragment_count

# Make sure provided parts assemble a single (multiline message)
if len(message_as_bytes) > frag_cnt:
raise TooManyMessagesException(f"Got {len(message_as_bytes)} messages, but fragment count is {frag_cnt}")

# Make sure all parts of a multipart message are provided
diff = [x for x in range(1, frag_cnt + 1) if x not in frags]
if len(diff):
raise MissingMultipartMessageException(f"Missing fragment numbers: {diff}")

# Assemble temporary messages
final = messages.NMEAMessage.assemble_from_iterable(temp)
Expand Down
8 changes: 8 additions & 0 deletions pyais/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,11 @@ class InvalidNMEAMessageException(Exception):
class UnknownMessageException(Exception):
"""Message not supported yet"""
pass


class MissingMultipartMessageException(Exception):
"""Multipart message with missing parts provided"""


class TooManyMessagesException(Exception):
"""Too many messages"""
4 changes: 2 additions & 2 deletions pyais/messages.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import json
from typing import Any, Dict, Optional, Sequence, Tuple, Type, Union

from bitarray import bitarray # type: ignore
from bitarray import bitarray

from pyais.ais_types import AISType
from pyais.constants import TalkerID
Expand Down Expand Up @@ -179,7 +179,7 @@ def __str__(self) -> str:
def __getitem__(self, item: str) -> Union[int, str, bytes, bitarray]:
if isinstance(item, str):
try:
return getattr(self, item)
return getattr(self, item) # type: ignore
except AttributeError:
raise KeyError(item)
else:
Expand Down
4 changes: 2 additions & 2 deletions pyais/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from operator import xor
from typing import Any, Generator, Hashable, TYPE_CHECKING, Callable

from bitarray import bitarray # type: ignore
from bitarray import bitarray

if TYPE_CHECKING:
BaseDict = OrderedDict[Hashable, Any]
Expand Down Expand Up @@ -43,7 +43,7 @@ def decode_into_bit_array(data: bytes) -> bitarray:
# Convert 8 bit binary to 6 bit binary
c -= 0x30 if (c < 0x60) else 0x38
c &= 0x3F
bit_arr += f'{c:06b}'
bit_arr += bitarray(f'{c:06b}')

return bit_arr

Expand Down
39 changes: 33 additions & 6 deletions tests/test_decode_raw.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import unittest

from pyais import decode_msg
from pyais.exceptions import InvalidNMEAMessageException
from pyais.exceptions import InvalidNMEAMessageException, MissingMultipartMessageException, TooManyMessagesException


class TestDecode(unittest.TestCase):
Expand Down Expand Up @@ -64,9 +64,36 @@ def test_decode_multiline_message(self):
self.assertEqual(decoded["shipname"], "NORDIC HAMBURG")
self.assertEqual(decoded["destination"], "CTT-LAYBY")

decoded = decode_msg(
b'!AIVDM,2,1,1,A,538CQ>02A;h?D9QC800pu8@T>0P4l9E8L0000017Ah:;;5r50Ahm5;C0,0*07',
b'!AIVDM,2,2,1,A,F@V@00000000000,2*35',
b'!AIVDM,2,1,9,A,538CQ>02A;h?D9QC800pu8@T>0P4l9E8L0000017Ah:;;5r50Ahm5;C0,0*0F',
b'!AIVDM,2,2,9,A,F@V@00000000000,2*3D',
def test_too_many_messages(self):
with self.assertRaises(TooManyMessagesException) as err:
decode_msg(
b'!AIVDM,2,1,1,A,538CQ>02A;h?D9QC800pu8@T>0P4l9E8L0000017Ah:;;5r50Ahm5;C0,0*07',
b'!AIVDM,2,2,1,A,F@V@00000000000,2*35',
b'!AIVDM,2,1,9,A,538CQ>02A;h?D9QC800pu8@T>0P4l9E8L0000017Ah:;;5r50Ahm5;C0,0*0F',
b'!AIVDM,2,2,9,A,F@V@00000000000,2*3D',
)
self.assertEqual(str(err.exception), "Got 4 messages, but fragment count is 2")

def test_multipart_error_message(self):
"""Refer to issue #37"""
msg_1 = "!AIVDM,2,1,0,A,539p4OT00000@7W3K@08ThiLE8@E:0000000001S0h9135Pl?0R0C@UDQp00,0*68"
msg_2 = "!AIVDM,2,2,0,A,00000000000,2*24"

with self.assertRaises(MissingMultipartMessageException) as err:
decode_msg(msg_1)
self.assertEqual(str(err.exception), "Missing fragment numbers: [2]")

with self.assertRaises(MissingMultipartMessageException) as err:
decode_msg(msg_2)
self.assertEqual(str(err.exception), "Missing fragment numbers: [1]")

with self.assertRaises(MissingMultipartMessageException) as err:
decode_msg(
"!AIVDM,3,2,0,A,539p4OT00000@7W3K@08ThiLE8@E:0000000001S0h9135Pl?0R0C@UDQp00,0*68",
)
self.assertEqual(str(err.exception), "Missing fragment numbers: [1, 3]")

decode_msg(
msg_1,
msg_2
)

0 comments on commit 5527830

Please sign in to comment.