Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #368: Bad packet size handling in built-in server handlers #456

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ait/core/server/broker.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import zmq.green as zmq
import gevent
import gevent.monkey
from gevent import monkey
monkey.patch_all()

gevent.monkey.patch_all()
import zmq.green as zmq

from typing import List, Any

Expand Down
22 changes: 8 additions & 14 deletions ait/core/server/handlers/ccsds_packet_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,8 @@ def __init__(self, input_type=None, output_type=None, **kwargs):
tlm_dict = tlm.getDefaultDict()
for packet_name in self.packet_types.values():
if packet_name not in tlm_dict.keys():
msg = "CCSDSPacketHandler: Packet name {} not present in telemetry dictionary.".format(
packet_name
)
msg += " Available packet types are {}".format(tlm_dict.keys())
msg = f"CCSDSPacketHandler: Packet name {packet_name} not present in telemetry dictionary."
msg += f" Available packet types are {tlm_dict.keys()}"
raise ValueError(msg)

def handle(self, input_data):
Expand All @@ -59,24 +57,20 @@ def handle(self, input_data):
# Check if packet length is at least 7 bytes
primary_header_length = 6
if len(input_data) < primary_header_length + 1:
ait.core.log.info(
ait.core.log.error(
"CCSDSPacketHandler: Received packet length is less than minimum of 7 bytes."
)
return

# Extract APID from packet
packet_apid = str(bin(int(binascii.hexlify(input_data[0:2]), 16) & 0x07FF))[
2:
].zfill(11)
packet_apid = str(bin(int(binascii.hexlify(input_data[0:2]), 16) & 0x07FF))[2:].zfill(11)

# Check if packet_apid matches with an APID in the config
config_apid = self.comp_apid(packet_apid)
if not config_apid:
msg = "CCSDSPacketHandler: Packet APID {} not present in config.".format(
packet_apid
)
msg += " Available packet APIDs are {}".format(self.packet_types.keys())
ait.core.log.info(msg)
msg = f"CCSDSPacketHandler: Packet APID {packet_apid} not present in config."
msg += f" Available packet APIDs are {self.packet_types.keys()}"
ait.core.log.error(msg)
return

# Map APID to packet name in config to get UID from telemetry dictionary
Expand All @@ -87,7 +81,7 @@ def handle(self, input_data):
# Extract user data field from packet
packet_data_length = int(binascii.hexlify(input_data[4:6]), 16) + 1
if len(input_data) < primary_header_length + packet_data_length:
ait.core.log.info(
ait.core.log.error(
"CCSDSPacketHandler: Packet data length is less than stated length in packet primary header."
)
return
Expand Down
29 changes: 16 additions & 13 deletions ait/core/server/handlers/packet_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,30 @@ def __init__(self, input_type=None, output_type=None, **kwargs):
If packet is specified but not present in default tlm dict.
"""
super(PacketHandler, self).__init__(input_type, output_type)
self.packet = kwargs.get("packet", None)
self.packet_name = kwargs.get("packet", None)
self.tlm_dict = tlm.getDefaultDict()

if not self.packet:
msg = 'PacketHandler: No packet name provided in handler config as key "packet"'
if not self.packet_name:
msg = f'PacketHandler: No packet type provided in handler config as key "packet"'
raise ValueError(msg)

tlm_dict = tlm.getDefaultDict()
if self.packet not in tlm_dict:
msg = "PacketHandler: Packet name {} not present in telemetry dictionary".format(
self.packet
)
msg += " Available packet types are {}".format(tlm_dict.keys())
if self.packet_name not in self.tlm_dict:
msg = f"PacketHandler: Packet name '{self.packet_name}' not present in telemetry dictionary."
msg += f" Available packet types are {self.tlm_dict.keys()}"
raise ValueError(msg)

self._pkt_defn = tlm_dict[self.packet]
self._pkt_defn = self.tlm_dict[self.packet_name]

def handle(self, input_data):
def handle(self, packet):
"""
Params:
input_data: message received by stream
packet: message received by stream (packet)
nttoole marked this conversation as resolved.
Show resolved Hide resolved
Returns:
tuple of packet UID and message received by stream
"""
return pickle.dumps((self._pkt_defn.uid, input_data), 2)

if self._pkt_defn.nbytes != packet.nbytes:
msg = f"PacketHandler: Packet data length does not match packet definition."
raise ValueError(msg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JimHofman Could this be an error log message instead of thrown error (same as CCSDS handler)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, will work on pickle error before pushing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the pickle error is coming from the unit tests that test bad packet length. It's is set up by passing a 1552 packet to an Ethernet handler, so it makes sense.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check doesn't even look valid? You should be checking the length of the received input data not the number of bytes in a packet definition.


return pickle.dumps((self._pkt_defn.uid, packet), 2)
6 changes: 3 additions & 3 deletions ait/core/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ def __init__(self):

def wait(self):
"""
Starts all greenlets and plugin-pocesses for concurrent processing.
Starts all greenlets and plugin-processes for concurrent processing.
Joins over all greenlets that are not servers.
"""
# Start all of the greenlets managed by this process
# Start all greenlets managed by this process
for greenlet in self.greenlets + self.servers:
log.info(f"Starting {greenlet} greenlet...")
greenlet.start()

# Start all of the separate plugin processes
# Start all separate plugin processes
for plugin_process in self.plugin_processes:
log.info(f"Spawning {plugin_process} process...")
plugin_process.spawn_process()
Expand Down
3 changes: 2 additions & 1 deletion ait/core/tlm.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ def __getitem__(self, key):
"""Returns the words in this wordarray at the given Python slice
or word at the given integer index."""
length = len(self)

if isinstance(key, slice):
return [self[n] for n in range(*key.indices(length))]

Expand Down Expand Up @@ -371,6 +370,7 @@ def validate(self, value, messages=None):
Validation error messages are appended to an optional messages
array.
"""

valid = True
primitive = value

Expand Down Expand Up @@ -520,6 +520,7 @@ def validate(self, messages=None):
Validation error messages are appended to an optional messages
array.
"""

return self._defn.validate(self, messages)


Expand Down
64 changes: 61 additions & 3 deletions tests/ait/core/server/test_handler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pickle
import pytest
import unittest
from unittest import mock

Expand All @@ -14,28 +15,32 @@ def test_ccsds_packet_length(self):
handler = CCSDSPacketHandler(packet_types={"01011100111": "CCSDS_HEADER"})
data = bytearray(b"\x02\xE7\x40\x00\x00\x00")
with self.assertLogs("ait", level="INFO") as cm:
result = handler.handle(data)
handler.handle(data)
self.assertIn("less than minimum of 7 bytes", cm.output[0])

# Check if APID match between config and packet
def test_ccsds_packet_apid(self):
handler = CCSDSPacketHandler(packet_types={"00000000000": "CCSDS_HEADER"})
data = bytearray(b"\x02\xE7\x40\x00\x00\x00\x01")
with self.assertLogs("ait", level="INFO") as cm:
result = handler.handle(data)
handler.handle(data)
self.assertIn("not present in config", cm.output[0])

# Check packet length vs header reported length
def test_ccsds_packet_header(self):
handler = CCSDSPacketHandler(packet_types={"01011100111": "CCSDS_HEADER"})
data = bytearray(b"\x02\xE7\x40\x00\x00\x0F\x01")
with self.assertLogs("ait", level="INFO") as cm:
result = handler.handle(data)
handler.handle(data)
self.assertIn(
"Packet data length is less than stated length in packet primary header",
cm.output[0],
)

def test_packet_name_not_present(self):
with pytest.raises(ValueError):
CCSDSPacketHandler(packet_types={"01011100111": "CCSDS_"})

# Check if dumped uid match expected tlm dictionary uid
def test_ccsds_packet_uid(self):
handler = CCSDSPacketHandler(packet_types={"01011100111": "CCSDS_HEADER"})
Expand All @@ -62,6 +67,59 @@ def test_execute_handler_returns_handle_return_on_input(self, handle_mock):
assert returned == "SpecialReturn"


class TestHandlerClassWith1553HSPacket(object):
tlm_dict = tlm.getDefaultDict()
pkt_data = bytearray(b"\x02\xE7\x40\x00\x00\x00\x01")
pkt_1553 = tlm_dict['1553_HS_Packet']
handler = PacketHandler(pkt_data, packet="1553_HS_Packet")

def test_word_array(self):
packet = tlm.Packet(self.pkt_1553, self.pkt_data)
assert packet.words.__len__() == 3.5

def test_execute_handler_returns_handle_return_on_input(self):
packet = tlm.Packet(self.pkt_1553, self.pkt_data)
result = self.handler.handle(packet)
assert 'Ethernet 1553 packet' in str(result)

# Test packet length by sending a Ethernet_HS_Packet to a 1553_HS_Packet Handler
def test_bad_packet_length(self):
ethernet_pkt = self.tlm_dict['Ethernet_HS_Packet']
e_packet = tlm.Packet(ethernet_pkt, self.pkt_data)
with pytest.raises(ValueError):
self.handler.handle(e_packet)

def test_packet_name_error_and_no_packet_type(self):
pkt_data = bytearray(b"\x02\xE7\x40\x00\x00\x00\x01")
with pytest.raises(ValueError):
PacketHandler(pkt_data, packet="1553_HS_Packe")
with pytest.raises(ValueError):
PacketHandler(pkt_data)


class TestHandlerClassWithEthernetHSPacket(object):
tlm_dict = tlm.getDefaultDict()
pkt_data = bytearray(b"\x02\xE7\x40\x00\x00\x00\x01\x07\x08\x0a")
ethernet_pkt = tlm_dict['Ethernet_HS_Packet']
handler = PacketHandler(pkt_data, packet="Ethernet_HS_Packet")

def test_word_array(self):
e_packet = tlm.Packet(self.ethernet_pkt, self.pkt_data)
assert e_packet.words.__len__() == 5

def test_execute_handler_returns_handle_return_on_input(self):
e_packet = tlm.Packet(self.ethernet_pkt, self.pkt_data)
result = self.handler.handle(e_packet)
assert 'Ethernet Health and Status Packet' in str(result)

# Send a 1553 packet to an Ethernet_HS_Packet Handler
def test_bad_packet_length(self):
pkt_1553 = self.tlm_dict['1553_HS_Packet']
packet = tlm.Packet(pkt_1553, self.pkt_data)
with pytest.raises(ValueError):
self.handler.handle(packet)
nttoole marked this conversation as resolved.
Show resolved Hide resolved


class TestHandlerClassWithoutInputOutputTypes(object):
handler = PacketHandler(packet="CCSDS_HEADER")

Expand Down