From 4d9418b55f6a13218eed7a1cac28ba81f8b0da8a Mon Sep 17 00:00:00 2001 From: Theo Hill Date: Mon, 20 May 2024 23:17:06 -0700 Subject: [PATCH 1/3] Skip .direnv in pylama This is a virtualenv generated by direnv, pylama will try to scan all the packages in there if it's not ignored. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 714ae71..59cfa33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ line_length = 100 [tool.pylama] format = "pylint" -skip = "*/.tox/*,*/.env/,*/.git/*,*/.github/*,*/build/*" +skip = "*/.tox/*,*/.env/,*/.git/*,*/.github/*,*/build/*,.direnv/*" linters = "pycodestyle,pyflakes,pylint,mccabe,mypy,radon" # E402: Module level import not at top of file # C901: Function is too complex From d05bad4a7c45227f60b03e1e24b432da8f025e57 Mon Sep 17 00:00:00 2001 From: Theo Hill Date: Mon, 20 May 2024 23:19:22 -0700 Subject: [PATCH 2/3] First pass at CFDP Directory listing The only weird bit is that since it's a proxy request it'll send a final acknowledge back, similar to a proxy file transfer. That's not in the spec, but I can't see a way to distinguish between directory and file given the tools available. --- oresat_c3/services/edl.py | 122 +++++++++++++++++++++++++++---------- scripts/edl_file_upload.py | 92 +++++++++++++++++++--------- 2 files changed, 153 insertions(+), 61 deletions(-) diff --git a/oresat_c3/services/edl.py b/oresat_c3/services/edl.py index 16fac09..74d836e 100644 --- a/oresat_c3/services/edl.py +++ b/oresat_c3/services/edl.py @@ -1,5 +1,6 @@ """'EDL Service""" +import os import struct from collections.abc import Iterable from datetime import timedelta @@ -41,8 +42,11 @@ from spacepackets.cfdp.defs import DeliveryCode, FileStatus from spacepackets.cfdp.pdu import AbstractFileDirectiveBase from spacepackets.cfdp.tlv import ( + DirectoryListingResponse, + DirectoryOperationMessageType, FilestoreResponseStatusCode, OriginatingTransactionId, + ProxyMessageType, ProxyPutResponse, ProxyPutResponseParams, ) @@ -392,7 +396,12 @@ def create_directory(self, dir_name: Path) -> FilestoreResponseStatusCode: def list_directory( self, dir_name: Path, target_file: Path, recursive: bool = False ) -> FilestoreResponseStatusCode: - return super().list_directory(self._prefix.joinpath(dir_name), target_file, recursive) + # The upstream implementation added junk to the output and maybe didn't even work? + dir_name = self._prefix.joinpath(dir_name) + with open(target_file, "w") as f: + for line in os.walk(dir_name) if recursive else os.listdir(dir_name): + f.write(f"{line}\n") + return FilestoreResponseStatusCode.SUCCESS class VfsCrcHelper(CrcHelper): @@ -483,6 +492,23 @@ def __init__(self, upload_dir: str, fwrite_cache: OreSatFileCache): path.mkdir(parents=True, exist_ok=True) super().__init__(vfs=PrefixedFilestore(path)) + self.proxy_responses = { # FIXME: defaultdict with invalid response + ProxyMessageType.PUT_REQUEST: self.proxy_put_response, + ProxyMessageType.MSG_TO_USER: self.unimplemented, + ProxyMessageType.FS_REQUEST: self.unimplemented, + ProxyMessageType.FAULT_HANDLER_OVERRIDE: self.unimplemented, + ProxyMessageType.TRANSMISSION_MODE: self.unimplemented, + ProxyMessageType.FLOW_LABEL: self.unimplemented, + ProxyMessageType.SEGMENTATION_CTRL: self.unimplemented, + ProxyMessageType.PUT_RESPONSE: self.unimplemented, + ProxyMessageType.FS_RESPONSE: self.unimplemented, + ProxyMessageType.PUT_CANCEL: self.unimplemented, + ProxyMessageType.CLOSURE_REQUEST: self.unimplemented, + DirectoryOperationMessageType.LISTING_REQUEST: self.directory_listing_response, + DirectoryOperationMessageType.LISTING_RESPONSE: self.unimplemented, + DirectoryOperationMessageType.CUSTOM_LISTING_PARAMETERS: self.unimplemented, + } + SOURCE_ID = ByteFieldU8(0) DEST_ID = ByteFieldU8(1) @@ -601,6 +627,10 @@ def loop(self, pdu: AbstractFileDirectiveBase): logger.info(f"---> {out}") return pdus or None + def unimplemented(self, _source, _tid, _reserved_message) -> PutRequest: + """Default method for responding to unimplemented requests""" + return None + def missing_file_response(self, invalid: PutRequest) -> PutRequest: """Generates a resonse put for when a proxy request tries to access a missing file""" @@ -627,6 +657,58 @@ def missing_file_response(self, invalid: PutRequest) -> PutRequest: ], ) + def proxy_put_response(self, _source, _tid, reserved_message) -> PutRequest: + """Response for a proxy put request""" + params = reserved_message.get_proxy_put_request_params() + return PutRequest( + destination_id=params.dest_entity_id, + source_file=Path(params.source_file_as_path), + dest_file=Path(params.dest_file_as_path), + trans_mode=None, + closure_requested=True, + msgs_to_user=[ + OriginatingTransactionId(params.transaction_id).to_generic_msg_to_user_tlv() + ], + ) + + def directory_listing_response(self, source, tid, reserved_message) -> PutRequest: + """Response for a directory listing request""" + # See CFDP 6.3.4 + params = reserved_message.get_dir_listing_request_params() + self.vfs.list_directory(params.dir_path_as_path, params.dir_file_name_as_path, False) + return PutRequest( + destination_id=source, + source_file=params.dir_file_name_as_path, + dest_file=params.dir_file_name_as_path, + trans_mode=None, + closure_requested=True, + msgs_to_user=[ + DirectoryListingResponse( + listing_success=True, + dir_params=params, + ).to_generic_msg_to_user_tlv(), + OriginatingTransactionId(tid).to_generic_msg_to_user_tlv(), + ], + ) + + def proxy_request_complete(self, originating_id, params) -> PutRequest: + """Indicates that a proxy put request was successful""" + return PutRequest( + destination_id=originating_id.source_id, + source_file=None, + dest_file=None, + trans_mode=None, + # FIXME: upstream bug - DestHandler does not respect closure_requested=None when + # trans_mode defaults to ACKNOWLEGED + closure_requested=True, + msgs_to_user=[ + ProxyPutResponse( + ProxyPutResponseParams.from_finished_params(params.finished_params) + ).to_generic_msg_to_user_tlv(), + OriginatingTransactionId(originating_id).to_generic_msg_to_user_tlv(), + ], + ) + def transaction_indication(self, transaction_indication_params: TransactionParams): logger.info(f"Indication: Transaction. {transaction_indication_params}") t_id = transaction_indication_params.transaction_id @@ -642,44 +724,18 @@ def transaction_finished_indication(self, params: TransactionFinishedParams): if params.transaction_id in self.active_requests: originating_id = self.active_requests.get(params.transaction_id) assert originating_id is not None - put = PutRequest( - destination_id=originating_id.source_id, - source_file=None, - dest_file=None, - trans_mode=None, - # FIXME: upstream bug - DestHandler does not respect closure_requested=None when - # trans_mode defaults to ACKNOWLEGED - closure_requested=True, - msgs_to_user=[ - ProxyPutResponse( - ProxyPutResponseParams.from_finished_params(params.finished_params) - ).to_generic_msg_to_user_tlv(), - OriginatingTransactionId(originating_id).to_generic_msg_to_user_tlv(), - ], - ) + put = self.proxy_request_complete(originating_id, params) self.scheduled_requests.put(put) del self.active_requests[params.transaction_id] def metadata_recv_indication(self, params: MetadataRecvParams): logger.info(f"Indication: Metadata Recv. {params}") for msg in params.msgs_to_user or []: - if msg.is_reserved_cfdp_message(): - reserved = msg.to_reserved_msg_tlv() - if reserved.is_cfdp_proxy_operation(): - proxyparams = reserved.get_proxy_put_request_params() - put = PutRequest( - destination_id=proxyparams.dest_entity_id, - source_file=Path(proxyparams.source_file_as_path), - dest_file=Path(proxyparams.dest_file_as_path), - trans_mode=None, - closure_requested=None, - msgs_to_user=[ - OriginatingTransactionId( - params.transaction_id - ).to_generic_msg_to_user_tlv() - ], - ) - self.scheduled_requests.put(put) + if r := msg.to_reserved_msg_tlv(): # is None if not a reserved TLV message + op = r.get_cfdp_proxy_message_type() or r.get_directory_operation_type() + put = self.proxy_responses[op](params.source_id, params.transaction_id, r) + self.scheduled_requests.put(put) + # Ignore non-reserved messages for now def file_segment_recv_indication(self, params: FileSegmentRecvdParams): logger.info(f"Indication: File Segment Recv. {params}") diff --git a/scripts/edl_file_upload.py b/scripts/edl_file_upload.py index 58754cd..a3f7924 100755 --- a/scripts/edl_file_upload.py +++ b/scripts/edl_file_upload.py @@ -39,7 +39,12 @@ from olaf import OreSatFile from spacepackets.cfdp import CfdpLv from spacepackets.cfdp.defs import ChecksumType, ConditionCode, TransmissionMode -from spacepackets.cfdp.tlv import ProxyPutRequest, ProxyPutRequestParams +from spacepackets.cfdp.tlv import ( + DirectoryListingRequest, + DirectoryParams, + ProxyPutRequest, + ProxyPutRequestParams, +) from spacepackets.countdown import Countdown from spacepackets.seqcount import SeqCountProvider from spacepackets.util import ByteFieldU8 @@ -259,6 +264,56 @@ def run(self): self.uplink.put(pdu) +def put_request(dest: ByteFieldU8, file_path: str) -> PutRequest: + """Creates a simple PutRequest for the file in file_path""" + return PutRequest( + destination_id=dest, + source_file=Path(file_path), + dest_file=Path(file_path), + trans_mode=None, + closure_requested=None, + ) + + +def proxy_put_request(dest: ByteFieldU8, source: ByteFieldU8, file_path: str) -> PutRequest: + """Creates a put request for file_path to be transfered to the cfdp entity in dest""" + return PutRequest( + destination_id=dest, + source_file=None, + dest_file=None, + trans_mode=None, + # FIXME: upstream bug - DestHandler does not respect closure_requested=None when + # trans_mode defaults to ACKNOWLEGED + closure_requested=True, + msgs_to_user=[ + ProxyPutRequest( + ProxyPutRequestParams( + source, + source_file_name=CfdpLv(file_path.encode()), + dest_file_name=CfdpLv(file_path.encode()), + ) + ).to_generic_msg_to_user_tlv() + ], + ) + + +def directory_listing(dest: ByteFieldU8, path: str, result: str) -> PutRequest: + """Creates a directory listing put request""" + # CFDP 6.3.3 + return PutRequest( + destination_id=dest, + source_file=None, + dest_file=None, + trans_mode=None, + closure_requested=True, + msgs_to_user=[ + DirectoryListingRequest( + DirectoryParams.from_strs(path, result) + ).to_generic_msg_to_user_tlv() + ], + ) + + def main(): """Upload a file to the satellite.""" parser = ArgumentParser() @@ -307,12 +362,15 @@ def main(): default="", help="edl hmac, must be 32 bytes, default all zero", ) - parser.add_argument( + command = parser.add_mutually_exclusive_group() + command.add_argument( "-p", "--proxy", action="store_true", help="Initiate a proxy put request instead of a normal one", ) + command.add_argument("-dr", "--directory", help="Request a directory listing, for example '.'") + args = parser.parse_args() file_path = args.file_path.split("/")[-1] @@ -376,33 +434,11 @@ def main(): dest.start() if args.proxy: - put = PutRequest( - destination_id=DEST_ID, - source_file=None, - dest_file=None, - trans_mode=None, - # FIXME: upstream bug - DestHandler does not respect closure_requested=None when - # trans_mode defaults to ACKNOWLEGED - closure_requested=True, - msgs_to_user=[ - ProxyPutRequest( - ProxyPutRequestParams( - SOURCE_ID, - source_file_name=CfdpLv(args.file_path.encode()), - dest_file_name=CfdpLv(args.file_path.encode()), - ) - ).to_generic_msg_to_user_tlv() - ], - ) + put = proxy_put_request(DEST_ID, SOURCE_ID, args.file_path) + elif args.directory: + put = directory_listing(DEST_ID, args.directory, args.file_path) else: - put = PutRequest( - destination_id=DEST_ID, - source_file=Path(args.file_path), - dest_file=Path(args.file_path), - trans_mode=None, - closure_requested=None, - ) - + put = put_request(DEST_ID, args.file_path) source.send_packets(put) signal.pause() From 8668bca323ef14c1dabbcb989115d32c7f2cc4d8 Mon Sep 17 00:00:00 2001 From: Theo Hill Date: Thu, 23 May 2024 17:50:50 -0700 Subject: [PATCH 3/3] Additional cfdp-py vfs fixes This splits all the fixes (currently related to upstream inconsistently using the filestore in its implementation) out into protocols/cfdp.py. From before this moves the CrcHelper from edl.py to cfdp.py, and adds a new VfsSourceHandler that fixes up a couple internal methods. Finally this replaces the PrefixedFilestore with CacheStore. --- oresat_c3/protocols/cachestore.py | 14 +++ oresat_c3/protocols/cfdp.py | 100 ++++++++++++++++++++++ oresat_c3/services/edl.py | 132 ++--------------------------- tests/protocols/test_cachestore.py | 14 +++ 4 files changed, 137 insertions(+), 123 deletions(-) create mode 100644 oresat_c3/protocols/cfdp.py diff --git a/oresat_c3/protocols/cachestore.py b/oresat_c3/protocols/cachestore.py index d5f2f65..09f3134 100644 --- a/oresat_c3/protocols/cachestore.py +++ b/oresat_c3/protocols/cachestore.py @@ -42,6 +42,20 @@ def file_exists(self, path: Path) -> bool: with self._lock: return any(path.name == f.name for f in self._data) + def stat(self, file: Path) -> os.stat_result: + """Implements os.stat() but for a filestore + + I needed to find the size of a file but there's nothing that I can see in the spec that + would do it. That said it only gives a minimum recommended set of operations, so maybe + they just expect you to do it yourself? + """ + + with self._lock: + for f in self._data: + if file.name == f.name: + return Path(self._dir, f.name).stat() + raise FileNotFoundError(file) + def truncate_file(self, file: Path) -> None: with self._lock: for f in self._data: diff --git a/oresat_c3/protocols/cfdp.py b/oresat_c3/protocols/cfdp.py new file mode 100644 index 0000000..f1dd408 --- /dev/null +++ b/oresat_c3/protocols/cfdp.py @@ -0,0 +1,100 @@ +"""This module contains bugfixes to cfdp-py implementations. + +Most or all of these changes should eventually be submitted upstream. +""" + +import struct +from pathlib import Path + +from cfdppy.exceptions import SourceFileDoesNotExist +from cfdppy.handler.crc import CrcHelper +from cfdppy.handler.source import SourceHandler +from spacepackets.cfdp import NULL_CHECKSUM_U32, ChecksumType +from spacepackets.cfdp.pdu import FileDataPdu +from spacepackets.cfdp.pdu.file_data import FileDataParams + + +class VfsSourceHandler(SourceHandler): + """A SourceHandler but modified to always and only use Filestore operations""" + + def _prepare_file_params(self): + """Fixes the parent implementation not using vfs operations for file ops + + in particular file_exists() and stat() + """ + assert self._put_req is not None + if self._put_req.metadata_only: + self._params.fp.metadata_only = True + self._params.fp.no_eof = True + else: + assert self._put_req.source_file is not None + if not self.user.vfs.file_exists(self._put_req.source_file): + raise SourceFileDoesNotExist(self._put_req.source_file) + file_size = self.user.vfs.stat(self._put_req.source_file).st_size + if file_size == 0: + self._params.fp.metadata_only = True + else: + self._params.fp.file_size = file_size + + def _prepare_file_data_pdu(self, offset: int, read_len: int): + """Fixes the parent not using vfs operations + + They opened source_file manually and then used read_from_open_file(), but read_data() + will do all that for you but properly. + """ + assert self._put_req is not None + assert self._put_req.source_file is not None + file_data = self.user.vfs.read_data(self._put_req.source_file, offset, read_len) + 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) + + +class VfsCrcHelper(CrcHelper): + """CrcHelper but modified to only use Filestore operations. + + It previously would attempt to open the paths passed to it directly instead of asking the + filestore, which failed when using the above PrefixFilestore. + """ + + def calc_modular_checksum(self, file_path: Path) -> bytes: + """Calculates the modular checksum of the file in file_path. + + This was a module level function in cfdppy but it accessed the filesystem directly + instead of going through a filestore. It needs to become a CrcHelper method to use the + provided filestore. + """ + checksum = 0 + offset = 0 + while True: + data = self.vfs.read_data(file_path, offset, 4) + offset += 4 + if not data: + break + checksum += int.from_bytes(data.ljust(4, b"\0"), byteorder="big", signed=False) + + checksum %= 2**32 + return struct.pack("!I", checksum) + + def calc_for_file(self, file_path: Path, file_sz: int, segment_len: int = 4096) -> bytes: + if self.checksum_type == ChecksumType.NULL_CHECKSUM: + return NULL_CHECKSUM_U32 + if self.checksum_type == ChecksumType.MODULAR: + return self.calc_modular_checksum(file_path) + crc_obj = self.generate_crc_calculator() + if segment_len == 0: + raise ValueError("Segment length can not be 0") + if not self.vfs.file_exists(file_path): + raise SourceFileDoesNotExist(file_path) + current_offset = 0 + + # Calculate the file CRC + while current_offset < file_sz: + if current_offset + segment_len > file_sz: + read_len = file_sz - current_offset + else: + read_len = segment_len + if read_len > 0: + crc_obj.update(self.vfs.read_data(file_path, current_offset, read_len)) + current_offset += read_len + return crc_obj.digest() diff --git a/oresat_c3/services/edl.py b/oresat_c3/services/edl.py index 74d836e..5b21b9e 100644 --- a/oresat_c3/services/edl.py +++ b/oresat_c3/services/edl.py @@ -1,7 +1,5 @@ """'EDL Service""" -import os -import struct from collections.abc import Iterable from datetime import timedelta from pathlib import Path @@ -16,10 +14,7 @@ NoRemoteEntityCfgFound, SourceFileDoesNotExist, ) -from cfdppy.filestore import HostFilestore -from cfdppy.handler.crc import CrcHelper from cfdppy.handler.dest import DestHandler -from cfdppy.handler.source import SourceHandler from cfdppy.mib import ( CheckTimerProvider, DefaultFaultHandlerBase, @@ -37,14 +32,13 @@ TransactionId, TransactionParams, ) -from olaf import MasterNode, NodeStop, OreSatFileCache, Service, logger -from spacepackets.cfdp import NULL_CHECKSUM_U32, ChecksumType, ConditionCode, TransmissionMode +from olaf import MasterNode, NodeStop, Service, logger +from spacepackets.cfdp import ChecksumType, ConditionCode, TransmissionMode from spacepackets.cfdp.defs import DeliveryCode, FileStatus from spacepackets.cfdp.pdu import AbstractFileDirectiveBase from spacepackets.cfdp.tlv import ( DirectoryListingResponse, DirectoryOperationMessageType, - FilestoreResponseStatusCode, OriginatingTransactionId, ProxyMessageType, ProxyPutResponse, @@ -54,6 +48,8 @@ from spacepackets.seqcount import SeqCountProvider from spacepackets.util import ByteFieldU8 +from ..protocols.cachestore import CacheStore +from ..protocols.cfdp import VfsCrcHelper, VfsSourceHandler from ..protocols.edl_command import ( EdlCommandCode, EdlCommandError, @@ -83,8 +79,7 @@ def __init__( self._node_mgr_service = node_mgr_service self._beacon_service = beacon_service - upload_dir = f"{node.work_base_dir}/upload" - self._file_receiver = EdlFileReciever(upload_dir, node.fwrite_cache) + self._file_receiver = EdlFileReciever(node.fwrite_cache) # objs edl_rec = node.od["edl"] @@ -347,113 +342,6 @@ def _run_cmd(self, request: EdlCommandRequest) -> EdlCommandResponse: return response -class PrefixedFilestore(HostFilestore): - """A HostFilestore modified to only run in a specified directory""" - - def __init__(self, prefix: Path): - if not prefix.is_dir(): - raise NotADirectoryError("prefix must be a directory") - self._prefix = prefix - - def read_data(self, file: Path, offset: Optional[int], read_len: Optional[int] = None) -> bytes: - return super().read_data(self._prefix.joinpath(file), offset, read_len) - - def file_exists(self, path: Path) -> bool: - return super().file_exists(self._prefix.joinpath(path)) - - def is_directory(self, path: Path) -> bool: - return super().is_directory(self._prefix.joinpath(path)) - - def truncate_file(self, file: Path): - super().truncate_file(self._prefix.joinpath(file)) - - def write_data(self, file: Path, data: bytes, offset: Optional[int]): - super().write_data(self._prefix.joinpath(file), data, offset) - - def create_file(self, file: Path) -> FilestoreResponseStatusCode: - return super().create_file(self._prefix.joinpath(file)) - - def delete_file(self, file: Path) -> FilestoreResponseStatusCode: - return super().delete_file(self._prefix.joinpath(file)) - - def rename_file(self, old_file: Path, new_file: Path) -> FilestoreResponseStatusCode: - return super().rename_file(self._prefix.joinpath(old_file), self._prefix.joinpath(new_file)) - - def replace_file(self, replaced_file: Path, source_file: Path) -> FilestoreResponseStatusCode: - return super().replace_file( - self._prefix.joinpath(replaced_file), - self._prefix.joinpath(source_file), - ) - - def remove_directory( - self, dir_name: Path, recursive: bool = False - ) -> FilestoreResponseStatusCode: - return super().remove_directory(self._prefix.joinpath(dir_name), recursive) - - def create_directory(self, dir_name: Path) -> FilestoreResponseStatusCode: - return super().create_directory(self._prefix.joinpath(dir_name)) - - def list_directory( - self, dir_name: Path, target_file: Path, recursive: bool = False - ) -> FilestoreResponseStatusCode: - # The upstream implementation added junk to the output and maybe didn't even work? - dir_name = self._prefix.joinpath(dir_name) - with open(target_file, "w") as f: - for line in os.walk(dir_name) if recursive else os.listdir(dir_name): - f.write(f"{line}\n") - return FilestoreResponseStatusCode.SUCCESS - - -class VfsCrcHelper(CrcHelper): - """CrcHelper but modified to only use Filestore operations. - - It previously would attempt to open the paths passed to it directly instead of asking the - filestore, which failed when using the above PrefixFilestore. - """ - - def calc_modular_checksum(self, file_path: Path) -> bytes: - """Calculates the modular checksum of the file in file_path. - - This was a module level function in cfdppy but it accessed the filesystem directly - instead of going through a filestore. It needs to become a CrcHelper method to use the - provided filestore. - """ - checksum = 0 - offset = 0 - while True: - data = self.vfs.read_data(file_path, offset, 4) - offset += 4 - if not data: - break - checksum += int.from_bytes(data.ljust(4, b"\0"), byteorder="big", signed=False) - - checksum %= 2**32 - return struct.pack("!I", checksum) - - def calc_for_file(self, file_path: Path, file_sz: int, segment_len: int = 4096) -> bytes: - if self.checksum_type == ChecksumType.NULL_CHECKSUM: - return NULL_CHECKSUM_U32 - if self.checksum_type == ChecksumType.MODULAR: - return self.calc_modular_checksum(file_path) - crc_obj = self.generate_crc_calculator() - if segment_len == 0: - raise ValueError("Segment length can not be 0") - if not self.vfs.file_exists(file_path): - raise SourceFileDoesNotExist(file_path) - current_offset = 0 - - # Calculate the file CRC - while current_offset < file_sz: - if current_offset + segment_len > file_sz: - read_len = file_sz - current_offset - else: - read_len = segment_len - if read_len > 0: - crc_obj.update(self.vfs.read_data(file_path, current_offset, read_len)) - current_offset += read_len - return crc_obj.digest() - - class LogFaults(DefaultFaultHandlerBase): """A HaultHandler that only logs the faults and nothing more. @@ -487,10 +375,8 @@ def provide_check_timer(self, local_entity_id, remote_entity_id, entity_type) -> class EdlFileReciever(CfdpUserBase): """CFDP receiver for file uploads.""" - def __init__(self, upload_dir: str, fwrite_cache: OreSatFileCache): - path = Path(upload_dir) - path.mkdir(parents=True, exist_ok=True) - super().__init__(vfs=PrefixedFilestore(path)) + def __init__(self, fwrite_cache: CacheStore): + super().__init__(vfs=fwrite_cache) self.proxy_responses = { # FIXME: defaultdict with invalid response ProxyMessageType.PUT_REQUEST: self.proxy_put_response, @@ -535,7 +421,6 @@ def __init__(self, upload_dir: str, fwrite_cache: OreSatFileCache): ] ) - self.fwrite_cache = fwrite_cache self.dest = DestHandler( cfg=localcfg, user=self, @@ -544,13 +429,14 @@ def __init__(self, upload_dir: str, fwrite_cache: OreSatFileCache): ) self.dest._cksum_verif_helper = VfsCrcHelper(ChecksumType.NULL_CHECKSUM, self.vfs) - self.source = SourceHandler( + self.source = VfsSourceHandler( cfg=localcfg, user=self, remote_cfg_table=remote_entities, check_timer_provider=DefaultCheckTimer(), seq_num_provider=SeqCountProvider(16), ) + self.source._crc_helper = VfsCrcHelper(ChecksumType.NULL_CHECKSUM, self.vfs) self.scheduled_requests: SimpleQueue[PutRequest] = SimpleQueue() self.active_requests: dict[TransactionId, TransactionId] = {} diff --git a/tests/protocols/test_cachestore.py b/tests/protocols/test_cachestore.py index 370abf4..7f688b0 100644 --- a/tests/protocols/test_cachestore.py +++ b/tests/protocols/test_cachestore.py @@ -77,6 +77,20 @@ def test_file_exists(self): self.assertFalse(self.cache.file_exists(self.invalid)) self.assertFalse(self.cache.file_exists(self.directory)) + def test_stat(self): + """Test stat()""" + with self.assertRaises(FileNotFoundError): + self.cache.stat(self.doesnt) + with self.assertRaises(FileNotFoundError): + self.cache.stat(self.invalid) + with self.assertRaises(FileNotFoundError): + self.cache.stat(self.directory) + + self.assertEqual(self.cache.stat(self.exists).st_size, 0) + data = b"This is a test string of bytes \x01\x02\x03" + self.cache.write_data(self.exists, data) + self.assertEqual(self.cache.stat(self.exists).st_size, len(data)) + def test_truncate_file(self): """Test truncate_file()""" with self.assertRaises(FileNotFoundError):