Skip to content

Commit d907217

Browse files
authored
Merge pull request #39 from us-irs/optimizations
Optimizations
2 parents d8e2954 + 9d0388b commit d907217

File tree

9 files changed

+103
-98
lines changed

9 files changed

+103
-98
lines changed

examples/cfdp-libre-cube-crosstest/tmtccmd-client.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from logging import basicConfig
1515
from pathlib import Path
1616
from queue import Empty
17-
from typing import Any, cast
17+
from typing import TYPE_CHECKING, Any, cast
1818

1919
from common import REMOTE_CFG_FOR_DEST_ENTITY, UDP_SERVER_PORT, UDP_TM_SERVER_PORT
2020
from common import REMOTE_ENTITY_ID as REMOTE_ENTITY_ID_RAW
@@ -24,7 +24,6 @@
2424
TransactionId,
2525
TransmissionMode,
2626
)
27-
from spacepackets.cfdp.pdu import AbstractFileDirectiveBase
2827
from spacepackets.cfdp.pdu.helper import PduFactory
2928
from spacepackets.countdown import Countdown
3029
from spacepackets.seqcount import SeqCountProvider
@@ -50,6 +49,9 @@
5049
TransactionParams,
5150
)
5251

52+
if TYPE_CHECKING:
53+
from spacepackets.cfdp.pdu import AbstractFileDirectiveBase
54+
5355
SOURCE_ENTITY_ID = ByteFieldU16(SOURCE_ENTITY_ID_RAW)
5456
DEST_ENTITY_ID = ByteFieldU16(REMOTE_ENTITY_ID_RAW)
5557

@@ -292,7 +294,7 @@ def source_entity_handler(
292294
ready = select.select([tm_client], [], [], 0)
293295
if ready[0]:
294296
data, _ = tm_client.recvfrom(4096)
295-
packet = cast(AbstractFileDirectiveBase, PduFactory.from_raw(data))
297+
packet = cast("AbstractFileDirectiveBase", PduFactory.from_raw(data))
296298
packet_received = True
297299
except Empty:
298300
pass

justfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Run with `just all`
2+
all: fmt check test
3+
4+
fmt:
5+
ruff format
6+
7+
check:
8+
ruff check
9+
10+
test:
11+
pytest

src/cfdppy/handler/dest.py

Lines changed: 46 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ class CompletionDisposition(enum.Enum):
8181
@dataclass
8282
class _DestFileParams(_FileParamsBase):
8383
file_name: Path
84-
file_size_eof: int | None
8584

8685
@classmethod
8786
def empty(cls) -> _DestFileParams:
@@ -91,38 +90,41 @@ def empty(cls) -> _DestFileParams:
9190
crc32=b"",
9291
file_size=None,
9392
file_name=Path(),
94-
file_size_eof=None,
93+
# file_size_eof=None,
9594
metadata_only=False,
9695
)
9796

9897
def reset(self) -> None:
9998
super().reset()
10099
self.file_name = Path()
101-
self.file_size_eof = None
102100

103101

104102
class TransactionStep(enum.Enum):
105103
IDLE = 0
106104
TRANSACTION_START = 1
107105
"""Metadata was received, which triggered a transaction start."""
106+
108107
WAITING_FOR_METADATA = 2
109108
"""Special state which is only used for acknowledged mode. The CFDP entity is still waiting
110109
for a missing metadata PDU to be re-sent. Until then, all arriving file data PDUs will only
111110
update the internal lost segment tracker. When the EOF PDU arrives, the state will be left.
112111
Please note that deferred lost segment handling might also be active when this state is set."""
112+
113113
RECEIVING_FILE_DATA = 3
114+
114115
RECV_FILE_DATA_WITH_CHECK_LIMIT_HANDLING = 4
115116
"""This is the check timer step as specified in chapter 4.6.3.3 b) of the standard.
116117
The destination handler will still check for file data PDUs which might lead to a full
117118
file transfer completion."""
118-
SENDING_EOF_ACK_PDU = 5
119-
"""Sending the ACK (EOF) packet."""
119+
120120
WAITING_FOR_MISSING_DATA = 6
121121
"""Only relevant for acknowledged mode: Wait for lost metadata and file segments as part of
122122
the deferred lost segments detection procedure."""
123+
123124
TRANSFER_COMPLETION = 7
124125
"""File transfer complete. Perform checksum verification and notice of completion. Please
125126
note that this does not necessarily mean that the file transfer was completed successfully."""
127+
126128
SENDING_FINISHED_PDU = 8
127129
WAITING_FOR_FINISHED_ACK = 9
128130

