diff --git a/Cargo.toml b/Cargo.toml index 0db7b8e..e54532e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,3 @@ exclude = [ "embedded-examples/stm32f3-disco-rtic", "embedded-examples/stm32h7-rtic", ] - diff --git a/satrs-example/src/tmtc/tm_sink.rs b/satrs-example/src/tmtc/tm_sink.rs index 0771a79..afc15a0 100644 --- a/satrs-example/src/tmtc/tm_sink.rs +++ b/satrs-example/src/tmtc/tm_sink.rs @@ -4,16 +4,19 @@ use std::{ }; use log::info; -use satrs::tmtc::{PacketAsVec, PacketInPool, SharedPacketPool}; use satrs::{ pool::PoolProvider, - seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore}, + seq_count::CcsdsSimpleSeqCountProvider, spacepackets::{ ecss::{tm::PusTmZeroCopyWriter, PusPacket}, time::cds::MIN_CDS_FIELD_LEN, CcsdsPacket, }, }; +use satrs::{ + seq_count::SequenceCountProvider, + tmtc::{PacketAsVec, PacketInPool, SharedPacketPool}, +}; use crate::interface::tcp::SyncTcpTmSource; diff --git a/satrs-mib/Cargo.toml b/satrs-mib/Cargo.toml index 96c7a37..54759bf 100644 --- a/satrs-mib/Cargo.toml +++ b/satrs-mib/Cargo.toml @@ -25,6 +25,7 @@ optional = true [dependencies.satrs-shared] version = ">=0.1.3, <0.2" features = ["serde"] +path = "../satrs-shared" [dependencies.satrs-mib-codegen] path = "codegen" diff --git a/satrs-mib/codegen/Cargo.toml b/satrs-mib/codegen/Cargo.toml index 584a66e..f5a674a 100644 --- a/satrs-mib/codegen/Cargo.toml +++ b/satrs-mib/codegen/Cargo.toml @@ -29,6 +29,7 @@ trybuild = { version = "1", features = ["diff"] } [dev-dependencies.satrs-shared] version = ">=0.1.3, <0.2" +path = "../../satrs-shared" [dev-dependencies.satrs-mib] path = ".." diff --git a/satrs-shared/Cargo.toml b/satrs-shared/Cargo.toml index 175f909..a840192 100644 --- a/satrs-shared/Cargo.toml +++ b/satrs-shared/Cargo.toml @@ -22,8 +22,10 @@ version = "0.3" optional = true [dependencies.spacepackets] -version = ">0.9, <=0.11" +version = ">0.9" default-features = false +git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" +branch = "main" [features] serde = ["dep:serde", "spacepackets/serde"] diff --git a/satrs/CHANGELOG.md b/satrs/CHANGELOG.md index 6e9d5a6..921c1a9 100644 --- a/satrs/CHANGELOG.md +++ b/satrs/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Renamed `StaticPoolConfig::new` to `StaticPoolConfig::new_from_subpool_cfg_tuples`. The new `new` implementation expects a type struct instead of tuples. +- Moved `cfdp` module to [dedicated crate](https://egit.irs.uni-stuttgart.de/rust/cfdp) +- Moved `seq_count` module to [spacepackets](https://egit.irs.uni-stuttgart.de/rust/spacepackets) + crate ## Added diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index ec2b2d0..8e9c11e 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -21,14 +21,17 @@ crc = "3" [dependencies.satrs-shared] version = ">=0.1.3, <0.2" +path = "../satrs-shared" [dependencies.num_enum] version = ">0.5, <=0.7" default-features = false [dependencies.spacepackets] -version = "0.11" +version = "0.12" default-features = false +git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" +branch = "main" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs deleted file mode 100644 index 4a87ce6..0000000 --- a/satrs/src/cfdp/dest.rs +++ /dev/null @@ -1,1603 +0,0 @@ -use crate::cfdp::user::TransactionFinishedParams; -use core::str::{from_utf8, Utf8Error}; -use std::path::{Path, PathBuf}; - -use super::{ - filestore::{FilestoreError, VirtualFilestore}, - user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams}, - CheckTimerCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo, PacketTarget, - RemoteEntityConfig, RemoteEntityConfigProvider, State, TimerContext, TransactionId, - TransactionStep, -}; -use alloc::boxed::Box; -use smallvec::SmallVec; -use spacepackets::{ - cfdp::{ - pdu::{ - eof::EofPdu, - file_data::FileDataPdu, - finished::{DeliveryCode, FileStatus, FinishedPduCreator}, - metadata::{MetadataGenericParams, MetadataPduReader}, - CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, - }, - tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, TlvType}, - ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode, - }, - util::{UnsignedByteField, UnsignedEnum}, -}; -use thiserror::Error; - -#[derive(Debug)] -struct FileProperties { - src_file_name: [u8; u8::MAX as usize], - src_file_name_len: usize, - dest_file_name: [u8; u8::MAX as usize], - dest_file_name_len: usize, - dest_path_buf: PathBuf, -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -enum CompletionDisposition { - Completed = 0, - Cancelled = 1, -} - -#[derive(Debug)] -struct TransferState { - transaction_id: Option, - metadata_params: MetadataGenericParams, - progress: u64, - metadata_only: bool, - condition_code: ConditionCode, - delivery_code: DeliveryCode, - file_status: FileStatus, - completion_disposition: CompletionDisposition, - checksum: u32, - current_check_count: u32, - current_check_timer: Option>, -} - -impl Default for TransferState { - fn default() -> Self { - Self { - transaction_id: None, - metadata_params: Default::default(), - progress: Default::default(), - metadata_only: false, - condition_code: ConditionCode::NoError, - delivery_code: DeliveryCode::Incomplete, - file_status: FileStatus::Unreported, - completion_disposition: CompletionDisposition::Completed, - checksum: 0, - current_check_count: 0, - current_check_timer: None, - } - } -} - -#[derive(Debug)] -struct TransactionParams { - tstate: TransferState, - pdu_conf: CommonPduConfig, - file_properties: FileProperties, - cksum_buf: [u8; 1024], - msgs_to_user_size: usize, - msgs_to_user_buf: [u8; 1024], - remote_cfg: Option, -} - -impl TransactionParams { - fn transmission_mode(&self) -> TransmissionMode { - self.pdu_conf.trans_mode - } -} - -impl Default for FileProperties { - fn default() -> Self { - Self { - src_file_name: [0; u8::MAX as usize], - src_file_name_len: Default::default(), - dest_file_name: [0; u8::MAX as usize], - dest_file_name_len: Default::default(), - dest_path_buf: Default::default(), - } - } -} - -impl TransactionParams { - fn file_size(&self) -> u64 { - self.tstate.metadata_params.file_size - } - - fn metadata_params(&self) -> &MetadataGenericParams { - &self.tstate.metadata_params - } -} - -impl Default for TransactionParams { - fn default() -> Self { - Self { - pdu_conf: Default::default(), - cksum_buf: [0; 1024], - msgs_to_user_size: 0, - msgs_to_user_buf: [0; 1024], - tstate: Default::default(), - file_properties: Default::default(), - remote_cfg: None, - } - } -} - -impl TransactionParams { - fn reset(&mut self) { - self.tstate.condition_code = ConditionCode::NoError; - self.tstate.delivery_code = DeliveryCode::Incomplete; - self.tstate.file_status = FileStatus::Unreported; - } -} - -#[derive(Debug, Error)] -pub enum DestError { - /// File directive expected, but none specified - #[error("expected file directive")] - DirectiveExpected, - #[error("can not process packet type {0:?}")] - CantProcessPacketType(FileDirectiveType), - #[error("can not process file data PDUs in current state")] - WrongStateForFileDataAndEof, - // Received new metadata PDU while being already being busy with a file transfer. - #[error("busy with transfer")] - RecvdMetadataButIsBusy, - #[error("empty source file field")] - EmptySrcFileField, - #[error("empty dest file field")] - EmptyDestFileField, - #[error("packets to be sent are still left")] - PacketToSendLeft, - #[error("pdu error {0}")] - Pdu(#[from] PduError), - #[error("io error {0}")] - Io(#[from] std::io::Error), - #[error("file store error {0}")] - Filestore(#[from] FilestoreError), - #[error("path conversion error {0}")] - PathConversion(#[from] Utf8Error), - #[error("error building dest path from source file name and dest folder")] - PathConcat, - #[error("no remote entity configuration found for {0:?}")] - NoRemoteCfgFound(UnsignedByteField), -} - -pub trait CfdpPacketSender: Send { - fn send_pdu( - &mut self, - pdu_type: PduType, - file_directive_type: Option, - raw_pdu: &[u8], - ) -> Result<(), PduError>; -} - -/// This is the primary CFDP destination handler. It models the CFDP destination entity, which is -/// primarily responsible for receiving files sent from another CFDP entity. It performs the -/// reception side of File Copy Operations. -/// -/// The [DestinationHandler::state_machine] function is the primary function to drive the -/// destination handler. It can be used to insert packets into the destination -/// handler and driving the state machine, which might generate new -/// packets to be sent to the remote entity. Please note that the destination handler can also -/// only process Metadata, EOF and Prompt PDUs in addition to ACK PDUs where the acknowledged -/// PDU is the Finished PDU. -/// -/// All generated packets are sent via the [CfdpPacketSender] trait, which is implemented by the -/// user and passed as a constructor parameter. The number of generated packets is returned -/// by the state machine call. -pub struct DestinationHandler { - local_cfg: LocalEntityConfig, - step: TransactionStep, - state: State, - tparams: TransactionParams, - packet_buf: alloc::vec::Vec, - packet_sender: Box, - vfs: Box, - remote_cfg_table: Box, - check_timer_creator: Box, -} - -impl DestinationHandler { - /// Constructs a new destination handler. - /// - /// # Arguments - /// - /// * `local_cfg` - The local CFDP entity configuration, consisting of the local entity ID, - /// the indication configuration, and the fault handlers. - /// * `max_packet_len` - The maximum expected generated packet size in bytes. Each time a - /// packet is sent, it will be buffered inside an internal buffer. The length of this buffer - /// will be determined by this parameter. This parameter can either be a known upper bound, - /// or it can specifically be determined by the largest packet size parameter of all remote - /// entity configurations in the passed `remote_cfg_table`. - /// * `packet_sender` - All generated packets are sent via this abstraction. - /// * `vfs` - Virtual filestore implementation to decouple the CFDP implementation from the - /// underlying filestore/filesystem. This allows to use this handler for embedded systems - /// where a standard runtime might not be available. - /// * `remote_cfg_table` - A table of all expected remote entities this entity will communicate - /// with. It contains various configuration parameters required for file transfers. - /// * `check_timer_creator` - This is used by the CFDP handler to generate timers required - /// by various tasks. - pub fn new( - local_cfg: LocalEntityConfig, - max_packet_len: usize, - packet_sender: Box, - vfs: Box, - remote_cfg_table: Box, - check_timer_creator: Box, - ) -> Self { - Self { - local_cfg, - step: TransactionStep::Idle, - state: State::Idle, - tparams: Default::default(), - packet_buf: alloc::vec![0; max_packet_len], - packet_sender, - vfs, - remote_cfg_table, - check_timer_creator, - } - } - - /// This is the core function to drive the destination handler. It is also used to insert - /// packets into the destination handler. - /// - /// The state machine should either be called if a packet with the appropriate destination ID - /// is received, or periodically in IDLE periods to perform all CFDP related tasks, for example - /// checking for timeouts or missed file segments. - pub fn state_machine( - &mut self, - cfdp_user: &mut impl CfdpUser, - packet_to_insert: Option<&PacketInfo>, - ) -> Result { - if let Some(packet) = packet_to_insert { - self.insert_packet(cfdp_user, packet)?; - } - match self.state { - State::Idle => todo!(), - State::Busy => self.fsm_busy(cfdp_user), - State::Suspended => todo!(), - } - } - - /// Returns [None] if the state machine is IDLE, and the transmission mode of the current - /// request otherwise. - pub fn transmission_mode(&self) -> Option { - if self.state == State::Idle { - return None; - } - Some(self.tparams.transmission_mode()) - } - - pub fn transaction_id(&self) -> Option { - self.tstate().transaction_id - } - - fn insert_packet( - &mut self, - cfdp_user: &mut impl CfdpUser, - packet_info: &PacketInfo, - ) -> Result<(), DestError> { - if packet_info.target() != PacketTarget::DestEntity { - // Unwrap is okay here, a PacketInfo for a file data PDU should always have the - // destination as the target. - return Err(DestError::CantProcessPacketType( - packet_info.pdu_directive().unwrap(), - )); - } - match packet_info.pdu_type { - PduType::FileDirective => { - if packet_info.pdu_directive.is_none() { - return Err(DestError::DirectiveExpected); - } - self.handle_file_directive( - cfdp_user, - packet_info.pdu_directive.unwrap(), - packet_info.raw_packet, - ) - } - PduType::FileData => self.handle_file_data(cfdp_user, packet_info.raw_packet), - } - } - - fn handle_file_directive( - &mut self, - cfdp_user: &mut impl CfdpUser, - pdu_directive: FileDirectiveType, - raw_packet: &[u8], - ) -> Result<(), DestError> { - match pdu_directive { - FileDirectiveType::EofPdu => self.handle_eof_pdu(cfdp_user, raw_packet)?, - FileDirectiveType::FinishedPdu - | FileDirectiveType::NakPdu - | FileDirectiveType::KeepAlivePdu => { - return Err(DestError::CantProcessPacketType(pdu_directive)); - } - FileDirectiveType::AckPdu => { - todo!( - "check whether ACK pdu handling is applicable by checking the acked directive field" - ) - } - FileDirectiveType::MetadataPdu => self.handle_metadata_pdu(raw_packet)?, - FileDirectiveType::PromptPdu => self.handle_prompt_pdu(raw_packet)?, - }; - Ok(()) - } - - fn handle_metadata_pdu(&mut self, raw_packet: &[u8]) -> Result<(), DestError> { - if self.state != State::Idle { - return Err(DestError::RecvdMetadataButIsBusy); - } - let metadata_pdu = MetadataPduReader::from_bytes(raw_packet)?; - self.tparams.reset(); - self.tparams.tstate.metadata_params = *metadata_pdu.metadata_params(); - let remote_cfg = self - .remote_cfg_table - .get_remote_config(metadata_pdu.source_id().value()); - if remote_cfg.is_none() { - return Err(DestError::NoRemoteCfgFound(metadata_pdu.dest_id())); - } - self.tparams.remote_cfg = Some(*remote_cfg.unwrap()); - - // TODO: Support for metadata only PDUs. - let src_name = metadata_pdu.src_file_name(); - let dest_name = metadata_pdu.dest_file_name(); - if src_name.is_empty() && dest_name.is_empty() { - self.tparams.tstate.metadata_only = true; - } - if !self.tparams.tstate.metadata_only && src_name.is_empty() { - return Err(DestError::EmptySrcFileField); - } - if !self.tparams.tstate.metadata_only && dest_name.is_empty() { - return Err(DestError::EmptyDestFileField); - } - if !self.tparams.tstate.metadata_only { - self.tparams.file_properties.src_file_name[..src_name.len_value()] - .copy_from_slice(src_name.value()); - self.tparams.file_properties.src_file_name_len = src_name.len_value(); - if dest_name.is_empty() { - return Err(DestError::EmptyDestFileField); - } - self.tparams.file_properties.dest_file_name[..dest_name.len_value()] - .copy_from_slice(dest_name.value()); - self.tparams.file_properties.dest_file_name_len = dest_name.len_value(); - self.tparams.pdu_conf = *metadata_pdu.pdu_header().common_pdu_conf(); - self.tparams.msgs_to_user_size = 0; - } - if !metadata_pdu.options().is_empty() { - for option_tlv in metadata_pdu.options_iter().unwrap() { - if option_tlv.is_standard_tlv() - && option_tlv.tlv_type().unwrap() == TlvType::MsgToUser - { - self.tparams - .msgs_to_user_buf - .copy_from_slice(option_tlv.raw_data().unwrap()); - self.tparams.msgs_to_user_size += option_tlv.len_full(); - } - } - } - self.state = State::Busy; - self.step = TransactionStep::TransactionStart; - Ok(()) - } - - fn handle_file_data( - &mut self, - user: &mut impl CfdpUser, - raw_packet: &[u8], - ) -> Result<(), DestError> { - if self.state == State::Idle - || (self.step != TransactionStep::ReceivingFileDataPdus - && self.step != TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling) - { - return Err(DestError::WrongStateForFileDataAndEof); - } - let fd_pdu = FileDataPdu::from_bytes(raw_packet)?; - if self.local_cfg.indication_cfg.file_segment_recv { - user.file_segment_recvd_indication(&FileSegmentRecvdParams { - id: self.tstate().transaction_id.unwrap(), - offset: fd_pdu.offset(), - length: fd_pdu.file_data().len(), - segment_metadata: fd_pdu.segment_metadata(), - }); - } - if let Err(e) = self.vfs.write_data( - self.tparams.file_properties.dest_path_buf.to_str().unwrap(), - fd_pdu.offset(), - fd_pdu.file_data(), - ) { - self.declare_fault(ConditionCode::FilestoreRejection); - return Err(e.into()); - } - self.tstate_mut().progress += fd_pdu.file_data().len() as u64; - Ok(()) - } - - fn handle_eof_pdu( - &mut self, - cfdp_user: &mut impl CfdpUser, - raw_packet: &[u8], - ) -> Result<(), DestError> { - if self.state == State::Idle || self.step != TransactionStep::ReceivingFileDataPdus { - return Err(DestError::WrongStateForFileDataAndEof); - } - let eof_pdu = EofPdu::from_bytes(raw_packet)?; - if self.local_cfg.indication_cfg.eof_recv { - // Unwrap is okay here, application logic ensures that transaction ID is valid here. - cfdp_user.eof_recvd_indication(self.tparams.tstate.transaction_id.as_ref().unwrap()); - } - let regular_transfer_finish = if eof_pdu.condition_code() == ConditionCode::NoError { - self.handle_no_error_eof_pdu(&eof_pdu)? - } else { - todo!("implement cancel request handling"); - }; - if regular_transfer_finish { - self.file_transfer_complete_transition(); - } - Ok(()) - } - - /// Returns whether the transfer can be completed regularly. - fn handle_no_error_eof_pdu(&mut self, eof_pdu: &EofPdu) -> Result { - // CFDP 4.6.1.2.9: Declare file size error if progress exceeds file size - if self.tparams.tstate.progress > eof_pdu.file_size() - && self.declare_fault(ConditionCode::FileSizeError) != FaultHandlerCode::IgnoreError - { - return Ok(false); - } else if (self.tparams.tstate.progress < eof_pdu.file_size()) - && self.tparams.transmission_mode() == TransmissionMode::Acknowledged - { - // CFDP 4.6.4.3.1: The end offset of the last received file segment and the file - // size as stated in the EOF PDU is not the same, so we need to add that segment to - // the lost segments for the deferred lost segment detection procedure. - // TODO: Proper lost segment handling. - // self._params.acked_params.lost_seg_tracker.add_lost_segment( - // (self._params.fp.progress, self._params.fp.file_size_eof) - // ) - } - - self.tparams.tstate.checksum = eof_pdu.file_checksum(); - if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged - && !self.checksum_verify(self.tparams.tstate.checksum) - { - if self.declare_fault(ConditionCode::FileChecksumFailure) - != FaultHandlerCode::IgnoreError - { - return Ok(false); - } - self.start_check_limit_handling(); - return Ok(false); - } - Ok(true) - } - - fn file_transfer_complete_transition(&mut self) { - if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged { - self.step = TransactionStep::TransferCompletion; - } else { - // TODO: Prepare ACK PDU somehow. - self.step = TransactionStep::SendingAckPdu; - } - } - - fn checksum_verify(&mut self, checksum: u32) -> bool { - let mut file_delivery_complete = false; - if self.tparams.metadata_params().checksum_type == ChecksumType::NullChecksum - || self.tparams.tstate.metadata_only - { - file_delivery_complete = true; - self.tparams.tstate.delivery_code = DeliveryCode::Complete; - self.tparams.tstate.condition_code = ConditionCode::NoError; - } else { - match self.vfs.checksum_verify( - self.tparams.file_properties.dest_path_buf.to_str().unwrap(), - self.tparams.metadata_params().checksum_type, - checksum, - &mut self.tparams.cksum_buf, - ) { - Ok(checksum_success) => { - file_delivery_complete = checksum_success; - } - Err(e) => match e { - FilestoreError::ChecksumTypeNotImplemented(_) => { - self.declare_fault(ConditionCode::UnsupportedChecksumType); - // For this case, the applicable algorithm shall be the the null checksum, - // which is always succesful. - file_delivery_complete = true; - } - _ => { - self.declare_fault(ConditionCode::FilestoreRejection); - // Treat this equivalent to a failed checksum procedure. - } - }, - }; - } - file_delivery_complete - } - - fn start_check_limit_handling(&mut self) { - self.step = TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling; - self.tparams.tstate.current_check_timer = Some( - self.check_timer_creator - .get_check_timer_provider(TimerContext::CheckLimit { - local_id: self.local_cfg.id, - remote_id: self.tparams.remote_cfg.unwrap().entity_id, - entity_type: EntityType::Receiving, - }), - ); - self.tparams.tstate.current_check_count = 0; - } - - fn check_limit_handling(&mut self) { - if self.tparams.tstate.current_check_timer.is_none() { - return; - } - let check_timer = self.tparams.tstate.current_check_timer.as_ref().unwrap(); - if check_timer.has_expired() { - if self.checksum_verify(self.tparams.tstate.checksum) { - self.file_transfer_complete_transition(); - return; - } - if self.tparams.tstate.current_check_count + 1 - >= self.tparams.remote_cfg.unwrap().check_limit - { - self.declare_fault(ConditionCode::CheckLimitReached); - } else { - self.tparams.tstate.current_check_count += 1; - self.tparams - .tstate - .current_check_timer - .as_mut() - .unwrap() - .reset(); - } - } - } - - pub fn handle_prompt_pdu(&mut self, _raw_packet: &[u8]) -> Result<(), DestError> { - todo!(); - } - - fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { - let mut sent_packets = 0; - if self.step == TransactionStep::TransactionStart { - self.transaction_start(cfdp_user)?; - } - if self.step == TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling { - self.check_limit_handling(); - } - if self.step == TransactionStep::TransferCompletion { - sent_packets += self.transfer_completion(cfdp_user)?; - } - if self.step == TransactionStep::SendingAckPdu { - todo!("no support for acknowledged mode yet"); - } - if self.step == TransactionStep::SendingFinishedPdu { - self.reset(); - } - Ok(sent_packets) - } - - /// Get the step, which denotes the exact step of a pending CFDP transaction when applicable. - pub fn step(&self) -> TransactionStep { - self.step - } - - /// Get the step, which denotes whether the CFDP handler is active, and which CFDP class - /// is used if it is active. - pub fn state(&self) -> State { - self.state - } - - fn transaction_start(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { - let dest_name = from_utf8( - &self.tparams.file_properties.dest_file_name - [..self.tparams.file_properties.dest_file_name_len], - )?; - let dest_path = Path::new(dest_name); - self.tparams.file_properties.dest_path_buf = dest_path.to_path_buf(); - let source_id = self.tparams.pdu_conf.source_id(); - let id = TransactionId::new(source_id, self.tparams.pdu_conf.transaction_seq_num); - let src_name = from_utf8( - &self.tparams.file_properties.src_file_name - [0..self.tparams.file_properties.src_file_name_len], - )?; - let mut msgs_to_user = SmallVec::<[MsgToUserTlv<'_>; 16]>::new(); - let mut num_msgs_to_user = 0; - if self.tparams.msgs_to_user_size > 0 { - let mut index = 0; - while index < self.tparams.msgs_to_user_size { - // This should never panic as the validity of the options was checked beforehand. - let msgs_to_user_tlv = - MsgToUserTlv::from_bytes(&self.tparams.msgs_to_user_buf[index..]) - .expect("message to user creation failed unexpectedly"); - msgs_to_user.push(msgs_to_user_tlv); - index += msgs_to_user_tlv.len_full(); - num_msgs_to_user += 1; - } - } - let metadata_recvd_params = MetadataReceivedParams { - id, - source_id, - file_size: self.tparams.file_size(), - src_file_name: src_name, - dest_file_name: dest_name, - msgs_to_user: &msgs_to_user[..num_msgs_to_user], - }; - self.tparams.tstate.transaction_id = Some(id); - cfdp_user.metadata_recvd_indication(&metadata_recvd_params); - - // TODO: This is the only remaining function which uses std.. the easiest way would - // probably be to use a static pre-allocated dest path buffer to store any concatenated - // paths. - if dest_path.exists() && self.vfs.is_dir(dest_path.to_str().unwrap()) { - // Create new destination path by concatenating the last part of the source source - // name and the destination folder. For example, for a source file of /tmp/hello.txt - // and a destination name of /home/test, the resulting file name should be - // /home/test/hello.txt - let source_path = Path::new(from_utf8( - &self.tparams.file_properties.src_file_name - [..self.tparams.file_properties.src_file_name_len], - )?); - let source_name = source_path.file_name(); - if source_name.is_none() { - return Err(DestError::PathConcat); - } - let source_name = source_name.unwrap(); - self.tparams.file_properties.dest_path_buf.push(source_name); - } - let dest_path_str = self.tparams.file_properties.dest_path_buf.to_str().unwrap(); - if self.vfs.exists(dest_path_str) { - self.vfs.truncate_file(dest_path_str)?; - } else { - self.vfs.create_file(dest_path_str)?; - } - self.tparams.tstate.file_status = FileStatus::Retained; - self.step = TransactionStep::ReceivingFileDataPdus; - Ok(()) - } - - fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { - let mut sent_packets = 0; - self.notice_of_completion(cfdp_user)?; - if self.tparams.transmission_mode() == TransmissionMode::Acknowledged - || self.tparams.metadata_params().closure_requested - { - sent_packets += self.send_finished_pdu()?; - self.step = TransactionStep::SendingFinishedPdu; - } else { - self.reset(); - } - Ok(sent_packets) - } - - fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { - if self.tstate().completion_disposition == CompletionDisposition::Completed { - // TODO: Execute any filestore requests - } else if self - .tparams - .remote_cfg - .as_ref() - .unwrap() - .disposition_on_cancellation - && self.tstate().delivery_code == DeliveryCode::Incomplete - { - self.vfs - .remove_file(self.tparams.file_properties.dest_path_buf.to_str().unwrap())?; - self.tstate_mut().file_status = FileStatus::DiscardDeliberately; - } - let tstate = self.tstate(); - let transaction_finished_params = TransactionFinishedParams { - id: tstate.transaction_id.unwrap(), - condition_code: tstate.condition_code, - delivery_code: tstate.delivery_code, - file_status: tstate.file_status, - }; - cfdp_user.transaction_finished_indication(&transaction_finished_params); - Ok(()) - } - - fn declare_fault(&mut self, condition_code: ConditionCode) -> FaultHandlerCode { - // Cache those, because they might be reset when abandoning the transaction. - let transaction_id = self.tstate().transaction_id.unwrap(); - let progress = self.tstate().progress; - let fh_code = self - .local_cfg - .default_fault_handler - .get_fault_handler(condition_code); - match fh_code { - FaultHandlerCode::NoticeOfCancellation => { - self.notice_of_cancellation(condition_code); - } - FaultHandlerCode::NoticeOfSuspension => self.notice_of_suspension(), - FaultHandlerCode::IgnoreError => (), - FaultHandlerCode::AbandonTransaction => self.abandon_transaction(), - } - self.local_cfg - .default_fault_handler - .report_fault(transaction_id, condition_code, progress) - } - - fn notice_of_cancellation(&mut self, condition_code: ConditionCode) { - self.step = TransactionStep::TransferCompletion; - self.tstate_mut().condition_code = condition_code; - self.tstate_mut().completion_disposition = CompletionDisposition::Cancelled; - } - - fn notice_of_suspension(&mut self) { - // TODO: Implement suspension handling. - } - fn abandon_transaction(&mut self) { - self.reset(); - } - - fn reset(&mut self) { - self.step = TransactionStep::Idle; - self.state = State::Idle; - // self.packets_to_send_ctx.packet_available = false; - self.tparams.reset(); - } - - fn send_finished_pdu(&mut self) -> Result { - let tstate = self.tstate(); - - let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0); - let finished_pdu = if tstate.condition_code == ConditionCode::NoError - || tstate.condition_code == ConditionCode::UnsupportedChecksumType - { - FinishedPduCreator::new_default(pdu_header, tstate.delivery_code, tstate.file_status) - } else { - // TODO: Are there cases where this ID is actually the source entity ID? - let entity_id = EntityIdTlv::new(self.local_cfg.id); - FinishedPduCreator::new_with_error( - pdu_header, - tstate.condition_code, - tstate.delivery_code, - tstate.file_status, - entity_id, - ) - }; - finished_pdu.write_to_bytes(&mut self.packet_buf)?; - self.packet_sender.send_pdu( - finished_pdu.pdu_type(), - finished_pdu.file_directive_type(), - &self.packet_buf[0..finished_pdu.len_written()], - )?; - Ok(1) - } - - fn tstate(&self) -> &TransferState { - &self.tparams.tstate - } - - fn tstate_mut(&mut self) -> &mut TransferState { - &mut self.tparams.tstate - } -} - -#[cfg(test)] -mod tests { - use core::{cell::Cell, sync::atomic::AtomicBool}; - #[allow(unused_imports)] - use std::println; - use std::{fs, sync::Mutex}; - - use alloc::{collections::VecDeque, string::String, sync::Arc, vec::Vec}; - use rand::Rng; - use spacepackets::{ - cfdp::{ - lv::Lv, - pdu::{finished::FinishedPduReader, metadata::MetadataPduCreator, WritablePduPacket}, - ChecksumType, TransmissionMode, - }, - util::{UbfU16, UnsignedByteFieldU16}, - }; - - use crate::cfdp::{ - filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimerCreator, - CountdownProvider, DefaultFaultHandler, IndicationConfig, RemoteEntityConfig, - StdRemoteEntityConfigProvider, UserFaultHandler, CRC_32, - }; - - use super::*; - - const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); - const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2); - - pub struct FileSegmentRecvdParamsNoSegMetadata { - pub id: TransactionId, - pub offset: u64, - pub length: usize, - } - - struct SentPdu { - pdu_type: PduType, - file_directive_type: Option, - raw_pdu: Vec, - } - type SharedPduPacketQueue = Arc>>; - #[derive(Default, Clone)] - struct TestCfdpSender { - packet_queue: SharedPduPacketQueue, - } - - impl CfdpPacketSender for TestCfdpSender { - fn send_pdu( - &mut self, - pdu_type: PduType, - file_directive_type: Option, - raw_pdu: &[u8], - ) -> Result<(), PduError> { - self.packet_queue.lock().unwrap().push_back(SentPdu { - pdu_type, - file_directive_type, - raw_pdu: raw_pdu.to_vec(), - }); - Ok(()) - } - } - - impl TestCfdpSender { - pub fn retrieve_next_pdu(&self) -> Option { - self.packet_queue.lock().unwrap().pop_front() - } - pub fn queue_empty(&self) -> bool { - self.packet_queue.lock().unwrap().is_empty() - } - } - - #[derive(Default)] - struct TestCfdpUser { - next_expected_seq_num: u64, - expected_full_src_name: String, - expected_full_dest_name: String, - expected_file_size: u64, - transaction_indication_call_count: u32, - eof_recvd_call_count: u32, - finished_indic_queue: VecDeque, - metadata_recv_queue: VecDeque, - file_seg_recvd_queue: VecDeque, - } - - impl TestCfdpUser { - fn new( - next_expected_seq_num: u64, - expected_full_src_name: String, - expected_full_dest_name: String, - expected_file_size: u64, - ) -> Self { - Self { - next_expected_seq_num, - expected_full_src_name, - expected_full_dest_name, - expected_file_size, - transaction_indication_call_count: 0, - eof_recvd_call_count: 0, - finished_indic_queue: VecDeque::new(), - metadata_recv_queue: VecDeque::new(), - file_seg_recvd_queue: VecDeque::new(), - } - } - - fn generic_id_check(&self, id: &crate::cfdp::TransactionId) { - assert_eq!(id.source_id, LOCAL_ID.into()); - assert_eq!(id.seq_num().value(), self.next_expected_seq_num); - } - } - - impl CfdpUser for TestCfdpUser { - fn transaction_indication(&mut self, id: &crate::cfdp::TransactionId) { - self.generic_id_check(id); - self.transaction_indication_call_count += 1; - } - - fn eof_sent_indication(&mut self, id: &crate::cfdp::TransactionId) { - self.generic_id_check(id); - } - - fn transaction_finished_indication( - &mut self, - finished_params: &crate::cfdp::user::TransactionFinishedParams, - ) { - self.generic_id_check(&finished_params.id); - self.finished_indic_queue.push_back(*finished_params); - } - - fn metadata_recvd_indication( - &mut self, - md_recvd_params: &crate::cfdp::user::MetadataReceivedParams, - ) { - self.generic_id_check(&md_recvd_params.id); - assert_eq!( - String::from(md_recvd_params.src_file_name), - self.expected_full_src_name - ); - assert_eq!( - String::from(md_recvd_params.dest_file_name), - self.expected_full_dest_name - ); - assert_eq!(md_recvd_params.msgs_to_user.len(), 0); - assert_eq!(md_recvd_params.source_id, LOCAL_ID.into()); - assert_eq!(md_recvd_params.file_size, self.expected_file_size); - self.metadata_recv_queue.push_back(md_recvd_params.into()); - } - - fn file_segment_recvd_indication( - &mut self, - segment_recvd_params: &crate::cfdp::user::FileSegmentRecvdParams, - ) { - self.generic_id_check(&segment_recvd_params.id); - self.file_seg_recvd_queue - .push_back(FileSegmentRecvdParamsNoSegMetadata { - id: segment_recvd_params.id, - offset: segment_recvd_params.offset, - length: segment_recvd_params.length, - }) - } - - fn report_indication(&mut self, _id: &crate::cfdp::TransactionId) {} - - fn suspended_indication( - &mut self, - _id: &crate::cfdp::TransactionId, - _condition_code: ConditionCode, - ) { - panic!("unexpected suspended indication"); - } - - fn resumed_indication(&mut self, _id: &crate::cfdp::TransactionId, _progresss: u64) {} - - fn fault_indication( - &mut self, - _id: &crate::cfdp::TransactionId, - _condition_code: ConditionCode, - _progress: u64, - ) { - panic!("unexpected fault indication"); - } - - fn abandoned_indication( - &mut self, - _id: &crate::cfdp::TransactionId, - _condition_code: ConditionCode, - _progress: u64, - ) { - panic!("unexpected abandoned indication"); - } - - fn eof_recvd_indication(&mut self, id: &crate::cfdp::TransactionId) { - self.generic_id_check(id); - self.eof_recvd_call_count += 1; - } - } - - #[derive(Default, Clone)] - struct TestFaultHandler { - notice_of_suspension_queue: Arc>>, - notice_of_cancellation_queue: Arc>>, - abandoned_queue: Arc>>, - ignored_queue: Arc>>, - } - - impl UserFaultHandler for TestFaultHandler { - fn notice_of_suspension_cb( - &mut self, - transaction_id: TransactionId, - cond: ConditionCode, - progress: u64, - ) { - self.notice_of_suspension_queue.lock().unwrap().push_back(( - transaction_id, - cond, - progress, - )) - } - - fn notice_of_cancellation_cb( - &mut self, - transaction_id: TransactionId, - cond: ConditionCode, - progress: u64, - ) { - self.notice_of_cancellation_queue - .lock() - .unwrap() - .push_back((transaction_id, cond, progress)) - } - - fn abandoned_cb( - &mut self, - transaction_id: TransactionId, - cond: ConditionCode, - progress: u64, - ) { - self.abandoned_queue - .lock() - .unwrap() - .push_back((transaction_id, cond, progress)) - } - - fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) { - self.ignored_queue - .lock() - .unwrap() - .push_back((transaction_id, cond, progress)) - } - } - - impl TestFaultHandler { - fn suspension_queue_empty(&self) -> bool { - self.notice_of_suspension_queue.lock().unwrap().is_empty() - } - fn cancellation_queue_empty(&self) -> bool { - self.notice_of_cancellation_queue.lock().unwrap().is_empty() - } - fn ignored_queue_empty(&self) -> bool { - self.ignored_queue.lock().unwrap().is_empty() - } - fn abandoned_queue_empty(&self) -> bool { - self.abandoned_queue.lock().unwrap().is_empty() - } - fn all_queues_empty(&self) -> bool { - self.suspension_queue_empty() - && self.cancellation_queue_empty() - && self.ignored_queue_empty() - && self.abandoned_queue_empty() - } - } - - #[derive(Debug)] - struct TestCheckTimer { - counter: Cell, - expired: Arc, - } - - impl CountdownProvider for TestCheckTimer { - fn has_expired(&self) -> bool { - self.expired.load(core::sync::atomic::Ordering::Relaxed) - } - fn reset(&mut self) { - self.counter.set(0); - } - } - - impl TestCheckTimer { - pub fn new(expired_flag: Arc) -> Self { - Self { - counter: Cell::new(0), - expired: expired_flag, - } - } - } - - struct TestCheckTimerCreator { - check_limit_expired_flag: Arc, - } - - impl TestCheckTimerCreator { - pub fn new(expired_flag: Arc) -> Self { - Self { - check_limit_expired_flag: expired_flag, - } - } - } - - impl CheckTimerCreator for TestCheckTimerCreator { - fn get_check_timer_provider( - &self, - timer_context: TimerContext, - ) -> Box { - match timer_context { - TimerContext::CheckLimit { .. } => { - Box::new(TestCheckTimer::new(self.check_limit_expired_flag.clone())) - } - _ => { - panic!("invalid check timer creator, can only be used for check limit handling") - } - } - } - } - - struct DestHandlerTester { - check_timer_expired: Arc, - pdu_sender: TestCfdpSender, - handler: DestinationHandler, - src_path: PathBuf, - dest_path: PathBuf, - check_dest_file: bool, - check_handler_idle_at_drop: bool, - expected_file_size: u64, - closure_requested: bool, - pdu_header: PduHeader, - expected_full_data: Vec, - buf: [u8; 512], - } - - impl DestHandlerTester { - fn new(fault_handler: TestFaultHandler, closure_requested: bool) -> Self { - let check_timer_expired = Arc::new(AtomicBool::new(false)); - let test_sender = TestCfdpSender::default(); - let dest_handler = default_dest_handler( - fault_handler, - test_sender.clone(), - check_timer_expired.clone(), - ); - let (src_path, dest_path) = init_full_filenames(); - assert!(!Path::exists(&dest_path)); - let handler = Self { - check_timer_expired, - pdu_sender: test_sender, - handler: dest_handler, - src_path, - closure_requested, - dest_path, - check_dest_file: false, - check_handler_idle_at_drop: false, - expected_file_size: 0, - pdu_header: create_pdu_header(UbfU16::new(0)), - expected_full_data: Vec::new(), - buf: [0; 512], - }; - handler.state_check(State::Idle, TransactionStep::Idle); - handler - } - - fn dest_path(&self) -> &PathBuf { - &self.dest_path - } - - #[allow(dead_code)] - fn indication_cfg_mut(&mut self) -> &mut IndicationConfig { - &mut self.handler.local_cfg.indication_cfg - } - - fn indication_cfg(&mut self) -> &IndicationConfig { - &self.handler.local_cfg.indication_cfg - } - - fn set_check_timer_expired(&mut self) { - self.check_timer_expired - .store(true, core::sync::atomic::Ordering::Relaxed); - } - - fn test_user_from_cached_paths(&self, expected_file_size: u64) -> TestCfdpUser { - TestCfdpUser::new( - 0, - self.src_path.to_string_lossy().into(), - self.dest_path.to_string_lossy().into(), - expected_file_size, - ) - } - - fn generic_transfer_init( - &mut self, - user: &mut TestCfdpUser, - file_size: u64, - ) -> Result { - self.expected_file_size = file_size; - let metadata_pdu = create_metadata_pdu( - &self.pdu_header, - self.src_path.as_path(), - self.dest_path.as_path(), - file_size, - self.closure_requested, - ); - let packet_info = create_packet_info(&metadata_pdu, &mut self.buf); - self.handler.state_machine(user, Some(&packet_info))?; - assert_eq!(user.metadata_recv_queue.len(), 1); - assert_eq!( - self.handler.transmission_mode().unwrap(), - TransmissionMode::Unacknowledged - ); - Ok(self.handler.transaction_id().unwrap()) - } - - fn generic_file_data_insert( - &mut self, - user: &mut TestCfdpUser, - offset: u64, - file_data_chunk: &[u8], - ) -> Result { - let filedata_pdu = - FileDataPdu::new_no_seg_metadata(self.pdu_header, offset, file_data_chunk); - filedata_pdu - .write_to_bytes(&mut self.buf) - .expect("writing file data PDU failed"); - let packet_info = PacketInfo::new(&self.buf).expect("creating packet info failed"); - let result = self.handler.state_machine(user, Some(&packet_info)); - if self.indication_cfg().file_segment_recv { - assert!(!user.file_seg_recvd_queue.is_empty()); - assert_eq!(user.file_seg_recvd_queue.back().unwrap().offset, offset); - assert_eq!( - user.file_seg_recvd_queue.back().unwrap().length, - file_data_chunk.len() - ); - } - result - } - - fn generic_eof_no_error( - &mut self, - user: &mut TestCfdpUser, - expected_full_data: Vec, - ) -> Result { - self.expected_full_data = expected_full_data; - let eof_pdu = create_no_error_eof(&self.expected_full_data, &self.pdu_header); - let packet_info = create_packet_info(&eof_pdu, &mut self.buf); - self.check_handler_idle_at_drop = true; - self.check_dest_file = true; - let result = self.handler.state_machine(user, Some(&packet_info)); - if self.indication_cfg().eof_recv { - assert_eq!(user.eof_recvd_call_count, 1); - } - result - } - - fn state_check(&self, state: State, step: TransactionStep) { - assert_eq!(self.handler.state(), state); - assert_eq!(self.handler.step(), step); - } - } - - impl Drop for DestHandlerTester { - fn drop(&mut self) { - if self.check_handler_idle_at_drop { - self.state_check(State::Idle, TransactionStep::Idle); - } - if self.check_dest_file { - assert!(Path::exists(&self.dest_path)); - let read_content = fs::read(&self.dest_path).expect("reading back string failed"); - assert_eq!(read_content.len() as u64, self.expected_file_size); - assert_eq!(read_content, self.expected_full_data); - assert!(fs::remove_file(self.dest_path.as_path()).is_ok()); - } - } - } - - fn init_full_filenames() -> (PathBuf, PathBuf) { - ( - tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf(), - tempfile::NamedTempFile::new() - .unwrap() - .into_temp_path() - .to_path_buf(), - ) - } - - fn basic_remote_cfg_table() -> StdRemoteEntityConfigProvider { - let mut table = StdRemoteEntityConfigProvider::default(); - let remote_entity_cfg = RemoteEntityConfig::new_with_default_values( - UnsignedByteFieldU16::new(1).into(), - 1024, - 1024, - true, - true, - TransmissionMode::Unacknowledged, - ChecksumType::Crc32, - ); - table.add_config(&remote_entity_cfg); - table - } - - fn default_dest_handler( - test_fault_handler: TestFaultHandler, - test_packet_sender: TestCfdpSender, - check_timer_expired: Arc, - ) -> DestinationHandler { - let local_entity_cfg = LocalEntityConfig { - id: REMOTE_ID.into(), - indication_cfg: IndicationConfig::default(), - default_fault_handler: DefaultFaultHandler::new(Box::new(test_fault_handler)), - }; - DestinationHandler::new( - local_entity_cfg, - 2048, - Box::new(test_packet_sender), - Box::::default(), - Box::new(basic_remote_cfg_table()), - Box::new(TestCheckTimerCreator::new(check_timer_expired)), - ) - } - - fn create_pdu_header(seq_num: impl Into) -> PduHeader { - let mut pdu_conf = - CommonPduConfig::new_with_byte_fields(LOCAL_ID, REMOTE_ID, seq_num).unwrap(); - pdu_conf.trans_mode = TransmissionMode::Unacknowledged; - PduHeader::new_no_file_data(pdu_conf, 0) - } - - fn create_metadata_pdu<'filename>( - pdu_header: &PduHeader, - src_name: &'filename Path, - dest_name: &'filename Path, - file_size: u64, - closure_requested: bool, - ) -> MetadataPduCreator<'filename, 'filename, 'static> { - let checksum_type = if file_size == 0 { - ChecksumType::NullChecksum - } else { - ChecksumType::Crc32 - }; - let metadata_params = - MetadataGenericParams::new(closure_requested, checksum_type, file_size); - MetadataPduCreator::new_no_opts( - *pdu_header, - metadata_params, - Lv::new_from_str(src_name.as_os_str().to_str().unwrap()).unwrap(), - Lv::new_from_str(dest_name.as_os_str().to_str().unwrap()).unwrap(), - ) - } - - fn create_packet_info<'a>( - pdu: &'a impl WritablePduPacket, - buf: &'a mut [u8], - ) -> PacketInfo<'a> { - let written_len = pdu - .write_to_bytes(buf) - .expect("writing metadata PDU failed"); - PacketInfo::new(&buf[..written_len]).expect("generating packet info failed") - } - - fn create_no_error_eof(file_data: &[u8], pdu_header: &PduHeader) -> EofPdu { - let crc32 = if !file_data.is_empty() { - let mut digest = CRC_32.digest(); - digest.update(file_data); - digest.finalize() - } else { - 0 - }; - EofPdu::new_no_error(*pdu_header, crc32, file_data.len() as u64) - } - - #[test] - fn test_basic() { - let fault_handler = TestFaultHandler::default(); - let test_sender = TestCfdpSender::default(); - let dest_handler = default_dest_handler(fault_handler.clone(), test_sender, Arc::default()); - assert!(dest_handler.transmission_mode().is_none()); - assert!(fault_handler.all_queues_empty()); - } - - #[test] - fn test_empty_file_transfer_not_acked_no_closure() { - let fault_handler = TestFaultHandler::default(); - let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); - let mut test_user = test_obj.test_user_from_cached_paths(0); - test_obj - .generic_transfer_init(&mut test_user, 0) - .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj - .generic_eof_no_error(&mut test_user, Vec::new()) - .expect("EOF no error insertion failed"); - assert!(fault_handler.all_queues_empty()); - assert!(test_obj.pdu_sender.queue_empty()); - test_obj.state_check(State::Idle, TransactionStep::Idle); - } - - #[test] - fn test_small_file_transfer_not_acked() { - let file_data_str = "Hello World!"; - let file_data = file_data_str.as_bytes(); - let file_size = file_data.len() as u64; - let fault_handler = TestFaultHandler::default(); - - let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); - let mut test_user = test_obj.test_user_from_cached_paths(file_size); - test_obj - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj - .generic_file_data_insert(&mut test_user, 0, file_data) - .expect("file data insertion failed"); - test_obj - .generic_eof_no_error(&mut test_user, file_data.to_vec()) - .expect("EOF no error insertion failed"); - assert!(fault_handler.all_queues_empty()); - assert!(test_obj.pdu_sender.queue_empty()); - test_obj.state_check(State::Idle, TransactionStep::Idle); - } - - #[test] - fn test_segmented_file_transfer_not_acked() { - let mut rng = rand::thread_rng(); - let mut random_data = [0u8; 512]; - rng.fill(&mut random_data); - let file_size = random_data.len() as u64; - let segment_len = 256; - let fault_handler = TestFaultHandler::default(); - - let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); - let mut test_user = test_obj.test_user_from_cached_paths(file_size); - test_obj - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj - .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) - .expect("file data insertion failed"); - test_obj - .generic_file_data_insert( - &mut test_user, - segment_len as u64, - &random_data[segment_len..], - ) - .expect("file data insertion failed"); - test_obj - .generic_eof_no_error(&mut test_user, random_data.to_vec()) - .expect("EOF no error insertion failed"); - assert!(fault_handler.all_queues_empty()); - assert!(test_obj.pdu_sender.queue_empty()); - test_obj.state_check(State::Idle, TransactionStep::Idle); - } - - #[test] - fn test_check_limit_handling_transfer_success() { - let mut rng = rand::thread_rng(); - let mut random_data = [0u8; 512]; - rng.fill(&mut random_data); - let file_size = random_data.len() as u64; - let segment_len = 256; - let fault_handler = TestFaultHandler::default(); - - let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); - let mut test_user = test_obj.test_user_from_cached_paths(file_size); - let transaction_id = test_obj - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj - .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) - .expect("file data insertion 0 failed"); - test_obj - .generic_eof_no_error(&mut test_user, random_data.to_vec()) - .expect("EOF no error insertion failed"); - test_obj.state_check( - State::Busy, - TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, - ); - test_obj - .generic_file_data_insert( - &mut test_user, - segment_len as u64, - &random_data[segment_len..], - ) - .expect("file data insertion 1 failed"); - test_obj.set_check_timer_expired(); - test_obj - .handler - .state_machine(&mut test_user, None) - .expect("fsm failure"); - - let ignored_queue = fault_handler.ignored_queue.lock().unwrap(); - assert_eq!(ignored_queue.len(), 1); - let cancelled = *ignored_queue.front().unwrap(); - assert_eq!(cancelled.0, transaction_id); - assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure); - assert_eq!(cancelled.2, segment_len as u64); - assert!(test_obj.pdu_sender.queue_empty()); - test_obj.state_check(State::Idle, TransactionStep::Idle); - } - - #[test] - fn test_check_limit_handling_limit_reached() { - let mut rng = rand::thread_rng(); - let mut random_data = [0u8; 512]; - rng.fill(&mut random_data); - let file_size = random_data.len() as u64; - let segment_len = 256; - - let fault_handler = TestFaultHandler::default(); - let mut test_obj = DestHandlerTester::new(fault_handler.clone(), false); - let mut test_user = test_obj.test_user_from_cached_paths(file_size); - let transaction_id = test_obj - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj - .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) - .expect("file data insertion 0 failed"); - test_obj - .generic_eof_no_error(&mut test_user, random_data.to_vec()) - .expect("EOF no error insertion failed"); - test_obj.state_check( - State::Busy, - TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, - ); - test_obj.set_check_timer_expired(); - test_obj - .handler - .state_machine(&mut test_user, None) - .expect("fsm error"); - test_obj.state_check( - State::Busy, - TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, - ); - test_obj.set_check_timer_expired(); - test_obj - .handler - .state_machine(&mut test_user, None) - .expect("fsm error"); - test_obj.state_check(State::Idle, TransactionStep::Idle); - - assert!(fault_handler - .notice_of_suspension_queue - .lock() - .unwrap() - .is_empty()); - - let ignored_queue = fault_handler.ignored_queue.lock().unwrap(); - assert_eq!(ignored_queue.len(), 1); - let cancelled = *ignored_queue.front().unwrap(); - assert_eq!(cancelled.0, transaction_id); - assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure); - assert_eq!(cancelled.2, segment_len as u64); - - let cancelled_queue = fault_handler.notice_of_cancellation_queue.lock().unwrap(); - assert_eq!(cancelled_queue.len(), 1); - let cancelled = *cancelled_queue.front().unwrap(); - assert_eq!(cancelled.0, transaction_id); - assert_eq!(cancelled.1, ConditionCode::CheckLimitReached); - assert_eq!(cancelled.2, segment_len as u64); - - drop(cancelled_queue); - - assert!(test_obj.pdu_sender.queue_empty()); - - // Check that the broken file exists. - test_obj.check_dest_file = false; - assert!(Path::exists(test_obj.dest_path())); - let read_content = fs::read(test_obj.dest_path()).expect("reading back string failed"); - assert_eq!(read_content.len(), segment_len); - assert_eq!(read_content, &random_data[0..segment_len]); - assert!(fs::remove_file(test_obj.dest_path().as_path()).is_ok()); - } - - fn check_finished_pdu_success(sent_pdu: &SentPdu) { - assert_eq!(sent_pdu.pdu_type, PduType::FileDirective); - assert_eq!( - sent_pdu.file_directive_type, - Some(FileDirectiveType::FinishedPdu) - ); - let finished_pdu = FinishedPduReader::from_bytes(&sent_pdu.raw_pdu).unwrap(); - assert_eq!(finished_pdu.file_status(), FileStatus::Retained); - assert_eq!(finished_pdu.condition_code(), ConditionCode::NoError); - assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete); - assert!(finished_pdu.fault_location().is_none()); - assert_eq!(finished_pdu.fs_responses_raw(), &[]); - } - - #[test] - fn test_file_transfer_with_closure() { - let fault_handler = TestFaultHandler::default(); - let mut test_obj = DestHandlerTester::new(fault_handler.clone(), true); - let mut test_user = test_obj.test_user_from_cached_paths(0); - test_obj - .generic_transfer_init(&mut test_user, 0) - .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - let sent_packets = test_obj - .generic_eof_no_error(&mut test_user, Vec::new()) - .expect("EOF no error insertion failed"); - assert_eq!(sent_packets, 1); - assert!(fault_handler.all_queues_empty()); - // The Finished PDU was sent, so the state machine is done. - test_obj.state_check(State::Idle, TransactionStep::Idle); - assert!(!test_obj.pdu_sender.queue_empty()); - let sent_pdu = test_obj.pdu_sender.retrieve_next_pdu().unwrap(); - check_finished_pdu_success(&sent_pdu); - } - - #[test] - fn test_file_transfer_with_closure_check_limit_reached() { - // TODO: Implement test. - } -} diff --git a/satrs/src/cfdp/filestore.rs b/satrs/src/cfdp/filestore.rs deleted file mode 100644 index 528d1b8..0000000 --- a/satrs/src/cfdp/filestore.rs +++ /dev/null @@ -1,769 +0,0 @@ -use alloc::string::{String, ToString}; -use core::fmt::Display; -use crc::{Crc, CRC_32_CKSUM}; -use spacepackets::cfdp::ChecksumType; -use spacepackets::ByteConversionError; -#[cfg(feature = "std")] -use std::error::Error; -use std::path::Path; -#[cfg(feature = "std")] -pub use std_mod::*; - -pub const CRC_32: Crc = Crc::::new(&CRC_32_CKSUM); - -#[derive(Debug, Clone)] -pub enum FilestoreError { - FileDoesNotExist, - FileAlreadyExists, - DirDoesNotExist, - Permission, - IsNotFile, - IsNotDirectory, - ByteConversion(ByteConversionError), - Io { - raw_errno: Option, - string: String, - }, - ChecksumTypeNotImplemented(ChecksumType), -} - -impl From for FilestoreError { - fn from(value: ByteConversionError) -> Self { - Self::ByteConversion(value) - } -} - -impl Display for FilestoreError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - FilestoreError::FileDoesNotExist => { - write!(f, "file does not exist") - } - FilestoreError::FileAlreadyExists => { - write!(f, "file already exists") - } - FilestoreError::DirDoesNotExist => { - write!(f, "directory does not exist") - } - FilestoreError::Permission => { - write!(f, "permission error") - } - FilestoreError::IsNotFile => { - write!(f, "is not a file") - } - FilestoreError::IsNotDirectory => { - write!(f, "is not a directory") - } - FilestoreError::ByteConversion(e) => { - write!(f, "filestore error: {e}") - } - FilestoreError::Io { raw_errno, string } => { - write!( - f, - "filestore generic IO error with raw errno {:?}: {}", - raw_errno, string - ) - } - FilestoreError::ChecksumTypeNotImplemented(checksum_type) => { - write!(f, "checksum {:?} not implemented", checksum_type) - } - } - } -} - -impl Error for FilestoreError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - FilestoreError::ByteConversion(e) => Some(e), - _ => None, - } - } -} - -#[cfg(feature = "std")] -impl From for FilestoreError { - fn from(value: std::io::Error) -> Self { - Self::Io { - raw_errno: value.raw_os_error(), - string: value.to_string(), - } - } -} - -pub trait VirtualFilestore { - fn create_file(&self, file_path: &str) -> Result<(), FilestoreError>; - - fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError>; - - /// Truncating a file means deleting all its data so the resulting file is empty. - /// This can be more efficient than removing and re-creating a file. - fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError>; - - fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError>; - fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError>; - - fn read_data( - &self, - file_path: &str, - offset: u64, - read_len: u64, - buf: &mut [u8], - ) -> Result<(), FilestoreError>; - - fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError>; - - fn filename_from_full_path(path: &str) -> Option<&str> - where - Self: Sized, - { - // Convert the path string to a Path - let path = Path::new(path); - - // Extract the file name using the file_name() method - path.file_name().and_then(|name| name.to_str()) - } - - fn is_file(&self, path: &str) -> bool; - - fn is_dir(&self, path: &str) -> bool { - !self.is_file(path) - } - - fn exists(&self, path: &str) -> bool; - - /// This special function is the CFDP specific abstraction to verify the checksum of a file. - /// This allows to keep OS specific details like reading the whole file in the most efficient - /// manner inside the file system abstraction. - /// - /// The passed verification buffer argument will be used by the specific implementation as - /// a buffer to read the file into. It is recommended to use common buffer sizes like - /// 4096 or 8192 bytes. - fn checksum_verify( - &self, - file_path: &str, - checksum_type: ChecksumType, - expected_checksum: u32, - verification_buf: &mut [u8], - ) -> Result; -} - -#[cfg(feature = "std")] -pub mod std_mod { - use super::*; - use std::{ - fs::{self, File, OpenOptions}, - io::{BufReader, Read, Seek, SeekFrom, Write}, - }; - - #[derive(Default)] - pub struct NativeFilestore {} - - impl VirtualFilestore for NativeFilestore { - fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> { - if self.exists(file_path) { - return Err(FilestoreError::FileAlreadyExists); - } - File::create(file_path)?; - Ok(()) - } - - fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> { - if !self.exists(file_path) { - return Err(FilestoreError::FileDoesNotExist); - } - if !self.is_file(file_path) { - return Err(FilestoreError::IsNotFile); - } - fs::remove_file(file_path)?; - Ok(()) - } - - fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> { - if !self.exists(file_path) { - return Err(FilestoreError::FileDoesNotExist); - } - if !self.is_file(file_path) { - return Err(FilestoreError::IsNotFile); - } - OpenOptions::new() - .write(true) - .truncate(true) - .open(file_path)?; - Ok(()) - } - - fn create_dir(&self, dir_path: &str) -> Result<(), FilestoreError> { - fs::create_dir(dir_path).map_err(|e| FilestoreError::Io { - raw_errno: e.raw_os_error(), - string: e.to_string(), - })?; - Ok(()) - } - - fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> { - if !self.exists(dir_path) { - return Err(FilestoreError::DirDoesNotExist); - } - if !self.is_dir(dir_path) { - return Err(FilestoreError::IsNotDirectory); - } - if !all { - fs::remove_dir(dir_path)?; - return Ok(()); - } - fs::remove_dir_all(dir_path)?; - Ok(()) - } - - fn read_data( - &self, - file_name: &str, - offset: u64, - read_len: u64, - buf: &mut [u8], - ) -> Result<(), FilestoreError> { - if buf.len() < read_len as usize { - return Err(ByteConversionError::ToSliceTooSmall { - found: buf.len(), - expected: read_len as usize, - } - .into()); - } - if !self.exists(file_name) { - return Err(FilestoreError::FileDoesNotExist); - } - if !self.is_file(file_name) { - return Err(FilestoreError::IsNotFile); - } - let mut file = File::open(file_name)?; - file.seek(SeekFrom::Start(offset))?; - file.read_exact(&mut buf[0..read_len as usize])?; - Ok(()) - } - - fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> { - if !self.exists(file) { - return Err(FilestoreError::FileDoesNotExist); - } - if !self.is_file(file) { - return Err(FilestoreError::IsNotFile); - } - let mut file = OpenOptions::new().write(true).open(file)?; - file.seek(SeekFrom::Start(offset))?; - file.write_all(buf)?; - Ok(()) - } - - fn is_file(&self, path: &str) -> bool { - let path = Path::new(path); - path.is_file() - } - - fn exists(&self, path: &str) -> bool { - let path = Path::new(path); - if !path.exists() { - return false; - } - true - } - - fn checksum_verify( - &self, - file_path: &str, - checksum_type: ChecksumType, - expected_checksum: u32, - verification_buf: &mut [u8], - ) -> Result { - match checksum_type { - ChecksumType::Modular => { - if self.calc_modular_checksum(file_path)? == expected_checksum { - return Ok(true); - } - Ok(false) - } - ChecksumType::Crc32 => { - let mut digest = CRC_32.digest(); - let file_to_check = File::open(file_path)?; - let mut buf_reader = BufReader::new(file_to_check); - loop { - let bytes_read = buf_reader.read(verification_buf)?; - if bytes_read == 0 { - break; - } - digest.update(&verification_buf[0..bytes_read]); - } - if digest.finalize() == expected_checksum { - return Ok(true); - } - Ok(false) - } - ChecksumType::NullChecksum => Ok(true), - _ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)), - } - } - } - - impl NativeFilestore { - pub fn calc_modular_checksum(&self, file_path: &str) -> Result { - let mut checksum: u32 = 0; - let file = File::open(file_path)?; - let mut buf_reader = BufReader::new(file); - let mut buffer = [0; 4]; - - loop { - let bytes_read = buf_reader.read(&mut buffer)?; - if bytes_read == 0 { - break; - } - // Perform padding directly in the buffer - (bytes_read..4).for_each(|i| { - buffer[i] = 0; - }); - - checksum = checksum.wrapping_add(u32::from_be_bytes(buffer)); - } - Ok(checksum) - } - } -} - -#[cfg(test)] -mod tests { - use std::{fs, path::Path, println}; - - use super::*; - use alloc::format; - use tempfile::tempdir; - - const EXAMPLE_DATA_CFDP: [u8; 15] = [ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, - ]; - - const NATIVE_FS: NativeFilestore = NativeFilestore {}; - - #[test] - fn test_basic_native_filestore_create() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - let result = - NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed")); - assert!(result.is_ok()); - let path = Path::new(&file_path); - assert!(path.exists()); - assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); - } - - #[test] - fn test_basic_native_fs_file_exists() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); - } - - #[test] - fn test_basic_native_fs_dir_exists() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let dir_path = tmpdir.path().join("testdir"); - assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap())); - NATIVE_FS - .create_dir(dir_path.to_str().expect("getting str for file failed")) - .unwrap(); - assert!(NATIVE_FS.exists(dir_path.to_str().unwrap())); - assert!(NATIVE_FS.is_dir(dir_path.as_path().to_str().unwrap())); - } - - #[test] - fn test_basic_native_fs_remove_file() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .expect("creating file failed"); - assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); - NATIVE_FS - .remove_file(file_path.to_str().unwrap()) - .expect("removing file failed"); - assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); - } - - #[test] - fn test_basic_native_fs_write() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); - println!("{}", file_path.to_str().unwrap()); - let write_data = "hello world\n"; - NATIVE_FS - .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes()) - .expect("writing to file failed"); - let read_back = fs::read_to_string(file_path).expect("reading back data failed"); - assert_eq!(read_back, write_data); - } - - #[test] - fn test_basic_native_fs_read() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - assert!(NATIVE_FS.exists(file_path.to_str().unwrap())); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap())); - println!("{}", file_path.to_str().unwrap()); - let write_data = "hello world\n"; - NATIVE_FS - .write_data(file_path.to_str().unwrap(), 0, write_data.as_bytes()) - .expect("writing to file failed"); - let read_back = fs::read_to_string(file_path).expect("reading back data failed"); - assert_eq!(read_back, write_data); - } - - #[test] - fn test_truncate_file() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .expect("creating file failed"); - fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap(); - assert_eq!(fs::read(file_path.clone()).unwrap(), [1, 2, 3, 4]); - NATIVE_FS - .truncate_file(file_path.to_str().unwrap()) - .unwrap(); - assert_eq!(fs::read(file_path.clone()).unwrap(), []); - } - - #[test] - fn test_remove_dir() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let dir_path = tmpdir.path().join("testdir"); - assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap())); - NATIVE_FS - .create_dir(dir_path.to_str().expect("getting str for file failed")) - .unwrap(); - assert!(NATIVE_FS.exists(dir_path.to_str().unwrap())); - NATIVE_FS - .remove_dir(dir_path.to_str().unwrap(), false) - .unwrap(); - assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap())); - } - - #[test] - fn test_read_file() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .expect("creating file failed"); - fs::write(file_path.clone(), [1, 2, 3, 4]).unwrap(); - let read_buf: &mut [u8] = &mut [0; 4]; - NATIVE_FS - .read_data(file_path.to_str().unwrap(), 0, 4, read_buf) - .unwrap(); - assert_eq!([1, 2, 3, 4], read_buf); - NATIVE_FS - .write_data(file_path.to_str().unwrap(), 4, &[5, 6, 7, 8]) - .expect("writing to file failed"); - NATIVE_FS - .read_data(file_path.to_str().unwrap(), 2, 4, read_buf) - .unwrap(); - assert_eq!([3, 4, 5, 6], read_buf); - } - - #[test] - fn test_remove_which_does_not_exist() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 4, &mut [0; 4]); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::FileDoesNotExist = error { - assert_eq!(error.to_string(), "file does not exist"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_file_already_exists() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - let result = - NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed")); - assert!(result.is_ok()); - let result = - NATIVE_FS.create_file(file_path.to_str().expect("getting str for file failed")); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::FileAlreadyExists = error { - assert_eq!(error.to_string(), "file already exists"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_remove_file_with_dir_api() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::IsNotDirectory = error { - assert_eq!(error.to_string(), "is not a directory"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_remove_dir_remove_all() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let dir_path = tmpdir.path().join("test"); - NATIVE_FS - .create_dir(dir_path.to_str().expect("getting str for file failed")) - .unwrap(); - let file_path = dir_path.as_path().join("test.txt"); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - let result = NATIVE_FS.remove_dir(dir_path.to_str().unwrap(), true); - assert!(result.is_ok()); - assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap())); - } - - #[test] - fn test_remove_dir_with_file_api() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test"); - NATIVE_FS - .create_dir(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - let result = NATIVE_FS.remove_file(file_path.to_str().unwrap()); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::IsNotFile = error { - assert_eq!(error.to_string(), "is not a file"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_remove_dir_which_does_not_exist() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test"); - let result = NATIVE_FS.remove_dir(file_path.to_str().unwrap(), true); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::DirDoesNotExist = error { - assert_eq!(error.to_string(), "directory does not exist"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_remove_file_which_does_not_exist() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - let result = NATIVE_FS.remove_file(file_path.to_str().unwrap()); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::FileDoesNotExist = error { - assert_eq!(error.to_string(), "file does not exist"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_truncate_file_which_does_not_exist() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap()); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::FileDoesNotExist = error { - assert_eq!(error.to_string(), "file does not exist"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_truncate_file_on_directory() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test"); - NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap(); - let result = NATIVE_FS.truncate_file(file_path.to_str().unwrap()); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::IsNotFile = error { - assert_eq!(error.to_string(), "is not a file"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_byte_conversion_error_when_reading() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - let result = NATIVE_FS.read_data(file_path.to_str().unwrap(), 0, 2, &mut []); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::ByteConversion(byte_conv_error) = error { - if let ByteConversionError::ToSliceTooSmall { found, expected } = byte_conv_error { - assert_eq!(found, 0); - assert_eq!(expected, 2); - } else { - panic!("unexpected error"); - } - assert_eq!( - error.to_string(), - format!("filestore error: {}", byte_conv_error) - ); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_read_file_on_dir() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let dir_path = tmpdir.path().join("test"); - NATIVE_FS - .create_dir(dir_path.to_str().expect("getting str for file failed")) - .unwrap(); - let result = NATIVE_FS.read_data(dir_path.to_str().unwrap(), 0, 4, &mut [0; 4]); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::IsNotFile = error { - assert_eq!(error.to_string(), "is not a file"); - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_write_file_non_existing() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::FileDoesNotExist = error { - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_write_file_on_dir() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test"); - NATIVE_FS.create_dir(file_path.to_str().unwrap()).unwrap(); - let result = NATIVE_FS.write_data(file_path.to_str().unwrap(), 0, &[]); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::IsNotFile = error { - } else { - panic!("unexpected error"); - } - } - - #[test] - fn test_filename_extraction() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("test.txt"); - NATIVE_FS - .create_file(file_path.to_str().expect("getting str for file failed")) - .unwrap(); - NativeFilestore::filename_from_full_path(file_path.to_str().unwrap()); - } - - #[test] - fn test_modular_checksum() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("mod-crc.bin"); - fs::write(file_path.as_path(), EXAMPLE_DATA_CFDP).expect("writing test file failed"); - // Kind of re-writing the modular checksum impl here which we are trying to test, but the - // numbers/correctness were verified manually using calculators, so this is okay. - let mut checksum: u32 = 0; - let mut buffer: [u8; 4] = [0; 4]; - for i in 0..3 { - buffer = EXAMPLE_DATA_CFDP[i * 4..(i + 1) * 4].try_into().unwrap(); - checksum = checksum.wrapping_add(u32::from_be_bytes(buffer)); - } - buffer[0..3].copy_from_slice(&EXAMPLE_DATA_CFDP[12..15]); - buffer[3] = 0; - checksum = checksum.wrapping_add(u32::from_be_bytes(buffer)); - let mut verif_buf: [u8; 32] = [0; 32]; - let result = NATIVE_FS.checksum_verify( - file_path.to_str().unwrap(), - ChecksumType::Modular, - checksum, - &mut verif_buf, - ); - assert!(result.is_ok()); - } - - #[test] - fn test_null_checksum_impl() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("mod-crc.bin"); - // The file to check does not even need to exist, and the verification buffer can be - // empty: the null checksum is always yields the same result. - let result = NATIVE_FS.checksum_verify( - file_path.to_str().unwrap(), - ChecksumType::NullChecksum, - 0, - &mut [], - ); - assert!(result.is_ok()); - assert!(result.unwrap()); - } - - #[test] - fn test_checksum_not_implemented() { - let tmpdir = tempdir().expect("creating tmpdir failed"); - let file_path = tmpdir.path().join("mod-crc.bin"); - // The file to check does not even need to exist, and the verification buffer can be - // empty: the null checksum is always yields the same result. - let result = NATIVE_FS.checksum_verify( - file_path.to_str().unwrap(), - ChecksumType::Crc32Proximity1, - 0, - &mut [], - ); - assert!(result.is_err()); - let error = result.unwrap_err(); - if let FilestoreError::ChecksumTypeNotImplemented(cksum_type) = error { - assert_eq!( - error.to_string(), - format!("checksum {:?} not implemented", cksum_type) - ); - } else { - panic!("unexpected error"); - } - } -} diff --git a/satrs/src/cfdp/mod.rs b/satrs/src/cfdp/mod.rs deleted file mode 100644 index c2f6d01..0000000 --- a/satrs/src/cfdp/mod.rs +++ /dev/null @@ -1,668 +0,0 @@ -//! This module contains the implementation of the CFDP high level classes as specified in the -//! CCSDS 727.0-B-5. -use core::{cell::RefCell, fmt::Debug, hash::Hash}; - -use crc::{Crc, CRC_32_CKSUM}; -use hashbrown::HashMap; -use spacepackets::{ - cfdp::{ - pdu::{FileDirectiveType, PduError, PduHeader}, - ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode, - }, - util::{UnsignedByteField, UnsignedEnum}, -}; - -#[cfg(feature = "alloc")] -use alloc::boxed::Box; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::time::CountdownProvider; - -#[cfg(feature = "std")] -pub mod dest; -#[cfg(feature = "alloc")] -pub mod filestore; -#[cfg(feature = "std")] -pub mod source; -pub mod user; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum EntityType { - Sending, - Receiving, -} - -pub enum TimerContext { - CheckLimit { - local_id: UnsignedByteField, - remote_id: UnsignedByteField, - entity_type: EntityType, - }, - NakActivity { - expiry_time_seconds: f32, - }, - PositiveAck { - expiry_time_seconds: f32, - }, -} - -/// A generic trait which allows CFDP entities to create check timers which are required to -/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2 -/// and 4.6.3.3. -/// -/// This trait also allows the creation of different check timers depending on context and purpose -/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or -/// other factors. -/// -/// The countdown timer is used by 3 mechanisms of the CFDP protocol. -/// -/// ## 1. Check limit handling -/// -/// The first mechanism is the check limit handling for unacknowledged transfers as specified -/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard. -/// For this mechanism, the timer has different functionality depending on whether -/// the using entity is the sending entity or the receiving entity for the unacknowledged -/// transmission mode. -/// -/// For the sending entity, this timer determines the expiry period for declaring a check limit -/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer. -/// Also see 4.6.3.2 of the CFDP standard. -/// -/// For the receiving entity, this timer determines the expiry period for incrementing a check -/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order -/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. -/// -/// ## 2. NAK activity limit -/// -/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP -/// standard. The expiration period will be provided by the NAK timer expiration limit of the -/// remote entity configuration. -/// -/// ## 3. Positive ACK procedures -/// -/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in -/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer -/// interval of the remote entity configuration. -#[cfg(feature = "alloc")] -pub trait CheckTimerCreator { - fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box; -} - -/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime. -/// It also assumes that a second accuracy of the check timer period is sufficient. -#[cfg(feature = "std")] -#[derive(Debug)] -pub struct StdCheckTimer { - expiry_time_seconds: u64, - start_time: std::time::Instant, -} - -#[cfg(feature = "std")] -impl StdCheckTimer { - pub fn new(expiry_time_seconds: u64) -> Self { - Self { - expiry_time_seconds, - start_time: std::time::Instant::now(), - } - } -} - -#[cfg(feature = "std")] -impl CountdownProvider for StdCheckTimer { - fn has_expired(&self) -> bool { - let elapsed_time = self.start_time.elapsed(); - if elapsed_time.as_secs() > self.expiry_time_seconds { - return true; - } - false - } - - fn reset(&mut self) { - self.start_time = std::time::Instant::now(); - } -} - -/// This structure models the remote entity configuration information as specified in chapter 8.3 -/// of the CFDP standard. - -/// Some of the fields which were not considered necessary for the Rust implementation -/// were omitted. Some other fields which are not contained inside the standard but are considered -/// necessary for the Rust implementation are included. -/// -/// ## Notes on Positive Acknowledgment Procedures -/// -/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will -/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending -/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU). -/// Once the expected ACK response has not been received for that interval, as counter will be -/// incremented and the timer will be reset. Once the counter exceeds the -/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared. -/// -/// ## Notes on Deferred Lost Segment Procedures -/// -/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After -/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is -/// reset when missing segments or missing metadata is received. The timer will be deactivated if -/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a -/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared. -/// -/// ## Fields -/// -/// * `entity_id` - The ID of the remote entity. -/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition -/// to the `max_file_segment_len` attribute which also determines the size of file data PDUs. -/// * `max_file_segment_len` The maximum file segment length which determines the maximum size -/// of file data PDUs in addition to the `max_packet_len` attribute. If this field is set -/// to None, the maximum file segment length will be derived from the maximum packet length. -/// If this has some value which is smaller than the segment value derived from -/// `max_packet_len`, this value will be picked. -/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of -/// the Put Request, it will be determined from this field in the remote configuration. -/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put -/// Request, it will be determined from this field in the remote configuration. -/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the -/// Put Request, it will be determined from this field in the remote configuration. -/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on -/// transaction cancellation. Defaults to False. -/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to -/// this remote entity. -/// * `check_limit` - This timer determines the expiry period for incrementing a check counter -/// after an EOF PDU is received for an incomplete file transfer. This allows out-of-order -/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to -/// 2, so the check limit timer may expire twice. -/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment -/// Procedures inside the class documentation. Expected as floating point seconds. Defaults to -/// 10 seconds. -/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment -/// Procedures inside the class documentation. Defaults to 2, so the timer may expire twice. -/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a -/// file data gap or lost metadata is detected in the acknowledged mode. Defaults to True. -/// * `nak_timer_interval_seconds` - See the notes on the Deferred Lost Segment Procedure inside -/// the class documentation. Expected as floating point seconds. Defaults to 10 seconds. -/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside -/// the class documentation. Defaults to 2, so the timer may expire two times. -#[derive(Debug, Copy, Clone)] -pub struct RemoteEntityConfig { - pub entity_id: UnsignedByteField, - pub max_packet_len: usize, - pub max_file_segment_len: usize, - pub closure_requested_by_default: bool, - pub crc_on_transmission_by_default: bool, - pub default_transmission_mode: TransmissionMode, - pub default_crc_type: ChecksumType, - pub positive_ack_timer_interval_seconds: f32, - pub positive_ack_timer_expiration_limit: u32, - pub check_limit: u32, - pub disposition_on_cancellation: bool, - pub immediate_nak_mode: bool, - pub nak_timer_interval_seconds: f32, - pub nak_timer_expiration_limit: u32, -} - -impl RemoteEntityConfig { - pub fn new_with_default_values( - entity_id: UnsignedByteField, - max_file_segment_len: usize, - max_packet_len: usize, - closure_requested_by_default: bool, - crc_on_transmission_by_default: bool, - default_transmission_mode: TransmissionMode, - default_crc_type: ChecksumType, - ) -> Self { - Self { - entity_id, - max_file_segment_len, - max_packet_len, - closure_requested_by_default, - crc_on_transmission_by_default, - default_transmission_mode, - default_crc_type, - check_limit: 2, - positive_ack_timer_interval_seconds: 10.0, - positive_ack_timer_expiration_limit: 2, - disposition_on_cancellation: false, - immediate_nak_mode: true, - nak_timer_interval_seconds: 10.0, - nak_timer_expiration_limit: 2, - } - } -} - -pub trait RemoteEntityConfigProvider { - /// Retrieve the remote entity configuration for the given remote ID. - fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig>; - fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>; - /// Add a new remote configuration. Return [true] if the configuration was - /// inserted successfully, and [false] if a configuration already exists. - fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool; - /// Remote a configuration. Returns [true] if the configuration was removed successfully, - /// and [false] if no configuration exists for the given remote ID. - fn remove_config(&mut self, remote_id: u64) -> bool; -} - -#[cfg(feature = "std")] -#[derive(Default)] -pub struct StdRemoteEntityConfigProvider { - remote_cfg_table: HashMap, -} - -#[cfg(feature = "std")] -impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider { - fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> { - self.remote_cfg_table.get(&remote_id) - } - fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> { - self.remote_cfg_table.get_mut(&remote_id) - } - fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool { - self.remote_cfg_table - .insert(cfg.entity_id.value(), *cfg) - .is_some() - } - fn remove_config(&mut self, remote_id: u64) -> bool { - self.remote_cfg_table.remove(&remote_id).is_some() - } -} - -/// This trait introduces some callbacks which will be called when a particular CFDP fault -/// handler is called. -/// -/// It is passed into the CFDP handlers as part of the [DefaultFaultHandler] and the local entity -/// configuration and provides a way to specify custom user error handlers. This allows to -/// implement some CFDP features like fault handler logging, which would not be possible -/// generically otherwise. -/// -/// For each error reported by the [DefaultFaultHandler], the appropriate fault handler callback -/// will be called depending on the [FaultHandlerCode]. -pub trait UserFaultHandler { - fn notice_of_suspension_cb( - &mut self, - transaction_id: TransactionId, - cond: ConditionCode, - progress: u64, - ); - - fn notice_of_cancellation_cb( - &mut self, - transaction_id: TransactionId, - cond: ConditionCode, - progress: u64, - ); - - fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64); - - fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64); -} - -/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP -/// standard. -/// -/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler -/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used -/// to select the error handling inside the CFDP handler itself in addition to dispatching to a -/// user-provided callback function provided by the [UserFaultHandler]. -/// -/// Some note on the provided default settings: -/// -/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers, -/// cancelling the transfer immediately would interfere with the check limit mechanism specified -/// in chapter 4.6.3.3. -/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is -/// not supported the file transfer might still have worked properly. -/// -/// For all other faults, the default fault handling operation will be to cancel the transaction. -/// These defaults can be overriden by using the [Self::set_fault_handler] method. -/// Please note that in any case, fault handler overrides can be specified by the sending CFDP -/// entity. -pub struct DefaultFaultHandler { - handler_array: [FaultHandlerCode; 10], - // Could also change the user fault handler trait to have non mutable methods, but that limits - // flexbility on the user side.. - user_fault_handler: RefCell>, -} - -impl DefaultFaultHandler { - fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option { - Some(match conditon_code { - ConditionCode::PositiveAckLimitReached => 0, - ConditionCode::KeepAliveLimitReached => 1, - ConditionCode::InvalidTransmissionMode => 2, - ConditionCode::FilestoreRejection => 3, - ConditionCode::FileChecksumFailure => 4, - ConditionCode::FileSizeError => 5, - ConditionCode::NakLimitReached => 6, - ConditionCode::InactivityDetected => 7, - ConditionCode::CheckLimitReached => 8, - ConditionCode::UnsupportedChecksumType => 9, - _ => return None, - }) - } - - pub fn set_fault_handler( - &mut self, - condition_code: ConditionCode, - fault_handler: FaultHandlerCode, - ) { - let array_idx = Self::condition_code_to_array_index(condition_code); - if array_idx.is_none() { - return; - } - self.handler_array[array_idx.unwrap()] = fault_handler; - } - - pub fn new(user_fault_handler: Box) -> Self { - let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10]; - init_array - [Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] = - FaultHandlerCode::IgnoreError; - init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType) - .unwrap()] = FaultHandlerCode::IgnoreError; - Self { - handler_array: init_array, - user_fault_handler: RefCell::new(user_fault_handler), - } - } - - pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode { - let array_idx = Self::condition_code_to_array_index(condition_code); - if array_idx.is_none() { - return FaultHandlerCode::IgnoreError; - } - self.handler_array[array_idx.unwrap()] - } - - pub fn report_fault( - &self, - transaction_id: TransactionId, - condition: ConditionCode, - progress: u64, - ) -> FaultHandlerCode { - let array_idx = Self::condition_code_to_array_index(condition); - if array_idx.is_none() { - return FaultHandlerCode::IgnoreError; - } - let fh_code = self.handler_array[array_idx.unwrap()]; - let mut handler_mut = self.user_fault_handler.borrow_mut(); - match fh_code { - FaultHandlerCode::NoticeOfCancellation => { - handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress); - } - FaultHandlerCode::NoticeOfSuspension => { - handler_mut.notice_of_suspension_cb(transaction_id, condition, progress); - } - FaultHandlerCode::IgnoreError => { - handler_mut.ignore_cb(transaction_id, condition, progress); - } - FaultHandlerCode::AbandonTransaction => { - handler_mut.abandoned_cb(transaction_id, condition, progress); - } - } - fh_code - } -} - -pub struct IndicationConfig { - pub eof_sent: bool, - pub eof_recv: bool, - pub file_segment_recv: bool, - pub transaction_finished: bool, - pub suspended: bool, - pub resumed: bool, -} - -impl Default for IndicationConfig { - fn default() -> Self { - Self { - eof_sent: true, - eof_recv: true, - file_segment_recv: true, - transaction_finished: true, - suspended: true, - resumed: true, - } - } -} - -pub struct LocalEntityConfig { - pub id: UnsignedByteField, - pub indication_cfg: IndicationConfig, - pub default_fault_handler: DefaultFaultHandler, -} - -/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence -/// number of that transfer which is also determined by the CFDP source entity. -#[derive(Debug, Eq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct TransactionId { - source_id: UnsignedByteField, - seq_num: UnsignedByteField, -} - -impl TransactionId { - pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self { - Self { source_id, seq_num } - } - - pub fn source_id(&self) -> &UnsignedByteField { - &self.source_id - } - - pub fn seq_num(&self) -> &UnsignedByteField { - &self.seq_num - } -} - -impl Hash for TransactionId { - fn hash(&self, state: &mut H) { - self.source_id.value().hash(state); - self.seq_num.value().hash(state); - } -} - -impl PartialEq for TransactionId { - fn eq(&self, other: &Self) -> bool { - self.source_id.value() == other.source_id.value() - && self.seq_num.value() == other.seq_num.value() - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum TransactionStep { - Idle = 0, - TransactionStart = 1, - ReceivingFileDataPdus = 2, - ReceivingFileDataPdusWithCheckLimitHandling = 3, - SendingAckPdu = 4, - TransferCompletion = 5, - SendingFinishedPdu = 6, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum State { - Idle = 0, - Busy = 1, - Suspended = 2, -} - -pub const CRC_32: Crc = Crc::::new(&CRC_32_CKSUM); - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum PacketTarget { - SourceEntity, - DestEntity, -} - -/// This is a helper struct which contains base information about a particular PDU packet. -/// This is also necessary information for CFDP packet routing. For example, some packet types -/// like file data PDUs can only be used by CFDP source entities. -pub struct PacketInfo<'raw_packet> { - pdu_type: PduType, - pdu_directive: Option, - target: PacketTarget, - raw_packet: &'raw_packet [u8], -} - -impl<'raw> PacketInfo<'raw> { - pub fn new(raw_packet: &'raw [u8]) -> Result { - let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?; - if pdu_header.pdu_type() == PduType::FileData { - return Ok(Self { - pdu_type: pdu_header.pdu_type(), - pdu_directive: None, - target: PacketTarget::DestEntity, - raw_packet, - }); - } - if pdu_header.pdu_datafield_len() < 1 { - return Err(PduError::FormatError); - } - // Route depending on PDU type and directive type if applicable. Retrieve directive type - // from the raw stream for better performance (with sanity and directive code check). - // The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding - // procedure. - let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| { - PduError::InvalidDirectiveType { - found: raw_packet[header_len], - expected: None, - } - })?; - let packet_target = match directive { - // Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a. - // the source handler - FileDirectiveType::NakPdu - | FileDirectiveType::FinishedPdu - | FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity, - // Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a. - // the destination handler - FileDirectiveType::MetadataPdu - | FileDirectiveType::EofPdu - | FileDirectiveType::PromptPdu => PacketTarget::DestEntity, - // Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply - // extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to - // the source handler, for a Finished PDU, it is passed to the destination handler. - FileDirectiveType::AckPdu => { - let acked_directive = FileDirectiveType::try_from(raw_packet[header_len + 1]) - .map_err(|_| PduError::InvalidDirectiveType { - found: raw_packet[header_len], - expected: None, - })?; - if acked_directive == FileDirectiveType::EofPdu { - PacketTarget::SourceEntity - } else if acked_directive == FileDirectiveType::FinishedPdu { - PacketTarget::DestEntity - } else { - // TODO: Maybe a better error? This might be confusing.. - return Err(PduError::InvalidDirectiveType { - found: raw_packet[header_len + 1], - expected: None, - }); - } - } - }; - Ok(Self { - pdu_type: pdu_header.pdu_type(), - pdu_directive: Some(directive), - target: packet_target, - raw_packet, - }) - } - - pub fn pdu_type(&self) -> PduType { - self.pdu_type - } - - pub fn pdu_directive(&self) -> Option { - self.pdu_directive - } - - pub fn target(&self) -> PacketTarget { - self.target - } - - pub fn raw_packet(&self) -> &[u8] { - self.raw_packet - } -} - -#[cfg(test)] -mod tests { - use spacepackets::cfdp::{ - lv::Lv, - pdu::{ - eof::EofPdu, - file_data::FileDataPdu, - metadata::{MetadataGenericParams, MetadataPduCreator}, - CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket, - }, - PduType, - }; - - use crate::cfdp::PacketTarget; - - use super::PacketInfo; - - fn generic_pdu_header() -> PduHeader { - let pdu_conf = CommonPduConfig::default(); - PduHeader::new_no_file_data(pdu_conf, 0) - } - - #[test] - fn test_metadata_pdu_info() { - let mut buf: [u8; 128] = [0; 128]; - let pdu_header = generic_pdu_header(); - let metadata_params = MetadataGenericParams::default(); - let src_file_name = "hello.txt"; - let dest_file_name = "hello-dest.txt"; - let src_lv = Lv::new_from_str(src_file_name).unwrap(); - let dest_lv = Lv::new_from_str(dest_file_name).unwrap(); - let metadata_pdu = - MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv); - metadata_pdu - .write_to_bytes(&mut buf) - .expect("writing metadata PDU failed"); - - let packet_info = PacketInfo::new(&buf).expect("creating packet info failed"); - assert_eq!(packet_info.pdu_type(), PduType::FileDirective); - assert!(packet_info.pdu_directive().is_some()); - assert_eq!( - packet_info.pdu_directive().unwrap(), - FileDirectiveType::MetadataPdu - ); - assert_eq!(packet_info.target(), PacketTarget::DestEntity); - } - - #[test] - fn test_filedata_pdu_info() { - let mut buf: [u8; 128] = [0; 128]; - let pdu_header = generic_pdu_header(); - let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]); - file_data_pdu - .write_to_bytes(&mut buf) - .expect("writing file data PDU failed"); - let packet_info = PacketInfo::new(&buf).expect("creating packet info failed"); - assert_eq!(packet_info.pdu_type(), PduType::FileData); - assert!(packet_info.pdu_directive().is_none()); - assert_eq!(packet_info.target(), PacketTarget::DestEntity); - } - - #[test] - fn test_eof_pdu_info() { - let mut buf: [u8; 128] = [0; 128]; - let pdu_header = generic_pdu_header(); - let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0); - eof_pdu - .write_to_bytes(&mut buf) - .expect("writing file data PDU failed"); - let packet_info = PacketInfo::new(&buf).expect("creating packet info failed"); - assert_eq!(packet_info.pdu_type(), PduType::FileDirective); - assert!(packet_info.pdu_directive().is_some()); - assert_eq!( - packet_info.pdu_directive().unwrap(), - FileDirectiveType::EofPdu - ); - } -} diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs deleted file mode 100644 index 433f4d2..0000000 --- a/satrs/src/cfdp/source.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![allow(dead_code)] -use spacepackets::util::UnsignedByteField; - -pub struct SourceHandler { - id: UnsignedByteField, -} - -impl SourceHandler { - pub fn new(id: impl Into) -> Self { - Self { id: id.into() } - } -} - -#[cfg(test)] -mod tests {} diff --git a/satrs/src/cfdp/user.rs b/satrs/src/cfdp/user.rs deleted file mode 100644 index 65ce909..0000000 --- a/satrs/src/cfdp/user.rs +++ /dev/null @@ -1,96 +0,0 @@ -use spacepackets::{ - cfdp::{ - pdu::{ - file_data::SegmentMetadata, - finished::{DeliveryCode, FileStatus}, - }, - tlv::{msg_to_user::MsgToUserTlv, WritableTlv}, - ConditionCode, - }, - util::UnsignedByteField, -}; - -use super::TransactionId; - -#[derive(Debug, Copy, Clone)] -pub struct TransactionFinishedParams { - pub id: TransactionId, - pub condition_code: ConditionCode, - pub delivery_code: DeliveryCode, - pub file_status: FileStatus, -} - -#[derive(Debug)] -pub struct MetadataReceivedParams<'src_file, 'dest_file, 'msgs_to_user> { - pub id: TransactionId, - pub source_id: UnsignedByteField, - pub file_size: u64, - pub src_file_name: &'src_file str, - pub dest_file_name: &'dest_file str, - pub msgs_to_user: &'msgs_to_user [MsgToUserTlv<'msgs_to_user>], -} - -#[cfg(feature = "alloc")] -#[derive(Debug)] -pub struct OwnedMetadataRecvdParams { - pub id: TransactionId, - pub source_id: UnsignedByteField, - pub file_size: u64, - pub src_file_name: alloc::string::String, - pub dest_file_name: alloc::string::String, - pub msgs_to_user: alloc::vec::Vec>, -} - -#[cfg(feature = "alloc")] -impl From> for OwnedMetadataRecvdParams { - fn from(value: MetadataReceivedParams) -> Self { - Self::from(&value) - } -} - -#[cfg(feature = "alloc")] -impl From<&MetadataReceivedParams<'_, '_, '_>> for OwnedMetadataRecvdParams { - fn from(value: &MetadataReceivedParams) -> Self { - Self { - id: value.id, - source_id: value.source_id, - file_size: value.file_size, - src_file_name: value.src_file_name.into(), - dest_file_name: value.dest_file_name.into(), - msgs_to_user: value.msgs_to_user.iter().map(|tlv| tlv.to_vec()).collect(), - } - } -} - -#[derive(Debug)] -pub struct FileSegmentRecvdParams<'seg_meta> { - pub id: TransactionId, - pub offset: u64, - pub length: usize, - pub segment_metadata: Option<&'seg_meta SegmentMetadata<'seg_meta>>, -} - -pub trait CfdpUser { - fn transaction_indication(&mut self, id: &TransactionId); - fn eof_sent_indication(&mut self, id: &TransactionId); - fn transaction_finished_indication(&mut self, finished_params: &TransactionFinishedParams); - fn metadata_recvd_indication(&mut self, md_recvd_params: &MetadataReceivedParams); - fn file_segment_recvd_indication(&mut self, segment_recvd_params: &FileSegmentRecvdParams); - // TODO: The standard does not strictly specify how the report information looks.. - fn report_indication(&mut self, id: &TransactionId); - fn suspended_indication(&mut self, id: &TransactionId, condition_code: ConditionCode); - fn resumed_indication(&mut self, id: &TransactionId, progress: u64); - fn fault_indication( - &mut self, - id: &TransactionId, - condition_code: ConditionCode, - progress: u64, - ); - fn abandoned_indication( - &mut self, - id: &TransactionId, - condition_code: ConditionCode, - progress: u64, - ); - fn eof_recvd_indication(&mut self, id: &TransactionId); -} diff --git a/satrs/src/lib.rs b/satrs/src/lib.rs index 9516289..6e2bef0 100644 --- a/satrs/src/lib.rs +++ b/satrs/src/lib.rs @@ -22,8 +22,6 @@ extern crate downcast_rs; #[cfg(any(feature = "std", test))] extern crate std; -#[cfg(feature = "alloc")] -pub mod cfdp; pub mod encoding; pub mod event_man; pub mod events; @@ -38,7 +36,6 @@ pub mod pus; pub mod queue; pub mod request; pub mod res_code; -pub mod seq_count; pub mod time; pub mod tmtc; diff --git a/satrs/src/pool.rs b/satrs/src/pool.rs index e4c2776..5900a2a 100644 --- a/satrs/src/pool.rs +++ b/satrs/src/pool.rs @@ -1584,23 +1584,21 @@ mod tests { mod heapless_tests { use super::*; use crate::static_subpool; - use core::mem::MaybeUninit; + use core::ptr::addr_of_mut; const SUBPOOL_1_BLOCK_SIZE: usize = 4; const SUBPOOL_1_NUM_ELEMENTS: u16 = 4; - static mut SUBPOOL_1: MaybeUninit< - [u8; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE], - > = MaybeUninit::new([0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE]); - static mut SUBPOOL_1_SIZES: MaybeUninit<[usize; SUBPOOL_1_NUM_ELEMENTS as usize]> = - MaybeUninit::new([STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize]); + static mut SUBPOOL_1: [u8; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE] = + [0; SUBPOOL_1_NUM_ELEMENTS as usize * SUBPOOL_1_BLOCK_SIZE]; + static mut SUBPOOL_1_SIZES: [usize; SUBPOOL_1_NUM_ELEMENTS as usize] = + [STORE_FREE; SUBPOOL_1_NUM_ELEMENTS as usize]; const SUBPOOL_2_NUM_ELEMENTS: u16 = 2; const SUBPOOL_2_BLOCK_SIZE: usize = 8; - static mut SUBPOOL_2: MaybeUninit< - [u8; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE], - > = MaybeUninit::new([0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE]); - static mut SUBPOOL_2_SIZES: MaybeUninit<[usize; SUBPOOL_2_NUM_ELEMENTS as usize]> = - MaybeUninit::new([STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]); + static mut SUBPOOL_2: [u8; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE] = + [0; SUBPOOL_2_NUM_ELEMENTS as usize * SUBPOOL_2_BLOCK_SIZE]; + static mut SUBPOOL_2_SIZES: [usize; SUBPOOL_2_NUM_ELEMENTS as usize] = + [STORE_FREE; SUBPOOL_2_NUM_ELEMENTS as usize]; const SUBPOOL_3_NUM_ELEMENTS: u16 = 1; const SUBPOOL_3_BLOCK_SIZE: usize = 16; @@ -1643,18 +1641,18 @@ mod tests { StaticHeaplessMemoryPool::new(false); assert!(heapless_pool .grow( - unsafe { SUBPOOL_1.assume_init_mut() }, - unsafe { SUBPOOL_1_SIZES.assume_init_mut() }, + unsafe { &mut *addr_of_mut!(SUBPOOL_1) }, + unsafe { &mut *addr_of_mut!(SUBPOOL_1_SIZES) }, SUBPOOL_1_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool .grow( - unsafe { SUBPOOL_2.assume_init_mut() }, - unsafe { SUBPOOL_2_SIZES.assume_init_mut() }, + unsafe { &mut *addr_of_mut!(SUBPOOL_2) }, + unsafe { &mut *addr_of_mut!(SUBPOOL_2_SIZES) }, SUBPOOL_2_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool @@ -1662,7 +1660,7 @@ mod tests { unsafe { SUBPOOL_3.assume_init_mut() }, unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, SUBPOOL_3_NUM_ELEMENTS, - false + true ) .is_ok()); heapless_pool @@ -1782,10 +1780,10 @@ mod tests { StaticHeaplessMemoryPool::new(true); assert!(heapless_pool .grow( - unsafe { SUBPOOL_2.assume_init_mut() }, - unsafe { SUBPOOL_2_SIZES.assume_init_mut() }, + unsafe { &mut *addr_of_mut!(SUBPOOL_2) }, + unsafe { &mut *addr_of_mut!(SUBPOOL_2_SIZES) }, SUBPOOL_2_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool @@ -1793,7 +1791,7 @@ mod tests { unsafe { SUBPOOL_4.assume_init_mut() }, unsafe { SUBPOOL_4_SIZES.assume_init_mut() }, SUBPOOL_4_NUM_ELEMENTS, - false + true ) .is_ok()); generic_test_spills_to_higher_subpools(&mut heapless_pool); @@ -1808,7 +1806,7 @@ mod tests { unsafe { SUBPOOL_5.assume_init_mut() }, unsafe { SUBPOOL_5_SIZES.assume_init_mut() }, SUBPOOL_5_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool @@ -1816,7 +1814,7 @@ mod tests { unsafe { SUBPOOL_3.assume_init_mut() }, unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, SUBPOOL_3_NUM_ELEMENTS, - false + true ) .is_ok()); generic_test_spillage_fails_as_well(&mut heapless_pool); @@ -1831,7 +1829,7 @@ mod tests { unsafe { SUBPOOL_5.assume_init_mut() }, unsafe { SUBPOOL_5_SIZES.assume_init_mut() }, SUBPOOL_5_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool @@ -1839,7 +1837,7 @@ mod tests { unsafe { SUBPOOL_6.assume_init_mut() }, unsafe { SUBPOOL_6_SIZES.assume_init_mut() }, SUBPOOL_6_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool @@ -1847,7 +1845,7 @@ mod tests { unsafe { SUBPOOL_3.assume_init_mut() }, unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, SUBPOOL_3_NUM_ELEMENTS, - false + true ) .is_ok()); generic_test_spillage_works_across_multiple_subpools(&mut heapless_pool); @@ -1862,7 +1860,7 @@ mod tests { unsafe { SUBPOOL_5.assume_init_mut() }, unsafe { SUBPOOL_5_SIZES.assume_init_mut() }, SUBPOOL_5_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool @@ -1870,7 +1868,7 @@ mod tests { unsafe { SUBPOOL_6.assume_init_mut() }, unsafe { SUBPOOL_6_SIZES.assume_init_mut() }, SUBPOOL_6_NUM_ELEMENTS, - false + true ) .is_ok()); assert!(heapless_pool @@ -1878,7 +1876,7 @@ mod tests { unsafe { SUBPOOL_3.assume_init_mut() }, unsafe { SUBPOOL_3_SIZES.assume_init_mut() }, SUBPOOL_3_NUM_ELEMENTS, - false + true ) .is_ok()); generic_test_spillage_fails_across_multiple_subpools(&mut heapless_pool); diff --git a/satrs/src/pus/verification.rs b/satrs/src/pus/verification.rs index 8986e88..f937c8a 100644 --- a/satrs/src/pus/verification.rs +++ b/satrs/src/pus/verification.rs @@ -97,7 +97,7 @@ use spacepackets::ecss::EcssEnumeration; use spacepackets::{ByteConversionError, CcsdsPacket, PacketId, PacketSequenceCtrl}; use spacepackets::{SpHeader, MAX_APID}; -pub use crate::seq_count::SeqCountProviderSimple; +pub use spacepackets::seq_count::SeqCountProviderSimple; pub use spacepackets::ecss::verification::*; #[cfg(feature = "alloc")] @@ -1702,7 +1702,7 @@ pub mod tests { }; use crate::pus::{ChannelWithId, PusTmVariant}; use crate::request::MessageMetadata; - use crate::seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProviderCore}; + use crate::seq_count::{CcsdsSimpleSeqCountProvider, SequenceCountProvider}; use crate::tmtc::{PacketSenderWithSharedPool, SharedPacketPool}; use crate::ComponentId; use alloc::format; diff --git a/satrs/src/seq_count.rs b/satrs/src/seq_count.rs deleted file mode 100644 index b4539b0..0000000 --- a/satrs/src/seq_count.rs +++ /dev/null @@ -1,250 +0,0 @@ -use core::cell::Cell; -#[cfg(feature = "alloc")] -use dyn_clone::DynClone; -use paste::paste; -use spacepackets::MAX_SEQ_COUNT; -#[cfg(feature = "std")] -pub use stdmod::*; - -/// Core trait for objects which can provide a sequence count. -/// -/// The core functions are not mutable on purpose to allow easier usage with -/// static structs when using the interior mutability pattern. This can be achieved by using -/// [Cell], [core::cell::RefCell] or atomic types. -pub trait SequenceCountProviderCore { - fn get(&self) -> Raw; - - fn increment(&self); - - fn get_and_increment(&self) -> Raw { - let val = self.get(); - self.increment(); - val - } -} - -/// Extension trait which allows cloning a sequence count provider after it was turned into -/// a trait object. -#[cfg(feature = "alloc")] -pub trait SequenceCountProvider: SequenceCountProviderCore + DynClone {} -#[cfg(feature = "alloc")] -dyn_clone::clone_trait_object!(SequenceCountProvider); -#[cfg(feature = "alloc")] -impl SequenceCountProvider for T where T: SequenceCountProviderCore + Clone {} - -#[derive(Clone)] -pub struct SeqCountProviderSimple { - seq_count: Cell, - max_val: T, -} - -macro_rules! impl_for_primitives { - ($($ty: ident,)+) => { - $( - paste! { - impl SeqCountProviderSimple<$ty> { - pub fn [](max_val: $ty) -> Self { - Self { - seq_count: Cell::new(0), - max_val, - } - } - pub fn []() -> Self { - Self { - seq_count: Cell::new(0), - max_val: $ty::MAX - } - } - } - - impl Default for SeqCountProviderSimple<$ty> { - fn default() -> Self { - Self::[]() - } - } - - impl SequenceCountProviderCore<$ty> for SeqCountProviderSimple<$ty> { - fn get(&self) -> $ty { - self.seq_count.get() - } - - fn increment(&self) { - self.get_and_increment(); - } - - fn get_and_increment(&self) -> $ty { - let curr_count = self.seq_count.get(); - - if curr_count == self.max_val { - self.seq_count.set(0); - } else { - self.seq_count.set(curr_count + 1); - } - curr_count - } - } - } - )+ - } -} - -impl_for_primitives!(u8, u16, u32, u64,); - -/// This is a sequence count provider which wraps around at [MAX_SEQ_COUNT]. -#[derive(Clone)] -pub struct CcsdsSimpleSeqCountProvider { - provider: SeqCountProviderSimple, -} - -impl Default for CcsdsSimpleSeqCountProvider { - fn default() -> Self { - Self { - provider: SeqCountProviderSimple::new_custom_max_val_u16(MAX_SEQ_COUNT), - } - } -} - -impl SequenceCountProviderCore for CcsdsSimpleSeqCountProvider { - delegate::delegate! { - to self.provider { - fn get(&self) -> u16; - fn increment(&self); - fn get_and_increment(&self) -> u16; - } - } -} - -#[cfg(feature = "std")] -pub mod stdmod { - use super::*; - use std::sync::{Arc, Mutex}; - - macro_rules! sync_clonable_seq_counter_impl { - ($($ty: ident,)+) => { - $(paste! { - /// These sequence counters can be shared between threads and can also be - /// configured to wrap around at specified maximum values. Please note that - /// that the API provided by this class will not panic und [Mutex] lock errors, - /// but it will yield 0 for the getter functions. - #[derive(Clone, Default)] - pub struct [] { - seq_count: Arc>, - max_val: $ty - } - - impl [] { - pub fn new() -> Self { - Self::new_with_max_val($ty::MAX) - } - - pub fn new_with_max_val(max_val: $ty) -> Self { - Self { - seq_count: Arc::default(), - max_val - } - } - } - impl SequenceCountProviderCore<$ty> for [] { - fn get(&self) -> $ty { - match self.seq_count.lock() { - Ok(counter) => *counter, - Err(_) => 0 - } - } - - fn increment(&self) { - self.get_and_increment(); - } - - fn get_and_increment(&self) -> $ty { - match self.seq_count.lock() { - Ok(mut counter) => { - let val = *counter; - if val == self.max_val { - *counter = 0; - } else { - *counter += 1; - } - val - } - Err(_) => 0, - } - } - } - })+ - } - } - sync_clonable_seq_counter_impl!(u8, u16, u32, u64,); -} - -#[cfg(test)] -mod tests { - use crate::seq_count::{ - CcsdsSimpleSeqCountProvider, SeqCountProviderSimple, SeqCountProviderSyncU8, - SequenceCountProviderCore, - }; - use spacepackets::MAX_SEQ_COUNT; - - #[test] - fn test_u8_counter() { - let u8_counter = SeqCountProviderSimple::::default(); - assert_eq!(u8_counter.get(), 0); - assert_eq!(u8_counter.get_and_increment(), 0); - assert_eq!(u8_counter.get_and_increment(), 1); - assert_eq!(u8_counter.get(), 2); - } - - #[test] - fn test_u8_counter_overflow() { - let u8_counter = SeqCountProviderSimple::new_u8(); - for _ in 0..256 { - u8_counter.increment(); - } - assert_eq!(u8_counter.get(), 0); - } - - #[test] - fn test_ccsds_counter() { - let ccsds_counter = CcsdsSimpleSeqCountProvider::default(); - assert_eq!(ccsds_counter.get(), 0); - assert_eq!(ccsds_counter.get_and_increment(), 0); - assert_eq!(ccsds_counter.get_and_increment(), 1); - assert_eq!(ccsds_counter.get(), 2); - } - - #[test] - fn test_ccsds_counter_overflow() { - let ccsds_counter = CcsdsSimpleSeqCountProvider::default(); - for _ in 0..MAX_SEQ_COUNT + 1 { - ccsds_counter.increment(); - } - assert_eq!(ccsds_counter.get(), 0); - } - - #[test] - fn test_atomic_ref_counters() { - let sync_u8_counter = SeqCountProviderSyncU8::new(); - assert_eq!(sync_u8_counter.get(), 0); - assert_eq!(sync_u8_counter.get_and_increment(), 0); - assert_eq!(sync_u8_counter.get_and_increment(), 1); - assert_eq!(sync_u8_counter.get(), 2); - } - - #[test] - fn test_atomic_ref_counters_overflow() { - let sync_u8_counter = SeqCountProviderSyncU8::new(); - for _ in 0..u8::MAX as u16 + 1 { - sync_u8_counter.increment(); - } - assert_eq!(sync_u8_counter.get(), 0); - } - - #[test] - fn test_atomic_ref_counters_overflow_custom_max_val() { - let sync_u8_counter = SeqCountProviderSyncU8::new_with_max_val(128); - for _ in 0..129 { - sync_u8_counter.increment(); - } - assert_eq!(sync_u8_counter.get(), 0); - } -}