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

Rest of roundtrip fixes with CRC + CHANGELOG #91

Merged
merged 1 commit into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

# [unreleased]

## Fixed

- Unpacking / re-packing was buggy for some file directives when the PDU checksum was activated.
This was fixed for the following PDUs:
- NAK
- EOF
- File Data
- Metadata
- Finished

# [v0.26.0] 2024-11-27

- Python 3.8 is not supported anymore as it has reached end-of-life.
Expand Down
4 changes: 3 additions & 1 deletion spacepackets/cfdp/pdu/ack.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@
def pdu_header(self) -> PduHeader:
return self.pdu_file_directive.pdu_header

def __eq__(self, other: AckPdu):
def __eq__(self, other: object):
if not isinstance(other, AckPdu):
return False

Check warning on line 81 in spacepackets/cfdp/pdu/ack.py

View check run for this annotation

Codecov / codecov/patch

spacepackets/cfdp/pdu/ack.py#L81

Added line #L81 was not covered by tests
return (
self.pdu_file_directive == other.pdu_file_directive
and self.directive_code_of_acked_pdu == other.directive_code_of_acked_pdu
Expand Down
4 changes: 3 additions & 1 deletion spacepackets/cfdp/pdu/file_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,9 @@
def packet_len(self) -> int:
return self.pdu_header.packet_len

def __eq__(self, other: FileDataPdu):
def __eq__(self, other: object):
if not isinstance(other, FileDataPdu):
return False

Check warning on line 271 in spacepackets/cfdp/pdu/file_data.py

View check run for this annotation

Codecov / codecov/patch

spacepackets/cfdp/pdu/file_data.py#L271

Added line #L271 was not covered by tests
return self.pdu_header == other.pdu_header and self._params == other._params

def __repr__(self):
Expand Down
11 changes: 8 additions & 3 deletions spacepackets/cfdp/pdu/finished.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,11 @@
finished_pdu.condition_code = params.condition_code
finished_pdu._params = params
current_idx += 1
if len(data) > current_idx:
finished_pdu._unpack_tlvs(rest_of_packet=data[current_idx : finished_pdu.packet_len])
end_of_optional_tlvs_idx = finished_pdu.packet_len
if finished_pdu.pdu_header.crc_flag == CrcFlag.WITH_CRC:
end_of_optional_tlvs_idx -= 2
if end_of_optional_tlvs_idx > current_idx:
finished_pdu._unpack_tlvs(rest_of_packet=data[current_idx:end_of_optional_tlvs_idx])
return finished_pdu

def _unpack_tlvs(self, rest_of_packet: bytes) -> int:
Expand Down Expand Up @@ -263,7 +266,9 @@
self.fault_location = fault_loc
return current_idx

def __eq__(self, other: FinishedPdu):
def __eq__(self, other: object):
if not isinstance(other, FinishedPdu):
return False

Check warning on line 271 in spacepackets/cfdp/pdu/finished.py

View check run for this annotation

Codecov / codecov/patch

spacepackets/cfdp/pdu/finished.py#L271

Added line #L271 was not covered by tests
return self._params == other._params and self.pdu_file_directive == other.pdu_file_directive

def __repr__(self):
Expand Down
4 changes: 3 additions & 1 deletion spacepackets/cfdp/pdu/keep_alive.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@
def packet_len(self) -> int:
return self.pdu_file_directive.packet_len

def __eq__(self, other: KeepAlivePdu):
def __eq__(self, other: object):
if not isinstance(other, KeepAlivePdu):
return False

Check warning on line 114 in spacepackets/cfdp/pdu/keep_alive.py

View check run for this annotation

Codecov / codecov/patch

spacepackets/cfdp/pdu/keep_alive.py#L114

Added line #L114 was not covered by tests
return (
self.pdu_file_directive == other.pdu_file_directive and self.progress == other.progress
)
Expand Down
4 changes: 3 additions & 1 deletion spacepackets/cfdp/pdu/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@
f" options={self.options!r}, pdu_conf={self.pdu_file_directive.pdu_conf})"
)

def __eq__(self, other: MetadataPdu):
def __eq__(self, other: object):
if not isinstance(other, MetadataPdu):
return False

Check warning on line 270 in spacepackets/cfdp/pdu/metadata.py

View check run for this annotation

Codecov / codecov/patch

spacepackets/cfdp/pdu/metadata.py#L270