@@ -162,6 +164,7 @@ def coalesce_lost_segments(self) -> None:
162164
if len(self.lost_segments) <= 1:
163165
return
164166
merged_segments = []
167+
# Initialize to the first entry.
165168
current_start, current_end = next(iter(self.lost_segments.items()))
166169

167170
for seg_start, seg_end in self.lost_segments.items():
@@ -220,6 +223,8 @@ def remove_lost_segment(self, segment_to_remove: tuple[int, int]) -> bool:
220223
@dataclass
221224
class _AckedModeParams:
222225
lost_seg_tracker: LostSegmentTracker = field(default=LostSegmentTracker())
226+
# Extra parameter: Missing metadata is not tracked inside the lost segment tracker, so we
227+
# need an extra parameter for this.
223228
metadata_missing: bool = False
224229
last_start_offset: int = 0
225230
last_end_offset: int = 0
@@ -520,11 +525,11 @@ def __idle_fsm(self, packet: GenericPduPacket | None) -> None:
520525
self._start_transaction(metadata_pdu)
521526
else:
522527
raise ValueError(
523-
f"unexpected configuration error: {pdu_holder.pdu} in " f"IDLE state machine"
528+
f"unexpected configuration error: {pdu_holder.pdu} in IDLE state machine"
524529
)
525530

