From 7de4ef9408b146e04c2de5560bf437565971c952 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 4 Jun 2024 10:01:26 +0200 Subject: [PATCH 01/18] continue source handler --- satrs/src/cfdp/dest.rs | 274 +++++++++++++++++++++++------------------ satrs/src/cfdp/mod.rs | 98 ++++++++++----- 2 files changed, 221 insertions(+), 151 deletions(-) diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs index 4a87ce6..75cf5e2 100644 --- a/satrs/src/cfdp/dest.rs +++ b/satrs/src/cfdp/dest.rs @@ -3,13 +3,13 @@ use core::str::{from_utf8, Utf8Error}; use std::path::{Path, PathBuf}; use super::{ - filestore::{FilestoreError, VirtualFilestore}, + filestore::{FilestoreError, NativeFilestore, VirtualFilestore}, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams}, - CheckTimerCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo, PacketTarget, - RemoteEntityConfig, RemoteEntityConfigProvider, State, TimerContext, TransactionId, + CheckTimerProviderCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo, + PacketTarget, RemoteEntityConfig, RemoteEntityConfigProvider, State, StdCheckTimer, + StdCheckTimerCreator, StdRemoteEntityConfigProvider, TimerContext, TransactionId, TransactionStep, }; -use alloc::boxed::Box; use smallvec::SmallVec; use spacepackets::{ cfdp::{ @@ -43,7 +43,7 @@ enum CompletionDisposition { } #[derive(Debug)] -struct TransferState { +struct TransferState { transaction_id: Option, metadata_params: MetadataGenericParams, progress: u64, @@ -54,10 +54,10 @@ struct TransferState { completion_disposition: CompletionDisposition, checksum: u32, current_check_count: u32, - current_check_timer: Option>, + current_check_timer: Option, } -impl Default for TransferState { +impl Default for TransferState { fn default() -> Self { Self { transaction_id: None, @@ -76,8 +76,8 @@ impl Default for TransferState { } #[derive(Debug)] -struct TransactionParams { - tstate: TransferState, +struct TransactionParams { + tstate: TransferState, pdu_conf: CommonPduConfig, file_properties: FileProperties, cksum_buf: [u8; 1024], @@ -86,7 +86,7 @@ struct TransactionParams { remote_cfg: Option, } -impl TransactionParams { +impl TransactionParams { fn transmission_mode(&self) -> TransmissionMode { self.pdu_conf.trans_mode } @@ -104,7 +104,7 @@ impl Default for FileProperties { } } -impl TransactionParams { +impl TransactionParams { fn file_size(&self) -> u64 { self.tstate.metadata_params.file_size } @@ -114,7 +114,7 @@ impl TransactionParams { } } -impl Default for TransactionParams { +impl Default for TransactionParams { fn default() -> Self { Self { pdu_conf: Default::default(), @@ -128,7 +128,7 @@ impl Default for TransactionParams { } } -impl TransactionParams { +impl TransactionParams { fn reset(&mut self) { self.tstate.condition_code = ConditionCode::NoError; self.tstate.delivery_code = DeliveryCode::Incomplete; @@ -170,7 +170,7 @@ pub enum DestError { pub trait CfdpPacketSender: Send { fn send_pdu( - &mut self, + &self, pdu_type: PduType, file_directive_type: Option, raw_pdu: &[u8], @@ -191,19 +191,41 @@ pub trait CfdpPacketSender: Send { /// 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 { +pub struct DestinationHandler< + PduSender: CfdpPacketSender, + Vfs: VirtualFilestore, + RemoteCfgTable: RemoteEntityConfigProvider, + CheckTimerCreator: CheckTimerProviderCreator, + CheckTimerProvider: CountdownProvider, +> { local_cfg: LocalEntityConfig, step: TransactionStep, state: State, - tparams: TransactionParams, + tparams: TransactionParams, packet_buf: alloc::vec::Vec, - packet_sender: Box, - vfs: Box, - remote_cfg_table: Box, - check_timer_creator: Box, + pub pdu_sender: PduSender, + pub vfs: Vfs, + pub remote_cfg_table: RemoteCfgTable, + pub check_timer_creator: CheckTimerCreator, } -impl DestinationHandler { +#[cfg(feature = "std")] +pub type StdDestinationHandler = DestinationHandler< + PduSender, + NativeFilestore, + StdRemoteEntityConfigProvider, + StdCheckTimerCreator, + StdCheckTimer, +>; + +impl< + PduSender: CfdpPacketSender, + Vfs: VirtualFilestore, + RemoteCfgTable: RemoteEntityConfigProvider, + CheckTimerCreator: CheckTimerProviderCreator, + CheckTimerProvider: CountdownProvider, + > DestinationHandler +{ /// Constructs a new destination handler. /// /// # Arguments @@ -226,10 +248,10 @@ impl DestinationHandler { pub fn new( local_cfg: LocalEntityConfig, max_packet_len: usize, - packet_sender: Box, - vfs: Box, - remote_cfg_table: Box, - check_timer_creator: Box, + packet_sender: PduSender, + vfs: Vfs, + remote_cfg_table: RemoteCfgTable, + check_timer_creator: CheckTimerCreator, ) -> Self { Self { local_cfg, @@ -237,7 +259,7 @@ impl DestinationHandler { state: State::Idle, tparams: Default::default(), packet_buf: alloc::vec![0; max_packet_len], - packet_sender, + pdu_sender: packet_sender, vfs, remote_cfg_table, check_timer_creator, @@ -524,7 +546,7 @@ impl DestinationHandler { self.step = TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling; self.tparams.tstate.current_check_timer = Some( self.check_timer_creator - .get_check_timer_provider(TimerContext::CheckLimit { + .create_check_timer_provider(TimerContext::CheckLimit { local_id: self.local_cfg.id, remote_id: self.tparams.remote_cfg.unwrap().entity_id, entity_type: EntityType::Receiving, @@ -763,7 +785,7 @@ impl DestinationHandler { ) }; finished_pdu.write_to_bytes(&mut self.packet_buf)?; - self.packet_sender.send_pdu( + self.pdu_sender.send_pdu( finished_pdu.pdu_type(), finished_pdu.file_directive_type(), &self.packet_buf[0..finished_pdu.len_written()], @@ -771,23 +793,26 @@ impl DestinationHandler { Ok(1) } - fn tstate(&self) -> &TransferState { + fn tstate(&self) -> &TransferState { &self.tparams.tstate } - fn tstate_mut(&mut self) -> &mut TransferState { + fn tstate_mut(&mut self) -> &mut TransferState { &mut self.tparams.tstate } } #[cfg(test)] mod tests { - use core::{cell::Cell, sync::atomic::AtomicBool}; + use core::{ + cell::{Cell, RefCell}, + 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 alloc::{boxed::Box, collections::VecDeque, string::String, sync::Arc, vec::Vec}; use rand::Rng; use spacepackets::{ cfdp::{ @@ -799,7 +824,7 @@ mod tests { }; use crate::cfdp::{ - filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimerCreator, + filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimerProviderCreator, CountdownProvider, DefaultFaultHandler, IndicationConfig, RemoteEntityConfig, StdRemoteEntityConfigProvider, UserFaultHandler, CRC_32, }; @@ -820,20 +845,19 @@ mod tests { file_directive_type: Option, raw_pdu: Vec, } - type SharedPduPacketQueue = Arc>>; - #[derive(Default, Clone)] + #[derive(Default)] struct TestCfdpSender { - packet_queue: SharedPduPacketQueue, + packet_queue: RefCell>, } impl CfdpPacketSender for TestCfdpSender { fn send_pdu( - &mut self, + &self, pdu_type: PduType, file_directive_type: Option, raw_pdu: &[u8], ) -> Result<(), PduError> { - self.packet_queue.lock().unwrap().push_back(SentPdu { + self.packet_queue.borrow_mut().push_back(SentPdu { pdu_type, file_directive_type, raw_pdu: raw_pdu.to_vec(), @@ -844,10 +868,10 @@ mod tests { impl TestCfdpSender { pub fn retrieve_next_pdu(&self) -> Option { - self.packet_queue.lock().unwrap().pop_front() + self.packet_queue.borrow_mut().pop_front() } pub fn queue_empty(&self) -> bool { - self.packet_queue.lock().unwrap().is_empty() + self.packet_queue.borrow_mut().is_empty() } } @@ -1087,14 +1111,13 @@ mod tests { } } - impl CheckTimerCreator for TestCheckTimerCreator { - fn get_check_timer_provider( - &self, - timer_context: TimerContext, - ) -> Box { + impl CheckTimerProviderCreator for TestCheckTimerCreator { + type CheckTimer = TestCheckTimer; + + fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer { match timer_context { TimerContext::CheckLimit { .. } => { - Box::new(TestCheckTimer::new(self.check_limit_expired_flag.clone())) + TestCheckTimer::new(self.check_limit_expired_flag.clone()) } _ => { panic!("invalid check timer creator, can only be used for check limit handling") @@ -1103,10 +1126,17 @@ mod tests { } } + type TestDestHandler = DestinationHandler< + TestCfdpSender, + NativeFilestore, + StdRemoteEntityConfigProvider, + TestCheckTimerCreator, + TestCheckTimer, + >; + struct DestHandlerTester { check_timer_expired: Arc, - pdu_sender: TestCfdpSender, - handler: DestinationHandler, + handler: TestDestHandler, src_path: PathBuf, dest_path: PathBuf, check_dest_file: bool, @@ -1122,16 +1152,12 @@ mod tests { 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 dest_handler = + default_dest_handler(fault_handler, test_sender, 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, @@ -1288,7 +1314,13 @@ mod tests { test_fault_handler: TestFaultHandler, test_packet_sender: TestCfdpSender, check_timer_expired: Arc, - ) -> DestinationHandler { + ) -> DestinationHandler< + TestCfdpSender, + NativeFilestore, + StdRemoteEntityConfigProvider, + TestCheckTimerCreator, + TestCheckTimer, + > { let local_entity_cfg = LocalEntityConfig { id: REMOTE_ID.into(), indication_cfg: IndicationConfig::default(), @@ -1297,10 +1329,10 @@ mod tests { 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)), + test_packet_sender, + NativeFilestore::default(), + basic_remote_cfg_table(), + TestCheckTimerCreator::new(check_timer_expired), ) } @@ -1366,18 +1398,18 @@ mod tests { #[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 + let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = testbench.test_user_from_cached_paths(0); + testbench .generic_transfer_init(&mut test_user, 0) .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj + testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + testbench .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); + assert!(testbench.handler.pdu_sender.queue_empty()); + testbench.state_check(State::Idle, TransactionStep::Idle); } #[test] @@ -1387,21 +1419,21 @@ mod tests { 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 + let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = testbench.test_user_from_cached_paths(file_size); + testbench .generic_transfer_init(&mut test_user, file_size) .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj + testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + testbench .generic_file_data_insert(&mut test_user, 0, file_data) .expect("file data insertion failed"); - test_obj + testbench .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); + assert!(testbench.handler.pdu_sender.queue_empty()); + testbench.state_check(State::Idle, TransactionStep::Idle); } #[test] @@ -1413,28 +1445,28 @@ mod tests { 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 + let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = testbench.test_user_from_cached_paths(file_size); + testbench .generic_transfer_init(&mut test_user, file_size) .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj + testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + testbench .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) .expect("file data insertion failed"); - test_obj + testbench .generic_file_data_insert( &mut test_user, segment_len as u64, &random_data[segment_len..], ) .expect("file data insertion failed"); - test_obj + testbench .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); + assert!(testbench.handler.pdu_sender.queue_empty()); + testbench.state_check(State::Idle, TransactionStep::Idle); } #[test] @@ -1446,32 +1478,32 @@ mod tests { 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 + let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = testbench.test_user_from_cached_paths(file_size); + let transaction_id = testbench .generic_transfer_init(&mut test_user, file_size) .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj + testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + testbench .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) .expect("file data insertion 0 failed"); - test_obj + testbench .generic_eof_no_error(&mut test_user, random_data.to_vec()) .expect("EOF no error insertion failed"); - test_obj.state_check( + testbench.state_check( State::Busy, TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, ); - test_obj + testbench .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 + testbench.set_check_timer_expired(); + testbench .handler .state_machine(&mut test_user, None) .expect("fsm failure"); @@ -1482,8 +1514,8 @@ mod tests { 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); + assert!(testbench.handler.pdu_sender.queue_empty()); + testbench.state_check(State::Idle, TransactionStep::Idle); } #[test] @@ -1495,38 +1527,38 @@ mod tests { 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 + let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut test_user = testbench.test_user_from_cached_paths(file_size); + let transaction_id = testbench .generic_transfer_init(&mut test_user, file_size) .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - test_obj + testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + testbench .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) .expect("file data insertion 0 failed"); - test_obj + testbench .generic_eof_no_error(&mut test_user, random_data.to_vec()) .expect("EOF no error insertion failed"); - test_obj.state_check( + testbench.state_check( State::Busy, TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, ); - test_obj.set_check_timer_expired(); - test_obj + testbench.set_check_timer_expired(); + testbench .handler .state_machine(&mut test_user, None) .expect("fsm error"); - test_obj.state_check( + testbench.state_check( State::Busy, TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, ); - test_obj.set_check_timer_expired(); - test_obj + testbench.set_check_timer_expired(); + testbench .handler .state_machine(&mut test_user, None) .expect("fsm error"); - test_obj.state_check(State::Idle, TransactionStep::Idle); + testbench.state_check(State::Idle, TransactionStep::Idle); assert!(fault_handler .notice_of_suspension_queue @@ -1550,15 +1582,15 @@ mod tests { drop(cancelled_queue); - assert!(test_obj.pdu_sender.queue_empty()); + assert!(testbench.handler.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"); + testbench.check_dest_file = false; + assert!(Path::exists(testbench.dest_path())); + let read_content = fs::read(testbench.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()); + assert!(fs::remove_file(testbench.dest_path().as_path()).is_ok()); } fn check_finished_pdu_success(sent_pdu: &SentPdu) { @@ -1578,21 +1610,21 @@ mod tests { #[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 + let mut testbench = DestHandlerTester::new(fault_handler.clone(), true); + let mut test_user = testbench.test_user_from_cached_paths(0); + testbench .generic_transfer_init(&mut test_user, 0) .expect("transfer init failed"); - test_obj.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - let sent_packets = test_obj + testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); + let sent_packets = testbench .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(); + testbench.state_check(State::Idle, TransactionStep::Idle); + assert!(!testbench.handler.pdu_sender.queue_empty()); + let sent_pdu = testbench.handler.pdu_sender.retrieve_next_pdu().unwrap(); check_finished_pdu_success(&sent_pdu); } diff --git a/satrs/src/cfdp/mod.rs b/satrs/src/cfdp/mod.rs index c2f6d01..172bbed 100644 --- a/satrs/src/cfdp/mod.rs +++ b/satrs/src/cfdp/mod.rs @@ -1,6 +1,6 @@ //! 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 core::{cell::RefCell, fmt::Debug, hash::Hash, time::Duration}; use crc::{Crc, CRC_32_CKSUM}; use hashbrown::HashMap; @@ -27,6 +27,9 @@ pub mod filestore; pub mod source; pub mod user; +#[cfg(feature = "std")] +pub use std_mod::*; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum EntityType { Sending, @@ -84,42 +87,77 @@ pub enum TimerContext { /// 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; -} +pub trait CheckTimerProviderCreator { + type CheckTimer: CountdownProvider; -/// 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, + fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer; } #[cfg(feature = "std")] -impl StdCheckTimer { - pub fn new(expiry_time_seconds: u64) -> Self { - Self { - expiry_time_seconds, - start_time: std::time::Instant::now(), - } - } -} +pub mod std_mod { + use super::*; -#[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 + /// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime. + /// It also assumes that a second accuracy of the check timer period is sufficient. + #[derive(Debug)] + pub struct StdCheckTimer { + expiry_time_seconds: u64, + start_time: std::time::Instant, } - fn reset(&mut self) { - self.start_time = std::time::Instant::now(); + impl StdCheckTimer { + pub fn new(expiry_time_seconds: u64) -> Self { + Self { + expiry_time_seconds, + start_time: std::time::Instant::now(), + } + } + } + + 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(); + } + } + + pub struct StdCheckTimerCreator { + pub check_limit_timeout_secs: u64, + } + + impl Default for StdCheckTimerCreator { + fn default() -> Self { + Self { + check_limit_timeout_secs: 5, + } + } + } + + impl CheckTimerProviderCreator for StdCheckTimerCreator { + type CheckTimer = StdCheckTimer; + + fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer { + match timer_context { + TimerContext::CheckLimit { + local_id: _, + remote_id: _, + entity_type: _, + } => StdCheckTimer::new(self.check_limit_timeout_secs), + TimerContext::NakActivity { + expiry_time_seconds, + } => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()), + TimerContext::PositiveAck { + expiry_time_seconds, + } => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()), + } + } } } -- 2.43.0 From 7649a7374024d354d80acfa954e88f86529e4f57 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 4 Jun 2024 16:26:07 +0200 Subject: [PATCH 02/18] continue source handler --- satrs/src/cfdp/dest.rs | 299 +++++++++++++-------------------------- satrs/src/cfdp/mod.rs | 230 +++++++++++++++++++++++++----- satrs/src/cfdp/source.rs | 218 ++++++++++++++++++++++++++-- 3 files changed, 506 insertions(+), 241 deletions(-) diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs index 75cf5e2..b1e12da 100644 --- a/satrs/src/cfdp/dest.rs +++ b/satrs/src/cfdp/dest.rs @@ -6,9 +6,9 @@ use super::{ filestore::{FilestoreError, NativeFilestore, VirtualFilestore}, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams}, CheckTimerProviderCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo, - PacketTarget, RemoteEntityConfig, RemoteEntityConfigProvider, State, StdCheckTimer, - StdCheckTimerCreator, StdRemoteEntityConfigProvider, TimerContext, TransactionId, - TransactionStep, + PacketTarget, PduSendProvider, RemoteEntityConfig, RemoteEntityConfigProvider, State, + StdCheckTimer, StdCheckTimerCreator, StdRemoteEntityConfigProvider, TimerContext, + TransactionId, UserFaultHookProvider, }; use smallvec::SmallVec; use spacepackets::{ @@ -42,6 +42,18 @@ enum CompletionDisposition { Cancelled = 1, } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TransactionStep { + Idle = 0, + TransactionStart = 1, + ReceivingFileDataPdus = 2, + ReceivingFileDataPdusWithCheckLimitHandling = 3, + SendingAckPdu = 4, + TransferCompletion = 5, + SendingFinishedPdu = 6, +} + #[derive(Debug)] struct TransferState { transaction_id: Option, @@ -140,9 +152,12 @@ impl TransactionParams { pub enum DestError { /// File directive expected, but none specified #[error("expected file directive")] - DirectiveExpected, - #[error("can not process packet type {0:?}")] - CantProcessPacketType(FileDirectiveType), + DirectiveFieldEmpty, + #[error("can not process packet type {pdu_type:?} with directive type {directive_type:?}")] + CantProcessPacketType { + pdu_type: PduType, + directive_type: Option, + }, #[error("can not process file data PDUs in current state")] WrongStateForFileDataAndEof, // Received new metadata PDU while being already being busy with a file transfer. @@ -168,15 +183,6 @@ pub enum DestError { NoRemoteCfgFound(UnsignedByteField), } -pub trait CfdpPacketSender: Send { - fn send_pdu( - &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. @@ -192,13 +198,14 @@ pub trait CfdpPacketSender: Send { /// user and passed as a constructor parameter. The number of generated packets is returned /// by the state machine call. pub struct DestinationHandler< - PduSender: CfdpPacketSender, + PduSender: PduSendProvider, + UserFaultHook: UserFaultHookProvider, Vfs: VirtualFilestore, RemoteCfgTable: RemoteEntityConfigProvider, CheckTimerCreator: CheckTimerProviderCreator, CheckTimerProvider: CountdownProvider, > { - local_cfg: LocalEntityConfig, + local_cfg: LocalEntityConfig, step: TransactionStep, state: State, tparams: TransactionParams, @@ -210,8 +217,9 @@ pub struct DestinationHandler< } #[cfg(feature = "std")] -pub type StdDestinationHandler = DestinationHandler< +pub type StdDestinationHandler = DestinationHandler< PduSender, + UserFaultHook, NativeFilestore, StdRemoteEntityConfigProvider, StdCheckTimerCreator, @@ -219,12 +227,21 @@ pub type StdDestinationHandler = DestinationHandler< >; impl< - PduSender: CfdpPacketSender, + PduSender: PduSendProvider, + UserFaultHook: UserFaultHookProvider, Vfs: VirtualFilestore, RemoteCfgTable: RemoteEntityConfigProvider, CheckTimerCreator: CheckTimerProviderCreator, CheckTimerProvider: CountdownProvider, - > DestinationHandler + > + DestinationHandler< + PduSender, + UserFaultHook, + Vfs, + RemoteCfgTable, + CheckTimerCreator, + CheckTimerProvider, + > { /// Constructs a new destination handler. /// @@ -246,9 +263,9 @@ impl< /// * `check_timer_creator` - This is used by the CFDP handler to generate timers required /// by various tasks. pub fn new( - local_cfg: LocalEntityConfig, + local_cfg: LocalEntityConfig, max_packet_len: usize, - packet_sender: PduSender, + pdu_sender: PduSender, vfs: Vfs, remote_cfg_table: RemoteCfgTable, check_timer_creator: CheckTimerCreator, @@ -259,7 +276,7 @@ impl< state: State::Idle, tparams: Default::default(), packet_buf: alloc::vec![0; max_packet_len], - pdu_sender: packet_sender, + pdu_sender, vfs, remote_cfg_table, check_timer_creator, @@ -272,6 +289,8 @@ impl< /// 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. + /// + /// The function returns the number of sent PDU packets on success. pub fn state_machine( &mut self, cfdp_user: &mut impl CfdpUser, @@ -308,14 +327,15 @@ impl< 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(), - )); + return Err(DestError::CantProcessPacketType { + pdu_type: packet_info.pdu_type(), + directive_type: packet_info.pdu_directive(), + }); } match packet_info.pdu_type { PduType::FileDirective => { if packet_info.pdu_directive.is_none() { - return Err(DestError::DirectiveExpected); + return Err(DestError::DirectiveFieldEmpty); } self.handle_file_directive( cfdp_user, @@ -338,12 +358,13 @@ impl< FileDirectiveType::FinishedPdu | FileDirectiveType::NakPdu | FileDirectiveType::KeepAlivePdu => { - return Err(DestError::CantProcessPacketType(pdu_directive)); + return Err(DestError::CantProcessPacketType { + pdu_type: PduType::FileDirective, + directive_type: Some(pdu_directive), + }); } FileDirectiveType::AckPdu => { - todo!( - "check whether ACK pdu handling is applicable by checking the acked directive field" - ) + todo!("acknowledged mode not implemented yet") } FileDirectiveType::MetadataPdu => self.handle_metadata_pdu(raw_packet)?, FileDirectiveType::PromptPdu => self.handle_prompt_pdu(raw_packet)?, @@ -730,7 +751,7 @@ impl< let progress = self.tstate().progress; let fh_code = self .local_cfg - .default_fault_handler + .fault_handler .get_fault_handler(condition_code); match fh_code { FaultHandlerCode::NoticeOfCancellation => { @@ -741,7 +762,7 @@ impl< FaultHandlerCode::AbandonTransaction => self.abandon_transaction(), } self.local_cfg - .default_fault_handler + .fault_handler .report_fault(transaction_id, condition_code, progress) } @@ -804,15 +825,12 @@ impl< #[cfg(test)] mod tests { - use core::{ - cell::{Cell, RefCell}, - sync::atomic::AtomicBool, - }; + use core::{cell::Cell, sync::atomic::AtomicBool}; + use std::fs; #[allow(unused_imports)] use std::println; - use std::{fs, sync::Mutex}; - use alloc::{boxed::Box, collections::VecDeque, string::String, sync::Arc, vec::Vec}; + use alloc::{collections::VecDeque, string::String, sync::Arc, vec::Vec}; use rand::Rng; use spacepackets::{ cfdp::{ @@ -824,9 +842,11 @@ mod tests { }; use crate::cfdp::{ - filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimerProviderCreator, - CountdownProvider, DefaultFaultHandler, IndicationConfig, RemoteEntityConfig, - StdRemoteEntityConfigProvider, UserFaultHandler, CRC_32, + filestore::NativeFilestore, + tests::{basic_remote_cfg_table, SentPdu, TestCfdpSender, TestFaultHandler}, + user::OwnedMetadataRecvdParams, + CheckTimerProviderCreator, CountdownProvider, FaultHandler, IndicationConfig, + RemoteEntityConfig, StdRemoteEntityConfigProvider, CRC_32, }; use super::*; @@ -840,41 +860,6 @@ mod tests { pub length: usize, } - struct SentPdu { - pdu_type: PduType, - file_directive_type: Option, - raw_pdu: Vec, - } - #[derive(Default)] - struct TestCfdpSender { - packet_queue: RefCell>, - } - - impl CfdpPacketSender for TestCfdpSender { - fn send_pdu( - &self, - pdu_type: PduType, - file_directive_type: Option, - raw_pdu: &[u8], - ) -> Result<(), PduError> { - self.packet_queue.borrow_mut().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.borrow_mut().pop_front() - } - pub fn queue_empty(&self) -> bool { - self.packet_queue.borrow_mut().is_empty() - } - } - #[derive(Default)] struct TestCfdpUser { next_expected_seq_num: u64, @@ -1000,81 +985,6 @@ mod tests { } } - #[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, @@ -1128,6 +1038,7 @@ mod tests { type TestDestHandler = DestinationHandler< TestCfdpSender, + TestFaultHandler, NativeFilestore, StdRemoteEntityConfigProvider, TestCheckTimerCreator, @@ -1177,6 +1088,14 @@ mod tests { &self.dest_path } + fn all_fault_queues_empty(&self) -> bool { + self.handler + .local_cfg + .user_fault_hook() + .borrow() + .all_queues_empty() + } + #[allow(dead_code)] fn indication_cfg_mut(&mut self) -> &mut IndicationConfig { &mut self.handler.local_cfg.indication_cfg @@ -1295,36 +1214,15 @@ mod tests { ) } - 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< - TestCfdpSender, - NativeFilestore, - StdRemoteEntityConfigProvider, - TestCheckTimerCreator, - TestCheckTimer, - > { + ) -> TestDestHandler { let local_entity_cfg = LocalEntityConfig { id: REMOTE_ID.into(), indication_cfg: IndicationConfig::default(), - default_fault_handler: DefaultFaultHandler::new(Box::new(test_fault_handler)), + fault_handler: FaultHandler::new(test_fault_handler), }; DestinationHandler::new( local_entity_cfg, @@ -1390,15 +1288,20 @@ mod tests { 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()); + let dest_handler = default_dest_handler(fault_handler, test_sender, Arc::default()); assert!(dest_handler.transmission_mode().is_none()); - assert!(fault_handler.all_queues_empty()); + assert!(dest_handler + .local_cfg + .fault_handler + .user_hook + .borrow() + .all_queues_empty()); } #[test] fn test_empty_file_transfer_not_acked_no_closure() { let fault_handler = TestFaultHandler::default(); - let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut testbench = DestHandlerTester::new(fault_handler, false); let mut test_user = testbench.test_user_from_cached_paths(0); testbench .generic_transfer_init(&mut test_user, 0) @@ -1407,7 +1310,7 @@ mod tests { testbench .generic_eof_no_error(&mut test_user, Vec::new()) .expect("EOF no error insertion failed"); - assert!(fault_handler.all_queues_empty()); + assert!(testbench.all_fault_queues_empty()); assert!(testbench.handler.pdu_sender.queue_empty()); testbench.state_check(State::Idle, TransactionStep::Idle); } @@ -1419,7 +1322,7 @@ mod tests { let file_size = file_data.len() as u64; let fault_handler = TestFaultHandler::default(); - let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut testbench = DestHandlerTester::new(fault_handler, false); let mut test_user = testbench.test_user_from_cached_paths(file_size); testbench .generic_transfer_init(&mut test_user, file_size) @@ -1431,7 +1334,7 @@ mod tests { testbench .generic_eof_no_error(&mut test_user, file_data.to_vec()) .expect("EOF no error insertion failed"); - assert!(fault_handler.all_queues_empty()); + assert!(testbench.all_fault_queues_empty()); assert!(testbench.handler.pdu_sender.queue_empty()); testbench.state_check(State::Idle, TransactionStep::Idle); } @@ -1445,7 +1348,7 @@ mod tests { let segment_len = 256; let fault_handler = TestFaultHandler::default(); - let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut testbench = DestHandlerTester::new(fault_handler, false); let mut test_user = testbench.test_user_from_cached_paths(file_size); testbench .generic_transfer_init(&mut test_user, file_size) @@ -1464,7 +1367,7 @@ mod tests { testbench .generic_eof_no_error(&mut test_user, random_data.to_vec()) .expect("EOF no error insertion failed"); - assert!(fault_handler.all_queues_empty()); + assert!(testbench.all_fault_queues_empty()); assert!(testbench.handler.pdu_sender.queue_empty()); testbench.state_check(State::Idle, TransactionStep::Idle); } @@ -1478,7 +1381,7 @@ mod tests { let segment_len = 256; let fault_handler = TestFaultHandler::default(); - let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut testbench = DestHandlerTester::new(fault_handler, false); let mut test_user = testbench.test_user_from_cached_paths(file_size); let transaction_id = testbench .generic_transfer_init(&mut test_user, file_size) @@ -1507,10 +1410,10 @@ mod tests { .handler .state_machine(&mut test_user, None) .expect("fsm failure"); + let fault_handler = testbench.handler.local_cfg.fault_handler.user_hook.borrow(); - let ignored_queue = fault_handler.ignored_queue.lock().unwrap(); - assert_eq!(ignored_queue.len(), 1); - let cancelled = *ignored_queue.front().unwrap(); + assert_eq!(fault_handler.ignored_queue.len(), 1); + let cancelled = fault_handler.ignored_queue.front().unwrap(); assert_eq!(cancelled.0, transaction_id); assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure); assert_eq!(cancelled.2, segment_len as u64); @@ -1527,7 +1430,7 @@ mod tests { let segment_len = 256; let fault_handler = TestFaultHandler::default(); - let mut testbench = DestHandlerTester::new(fault_handler.clone(), false); + let mut testbench = DestHandlerTester::new(fault_handler, false); let mut test_user = testbench.test_user_from_cached_paths(file_size); let transaction_id = testbench .generic_transfer_init(&mut test_user, file_size) @@ -1560,27 +1463,25 @@ mod tests { .expect("fsm error"); testbench.state_check(State::Idle, TransactionStep::Idle); - assert!(fault_handler - .notice_of_suspension_queue - .lock() - .unwrap() - .is_empty()); + let fault_hook = testbench.handler.local_cfg.user_fault_hook().borrow(); - let ignored_queue = fault_handler.ignored_queue.lock().unwrap(); + assert!(fault_hook.notice_of_suspension_queue.is_empty()); + let ignored_queue = &fault_hook.ignored_queue; assert_eq!(ignored_queue.len(), 1); - let cancelled = *ignored_queue.front().unwrap(); + 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(); + let fault_hook = testbench.handler.local_cfg.user_fault_hook().borrow(); + let cancelled_queue = &fault_hook.notice_of_cancellation_queue; 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); + drop(fault_hook); assert!(testbench.handler.pdu_sender.queue_empty()); @@ -1610,7 +1511,7 @@ mod tests { #[test] fn test_file_transfer_with_closure() { let fault_handler = TestFaultHandler::default(); - let mut testbench = DestHandlerTester::new(fault_handler.clone(), true); + let mut testbench = DestHandlerTester::new(fault_handler, true); let mut test_user = testbench.test_user_from_cached_paths(0); testbench .generic_transfer_init(&mut test_user, 0) @@ -1620,7 +1521,7 @@ mod tests { .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()); + assert!(testbench.all_fault_queues_empty()); // The Finished PDU was sent, so the state machine is done. testbench.state_check(State::Idle, TransactionStep::Idle); assert!(!testbench.handler.pdu_sender.queue_empty()); diff --git a/satrs/src/cfdp/mod.rs b/satrs/src/cfdp/mod.rs index 172bbed..51f72e6 100644 --- a/satrs/src/cfdp/mod.rs +++ b/satrs/src/cfdp/mod.rs @@ -12,8 +12,6 @@ use spacepackets::{ util::{UnsignedByteField, UnsignedEnum}, }; -#[cfg(feature = "alloc")] -use alloc::boxed::Box; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -311,9 +309,9 @@ impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider { /// 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 +/// For each error reported by the [FaultHandler], the appropriate fault handler callback /// will be called depending on the [FaultHandlerCode]. -pub trait UserFaultHandler { +pub trait UserFaultHookProvider { fn notice_of_suspension_cb( &mut self, transaction_id: TransactionId, @@ -333,6 +331,37 @@ pub trait UserFaultHandler { fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64); } +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +struct DummyFaultHook {} + +impl UserFaultHookProvider for DummyFaultHook { + 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. /// @@ -353,14 +382,14 @@ pub trait UserFaultHandler { /// 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 { +pub struct FaultHandler { 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>, + pub user_hook: RefCell, } -impl DefaultFaultHandler { +impl FaultHandler { fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option { Some(match conditon_code { ConditionCode::PositiveAckLimitReached => 0, @@ -389,7 +418,7 @@ impl DefaultFaultHandler { self.handler_array[array_idx.unwrap()] = fault_handler; } - pub fn new(user_fault_handler: Box) -> Self { + pub fn new(user_fault_handler: UserHandler) -> Self { let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10]; init_array [Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] = @@ -398,7 +427,7 @@ impl DefaultFaultHandler { .unwrap()] = FaultHandlerCode::IgnoreError; Self { handler_array: init_array, - user_fault_handler: RefCell::new(user_fault_handler), + user_hook: RefCell::new(user_fault_handler), } } @@ -421,7 +450,7 @@ impl DefaultFaultHandler { return FaultHandlerCode::IgnoreError; } let fh_code = self.handler_array[array_idx.unwrap()]; - let mut handler_mut = self.user_fault_handler.borrow_mut(); + let mut handler_mut = self.user_hook.borrow_mut(); match fh_code { FaultHandlerCode::NoticeOfCancellation => { handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress); @@ -462,10 +491,29 @@ impl Default for IndicationConfig { } } -pub struct LocalEntityConfig { +pub struct LocalEntityConfig { pub id: UnsignedByteField, pub indication_cfg: IndicationConfig, - pub default_fault_handler: DefaultFaultHandler, + pub fault_handler: FaultHandler, +} + +impl LocalEntityConfig { + pub fn user_fault_hook_mut(&mut self) -> &mut RefCell { + &mut self.fault_handler.user_hook + } + + pub fn user_fault_hook(&self) -> &RefCell { + &self.fault_handler.user_hook + } +} + +pub trait PduSendProvider { + fn send_pdu( + &self, + pdu_type: PduType, + file_directive_type: Option, + raw_pdu: &[u8], + ) -> Result<(), PduError>; } /// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence @@ -505,18 +553,6 @@ impl PartialEq for TransactionId { } } -#[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 { @@ -627,21 +663,147 @@ impl<'raw> PacketInfo<'raw> { } #[cfg(test)] -mod tests { - use spacepackets::cfdp::{ - lv::Lv, - pdu::{ - eof::EofPdu, - file_data::FileDataPdu, - metadata::{MetadataGenericParams, MetadataPduCreator}, - CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket, +pub(crate) mod tests { + use core::cell::RefCell; + + use alloc::{collections::VecDeque, vec::Vec}; + use spacepackets::{ + cfdp::{ + lv::Lv, + pdu::{ + eof::EofPdu, + file_data::FileDataPdu, + metadata::{MetadataGenericParams, MetadataPduCreator}, + CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, + }, + ChecksumType, ConditionCode, PduType, TransmissionMode, }, - PduType, + util::UnsignedByteFieldU16, }; use crate::cfdp::PacketTarget; - use super::PacketInfo; + use super::{ + PacketInfo, PduSendProvider, RemoteEntityConfig, RemoteEntityConfigProvider, + StdRemoteEntityConfigProvider, TransactionId, UserFaultHookProvider, + }; + + #[derive(Default)] + pub(crate) struct TestFaultHandler { + pub notice_of_suspension_queue: VecDeque<(TransactionId, ConditionCode, u64)>, + pub notice_of_cancellation_queue: VecDeque<(TransactionId, ConditionCode, u64)>, + pub abandoned_queue: VecDeque<(TransactionId, ConditionCode, u64)>, + pub ignored_queue: VecDeque<(TransactionId, ConditionCode, u64)>, + } + + impl UserFaultHookProvider for TestFaultHandler { + fn notice_of_suspension_cb( + &mut self, + transaction_id: TransactionId, + cond: ConditionCode, + progress: u64, + ) { + self.notice_of_suspension_queue + .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 + .push_back((transaction_id, cond, progress)) + } + + fn abandoned_cb( + &mut self, + transaction_id: TransactionId, + cond: ConditionCode, + progress: u64, + ) { + self.abandoned_queue + .push_back((transaction_id, cond, progress)) + } + + fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) { + self.ignored_queue + .push_back((transaction_id, cond, progress)) + } + } + + impl TestFaultHandler { + pub(crate) fn suspension_queue_empty(&self) -> bool { + self.notice_of_suspension_queue.is_empty() + } + pub(crate) fn cancellation_queue_empty(&self) -> bool { + self.notice_of_cancellation_queue.is_empty() + } + pub(crate) fn ignored_queue_empty(&self) -> bool { + self.ignored_queue.is_empty() + } + pub(crate) fn abandoned_queue_empty(&self) -> bool { + self.abandoned_queue.is_empty() + } + pub(crate) fn all_queues_empty(&self) -> bool { + self.suspension_queue_empty() + && self.cancellation_queue_empty() + && self.ignored_queue_empty() + && self.abandoned_queue_empty() + } + } + + pub struct SentPdu { + pub pdu_type: PduType, + pub file_directive_type: Option, + pub raw_pdu: Vec, + } + + #[derive(Default)] + pub struct TestCfdpSender { + pub packet_queue: RefCell>, + } + + impl PduSendProvider for TestCfdpSender { + fn send_pdu( + &self, + pdu_type: PduType, + file_directive_type: Option, + raw_pdu: &[u8], + ) -> Result<(), PduError> { + self.packet_queue.borrow_mut().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.borrow_mut().pop_front() + } + pub fn queue_empty(&self) -> bool { + self.packet_queue.borrow_mut().is_empty() + } + } + + pub 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 generic_pdu_header() -> PduHeader { let pdu_conf = CommonPduConfig::default(); diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index 433f4d2..250bc59 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,15 +1,217 @@ -#![allow(dead_code)] -use spacepackets::util::UnsignedByteField; +use spacepackets::cfdp::{pdu::FileDirectiveType, PduType}; -pub struct SourceHandler { - id: UnsignedByteField, +use super::{ + filestore::VirtualFilestore, user::CfdpUser, LocalEntityConfig, PacketInfo, PacketTarget, + PduSendProvider, RemoteEntityConfigProvider, UserFaultHookProvider, +}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TransactionStep { + Idle = 0, + TransactionStart = 1, + SendingMetadata = 3, + SendingFileData = 4, + /// Re-transmitting missing packets in acknowledged mode6 + Retransmitting = 5, + SendingEof = 6, + WaitingForEofAck = 7, + WaitingForFinished = 8, + SendingAckOfFinished = 9, + NoticeOfCompletion = 10, } -impl SourceHandler { - pub fn new(id: impl Into) -> Self { - Self { id: id.into() } +pub struct FileParams { + pub progress: usize, + pub segment_len: usize, + pub crc32: Option<[u8; 4]>, + pub metadata_only: bool, + pub file_size: usize, + pub no_eof: bool, +} + +pub struct StateHelper { + state: super::State, + step: TransactionStep, + num_packets_ready: u32, +} + +impl Default for StateHelper { + fn default() -> Self { + Self { + state: super::State::Idle, + step: TransactionStep::Idle, + num_packets_ready: 0, + } } } +#[derive(Debug, thiserror::Error)] +pub enum SourceError { + #[error("can not process packet type {pdu_type:?} with directive type {directive_type:?}")] + CantProcessPacketType { + pdu_type: PduType, + directive_type: Option, + }, + #[error("unexpected file data PDU")] + UnexpectedFileDataPdu, +} + +pub struct SourceHandler< + PduSender: PduSendProvider, + UserFaultHook: UserFaultHookProvider, + Vfs: VirtualFilestore, + RemoteCfgTable: RemoteEntityConfigProvider, +> { + local_cfg: LocalEntityConfig, + pdu_sender: PduSender, + remote_cfg_table: RemoteCfgTable, + vfs: Vfs, + state_helper: StateHelper, +} + +impl< + PduSender: PduSendProvider, + UserFaultHook: UserFaultHookProvider, + Vfs: VirtualFilestore, + RemoteCfgTable: RemoteEntityConfigProvider, + > SourceHandler +{ + pub fn new( + cfg: LocalEntityConfig, + pdu_sender: PduSender, + vfs: Vfs, + remote_cfg_table: RemoteCfgTable, + ) -> Self { + Self { + local_cfg: cfg, + remote_cfg_table, + vfs, + pdu_sender, + state_helper: Default::default(), + } + } + + /// This is the core function to drive the source handler. It is also used to insert + /// packets into the source 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. + /// + /// The function returns the number of sent PDU packets on success. + 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_helper.state { + super::State::Idle => todo!(), + super::State::Busy => self.fsm_busy(cfdp_user), + super::State::Suspended => todo!(), + } + } + + fn insert_packet( + &mut self, + cfdp_user: &mut impl CfdpUser, + packet_info: &PacketInfo, + ) -> Result<(), SourceError> { + if packet_info.target() != PacketTarget::SourceEntity { + // Unwrap is okay here, a PacketInfo for a file data PDU should always have the + // destination as the target. + return Err(SourceError::CantProcessPacketType { + pdu_type: packet_info.pdu_type(), + directive_type: packet_info.pdu_directive(), + }); + } + if packet_info.pdu_type() == PduType::FileData { + // The [PacketInfo] API should ensure that file data PDUs can not be passed + // into a source entity, so this should never happen. + return Err(SourceError::UnexpectedFileDataPdu); + } + // Unwrap is okay here, the [PacketInfo] API should ensure that the directive type is + // always a valid value. + match packet_info + .pdu_directive() + .expect("PDU directive type unexpectedly not set") + { + FileDirectiveType::FinishedPdu => self.handle_finished_pdu(), + FileDirectiveType::NakPdu => self.handle_nak_pdu(), + FileDirectiveType::KeepAlivePdu => self.handle_keep_alive_pdu(), + FileDirectiveType::AckPdu => todo!("acknowledged mode not implemented yet"), + FileDirectiveType::EofPdu + | FileDirectiveType::PromptPdu + | FileDirectiveType::MetadataPdu => { + return Err(SourceError::CantProcessPacketType { + pdu_type: packet_info.pdu_type(), + directive_type: packet_info.pdu_directive(), + }); + } + } + Ok(()) + } + + fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { + Ok(0) + } + + fn handle_finished_pdu(&mut self) {} + + fn handle_nak_pdu(&mut self) {} + + fn handle_keep_alive_pdu(&mut self) {} +} + #[cfg(test)] -mod tests {} +mod tests { + use alloc::sync::Arc; + use spacepackets::util::UnsignedByteFieldU16; + + use super::*; + use crate::cfdp::{ + filestore::NativeFilestore, + tests::{basic_remote_cfg_table, TestCfdpSender, TestFaultHandler}, + FaultHandler, IndicationConfig, StdRemoteEntityConfigProvider, + }; + + const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); + const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2); + + type TestSourceHandler = SourceHandler< + TestCfdpSender, + TestFaultHandler, + NativeFilestore, + StdRemoteEntityConfigProvider, + >; + + fn default_source_handler( + test_fault_handler: TestFaultHandler, + test_packet_sender: TestCfdpSender, + ) -> TestSourceHandler { + let local_entity_cfg = LocalEntityConfig { + id: REMOTE_ID.into(), + indication_cfg: IndicationConfig::default(), + fault_handler: FaultHandler::new(test_fault_handler), + }; + SourceHandler::new( + local_entity_cfg, + test_packet_sender, + NativeFilestore::default(), + basic_remote_cfg_table(), + // TestCheckTimerCreator::new(check_timer_expired), + ) + } + + #[test] + fn test_basic() { + let fault_handler = TestFaultHandler::default(); + let test_sender = TestCfdpSender::default(); + let source_handler = default_source_handler(fault_handler, test_sender); + // assert!(dest_handler.transmission_mode().is_none()); + // assert!(fault_handler.all_queues_empty()); + } +} -- 2.43.0 From e9df41614573b0548d356bb6e3892ab42e0ce61b Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Wed, 5 Jun 2024 16:34:21 +0200 Subject: [PATCH 03/18] fix dependency issues --- satrs-mib/Cargo.toml | 1 + satrs-mib/codegen/Cargo.toml | 1 + satrs-shared/Cargo.toml | 4 +- satrs/Cargo.toml | 5 +- satrs/src/cfdp/dest.rs | 3 +- satrs/src/cfdp/mod.rs | 3 +- satrs/src/cfdp/request.rs | 308 +++++++++++++++++++++++++++++++++++ 7 files changed, 321 insertions(+), 4 deletions(-) create mode 100644 satrs/src/cfdp/request.rs 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..3a2520b 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 = "cfdp-tlv-owned-type" [features] serde = ["dep:serde", "spacepackets/serde"] diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index ec2b2d0..5c9fd0d 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 = "cfdp-tlv-owned-type" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs index b1e12da..c320bb2 100644 --- a/satrs/src/cfdp/dest.rs +++ b/satrs/src/cfdp/dest.rs @@ -20,7 +20,7 @@ use spacepackets::{ metadata::{MetadataGenericParams, MetadataPduReader}, CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }, - tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, TlvType}, + tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, ReadableTlv, TlvType}, ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode, }, util::{UnsignedByteField, UnsignedEnum}, @@ -94,6 +94,7 @@ struct TransactionParams { file_properties: FileProperties, cksum_buf: [u8; 1024], msgs_to_user_size: usize, + // TODO: Should we make this configurable? msgs_to_user_buf: [u8; 1024], remote_cfg: Option, } diff --git a/satrs/src/cfdp/mod.rs b/satrs/src/cfdp/mod.rs index 51f72e6..514b8ce 100644 --- a/satrs/src/cfdp/mod.rs +++ b/satrs/src/cfdp/mod.rs @@ -21,6 +21,7 @@ use crate::time::CountdownProvider; pub mod dest; #[cfg(feature = "alloc")] pub mod filestore; +pub mod request; #[cfg(feature = "std")] pub mod source; pub mod user; @@ -332,7 +333,7 @@ pub trait UserFaultHookProvider { } #[derive(Debug, PartialEq, Eq, Copy, Clone)] -struct DummyFaultHook {} +pub struct DummyFaultHook {} impl UserFaultHookProvider for DummyFaultHook { fn notice_of_suspension_cb( diff --git a/satrs/src/cfdp/request.rs b/satrs/src/cfdp/request.rs new file mode 100644 index 0000000..ce87c04 --- /dev/null +++ b/satrs/src/cfdp/request.rs @@ -0,0 +1,308 @@ +use spacepackets::{ + cfdp::{ + tlv::{GenericTlv, Tlv, TlvType}, + SegmentationControl, TransmissionMode, + }, + util::UnsignedByteField, +}; + +trait ReadablePutRequest { + fn destination_id(&self) -> UnsignedByteField; + fn source_file(&self) -> Option<&str>; + fn dest_file(&self) -> Option<&str>; + fn trans_mode(&self) -> Option; + fn closure_requested(&self) -> Option; + fn seg_ctrl(&self) -> Option; + fn msgs_to_user(&self, f: impl FnMut(&Tlv)); + fn fault_handler_overrides(&self, f: impl FnMut(&Tlv)); + fn flow_label(&self) -> Option; + fn fs_requests(&self, f: impl FnMut(&Tlv)); +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> { + pub destination_id: UnsignedByteField, + pub source_file: Option<&'src_file str>, + pub dest_file: Option<&'dest_file str>, + pub trans_mode: Option, + pub closure_requested: Option, + pub seg_ctrl: Option, + pub msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>, + pub fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>, + pub flow_label: Option>, + pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>, +} + +impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { + fn destination_id(&self) -> UnsignedByteField { + self.destination_id + } + + fn source_file(&self) -> Option<&str> { + self.source_file + } + + fn dest_file(&self) -> Option<&str> { + self.dest_file + } + + fn trans_mode(&self) -> Option { + self.trans_mode + } + + fn closure_requested(&self) -> Option { + self.closure_requested + } + + fn seg_ctrl(&self) -> Option { + self.seg_ctrl + } + + fn msgs_to_user(&self, mut f: impl FnMut(&Tlv)) { + if let Some(msgs_to_user) = self.msgs_to_user { + for msg_to_user in msgs_to_user { + f(msg_to_user) + } + } + } + + fn fault_handler_overrides(&self, mut f: impl FnMut(&Tlv)) { + if let Some(fh_overrides) = self.fault_handler_overrides { + for fh_override in fh_overrides { + f(fh_override) + } + } + } + + fn flow_label(&self) -> Option { + self.flow_label + } + + fn fs_requests(&self, mut f: impl FnMut(&Tlv)) { + if let Some(fs_requests) = self.fs_requests { + for fs_request in fs_requests { + f(fs_request) + } + } + } +} + +impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, 'static, 'static> { + pub fn new_regular_request( + dest_id: UnsignedByteField, + source_file: &'src_file str, + dest_file: &'dest_file str, + trans_mode: Option, + closure_requested: Option, + ) -> Self { + Self { + destination_id: dest_id, + source_file: Some(source_file), + dest_file: Some(dest_file), + trans_mode, + closure_requested, + seg_ctrl: None, + msgs_to_user: None, + fault_handler_overrides: None, + flow_label: None, + fs_requests: None, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct TlvWithInvalidType(pub(crate) ()); + +impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> { + pub fn new_msgs_to_user_only( + dest_id: UnsignedByteField, + msgs_to_user: &'msgs_to_user [Tlv<'msgs_to_user>], + ) -> Result { + Ok(Self { + destination_id: dest_id, + source_file: None, + dest_file: None, + trans_mode: None, + closure_requested: None, + seg_ctrl: None, + msgs_to_user: Some(msgs_to_user), + fault_handler_overrides: None, + flow_label: None, + fs_requests: None, + }) + } + + /// Uses [generic_tlv_list_type_check] to check the TLV type validity of all TLV fields. + pub fn check_tlv_type_validities(&self) -> bool { + generic_tlv_list_type_check(self.msgs_to_user, TlvType::MsgToUser); + if let Some(msgs_to_user) = self.msgs_to_user { + for msg_to_user in msgs_to_user { + if msg_to_user.tlv_type().is_none() { + return false; + } + if msg_to_user.tlv_type().unwrap() != TlvType::MsgToUser { + return false; + } + } + } + generic_tlv_list_type_check(self.fault_handler_overrides, TlvType::FaultHandler); + generic_tlv_list_type_check(self.fs_requests, TlvType::FilestoreRequest); + true + } +} + +pub fn generic_tlv_list_type_check(opt_tlvs: Option<&[Tlv<'_>]>, tlv_type: TlvType) -> bool { + if let Some(tlvs) = opt_tlvs { + for tlv in tlvs { + if tlv.tlv_type().is_none() { + return false; + } + if tlv.tlv_type().unwrap() != tlv_type { + return false; + } + } + } + true +} + +#[cfg(feature = "alloc")] +pub mod alloc_mod { + use super::*; + use alloc::string::ToString; + use spacepackets::cfdp::tlv::{msg_to_user::MsgToUserTlv, TlvOwned}; + + #[derive(Debug, Clone, PartialEq, Eq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct PutRequestOwned { + pub destination_id: UnsignedByteField, + pub source_file: Option, + pub dest_file: Option, + pub trans_mode: Option, + pub closure_requested: Option, + pub seg_ctrl: Option, + pub msgs_to_user: Option>, + pub fault_handler_overrides: Option>, + pub flow_label: Option, + pub fs_requests: Option>, + } + + impl PutRequestOwned { + pub fn new_regular_request( + dest_id: UnsignedByteField, + source_file: &str, + dest_file: &str, + trans_mode: Option, + closure_requested: Option, + ) -> Self { + Self { + destination_id: dest_id, + source_file: Some(source_file.to_string()), + dest_file: Some(dest_file.to_string()), + trans_mode, + closure_requested, + seg_ctrl: None, + msgs_to_user: None, + fault_handler_overrides: None, + flow_label: None, + fs_requests: None, + } + } + + pub fn new_msgs_to_user_only( + dest_id: UnsignedByteField, + msgs_to_user: &[MsgToUserTlv<'_>], + ) -> Result { + Ok(Self { + destination_id: dest_id, + source_file: None, + dest_file: None, + trans_mode: None, + closure_requested: None, + seg_ctrl: None, + msgs_to_user: Some(msgs_to_user.iter().map(|msg| msg.tlv.to_owned()).collect()), + fault_handler_overrides: None, + flow_label: None, + fs_requests: None, + }) + } + } + + impl From> for PutRequestOwned { + fn from(req: PutRequest<'_, '_, '_, '_, '_, '_>) -> Self { + Self { + destination_id: req.destination_id, + source_file: req.source_file.map(|s| s.into()), + dest_file: req.dest_file.map(|s| s.into()), + trans_mode: req.trans_mode, + closure_requested: req.closure_requested, + seg_ctrl: req.seg_ctrl, + msgs_to_user: req + .msgs_to_user + .map(|msgs_to_user| msgs_to_user.iter().map(|msg| msg.to_owned()).collect()), + fault_handler_overrides: req + .msgs_to_user + .map(|fh_overides| fh_overides.iter().map(|msg| msg.to_owned()).collect()), + flow_label: req + .flow_label + .map(|flow_label_tlv| flow_label_tlv.to_owned()), + fs_requests: req + .fs_requests + .map(|fs_requests| fs_requests.iter().map(|msg| msg.to_owned()).collect()), + } + } + } + + impl ReadablePutRequest for PutRequestOwned { + fn destination_id(&self) -> UnsignedByteField { + self.destination_id + } + + fn source_file(&self) -> Option<&str> { + self.source_file.as_deref() + } + + fn dest_file(&self) -> Option<&str> { + self.dest_file.as_deref() + } + + fn trans_mode(&self) -> Option { + self.trans_mode + } + + fn closure_requested(&self) -> Option { + self.closure_requested + } + + fn seg_ctrl(&self) -> Option { + self.seg_ctrl + } + + fn msgs_to_user(&self, mut f: impl FnMut(&Tlv)) { + if let Some(msgs_to_user) = &self.msgs_to_user { + for msg_to_user in msgs_to_user { + f(&msg_to_user.as_tlv()) + } + } + } + + fn fault_handler_overrides(&self, mut f: impl FnMut(&Tlv)) { + if let Some(fh_overrides) = &self.fault_handler_overrides { + for fh_override in fh_overrides { + f(&fh_override.as_tlv()) + } + } + } + + fn flow_label(&self) -> Option { + self.flow_label.as_ref().map(|tlv| tlv.as_tlv()) + } + + fn fs_requests(&self, mut f: impl FnMut(&Tlv)) { + if let Some(fs_requests) = &self.fs_requests { + for fs_request in fs_requests { + f(&fs_request.as_tlv()) + } + } + } + } +} -- 2.43.0 From 8a48a624cd6c2105d9d7487464a6347b578af17f Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 11 Jun 2024 09:45:55 +0200 Subject: [PATCH 04/18] introduce put request cacher --- satrs-shared/Cargo.toml | 2 +- satrs/Cargo.toml | 2 +- satrs/src/cfdp/request.rs | 430 ++++++++++++++++++++++++++++++++++++-- satrs/src/cfdp/source.rs | 8 +- 4 files changed, 417 insertions(+), 25 deletions(-) diff --git a/satrs-shared/Cargo.toml b/satrs-shared/Cargo.toml index 3a2520b..628ffa1 100644 --- a/satrs-shared/Cargo.toml +++ b/satrs-shared/Cargo.toml @@ -25,7 +25,7 @@ optional = true version = ">0.9" default-features = false git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" -branch = "cfdp-tlv-owned-type" +branch = "all-cfdp-updates" [features] serde = ["dep:serde", "spacepackets/serde"] diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index 5c9fd0d..3890dbd 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -31,7 +31,7 @@ default-features = false version = "0.12" default-features = false git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" -branch = "cfdp-tlv-owned-type" +branch = "all-cfdp-updates" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs/src/cfdp/request.rs b/satrs/src/cfdp/request.rs index ce87c04..7fc6e0a 100644 --- a/satrs/src/cfdp/request.rs +++ b/satrs/src/cfdp/request.rs @@ -6,24 +6,35 @@ use spacepackets::{ util::UnsignedByteField, }; -trait ReadablePutRequest { +#[cfg(feature = "alloc")] +pub use alloc_mod::*; + +#[derive(Debug, PartialEq, Eq)] +pub struct FilePathTooLarge(pub usize); + +/// This trait is an abstraction for different Put Request structures which can be used +/// by Put Request consumers. +pub trait ReadablePutRequest { fn destination_id(&self) -> UnsignedByteField; fn source_file(&self) -> Option<&str>; fn dest_file(&self) -> Option<&str>; fn trans_mode(&self) -> Option; fn closure_requested(&self) -> Option; fn seg_ctrl(&self) -> Option; + fn has_msgs_to_user(&self) -> bool; fn msgs_to_user(&self, f: impl FnMut(&Tlv)); + fn has_fault_handler_overrides(&self) -> bool; fn fault_handler_overrides(&self, f: impl FnMut(&Tlv)); fn flow_label(&self) -> Option; + fn has_fs_requests(&self) -> bool; fn fs_requests(&self, f: impl FnMut(&Tlv)); } #[derive(Debug, PartialEq, Eq)] pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> { pub destination_id: UnsignedByteField, - pub source_file: Option<&'src_file str>, - pub dest_file: Option<&'dest_file str>, + source_file: Option<&'src_file str>, + dest_file: Option<&'dest_file str>, pub trans_mode: Option, pub closure_requested: Option, pub seg_ctrl: Option, @@ -33,6 +44,38 @@ pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_lab pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>, } +impl<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> + PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> +{ + #[allow(clippy::too_many_arguments)] + pub fn new( + destination_id: UnsignedByteField, + source_file: Option<&'src_file str>, + dest_file: Option<&'dest_file str>, + trans_mode: Option, + closure_requested: Option, + seg_ctrl: Option, + msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>, + fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>, + flow_label: Option>, + fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>, + ) -> Result { + generic_path_checks(source_file, dest_file)?; + Ok(Self { + destination_id, + source_file, + dest_file, + trans_mode, + closure_requested, + seg_ctrl, + msgs_to_user, + fault_handler_overrides, + flow_label, + fs_requests, + }) + } +} + impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { fn destination_id(&self) -> UnsignedByteField { self.destination_id @@ -58,6 +101,10 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { self.seg_ctrl } + fn has_msgs_to_user(&self) -> bool { + self.msgs_to_user.is_some() && self.msgs_to_user.unwrap().is_empty() + } + fn msgs_to_user(&self, mut f: impl FnMut(&Tlv)) { if let Some(msgs_to_user) = self.msgs_to_user { for msg_to_user in msgs_to_user { @@ -66,6 +113,10 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { } } + fn has_fault_handler_overrides(&self) -> bool { + self.fault_handler_overrides.is_some() && self.fault_handler_overrides.unwrap().is_empty() + } + fn fault_handler_overrides(&self, mut f: impl FnMut(&Tlv)) { if let Some(fh_overrides) = self.fault_handler_overrides { for fh_override in fh_overrides { @@ -78,6 +129,10 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { self.flow_label } + fn has_fs_requests(&self) -> bool { + self.fs_requests.is_some() && self.fs_requests.unwrap().is_empty() + } + fn fs_requests(&self, mut f: impl FnMut(&Tlv)) { if let Some(fs_requests) = self.fs_requests { for fs_request in fs_requests { @@ -87,6 +142,23 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { } } +pub fn generic_path_checks( + source_file: Option<&str>, + dest_file: Option<&str>, +) -> Result<(), FilePathTooLarge> { + if let Some(src_file) = source_file { + if src_file.len() > u8::MAX as usize { + return Err(FilePathTooLarge(src_file.len())); + } + } + if let Some(dest_file) = dest_file { + if dest_file.len() > u8::MAX as usize { + return Err(FilePathTooLarge(dest_file.len())); + } + } + Ok(()) +} + impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, 'static, 'static> { pub fn new_regular_request( dest_id: UnsignedByteField, @@ -94,8 +166,9 @@ impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, dest_file: &'dest_file str, trans_mode: Option, closure_requested: Option, - ) -> Self { - Self { + ) -> Result { + generic_path_checks(Some(source_file), Some(dest_file))?; + Ok(Self { destination_id: dest_id, source_file: Some(source_file), dest_file: Some(dest_file), @@ -106,7 +179,7 @@ impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, fault_handler_overrides: None, flow_label: None, fs_requests: None, - } + }) } } @@ -135,14 +208,12 @@ impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static /// Uses [generic_tlv_list_type_check] to check the TLV type validity of all TLV fields. pub fn check_tlv_type_validities(&self) -> bool { generic_tlv_list_type_check(self.msgs_to_user, TlvType::MsgToUser); - if let Some(msgs_to_user) = self.msgs_to_user { - for msg_to_user in msgs_to_user { - if msg_to_user.tlv_type().is_none() { - return false; - } - if msg_to_user.tlv_type().unwrap() != TlvType::MsgToUser { - return false; - } + if let Some(flow_label) = &self.flow_label { + if flow_label.tlv_type().is_none() { + return false; + } + if flow_label.tlv_type().unwrap() != TlvType::FlowLabel { + return false; } } generic_tlv_list_type_check(self.fault_handler_overrides, TlvType::FaultHandler); @@ -151,7 +222,10 @@ impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static } } -pub fn generic_tlv_list_type_check(opt_tlvs: Option<&[Tlv<'_>]>, tlv_type: TlvType) -> bool { +pub fn generic_tlv_list_type_check( + opt_tlvs: Option<&[TlvProvider]>, + tlv_type: TlvType, +) -> bool { if let Some(tlvs) = opt_tlvs { for tlv in tlvs { if tlv.tlv_type().is_none() { @@ -167,16 +241,22 @@ pub fn generic_tlv_list_type_check(opt_tlvs: Option<&[Tlv<'_>]>, tlv_type: TlvTy #[cfg(feature = "alloc")] pub mod alloc_mod { + use core::str::Utf8Error; + use super::*; use alloc::string::ToString; - use spacepackets::cfdp::tlv::{msg_to_user::MsgToUserTlv, TlvOwned}; + use spacepackets::{ + cfdp::tlv::{msg_to_user::MsgToUserTlv, ReadableTlv, TlvOwned, WritableTlv}, + ByteConversionError, + }; + /// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PutRequestOwned { pub destination_id: UnsignedByteField, - pub source_file: Option, - pub dest_file: Option, + source_file: Option, + dest_file: Option, pub trans_mode: Option, pub closure_requested: Option, pub seg_ctrl: Option, @@ -193,8 +273,14 @@ pub mod alloc_mod { dest_file: &str, trans_mode: Option, closure_requested: Option, - ) -> Self { - Self { + ) -> Result { + if source_file.len() > u8::MAX as usize { + return Err(FilePathTooLarge(source_file.len())); + } + if dest_file.len() > u8::MAX as usize { + return Err(FilePathTooLarge(dest_file.len())); + } + Ok(Self { destination_id: dest_id, source_file: Some(source_file.to_string()), dest_file: Some(dest_file.to_string()), @@ -205,7 +291,7 @@ pub mod alloc_mod { fault_handler_overrides: None, flow_label: None, fs_requests: None, - } + }) } pub fn new_msgs_to_user_only( @@ -225,6 +311,25 @@ pub mod alloc_mod { fs_requests: None, }) } + + /// Uses [generic_tlv_list_type_check] to check the TLV type validity of all TLV fields. + pub fn check_tlv_type_validities(&self) -> bool { + generic_tlv_list_type_check(self.msgs_to_user.as_deref(), TlvType::MsgToUser); + if let Some(flow_label) = &self.flow_label { + if flow_label.tlv_type().is_none() { + return false; + } + if flow_label.tlv_type().unwrap() != TlvType::FlowLabel { + return false; + } + } + generic_tlv_list_type_check( + self.fault_handler_overrides.as_deref(), + TlvType::FaultHandler, + ); + generic_tlv_list_type_check(self.fs_requests.as_deref(), TlvType::FilestoreRequest); + true + } } impl From> for PutRequestOwned { @@ -304,5 +409,288 @@ pub mod alloc_mod { } } } + + fn has_msgs_to_user(&self) -> bool { + self.msgs_to_user.is_some() && !self.msgs_to_user.as_ref().unwrap().is_empty() + } + + fn has_fault_handler_overrides(&self) -> bool { + self.fault_handler_overrides.is_some() + && !self.fault_handler_overrides.as_ref().unwrap().is_empty() + } + + fn has_fs_requests(&self) -> bool { + self.fs_requests.is_some() && !self.fs_requests.as_ref().unwrap().is_empty() + } + } + + pub struct StaticPutRequestFields { + pub destination_id: UnsignedByteField, + /// Static buffer to store source file path. + pub source_file_buf: [u8; u8::MAX as usize], + /// Current source path length. + pub source_file_len: usize, + /// Static buffer to store dest file path. + pub dest_file_buf: [u8; u8::MAX as usize], + /// Current destination path length. + pub dest_file_len: usize, + pub trans_mode: Option, + pub closure_requested: Option, + pub seg_ctrl: Option, + } + + impl Default for StaticPutRequestFields { + fn default() -> Self { + Self { + destination_id: UnsignedByteField::new(0, 0), + source_file_buf: [0; u8::MAX as usize], + source_file_len: Default::default(), + dest_file_buf: [0; u8::MAX as usize], + dest_file_len: Default::default(), + trans_mode: Default::default(), + closure_requested: Default::default(), + seg_ctrl: Default::default(), + } + } + } + + impl StaticPutRequestFields { + pub fn clear(&mut self) { + self.destination_id = UnsignedByteField::new(0, 0); + self.source_file_len = 0; + self.dest_file_len = 0; + self.trans_mode = None; + self.closure_requested = None; + self.seg_ctrl = None; + } + } + + /// This is a put request cache structure which can be used to cache [ReadablePutRequest]s + /// without requiring run-time allocation. The user must specify the static buffer sizes used + /// to store TLVs or list of TLVs. + pub struct StaticPutRequestCacher { + pub static_fields: StaticPutRequestFields, + /// Static buffer to store file store requests. + pub fs_requests: alloc::vec::Vec, + /// Current total length of stored filestore requests. + pub fs_requests_len: usize, + } + + pub struct PutRequestCacheConfig { + pub max_msgs_to_user_storage: usize, + pub max_fault_handler_overrides_storage: usize, + pub max_flow_label_storage: usize, + pub max_fs_requests_storage: usize, + } + + impl StaticPutRequestCacher { + pub fn new(cfg: PutRequestCacheConfig) -> Self { + Self { + static_fields: StaticPutRequestFields::default(), + fs_requests: alloc::vec![0; cfg.max_fs_requests_storage], + fs_requests_len: 0, + } + } + + pub fn set( + &mut self, + put_request: &impl ReadablePutRequest, + ) -> Result<(), ByteConversionError> { + self.static_fields.destination_id = put_request.destination_id(); + if let Some(source_file) = put_request.source_file() { + if source_file.len() > u8::MAX as usize { + return Err(ByteConversionError::ToSliceTooSmall { + found: self.static_fields.source_file_buf.len(), + expected: source_file.len(), + }); + } + self.static_fields.source_file_buf[..source_file.len()] + .copy_from_slice(source_file.as_bytes()); + self.static_fields.source_file_len = source_file.len(); + } + if let Some(dest_file) = put_request.dest_file() { + if dest_file.len() > u8::MAX as usize { + return Err(ByteConversionError::ToSliceTooSmall { + found: self.static_fields.source_file_buf.len(), + expected: dest_file.len(), + }); + } + self.static_fields.dest_file_buf[..dest_file.len()] + .copy_from_slice(dest_file.as_bytes()); + self.static_fields.dest_file_len = dest_file.len(); + } + self.static_fields.trans_mode = put_request.trans_mode(); + self.static_fields.closure_requested = put_request.closure_requested(); + self.static_fields.seg_ctrl = put_request.seg_ctrl(); + let mut current_idx = 0; + let mut error_if_too_large = None; + let mut store_fs_requests = |tlv: &Tlv| { + if current_idx + tlv.len_full() > self.fs_requests.len() { + error_if_too_large = Some(ByteConversionError::ToSliceTooSmall { + found: self.fs_requests.len(), + expected: current_idx + tlv.len_full(), + }); + return; + } + // We checked the buffer lengths, so this should never fail. + tlv.write_to_bytes( + &mut self.fs_requests[current_idx..current_idx + tlv.len_full()], + ) + .unwrap(); + current_idx += tlv.len_full(); + }; + put_request.fs_requests(&mut store_fs_requests); + if let Some(err) = error_if_too_large { + return Err(err); + } + self.fs_requests_len = current_idx; + Ok(()) + } + + pub fn source_file(&self) -> Result<&str, Utf8Error> { + core::str::from_utf8( + &self.static_fields.source_file_buf[0..self.static_fields.source_file_len], + ) + } + + pub fn dest_file(&self) -> Result<&str, Utf8Error> { + core::str::from_utf8( + &self.static_fields.dest_file_buf[0..self.static_fields.dest_file_len], + ) + } + + /// This clears the cacher structure. This is a cheap operation because it only + /// sets [Option]al values to [None] and the length of stores TLVs to 0. + /// + /// Please note that this method will not set the values in the buffer to 0. + pub fn clear(&mut self) { + self.static_fields.clear(); + self.fs_requests_len = 0; + } + } +} + +#[cfg(test)] +mod tests { + use spacepackets::util::UbfU16; + + use super::*; + + pub const DEST_ID: UbfU16 = UbfU16::new(5); + + #[test] + fn test_put_request_basic() { + let src_file = "/tmp/hello.txt"; + let dest_file = "/tmp/hello2.txt"; + let put_request = + PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) + .unwrap(); + assert_eq!(put_request.source_file(), Some(src_file)); + assert_eq!(put_request.dest_file(), Some(dest_file)); + assert_eq!(put_request.destination_id(), DEST_ID.into()); + assert_eq!(put_request.seg_ctrl(), None); + assert_eq!(put_request.closure_requested(), None); + assert_eq!(put_request.trans_mode(), None); + assert!(!put_request.has_fs_requests()); + let dummy = |_tlv: &Tlv| { + panic!("should not be called"); + }; + put_request.fs_requests(&dummy); + assert!(!put_request.has_msgs_to_user()); + put_request.msgs_to_user(&dummy); + assert!(!put_request.has_fault_handler_overrides()); + put_request.fault_handler_overrides(&dummy); + assert!(put_request.flow_label().is_none()); + } + + #[test] + fn test_put_request_owned_basic() { + let src_file = "/tmp/hello.txt"; + let dest_file = "/tmp/hello2.txt"; + let put_request = + PutRequestOwned::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) + .unwrap(); + assert_eq!(put_request.source_file(), Some(src_file)); + assert_eq!(put_request.dest_file(), Some(dest_file)); + assert_eq!(put_request.destination_id(), DEST_ID.into()); + assert_eq!(put_request.seg_ctrl(), None); + assert_eq!(put_request.closure_requested(), None); + assert_eq!(put_request.trans_mode(), None); + assert!(!put_request.has_fs_requests()); + let dummy = |_tlv: &Tlv| { + panic!("should not be called"); + }; + put_request.fs_requests(&dummy); + assert!(!put_request.has_msgs_to_user()); + put_request.msgs_to_user(&dummy); + assert!(!put_request.has_fault_handler_overrides()); + put_request.fault_handler_overrides(&dummy); + assert!(put_request.flow_label().is_none()); + let put_request_cloned = put_request.clone(); + assert_eq!(put_request, put_request_cloned); + } + + #[test] + fn test_put_request_cacher_basic() { + let cacher_cfg = PutRequestCacheConfig { + max_msgs_to_user_storage: 512, + max_fault_handler_overrides_storage: 128, + max_flow_label_storage: 128, + max_fs_requests_storage: 512, + }; + let put_request_cached = StaticPutRequestCacher::new(cacher_cfg); + assert_eq!(put_request_cached.static_fields.source_file_len, 0); + assert_eq!(put_request_cached.static_fields.dest_file_len, 0); + assert_eq!(put_request_cached.fs_requests_len, 0); + assert_eq!(put_request_cached.fs_requests.len(), 512); + } + + #[test] + fn test_put_request_cacher_set() { + let cacher_cfg = PutRequestCacheConfig { + max_msgs_to_user_storage: 512, + max_fault_handler_overrides_storage: 128, + max_flow_label_storage: 128, + max_fs_requests_storage: 512, + }; + let mut put_request_cached = StaticPutRequestCacher::new(cacher_cfg); + let src_file = "/tmp/hello.txt"; + let dest_file = "/tmp/hello2.txt"; + let put_request = + PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) + .unwrap(); + put_request_cached.set(&put_request).unwrap(); + assert_eq!( + put_request_cached.static_fields.source_file_len, + src_file.len() + ); + assert_eq!( + put_request_cached.static_fields.dest_file_len, + dest_file.len() + ); + assert_eq!(put_request_cached.source_file().unwrap(), src_file); + assert_eq!(put_request_cached.dest_file().unwrap(), dest_file); + assert_eq!(put_request_cached.fs_requests_len, 0); + } + + #[test] + fn test_put_request_cacher_set_and_clear() { + let cacher_cfg = PutRequestCacheConfig { + max_msgs_to_user_storage: 512, + max_fault_handler_overrides_storage: 128, + max_flow_label_storage: 128, + max_fs_requests_storage: 512, + }; + let mut put_request_cached = StaticPutRequestCacher::new(cacher_cfg); + let src_file = "/tmp/hello.txt"; + let dest_file = "/tmp/hello2.txt"; + let put_request = + PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) + .unwrap(); + put_request_cached.set(&put_request).unwrap(); + put_request_cached.clear(); + assert_eq!(put_request_cached.static_fields.source_file_len, 0); + assert_eq!(put_request_cached.static_fields.dest_file_len, 0); + assert_eq!(put_request_cached.fs_requests_len, 0); } } diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index 250bc59..ca81be4 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,8 +1,8 @@ use spacepackets::cfdp::{pdu::FileDirectiveType, PduType}; use super::{ - filestore::VirtualFilestore, user::CfdpUser, LocalEntityConfig, PacketInfo, PacketTarget, - PduSendProvider, RemoteEntityConfigProvider, UserFaultHookProvider, + filestore::VirtualFilestore, request::ReadablePutRequest, user::CfdpUser, LocalEntityConfig, + PacketInfo, PacketTarget, PduSendProvider, RemoteEntityConfigProvider, UserFaultHookProvider, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -155,6 +155,10 @@ impl< Ok(()) } + fn put_request(&mut self, put_request: &impl ReadablePutRequest) -> Result<(), SourceError> { + Ok(()) + } + fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { Ok(0) } -- 2.43.0 From 35b24ba9de453aabc08909880f42980cb277ae26 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Thu, 27 Jun 2024 20:29:37 +0200 Subject: [PATCH 05/18] re-worked seq count provider a bit --- satrs-example/src/tmtc/tm_sink.rs | 7 +- satrs/src/cfdp/dest.rs | 8 +- satrs/src/cfdp/mod.rs | 8 +- satrs/src/cfdp/request.rs | 11 +-- satrs/src/cfdp/source.rs | 126 +++++++++++++++++++++++++++--- satrs/src/seq_count.rs | 40 +++++----- 6 files changed, 148 insertions(+), 52 deletions(-) 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/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs index c320bb2..39f3836 100644 --- a/satrs/src/cfdp/dest.rs +++ b/satrs/src/cfdp/dest.rs @@ -54,6 +54,7 @@ pub enum TransactionStep { SendingFinishedPdu = 6, } +// This contains transfer state parameters for destination transaction. #[derive(Debug)] struct TransferState { transaction_id: Option, @@ -87,6 +88,7 @@ impl Default for TransferState { } } +// This contains parameters for destination transaction. #[derive(Debug)] struct TransactionParams { tstate: TransferState, @@ -380,9 +382,7 @@ impl< 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()); + let remote_cfg = self.remote_cfg_table.get(metadata_pdu.source_id().value()); if remote_cfg.is_none() { return Err(DestError::NoRemoteCfgFound(metadata_pdu.dest_id())); } @@ -847,7 +847,7 @@ mod tests { tests::{basic_remote_cfg_table, SentPdu, TestCfdpSender, TestFaultHandler}, user::OwnedMetadataRecvdParams, CheckTimerProviderCreator, CountdownProvider, FaultHandler, IndicationConfig, - RemoteEntityConfig, StdRemoteEntityConfigProvider, CRC_32, + StdRemoteEntityConfigProvider, CRC_32, }; use super::*; diff --git a/satrs/src/cfdp/mod.rs b/satrs/src/cfdp/mod.rs index 514b8ce..322bd81 100644 --- a/satrs/src/cfdp/mod.rs +++ b/satrs/src/cfdp/mod.rs @@ -268,8 +268,8 @@ impl RemoteEntityConfig { 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>; + fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig>; + fn get_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; @@ -286,10 +286,10 @@ pub struct StdRemoteEntityConfigProvider { #[cfg(feature = "std")] impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider { - fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> { + fn get(&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> { + fn get_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 { diff --git a/satrs/src/cfdp/request.rs b/satrs/src/cfdp/request.rs index 7fc6e0a..9254f70 100644 --- a/satrs/src/cfdp/request.rs +++ b/satrs/src/cfdp/request.rs @@ -476,18 +476,11 @@ pub mod alloc_mod { pub fs_requests_len: usize, } - pub struct PutRequestCacheConfig { - pub max_msgs_to_user_storage: usize, - pub max_fault_handler_overrides_storage: usize, - pub max_flow_label_storage: usize, - pub max_fs_requests_storage: usize, - } - impl StaticPutRequestCacher { - pub fn new(cfg: PutRequestCacheConfig) -> Self { + pub fn new(max_fs_requests_storage: usize) -> Self { Self { static_fields: StaticPutRequestFields::default(), - fs_requests: alloc::vec![0; cfg.max_fs_requests_storage], + fs_requests: alloc::vec![0; max_fs_requests_storage], fs_requests_len: 0, } } diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index ca81be4..4e1c4eb 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,8 +1,17 @@ -use spacepackets::cfdp::{pdu::FileDirectiveType, PduType}; +use spacepackets::{ + cfdp::{pdu::FileDirectiveType, PduType}, + util::UnsignedByteField, + ByteConversionError, +}; + +use crate::seq_count::SequenceCountProvider; use super::{ - filestore::VirtualFilestore, request::ReadablePutRequest, user::CfdpUser, LocalEntityConfig, - PacketInfo, PacketTarget, PduSendProvider, RemoteEntityConfigProvider, UserFaultHookProvider, + filestore::VirtualFilestore, + request::{ReadablePutRequest, StaticPutRequestCacher}, + user::CfdpUser, + LocalEntityConfig, PacketInfo, PacketTarget, PduSendProvider, RemoteEntityConfig, + RemoteEntityConfigProvider, TransactionId, UserFaultHookProvider, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -12,7 +21,7 @@ pub enum TransactionStep { TransactionStart = 1, SendingMetadata = 3, SendingFileData = 4, - /// Re-transmitting missing packets in acknowledged mode6 + /// Re-transmitting missing packets in acknowledged mode Retransmitting = 5, SendingEof = 6, WaitingForEofAck = 7, @@ -36,6 +45,14 @@ pub struct StateHelper { num_packets_ready: u32, } +#[derive(Debug, Copy, Clone, derive_new::new)] +pub struct TransferState { + transaction_id: TransactionId, + remote_cfg: RemoteEntityConfig, + transmission_mode: super::TransmissionMode, + closure_requested: bool, +} + impl Default for StateHelper { fn default() -> Self { Self { @@ -55,6 +72,18 @@ pub enum SourceError { }, #[error("unexpected file data PDU")] UnexpectedFileDataPdu, + #[error("source handler is already busy with put request")] + PutRequestAlreadyActive, + #[error("error caching put request")] + PutRequestCaching(ByteConversionError), +} + +#[derive(Debug, thiserror::Error)] +pub enum PutRequestError { + #[error("error caching put request: {0}")] + Storage(#[from] ByteConversionError), + #[error("already busy with put request")] + AlreadyBusy, } pub struct SourceHandler< @@ -62,12 +91,16 @@ pub struct SourceHandler< UserFaultHook: UserFaultHookProvider, Vfs: VirtualFilestore, RemoteCfgTable: RemoteEntityConfigProvider, + SeqCountProvider: SequenceCountProvider, > { local_cfg: LocalEntityConfig, pdu_sender: PduSender, + put_request_cacher: StaticPutRequestCacher, remote_cfg_table: RemoteCfgTable, vfs: Vfs, state_helper: StateHelper, + tstate: Option, + seq_count_provider: SeqCountProvider, } impl< @@ -75,20 +108,26 @@ impl< UserFaultHook: UserFaultHookProvider, Vfs: VirtualFilestore, RemoteCfgTable: RemoteEntityConfigProvider, - > SourceHandler + SeqCountProvider: SequenceCountProvider, + > SourceHandler { pub fn new( cfg: LocalEntityConfig, pdu_sender: PduSender, vfs: Vfs, + put_request_cacher: StaticPutRequestCacher, remote_cfg_table: RemoteCfgTable, + seq_count_provider: SeqCountProvider, ) -> Self { Self { local_cfg: cfg, remote_cfg_table, - vfs, pdu_sender, + vfs, + put_request_cacher, state_helper: Default::default(), + tstate: Default::default(), + seq_count_provider, } } @@ -155,10 +194,66 @@ impl< Ok(()) } - fn put_request(&mut self, put_request: &impl ReadablePutRequest) -> Result<(), SourceError> { + pub fn put_request( + &mut self, + put_request: &impl ReadablePutRequest, + ) -> Result<(), PutRequestError> { + if self.state_helper.state != super::State::Idle { + return Err(PutRequestError::AlreadyBusy); + } + self.put_request_cacher.set(put_request)?; + self.state_helper.state = super::State::Busy; + let source_file = self.put_request_cacher.source_file().unwrap(); + if !self.vfs.exists(source_file) { + // TODO: Specific error. + } + let remote_cfg = self.remote_cfg_table.get( + self.put_request_cacher + .static_fields + .destination_id + .value_const(), + ); + if remote_cfg.is_none() { + // TODO: Specific error. + } + let remote_cfg = remote_cfg.unwrap(); + self.state_helper.num_packets_ready = 0; + //self.tstate.remote_cfg = Some(*remote_cfg); + let transmission_mode = if self.put_request_cacher.static_fields.trans_mode.is_some() { + self.put_request_cacher.static_fields.trans_mode.unwrap() + } else { + remote_cfg.default_transmission_mode + }; + let closure_requested = if self + .put_request_cacher + .static_fields + .closure_requested + .is_some() + { + self.put_request_cacher + .static_fields + .closure_requested + .unwrap() + } else { + remote_cfg.closure_requested_by_default + }; + self.tstate = Some(TransferState::new( + TransactionId::new( + self.put_request_cacher.static_fields.destination_id, + UnsignedByteField::new( + SeqCountProvider::MAX_BIT_WIDTH / 8, + self.seq_count_provider.get_and_increment().into(), + ), + ), + *remote_cfg, + transmission_mode, + closure_requested, + )); Ok(()) } + pub fn transmission_mode(&self) {} + fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { Ok(0) } @@ -172,14 +267,16 @@ impl< #[cfg(test)] mod tests { - use alloc::sync::Arc; use spacepackets::util::UnsignedByteFieldU16; use super::*; - use crate::cfdp::{ - filestore::NativeFilestore, - tests::{basic_remote_cfg_table, TestCfdpSender, TestFaultHandler}, - FaultHandler, IndicationConfig, StdRemoteEntityConfigProvider, + use crate::{ + cfdp::{ + filestore::NativeFilestore, + tests::{basic_remote_cfg_table, TestCfdpSender, TestFaultHandler}, + FaultHandler, IndicationConfig, StdRemoteEntityConfigProvider, + }, + seq_count::SeqCountProviderSimple, }; const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); @@ -190,6 +287,7 @@ mod tests { TestFaultHandler, NativeFilestore, StdRemoteEntityConfigProvider, + SeqCountProviderSimple, >; fn default_source_handler( @@ -201,12 +299,14 @@ mod tests { indication_cfg: IndicationConfig::default(), fault_handler: FaultHandler::new(test_fault_handler), }; + let static_put_request_cacher = StaticPutRequestCacher::new(1024); SourceHandler::new( local_entity_cfg, test_packet_sender, NativeFilestore::default(), + static_put_request_cacher, basic_remote_cfg_table(), - // TestCheckTimerCreator::new(check_timer_expired), + SeqCountProviderSimple::default(), ) } diff --git a/satrs/src/seq_count.rs b/satrs/src/seq_count.rs index b4539b0..1dcd17c 100644 --- a/satrs/src/seq_count.rs +++ b/satrs/src/seq_count.rs @@ -1,6 +1,4 @@ use core::cell::Cell; -#[cfg(feature = "alloc")] -use dyn_clone::DynClone; use paste::paste; use spacepackets::MAX_SEQ_COUNT; #[cfg(feature = "std")] @@ -11,27 +9,21 @@ pub use stdmod::*; /// 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; +pub trait SequenceCountProvider { + type Raw: Into; + const MAX_BIT_WIDTH: usize; + + fn get(&self) -> Self::Raw; fn increment(&self); - fn get_and_increment(&self) -> Raw { + fn get_and_increment(&self) -> 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, @@ -63,8 +55,11 @@ macro_rules! impl_for_primitives { } } - impl SequenceCountProviderCore<$ty> for SeqCountProviderSimple<$ty> { - fn get(&self) -> $ty { + impl SequenceCountProvider for SeqCountProviderSimple<$ty> { + type Raw = $ty; + const MAX_BIT_WIDTH: usize = core::mem::size_of::() * 8; + + fn get(&self) -> Self::Raw { self.seq_count.get() } @@ -72,7 +67,7 @@ macro_rules! impl_for_primitives { self.get_and_increment(); } - fn get_and_increment(&self) -> $ty { + fn get_and_increment(&self) -> Self::Raw { let curr_count = self.seq_count.get(); if curr_count == self.max_val { @@ -104,7 +99,9 @@ impl Default for CcsdsSimpleSeqCountProvider { } } -impl SequenceCountProviderCore for CcsdsSimpleSeqCountProvider { +impl SequenceCountProvider for CcsdsSimpleSeqCountProvider { + type Raw = u16; + const MAX_BIT_WIDTH: usize = core::mem::size_of::() * 8; delegate::delegate! { to self.provider { fn get(&self) -> u16; @@ -144,7 +141,10 @@ pub mod stdmod { } } } - impl SequenceCountProviderCore<$ty> for [] { + impl SequenceCountProvider for [] { + type Raw = $ty; + const MAX_BIT_WIDTH: usize = core::mem::size_of::() * 8; + fn get(&self) -> $ty { match self.seq_count.lock() { Ok(counter) => *counter, @@ -181,7 +181,7 @@ pub mod stdmod { mod tests { use crate::seq_count::{ CcsdsSimpleSeqCountProvider, SeqCountProviderSimple, SeqCountProviderSyncU8, - SequenceCountProviderCore, + SequenceCountProvider, }; use spacepackets::MAX_SEQ_COUNT; -- 2.43.0 From ab56f4b49f77a8dac5cba9b974908b2bf8392b9c Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 28 Jun 2024 17:23:39 +0200 Subject: [PATCH 06/18] continue source handler --- satrs/src/cfdp/dest.rs | 4 +- satrs/src/cfdp/filestore.rs | 106 ++++++++++++++---------- satrs/src/cfdp/request.rs | 8 ++ satrs/src/cfdp/source.rs | 158 +++++++++++++++++++++++++++++++++--- 4 files changed, 221 insertions(+), 55 deletions(-) diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs index 39f3836..5002cc9 100644 --- a/satrs/src/cfdp/dest.rs +++ b/satrs/src/cfdp/dest.rs @@ -679,7 +679,7 @@ impl< // 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()) { + 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 @@ -696,7 +696,7 @@ impl< 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) { + if self.vfs.exists(dest_path_str)? { self.vfs.truncate_file(dest_path_str)?; } else { self.vfs.create_file(dest_path_str)?; diff --git a/satrs/src/cfdp/filestore.rs b/satrs/src/cfdp/filestore.rs index 528d1b8..ecf35e6 100644 --- a/satrs/src/cfdp/filestore.rs +++ b/satrs/src/cfdp/filestore.rs @@ -123,13 +123,15 @@ pub trait VirtualFilestore { path.file_name().and_then(|name| name.to_str()) } - fn is_file(&self, path: &str) -> bool; + fn is_file(&self, path: &str) -> Result; - fn is_dir(&self, path: &str) -> bool { - !self.is_file(path) + fn is_dir(&self, path: &str) -> Result { + Ok(!self.is_file(path)?) } - fn exists(&self, path: &str) -> bool; + fn exists(&self, path: &str) -> Result; + + fn file_size(&self, path: &str) -> Result; /// 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 @@ -160,7 +162,7 @@ pub mod std_mod { impl VirtualFilestore for NativeFilestore { fn create_file(&self, file_path: &str) -> Result<(), FilestoreError> { - if self.exists(file_path) { + if self.exists(file_path)? { return Err(FilestoreError::FileAlreadyExists); } File::create(file_path)?; @@ -168,10 +170,10 @@ pub mod std_mod { } fn remove_file(&self, file_path: &str) -> Result<(), FilestoreError> { - if !self.exists(file_path) { + if !self.exists(file_path)? { return Err(FilestoreError::FileDoesNotExist); } - if !self.is_file(file_path) { + if !self.is_file(file_path)? { return Err(FilestoreError::IsNotFile); } fs::remove_file(file_path)?; @@ -179,10 +181,10 @@ pub mod std_mod { } fn truncate_file(&self, file_path: &str) -> Result<(), FilestoreError> { - if !self.exists(file_path) { + if !self.exists(file_path)? { return Err(FilestoreError::FileDoesNotExist); } - if !self.is_file(file_path) { + if !self.is_file(file_path)? { return Err(FilestoreError::IsNotFile); } OpenOptions::new() @@ -201,10 +203,10 @@ pub mod std_mod { } fn remove_dir(&self, dir_path: &str, all: bool) -> Result<(), FilestoreError> { - if !self.exists(dir_path) { + if !self.exists(dir_path)? { return Err(FilestoreError::DirDoesNotExist); } - if !self.is_dir(dir_path) { + if !self.is_dir(dir_path)? { return Err(FilestoreError::IsNotDirectory); } if !all { @@ -229,10 +231,10 @@ pub mod std_mod { } .into()); } - if !self.exists(file_name) { + if !self.exists(file_name)? { return Err(FilestoreError::FileDoesNotExist); } - if !self.is_file(file_name) { + if !self.is_file(file_name)? { return Err(FilestoreError::IsNotFile); } let mut file = File::open(file_name)?; @@ -242,10 +244,10 @@ pub mod std_mod { } fn write_data(&self, file: &str, offset: u64, buf: &[u8]) -> Result<(), FilestoreError> { - if !self.exists(file) { + if !self.exists(file)? { return Err(FilestoreError::FileDoesNotExist); } - if !self.is_file(file) { + if !self.is_file(file)? { return Err(FilestoreError::IsNotFile); } let mut file = OpenOptions::new().write(true).open(file)?; @@ -254,17 +256,28 @@ pub mod std_mod { Ok(()) } - fn is_file(&self, path: &str) -> bool { - let path = Path::new(path); - path.is_file() + fn is_file(&self, str_path: &str) -> Result { + let path = Path::new(str_path); + if !self.exists(str_path)? { + return Err(FilestoreError::FileDoesNotExist); + } + Ok(path.is_file()) } - fn exists(&self, path: &str) -> bool { + fn exists(&self, path: &str) -> Result { let path = Path::new(path); - if !path.exists() { - return false; + Ok(self.exists_internal(path)) + } + + fn file_size(&self, str_path: &str) -> Result { + let path = Path::new(str_path); + if !self.exists_internal(path) { + return Err(FilestoreError::FileDoesNotExist); } - true + if !path.is_file() { + return Err(FilestoreError::IsNotFile); + } + Ok(path.metadata()?.len()) } fn checksum_verify( @@ -324,6 +337,13 @@ pub mod std_mod { } Ok(checksum) } + + fn exists_internal(&self, path: &Path) -> bool { + if !path.exists() { + return false; + } + true + } } } @@ -350,32 +370,34 @@ mod tests { 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())); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap()); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).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())); + assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).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())); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap()); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).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())); + assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).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())); + assert!(NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap()); + assert!(NATIVE_FS + .is_dir(dir_path.as_path().to_str().unwrap()) + .unwrap()); } #[test] @@ -385,23 +407,23 @@ mod tests { 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())); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap()); NATIVE_FS .remove_file(file_path.to_str().unwrap()) .expect("removing file failed"); - assert!(!NATIVE_FS.exists(file_path.to_str().unwrap())); + assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).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())); + assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).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())); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap()); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap()); println!("{}", file_path.to_str().unwrap()); let write_data = "hello world\n"; NATIVE_FS @@ -415,12 +437,12 @@ mod tests { 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())); + assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).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())); + assert!(NATIVE_FS.exists(file_path.to_str().unwrap()).unwrap()); + assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).unwrap()); println!("{}", file_path.to_str().unwrap()); let write_data = "hello world\n"; NATIVE_FS @@ -449,15 +471,15 @@ mod tests { 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())); + assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).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.exists(dir_path.to_str().unwrap()).unwrap()); NATIVE_FS .remove_dir(dir_path.to_str().unwrap(), false) .unwrap(); - assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap())); + assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap()); } #[test] @@ -544,7 +566,7 @@ mod tests { .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())); + assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).unwrap()); } #[test] diff --git a/satrs/src/cfdp/request.rs b/satrs/src/cfdp/request.rs index 9254f70..8794900 100644 --- a/satrs/src/cfdp/request.rs +++ b/satrs/src/cfdp/request.rs @@ -540,6 +540,14 @@ pub mod alloc_mod { Ok(()) } + pub fn has_source_file(&self) -> bool { + self.static_fields.source_file_len > 0 + } + + pub fn has_dest_file(&self) -> bool { + self.static_fields.dest_file_len > 0 + } + pub fn source_file(&self) -> Result<&str, Utf8Error> { core::str::from_utf8( &self.static_fields.source_file_buf[0..self.static_fields.source_file_len], diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index 4e1c4eb..c3730f9 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,13 +1,22 @@ +use core::str::Utf8Error; + use spacepackets::{ - cfdp::{pdu::FileDirectiveType, PduType}, - util::UnsignedByteField, + cfdp::{ + lv::Lv, + pdu::{ + metadata::{MetadataGenericParams, MetadataPduCreator}, + CommonPduConfig, FileDirectiveType, PduHeader, + }, + Direction, LargeFileFlag, PduType, + }, + util::{UnsignedByteField, UnsignedEnum}, ByteConversionError, }; use crate::seq_count::SequenceCountProvider; use super::{ - filestore::VirtualFilestore, + filestore::{FilestoreError, VirtualFilestore}, request::{ReadablePutRequest, StaticPutRequestCacher}, user::CfdpUser, LocalEntityConfig, PacketInfo, PacketTarget, PduSendProvider, RemoteEntityConfig, @@ -30,12 +39,13 @@ pub enum TransactionStep { NoticeOfCompletion = 10, } +#[derive(Default)] pub struct FileParams { pub progress: usize, pub segment_len: usize, - pub crc32: Option<[u8; 4]>, + //pub crc32: Option<[u8; 4]>, pub metadata_only: bool, - pub file_size: usize, + pub file_size: u64, pub no_eof: bool, } @@ -76,6 +86,12 @@ pub enum SourceError { PutRequestAlreadyActive, #[error("error caching put request")] PutRequestCaching(ByteConversionError), + #[error("filestore error: {0}")] + FilestoreError(#[from] FilestoreError), + #[error("source file does not have valid UTF8 format: {0}")] + SourceFileNotValidUtf8(Utf8Error), + #[error("destination file does not have valid UTF8 format: {0}")] + DestFileNotValidUtf8(Utf8Error), } #[derive(Debug, thiserror::Error)] @@ -99,7 +115,12 @@ pub struct SourceHandler< remote_cfg_table: RemoteCfgTable, vfs: Vfs, state_helper: StateHelper, + // Transfer related state information tstate: Option, + // File specific transfer fields + fparams: FileParams, + // PDU configuration is cached so it can be re-used for all PDUs generated for file transfers. + pdu_conf: CommonPduConfig, seq_count_provider: SeqCountProvider, } @@ -127,6 +148,8 @@ impl< put_request_cacher, state_helper: Default::default(), tstate: Default::default(), + fparams: Default::default(), + pdu_conf: Default::default(), seq_count_provider, } } @@ -203,10 +226,6 @@ impl< } self.put_request_cacher.set(put_request)?; self.state_helper.state = super::State::Busy; - let source_file = self.put_request_cacher.source_file().unwrap(); - if !self.vfs.exists(source_file) { - // TODO: Specific error. - } let remote_cfg = self.remote_cfg_table.get( self.put_request_cacher .static_fields @@ -218,7 +237,6 @@ impl< } let remote_cfg = remote_cfg.unwrap(); self.state_helper.num_packets_ready = 0; - //self.tstate.remote_cfg = Some(*remote_cfg); let transmission_mode = if self.put_request_cacher.static_fields.trans_mode.is_some() { self.put_request_cacher.static_fields.trans_mode.unwrap() } else { @@ -252,12 +270,130 @@ impl< Ok(()) } - pub fn transmission_mode(&self) {} + pub fn transmission_mode(&self) -> Option { + self.tstate.map(|v| v.transmission_mode) + } fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { + if self.state_helper.step == TransactionStep::Idle { + self.state_helper.step = TransactionStep::TransactionStart; + } + if self.state_helper.step == TransactionStep::TransactionStart { + self.handle_transaction_start(cfdp_user)?; + self.state_helper.step = TransactionStep::SendingMetadata; + } + if self.state_helper.step == TransactionStep::SendingMetadata { + self.prepare_and_send_metadata_pdu(); + } Ok(0) } + fn handle_transaction_start( + &mut self, + cfdp_user: &mut impl CfdpUser, + ) -> Result<(), SourceError> { + let tstate = &self.tstate.expect("transfer state unexpectedly empty"); + if !self.put_request_cacher.has_source_file() { + self.fparams.metadata_only = true; + self.fparams.no_eof = true; + } else { + let source_file = self + .put_request_cacher + .source_file() + .map_err(SourceError::SourceFileNotValidUtf8)?; + if !self.vfs.exists(source_file)? { + return Err(SourceError::FilestoreError( + FilestoreError::FileDoesNotExist, + )); + } + // We expect the destination file path to consist of valid UTF-8 characters as well. + self.put_request_cacher + .dest_file() + .map_err(SourceError::DestFileNotValidUtf8)?; + if self.vfs.file_size(source_file)? > u32::MAX as u64 { + self.pdu_conf.file_flag = LargeFileFlag::Large + } else { + self.pdu_conf.file_flag = LargeFileFlag::Normal + } + } + // Both the source entity and destination entity ID field must have the same size. + // We use the larger of either the Put Request destination ID or the local entity ID + // as the size for the new entity IDs. + let larger_entity_width = core::cmp::max( + self.local_cfg.id.size(), + self.put_request_cacher.static_fields.destination_id.size(), + ); + let create_id = |cached_id: &UnsignedByteField| { + if larger_entity_width != cached_id.size() { + UnsignedByteField::new(larger_entity_width, cached_id.value_const()) + } else { + self.local_cfg.id + } + }; + self.pdu_conf + .set_source_and_dest_id( + create_id(&self.local_cfg.id), + create_id(&self.put_request_cacher.static_fields.destination_id), + ) + .unwrap(); + // Set up other PDU configuration fields. + self.pdu_conf.direction = Direction::TowardsReceiver; + self.pdu_conf.crc_flag = tstate.remote_cfg.crc_on_transmission_by_default.into(); + self.pdu_conf.transaction_seq_num = *tstate.transaction_id.seq_num(); + self.pdu_conf.trans_mode = tstate.transmission_mode; + + cfdp_user.transaction_indication(&tstate.transaction_id); + Ok(()) + } + + fn prepare_and_send_metadata_pdu(&self) { + let tstate = &self.tstate.expect("transfer state unexpectedly empty"); + if self.fparams.metadata_only { + let metadata_params = MetadataGenericParams::new( + tstate.closure_requested, + tstate.remote_cfg.default_crc_type, + self.fparams.file_size, + ); + let metadata_pdu = MetadataPduCreator::new( + PduHeader::new_no_file_data(self.pdu_conf, 0), + metadata_params, + Lv::new_empty(), + Lv::new_empty(), + &[], + ); + //self.pdu_sender.send_pdu(pdu_type, file_directive_type, raw_pdu) + } + /* + assert self._put_req is not None + options = [] + if self._put_req.metadata_only: + params = MetadataParams( + closure_requested=self._params.closure_requested, + checksum_type=self._crc_helper.checksum_type, + file_size=0, + dest_file_name=None, + source_file_name=None, + ) + else: + # Funny name. + params = self._prepare_metadata_base_params_with_metadata() + if self._put_req.fs_requests is not None: + for fs_request in self._put_req.fs_requests: + options.append(fs_request) + if self._put_req.fault_handler_overrides is not None: + for fh_override in self._put_req.fault_handler_overrides: + options.append(fh_override) + if self._put_req.flow_label_tlv is not None: + options.append(self._put_req.flow_label_tlv) + if self._put_req.msgs_to_user is not None: + for msg_to_user in self._put_req.msgs_to_user: + options.append(msg_to_user) + self._add_packet_to_be_sent( + MetadataPdu(pdu_conf=self._params.pdu_conf, params=params, options=options) + ) + */ + } + fn handle_finished_pdu(&mut self) {} fn handle_nak_pdu(&mut self) {} -- 2.43.0 From 0aa3030bf3e487dc1877574f078ecb0ac78b70f1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 14 Jul 2024 17:33:58 -0700 Subject: [PATCH 07/18] continue with CFDP source handler --- Cargo.toml | 1 - satrs/Cargo.toml | 2 +- satrs/src/cfdp/request.rs | 191 ++++++++++++---------------------- satrs/src/cfdp/source.rs | 76 +++++++------- satrs/src/pool.rs | 58 +++++------ satrs/src/pus/verification.rs | 2 +- 6 files changed, 135 insertions(+), 195 deletions(-) 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/Cargo.toml b/satrs/Cargo.toml index 3890dbd..8e9c11e 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -31,7 +31,7 @@ default-features = false version = "0.12" default-features = false git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" -branch = "all-cfdp-updates" +branch = "main" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs/src/cfdp/request.rs b/satrs/src/cfdp/request.rs index 8794900..d23fed6 100644 --- a/satrs/src/cfdp/request.rs +++ b/satrs/src/cfdp/request.rs @@ -21,13 +21,11 @@ pub trait ReadablePutRequest { fn trans_mode(&self) -> Option; fn closure_requested(&self) -> Option; fn seg_ctrl(&self) -> Option; - fn has_msgs_to_user(&self) -> bool; - fn msgs_to_user(&self, f: impl FnMut(&Tlv)); - fn has_fault_handler_overrides(&self) -> bool; - fn fault_handler_overrides(&self, f: impl FnMut(&Tlv)); + + fn msgs_to_user(&self) -> Option>; + fn fault_handler_overrides(&self) -> Option>; fn flow_label(&self) -> Option; - fn has_fs_requests(&self) -> bool; - fn fs_requests(&self, f: impl FnMut(&Tlv)); + fn fs_requests(&self) -> Option>; } #[derive(Debug, PartialEq, Eq)] @@ -101,44 +99,29 @@ impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { self.seg_ctrl } - fn has_msgs_to_user(&self) -> bool { - self.msgs_to_user.is_some() && self.msgs_to_user.unwrap().is_empty() - } - - fn msgs_to_user(&self, mut f: impl FnMut(&Tlv)) { + fn msgs_to_user(&self) -> Option> { if let Some(msgs_to_user) = self.msgs_to_user { - for msg_to_user in msgs_to_user { - f(msg_to_user) - } + return Some(msgs_to_user.iter().copied()); } + None } - fn has_fault_handler_overrides(&self) -> bool { - self.fault_handler_overrides.is_some() && self.fault_handler_overrides.unwrap().is_empty() - } - - fn fault_handler_overrides(&self, mut f: impl FnMut(&Tlv)) { + fn fault_handler_overrides(&self) -> Option> { if let Some(fh_overrides) = self.fault_handler_overrides { - for fh_override in fh_overrides { - f(fh_override) - } + return Some(fh_overrides.iter().copied()); } + None } fn flow_label(&self) -> Option { self.flow_label } - fn has_fs_requests(&self) -> bool { - self.fs_requests.is_some() && self.fs_requests.unwrap().is_empty() - } - - fn fs_requests(&self, mut f: impl FnMut(&Tlv)) { - if let Some(fs_requests) = self.fs_requests { - for fs_request in fs_requests { - f(fs_request) - } + fn fs_requests(&self) -> Option> { + if let Some(fs_requests) = self.msgs_to_user { + return Some(fs_requests.iter().copied()); } + None } } @@ -382,45 +365,29 @@ pub mod alloc_mod { self.seg_ctrl } - fn msgs_to_user(&self, mut f: impl FnMut(&Tlv)) { + fn msgs_to_user(&self) -> Option> { if let Some(msgs_to_user) = &self.msgs_to_user { - for msg_to_user in msgs_to_user { - f(&msg_to_user.as_tlv()) - } + return Some(msgs_to_user.iter().map(|tlv_owned| tlv_owned.as_tlv())); } + None } - fn fault_handler_overrides(&self, mut f: impl FnMut(&Tlv)) { + fn fault_handler_overrides(&self) -> Option> { if let Some(fh_overrides) = &self.fault_handler_overrides { - for fh_override in fh_overrides { - f(&fh_override.as_tlv()) - } + return Some(fh_overrides.iter().map(|tlv_owned| tlv_owned.as_tlv())); } + None } fn flow_label(&self) -> Option { self.flow_label.as_ref().map(|tlv| tlv.as_tlv()) } - fn fs_requests(&self, mut f: impl FnMut(&Tlv)) { - if let Some(fs_requests) = &self.fs_requests { - for fs_request in fs_requests { - f(&fs_request.as_tlv()) - } + fn fs_requests(&self) -> Option> { + if let Some(requests) = &self.fs_requests { + return Some(requests.iter().map(|tlv_owned| tlv_owned.as_tlv())); } - } - - fn has_msgs_to_user(&self) -> bool { - self.msgs_to_user.is_some() && !self.msgs_to_user.as_ref().unwrap().is_empty() - } - - fn has_fault_handler_overrides(&self) -> bool { - self.fault_handler_overrides.is_some() - && !self.fault_handler_overrides.as_ref().unwrap().is_empty() - } - - fn has_fs_requests(&self) -> bool { - self.fs_requests.is_some() && !self.fs_requests.as_ref().unwrap().is_empty() + None } } @@ -470,18 +437,16 @@ pub mod alloc_mod { /// to store TLVs or list of TLVs. pub struct StaticPutRequestCacher { pub static_fields: StaticPutRequestFields, - /// Static buffer to store file store requests. - pub fs_requests: alloc::vec::Vec, - /// Current total length of stored filestore requests. - pub fs_requests_len: usize, + opts_buf: alloc::vec::Vec, + opts_len: usize, // fs_request_start_end_pos: Option<(usize, usize)> } impl StaticPutRequestCacher { - pub fn new(max_fs_requests_storage: usize) -> Self { + pub fn new(max_len_opts_buf: usize) -> Self { Self { static_fields: StaticPutRequestFields::default(), - fs_requests: alloc::vec![0; max_fs_requests_storage], - fs_requests_len: 0, + opts_buf: alloc::vec![0; max_len_opts_buf], + opts_len: 0, } } @@ -516,27 +481,30 @@ pub mod alloc_mod { self.static_fields.closure_requested = put_request.closure_requested(); self.static_fields.seg_ctrl = put_request.seg_ctrl(); let mut current_idx = 0; - let mut error_if_too_large = None; - let mut store_fs_requests = |tlv: &Tlv| { - if current_idx + tlv.len_full() > self.fs_requests.len() { - error_if_too_large = Some(ByteConversionError::ToSliceTooSmall { - found: self.fs_requests.len(), + let mut store_tlv = |tlv: &Tlv| { + if current_idx + tlv.len_full() > self.opts_buf.len() { + return Err(ByteConversionError::ToSliceTooSmall { + found: self.opts_buf.len(), expected: current_idx + tlv.len_full(), }); - return; } // We checked the buffer lengths, so this should never fail. - tlv.write_to_bytes( - &mut self.fs_requests[current_idx..current_idx + tlv.len_full()], - ) - .unwrap(); + tlv.write_to_bytes(&mut self.opts_buf[current_idx..current_idx + tlv.len_full()]) + .unwrap(); current_idx += tlv.len_full(); + Ok(()) }; - put_request.fs_requests(&mut store_fs_requests); - if let Some(err) = error_if_too_large { - return Err(err); + if let Some(fs_req) = put_request.fs_requests() { + for fs_req in fs_req { + store_tlv(&fs_req)?; + } } - self.fs_requests_len = current_idx; + if let Some(msgs_to_user) = put_request.msgs_to_user() { + for msg_to_user in msgs_to_user { + store_tlv(&msg_to_user)?; + } + } + self.opts_len = current_idx; Ok(()) } @@ -560,13 +528,21 @@ pub mod alloc_mod { ) } + pub fn opts_len(&self) -> usize { + self.opts_len + } + + pub fn opts_slice(&self) -> &[u8] { + &self.opts_buf[0..self.opts_len] + } + /// This clears the cacher structure. This is a cheap operation because it only /// sets [Option]al values to [None] and the length of stores TLVs to 0. /// /// Please note that this method will not set the values in the buffer to 0. pub fn clear(&mut self) { self.static_fields.clear(); - self.fs_requests_len = 0; + self.opts_len = 0; } } } @@ -592,15 +568,9 @@ mod tests { assert_eq!(put_request.seg_ctrl(), None); assert_eq!(put_request.closure_requested(), None); assert_eq!(put_request.trans_mode(), None); - assert!(!put_request.has_fs_requests()); - let dummy = |_tlv: &Tlv| { - panic!("should not be called"); - }; - put_request.fs_requests(&dummy); - assert!(!put_request.has_msgs_to_user()); - put_request.msgs_to_user(&dummy); - assert!(!put_request.has_fault_handler_overrides()); - put_request.fault_handler_overrides(&dummy); + assert!(put_request.fs_requests().is_none()); + assert!(put_request.msgs_to_user().is_none()); + assert!(put_request.fault_handler_overrides().is_none()); assert!(put_request.flow_label().is_none()); } @@ -617,15 +587,10 @@ mod tests { assert_eq!(put_request.seg_ctrl(), None); assert_eq!(put_request.closure_requested(), None); assert_eq!(put_request.trans_mode(), None); - assert!(!put_request.has_fs_requests()); - let dummy = |_tlv: &Tlv| { - panic!("should not be called"); - }; - put_request.fs_requests(&dummy); - assert!(!put_request.has_msgs_to_user()); - put_request.msgs_to_user(&dummy); - assert!(!put_request.has_fault_handler_overrides()); - put_request.fault_handler_overrides(&dummy); + assert!(put_request.flow_label().is_none()); + assert!(put_request.fs_requests().is_none()); + assert!(put_request.msgs_to_user().is_none()); + assert!(put_request.fault_handler_overrides().is_none()); assert!(put_request.flow_label().is_none()); let put_request_cloned = put_request.clone(); assert_eq!(put_request, put_request_cloned); @@ -633,28 +598,16 @@ mod tests { #[test] fn test_put_request_cacher_basic() { - let cacher_cfg = PutRequestCacheConfig { - max_msgs_to_user_storage: 512, - max_fault_handler_overrides_storage: 128, - max_flow_label_storage: 128, - max_fs_requests_storage: 512, - }; - let put_request_cached = StaticPutRequestCacher::new(cacher_cfg); + let put_request_cached = StaticPutRequestCacher::new(128); assert_eq!(put_request_cached.static_fields.source_file_len, 0); assert_eq!(put_request_cached.static_fields.dest_file_len, 0); - assert_eq!(put_request_cached.fs_requests_len, 0); - assert_eq!(put_request_cached.fs_requests.len(), 512); + assert_eq!(put_request_cached.opts_len(), 0); + assert_eq!(put_request_cached.opts_slice(), &[]); } #[test] fn test_put_request_cacher_set() { - let cacher_cfg = PutRequestCacheConfig { - max_msgs_to_user_storage: 512, - max_fault_handler_overrides_storage: 128, - max_flow_label_storage: 128, - max_fs_requests_storage: 512, - }; - let mut put_request_cached = StaticPutRequestCacher::new(cacher_cfg); + let mut put_request_cached = StaticPutRequestCacher::new(128); let src_file = "/tmp/hello.txt"; let dest_file = "/tmp/hello2.txt"; let put_request = @@ -671,18 +624,12 @@ mod tests { ); assert_eq!(put_request_cached.source_file().unwrap(), src_file); assert_eq!(put_request_cached.dest_file().unwrap(), dest_file); - assert_eq!(put_request_cached.fs_requests_len, 0); + assert_eq!(put_request_cached.opts_len(), 0); } #[test] fn test_put_request_cacher_set_and_clear() { - let cacher_cfg = PutRequestCacheConfig { - max_msgs_to_user_storage: 512, - max_fault_handler_overrides_storage: 128, - max_flow_label_storage: 128, - max_fs_requests_storage: 512, - }; - let mut put_request_cached = StaticPutRequestCacher::new(cacher_cfg); + let mut put_request_cached = StaticPutRequestCacher::new(128); let src_file = "/tmp/hello.txt"; let dest_file = "/tmp/hello2.txt"; let put_request = @@ -692,6 +639,6 @@ mod tests { put_request_cached.clear(); assert_eq!(put_request_cached.static_fields.source_file_len, 0); assert_eq!(put_request_cached.static_fields.dest_file_len, 0); - assert_eq!(put_request_cached.fs_requests_len, 0); + assert_eq!(put_request_cached.opts_len(), 0); } } diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index c3730f9..cfee613 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,11 +1,11 @@ -use core::str::Utf8Error; +use core::{cell::RefCell, str::Utf8Error}; use spacepackets::{ cfdp::{ lv::Lv, pdu::{ metadata::{MetadataGenericParams, MetadataPduCreator}, - CommonPduConfig, FileDirectiveType, PduHeader, + CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }, Direction, LargeFileFlag, PduType, }, @@ -92,6 +92,8 @@ pub enum SourceError { SourceFileNotValidUtf8(Utf8Error), #[error("destination file does not have valid UTF8 format: {0}")] DestFileNotValidUtf8(Utf8Error), + #[error("error related to PDU creation")] + Pdu(#[from] PduError), } #[derive(Debug, thiserror::Error)] @@ -111,6 +113,7 @@ pub struct SourceHandler< > { local_cfg: LocalEntityConfig, pdu_sender: PduSender, + pdu_buffer: RefCell>, put_request_cacher: StaticPutRequestCacher, remote_cfg_table: RemoteCfgTable, vfs: Vfs, @@ -137,6 +140,7 @@ impl< pdu_sender: PduSender, vfs: Vfs, put_request_cacher: StaticPutRequestCacher, + max_pdu_len: usize, remote_cfg_table: RemoteCfgTable, seq_count_provider: SeqCountProvider, ) -> Self { @@ -144,6 +148,7 @@ impl< local_cfg: cfg, remote_cfg_table, pdu_sender, + pdu_buffer: RefCell::new(alloc::vec![0; max_pdu_len]), vfs, put_request_cacher, state_helper: Default::default(), @@ -283,7 +288,7 @@ impl< self.state_helper.step = TransactionStep::SendingMetadata; } if self.state_helper.step == TransactionStep::SendingMetadata { - self.prepare_and_send_metadata_pdu(); + self.prepare_and_send_metadata_pdu()?; } Ok(0) } @@ -346,52 +351,42 @@ impl< Ok(()) } - fn prepare_and_send_metadata_pdu(&self) { + fn prepare_and_send_metadata_pdu(&self) -> Result<(), PduError> { let tstate = &self.tstate.expect("transfer state unexpectedly empty"); + let metadata_params = MetadataGenericParams::new( + tstate.closure_requested, + tstate.remote_cfg.default_crc_type, + self.fparams.file_size, + ); if self.fparams.metadata_only { - let metadata_params = MetadataGenericParams::new( - tstate.closure_requested, - tstate.remote_cfg.default_crc_type, - self.fparams.file_size, - ); let metadata_pdu = MetadataPduCreator::new( PduHeader::new_no_file_data(self.pdu_conf, 0), metadata_params, Lv::new_empty(), Lv::new_empty(), - &[], + self.put_request_cacher.opts_slice(), ); - //self.pdu_sender.send_pdu(pdu_type, file_directive_type, raw_pdu) + return self.pdu_send_helper(metadata_pdu); } - /* - assert self._put_req is not None - options = [] - if self._put_req.metadata_only: - params = MetadataParams( - closure_requested=self._params.closure_requested, - checksum_type=self._crc_helper.checksum_type, - file_size=0, - dest_file_name=None, - source_file_name=None, - ) - else: - # Funny name. - params = self._prepare_metadata_base_params_with_metadata() - if self._put_req.fs_requests is not None: - for fs_request in self._put_req.fs_requests: - options.append(fs_request) - if self._put_req.fault_handler_overrides is not None: - for fh_override in self._put_req.fault_handler_overrides: - options.append(fh_override) - if self._put_req.flow_label_tlv is not None: - options.append(self._put_req.flow_label_tlv) - if self._put_req.msgs_to_user is not None: - for msg_to_user in self._put_req.msgs_to_user: - options.append(msg_to_user) - self._add_packet_to_be_sent( - MetadataPdu(pdu_conf=self._params.pdu_conf, params=params, options=options) - ) - */ + let metadata_pdu = MetadataPduCreator::new( + PduHeader::new_no_file_data(self.pdu_conf, 0), + metadata_params, + Lv::new_from_str(self.put_request_cacher.source_file().unwrap()).unwrap(), + Lv::new_from_str(self.put_request_cacher.dest_file().unwrap()).unwrap(), + self.put_request_cacher.opts_slice(), + ); + self.pdu_send_helper(metadata_pdu) + } + + fn pdu_send_helper(&self, pdu: impl WritablePduPacket + CfdpPdu) -> Result<(), PduError> { + let mut pdu_buffer_mut = self.pdu_buffer.borrow_mut(); + let written_len = pdu.write_to_bytes(&mut pdu_buffer_mut)?; + self.pdu_sender.send_pdu( + PduType::FileDirective, + Some(FileDirectiveType::MetadataPdu), + &pdu_buffer_mut[0..written_len], + )?; + Ok(()) } fn handle_finished_pdu(&mut self) {} @@ -441,6 +436,7 @@ mod tests { test_packet_sender, NativeFilestore::default(), static_put_request_cacher, + 1024, basic_remote_cfg_table(), SeqCountProviderSimple::default(), ) 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..47b57f4 100644 --- a/satrs/src/pus/verification.rs +++ b/satrs/src/pus/verification.rs @@ -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; -- 2.43.0 From b77252d16b87d64c95010a025bc14294d71cd044 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 19 Jul 2024 11:35:33 -0700 Subject: [PATCH 08/18] impl file data send handling --- satrs/Cargo.toml | 2 +- satrs/src/cfdp/source.rs | 128 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 8 deletions(-) diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index 8e9c11e..01f9e41 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -31,7 +31,7 @@ default-features = false version = "0.12" default-features = false git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" -branch = "main" +branch = "file-data-pdu-update" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index cfee613..71e318e 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,13 +1,14 @@ -use core::{cell::RefCell, str::Utf8Error}; +use core::{cell::RefCell, ops::ControlFlow, str::Utf8Error}; use spacepackets::{ cfdp::{ lv::Lv, pdu::{ + file_data::FileDataPduCreatorWithReservedDatafield, metadata::{MetadataGenericParams, MetadataPduCreator}, CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }, - Direction, LargeFileFlag, PduType, + Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, }, util::{UnsignedByteField, UnsignedEnum}, ByteConversionError, @@ -41,12 +42,12 @@ pub enum TransactionStep { #[derive(Default)] pub struct FileParams { - pub progress: usize, - pub segment_len: usize, + pub progress: u64, + pub segment_len: u64, //pub crc32: Option<[u8; 4]>, pub metadata_only: bool, pub file_size: u64, - pub no_eof: bool, + pub empty_file: bool, } pub struct StateHelper { @@ -275,6 +276,7 @@ impl< Ok(()) } + #[inline] pub fn transmission_mode(&self) -> Option { self.tstate.map(|v| v.transmission_mode) } @@ -289,6 +291,13 @@ impl< } if self.state_helper.step == TransactionStep::SendingMetadata { self.prepare_and_send_metadata_pdu()?; + self.state_helper.step = TransactionStep::SendingFileData; + return Ok(1); + } + if self.state_helper.step == TransactionStep::SendingFileData { + if let ControlFlow::Break(result) = self.file_data_fsm()? { + return Ok(result); + } } Ok(0) } @@ -300,7 +309,7 @@ impl< let tstate = &self.tstate.expect("transfer state unexpectedly empty"); if !self.put_request_cacher.has_source_file() { self.fparams.metadata_only = true; - self.fparams.no_eof = true; + self.fparams.empty_file = true; } else { let source_file = self .put_request_cacher @@ -351,7 +360,7 @@ impl< Ok(()) } - fn prepare_and_send_metadata_pdu(&self) -> Result<(), PduError> { + fn prepare_and_send_metadata_pdu(&mut self) -> Result<(), PduError> { let tstate = &self.tstate.expect("transfer state unexpectedly empty"); let metadata_params = MetadataGenericParams::new( tstate.closure_requested, @@ -378,6 +387,111 @@ impl< self.pdu_send_helper(metadata_pdu) } + fn file_data_fsm(&mut self) -> Result, SourceError> { + if self.transmission_mode().unwrap() == super::TransmissionMode::Acknowledged { + // TODO: Handle re-transmission + } + if !self.fparams.metadata_only + && self.fparams.progress < self.fparams.file_size + && self.send_progressing_file_data_pdu()? + { + return Ok(ControlFlow::Break(1)); + } + if self.fparams.empty_file { + // EOF is still expected. + self.state_helper.step = TransactionStep::SendingEof; + } else if self.fparams.metadata_only { + // Special case: Metadata Only, no EOF required. + if self.tstate.as_ref().unwrap().closure_requested { + self.state_helper.step = TransactionStep::WaitingForFinished; + } else { + self.state_helper.step = TransactionStep::NoticeOfCompletion; + } + } + Ok(ControlFlow::Continue(())) + } + + fn send_progressing_file_data_pdu(&mut self) -> Result { + // Should never be called, but use defensive programming here. + if self.fparams.progress >= self.fparams.file_size { + return Ok(false); + } + let read_len = if self.fparams.file_size < self.fparams.segment_len { + self.fparams.file_size + } else if self.fparams.progress + self.fparams.segment_len > self.fparams.file_size { + self.fparams.file_size - self.fparams.progress + } else { + self.fparams.segment_len + }; + // TODO: Send File Data PDU. + let pdu_creator = FileDataPduCreatorWithReservedDatafield::new_no_seg_metadata( + PduHeader::new_for_file_data( + self.pdu_conf, + 0, + SegmentMetadataFlag::NotPresent, + SegmentationControl::NoRecordBoundaryPreservation, + ), + self.fparams.progress, + read_len, + ); + let mut unwritten_pdu = pdu_creator.write_to_bytes_partially(self.pdu_buffer.get_mut())?; + self.vfs.read_data( + self.put_request_cacher.source_file().unwrap(), + self.fparams.progress, + read_len, + unwritten_pdu.file_data_field_mut(), + )?; + let written_len = unwritten_pdu.finish(); + self.pdu_sender.send_pdu( + PduType::FileData, + None, + &self.pdu_buffer.borrow()[0..written_len], + )?; + self.fparams.progress += read_len; + /* + """Generic function to prepare a file data PDU. This function can also be used to + re-transmit file data PDUs of segments which were already sent.""" + assert self._put_req is not None + assert self._put_req.source_file is not None + with open(self._put_req.source_file, "rb") as of: + file_data = self.user.vfs.read_from_opened_file(of, offset, read_len) + # TODO: Support for record continuation state not implemented yet. Segment metadata + # flag is therefore always set to False. Segment metadata support also omitted + # for now. Implementing those generically could be done in form of a callback, + # e.g. abstractmethod of this handler as a first way, another one being + # to expect the user to supply some helper class to split up a file + fd_params = FileDataParams( + file_data=file_data, offset=offset, segment_metadata=None + ) + file_data_pdu = FileDataPdu( + pdu_conf=self._params.pdu_conf, params=fd_params + ) + self._add_packet_to_be_sent(file_data_pdu) + */ + /* + """Prepare the next file data PDU, which also progresses the file copy operation. + + :return: True if a packet was prepared, False if PDU handling is done and the next steps + in the Copy File procedure can be performed + """ + # This function should only be called if file segments still need to be sent. + assert self._params.fp.progress < self._params.fp.file_size + if self._params.fp.file_size < self._params.fp.segment_len: + read_len = self._params.fp.file_size + else: + if ( + self._params.fp.progress + self._params.fp.segment_len + > self._params.fp.file_size + ): + read_len = self._params.fp.file_size - self._params.fp.progress + else: + read_len = self._params.fp.segment_len + self._prepare_file_data_pdu(self._params.fp.progress, read_len) + self._params.fp.progress += read_len + */ + Ok(true) + } + fn pdu_send_helper(&self, pdu: impl WritablePduPacket + CfdpPdu) -> Result<(), PduError> { let mut pdu_buffer_mut = self.pdu_buffer.borrow_mut(); let written_len = pdu.write_to_bytes(&mut pdu_buffer_mut)?; -- 2.43.0 From a451868a1c0358facb3715bdaa38801b2bb3e2c6 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 21 Jul 2024 10:33:56 -0700 Subject: [PATCH 09/18] Add EOF PDU handling for source handler --- satrs/Cargo.toml | 2 +- satrs/src/cfdp/source.rs | 67 +++++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index 01f9e41..a322896 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -31,7 +31,7 @@ default-features = false version = "0.12" default-features = false git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" -branch = "file-data-pdu-update" +branch = "eof-pdu-update" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index 71e318e..7f4ddbb 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -4,11 +4,13 @@ use spacepackets::{ cfdp::{ lv::Lv, pdu::{ + eof::EofPdu, file_data::FileDataPduCreatorWithReservedDatafield, metadata::{MetadataGenericParams, MetadataPduCreator}, CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }, - Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, + ConditionCode, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, + NULL_CHECKSUM_U32, }, util::{UnsignedByteField, UnsignedEnum}, ByteConversionError, @@ -44,12 +46,23 @@ pub enum TransactionStep { pub struct FileParams { pub progress: u64, pub segment_len: u64, - //pub crc32: Option<[u8; 4]>, + pub crc32: [u8; 4], pub metadata_only: bool, pub file_size: u64, pub empty_file: bool, } +impl FileParams { + pub fn reset(&mut self) { + self.progress = 0; + self.segment_len = 0; + self.crc32 = NULL_CHECKSUM_U32; + self.metadata_only = false; + self.file_size = 0; + self.empty_file = false; + } +} + pub struct StateHelper { state: super::State, step: TransactionStep, @@ -62,6 +75,7 @@ pub struct TransferState { remote_cfg: RemoteEntityConfig, transmission_mode: super::TransmissionMode, closure_requested: bool, + cond_code_eof: Option, } impl Default for StateHelper { @@ -272,6 +286,7 @@ impl< *remote_cfg, transmission_mode, closure_requested, + None, )); Ok(()) } @@ -299,6 +314,10 @@ impl< return Ok(result); } } + if self.state_helper.step == TransactionStep::SendingEof { + // TODO: Checksum calculation using VFS. + self.prepare_and_send_eof_pdu(0)?; + } Ok(0) } @@ -306,6 +325,7 @@ impl< &mut self, cfdp_user: &mut impl CfdpUser, ) -> Result<(), SourceError> { + self.fparams.reset(); let tstate = &self.tstate.expect("transfer state unexpectedly empty"); if !self.put_request_cacher.has_source_file() { self.fparams.metadata_only = true; @@ -361,7 +381,10 @@ impl< } fn prepare_and_send_metadata_pdu(&mut self) -> Result<(), PduError> { - let tstate = &self.tstate.expect("transfer state unexpectedly empty"); + let tstate = self + .tstate + .as_ref() + .expect("transfer state unexpectedly empty"); let metadata_params = MetadataGenericParams::new( tstate.closure_requested, tstate.remote_cfg.default_crc_type, @@ -375,7 +398,7 @@ impl< Lv::new_empty(), self.put_request_cacher.opts_slice(), ); - return self.pdu_send_helper(metadata_pdu); + return self.pdu_send_helper(&metadata_pdu); } let metadata_pdu = MetadataPduCreator::new( PduHeader::new_no_file_data(self.pdu_conf, 0), @@ -384,7 +407,7 @@ impl< Lv::new_from_str(self.put_request_cacher.dest_file().unwrap()).unwrap(), self.put_request_cacher.opts_slice(), ); - self.pdu_send_helper(metadata_pdu) + self.pdu_send_helper(&metadata_pdu) } fn file_data_fsm(&mut self) -> Result, SourceError> { @@ -397,9 +420,10 @@ impl< { return Ok(ControlFlow::Break(1)); } - if self.fparams.empty_file { + if self.fparams.empty_file || self.fparams.progress >= self.fparams.file_size { // EOF is still expected. self.state_helper.step = TransactionStep::SendingEof; + self.tstate.as_mut().unwrap().cond_code_eof = Some(ConditionCode::NoError); } else if self.fparams.metadata_only { // Special case: Metadata Only, no EOF required. if self.tstate.as_ref().unwrap().closure_requested { @@ -492,7 +516,36 @@ impl< Ok(true) } - fn pdu_send_helper(&self, pdu: impl WritablePduPacket + CfdpPdu) -> Result<(), PduError> { + fn prepare_and_send_eof_pdu(&mut self, checksum: u32) -> Result<(), PduError> { + let tstate = self + .tstate + .as_ref() + .expect("transfer state unexpectedly empty"); + //let checksum_u32 = u32::from_be_bytes(self.fparams.crc32); + let eof_pdu = EofPdu::new( + PduHeader::new_no_file_data(self.pdu_conf, 0), + tstate.cond_code_eof.unwrap_or(ConditionCode::NoError), + checksum, + self.fparams.file_size, + None, + ); + self.pdu_send_helper(&eof_pdu)?; + /* + * + assert self._params.cond_code_eof is not None + self._add_packet_to_be_sent( + EofPdu( + file_checksum=checksum, + file_size=self._params.fp.progress, + pdu_conf=self._params.pdu_conf, + condition_code=self._params.cond_code_eof, + ) + ) + */ + Ok(()) + } + + fn pdu_send_helper(&self, pdu: &(impl WritablePduPacket + CfdpPdu)) -> Result<(), PduError> { let mut pdu_buffer_mut = self.pdu_buffer.borrow_mut(); let written_len = pdu.write_to_bytes(&mut pdu_buffer_mut)?; self.pdu_sender.send_pdu( -- 2.43.0 From 97285baaf4e8eb037fecc5ab0789d4c73757be2e Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sun, 21 Jul 2024 22:20:10 -0700 Subject: [PATCH 10/18] add function to calc checksum --- satrs/src/cfdp/filestore.rs | 41 +++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/satrs/src/cfdp/filestore.rs b/satrs/src/cfdp/filestore.rs index ecf35e6..d865b5e 100644 --- a/satrs/src/cfdp/filestore.rs +++ b/satrs/src/cfdp/filestore.rs @@ -133,6 +133,20 @@ pub trait VirtualFilestore { fn file_size(&self, path: &str) -> Result; + /// This special function is the CFDP specific abstraction to calculate 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 calculate_checksum( + &self, + file_path: &str, + checksum_type: ChecksumType, + verification_buf: &mut [u8], + ) -> Result; + /// 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. @@ -146,11 +160,17 @@ pub trait VirtualFilestore { checksum_type: ChecksumType, expected_checksum: u32, verification_buf: &mut [u8], - ) -> Result; + ) -> Result { + Ok( + self.calculate_checksum(file_path, checksum_type, verification_buf)? + == expected_checksum, + ) + } } #[cfg(feature = "std")] pub mod std_mod { + use super::*; use std::{ fs::{self, File, OpenOptions}, @@ -280,20 +300,14 @@ pub mod std_mod { Ok(path.metadata()?.len()) } - fn checksum_verify( + fn calculate_checksum( &self, file_path: &str, checksum_type: ChecksumType, - expected_checksum: u32, verification_buf: &mut [u8], - ) -> Result { + ) -> Result { match checksum_type { - ChecksumType::Modular => { - if self.calc_modular_checksum(file_path)? == expected_checksum { - return Ok(true); - } - Ok(false) - } + ChecksumType::Modular => self.calc_modular_checksum(file_path), ChecksumType::Crc32 => { let mut digest = CRC_32.digest(); let file_to_check = File::open(file_path)?; @@ -305,12 +319,9 @@ pub mod std_mod { } digest.update(&verification_buf[0..bytes_read]); } - if digest.finalize() == expected_checksum { - return Ok(true); - } - Ok(false) + Ok(digest.finalize()) } - ChecksumType::NullChecksum => Ok(true), + ChecksumType::NullChecksum => Ok(0), _ => Err(FilestoreError::ChecksumTypeNotImplemented(checksum_type)), } } -- 2.43.0 From 643ce9ace114cf72243f0b2f3d89cc581026cf67 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 22 Jul 2024 23:57:56 -0700 Subject: [PATCH 11/18] continue source handler --- satrs/src/cfdp/source.rs | 247 +++++++++++++++++++++++++++++++-------- 1 file changed, 200 insertions(+), 47 deletions(-) diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index 7f4ddbb..7a77704 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -6,11 +6,12 @@ use spacepackets::{ pdu::{ eof::EofPdu, file_data::FileDataPduCreatorWithReservedDatafield, + finished::{DeliveryCode, FileStatus, FinishedPduReader}, metadata::{MetadataGenericParams, MetadataPduCreator}, CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }, ConditionCode, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, - NULL_CHECKSUM_U32, + TransmissionMode, }, util::{UnsignedByteField, UnsignedEnum}, ByteConversionError, @@ -21,9 +22,9 @@ use crate::seq_count::SequenceCountProvider; use super::{ filestore::{FilestoreError, VirtualFilestore}, request::{ReadablePutRequest, StaticPutRequestCacher}, - user::CfdpUser, + user::{CfdpUser, TransactionFinishedParams}, LocalEntityConfig, PacketInfo, PacketTarget, PduSendProvider, RemoteEntityConfig, - RemoteEntityConfigProvider, TransactionId, UserFaultHookProvider, + RemoteEntityConfigProvider, State, TransactionId, UserFaultHookProvider, }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -38,7 +39,7 @@ pub enum TransactionStep { SendingEof = 6, WaitingForEofAck = 7, WaitingForFinished = 8, - SendingAckOfFinished = 9, + // SendingAckOfFinished = 9, NoticeOfCompletion = 10, } @@ -46,36 +47,33 @@ pub enum TransactionStep { pub struct FileParams { pub progress: u64, pub segment_len: u64, - pub crc32: [u8; 4], + pub crc32: u32, pub metadata_only: bool, pub file_size: u64, pub empty_file: bool, } -impl FileParams { - pub fn reset(&mut self) { - self.progress = 0; - self.segment_len = 0; - self.crc32 = NULL_CHECKSUM_U32; - self.metadata_only = false; - self.file_size = 0; - self.empty_file = false; - } -} - pub struct StateHelper { state: super::State, step: TransactionStep, num_packets_ready: u32, } -#[derive(Debug, Copy, Clone, derive_new::new)] +#[derive(Debug)] +pub struct FinishedParams { + condition_code: ConditionCode, + delivery_code: DeliveryCode, + file_status: FileStatus, +} + +#[derive(Debug, derive_new::new)] pub struct TransferState { transaction_id: TransactionId, remote_cfg: RemoteEntityConfig, transmission_mode: super::TransmissionMode, closure_requested: bool, cond_code_eof: Option, + finished_params: Option, } impl Default for StateHelper { @@ -95,8 +93,11 @@ pub enum SourceError { pdu_type: PduType, directive_type: Option, }, - #[error("unexpected file data PDU")] - UnexpectedFileDataPdu, + #[error("unexpected PDU")] + UnexpectedPdu { + pdu_type: PduType, + directive_type: Option, + }, #[error("source handler is already busy with put request")] PutRequestAlreadyActive, #[error("error caching put request")] @@ -128,7 +129,7 @@ pub struct SourceHandler< > { local_cfg: LocalEntityConfig, pdu_sender: PduSender, - pdu_buffer: RefCell>, + pdu_and_cksum_buffer: RefCell>, put_request_cacher: StaticPutRequestCacher, remote_cfg_table: RemoteCfgTable, vfs: Vfs, @@ -155,7 +156,7 @@ impl< pdu_sender: PduSender, vfs: Vfs, put_request_cacher: StaticPutRequestCacher, - max_pdu_len: usize, + pdu_and_cksum_buf_size: usize, remote_cfg_table: RemoteCfgTable, seq_count_provider: SeqCountProvider, ) -> Self { @@ -163,7 +164,7 @@ impl< local_cfg: cfg, remote_cfg_table, pdu_sender, - pdu_buffer: RefCell::new(alloc::vec![0; max_pdu_len]), + pdu_and_cksum_buffer: RefCell::new(alloc::vec![0; pdu_and_cksum_buf_size]), vfs, put_request_cacher, state_helper: Default::default(), @@ -213,7 +214,10 @@ impl< if packet_info.pdu_type() == PduType::FileData { // The [PacketInfo] API should ensure that file data PDUs can not be passed // into a source entity, so this should never happen. - return Err(SourceError::UnexpectedFileDataPdu); + return Err(SourceError::UnexpectedPdu { + pdu_type: PduType::FileData, + directive_type: None, + }); } // Unwrap is okay here, the [PacketInfo] API should ensure that the directive type is // always a valid value. @@ -221,7 +225,7 @@ impl< .pdu_directive() .expect("PDU directive type unexpectedly not set") { - FileDirectiveType::FinishedPdu => self.handle_finished_pdu(), + FileDirectiveType::FinishedPdu => self.handle_finished_pdu(packet_info)?, FileDirectiveType::NakPdu => self.handle_nak_pdu(), FileDirectiveType::KeepAlivePdu => self.handle_keep_alive_pdu(), FileDirectiveType::AckPdu => todo!("acknowledged mode not implemented yet"), @@ -287,13 +291,14 @@ impl< transmission_mode, closure_requested, None, + None, )); Ok(()) } #[inline] pub fn transmission_mode(&self) -> Option { - self.tstate.map(|v| v.transmission_mode) + self.tstate.as_ref().map(|v| v.transmission_mode) } fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { @@ -315,18 +320,114 @@ impl< } } if self.state_helper.step == TransactionStep::SendingEof { - // TODO: Checksum calculation using VFS. - self.prepare_and_send_eof_pdu(0)?; + self.eof_fsm(cfdp_user)?; + return Ok(1); + } + if self.state_helper.step == TransactionStep::WaitingForFinished { + /* + def _handle_wait_for_finish(self): + if ( + self.transmission_mode == TransmissionMode.ACKNOWLEDGED + and self.__handle_retransmission() + ): + return + if ( + self._inserted_pdu.pdu is None + or self._inserted_pdu.pdu_directive_type is None + or self._inserted_pdu.pdu_directive_type != DirectiveType.FINISHED_PDU + ): + if self._params.check_timer is not None: + if self._params.check_timer.timed_out(): + self._declare_fault(ConditionCode.CHECK_LIMIT_REACHED) + return + finished_pdu = self._inserted_pdu.to_finished_pdu() + self._inserted_pdu.pdu = None + self._params.finished_params = finished_pdu.finished_params + if self.transmission_mode == TransmissionMode.ACKNOWLEDGED: + self._prepare_finished_ack_packet(finished_pdu.condition_code) + self.states.step = TransactionStep.SENDING_ACK_OF_FINISHED + else: + self.states.step = TransactionStep.NOTICE_OF_COMPLETION + */ + } + if self.state_helper.step == TransactionStep::NoticeOfCompletion { + self.notice_of_completion(cfdp_user); + /* + def _notice_of_completion(self): + if self.cfg.indication_cfg.transaction_finished_indication_required: + assert self._params.transaction_id is not None + # This happens for unacknowledged file copy operation with no closure. + if self._params.finished_params is None: + self._params.finished_params = FinishedParams( + condition_code=ConditionCode.NO_ERROR, + delivery_code=DeliveryCode.DATA_COMPLETE, + file_status=FileStatus.FILE_STATUS_UNREPORTED, + ) + indication_params = TransactionFinishedParams( + transaction_id=self._params.transaction_id, + finished_params=self._params.finished_params, + ) + self.user.transaction_finished_indication(indication_params) + # Transaction finished + self.reset() + */ } Ok(0) } + fn eof_fsm(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), SourceError> { + let tstate = self.tstate.as_ref().unwrap(); + let checksum = self.vfs.calculate_checksum( + self.put_request_cacher.source_file().unwrap(), + tstate.remote_cfg.default_crc_type, + self.pdu_and_cksum_buffer.get_mut(), + )?; + self.prepare_and_send_eof_pdu(checksum)?; + let tstate = self.tstate.as_ref().unwrap(); + if self.local_cfg.indication_cfg.eof_sent { + cfdp_user.eof_sent_indication(&tstate.transaction_id); + } + if tstate.transmission_mode == TransmissionMode::Unacknowledged { + if tstate.closure_requested { + // TODO: Check timer handling. + self.state_helper.step = TransactionStep::WaitingForFinished; + } else { + self.state_helper.step = TransactionStep::NoticeOfCompletion; + } + } else { + // TODO: Start positive ACK procedure. + } + /* + if self.cfg.indication_cfg.eof_sent_indication_required: + assert self._params.transaction_id is not None + self.user.eof_sent_indication(self._params.transaction_id) + if self.transmission_mode == TransmissionMode.UNACKNOWLEDGED: + if self._params.closure_requested: + assert self._params.remote_cfg is not None + self._params.check_timer = ( + self.check_timer_provider.provide_check_timer( + local_entity_id=self.cfg.local_entity_id, + remote_entity_id=self._params.remote_cfg.entity_id, + entity_type=EntityType.SENDING, + ) + ) + self.states.step = TransactionStep.WAITING_FOR_FINISHED + else: + self.states.step = TransactionStep.NOTICE_OF_COMPLETION + else: + self._start_positive_ack_procedure() + */ + Ok(()) + } + fn handle_transaction_start( &mut self, cfdp_user: &mut impl CfdpUser, ) -> Result<(), SourceError> { - self.fparams.reset(); - let tstate = &self.tstate.expect("transfer state unexpectedly empty"); + let tstate = self + .tstate + .as_ref() + .expect("transfer state unexpectedly empty"); if !self.put_request_cacher.has_source_file() { self.fparams.metadata_only = true; self.fparams.empty_file = true; @@ -435,6 +536,30 @@ impl< Ok(ControlFlow::Continue(())) } + fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) { + let tstate = self.tstate.as_ref().unwrap(); + if self.local_cfg.indication_cfg.transaction_finished { + // The first case happens for unacknowledged file copy operation with no closure. + let finished_params = if tstate.finished_params.is_none() { + TransactionFinishedParams { + id: tstate.transaction_id, + condition_code: ConditionCode::NoError, + delivery_code: DeliveryCode::Complete, + file_status: FileStatus::Unreported, + } + } else { + let finished_params = tstate.finished_params.as_ref().unwrap(); + TransactionFinishedParams { + id: tstate.transaction_id, + condition_code: finished_params.condition_code, + delivery_code: finished_params.delivery_code, + file_status: finished_params.file_status, + } + }; + cfdp_user.transaction_finished_indication(&finished_params); + } + } + fn send_progressing_file_data_pdu(&mut self) -> Result { // Should never be called, but use defensive programming here. if self.fparams.progress >= self.fparams.file_size { @@ -447,7 +572,6 @@ impl< } else { self.fparams.segment_len }; - // TODO: Send File Data PDU. let pdu_creator = FileDataPduCreatorWithReservedDatafield::new_no_seg_metadata( PduHeader::new_for_file_data( self.pdu_conf, @@ -458,7 +582,8 @@ impl< self.fparams.progress, read_len, ); - let mut unwritten_pdu = pdu_creator.write_to_bytes_partially(self.pdu_buffer.get_mut())?; + let mut unwritten_pdu = + pdu_creator.write_to_bytes_partially(self.pdu_and_cksum_buffer.get_mut())?; self.vfs.read_data( self.put_request_cacher.source_file().unwrap(), self.fparams.progress, @@ -469,7 +594,7 @@ impl< self.pdu_sender.send_pdu( PduType::FileData, None, - &self.pdu_buffer.borrow()[0..written_len], + &self.pdu_and_cksum_buffer.borrow()[0..written_len], )?; self.fparams.progress += read_len; /* @@ -521,7 +646,6 @@ impl< .tstate .as_ref() .expect("transfer state unexpectedly empty"); - //let checksum_u32 = u32::from_be_bytes(self.fparams.crc32); let eof_pdu = EofPdu::new( PduHeader::new_no_file_data(self.pdu_conf, 0), tstate.cond_code_eof.unwrap_or(ConditionCode::NoError), @@ -530,23 +654,11 @@ impl< None, ); self.pdu_send_helper(&eof_pdu)?; - /* - * - assert self._params.cond_code_eof is not None - self._add_packet_to_be_sent( - EofPdu( - file_checksum=checksum, - file_size=self._params.fp.progress, - pdu_conf=self._params.pdu_conf, - condition_code=self._params.cond_code_eof, - ) - ) - */ Ok(()) } fn pdu_send_helper(&self, pdu: &(impl WritablePduPacket + CfdpPdu)) -> Result<(), PduError> { - let mut pdu_buffer_mut = self.pdu_buffer.borrow_mut(); + let mut pdu_buffer_mut = self.pdu_and_cksum_buffer.borrow_mut(); let written_len = pdu.write_to_bytes(&mut pdu_buffer_mut)?; self.pdu_sender.send_pdu( PduType::FileDirective, @@ -556,11 +668,52 @@ impl< Ok(()) } - fn handle_finished_pdu(&mut self) {} + fn handle_finished_pdu(&mut self, packet_info: &PacketInfo) -> Result<(), SourceError> { + // Ignore this packet when we are idle. + if self.state_helper.state == State::Idle { + return Ok(()); + } + if self.state_helper.step != TransactionStep::WaitingForFinished { + return Err(SourceError::UnexpectedPdu { + pdu_type: PduType::FileDirective, + directive_type: Some(FileDirectiveType::FinishedPdu), + }); + } + let finished_pdu = FinishedPduReader::new(packet_info.raw_packet())?; + // Unwrapping should be fine here, the transfer state is valid when we are not in IDLE + // mode. + self.tstate.as_mut().unwrap().finished_params = Some(FinishedParams { + condition_code: finished_pdu.condition_code(), + delivery_code: finished_pdu.delivery_code(), + file_status: finished_pdu.file_status(), + }); + if self.tstate.as_ref().unwrap().transmission_mode == TransmissionMode::Acknowledged { + // TODO: Send ACK packet here immediately and continue. + //self.state_helper.step = TransactionStep::SendingAckOfFinished; + } + self.state_helper.step = TransactionStep::NoticeOfCompletion; + + /* + if self.transmission_mode == TransmissionMode.ACKNOWLEDGED: + self._prepare_finished_ack_packet(finished_pdu.condition_code) + self.states.step = TransactionStep.SENDING_ACK_OF_FINISHED + else: + self.states.step = TransactionStep.NOTICE_OF_COMPLETION + */ + Ok(()) + } fn handle_nak_pdu(&mut self) {} fn handle_keep_alive_pdu(&mut self) {} + + /// This function is public to allow completely resetting the handler, but it is explicitely + /// discouraged to do this. CFDP has mechanism to detect issues and errors on itself. + pub fn reset(&mut self) { + self.state_helper = Default::default(); + self.tstate = None; + self.fparams = Default::default(); + } } #[cfg(test)] -- 2.43.0 From d3aa56a95413f6d5cea647e19a4723229a769df6 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 19 Aug 2024 11:03:09 +0200 Subject: [PATCH 12/18] use same spacepackets version --- satrs-shared/Cargo.toml | 2 +- satrs/Cargo.toml | 2 +- satrs/src/cfdp/source.rs | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/satrs-shared/Cargo.toml b/satrs-shared/Cargo.toml index 628ffa1..a840192 100644 --- a/satrs-shared/Cargo.toml +++ b/satrs-shared/Cargo.toml @@ -25,7 +25,7 @@ optional = true version = ">0.9" default-features = false git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" -branch = "all-cfdp-updates" +branch = "main" [features] serde = ["dep:serde", "spacepackets/serde"] diff --git a/satrs/Cargo.toml b/satrs/Cargo.toml index a322896..8e9c11e 100644 --- a/satrs/Cargo.toml +++ b/satrs/Cargo.toml @@ -31,7 +31,7 @@ default-features = false version = "0.12" default-features = false git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets" -branch = "eof-pdu-update" +branch = "main" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index 7a77704..f3d02fa 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -352,6 +352,7 @@ impl< } if self.state_helper.step == TransactionStep::NoticeOfCompletion { self.notice_of_completion(cfdp_user); + self.reset(); /* def _notice_of_completion(self): if self.cfg.indication_cfg.transaction_finished_indication_required: @@ -709,6 +710,8 @@ impl< /// This function is public to allow completely resetting the handler, but it is explicitely /// discouraged to do this. CFDP has mechanism to detect issues and errors on itself. + /// Resetting the handler might interfere with these mechanisms and lead to unexpected + /// behaviour. pub fn reset(&mut self) { self.state_helper = Default::default(); self.tstate = None; @@ -767,7 +770,7 @@ mod tests { let fault_handler = TestFaultHandler::default(); let test_sender = TestCfdpSender::default(); let source_handler = default_source_handler(fault_handler, test_sender); - // assert!(dest_handler.transmission_mode().is_none()); + assert!(source_handler.transmission_mode().is_none()); // assert!(fault_handler.all_queues_empty()); } } -- 2.43.0 From ed4b3ee8583a0d2e861e8c342f965bc302852a0a Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Mon, 19 Aug 2024 17:54:31 +0200 Subject: [PATCH 13/18] first unittest for source handler --- satrs/src/cfdp/dest.rs | 154 +++-------------------- satrs/src/cfdp/mod.rs | 147 +++++++++++++++++++++- satrs/src/cfdp/source.rs | 257 +++++++++++++++++++++++++++++++++------ 3 files changed, 381 insertions(+), 177 deletions(-) diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs index 5002cc9..f0e1e87 100644 --- a/satrs/src/cfdp/dest.rs +++ b/satrs/src/cfdp/dest.rs @@ -776,6 +776,7 @@ impl< fn notice_of_suspension(&mut self) { // TODO: Implement suspension handling. } + fn abandon_transaction(&mut self) { self.reset(); } @@ -815,6 +816,10 @@ impl< Ok(1) } + pub fn local_cfg(&self) -> &LocalEntityConfig { + &self.local_cfg + } + fn tstate(&self) -> &TransferState { &self.tparams.tstate } @@ -831,7 +836,7 @@ mod tests { #[allow(unused_imports)] use std::println; - use alloc::{collections::VecDeque, string::String, sync::Arc, vec::Vec}; + use alloc::{sync::Arc, vec::Vec}; use rand::Rng; use spacepackets::{ cfdp::{ @@ -844,148 +849,18 @@ mod tests { use crate::cfdp::{ filestore::NativeFilestore, - tests::{basic_remote_cfg_table, SentPdu, TestCfdpSender, TestFaultHandler}, - user::OwnedMetadataRecvdParams, + tests::{ + basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler, + LOCAL_ID, + }, CheckTimerProviderCreator, CountdownProvider, FaultHandler, IndicationConfig, StdRemoteEntityConfigProvider, 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, - } - - #[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(Debug)] struct TestCheckTimer { counter: Cell, @@ -1066,7 +941,7 @@ mod tests { let test_sender = TestCfdpSender::default(); let dest_handler = default_dest_handler(fault_handler, test_sender, check_timer_expired.clone()); - let (src_path, dest_path) = init_full_filenames(); + let (src_path, dest_path) = init_full_filepaths_textfile(); assert!(!Path::exists(&dest_path)); let handler = Self { check_timer_expired, @@ -1205,7 +1080,7 @@ mod tests { } } - fn init_full_filenames() -> (PathBuf, PathBuf) { + fn init_full_filepaths_textfile() -> (PathBuf, PathBuf) { ( tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf(), tempfile::NamedTempFile::new() @@ -1230,7 +1105,7 @@ mod tests { 2048, test_packet_sender, NativeFilestore::default(), - basic_remote_cfg_table(), + basic_remote_cfg_table(LOCAL_ID, true), TestCheckTimerCreator::new(check_timer_expired), ) } @@ -1297,6 +1172,9 @@ mod tests { .user_hook .borrow() .all_queues_empty()); + assert!(dest_handler.pdu_sender.queue_empty()); + assert_eq!(dest_handler.state(), State::Idle); + assert_eq!(dest_handler.step(), TransactionStep::Idle); } #[test] diff --git a/satrs/src/cfdp/mod.rs b/satrs/src/cfdp/mod.rs index 322bd81..6622371 100644 --- a/satrs/src/cfdp/mod.rs +++ b/satrs/src/cfdp/mod.rs @@ -667,7 +667,7 @@ impl<'raw> PacketInfo<'raw> { pub(crate) mod tests { use core::cell::RefCell; - use alloc::{collections::VecDeque, vec::Vec}; + use alloc::{collections::VecDeque, string::String, vec::Vec}; use spacepackets::{ cfdp::{ lv::Lv, @@ -679,16 +679,150 @@ pub(crate) mod tests { }, ChecksumType, ConditionCode, PduType, TransmissionMode, }, - util::UnsignedByteFieldU16, + util::{UnsignedByteField, UnsignedByteFieldU16, UnsignedEnum}, }; use crate::cfdp::PacketTarget; use super::{ + user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams}, PacketInfo, PduSendProvider, RemoteEntityConfig, RemoteEntityConfigProvider, StdRemoteEntityConfigProvider, TransactionId, UserFaultHookProvider, }; + pub const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); + + pub struct FileSegmentRecvdParamsNoSegMetadata { + pub id: TransactionId, + pub offset: u64, + pub length: usize, + } + + #[derive(Default)] + pub struct TestCfdpUser { + pub next_expected_seq_num: u64, + pub expected_full_src_name: String, + pub expected_full_dest_name: String, + pub expected_file_size: u64, + pub transaction_indication_call_count: u32, + pub eof_recvd_call_count: u32, + pub finished_indic_queue: VecDeque, + pub metadata_recv_queue: VecDeque, + pub file_seg_recvd_queue: VecDeque, + } + + impl TestCfdpUser { + pub 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(), + } + } + + pub 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)] pub(crate) struct TestFaultHandler { pub notice_of_suspension_queue: VecDeque<(TransactionId, ConditionCode, u64)>, @@ -791,14 +925,17 @@ pub(crate) mod tests { } } - pub fn basic_remote_cfg_table() -> StdRemoteEntityConfigProvider { + pub fn basic_remote_cfg_table( + dest_id: impl Into, + crc_on_transmission_by_default: bool, + ) -> StdRemoteEntityConfigProvider { let mut table = StdRemoteEntityConfigProvider::default(); let remote_entity_cfg = RemoteEntityConfig::new_with_default_values( - UnsignedByteFieldU16::new(1).into(), + dest_id.into(), 1024, 1024, true, - true, + crc_on_transmission_by_default, TransmissionMode::Unacknowledged, ChecksumType::Crc32, ); diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index f3d02fa..b05bca9 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,4 +1,5 @@ use core::{cell::RefCell, ops::ControlFlow, str::Utf8Error}; +use std::println; use spacepackets::{ cfdp::{ @@ -118,6 +119,8 @@ pub enum PutRequestError { Storage(#[from] ByteConversionError), #[error("already busy with put request")] AlreadyBusy, + #[error("no remote entity configuration found for {0:?}")] + NoRemoteCfgFound(UnsignedByteField), } pub struct SourceHandler< @@ -258,6 +261,9 @@ impl< ); if remote_cfg.is_none() { // TODO: Specific error. + return Err(PutRequestError::NoRemoteCfgFound( + self.put_request_cacher.static_fields.destination_id, + )); } let remote_cfg = remote_cfg.unwrap(); self.state_helper.num_packets_ready = 0; @@ -281,7 +287,7 @@ impl< }; self.tstate = Some(TransferState::new( TransactionId::new( - self.put_request_cacher.static_fields.destination_id, + self.local_cfg().id, UnsignedByteField::new( SeqCountProvider::MAX_BIT_WIDTH / 8, self.seq_count_provider.get_and_increment().into(), @@ -302,6 +308,7 @@ impl< } fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { + let mut sent_packets = 0; if self.state_helper.step == TransactionStep::Idle { self.state_helper.step = TransactionStep::TransactionStart; } @@ -312,7 +319,8 @@ impl< if self.state_helper.step == TransactionStep::SendingMetadata { self.prepare_and_send_metadata_pdu()?; self.state_helper.step = TransactionStep::SendingFileData; - return Ok(1); + sent_packets += 1; + // return Ok(1); } if self.state_helper.step == TransactionStep::SendingFileData { if let ControlFlow::Break(result) = self.file_data_fsm()? { @@ -321,7 +329,8 @@ impl< } if self.state_helper.step == TransactionStep::SendingEof { self.eof_fsm(cfdp_user)?; - return Ok(1); + sent_packets += 1; + // return Ok(1); } if self.state_helper.step == TransactionStep::WaitingForFinished { /* @@ -373,7 +382,7 @@ impl< self.reset() */ } - Ok(0) + Ok(sent_packets) } fn eof_fsm(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), SourceError> { @@ -431,7 +440,6 @@ impl< .expect("transfer state unexpectedly empty"); if !self.put_request_cacher.has_source_file() { self.fparams.metadata_only = true; - self.fparams.empty_file = true; } else { let source_file = self .put_request_cacher @@ -446,9 +454,13 @@ impl< self.put_request_cacher .dest_file() .map_err(SourceError::DestFileNotValidUtf8)?; - if self.vfs.file_size(source_file)? > u32::MAX as u64 { + let file_size = self.vfs.file_size(source_file)?; + if file_size > u32::MAX as u64 { self.pdu_conf.file_flag = LargeFileFlag::Large } else { + if file_size == 0 { + self.fparams.empty_file = true; + } self.pdu_conf.file_flag = LargeFileFlag::Normal } } @@ -463,7 +475,7 @@ impl< if larger_entity_width != cached_id.size() { UnsignedByteField::new(larger_entity_width, cached_id.value_const()) } else { - self.local_cfg.id + *cached_id } }; self.pdu_conf @@ -662,8 +674,8 @@ impl< let mut pdu_buffer_mut = self.pdu_and_cksum_buffer.borrow_mut(); let written_len = pdu.write_to_bytes(&mut pdu_buffer_mut)?; self.pdu_sender.send_pdu( - PduType::FileDirective, - Some(FileDirectiveType::MetadataPdu), + pdu.pdu_type(), + pdu.file_directive_type(), &pdu_buffer_mut[0..written_len], )?; Ok(()) @@ -708,6 +720,21 @@ impl< fn handle_keep_alive_pdu(&mut self) {} + /// Get the step, which denotes the exact step of a pending CFDP transaction when applicable. + pub fn step(&self) -> TransactionStep { + self.state_helper.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_helper.state + } + + pub fn local_cfg(&self) -> &LocalEntityConfig { + &self.local_cfg + } + /// This function is public to allow completely resetting the handler, but it is explicitely /// discouraged to do this. CFDP has mechanism to detect issues and errors on itself. /// Resetting the handler might interfere with these mechanisms and lead to unexpected @@ -721,14 +748,24 @@ impl< #[cfg(test)] mod tests { - use spacepackets::util::UnsignedByteFieldU16; + use std::path::PathBuf; + + use alloc::string::String; + use spacepackets::{ + cfdp::{pdu::metadata::MetadataPduReader, ChecksumType, CrcFlag}, + util::UnsignedByteFieldU16, + }; + use tempfile::TempPath; use super::*; use crate::{ cfdp::{ filestore::NativeFilestore, - tests::{basic_remote_cfg_table, TestCfdpSender, TestFaultHandler}, - FaultHandler, IndicationConfig, StdRemoteEntityConfigProvider, + request::PutRequestOwned, + tests::{ + basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler, + }, + FaultHandler, IndicationConfig, StdRemoteEntityConfigProvider, CRC_32, }, seq_count::SeqCountProviderSimple, }; @@ -736,6 +773,13 @@ mod tests { const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2); + fn init_full_filepaths_textfile() -> (TempPath, PathBuf) { + ( + tempfile::NamedTempFile::new().unwrap().into_temp_path(), + tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf(), + ) + } + type TestSourceHandler = SourceHandler< TestCfdpSender, TestFaultHandler, @@ -744,33 +788,178 @@ mod tests { SeqCountProviderSimple, >; - fn default_source_handler( - test_fault_handler: TestFaultHandler, - test_packet_sender: TestCfdpSender, - ) -> TestSourceHandler { - let local_entity_cfg = LocalEntityConfig { - id: REMOTE_ID.into(), - indication_cfg: IndicationConfig::default(), - fault_handler: FaultHandler::new(test_fault_handler), - }; - let static_put_request_cacher = StaticPutRequestCacher::new(1024); - SourceHandler::new( - local_entity_cfg, - test_packet_sender, - NativeFilestore::default(), - static_put_request_cacher, - 1024, - basic_remote_cfg_table(), - SeqCountProviderSimple::default(), - ) + struct SourceHandlerTestbench { + handler: TestSourceHandler, + // src_path: PathBuf, + // dest_path: PathBuf, + } + + impl SourceHandlerTestbench { + fn new( + crc_on_transmission_by_default: bool, + test_fault_handler: TestFaultHandler, + test_packet_sender: TestCfdpSender, + ) -> Self { + let local_entity_cfg = LocalEntityConfig { + id: LOCAL_ID.into(), + indication_cfg: IndicationConfig::default(), + fault_handler: FaultHandler::new(test_fault_handler), + }; + let static_put_request_cacher = StaticPutRequestCacher::new(2048); + Self { + handler: SourceHandler::new( + local_entity_cfg, + test_packet_sender, + NativeFilestore::default(), + static_put_request_cacher, + 1024, + basic_remote_cfg_table(REMOTE_ID, crc_on_transmission_by_default), + SeqCountProviderSimple::default(), + ), + } + } + + fn all_fault_queues_empty(&self) -> bool { + self.handler + .local_cfg + .user_fault_hook() + .borrow() + .all_queues_empty() + } + + fn pdu_queue_empty(&self) -> bool { + self.handler.pdu_sender.queue_empty() + } + + fn get_next_sent_pdu(&self) -> Option { + self.handler.pdu_sender.retrieve_next_pdu() + } } #[test] fn test_basic() { let fault_handler = TestFaultHandler::default(); let test_sender = TestCfdpSender::default(); - let source_handler = default_source_handler(fault_handler, test_sender); - assert!(source_handler.transmission_mode().is_none()); - // assert!(fault_handler.all_queues_empty()); + let tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); + assert!(tb.handler.transmission_mode().is_none()); + assert!(tb.all_fault_queues_empty()); + assert!(tb.pdu_queue_empty()); + assert_eq!(tb.handler.state(), State::Idle); + assert_eq!(tb.handler.step(), TransactionStep::Idle); + } + + #[test] + fn test_empty_file_transfer_not_acked_no_closure() { + let fault_handler = TestFaultHandler::default(); + let test_sender = TestCfdpSender::default(); + let mut tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); + let (srcfile, destfile) = init_full_filepaths_textfile(); + let srcfile_str = String::from(srcfile.to_str().unwrap()); + let destfile_str = String::from(destfile.to_str().unwrap()); + let put_request = PutRequestOwned::new_regular_request( + REMOTE_ID.into(), + &srcfile_str, + &destfile_str, + Some(TransmissionMode::Unacknowledged), + Some(false), + ) + .expect("creating put request failed"); + tb.handler + .put_request(&put_request) + .expect("put_request call failed"); + assert_eq!(tb.handler.state(), State::Busy); + assert_eq!(tb.handler.step(), TransactionStep::Idle); + let mut cfdp_user = TestCfdpUser::new(0, srcfile_str.clone(), destfile_str.clone(), 0); + let sent_packets = tb + .handler + .state_machine(&mut cfdp_user, None) + .expect("source handler FSM failure"); + assert_eq!(sent_packets, 2); + assert!(!tb.pdu_queue_empty()); + let next_pdu = tb.get_next_sent_pdu().unwrap(); + assert!(!tb.pdu_queue_empty()); + assert_eq!(next_pdu.pdu_type, PduType::FileDirective); + assert_eq!( + next_pdu.file_directive_type, + Some(FileDirectiveType::MetadataPdu) + ); + let metadata_pdu = + MetadataPduReader::new(&next_pdu.raw_pdu).expect("invalid metadata PDU format"); + assert_eq!( + metadata_pdu + .src_file_name() + .value_as_str() + .unwrap() + .unwrap(), + srcfile_str + ); + assert_eq!( + metadata_pdu + .dest_file_name() + .value_as_str() + .unwrap() + .unwrap(), + destfile_str + ); + assert_eq!(metadata_pdu.metadata_params().file_size, 0); + assert_eq!( + metadata_pdu.metadata_params().checksum_type, + ChecksumType::Crc32 + ); + assert!(!metadata_pdu.metadata_params().closure_requested); + assert_eq!(metadata_pdu.options(), &[]); + + let next_pdu = tb.get_next_sent_pdu().unwrap(); + assert_eq!(next_pdu.pdu_type, PduType::FileDirective); + assert_eq!( + next_pdu.file_directive_type, + Some(FileDirectiveType::EofPdu) + ); + let eof_pdu = EofPdu::from_bytes(&next_pdu.raw_pdu).expect("invalid metadata PDU format"); + assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError); + assert_eq!(eof_pdu.file_size(), 0); + assert_eq!(eof_pdu.file_checksum(), CRC_32.digest().finalize()); + assert_eq!( + eof_pdu.pdu_header().common_pdu_conf().source_id(), + LOCAL_ID.into() + ); + assert_eq!( + eof_pdu.pdu_header().common_pdu_conf().dest_id(), + REMOTE_ID.into() + ); + assert_eq!( + eof_pdu.pdu_header().common_pdu_conf().crc_flag, + CrcFlag::NoCrc + ); + assert_eq!( + eof_pdu.pdu_header().common_pdu_conf().direction, + Direction::TowardsReceiver + ); + assert_eq!( + eof_pdu.pdu_header().common_pdu_conf().file_flag, + LargeFileFlag::Normal + ); + assert_eq!( + eof_pdu.pdu_header().common_pdu_conf().trans_mode, + TransmissionMode::Unacknowledged + ); + assert_eq!( + eof_pdu + .pdu_header() + .common_pdu_conf() + .transaction_seq_num + .value_const(), + 0 + ); + assert_eq!( + eof_pdu + .pdu_header() + .common_pdu_conf() + .transaction_seq_num + .size(), + 2 + ); + assert_eq!(tb.handler.state(), State::Idle); + assert_eq!(tb.handler.step(), TransactionStep::Idle); } } -- 2.43.0 From 1646aac0048c161c00f690d557e67b67d5b11884 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 20 Aug 2024 09:32:32 +0200 Subject: [PATCH 14/18] add unittest for requested closure --- satrs/src/cfdp/source.rs | 192 ++++++++++++++++++++++++++++----------- 1 file changed, 137 insertions(+), 55 deletions(-) diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs index b05bca9..84aea47 100644 --- a/satrs/src/cfdp/source.rs +++ b/satrs/src/cfdp/source.rs @@ -1,5 +1,4 @@ use core::{cell::RefCell, ops::ControlFlow, str::Utf8Error}; -use std::println; use spacepackets::{ cfdp::{ @@ -748,11 +747,14 @@ impl< #[cfg(test)] mod tests { - use std::path::PathBuf; + use std::{path::PathBuf, string::ToString}; use alloc::string::String; use spacepackets::{ - cfdp::{pdu::metadata::MetadataPduReader, ChecksumType, CrcFlag}, + cfdp::{ + pdu::{finished::FinishedPduCreator, metadata::MetadataPduReader}, + ChecksumType, CrcFlag, + }, util::UnsignedByteFieldU16, }; use tempfile::TempPath; @@ -790,8 +792,8 @@ mod tests { struct SourceHandlerTestbench { handler: TestSourceHandler, - // src_path: PathBuf, - // dest_path: PathBuf, + srcfile: Option, + destfile: Option, } impl SourceHandlerTestbench { @@ -816,9 +818,22 @@ mod tests { basic_remote_cfg_table(REMOTE_ID, crc_on_transmission_by_default), SeqCountProviderSimple::default(), ), + srcfile: None, + destfile: None, } } + fn put_request( + &mut self, + put_request: &impl ReadablePutRequest, + ) -> Result<(), PutRequestError> { + if self.srcfile.is_some() || self.destfile.is_some() { + self.srcfile = Some(put_request.source_file().unwrap().to_string()); + self.destfile = Some(put_request.dest_file().unwrap().to_string()); + } + self.handler.put_request(put_request) + } + fn all_fault_queues_empty(&self) -> bool { self.handler .local_cfg @@ -834,6 +849,33 @@ mod tests { fn get_next_sent_pdu(&self) -> Option { self.handler.pdu_sender.retrieve_next_pdu() } + + fn common_pdu_check_for_file_transfer(&self, pdu_header: &PduHeader, crc_flag: CrcFlag) { + assert_eq!( + pdu_header.seg_ctrl(), + SegmentationControl::NoRecordBoundaryPreservation + ); + assert_eq!( + pdu_header.seg_metadata_flag(), + SegmentMetadataFlag::NotPresent + ); + assert_eq!(pdu_header.common_pdu_conf().source_id(), LOCAL_ID.into()); + assert_eq!(pdu_header.common_pdu_conf().dest_id(), REMOTE_ID.into()); + assert_eq!(pdu_header.common_pdu_conf().crc_flag, crc_flag); + assert_eq!( + pdu_header.common_pdu_conf().trans_mode, + TransmissionMode::Unacknowledged + ); + assert_eq!( + pdu_header.common_pdu_conf().direction, + Direction::TowardsReceiver + ); + assert_eq!( + pdu_header.common_pdu_conf().file_flag, + LargeFileFlag::Normal + ); + assert_eq!(pdu_header.common_pdu_conf().transaction_seq_num.size(), 2); + } } #[test] @@ -848,31 +890,21 @@ mod tests { assert_eq!(tb.handler.step(), TransactionStep::Idle); } - #[test] - fn test_empty_file_transfer_not_acked_no_closure() { - let fault_handler = TestFaultHandler::default(); - let test_sender = TestCfdpSender::default(); - let mut tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); - let (srcfile, destfile) = init_full_filepaths_textfile(); - let srcfile_str = String::from(srcfile.to_str().unwrap()); - let destfile_str = String::from(destfile.to_str().unwrap()); - let put_request = PutRequestOwned::new_regular_request( - REMOTE_ID.into(), - &srcfile_str, - &destfile_str, - Some(TransmissionMode::Unacknowledged), - Some(false), - ) - .expect("creating put request failed"); + fn common_no_acked_file_transfer( + tb: &mut SourceHandlerTestbench, + cfdp_user: &mut TestCfdpUser, + put_request: PutRequestOwned, + srcfile_str: String, + destfile_str: String, + ) -> PduHeader { tb.handler .put_request(&put_request) .expect("put_request call failed"); assert_eq!(tb.handler.state(), State::Busy); assert_eq!(tb.handler.step(), TransactionStep::Idle); - let mut cfdp_user = TestCfdpUser::new(0, srcfile_str.clone(), destfile_str.clone(), 0); let sent_packets = tb .handler - .state_machine(&mut cfdp_user, None) + .state_machine(cfdp_user, None) .expect("source handler FSM failure"); assert_eq!(sent_packets, 2); assert!(!tb.pdu_queue_empty()); @@ -885,6 +917,8 @@ mod tests { ); let metadata_pdu = MetadataPduReader::new(&next_pdu.raw_pdu).expect("invalid metadata PDU format"); + let pdu_header = metadata_pdu.pdu_header(); + tb.common_pdu_check_for_file_transfer(metadata_pdu.pdu_header(), CrcFlag::NoCrc); assert_eq!( metadata_pdu .src_file_name() @@ -906,7 +940,16 @@ mod tests { metadata_pdu.metadata_params().checksum_type, ChecksumType::Crc32 ); - assert!(!metadata_pdu.metadata_params().closure_requested); + let closure_requested = if let Some(closure_requested) = put_request.closure_requested { + assert_eq!( + metadata_pdu.metadata_params().closure_requested, + closure_requested + ); + closure_requested + } else { + assert!(metadata_pdu.metadata_params().closure_requested); + metadata_pdu.metadata_params().closure_requested + }; assert_eq!(metadata_pdu.options(), &[]); let next_pdu = tb.get_next_sent_pdu().unwrap(); @@ -916,33 +959,10 @@ mod tests { Some(FileDirectiveType::EofPdu) ); let eof_pdu = EofPdu::from_bytes(&next_pdu.raw_pdu).expect("invalid metadata PDU format"); + tb.common_pdu_check_for_file_transfer(eof_pdu.pdu_header(), CrcFlag::NoCrc); assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError); assert_eq!(eof_pdu.file_size(), 0); assert_eq!(eof_pdu.file_checksum(), CRC_32.digest().finalize()); - assert_eq!( - eof_pdu.pdu_header().common_pdu_conf().source_id(), - LOCAL_ID.into() - ); - assert_eq!( - eof_pdu.pdu_header().common_pdu_conf().dest_id(), - REMOTE_ID.into() - ); - assert_eq!( - eof_pdu.pdu_header().common_pdu_conf().crc_flag, - CrcFlag::NoCrc - ); - assert_eq!( - eof_pdu.pdu_header().common_pdu_conf().direction, - Direction::TowardsReceiver - ); - assert_eq!( - eof_pdu.pdu_header().common_pdu_conf().file_flag, - LargeFileFlag::Normal - ); - assert_eq!( - eof_pdu.pdu_header().common_pdu_conf().trans_mode, - TransmissionMode::Unacknowledged - ); assert_eq!( eof_pdu .pdu_header() @@ -951,14 +971,76 @@ mod tests { .value_const(), 0 ); - assert_eq!( - eof_pdu - .pdu_header() - .common_pdu_conf() - .transaction_seq_num - .size(), - 2 + if !closure_requested { + assert_eq!(tb.handler.state(), State::Idle); + assert_eq!(tb.handler.step(), TransactionStep::Idle); + } else { + assert_eq!(tb.handler.state(), State::Busy); + assert_eq!(tb.handler.step(), TransactionStep::WaitingForFinished); + } + *pdu_header + } + + #[test] + fn test_empty_file_transfer_not_acked_no_closure() { + let fault_handler = TestFaultHandler::default(); + let test_sender = TestCfdpSender::default(); + let mut tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); + let (srcfile, destfile) = init_full_filepaths_textfile(); + let srcfile_str = String::from(srcfile.to_str().unwrap()); + let destfile_str = String::from(destfile.to_str().unwrap()); + let put_request = PutRequestOwned::new_regular_request( + REMOTE_ID.into(), + &srcfile_str, + &destfile_str, + Some(TransmissionMode::Unacknowledged), + Some(false), + ) + .expect("creating put request failed"); + let mut cfdp_user = TestCfdpUser::new(0, srcfile_str.clone(), destfile_str.clone(), 0); + common_no_acked_file_transfer( + &mut tb, + &mut cfdp_user, + put_request, + srcfile_str, + destfile_str, ); + } + + #[test] + fn test_empty_file_transfer_not_acked_with_closure() { + let fault_handler = TestFaultHandler::default(); + let test_sender = TestCfdpSender::default(); + let mut tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); + let (srcfile, destfile) = init_full_filepaths_textfile(); + let srcfile_str = String::from(srcfile.to_str().unwrap()); + let destfile_str = String::from(destfile.to_str().unwrap()); + let put_request = PutRequestOwned::new_regular_request( + REMOTE_ID.into(), + &srcfile_str, + &destfile_str, + Some(TransmissionMode::Unacknowledged), + Some(true), + ) + .expect("creating put request failed"); + let mut cfdp_user = TestCfdpUser::new(0, srcfile_str.clone(), destfile_str.clone(), 0); + let pdu_header = common_no_acked_file_transfer( + &mut tb, + &mut cfdp_user, + put_request, + srcfile_str, + destfile_str, + ); + let finished_pdu = FinishedPduCreator::new_default( + pdu_header, + DeliveryCode::Complete, + FileStatus::Retained, + ); + let finished_pdu_vec = finished_pdu.to_vec().unwrap(); + let packet_info = PacketInfo::new(&finished_pdu_vec).unwrap(); + tb.handler + .state_machine(&mut cfdp_user, Some(&packet_info)) + .unwrap(); assert_eq!(tb.handler.state(), State::Idle); assert_eq!(tb.handler.step(), TransactionStep::Idle); } -- 2.43.0 From 8b887f9466a70cadf50157ec22187bcca79b83f1 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 20 Aug 2024 15:11:05 +0200 Subject: [PATCH 15/18] moved CFDP to own crate --- satrs/src/cfdp/dest.rs | 1415 ----------------------------------- satrs/src/cfdp/filestore.rs | 802 -------------------- satrs/src/cfdp/mod.rs | 1006 ------------------------- satrs/src/cfdp/request.rs | 644 ---------------- satrs/src/cfdp/source.rs | 1047 -------------------------- satrs/src/cfdp/user.rs | 96 --- satrs/src/lib.rs | 2 - 7 files changed, 5012 deletions(-) delete mode 100644 satrs/src/cfdp/dest.rs delete mode 100644 satrs/src/cfdp/filestore.rs delete mode 100644 satrs/src/cfdp/mod.rs delete mode 100644 satrs/src/cfdp/request.rs delete mode 100644 satrs/src/cfdp/source.rs delete mode 100644 satrs/src/cfdp/user.rs diff --git a/satrs/src/cfdp/dest.rs b/satrs/src/cfdp/dest.rs deleted file mode 100644 index f0e1e87..0000000 --- a/satrs/src/cfdp/dest.rs +++ /dev/null @@ -1,1415 +0,0 @@ -use crate::cfdp::user::TransactionFinishedParams; -use core::str::{from_utf8, Utf8Error}; -use std::path::{Path, PathBuf}; - -use super::{ - filestore::{FilestoreError, NativeFilestore, VirtualFilestore}, - user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams}, - CheckTimerProviderCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo, - PacketTarget, PduSendProvider, RemoteEntityConfig, RemoteEntityConfigProvider, State, - StdCheckTimer, StdCheckTimerCreator, StdRemoteEntityConfigProvider, TimerContext, - TransactionId, UserFaultHookProvider, -}; -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, ReadableTlv, 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, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TransactionStep { - Idle = 0, - TransactionStart = 1, - ReceivingFileDataPdus = 2, - ReceivingFileDataPdusWithCheckLimitHandling = 3, - SendingAckPdu = 4, - TransferCompletion = 5, - SendingFinishedPdu = 6, -} - -// This contains transfer state parameters for destination transaction. -#[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, - } - } -} - -// This contains parameters for destination transaction. -#[derive(Debug)] -struct TransactionParams { - tstate: TransferState, - pdu_conf: CommonPduConfig, - file_properties: FileProperties, - cksum_buf: [u8; 1024], - msgs_to_user_size: usize, - // TODO: Should we make this configurable? - 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")] - DirectiveFieldEmpty, - #[error("can not process packet type {pdu_type:?} with directive type {directive_type:?}")] - CantProcessPacketType { - pdu_type: PduType, - directive_type: Option, - }, - #[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), -} - -/// 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< - PduSender: PduSendProvider, - UserFaultHook: UserFaultHookProvider, - Vfs: VirtualFilestore, - RemoteCfgTable: RemoteEntityConfigProvider, - CheckTimerCreator: CheckTimerProviderCreator, - CheckTimerProvider: CountdownProvider, -> { - local_cfg: LocalEntityConfig, - step: TransactionStep, - state: State, - tparams: TransactionParams, - packet_buf: alloc::vec::Vec, - pub pdu_sender: PduSender, - pub vfs: Vfs, - pub remote_cfg_table: RemoteCfgTable, - pub check_timer_creator: CheckTimerCreator, -} - -#[cfg(feature = "std")] -pub type StdDestinationHandler = DestinationHandler< - PduSender, - UserFaultHook, - NativeFilestore, - StdRemoteEntityConfigProvider, - StdCheckTimerCreator, - StdCheckTimer, ->; - -impl< - PduSender: PduSendProvider, - UserFaultHook: UserFaultHookProvider, - Vfs: VirtualFilestore, - RemoteCfgTable: RemoteEntityConfigProvider, - CheckTimerCreator: CheckTimerProviderCreator, - CheckTimerProvider: CountdownProvider, - > - DestinationHandler< - PduSender, - UserFaultHook, - Vfs, - RemoteCfgTable, - CheckTimerCreator, - CheckTimerProvider, - > -{ - /// 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, - pdu_sender: PduSender, - vfs: Vfs, - remote_cfg_table: RemoteCfgTable, - check_timer_creator: CheckTimerCreator, - ) -> Self { - Self { - local_cfg, - step: TransactionStep::Idle, - state: State::Idle, - tparams: Default::default(), - packet_buf: alloc::vec![0; max_packet_len], - pdu_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. - /// - /// The function returns the number of sent PDU packets on success. - 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 { - pdu_type: packet_info.pdu_type(), - directive_type: packet_info.pdu_directive(), - }); - } - match packet_info.pdu_type { - PduType::FileDirective => { - if packet_info.pdu_directive.is_none() { - return Err(DestError::DirectiveFieldEmpty); - } - 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_type: PduType::FileDirective, - directive_type: Some(pdu_directive), - }); - } - FileDirectiveType::AckPdu => { - todo!("acknowledged mode not implemented yet") - } - 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(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 - .create_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 - .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 - .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.pdu_sender.send_pdu( - finished_pdu.pdu_type(), - finished_pdu.file_directive_type(), - &self.packet_buf[0..finished_pdu.len_written()], - )?; - Ok(1) - } - - pub fn local_cfg(&self) -> &LocalEntityConfig { - &self.local_cfg - } - - 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}; - use std::fs; - #[allow(unused_imports)] - use std::println; - - use alloc::{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, - tests::{ - basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler, - LOCAL_ID, - }, - CheckTimerProviderCreator, CountdownProvider, FaultHandler, IndicationConfig, - StdRemoteEntityConfigProvider, CRC_32, - }; - - use super::*; - - const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2); - - #[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 CheckTimerProviderCreator for TestCheckTimerCreator { - type CheckTimer = TestCheckTimer; - - fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer { - match timer_context { - TimerContext::CheckLimit { .. } => { - TestCheckTimer::new(self.check_limit_expired_flag.clone()) - } - _ => { - panic!("invalid check timer creator, can only be used for check limit handling") - } - } - } - } - - type TestDestHandler = DestinationHandler< - TestCfdpSender, - TestFaultHandler, - NativeFilestore, - StdRemoteEntityConfigProvider, - TestCheckTimerCreator, - TestCheckTimer, - >; - - struct DestHandlerTester { - check_timer_expired: Arc, - handler: TestDestHandler, - 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, check_timer_expired.clone()); - let (src_path, dest_path) = init_full_filepaths_textfile(); - assert!(!Path::exists(&dest_path)); - let handler = Self { - check_timer_expired, - 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 - } - - fn all_fault_queues_empty(&self) -> bool { - self.handler - .local_cfg - .user_fault_hook() - .borrow() - .all_queues_empty() - } - - #[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_filepaths_textfile() -> (PathBuf, PathBuf) { - ( - tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf(), - tempfile::NamedTempFile::new() - .unwrap() - .into_temp_path() - .to_path_buf(), - ) - } - - fn default_dest_handler( - test_fault_handler: TestFaultHandler, - test_packet_sender: TestCfdpSender, - check_timer_expired: Arc, - ) -> TestDestHandler { - let local_entity_cfg = LocalEntityConfig { - id: REMOTE_ID.into(), - indication_cfg: IndicationConfig::default(), - fault_handler: FaultHandler::new(test_fault_handler), - }; - DestinationHandler::new( - local_entity_cfg, - 2048, - test_packet_sender, - NativeFilestore::default(), - basic_remote_cfg_table(LOCAL_ID, true), - 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, test_sender, Arc::default()); - assert!(dest_handler.transmission_mode().is_none()); - assert!(dest_handler - .local_cfg - .fault_handler - .user_hook - .borrow() - .all_queues_empty()); - assert!(dest_handler.pdu_sender.queue_empty()); - assert_eq!(dest_handler.state(), State::Idle); - assert_eq!(dest_handler.step(), TransactionStep::Idle); - } - - #[test] - fn test_empty_file_transfer_not_acked_no_closure() { - let fault_handler = TestFaultHandler::default(); - let mut testbench = DestHandlerTester::new(fault_handler, false); - let mut test_user = testbench.test_user_from_cached_paths(0); - testbench - .generic_transfer_init(&mut test_user, 0) - .expect("transfer init failed"); - testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - testbench - .generic_eof_no_error(&mut test_user, Vec::new()) - .expect("EOF no error insertion failed"); - assert!(testbench.all_fault_queues_empty()); - assert!(testbench.handler.pdu_sender.queue_empty()); - testbench.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 testbench = DestHandlerTester::new(fault_handler, false); - let mut test_user = testbench.test_user_from_cached_paths(file_size); - testbench - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - testbench - .generic_file_data_insert(&mut test_user, 0, file_data) - .expect("file data insertion failed"); - testbench - .generic_eof_no_error(&mut test_user, file_data.to_vec()) - .expect("EOF no error insertion failed"); - assert!(testbench.all_fault_queues_empty()); - assert!(testbench.handler.pdu_sender.queue_empty()); - testbench.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 testbench = DestHandlerTester::new(fault_handler, false); - let mut test_user = testbench.test_user_from_cached_paths(file_size); - testbench - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - testbench - .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) - .expect("file data insertion failed"); - testbench - .generic_file_data_insert( - &mut test_user, - segment_len as u64, - &random_data[segment_len..], - ) - .expect("file data insertion failed"); - testbench - .generic_eof_no_error(&mut test_user, random_data.to_vec()) - .expect("EOF no error insertion failed"); - assert!(testbench.all_fault_queues_empty()); - assert!(testbench.handler.pdu_sender.queue_empty()); - testbench.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 testbench = DestHandlerTester::new(fault_handler, false); - let mut test_user = testbench.test_user_from_cached_paths(file_size); - let transaction_id = testbench - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - - testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - testbench - .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) - .expect("file data insertion 0 failed"); - testbench - .generic_eof_no_error(&mut test_user, random_data.to_vec()) - .expect("EOF no error insertion failed"); - testbench.state_check( - State::Busy, - TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, - ); - testbench - .generic_file_data_insert( - &mut test_user, - segment_len as u64, - &random_data[segment_len..], - ) - .expect("file data insertion 1 failed"); - testbench.set_check_timer_expired(); - testbench - .handler - .state_machine(&mut test_user, None) - .expect("fsm failure"); - let fault_handler = testbench.handler.local_cfg.fault_handler.user_hook.borrow(); - - assert_eq!(fault_handler.ignored_queue.len(), 1); - let cancelled = fault_handler.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!(testbench.handler.pdu_sender.queue_empty()); - testbench.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 testbench = DestHandlerTester::new(fault_handler, false); - let mut test_user = testbench.test_user_from_cached_paths(file_size); - let transaction_id = testbench - .generic_transfer_init(&mut test_user, file_size) - .expect("transfer init failed"); - - testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - testbench - .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len]) - .expect("file data insertion 0 failed"); - testbench - .generic_eof_no_error(&mut test_user, random_data.to_vec()) - .expect("EOF no error insertion failed"); - testbench.state_check( - State::Busy, - TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, - ); - testbench.set_check_timer_expired(); - testbench - .handler - .state_machine(&mut test_user, None) - .expect("fsm error"); - testbench.state_check( - State::Busy, - TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling, - ); - testbench.set_check_timer_expired(); - testbench - .handler - .state_machine(&mut test_user, None) - .expect("fsm error"); - testbench.state_check(State::Idle, TransactionStep::Idle); - - let fault_hook = testbench.handler.local_cfg.user_fault_hook().borrow(); - - assert!(fault_hook.notice_of_suspension_queue.is_empty()); - let ignored_queue = &fault_hook.ignored_queue; - 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 fault_hook = testbench.handler.local_cfg.user_fault_hook().borrow(); - let cancelled_queue = &fault_hook.notice_of_cancellation_queue; - 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(fault_hook); - - assert!(testbench.handler.pdu_sender.queue_empty()); - - // Check that the broken file exists. - testbench.check_dest_file = false; - assert!(Path::exists(testbench.dest_path())); - let read_content = fs::read(testbench.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(testbench.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 testbench = DestHandlerTester::new(fault_handler, true); - let mut test_user = testbench.test_user_from_cached_paths(0); - testbench - .generic_transfer_init(&mut test_user, 0) - .expect("transfer init failed"); - testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus); - let sent_packets = testbench - .generic_eof_no_error(&mut test_user, Vec::new()) - .expect("EOF no error insertion failed"); - assert_eq!(sent_packets, 1); - assert!(testbench.all_fault_queues_empty()); - // The Finished PDU was sent, so the state machine is done. - testbench.state_check(State::Idle, TransactionStep::Idle); - assert!(!testbench.handler.pdu_sender.queue_empty()); - let sent_pdu = testbench.handler.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 d865b5e..0000000 --- a/satrs/src/cfdp/filestore.rs +++ /dev/null @@ -1,802 +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) -> Result; - - fn is_dir(&self, path: &str) -> Result { - Ok(!self.is_file(path)?) - } - - fn exists(&self, path: &str) -> Result; - - fn file_size(&self, path: &str) -> Result; - - /// This special function is the CFDP specific abstraction to calculate 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 calculate_checksum( - &self, - file_path: &str, - checksum_type: ChecksumType, - verification_buf: &mut [u8], - ) -> Result; - - /// 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 { - Ok( - self.calculate_checksum(file_path, checksum_type, verification_buf)? - == expected_checksum, - ) - } -} - -#[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, str_path: &str) -> Result { - let path = Path::new(str_path); - if !self.exists(str_path)? { - return Err(FilestoreError::FileDoesNotExist); - } - Ok(path.is_file()) - } - - fn exists(&self, path: &str) -> Result { - let path = Path::new(path); - Ok(self.exists_internal(path)) - } - - fn file_size(&self, str_path: &str) -> Result { - let path = Path::new(str_path); - if !self.exists_internal(path) { - return Err(FilestoreError::FileDoesNotExist); - } - if !path.is_file() { - return Err(FilestoreError::IsNotFile); - } - Ok(path.metadata()?.len()) - } - - fn calculate_checksum( - &self, - file_path: &str, - checksum_type: ChecksumType, - verification_buf: &mut [u8], - ) -> Result { - match checksum_type { - ChecksumType::Modular => self.calc_modular_checksum(file_path), - 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]); - } - Ok(digest.finalize()) - } - ChecksumType::NullChecksum => Ok(0), - _ => 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) - } - - fn exists_internal(&self, path: &Path) -> bool { - if !path.exists() { - return false; - } - true - } - } -} - -#[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()).unwrap()); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).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()).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()).unwrap()); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).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()).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()).unwrap()); - assert!(NATIVE_FS - .is_dir(dir_path.as_path().to_str().unwrap()) - .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()).unwrap()); - NATIVE_FS - .remove_file(file_path.to_str().unwrap()) - .expect("removing file failed"); - assert!(!NATIVE_FS.exists(file_path.to_str().unwrap()).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()).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()).unwrap()); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).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()).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()).unwrap()); - assert!(NATIVE_FS.is_file(file_path.to_str().unwrap()).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()).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()).unwrap()); - NATIVE_FS - .remove_dir(dir_path.to_str().unwrap(), false) - .unwrap(); - assert!(!NATIVE_FS.exists(dir_path.to_str().unwrap()).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()).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 6622371..0000000 --- a/satrs/src/cfdp/mod.rs +++ /dev/null @@ -1,1006 +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, time::Duration}; - -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 = "serde")] -use serde::{Deserialize, Serialize}; - -use crate::time::CountdownProvider; - -#[cfg(feature = "std")] -pub mod dest; -#[cfg(feature = "alloc")] -pub mod filestore; -pub mod request; -#[cfg(feature = "std")] -pub mod source; -pub mod user; - -#[cfg(feature = "std")] -pub use std_mod::*; - -#[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. -pub trait CheckTimerProviderCreator { - type CheckTimer: CountdownProvider; - - fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer; -} - -#[cfg(feature = "std")] -pub mod std_mod { - use super::*; - - /// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime. - /// It also assumes that a second accuracy of the check timer period is sufficient. - #[derive(Debug)] - pub struct StdCheckTimer { - expiry_time_seconds: u64, - start_time: std::time::Instant, - } - - impl StdCheckTimer { - pub fn new(expiry_time_seconds: u64) -> Self { - Self { - expiry_time_seconds, - start_time: std::time::Instant::now(), - } - } - } - - 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(); - } - } - - pub struct StdCheckTimerCreator { - pub check_limit_timeout_secs: u64, - } - - impl Default for StdCheckTimerCreator { - fn default() -> Self { - Self { - check_limit_timeout_secs: 5, - } - } - } - - impl CheckTimerProviderCreator for StdCheckTimerCreator { - type CheckTimer = StdCheckTimer; - - fn create_check_timer_provider(&self, timer_context: TimerContext) -> Self::CheckTimer { - match timer_context { - TimerContext::CheckLimit { - local_id: _, - remote_id: _, - entity_type: _, - } => StdCheckTimer::new(self.check_limit_timeout_secs), - TimerContext::NakActivity { - expiry_time_seconds, - } => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()), - TimerContext::PositiveAck { - expiry_time_seconds, - } => StdCheckTimer::new(Duration::from_secs_f32(expiry_time_seconds).as_secs()), - } - } - } -} - -/// 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(&self, remote_id: u64) -> Option<&RemoteEntityConfig>; - fn get_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(&self, remote_id: u64) -> Option<&RemoteEntityConfig> { - self.remote_cfg_table.get(&remote_id) - } - fn get_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 [FaultHandler], the appropriate fault handler callback -/// will be called depending on the [FaultHandlerCode]. -pub trait UserFaultHookProvider { - 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); -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct DummyFaultHook {} - -impl UserFaultHookProvider for DummyFaultHook { - 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 FaultHandler { - 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.. - pub user_hook: RefCell, -} - -impl FaultHandler { - 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: UserHandler) -> 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_hook: 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_hook.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 fault_handler: FaultHandler, -} - -impl LocalEntityConfig { - pub fn user_fault_hook_mut(&mut self) -> &mut RefCell { - &mut self.fault_handler.user_hook - } - - pub fn user_fault_hook(&self) -> &RefCell { - &self.fault_handler.user_hook - } -} - -pub trait PduSendProvider { - fn send_pdu( - &self, - pdu_type: PduType, - file_directive_type: Option, - raw_pdu: &[u8], - ) -> Result<(), PduError>; -} - -/// 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 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)] -pub(crate) mod tests { - use core::cell::RefCell; - - use alloc::{collections::VecDeque, string::String, vec::Vec}; - use spacepackets::{ - cfdp::{ - lv::Lv, - pdu::{ - eof::EofPdu, - file_data::FileDataPdu, - metadata::{MetadataGenericParams, MetadataPduCreator}, - CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, - }, - ChecksumType, ConditionCode, PduType, TransmissionMode, - }, - util::{UnsignedByteField, UnsignedByteFieldU16, UnsignedEnum}, - }; - - use crate::cfdp::PacketTarget; - - use super::{ - user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams}, - PacketInfo, PduSendProvider, RemoteEntityConfig, RemoteEntityConfigProvider, - StdRemoteEntityConfigProvider, TransactionId, UserFaultHookProvider, - }; - - pub const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); - - pub struct FileSegmentRecvdParamsNoSegMetadata { - pub id: TransactionId, - pub offset: u64, - pub length: usize, - } - - #[derive(Default)] - pub struct TestCfdpUser { - pub next_expected_seq_num: u64, - pub expected_full_src_name: String, - pub expected_full_dest_name: String, - pub expected_file_size: u64, - pub transaction_indication_call_count: u32, - pub eof_recvd_call_count: u32, - pub finished_indic_queue: VecDeque, - pub metadata_recv_queue: VecDeque, - pub file_seg_recvd_queue: VecDeque, - } - - impl TestCfdpUser { - pub 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(), - } - } - - pub 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)] - pub(crate) struct TestFaultHandler { - pub notice_of_suspension_queue: VecDeque<(TransactionId, ConditionCode, u64)>, - pub notice_of_cancellation_queue: VecDeque<(TransactionId, ConditionCode, u64)>, - pub abandoned_queue: VecDeque<(TransactionId, ConditionCode, u64)>, - pub ignored_queue: VecDeque<(TransactionId, ConditionCode, u64)>, - } - - impl UserFaultHookProvider for TestFaultHandler { - fn notice_of_suspension_cb( - &mut self, - transaction_id: TransactionId, - cond: ConditionCode, - progress: u64, - ) { - self.notice_of_suspension_queue - .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 - .push_back((transaction_id, cond, progress)) - } - - fn abandoned_cb( - &mut self, - transaction_id: TransactionId, - cond: ConditionCode, - progress: u64, - ) { - self.abandoned_queue - .push_back((transaction_id, cond, progress)) - } - - fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) { - self.ignored_queue - .push_back((transaction_id, cond, progress)) - } - } - - impl TestFaultHandler { - pub(crate) fn suspension_queue_empty(&self) -> bool { - self.notice_of_suspension_queue.is_empty() - } - pub(crate) fn cancellation_queue_empty(&self) -> bool { - self.notice_of_cancellation_queue.is_empty() - } - pub(crate) fn ignored_queue_empty(&self) -> bool { - self.ignored_queue.is_empty() - } - pub(crate) fn abandoned_queue_empty(&self) -> bool { - self.abandoned_queue.is_empty() - } - pub(crate) fn all_queues_empty(&self) -> bool { - self.suspension_queue_empty() - && self.cancellation_queue_empty() - && self.ignored_queue_empty() - && self.abandoned_queue_empty() - } - } - - pub struct SentPdu { - pub pdu_type: PduType, - pub file_directive_type: Option, - pub raw_pdu: Vec, - } - - #[derive(Default)] - pub struct TestCfdpSender { - pub packet_queue: RefCell>, - } - - impl PduSendProvider for TestCfdpSender { - fn send_pdu( - &self, - pdu_type: PduType, - file_directive_type: Option, - raw_pdu: &[u8], - ) -> Result<(), PduError> { - self.packet_queue.borrow_mut().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.borrow_mut().pop_front() - } - pub fn queue_empty(&self) -> bool { - self.packet_queue.borrow_mut().is_empty() - } - } - - pub fn basic_remote_cfg_table( - dest_id: impl Into, - crc_on_transmission_by_default: bool, - ) -> StdRemoteEntityConfigProvider { - let mut table = StdRemoteEntityConfigProvider::default(); - let remote_entity_cfg = RemoteEntityConfig::new_with_default_values( - dest_id.into(), - 1024, - 1024, - true, - crc_on_transmission_by_default, - TransmissionMode::Unacknowledged, - ChecksumType::Crc32, - ); - table.add_config(&remote_entity_cfg); - table - } - - 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/request.rs b/satrs/src/cfdp/request.rs deleted file mode 100644 index d23fed6..0000000 --- a/satrs/src/cfdp/request.rs +++ /dev/null @@ -1,644 +0,0 @@ -use spacepackets::{ - cfdp::{ - tlv::{GenericTlv, Tlv, TlvType}, - SegmentationControl, TransmissionMode, - }, - util::UnsignedByteField, -}; - -#[cfg(feature = "alloc")] -pub use alloc_mod::*; - -#[derive(Debug, PartialEq, Eq)] -pub struct FilePathTooLarge(pub usize); - -/// This trait is an abstraction for different Put Request structures which can be used -/// by Put Request consumers. -pub trait ReadablePutRequest { - fn destination_id(&self) -> UnsignedByteField; - fn source_file(&self) -> Option<&str>; - fn dest_file(&self) -> Option<&str>; - fn trans_mode(&self) -> Option; - fn closure_requested(&self) -> Option; - fn seg_ctrl(&self) -> Option; - - fn msgs_to_user(&self) -> Option>; - fn fault_handler_overrides(&self) -> Option>; - fn flow_label(&self) -> Option; - fn fs_requests(&self) -> Option>; -} - -#[derive(Debug, PartialEq, Eq)] -pub struct PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> { - pub destination_id: UnsignedByteField, - source_file: Option<&'src_file str>, - dest_file: Option<&'dest_file str>, - pub trans_mode: Option, - pub closure_requested: Option, - pub seg_ctrl: Option, - pub msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>, - pub fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>, - pub flow_label: Option>, - pub fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>, -} - -impl<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> - PutRequest<'src_file, 'dest_file, 'msgs_to_user, 'fh_ovrds, 'flow_label, 'fs_requests> -{ - #[allow(clippy::too_many_arguments)] - pub fn new( - destination_id: UnsignedByteField, - source_file: Option<&'src_file str>, - dest_file: Option<&'dest_file str>, - trans_mode: Option, - closure_requested: Option, - seg_ctrl: Option, - msgs_to_user: Option<&'msgs_to_user [Tlv<'msgs_to_user>]>, - fault_handler_overrides: Option<&'fh_ovrds [Tlv<'fh_ovrds>]>, - flow_label: Option>, - fs_requests: Option<&'fs_requests [Tlv<'fs_requests>]>, - ) -> Result { - generic_path_checks(source_file, dest_file)?; - Ok(Self { - destination_id, - source_file, - dest_file, - trans_mode, - closure_requested, - seg_ctrl, - msgs_to_user, - fault_handler_overrides, - flow_label, - fs_requests, - }) - } -} - -impl ReadablePutRequest for PutRequest<'_, '_, '_, '_, '_, '_> { - fn destination_id(&self) -> UnsignedByteField { - self.destination_id - } - - fn source_file(&self) -> Option<&str> { - self.source_file - } - - fn dest_file(&self) -> Option<&str> { - self.dest_file - } - - fn trans_mode(&self) -> Option { - self.trans_mode - } - - fn closure_requested(&self) -> Option { - self.closure_requested - } - - fn seg_ctrl(&self) -> Option { - self.seg_ctrl - } - - fn msgs_to_user(&self) -> Option> { - if let Some(msgs_to_user) = self.msgs_to_user { - return Some(msgs_to_user.iter().copied()); - } - None - } - - fn fault_handler_overrides(&self) -> Option> { - if let Some(fh_overrides) = self.fault_handler_overrides { - return Some(fh_overrides.iter().copied()); - } - None - } - - fn flow_label(&self) -> Option { - self.flow_label - } - - fn fs_requests(&self) -> Option> { - if let Some(fs_requests) = self.msgs_to_user { - return Some(fs_requests.iter().copied()); - } - None - } -} - -pub fn generic_path_checks( - source_file: Option<&str>, - dest_file: Option<&str>, -) -> Result<(), FilePathTooLarge> { - if let Some(src_file) = source_file { - if src_file.len() > u8::MAX as usize { - return Err(FilePathTooLarge(src_file.len())); - } - } - if let Some(dest_file) = dest_file { - if dest_file.len() > u8::MAX as usize { - return Err(FilePathTooLarge(dest_file.len())); - } - } - Ok(()) -} - -impl<'src_file, 'dest_file> PutRequest<'src_file, 'dest_file, 'static, 'static, 'static, 'static> { - pub fn new_regular_request( - dest_id: UnsignedByteField, - source_file: &'src_file str, - dest_file: &'dest_file str, - trans_mode: Option, - closure_requested: Option, - ) -> Result { - generic_path_checks(Some(source_file), Some(dest_file))?; - Ok(Self { - destination_id: dest_id, - source_file: Some(source_file), - dest_file: Some(dest_file), - trans_mode, - closure_requested, - seg_ctrl: None, - msgs_to_user: None, - fault_handler_overrides: None, - flow_label: None, - fs_requests: None, - }) - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct TlvWithInvalidType(pub(crate) ()); - -impl<'msgs_to_user> PutRequest<'static, 'static, 'msgs_to_user, 'static, 'static, 'static> { - pub fn new_msgs_to_user_only( - dest_id: UnsignedByteField, - msgs_to_user: &'msgs_to_user [Tlv<'msgs_to_user>], - ) -> Result { - Ok(Self { - destination_id: dest_id, - source_file: None, - dest_file: None, - trans_mode: None, - closure_requested: None, - seg_ctrl: None, - msgs_to_user: Some(msgs_to_user), - fault_handler_overrides: None, - flow_label: None, - fs_requests: None, - }) - } - - /// Uses [generic_tlv_list_type_check] to check the TLV type validity of all TLV fields. - pub fn check_tlv_type_validities(&self) -> bool { - generic_tlv_list_type_check(self.msgs_to_user, TlvType::MsgToUser); - if let Some(flow_label) = &self.flow_label { - if flow_label.tlv_type().is_none() { - return false; - } - if flow_label.tlv_type().unwrap() != TlvType::FlowLabel { - return false; - } - } - generic_tlv_list_type_check(self.fault_handler_overrides, TlvType::FaultHandler); - generic_tlv_list_type_check(self.fs_requests, TlvType::FilestoreRequest); - true - } -} - -pub fn generic_tlv_list_type_check( - opt_tlvs: Option<&[TlvProvider]>, - tlv_type: TlvType, -) -> bool { - if let Some(tlvs) = opt_tlvs { - for tlv in tlvs { - if tlv.tlv_type().is_none() { - return false; - } - if tlv.tlv_type().unwrap() != tlv_type { - return false; - } - } - } - true -} - -#[cfg(feature = "alloc")] -pub mod alloc_mod { - use core::str::Utf8Error; - - use super::*; - use alloc::string::ToString; - use spacepackets::{ - cfdp::tlv::{msg_to_user::MsgToUserTlv, ReadableTlv, TlvOwned, WritableTlv}, - ByteConversionError, - }; - - /// Owned variant of [PutRequest] with no lifetimes which is also [Clone]able. - #[derive(Debug, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] - pub struct PutRequestOwned { - pub destination_id: UnsignedByteField, - source_file: Option, - dest_file: Option, - pub trans_mode: Option, - pub closure_requested: Option, - pub seg_ctrl: Option, - pub msgs_to_user: Option>, - pub fault_handler_overrides: Option>, - pub flow_label: Option, - pub fs_requests: Option>, - } - - impl PutRequestOwned { - pub fn new_regular_request( - dest_id: UnsignedByteField, - source_file: &str, - dest_file: &str, - trans_mode: Option, - closure_requested: Option, - ) -> Result { - if source_file.len() > u8::MAX as usize { - return Err(FilePathTooLarge(source_file.len())); - } - if dest_file.len() > u8::MAX as usize { - return Err(FilePathTooLarge(dest_file.len())); - } - Ok(Self { - destination_id: dest_id, - source_file: Some(source_file.to_string()), - dest_file: Some(dest_file.to_string()), - trans_mode, - closure_requested, - seg_ctrl: None, - msgs_to_user: None, - fault_handler_overrides: None, - flow_label: None, - fs_requests: None, - }) - } - - pub fn new_msgs_to_user_only( - dest_id: UnsignedByteField, - msgs_to_user: &[MsgToUserTlv<'_>], - ) -> Result { - Ok(Self { - destination_id: dest_id, - source_file: None, - dest_file: None, - trans_mode: None, - closure_requested: None, - seg_ctrl: None, - msgs_to_user: Some(msgs_to_user.iter().map(|msg| msg.tlv.to_owned()).collect()), - fault_handler_overrides: None, - flow_label: None, - fs_requests: None, - }) - } - - /// Uses [generic_tlv_list_type_check] to check the TLV type validity of all TLV fields. - pub fn check_tlv_type_validities(&self) -> bool { - generic_tlv_list_type_check(self.msgs_to_user.as_deref(), TlvType::MsgToUser); - if let Some(flow_label) = &self.flow_label { - if flow_label.tlv_type().is_none() { - return false; - } - if flow_label.tlv_type().unwrap() != TlvType::FlowLabel { - return false; - } - } - generic_tlv_list_type_check( - self.fault_handler_overrides.as_deref(), - TlvType::FaultHandler, - ); - generic_tlv_list_type_check(self.fs_requests.as_deref(), TlvType::FilestoreRequest); - true - } - } - - impl From> for PutRequestOwned { - fn from(req: PutRequest<'_, '_, '_, '_, '_, '_>) -> Self { - Self { - destination_id: req.destination_id, - source_file: req.source_file.map(|s| s.into()), - dest_file: req.dest_file.map(|s| s.into()), - trans_mode: req.trans_mode, - closure_requested: req.closure_requested, - seg_ctrl: req.seg_ctrl, - msgs_to_user: req - .msgs_to_user - .map(|msgs_to_user| msgs_to_user.iter().map(|msg| msg.to_owned()).collect()), - fault_handler_overrides: req - .msgs_to_user - .map(|fh_overides| fh_overides.iter().map(|msg| msg.to_owned()).collect()), - flow_label: req - .flow_label - .map(|flow_label_tlv| flow_label_tlv.to_owned()), - fs_requests: req - .fs_requests - .map(|fs_requests| fs_requests.iter().map(|msg| msg.to_owned()).collect()), - } - } - } - - impl ReadablePutRequest for PutRequestOwned { - fn destination_id(&self) -> UnsignedByteField { - self.destination_id - } - - fn source_file(&self) -> Option<&str> { - self.source_file.as_deref() - } - - fn dest_file(&self) -> Option<&str> { - self.dest_file.as_deref() - } - - fn trans_mode(&self) -> Option { - self.trans_mode - } - - fn closure_requested(&self) -> Option { - self.closure_requested - } - - fn seg_ctrl(&self) -> Option { - self.seg_ctrl - } - - fn msgs_to_user(&self) -> Option> { - if let Some(msgs_to_user) = &self.msgs_to_user { - return Some(msgs_to_user.iter().map(|tlv_owned| tlv_owned.as_tlv())); - } - None - } - - fn fault_handler_overrides(&self) -> Option> { - if let Some(fh_overrides) = &self.fault_handler_overrides { - return Some(fh_overrides.iter().map(|tlv_owned| tlv_owned.as_tlv())); - } - None - } - - fn flow_label(&self) -> Option { - self.flow_label.as_ref().map(|tlv| tlv.as_tlv()) - } - - fn fs_requests(&self) -> Option> { - if let Some(requests) = &self.fs_requests { - return Some(requests.iter().map(|tlv_owned| tlv_owned.as_tlv())); - } - None - } - } - - pub struct StaticPutRequestFields { - pub destination_id: UnsignedByteField, - /// Static buffer to store source file path. - pub source_file_buf: [u8; u8::MAX as usize], - /// Current source path length. - pub source_file_len: usize, - /// Static buffer to store dest file path. - pub dest_file_buf: [u8; u8::MAX as usize], - /// Current destination path length. - pub dest_file_len: usize, - pub trans_mode: Option, - pub closure_requested: Option, - pub seg_ctrl: Option, - } - - impl Default for StaticPutRequestFields { - fn default() -> Self { - Self { - destination_id: UnsignedByteField::new(0, 0), - source_file_buf: [0; u8::MAX as usize], - source_file_len: Default::default(), - dest_file_buf: [0; u8::MAX as usize], - dest_file_len: Default::default(), - trans_mode: Default::default(), - closure_requested: Default::default(), - seg_ctrl: Default::default(), - } - } - } - - impl StaticPutRequestFields { - pub fn clear(&mut self) { - self.destination_id = UnsignedByteField::new(0, 0); - self.source_file_len = 0; - self.dest_file_len = 0; - self.trans_mode = None; - self.closure_requested = None; - self.seg_ctrl = None; - } - } - - /// This is a put request cache structure which can be used to cache [ReadablePutRequest]s - /// without requiring run-time allocation. The user must specify the static buffer sizes used - /// to store TLVs or list of TLVs. - pub struct StaticPutRequestCacher { - pub static_fields: StaticPutRequestFields, - opts_buf: alloc::vec::Vec, - opts_len: usize, // fs_request_start_end_pos: Option<(usize, usize)> - } - - impl StaticPutRequestCacher { - pub fn new(max_len_opts_buf: usize) -> Self { - Self { - static_fields: StaticPutRequestFields::default(), - opts_buf: alloc::vec![0; max_len_opts_buf], - opts_len: 0, - } - } - - pub fn set( - &mut self, - put_request: &impl ReadablePutRequest, - ) -> Result<(), ByteConversionError> { - self.static_fields.destination_id = put_request.destination_id(); - if let Some(source_file) = put_request.source_file() { - if source_file.len() > u8::MAX as usize { - return Err(ByteConversionError::ToSliceTooSmall { - found: self.static_fields.source_file_buf.len(), - expected: source_file.len(), - }); - } - self.static_fields.source_file_buf[..source_file.len()] - .copy_from_slice(source_file.as_bytes()); - self.static_fields.source_file_len = source_file.len(); - } - if let Some(dest_file) = put_request.dest_file() { - if dest_file.len() > u8::MAX as usize { - return Err(ByteConversionError::ToSliceTooSmall { - found: self.static_fields.source_file_buf.len(), - expected: dest_file.len(), - }); - } - self.static_fields.dest_file_buf[..dest_file.len()] - .copy_from_slice(dest_file.as_bytes()); - self.static_fields.dest_file_len = dest_file.len(); - } - self.static_fields.trans_mode = put_request.trans_mode(); - self.static_fields.closure_requested = put_request.closure_requested(); - self.static_fields.seg_ctrl = put_request.seg_ctrl(); - let mut current_idx = 0; - let mut store_tlv = |tlv: &Tlv| { - if current_idx + tlv.len_full() > self.opts_buf.len() { - return Err(ByteConversionError::ToSliceTooSmall { - found: self.opts_buf.len(), - expected: current_idx + tlv.len_full(), - }); - } - // We checked the buffer lengths, so this should never fail. - tlv.write_to_bytes(&mut self.opts_buf[current_idx..current_idx + tlv.len_full()]) - .unwrap(); - current_idx += tlv.len_full(); - Ok(()) - }; - if let Some(fs_req) = put_request.fs_requests() { - for fs_req in fs_req { - store_tlv(&fs_req)?; - } - } - if let Some(msgs_to_user) = put_request.msgs_to_user() { - for msg_to_user in msgs_to_user { - store_tlv(&msg_to_user)?; - } - } - self.opts_len = current_idx; - Ok(()) - } - - pub fn has_source_file(&self) -> bool { - self.static_fields.source_file_len > 0 - } - - pub fn has_dest_file(&self) -> bool { - self.static_fields.dest_file_len > 0 - } - - pub fn source_file(&self) -> Result<&str, Utf8Error> { - core::str::from_utf8( - &self.static_fields.source_file_buf[0..self.static_fields.source_file_len], - ) - } - - pub fn dest_file(&self) -> Result<&str, Utf8Error> { - core::str::from_utf8( - &self.static_fields.dest_file_buf[0..self.static_fields.dest_file_len], - ) - } - - pub fn opts_len(&self) -> usize { - self.opts_len - } - - pub fn opts_slice(&self) -> &[u8] { - &self.opts_buf[0..self.opts_len] - } - - /// This clears the cacher structure. This is a cheap operation because it only - /// sets [Option]al values to [None] and the length of stores TLVs to 0. - /// - /// Please note that this method will not set the values in the buffer to 0. - pub fn clear(&mut self) { - self.static_fields.clear(); - self.opts_len = 0; - } - } -} - -#[cfg(test)] -mod tests { - use spacepackets::util::UbfU16; - - use super::*; - - pub const DEST_ID: UbfU16 = UbfU16::new(5); - - #[test] - fn test_put_request_basic() { - let src_file = "/tmp/hello.txt"; - let dest_file = "/tmp/hello2.txt"; - let put_request = - PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) - .unwrap(); - assert_eq!(put_request.source_file(), Some(src_file)); - assert_eq!(put_request.dest_file(), Some(dest_file)); - assert_eq!(put_request.destination_id(), DEST_ID.into()); - assert_eq!(put_request.seg_ctrl(), None); - assert_eq!(put_request.closure_requested(), None); - assert_eq!(put_request.trans_mode(), None); - assert!(put_request.fs_requests().is_none()); - assert!(put_request.msgs_to_user().is_none()); - assert!(put_request.fault_handler_overrides().is_none()); - assert!(put_request.flow_label().is_none()); - } - - #[test] - fn test_put_request_owned_basic() { - let src_file = "/tmp/hello.txt"; - let dest_file = "/tmp/hello2.txt"; - let put_request = - PutRequestOwned::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) - .unwrap(); - assert_eq!(put_request.source_file(), Some(src_file)); - assert_eq!(put_request.dest_file(), Some(dest_file)); - assert_eq!(put_request.destination_id(), DEST_ID.into()); - assert_eq!(put_request.seg_ctrl(), None); - assert_eq!(put_request.closure_requested(), None); - assert_eq!(put_request.trans_mode(), None); - assert!(put_request.flow_label().is_none()); - assert!(put_request.fs_requests().is_none()); - assert!(put_request.msgs_to_user().is_none()); - assert!(put_request.fault_handler_overrides().is_none()); - assert!(put_request.flow_label().is_none()); - let put_request_cloned = put_request.clone(); - assert_eq!(put_request, put_request_cloned); - } - - #[test] - fn test_put_request_cacher_basic() { - let put_request_cached = StaticPutRequestCacher::new(128); - assert_eq!(put_request_cached.static_fields.source_file_len, 0); - assert_eq!(put_request_cached.static_fields.dest_file_len, 0); - assert_eq!(put_request_cached.opts_len(), 0); - assert_eq!(put_request_cached.opts_slice(), &[]); - } - - #[test] - fn test_put_request_cacher_set() { - let mut put_request_cached = StaticPutRequestCacher::new(128); - let src_file = "/tmp/hello.txt"; - let dest_file = "/tmp/hello2.txt"; - let put_request = - PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) - .unwrap(); - put_request_cached.set(&put_request).unwrap(); - assert_eq!( - put_request_cached.static_fields.source_file_len, - src_file.len() - ); - assert_eq!( - put_request_cached.static_fields.dest_file_len, - dest_file.len() - ); - assert_eq!(put_request_cached.source_file().unwrap(), src_file); - assert_eq!(put_request_cached.dest_file().unwrap(), dest_file); - assert_eq!(put_request_cached.opts_len(), 0); - } - - #[test] - fn test_put_request_cacher_set_and_clear() { - let mut put_request_cached = StaticPutRequestCacher::new(128); - let src_file = "/tmp/hello.txt"; - let dest_file = "/tmp/hello2.txt"; - let put_request = - PutRequest::new_regular_request(DEST_ID.into(), src_file, dest_file, None, None) - .unwrap(); - put_request_cached.set(&put_request).unwrap(); - put_request_cached.clear(); - assert_eq!(put_request_cached.static_fields.source_file_len, 0); - assert_eq!(put_request_cached.static_fields.dest_file_len, 0); - assert_eq!(put_request_cached.opts_len(), 0); - } -} diff --git a/satrs/src/cfdp/source.rs b/satrs/src/cfdp/source.rs deleted file mode 100644 index 84aea47..0000000 --- a/satrs/src/cfdp/source.rs +++ /dev/null @@ -1,1047 +0,0 @@ -use core::{cell::RefCell, ops::ControlFlow, str::Utf8Error}; - -use spacepackets::{ - cfdp::{ - lv::Lv, - pdu::{ - eof::EofPdu, - file_data::FileDataPduCreatorWithReservedDatafield, - finished::{DeliveryCode, FileStatus, FinishedPduReader}, - metadata::{MetadataGenericParams, MetadataPduCreator}, - CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, - }, - ConditionCode, Direction, LargeFileFlag, PduType, SegmentMetadataFlag, SegmentationControl, - TransmissionMode, - }, - util::{UnsignedByteField, UnsignedEnum}, - ByteConversionError, -}; - -use crate::seq_count::SequenceCountProvider; - -use super::{ - filestore::{FilestoreError, VirtualFilestore}, - request::{ReadablePutRequest, StaticPutRequestCacher}, - user::{CfdpUser, TransactionFinishedParams}, - LocalEntityConfig, PacketInfo, PacketTarget, PduSendProvider, RemoteEntityConfig, - RemoteEntityConfigProvider, State, TransactionId, UserFaultHookProvider, -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TransactionStep { - Idle = 0, - TransactionStart = 1, - SendingMetadata = 3, - SendingFileData = 4, - /// Re-transmitting missing packets in acknowledged mode - Retransmitting = 5, - SendingEof = 6, - WaitingForEofAck = 7, - WaitingForFinished = 8, - // SendingAckOfFinished = 9, - NoticeOfCompletion = 10, -} - -#[derive(Default)] -pub struct FileParams { - pub progress: u64, - pub segment_len: u64, - pub crc32: u32, - pub metadata_only: bool, - pub file_size: u64, - pub empty_file: bool, -} - -pub struct StateHelper { - state: super::State, - step: TransactionStep, - num_packets_ready: u32, -} - -#[derive(Debug)] -pub struct FinishedParams { - condition_code: ConditionCode, - delivery_code: DeliveryCode, - file_status: FileStatus, -} - -#[derive(Debug, derive_new::new)] -pub struct TransferState { - transaction_id: TransactionId, - remote_cfg: RemoteEntityConfig, - transmission_mode: super::TransmissionMode, - closure_requested: bool, - cond_code_eof: Option, - finished_params: Option, -} - -impl Default for StateHelper { - fn default() -> Self { - Self { - state: super::State::Idle, - step: TransactionStep::Idle, - num_packets_ready: 0, - } - } -} - -#[derive(Debug, thiserror::Error)] -pub enum SourceError { - #[error("can not process packet type {pdu_type:?} with directive type {directive_type:?}")] - CantProcessPacketType { - pdu_type: PduType, - directive_type: Option, - }, - #[error("unexpected PDU")] - UnexpectedPdu { - pdu_type: PduType, - directive_type: Option, - }, - #[error("source handler is already busy with put request")] - PutRequestAlreadyActive, - #[error("error caching put request")] - PutRequestCaching(ByteConversionError), - #[error("filestore error: {0}")] - FilestoreError(#[from] FilestoreError), - #[error("source file does not have valid UTF8 format: {0}")] - SourceFileNotValidUtf8(Utf8Error), - #[error("destination file does not have valid UTF8 format: {0}")] - DestFileNotValidUtf8(Utf8Error), - #[error("error related to PDU creation")] - Pdu(#[from] PduError), -} - -#[derive(Debug, thiserror::Error)] -pub enum PutRequestError { - #[error("error caching put request: {0}")] - Storage(#[from] ByteConversionError), - #[error("already busy with put request")] - AlreadyBusy, - #[error("no remote entity configuration found for {0:?}")] - NoRemoteCfgFound(UnsignedByteField), -} - -pub struct SourceHandler< - PduSender: PduSendProvider, - UserFaultHook: UserFaultHookProvider, - Vfs: VirtualFilestore, - RemoteCfgTable: RemoteEntityConfigProvider, - SeqCountProvider: SequenceCountProvider, -> { - local_cfg: LocalEntityConfig, - pdu_sender: PduSender, - pdu_and_cksum_buffer: RefCell>, - put_request_cacher: StaticPutRequestCacher, - remote_cfg_table: RemoteCfgTable, - vfs: Vfs, - state_helper: StateHelper, - // Transfer related state information - tstate: Option, - // File specific transfer fields - fparams: FileParams, - // PDU configuration is cached so it can be re-used for all PDUs generated for file transfers. - pdu_conf: CommonPduConfig, - seq_count_provider: SeqCountProvider, -} - -impl< - PduSender: PduSendProvider, - UserFaultHook: UserFaultHookProvider, - Vfs: VirtualFilestore, - RemoteCfgTable: RemoteEntityConfigProvider, - SeqCountProvider: SequenceCountProvider, - > SourceHandler -{ - pub fn new( - cfg: LocalEntityConfig, - pdu_sender: PduSender, - vfs: Vfs, - put_request_cacher: StaticPutRequestCacher, - pdu_and_cksum_buf_size: usize, - remote_cfg_table: RemoteCfgTable, - seq_count_provider: SeqCountProvider, - ) -> Self { - Self { - local_cfg: cfg, - remote_cfg_table, - pdu_sender, - pdu_and_cksum_buffer: RefCell::new(alloc::vec![0; pdu_and_cksum_buf_size]), - vfs, - put_request_cacher, - state_helper: Default::default(), - tstate: Default::default(), - fparams: Default::default(), - pdu_conf: Default::default(), - seq_count_provider, - } - } - - /// This is the core function to drive the source handler. It is also used to insert - /// packets into the source 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. - /// - /// The function returns the number of sent PDU packets on success. - 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_helper.state { - super::State::Idle => todo!(), - super::State::Busy => self.fsm_busy(cfdp_user), - super::State::Suspended => todo!(), - } - } - - fn insert_packet( - &mut self, - cfdp_user: &mut impl CfdpUser, - packet_info: &PacketInfo, - ) -> Result<(), SourceError> { - if packet_info.target() != PacketTarget::SourceEntity { - // Unwrap is okay here, a PacketInfo for a file data PDU should always have the - // destination as the target. - return Err(SourceError::CantProcessPacketType { - pdu_type: packet_info.pdu_type(), - directive_type: packet_info.pdu_directive(), - }); - } - if packet_info.pdu_type() == PduType::FileData { - // The [PacketInfo] API should ensure that file data PDUs can not be passed - // into a source entity, so this should never happen. - return Err(SourceError::UnexpectedPdu { - pdu_type: PduType::FileData, - directive_type: None, - }); - } - // Unwrap is okay here, the [PacketInfo] API should ensure that the directive type is - // always a valid value. - match packet_info - .pdu_directive() - .expect("PDU directive type unexpectedly not set") - { - FileDirectiveType::FinishedPdu => self.handle_finished_pdu(packet_info)?, - FileDirectiveType::NakPdu => self.handle_nak_pdu(), - FileDirectiveType::KeepAlivePdu => self.handle_keep_alive_pdu(), - FileDirectiveType::AckPdu => todo!("acknowledged mode not implemented yet"), - FileDirectiveType::EofPdu - | FileDirectiveType::PromptPdu - | FileDirectiveType::MetadataPdu => { - return Err(SourceError::CantProcessPacketType { - pdu_type: packet_info.pdu_type(), - directive_type: packet_info.pdu_directive(), - }); - } - } - Ok(()) - } - - pub fn put_request( - &mut self, - put_request: &impl ReadablePutRequest, - ) -> Result<(), PutRequestError> { - if self.state_helper.state != super::State::Idle { - return Err(PutRequestError::AlreadyBusy); - } - self.put_request_cacher.set(put_request)?; - self.state_helper.state = super::State::Busy; - let remote_cfg = self.remote_cfg_table.get( - self.put_request_cacher - .static_fields - .destination_id - .value_const(), - ); - if remote_cfg.is_none() { - // TODO: Specific error. - return Err(PutRequestError::NoRemoteCfgFound( - self.put_request_cacher.static_fields.destination_id, - )); - } - let remote_cfg = remote_cfg.unwrap(); - self.state_helper.num_packets_ready = 0; - let transmission_mode = if self.put_request_cacher.static_fields.trans_mode.is_some() { - self.put_request_cacher.static_fields.trans_mode.unwrap() - } else { - remote_cfg.default_transmission_mode - }; - let closure_requested = if self - .put_request_cacher - .static_fields - .closure_requested - .is_some() - { - self.put_request_cacher - .static_fields - .closure_requested - .unwrap() - } else { - remote_cfg.closure_requested_by_default - }; - self.tstate = Some(TransferState::new( - TransactionId::new( - self.local_cfg().id, - UnsignedByteField::new( - SeqCountProvider::MAX_BIT_WIDTH / 8, - self.seq_count_provider.get_and_increment().into(), - ), - ), - *remote_cfg, - transmission_mode, - closure_requested, - None, - None, - )); - Ok(()) - } - - #[inline] - pub fn transmission_mode(&self) -> Option { - self.tstate.as_ref().map(|v| v.transmission_mode) - } - - fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result { - let mut sent_packets = 0; - if self.state_helper.step == TransactionStep::Idle { - self.state_helper.step = TransactionStep::TransactionStart; - } - if self.state_helper.step == TransactionStep::TransactionStart { - self.handle_transaction_start(cfdp_user)?; - self.state_helper.step = TransactionStep::SendingMetadata; - } - if self.state_helper.step == TransactionStep::SendingMetadata { - self.prepare_and_send_metadata_pdu()?; - self.state_helper.step = TransactionStep::SendingFileData; - sent_packets += 1; - // return Ok(1); - } - if self.state_helper.step == TransactionStep::SendingFileData { - if let ControlFlow::Break(result) = self.file_data_fsm()? { - return Ok(result); - } - } - if self.state_helper.step == TransactionStep::SendingEof { - self.eof_fsm(cfdp_user)?; - sent_packets += 1; - // return Ok(1); - } - if self.state_helper.step == TransactionStep::WaitingForFinished { - /* - def _handle_wait_for_finish(self): - if ( - self.transmission_mode == TransmissionMode.ACKNOWLEDGED - and self.__handle_retransmission() - ): - return - if ( - self._inserted_pdu.pdu is None - or self._inserted_pdu.pdu_directive_type is None - or self._inserted_pdu.pdu_directive_type != DirectiveType.FINISHED_PDU - ): - if self._params.check_timer is not None: - if self._params.check_timer.timed_out(): - self._declare_fault(ConditionCode.CHECK_LIMIT_REACHED) - return - finished_pdu = self._inserted_pdu.to_finished_pdu() - self._inserted_pdu.pdu = None - self._params.finished_params = finished_pdu.finished_params - if self.transmission_mode == TransmissionMode.ACKNOWLEDGED: - self._prepare_finished_ack_packet(finished_pdu.condition_code) - self.states.step = TransactionStep.SENDING_ACK_OF_FINISHED - else: - self.states.step = TransactionStep.NOTICE_OF_COMPLETION - */ - } - if self.state_helper.step == TransactionStep::NoticeOfCompletion { - self.notice_of_completion(cfdp_user); - self.reset(); - /* - def _notice_of_completion(self): - if self.cfg.indication_cfg.transaction_finished_indication_required: - assert self._params.transaction_id is not None - # This happens for unacknowledged file copy operation with no closure. - if self._params.finished_params is None: - self._params.finished_params = FinishedParams( - condition_code=ConditionCode.NO_ERROR, - delivery_code=DeliveryCode.DATA_COMPLETE, - file_status=FileStatus.FILE_STATUS_UNREPORTED, - ) - indication_params = TransactionFinishedParams( - transaction_id=self._params.transaction_id, - finished_params=self._params.finished_params, - ) - self.user.transaction_finished_indication(indication_params) - # Transaction finished - self.reset() - */ - } - Ok(sent_packets) - } - - fn eof_fsm(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), SourceError> { - let tstate = self.tstate.as_ref().unwrap(); - let checksum = self.vfs.calculate_checksum( - self.put_request_cacher.source_file().unwrap(), - tstate.remote_cfg.default_crc_type, - self.pdu_and_cksum_buffer.get_mut(), - )?; - self.prepare_and_send_eof_pdu(checksum)?; - let tstate = self.tstate.as_ref().unwrap(); - if self.local_cfg.indication_cfg.eof_sent { - cfdp_user.eof_sent_indication(&tstate.transaction_id); - } - if tstate.transmission_mode == TransmissionMode::Unacknowledged { - if tstate.closure_requested { - // TODO: Check timer handling. - self.state_helper.step = TransactionStep::WaitingForFinished; - } else { - self.state_helper.step = TransactionStep::NoticeOfCompletion; - } - } else { - // TODO: Start positive ACK procedure. - } - /* - if self.cfg.indication_cfg.eof_sent_indication_required: - assert self._params.transaction_id is not None - self.user.eof_sent_indication(self._params.transaction_id) - if self.transmission_mode == TransmissionMode.UNACKNOWLEDGED: - if self._params.closure_requested: - assert self._params.remote_cfg is not None - self._params.check_timer = ( - self.check_timer_provider.provide_check_timer( - local_entity_id=self.cfg.local_entity_id, - remote_entity_id=self._params.remote_cfg.entity_id, - entity_type=EntityType.SENDING, - ) - ) - self.states.step = TransactionStep.WAITING_FOR_FINISHED - else: - self.states.step = TransactionStep.NOTICE_OF_COMPLETION - else: - self._start_positive_ack_procedure() - */ - Ok(()) - } - - fn handle_transaction_start( - &mut self, - cfdp_user: &mut impl CfdpUser, - ) -> Result<(), SourceError> { - let tstate = self - .tstate - .as_ref() - .expect("transfer state unexpectedly empty"); - if !self.put_request_cacher.has_source_file() { - self.fparams.metadata_only = true; - } else { - let source_file = self - .put_request_cacher - .source_file() - .map_err(SourceError::SourceFileNotValidUtf8)?; - if !self.vfs.exists(source_file)? { - return Err(SourceError::FilestoreError( - FilestoreError::FileDoesNotExist, - )); - } - // We expect the destination file path to consist of valid UTF-8 characters as well. - self.put_request_cacher - .dest_file() - .map_err(SourceError::DestFileNotValidUtf8)?; - let file_size = self.vfs.file_size(source_file)?; - if file_size > u32::MAX as u64 { - self.pdu_conf.file_flag = LargeFileFlag::Large - } else { - if file_size == 0 { - self.fparams.empty_file = true; - } - self.pdu_conf.file_flag = LargeFileFlag::Normal - } - } - // Both the source entity and destination entity ID field must have the same size. - // We use the larger of either the Put Request destination ID or the local entity ID - // as the size for the new entity IDs. - let larger_entity_width = core::cmp::max( - self.local_cfg.id.size(), - self.put_request_cacher.static_fields.destination_id.size(), - ); - let create_id = |cached_id: &UnsignedByteField| { - if larger_entity_width != cached_id.size() { - UnsignedByteField::new(larger_entity_width, cached_id.value_const()) - } else { - *cached_id - } - }; - self.pdu_conf - .set_source_and_dest_id( - create_id(&self.local_cfg.id), - create_id(&self.put_request_cacher.static_fields.destination_id), - ) - .unwrap(); - // Set up other PDU configuration fields. - self.pdu_conf.direction = Direction::TowardsReceiver; - self.pdu_conf.crc_flag = tstate.remote_cfg.crc_on_transmission_by_default.into(); - self.pdu_conf.transaction_seq_num = *tstate.transaction_id.seq_num(); - self.pdu_conf.trans_mode = tstate.transmission_mode; - - cfdp_user.transaction_indication(&tstate.transaction_id); - Ok(()) - } - - fn prepare_and_send_metadata_pdu(&mut self) -> Result<(), PduError> { - let tstate = self - .tstate - .as_ref() - .expect("transfer state unexpectedly empty"); - let metadata_params = MetadataGenericParams::new( - tstate.closure_requested, - tstate.remote_cfg.default_crc_type, - self.fparams.file_size, - ); - if self.fparams.metadata_only { - let metadata_pdu = MetadataPduCreator::new( - PduHeader::new_no_file_data(self.pdu_conf, 0), - metadata_params, - Lv::new_empty(), - Lv::new_empty(), - self.put_request_cacher.opts_slice(), - ); - return self.pdu_send_helper(&metadata_pdu); - } - let metadata_pdu = MetadataPduCreator::new( - PduHeader::new_no_file_data(self.pdu_conf, 0), - metadata_params, - Lv::new_from_str(self.put_request_cacher.source_file().unwrap()).unwrap(), - Lv::new_from_str(self.put_request_cacher.dest_file().unwrap()).unwrap(), - self.put_request_cacher.opts_slice(), - ); - self.pdu_send_helper(&metadata_pdu) - } - - fn file_data_fsm(&mut self) -> Result, SourceError> { - if self.transmission_mode().unwrap() == super::TransmissionMode::Acknowledged { - // TODO: Handle re-transmission - } - if !self.fparams.metadata_only - && self.fparams.progress < self.fparams.file_size - && self.send_progressing_file_data_pdu()? - { - return Ok(ControlFlow::Break(1)); - } - if self.fparams.empty_file || self.fparams.progress >= self.fparams.file_size { - // EOF is still expected. - self.state_helper.step = TransactionStep::SendingEof; - self.tstate.as_mut().unwrap().cond_code_eof = Some(ConditionCode::NoError); - } else if self.fparams.metadata_only { - // Special case: Metadata Only, no EOF required. - if self.tstate.as_ref().unwrap().closure_requested { - self.state_helper.step = TransactionStep::WaitingForFinished; - } else { - self.state_helper.step = TransactionStep::NoticeOfCompletion; - } - } - Ok(ControlFlow::Continue(())) - } - - fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) { - let tstate = self.tstate.as_ref().unwrap(); - if self.local_cfg.indication_cfg.transaction_finished { - // The first case happens for unacknowledged file copy operation with no closure. - let finished_params = if tstate.finished_params.is_none() { - TransactionFinishedParams { - id: tstate.transaction_id, - condition_code: ConditionCode::NoError, - delivery_code: DeliveryCode::Complete, - file_status: FileStatus::Unreported, - } - } else { - let finished_params = tstate.finished_params.as_ref().unwrap(); - TransactionFinishedParams { - id: tstate.transaction_id, - condition_code: finished_params.condition_code, - delivery_code: finished_params.delivery_code, - file_status: finished_params.file_status, - } - }; - cfdp_user.transaction_finished_indication(&finished_params); - } - } - - fn send_progressing_file_data_pdu(&mut self) -> Result { - // Should never be called, but use defensive programming here. - if self.fparams.progress >= self.fparams.file_size { - return Ok(false); - } - let read_len = if self.fparams.file_size < self.fparams.segment_len { - self.fparams.file_size - } else if self.fparams.progress + self.fparams.segment_len > self.fparams.file_size { - self.fparams.file_size - self.fparams.progress - } else { - self.fparams.segment_len - }; - let pdu_creator = FileDataPduCreatorWithReservedDatafield::new_no_seg_metadata( - PduHeader::new_for_file_data( - self.pdu_conf, - 0, - SegmentMetadataFlag::NotPresent, - SegmentationControl::NoRecordBoundaryPreservation, - ), - self.fparams.progress, - read_len, - ); - let mut unwritten_pdu = - pdu_creator.write_to_bytes_partially(self.pdu_and_cksum_buffer.get_mut())?; - self.vfs.read_data( - self.put_request_cacher.source_file().unwrap(), - self.fparams.progress, - read_len, - unwritten_pdu.file_data_field_mut(), - )?; - let written_len = unwritten_pdu.finish(); - self.pdu_sender.send_pdu( - PduType::FileData, - None, - &self.pdu_and_cksum_buffer.borrow()[0..written_len], - )?; - self.fparams.progress += read_len; - /* - """Generic function to prepare a file data PDU. This function can also be used to - re-transmit file data PDUs of segments which were already sent.""" - assert self._put_req is not None - assert self._put_req.source_file is not None - with open(self._put_req.source_file, "rb") as of: - file_data = self.user.vfs.read_from_opened_file(of, offset, read_len) - # TODO: Support for record continuation state not implemented yet. Segment metadata - # flag is therefore always set to False. Segment metadata support also omitted - # for now. Implementing those generically could be done in form of a callback, - # e.g. abstractmethod of this handler as a first way, another one being - # to expect the user to supply some helper class to split up a file - fd_params = FileDataParams( - file_data=file_data, offset=offset, segment_metadata=None - ) - file_data_pdu = FileDataPdu( - pdu_conf=self._params.pdu_conf, params=fd_params - ) - self._add_packet_to_be_sent(file_data_pdu) - */ - /* - """Prepare the next file data PDU, which also progresses the file copy operation. - - :return: True if a packet was prepared, False if PDU handling is done and the next steps - in the Copy File procedure can be performed - """ - # This function should only be called if file segments still need to be sent. - assert self._params.fp.progress < self._params.fp.file_size - if self._params.fp.file_size < self._params.fp.segment_len: - read_len = self._params.fp.file_size - else: - if ( - self._params.fp.progress + self._params.fp.segment_len - > self._params.fp.file_size - ): - read_len = self._params.fp.file_size - self._params.fp.progress - else: - read_len = self._params.fp.segment_len - self._prepare_file_data_pdu(self._params.fp.progress, read_len) - self._params.fp.progress += read_len - */ - Ok(true) - } - - fn prepare_and_send_eof_pdu(&mut self, checksum: u32) -> Result<(), PduError> { - let tstate = self - .tstate - .as_ref() - .expect("transfer state unexpectedly empty"); - let eof_pdu = EofPdu::new( - PduHeader::new_no_file_data(self.pdu_conf, 0), - tstate.cond_code_eof.unwrap_or(ConditionCode::NoError), - checksum, - self.fparams.file_size, - None, - ); - self.pdu_send_helper(&eof_pdu)?; - Ok(()) - } - - fn pdu_send_helper(&self, pdu: &(impl WritablePduPacket + CfdpPdu)) -> Result<(), PduError> { - let mut pdu_buffer_mut = self.pdu_and_cksum_buffer.borrow_mut(); - let written_len = pdu.write_to_bytes(&mut pdu_buffer_mut)?; - self.pdu_sender.send_pdu( - pdu.pdu_type(), - pdu.file_directive_type(), - &pdu_buffer_mut[0..written_len], - )?; - Ok(()) - } - - fn handle_finished_pdu(&mut self, packet_info: &PacketInfo) -> Result<(), SourceError> { - // Ignore this packet when we are idle. - if self.state_helper.state == State::Idle { - return Ok(()); - } - if self.state_helper.step != TransactionStep::WaitingForFinished { - return Err(SourceError::UnexpectedPdu { - pdu_type: PduType::FileDirective, - directive_type: Some(FileDirectiveType::FinishedPdu), - }); - } - let finished_pdu = FinishedPduReader::new(packet_info.raw_packet())?; - // Unwrapping should be fine here, the transfer state is valid when we are not in IDLE - // mode. - self.tstate.as_mut().unwrap().finished_params = Some(FinishedParams { - condition_code: finished_pdu.condition_code(), - delivery_code: finished_pdu.delivery_code(), - file_status: finished_pdu.file_status(), - }); - if self.tstate.as_ref().unwrap().transmission_mode == TransmissionMode::Acknowledged { - // TODO: Send ACK packet here immediately and continue. - //self.state_helper.step = TransactionStep::SendingAckOfFinished; - } - self.state_helper.step = TransactionStep::NoticeOfCompletion; - - /* - if self.transmission_mode == TransmissionMode.ACKNOWLEDGED: - self._prepare_finished_ack_packet(finished_pdu.condition_code) - self.states.step = TransactionStep.SENDING_ACK_OF_FINISHED - else: - self.states.step = TransactionStep.NOTICE_OF_COMPLETION - */ - Ok(()) - } - - fn handle_nak_pdu(&mut self) {} - - fn handle_keep_alive_pdu(&mut self) {} - - /// Get the step, which denotes the exact step of a pending CFDP transaction when applicable. - pub fn step(&self) -> TransactionStep { - self.state_helper.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_helper.state - } - - pub fn local_cfg(&self) -> &LocalEntityConfig { - &self.local_cfg - } - - /// This function is public to allow completely resetting the handler, but it is explicitely - /// discouraged to do this. CFDP has mechanism to detect issues and errors on itself. - /// Resetting the handler might interfere with these mechanisms and lead to unexpected - /// behaviour. - pub fn reset(&mut self) { - self.state_helper = Default::default(); - self.tstate = None; - self.fparams = Default::default(); - } -} - -#[cfg(test)] -mod tests { - use std::{path::PathBuf, string::ToString}; - - use alloc::string::String; - use spacepackets::{ - cfdp::{ - pdu::{finished::FinishedPduCreator, metadata::MetadataPduReader}, - ChecksumType, CrcFlag, - }, - util::UnsignedByteFieldU16, - }; - use tempfile::TempPath; - - use super::*; - use crate::{ - cfdp::{ - filestore::NativeFilestore, - request::PutRequestOwned, - tests::{ - basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler, - }, - FaultHandler, IndicationConfig, StdRemoteEntityConfigProvider, CRC_32, - }, - seq_count::SeqCountProviderSimple, - }; - - const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); - const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2); - - fn init_full_filepaths_textfile() -> (TempPath, PathBuf) { - ( - tempfile::NamedTempFile::new().unwrap().into_temp_path(), - tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf(), - ) - } - - type TestSourceHandler = SourceHandler< - TestCfdpSender, - TestFaultHandler, - NativeFilestore, - StdRemoteEntityConfigProvider, - SeqCountProviderSimple, - >; - - struct SourceHandlerTestbench { - handler: TestSourceHandler, - srcfile: Option, - destfile: Option, - } - - impl SourceHandlerTestbench { - fn new( - crc_on_transmission_by_default: bool, - test_fault_handler: TestFaultHandler, - test_packet_sender: TestCfdpSender, - ) -> Self { - let local_entity_cfg = LocalEntityConfig { - id: LOCAL_ID.into(), - indication_cfg: IndicationConfig::default(), - fault_handler: FaultHandler::new(test_fault_handler), - }; - let static_put_request_cacher = StaticPutRequestCacher::new(2048); - Self { - handler: SourceHandler::new( - local_entity_cfg, - test_packet_sender, - NativeFilestore::default(), - static_put_request_cacher, - 1024, - basic_remote_cfg_table(REMOTE_ID, crc_on_transmission_by_default), - SeqCountProviderSimple::default(), - ), - srcfile: None, - destfile: None, - } - } - - fn put_request( - &mut self, - put_request: &impl ReadablePutRequest, - ) -> Result<(), PutRequestError> { - if self.srcfile.is_some() || self.destfile.is_some() { - self.srcfile = Some(put_request.source_file().unwrap().to_string()); - self.destfile = Some(put_request.dest_file().unwrap().to_string()); - } - self.handler.put_request(put_request) - } - - fn all_fault_queues_empty(&self) -> bool { - self.handler - .local_cfg - .user_fault_hook() - .borrow() - .all_queues_empty() - } - - fn pdu_queue_empty(&self) -> bool { - self.handler.pdu_sender.queue_empty() - } - - fn get_next_sent_pdu(&self) -> Option { - self.handler.pdu_sender.retrieve_next_pdu() - } - - fn common_pdu_check_for_file_transfer(&self, pdu_header: &PduHeader, crc_flag: CrcFlag) { - assert_eq!( - pdu_header.seg_ctrl(), - SegmentationControl::NoRecordBoundaryPreservation - ); - assert_eq!( - pdu_header.seg_metadata_flag(), - SegmentMetadataFlag::NotPresent - ); - assert_eq!(pdu_header.common_pdu_conf().source_id(), LOCAL_ID.into()); - assert_eq!(pdu_header.common_pdu_conf().dest_id(), REMOTE_ID.into()); - assert_eq!(pdu_header.common_pdu_conf().crc_flag, crc_flag); - assert_eq!( - pdu_header.common_pdu_conf().trans_mode, - TransmissionMode::Unacknowledged - ); - assert_eq!( - pdu_header.common_pdu_conf().direction, - Direction::TowardsReceiver - ); - assert_eq!( - pdu_header.common_pdu_conf().file_flag, - LargeFileFlag::Normal - ); - assert_eq!(pdu_header.common_pdu_conf().transaction_seq_num.size(), 2); - } - } - - #[test] - fn test_basic() { - let fault_handler = TestFaultHandler::default(); - let test_sender = TestCfdpSender::default(); - let tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); - assert!(tb.handler.transmission_mode().is_none()); - assert!(tb.all_fault_queues_empty()); - assert!(tb.pdu_queue_empty()); - assert_eq!(tb.handler.state(), State::Idle); - assert_eq!(tb.handler.step(), TransactionStep::Idle); - } - - fn common_no_acked_file_transfer( - tb: &mut SourceHandlerTestbench, - cfdp_user: &mut TestCfdpUser, - put_request: PutRequestOwned, - srcfile_str: String, - destfile_str: String, - ) -> PduHeader { - tb.handler - .put_request(&put_request) - .expect("put_request call failed"); - assert_eq!(tb.handler.state(), State::Busy); - assert_eq!(tb.handler.step(), TransactionStep::Idle); - let sent_packets = tb - .handler - .state_machine(cfdp_user, None) - .expect("source handler FSM failure"); - assert_eq!(sent_packets, 2); - assert!(!tb.pdu_queue_empty()); - let next_pdu = tb.get_next_sent_pdu().unwrap(); - assert!(!tb.pdu_queue_empty()); - assert_eq!(next_pdu.pdu_type, PduType::FileDirective); - assert_eq!( - next_pdu.file_directive_type, - Some(FileDirectiveType::MetadataPdu) - ); - let metadata_pdu = - MetadataPduReader::new(&next_pdu.raw_pdu).expect("invalid metadata PDU format"); - let pdu_header = metadata_pdu.pdu_header(); - tb.common_pdu_check_for_file_transfer(metadata_pdu.pdu_header(), CrcFlag::NoCrc); - assert_eq!( - metadata_pdu - .src_file_name() - .value_as_str() - .unwrap() - .unwrap(), - srcfile_str - ); - assert_eq!( - metadata_pdu - .dest_file_name() - .value_as_str() - .unwrap() - .unwrap(), - destfile_str - ); - assert_eq!(metadata_pdu.metadata_params().file_size, 0); - assert_eq!( - metadata_pdu.metadata_params().checksum_type, - ChecksumType::Crc32 - ); - let closure_requested = if let Some(closure_requested) = put_request.closure_requested { - assert_eq!( - metadata_pdu.metadata_params().closure_requested, - closure_requested - ); - closure_requested - } else { - assert!(metadata_pdu.metadata_params().closure_requested); - metadata_pdu.metadata_params().closure_requested - }; - assert_eq!(metadata_pdu.options(), &[]); - - let next_pdu = tb.get_next_sent_pdu().unwrap(); - assert_eq!(next_pdu.pdu_type, PduType::FileDirective); - assert_eq!( - next_pdu.file_directive_type, - Some(FileDirectiveType::EofPdu) - ); - let eof_pdu = EofPdu::from_bytes(&next_pdu.raw_pdu).expect("invalid metadata PDU format"); - tb.common_pdu_check_for_file_transfer(eof_pdu.pdu_header(), CrcFlag::NoCrc); - assert_eq!(eof_pdu.condition_code(), ConditionCode::NoError); - assert_eq!(eof_pdu.file_size(), 0); - assert_eq!(eof_pdu.file_checksum(), CRC_32.digest().finalize()); - assert_eq!( - eof_pdu - .pdu_header() - .common_pdu_conf() - .transaction_seq_num - .value_const(), - 0 - ); - if !closure_requested { - assert_eq!(tb.handler.state(), State::Idle); - assert_eq!(tb.handler.step(), TransactionStep::Idle); - } else { - assert_eq!(tb.handler.state(), State::Busy); - assert_eq!(tb.handler.step(), TransactionStep::WaitingForFinished); - } - *pdu_header - } - - #[test] - fn test_empty_file_transfer_not_acked_no_closure() { - let fault_handler = TestFaultHandler::default(); - let test_sender = TestCfdpSender::default(); - let mut tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); - let (srcfile, destfile) = init_full_filepaths_textfile(); - let srcfile_str = String::from(srcfile.to_str().unwrap()); - let destfile_str = String::from(destfile.to_str().unwrap()); - let put_request = PutRequestOwned::new_regular_request( - REMOTE_ID.into(), - &srcfile_str, - &destfile_str, - Some(TransmissionMode::Unacknowledged), - Some(false), - ) - .expect("creating put request failed"); - let mut cfdp_user = TestCfdpUser::new(0, srcfile_str.clone(), destfile_str.clone(), 0); - common_no_acked_file_transfer( - &mut tb, - &mut cfdp_user, - put_request, - srcfile_str, - destfile_str, - ); - } - - #[test] - fn test_empty_file_transfer_not_acked_with_closure() { - let fault_handler = TestFaultHandler::default(); - let test_sender = TestCfdpSender::default(); - let mut tb = SourceHandlerTestbench::new(false, fault_handler, test_sender); - let (srcfile, destfile) = init_full_filepaths_textfile(); - let srcfile_str = String::from(srcfile.to_str().unwrap()); - let destfile_str = String::from(destfile.to_str().unwrap()); - let put_request = PutRequestOwned::new_regular_request( - REMOTE_ID.into(), - &srcfile_str, - &destfile_str, - Some(TransmissionMode::Unacknowledged), - Some(true), - ) - .expect("creating put request failed"); - let mut cfdp_user = TestCfdpUser::new(0, srcfile_str.clone(), destfile_str.clone(), 0); - let pdu_header = common_no_acked_file_transfer( - &mut tb, - &mut cfdp_user, - put_request, - srcfile_str, - destfile_str, - ); - let finished_pdu = FinishedPduCreator::new_default( - pdu_header, - DeliveryCode::Complete, - FileStatus::Retained, - ); - let finished_pdu_vec = finished_pdu.to_vec().unwrap(); - let packet_info = PacketInfo::new(&finished_pdu_vec).unwrap(); - tb.handler - .state_machine(&mut cfdp_user, Some(&packet_info)) - .unwrap(); - assert_eq!(tb.handler.state(), State::Idle); - assert_eq!(tb.handler.step(), TransactionStep::Idle); - } -} 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..e11ce26 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; -- 2.43.0 From dba2f947be575cfec6f8c0ceac4503562a12bf38 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 20 Aug 2024 15:12:12 +0200 Subject: [PATCH 16/18] move sequence count module --- satrs/src/lib.rs | 1 - satrs/src/seq_count.rs | 250 ----------------------------------------- 2 files changed, 251 deletions(-) delete mode 100644 satrs/src/seq_count.rs diff --git a/satrs/src/lib.rs b/satrs/src/lib.rs index e11ce26..6e2bef0 100644 --- a/satrs/src/lib.rs +++ b/satrs/src/lib.rs @@ -36,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/seq_count.rs b/satrs/src/seq_count.rs deleted file mode 100644 index 1dcd17c..0000000 --- a/satrs/src/seq_count.rs +++ /dev/null @@ -1,250 +0,0 @@ -use core::cell::Cell; -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 SequenceCountProvider { - type Raw: Into; - const MAX_BIT_WIDTH: usize; - - fn get(&self) -> Self::Raw; - - fn increment(&self); - - fn get_and_increment(&self) -> Self::Raw { - let val = self.get(); - self.increment(); - val - } -} - -#[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 SequenceCountProvider for SeqCountProviderSimple<$ty> { - type Raw = $ty; - const MAX_BIT_WIDTH: usize = core::mem::size_of::() * 8; - - fn get(&self) -> Self::Raw { - self.seq_count.get() - } - - fn increment(&self) { - self.get_and_increment(); - } - - fn get_and_increment(&self) -> Self::Raw { - 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 SequenceCountProvider for CcsdsSimpleSeqCountProvider { - type Raw = u16; - const MAX_BIT_WIDTH: usize = core::mem::size_of::() * 8; - 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 SequenceCountProvider for [] { - type Raw = $ty; - const MAX_BIT_WIDTH: usize = core::mem::size_of::() * 8; - - 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, - SequenceCountProvider, - }; - 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); - } -} -- 2.43.0 From c73f7259a031e2977ff8902388460f79ffb1dcd3 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 20 Aug 2024 15:13:36 +0200 Subject: [PATCH 17/18] use moved sequence counter --- satrs/src/pus/verification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/satrs/src/pus/verification.rs b/satrs/src/pus/verification.rs index 47b57f4..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")] -- 2.43.0 From 69daae15ceca66b3440e0d757e64bf45c592fd28 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Tue, 20 Aug 2024 15:15:31 +0200 Subject: [PATCH 18/18] changelog --- satrs/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) 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 -- 2.43.0