Added line #L270 was not covered by tests
return (
self.pdu_file_directive == other.pdu_file_directive
and self.params.closure_requested == other.params.closure_requested
Expand Down
13 changes: 9 additions & 4 deletions spacepackets/cfdp/pdu/nak.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,18 @@
data[current_idx : current_idx + struct_arg_tuple[1]],
)[0]
current_idx += struct_arg_tuple[1]
if current_idx < len(data):
packet_size_check = (len(data) - current_idx) % (struct_arg_tuple[1] * 2)
end_of_segment_req_idx = len(data)
if nak_pdu.pdu_header.crc_flag == CrcFlag.WITH_CRC:
end_of_segment_req_idx -= 2
if current_idx < end_of_segment_req_idx:
packet_size_check = (end_of_segment_req_idx - current_idx) % (struct_arg_tuple[1] * 2)
if packet_size_check != 0:
raise ValueError(
"Invalid size for remaining data, "
f"which should be a multiple of {struct_arg_tuple[1] * 2}"
)
segment_requests = []
while current_idx < len(data):
while current_idx < end_of_segment_req_idx:
start_of_segment = struct.unpack(
struct_arg_tuple[0],
data[current_idx : current_idx + struct_arg_tuple[1]],
Expand All @@ -289,7 +292,9 @@
nak_pdu.segment_requests = segment_requests
return nak_pdu

def __eq__(self, other: NakPdu):
def __eq__(self, other: object):
if not isinstance(other, NakPdu):
return False

Check warning on line 297 in spacepackets/cfdp/pdu/nak.py

View check run for this annotation

Codecov / codecov/patch

spacepackets/cfdp/pdu/nak.py#L297

Added line #L297 was not covered by tests
return (
self.pdu_file_directive == other.pdu_file_directive
and self._segment_requests == other._segment_requests
Expand Down
4 changes: 3 additions & 1 deletion spacepackets/cfdp/pdu/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@
prompt_pdu.response_required = ResponseRequired((data[current_idx] & 0x80) >> 7)
return prompt_pdu

def __eq__(self, other: PromptPdu):
def __eq__(self, other: object):
if not isinstance(other, PromptPdu):
return False

Check warning on line 88 in spacepackets/cfdp/pdu/prompt.py

View check run for this annotation

Codecov / codecov/patch

spacepackets/cfdp/pdu/prompt.py#L88

Added line #L88 was not covered by tests
return (
self.pdu_file_directive == other.pdu_file_directive
and self.response_required == other.response_required
Expand Down
1 change: 1 addition & 0 deletions tests/cfdp/pdus/test_eof_pdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ def test_with_crc(self):
# verify we can round-trip pack/unpack
eof_unpacked = EofPdu.unpack(data=eof_raw)
self.assertEqual(eof_unpacked, eof)
self.assertEqual(eof_unpacked.pack(), eof.pack())

def test_from_factory(self):
eof_pdu_raw = self.eof_pdu.pack()
Expand Down
1 change: 1 addition & 0 deletions tests/cfdp/pdus/test_file_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def test_pack_unpack_w_crc(self):
self.assertEqual(packed, expected_bytes)
unpacked = FileDataPdu.unpack(packed)
self.assertEqual(unpacked, fd_pdu)
self.assertEqual(unpacked.pack(), fd_pdu.pack())

def test_with_seg_metadata(self):
fd_params = FileDataParams(
Expand Down
31 changes: 24 additions & 7 deletions tests/cfdp/pdus/test_finished_pdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
TlvType,
)
from spacepackets.cfdp.conf import PduConfig
from spacepackets.cfdp.defs import DeliveryCode, Direction, FileStatus
from spacepackets.cfdp.defs import CrcFlag, DeliveryCode, Direction, FileStatus
from spacepackets.cfdp.pdu import FinishedPdu
from spacepackets.cfdp.pdu.finished import (
FinishedParams,
Expand Down Expand Up @@ -73,7 +73,7 @@ def test_unpack_basic(self):
pdu_conf=self.pdu_conf,
)
finish_pdu_raw = finish_pdu.pack()
finish_pdu_unpacked = FinishedPdu.unpack(data=finish_pdu_raw)
finish_pdu_unpacked = FinishedPdu.unpack(data=bytes(finish_pdu_raw))
self.assertEqual(finish_pdu_unpacked.finished_params, self.params)
self.assertEqual(finish_pdu_unpacked.delivery_code, DeliveryCode.DATA_COMPLETE)
self.assertEqual(finish_pdu_unpacked.direction, Direction.TOWARDS_SENDER)
Expand All @@ -93,7 +93,7 @@ def test_unpack_failure(self):
pdu_conf=self.pdu_conf,
)
finish_pdu_raw = finish_pdu.pack()
finish_pdu_unpacked = FinishedPdu.unpack(data=finish_pdu_raw)
finish_pdu_unpacked = FinishedPdu.unpack(bytes(finish_pdu_raw))
finish_pdu_repacked = finish_pdu_unpacked.pack()
finish_pdu_repacked = finish_pdu_repacked[:-1]
self.assertRaises(ValueError, FinishedPdu.unpack, data=finish_pdu_repacked)
Expand All @@ -104,7 +104,7 @@ def test_unpack_failure(self):
finish_pdu_raw[1] = (current_size & 0xFF00) >> 8
finish_pdu_raw[2] = current_size & 0x00FF
with self.assertRaises(ValueError):
FinishedPdu.unpack(data=finish_pdu_raw)
FinishedPdu.unpack(bytes(finish_pdu_raw))

def test_with_fault_location(self):
# Now generate a packet with a fault location
Expand Down Expand Up @@ -134,6 +134,23 @@ def test_with_fault_location(self):
self.assertEqual(len(finish_pdu_with_fault_loc.pack()), 13)
self.assertEqual(finish_pdu_with_fault_loc.fault_location_len, 4)

def test_roundtrip_with_fault_location_and_crc(self):
params = FinishedParams(
delivery_code=DeliveryCode.DATA_INCOMPLETE,
file_status=FileStatus.DISCARDED_DELIBERATELY,
condition_code=ConditionCode.POSITIVE_ACK_LIMIT_REACHED,
fault_location=self.fault_location_tlv,
)
self.pdu_conf.crc_flag = CrcFlag.WITH_CRC
finish_pdu_with_fault_loc = FinishedPdu(
params=params,
pdu_conf=self.pdu_conf,
)
fd_raw = finish_pdu_with_fault_loc.pack()
finish_pdu_unpack = FinishedPdu.unpack(bytes(fd_raw))
self.assertEqual(finish_pdu_unpack, finish_pdu_with_fault_loc)
self.assertEqual(finish_pdu_unpack.pack(), finish_pdu_with_fault_loc.pack())

def test_with_fs_response(self):
# Now create a packet with filestore responses
filestore_response_1_packed = self.filestore_reponse_1.pack()
Expand Down Expand Up @@ -170,7 +187,7 @@ def test_with_fs_response(self):
expected_array = bytearray([0x28, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x05, 0x44])
expected_array.extend(filestore_response_1_packed)
self.assertEqual(expected_array, pdu_with_response_raw)
pdu_with_response_unpacked = FinishedPdu.unpack(data=pdu_with_response_raw)
pdu_with_response_unpacked = FinishedPdu.unpack(bytes(pdu_with_response_raw))
self.assertEqual(len(pdu_with_response_unpacked.file_store_responses), 1)

def test_finished_pdu(self):
Expand Down Expand Up @@ -207,15 +224,15 @@ def test_finished_pdu(self):
fs_responses = finish_pdu_two_responses_one_fault_loc.file_store_responses
self.assertEqual(len(fs_responses), 2)
complex_pdu_raw = finish_pdu_two_responses_one_fault_loc.pack()
complex_pdu_unpacked = FinishedPdu.unpack(data=complex_pdu_raw)
complex_pdu_unpacked = FinishedPdu.unpack(bytes(complex_pdu_raw))
self.assertEqual(complex_pdu_unpacked.fault_location.pack(), self.fault_location_tlv.pack())
self.assertEqual(self.filestore_reponse_1.pack(), fs_responses[0].pack())
self.assertEqual(filestore_reponse_2.pack(), fs_responses[1].pack())

# Change TLV type to make it invalid
complex_pdu_raw[-5] = TlvType.FILESTORE_RESPONSE
with self.assertRaises(ValueError):
FinishedPdu.unpack(data=complex_pdu_raw)
FinishedPdu.unpack(bytes(complex_pdu_raw))

def test_from_factory(self):
finish_pdu = FinishedPdu.success_pdu(pdu_conf=self.pdu_conf)
Expand Down
2 changes: 1 addition & 1 deletion tests/cfdp/pdus/test_keep_alive_pdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_keep_alive_pdu(self):
keep_alive_pdu_large = KeepAlivePdu(pdu_conf=self.pdu_conf, progress=0)
keep_alive_pdu_invalid = keep_alive_pdu_large.pack()[:-1]
with self.assertRaises(ValueError):
KeepAlivePdu.unpack(data=keep_alive_pdu_invalid)
KeepAlivePdu.unpack(bytes(keep_alive_pdu_invalid))

def test_unpack(self):
keep_alive_pdu_raw = self.keep_alive_pdu.pack()
Expand Down
1 change: 1 addition & 0 deletions tests/cfdp/pdus/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def test_metadata_with_crc(self):
# verify a round-trip works w/ CRC set.
metadata_unpacked = MetadataPdu.unpack(metadata_raw)
self.assertEqual(metadata_pdu, metadata_unpacked)
self.assertEqual(metadata_pdu.pack(), metadata_unpacked.pack())

def test_metadata_pdu(self):
self.assertEqual(self.option_0.packet_len, 13)
Expand Down
11 changes: 11 additions & 0 deletions tests/cfdp/pdus/test_nak_pdu.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,17 @@ def test_with_file_segments_1(self):
nak_unpacked = NakPdu.unpack(data=nak_packed)
self.assertEqual(nak_unpacked.pack(), nak_packed)

def test_roundtrip_with_file_segments_and_crc(self):
self.nak_pdu.start_of_scope = 0
self.nak_pdu.end_of_scope = 200
segment_requests = [(20, 40), (60, 80)]
self.nak_pdu.pdu_header.crc_flag = CrcFlag.WITH_CRC
self.nak_pdu.segment_requests = segment_requests
nak_raw = self.nak_pdu.pack()
nak_unpacked = NakPdu.unpack(nak_raw)
self.assertEqual(nak_unpacked, self.nak_pdu)
self.assertEqual(nak_unpacked.pack(), self.nak_pdu.pack())

def test_nak_pdu_errors(self):
self.nak_pdu.start_of_scope = 0
self.nak_pdu.end_of_scope = 200
Expand Down