Skip to content

Commit

Permalink
Merge pull request #34 from sgaisser/source_handler_check
Browse files Browse the repository at this point in the history
Added test with restricted filestore
  • Loading branch information
robamu authored Dec 20, 2024
2 parents 994c066 + 72a456a commit 6a557f7
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Correction for `InvalidDestinationId` exception arguments in destination handler.
- Destination handler now only checks entity ID values when checking inserted packets.
- Source handler used an incorrect check if the file exists without the virtual filestore.
- Source handler opened files without the virtual filestore

# [v0.4.0] 2024-11-08

Expand Down
25 changes: 13 additions & 12 deletions src/cfdppy/handler/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,8 @@ def _prepare_metadata_pdu(self) -> None:
def _prepare_metadata_base_params_with_metadata(self) -> MetadataParams:
assert self._params.remote_cfg is not None
return MetadataParams(
dest_file_name=self._put_req.dest_file.as_posix(), # type: ignore
source_file_name=self._put_req.source_file.as_posix(), # type: ignore
dest_file_name=self._put_req.dest_file.as_posix(),
source_file_name=self._put_req.source_file.as_posix(),
checksum_type=self._params.remote_cfg.crc_type,
closure_requested=self._params.closure_requested,
file_size=self._params.fp.file_size,
Expand Down Expand Up @@ -900,16 +900,17 @@ def _prepare_file_data_pdu(self, offset: int, read_len: int) -> None:
re-transmit file data PDUs of segments which were already sent."""
assert self._put_req is not None
assert self._put_req.source_file is not None
with open(self._put_req.source_file, "rb") as of:
file_data = self.user.vfs.read_from_opened_file(of, offset, read_len)
# TODO: Support for record continuation state not implemented yet. Segment metadata
# flag is therefore always set to False. Segment metadata support also omitted
# for now. Implementing those generically could be done in form of a callback,
# e.g. abstractmethod of this handler as a first way, another one being
# to expect the user to supply some helper class to split up a file
fd_params = FileDataParams(file_data=file_data, offset=offset, segment_metadata=None)
file_data_pdu = FileDataPdu(pdu_conf=self._params.pdu_conf, params=fd_params)
self._add_packet_to_be_sent(file_data_pdu)
file_data = self.user.vfs.read_data(
file=self._put_req.source_file, offset=offset, read_len=read_len
)
# TODO: Support for record continuation state not implemented yet. Segment metadata
# flag is therefore always set to False. Segment metadata support also omitted
# for now. Implementing those generically could be done in form of a callback,
# e.g. abstractmethod of this handler as a first way, another one being
# to expect the user to supply some helper class to split up a file
fd_params = FileDataParams(file_data=file_data, offset=offset, segment_metadata=None)
file_data_pdu = FileDataPdu(pdu_conf=self._params.pdu_conf, params=fd_params)
self._add_packet_to_be_sent(file_data_pdu)

def _prepare_eof_pdu(self, checksum: bytes) -> None:
assert self._params.cond_code_eof is not None
Expand Down
26 changes: 16 additions & 10 deletions tests/cfdp_user_mock.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
from spacepackets.cfdp import ConditionCode
from __future__ import annotations # Python 3.9 compatibility for | syntax

from cfdppy import CfdpUserBase, TransactionId
from cfdppy.user import (
FileSegmentRecvdParams,
MetadataRecvParams,
TransactionFinishedParams,
TransactionParams,
)
from typing import TYPE_CHECKING

from cfdppy import CfdpUserBase, TransactionId, VirtualFilestore

if TYPE_CHECKING:
from spacepackets.cfdp import ConditionCode

from cfdppy.user import (
FileSegmentRecvdParams,
MetadataRecvParams,
TransactionFinishedParams,
TransactionParams,
)


class CfdpUser(CfdpUserBase):
def __init__(self):
super().__init__()
def __init__(self, vfs: VirtualFilestore | None = None):
super().__init__(vfs=vfs)

def transaction_indication(self, transaction_params: TransactionParams):
pass
Expand Down
152 changes: 152 additions & 0 deletions tests/test_src_handler_restricted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
from __future__ import annotations # Python 3.9 compatibility for | syntax

import copy
import shutil
import tempfile
from pathlib import Path
from unittest import TestCase
from unittest.mock import MagicMock

from spacepackets.cfdp import (
ChecksumType,
ConditionCode,
CrcFlag,
Direction,
DirectiveType,
FinishedParams,
LargeFileFlag,
PduConfig,
SegmentationControl,
TransmissionMode,
)
from spacepackets.cfdp.pdu import (
AckPdu,
EofPdu,
FileDataPdu,
FinishedPdu,
MetadataPdu,
TransactionStatus,
)
from spacepackets.seqcount import SeqCountProvider
from spacepackets.util import ByteFieldU16

from cfdppy import (
CfdpState,
IndicationCfg,
LocalEntityCfg,
PutRequest,
RemoteEntityCfg,
RemoteEntityCfgTable,
RestrictedFilestore,
)
from cfdppy.handler import SourceHandler
from tests.cfdp_fault_handler_mock import FaultHandler
from tests.cfdp_user_mock import CfdpUser
from tests.common import CheckTimerProviderForTest


class TestSrcHandlerRestrictedFileStore(TestCase):
def setUp(self):
super().setUp()
self.temp_dir = Path(tempfile.mkdtemp())
self.closure_requested = False
self.indication_cfg = IndicationCfg(True, True, True, True, True, True)
self.fault_handler = MagicMock()
self.fault_handler.mock_add_spec(spec=FaultHandler, spec_set=True)
self.local_cfg = LocalEntityCfg(ByteFieldU16(1), self.indication_cfg, self.fault_handler)
self.cfdp_user = CfdpUser(vfs=RestrictedFilestore(self.temp_dir))
self.seq_num_provider = SeqCountProvider(bit_width=8)
self.expected_seq_num = 0
self.source_id = ByteFieldU16(1)
self.dest_id = ByteFieldU16(2)
self.alternative_dest_id = ByteFieldU16(3)
self.file_segment_len = 64
self.max_packet_len = 256
self.positive_ack_intvl_seconds = 0.02
self.default_remote_cfg = RemoteEntityCfg(
entity_id=self.dest_id,
max_packet_len=self.max_packet_len,
max_file_segment_len=self.file_segment_len,
closure_requested=self.closure_requested,
crc_on_transmission=False,
default_transmission_mode=TransmissionMode.ACKNOWLEDGED,
positive_ack_timer_interval_seconds=self.positive_ack_intvl_seconds,
positive_ack_timer_expiration_limit=2,
crc_type=ChecksumType.CRC_32,
check_limit=2,
)
self.alternative_remote_cfg = copy.copy(self.default_remote_cfg)
self.alternative_remote_cfg.entity_id = self.alternative_dest_id
self.remote_cfg_table = RemoteEntityCfgTable()
self.remote_cfg_table.add_config(self.default_remote_cfg)
self.remote_cfg_table.add_config(self.alternative_remote_cfg)
# Create an empty file and send it via CFDP
self.source_handler = SourceHandler(
cfg=self.local_cfg,
user=self.cfdp_user,
remote_cfg_table=self.remote_cfg_table,
seq_num_provider=self.seq_num_provider,
check_timer_provider=CheckTimerProviderForTest(),
)

def tearDown(self):
shutil.rmtree(self.temp_dir)

def test_src_handler_restricted(self):
file_content = "Hello, World!"
with open(self.temp_dir.joinpath("hello.txt"), "w") as f:
f.write(file_content)
source_path = Path("hello.txt")
dest_path = Path("hello_copy.txt")
self.seq_num_provider.get_and_increment = MagicMock(return_value=self.expected_seq_num)
self.source_handler.entity_id = self.source_id
put_req = PutRequest(
destination_id=self.dest_id,
source_file=source_path,
dest_file=dest_path,
# Let the transmission mode be auto-determined by the remote MIB
trans_mode=TransmissionMode.ACKNOWLEDGED,
closure_requested=True,
)
self.source_handler.put_request(put_req)

fsm = self.source_handler.state_machine()
self.assertTrue(fsm.states.packets_ready)
self.assertEqual(fsm.states.num_packets_ready, 1)
next_pdu = self.source_handler.get_next_packet()
self.assertIsInstance(next_pdu.base, MetadataPdu)
fsm = self.source_handler.state_machine()
self.assertTrue(fsm.states.packets_ready)
file_data = self.source_handler.get_next_packet()
self.assertIsInstance(file_data.base, FileDataPdu)
fsm = self.source_handler.state_machine()
self.assertTrue(fsm.states.packets_ready)
eof_data = self.source_handler.get_next_packet()
self.assertIsInstance(eof_data.base, EofPdu)
# Send ACK
pdu_conf = PduConfig(
direction=Direction.TOWARDS_SENDER,
transaction_seq_num=eof_data.base.transaction_seq_num,
source_entity_id=self.source_id,
dest_entity_id=self.dest_id,
trans_mode=TransmissionMode.ACKNOWLEDGED,
file_flag=LargeFileFlag.NORMAL,
crc_flag=CrcFlag.NO_CRC,
seg_ctrl=SegmentationControl.NO_RECORD_BOUNDARIES_PRESERVATION,
)
eof_ack = AckPdu(
pdu_conf=pdu_conf,
directive_code_of_acked_pdu=DirectiveType.EOF_PDU,
condition_code_of_acked_pdu=ConditionCode.NO_ERROR,
transaction_status=TransactionStatus.ACTIVE,
)
fsm = self.source_handler.state_machine(packet=eof_ack)
self.assertFalse(fsm.states.packets_ready)

finished = FinishedPdu(pdu_conf=pdu_conf, params=FinishedParams.success_params())
fsm = self.source_handler.state_machine(packet=finished)
self.assertTrue(fsm.states.packets_ready)
finished_ack = self.source_handler.get_next_packet()
self.assertIsInstance(finished_ack.base, AckPdu)
fsm = self.source_handler.state_machine()
self.assertEqual(fsm.states.state, CfdpState.IDLE)

0 comments on commit 6a557f7

Please sign in to comment.