526531
def __non_idle_fsm(self, packet: GenericPduPacket | None) -> None:
527-
self._fsm_advancement_after_packets_were_sent()
532+
self._assert_all_packets_were_sent()
528533
pdu_holder = PduHolder(packet)
529534
if (
530535
self.states.step
@@ -554,20 +559,10 @@ def __non_idle_fsm(self, packet: GenericPduPacket | None) -> None:
554559
if self.states.step == TransactionStep.WAITING_FOR_FINISHED_ACK:
555560
self._handle_waiting_for_finished_ack(pdu_holder)
556561

557-
def _fsm_advancement_after_packets_were_sent(self) -> None:
562+
def _assert_all_packets_were_sent(self) -> None:
558563
"""Advance the internal FSM after all packets to be sent were retrieved from the handler."""
559564
if len(self._pdus_to_be_sent) > 0:
560565
raise UnretrievedPdusToBeSent(f"{len(self._pdus_to_be_sent)} packets left to send")
561-
if self.states.step == TransactionStep.SENDING_EOF_ACK_PDU:
562-
if (
563-
self._params.acked_params.lost_seg_tracker.num_lost_segments > 0
564-
or self._params.acked_params.metadata_missing
565-
):
566-
self._start_deferred_lost_segment_handling()
567-
else:
568-
if self._params.completion_disposition != CompletionDisposition.CANCELED:
569-
self._checksum_verify()
570-
self.states.step = TransactionStep.TRANSFER_COMPLETION
571566

572567
def _start_transaction(self, metadata_pdu: MetadataPdu) -> bool:
573568
if self.states.state != CfdpState.IDLE:
@@ -595,7 +590,7 @@ def _start_transaction_missing_metadata_recv_eof(self, eof_pdu: EofPdu) -> None:
595590

596591
def _handle_eof_without_previous_metadata(self, eof_pdu: EofPdu) -> None:
597592
self._params.fp.progress = eof_pdu.file_size
598-
self._params.fp.file_size_eof = eof_pdu.file_size
593+
self._params.fp.file_size = eof_pdu.file_size
599594
self._params.acked_params.metadata_missing = True
600595
if self._params.fp.progress > 0:
601596
# Clear old list, deferred procedure for the whole file is now active.
@@ -609,11 +604,23 @@ def _handle_eof_without_previous_metadata(self, eof_pdu: EofPdu) -> None:
609604
assert self._params.transaction_id is not None
610605
self.user.eof_recv_indication(self._params.transaction_id)
611606
self._prepare_eof_ack_packet()
612-
self.states.step = TransactionStep.SENDING_EOF_ACK_PDU
607+
self._eof_ack_pdu_done()
608+
609+
def _eof_ack_pdu_done(self) -> None:
610+
if (
611+
self._params.acked_params.lost_seg_tracker.num_lost_segments > 0
612+
or self._params.acked_params.metadata_missing
613+
):
614+
self._start_deferred_lost_segment_handling()
615+
616+
else:
617+
if self._params.completion_disposition != CompletionDisposition.CANCELED:
618+
self._checksum_verify()
619+
self.states.step = TransactionStep.TRANSFER_COMPLETION
613620

614621
def _start_transaction_missing_metadata_recv_fd(self, fd_pdu: FileDataPdu) -> None:
615622
self._common_first_packet_not_metadata_pdu_handler(fd_pdu)
616-
self._handle_fd_without_previous_metadata(True, fd_pdu)
623+
self._handle_fd_without_previous_metadata(fd_pdu)
617624

618625
def _handle_finished_pdu_sent(self) -> None:
619626
if (
@@ -625,19 +632,16 @@ def _handle_finished_pdu_sent(self) -> None:
625632
return
626633
self._reset_internal(False)
627634

628-
def _handle_fd_without_previous_metadata(self, first_pdu: bool, fd_pdu: FileDataPdu) -> None:
635+
def _handle_fd_without_previous_metadata(self, fd_pdu: FileDataPdu) -> None:
629636
self._params.fp.progress = fd_pdu.offset + len(fd_pdu.file_data)
630637
if len(fd_pdu.file_data) > 0:
631-
start = fd_pdu.offset
632-
if first_pdu:
633-
start = 0
634638
# I will just wait until the metadata has been received with re-requesting the file
635639
# data PDU. How does the standard expect me to process file data PDUs where I do not
636640
# even know the filenames? How would I even generically do this?
637641
# I will add this file segment (and all others which came before and might be missing
638642
# as well) to the lost segment list.
639643
self._params.acked_params.lost_seg_tracker.add_lost_segment(
640-
(start, self._params.fp.progress)
644+
(0, self._params.fp.progress)
641645
)
642646
# This is a bit tricky: We need to set those variables to an appropriate value so
643647
# the removal of handled lost segments works properly. However, we can not set the
@@ -649,8 +653,7 @@ def _handle_fd_without_previous_metadata(self, first_pdu: bool, fd_pdu: FileData
649653
# Re-request the metadata PDU.
650654
if self._params.remote_cfg.immediate_nak_mode:
651655
lost_segments: list[tuple[int, int]] = []
652-
if first_pdu:
653-
lost_segments.append((0, 0))
656+
lost_segments.append((0, 0))
654657
if len(fd_pdu.file_data) > 0:
655658
lost_segments.append((0, self._params.fp.progress))
656659
if len(lost_segments) > 0:
@@ -698,7 +701,7 @@ def _handle_metadata_packet(self, metadata_pdu: MetadataPdu) -> None:
698701
# sender.
699702
if self._params.remote_cfg is None:
700703
_LOGGER.warning(
701-
"No remote configuration found for remote ID" f" {metadata_pdu.dest_entity_id}"
704+
f"No remote configuration found for remote ID {metadata_pdu.dest_entity_id}"
702705
)
703706
raise NoRemoteEntityCfgFound(metadata_pdu.dest_entity_id)
704707
if not self._params.fp.metadata_only:
@@ -753,7 +756,7 @@ def _handle_waiting_for_missing_metadata(self, packet_holder: PduHolder) -> None
753756
if packet_holder.pdu is None:
754757
return
755758
if packet_holder.pdu_type == PduType.FILE_DATA:
756-
self._handle_fd_without_previous_metadata(True, packet_holder.to_file_data_pdu())
759+
self._handle_fd_without_previous_metadata(packet_holder.to_file_data_pdu())
757760
elif packet_holder.pdu_directive_type == DirectiveType.METADATA_PDU:
758761
self._handle_metadata_packet(packet_holder.to_metadata_pdu())
759762
if self._params.acked_params.deferred_lost_segment_detection_active:
@@ -832,8 +835,8 @@ def _handle_fd_pdu(self, file_data_pdu: FileDataPdu) -> None:
832835
self._params.finished_params.file_status = FileStatus.FILE_RETAINED
833836

834837
if (
835-
self._params.fp.file_size_eof is not None
836-
and (offset + len(file_data_pdu.file_data) > self._params.fp.file_size_eof)
838+
self._params.fp.file_size is not None
839+
and (offset + len(file_data_pdu.file_data) > self._params.fp.file_size)
837840
and (
838841
self._declare_fault(ConditionCode.FILE_SIZE_ERROR)
839842
!= FaultHandlerCode.IGNORE_ERROR
@@ -895,7 +898,7 @@ def _deferred_lost_segment_handling(self) -> None:
895898
if not self._params.acked_params.deferred_lost_segment_detection_active:
896899
return
897900
assert self._params.remote_cfg is not None
898-
assert self._params.fp.file_size_eof is not None
901+
assert self._params.fp.file_size is not None
899902
if (
900903
self._params.acked_params.lost_seg_tracker.num_lost_segments == 0
901904
and not self._params.acked_params.metadata_missing
@@ -941,7 +944,7 @@ def _deferred_lost_segment_handling(self) -> None:
941944
NakPdu(
942945
self._params.pdu_conf,
943946
0,
944-
self._params.fp.file_size_eof,
947+
self._params.fp.file_size,
945948
next_segment_reqs,
946949
)
947950
)
@@ -951,7 +954,7 @@ def _deferred_lost_segment_handling(self) -> None:
951954
NakPdu(
952955
self._params.pdu_conf,
953956
0,
954-
self._params.fp.file_size_eof,
957+
self._params.fp.file_size,
955958
next_segment_reqs,
956959
)
957960
)
@@ -962,7 +965,7 @@ def _deferred_lost_segment_handling(self) -> None:
962965
def _handle_eof_pdu(self, eof_pdu: EofPdu) -> bool | None:
963966
"""Returns whether to exit the FSM prematurely."""
964967
self._params.fp.crc32 = eof_pdu.file_checksum
965-
self._params.fp.file_size_eof = eof_pdu.file_size
968+
self._params.fp.file_size = eof_pdu.file_size
966969
if self.cfg.indication_cfg.eof_recv_indication_required:
967970
assert self._params.transaction_id is not None
968971
self.user.eof_recv_indication(self._params.transaction_id)
@@ -978,30 +981,26 @@ def _handle_eof_pdu(self, eof_pdu: EofPdu) -> bool | None:
978981
EntityIdTlv(self._params.remote_cfg.entity_id.as_bytes),
979982
)
980983
# Store this as progress for the checksum calculation.
981-
self._params.fp.progress = self._params.fp.file_size_eof
984+
self._params.fp.progress = self._params.fp.file_size
982985
self._params.finished_params.delivery_code = DeliveryCode.DATA_INCOMPLETE
983986
self._file_transfer_complete_transition()
984987
return False
985988

986989
def _handle_no_error_eof(self) -> bool:
987990
"""Returns whether the transfer can be completed regularly."""
988991
# CFDP 4.6.1.2.9: Declare file size error if progress exceeds file size
989-
if self._params.fp.progress > self._params.fp.file_size_eof: # type: ignore
992+
if self._params.fp.progress > self._params.fp.file_size: # type: ignore
990993
if self._declare_fault(ConditionCode.FILE_SIZE_ERROR) != FaultHandlerCode.IGNORE_ERROR:
991994
return False
992995
elif (
993-
self._params.fp.progress < self._params.fp.file_size_eof # type: ignore
996+
self._params.fp.progress < self._params.fp.file_size # type: ignore
994997
) and self.transmission_mode == TransmissionMode.ACKNOWLEDGED:
995998
# CFDP 4.6.4.3.1: The end offset of the last received file segment and the file
996999
# size as stated in the EOF PDU is not the same, so we need to add that segment to
9971000
# the lost segments for the deferred lost segment detection procedure.
9981001
self._params.acked_params.lost_seg_tracker.add_lost_segment(
999-
(self._params.fp.progress, self._params.fp.file_size_eof) # type: ignore
1002+
(self._params.fp.progress, self._params.fp.file_size) # type: ignore
10001003
)
1001-
if self._params.fp.file_size_eof != self._params.fp.file_size:
1002-
# Can or should this ever happen for a No Error EOF? Treat this like a non-fatal
1003-
# error for now.
1004-
_LOGGER.warning("missmatch of EOF file size and Metadata File Size for success EOF")
10051004
if (
10061005
self.transmission_mode == TransmissionMode.UNACKNOWLEDGED
10071006
and not self._checksum_verify()
@@ -1022,8 +1021,8 @@ def _start_deferred_lost_segment_handling(self) -> None:
10221021
self.states.step = TransactionStep.WAITING_FOR_MISSING_DATA
10231022
self._params.acked_params.deferred_lost_segment_detection_active = True
10241023
self._params.acked_params.lost_seg_tracker.coalesce_lost_segments()
1025-
self._params.acked_params.last_start_offset = self._params.fp.file_size_eof # type: ignore
1026-
self._params.acked_params.last_end_offset = self._params.fp.file_size_eof # type: ignore
1024+
self._params.acked_params.last_start_offset = self._params.fp.file_size # type: ignore
1025+
self._params.acked_params.last_end_offset = self._params.fp.file_size # type: ignore
10271026
self._deferred_lost_segment_handling()
10281027

10291028
def _prepare_eof_ack_packet(self) -> None:
@@ -1062,7 +1061,7 @@ def _file_transfer_complete_transition(self) -> None:
10621061
self.states.step = TransactionStep.TRANSFER_COMPLETION
10631062
elif self.transmission_mode == TransmissionMode.ACKNOWLEDGED:
10641063
self._prepare_eof_ack_packet()
1065-
self.states.step = TransactionStep.SENDING_EOF_ACK_PDU
1064+
self._eof_ack_pdu_done()
10661065

10671066
def _trigger_notice_of_completion_canceled(
10681067
self, condition_code: ConditionCode, fault_location: EntityIdTlv
@@ -1102,8 +1101,6 @@ def _notice_of_completion(self) -> None:
11021101
self.user.transaction_finished_indication(finished_indic_params)
11031102

11041103
def _prepare_finished_pdu(self) -> None:
1105-
if self.states.packets_ready:
1106-
raise UnretrievedPdusToBeSent
11071104
# TODO: Fault location handling. Set remote entity ID for file copy
11081105
# operations cancelled with an EOF (Cancel) PDU, and the local ID for file
11091106
# copy operations cancelled with the local API.

src/cfdppy/handler/source.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,14 @@
8080
class TransactionStep(enum.Enum):
8181
IDLE = 0
8282
TRANSACTION_START = 1
83+
8384
# The following three are used for the Copy File Procedure
8485
SENDING_METADATA = 3
8586
SENDING_FILE_DATA = 4
87+
8688
RETRANSMITTING = 5
8789
"""Re-transmitting missing packets in acknowledged mode."""
90+
8891
SENDING_EOF = 6
8992
WAITING_FOR_EOF_ACK = 7
9093
WAITING_FOR_FINISHED = 8
@@ -886,12 +889,9 @@ def _prepare_progressing_file_data_pdu(self) -> None:
886889
:return: True if a packet was prepared, False if PDU handling is done and the next steps
887890
in the Copy File procedure can be performed
888891
"""
889-
if self._params.fp.file_size < self._params.fp.segment_len:
890-
read_len = self._params.fp.file_size
891-
elif self._params.fp.progress + self._params.fp.segment_len > self._params.fp.file_size:
892-
read_len = self._params.fp.file_size - self._params.fp.progress
893-
else:
894-
read_len = self._params.fp.segment_len
892+
read_len = min(
893+
self._params.fp.segment_len, self._params.fp.file_size - self._params.fp.progress
894+
)
895895
self._prepare_file_data_pdu(self._params.fp.progress, read_len)
896896
self._params.fp.progress += read_len
897897

@@ -930,7 +930,7 @@ def _get_next_transfer_seq_num(self) -> None:
930930
next_seq_num = self.seq_num_provider.get_and_increment()
931931
if self.seq_num_provider.max_bit_width not in [8, 16, 32]:
932932
raise ValueError(
933-
"Invalid bit width for sequence number provider, must be one of [8," " 16, 32]"
933+
"Invalid bit width for sequence number provider, must be one of [8, 16, 32]"
934934
)
935935
self._params.pdu_conf.transaction_seq_num = ByteFieldGenerator.from_int(
936936
self.seq_num_provider.max_bit_width // 8, next_seq_num

src/cfdppy/request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def __str_for_metadata_only(self) -> str:
7575
print_str = f"Metadata Only Put Request with Destination ID {self.destination_id.value}"
7676
if self.msgs_to_user is not None:
7777
for idx, msg_to_user in enumerate(self.msgs_to_user):
78-
msg_to_user = cast(MessageToUserTlv, msg_to_user)
78+
msg_to_user = cast("MessageToUserTlv", msg_to_user)
7979
if msg_to_user.is_reserved_cfdp_message():
8080
reserved_msg = msg_to_user.to_reserved_msg_tlv()
8181
assert reserved_msg is not None

0 commit comments

Comments
 (0)