From f7f1017fedb4293fb435ddd7d8b30bad6ec248ad Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Sat, 11 Nov 2023 19:05:46 +0100 Subject: [PATCH] continue CFDP handlers --- satrs-core/Cargo.toml | 6 +- satrs-core/src/cfdp/dest.rs | 209 +++++++++++++++++++++++++----------- satrs-core/src/cfdp/mod.rs | 96 +++++++++++++++-- 3 files changed, 235 insertions(+), 76 deletions(-) diff --git a/satrs-core/Cargo.toml b/satrs-core/Cargo.toml index 6fa8535..65ee5e7 100644 --- a/satrs-core/Cargo.toml +++ b/satrs-core/Cargo.toml @@ -73,11 +73,11 @@ features = ["all"] optional = true [dependencies.spacepackets] -version = "0.7.0-beta.2" +# version = "0.7.0-beta.2" default-features = false -# git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" +git = "https://egit.irs.uni-stuttgart.de/rust/spacepackets.git" # rev = "79d26e1a6" -# branch = "" +branch = "writable_pdu_packet" [dependencies.cobs] git = "https://github.com/robamu/cobs.rs.git" diff --git a/satrs-core/src/cfdp/dest.rs b/satrs-core/src/cfdp/dest.rs index b66365a..f54d22c 100644 --- a/satrs-core/src/cfdp/dest.rs +++ b/satrs-core/src/cfdp/dest.rs @@ -9,8 +9,10 @@ use crate::cfdp::user::TransactionFinishedParams; use super::{ user::{CfdpUser, MetadataReceivedParams}, - PacketInfo, PacketTarget, State, TransactionId, TransactionStep, CRC_32, + CheckTimerCreator, PacketInfo, PacketTarget, RemoteEntityConfigProvider, State, TransactionId, + TransactionStep, CRC_32, }; +use alloc::boxed::Box; use smallvec::SmallVec; use spacepackets::{ cfdp::{ @@ -19,10 +21,10 @@ use spacepackets::{ file_data::FileDataPdu, finished::{DeliveryCode, FileStatus, FinishedPdu}, metadata::{MetadataGenericParams, MetadataPdu}, - CommonPduConfig, FileDirectiveType, PduError, PduHeader, + CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket, }, tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, TlvType}, - ConditionCode, PduType, TransmissionMode, + ConditionCode, PduType, }, util::UnsignedByteField, }; @@ -34,6 +36,8 @@ pub struct DestinationHandler { state: State, tparams: TransactionParams, packets_to_send_ctx: PacketsToSendContext, + remote_cfg_table: Box, + check_timer_creator: Box, } #[derive(Debug, Default)] @@ -152,25 +156,38 @@ pub enum DestError { } impl DestinationHandler { - pub fn new(id: impl Into) -> Self { + pub fn new( + entity_id: impl Into, + remote_cfg_table: Box, + check_timer_creator: Box, + ) -> Self { Self { - id: id.into(), + id: entity_id.into(), step: TransactionStep::Idle, state: State::Idle, tparams: Default::default(), packets_to_send_ctx: Default::default(), + remote_cfg_table, + check_timer_creator, } } - pub fn state_machine(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { + pub fn state_machine( + &mut self, + cfdp_user: &mut impl CfdpUser, + packet_to_insert: Option<&PacketInfo>, + ) -> Result<(), DestError> { + if let Some(packet) = packet_to_insert { + self.insert_packet(packet)?; + } match self.state { State::Idle => todo!(), - State::BusyClass1Nacked => self.fsm_nacked(cfdp_user), - State::BusyClass2Acked => todo!("acknowledged mode not implemented yet"), + State::Busy => self.fsm_busy(cfdp_user), + State::Suspended => todo!(), } } - pub fn insert_packet(&mut self, packet_info: &PacketInfo) -> Result<(), DestError> { + fn insert_packet(&mut self, 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. @@ -297,11 +314,7 @@ impl DestinationHandler { } } } - if self.tparams.pdu_conf.trans_mode == TransmissionMode::Unacknowledged { - self.state = State::BusyClass1Nacked; - } else { - self.state = State::BusyClass2Acked; - } + self.state = State::Busy; self.step = TransactionStep::TransactionStart; Ok(()) } @@ -338,7 +351,7 @@ impl DestinationHandler { // TODO: Check progress, and implement transfer completion timer as specified in the // standard. This timer protects against out of order arrival of packets. if self.tparams.tstate.progress != self.tparams.file_size() {} - if self.state == State::BusyClass1Nacked { + if self.state == State::Busy { self.step = TransactionStep::TransferCompletion; } else { self.step = TransactionStep::SendingAckPdu; @@ -367,7 +380,7 @@ impl DestinationHandler { Ok(false) } - fn fsm_nacked(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { + fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { if self.step == TransactionStep::TransactionStart { self.transaction_start(cfdp_user)?; } @@ -496,7 +509,10 @@ impl DestinationHandler { #[cfg(test)] mod tests { - use core::sync::atomic::{AtomicU8, Ordering}; + use core::{ + cell::Cell, + sync::atomic::{AtomicU8, Ordering}, + }; #[allow(unused_imports)] use std::println; use std::{env::temp_dir, fs}; @@ -504,10 +520,14 @@ mod tests { use alloc::{format, string::String}; use rand::Rng; use spacepackets::{ - cfdp::{lv::Lv, ChecksumType}, + cfdp::{lv::Lv, pdu::WritablePduPacket, ChecksumType, TransmissionMode}, util::{UbfU16, UnsignedByteFieldU16}, }; + use crate::cfdp::{ + CheckTimer, CheckTimerCreator, RemoteEntityConfig, StdRemoteEntityConfigProvider, + }; + use super::*; const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1); @@ -608,6 +628,63 @@ mod tests { } } + struct TestCheckTimer { + counter: Cell, + expiry_count: u32, + } + + impl CheckTimer for TestCheckTimer { + fn has_expired(&self) -> bool { + let current_counter = self.counter.get(); + if self.expiry_count == current_counter { + return true; + } + self.counter.set(current_counter + 1); + false + } + } + + impl TestCheckTimer { + pub fn new(expiry_after_x_calls: u32) -> Self { + Self { + counter: Cell::new(0), + expiry_count: expiry_after_x_calls, + } + } + } + + struct TestCheckTimerCreator { + expiry_counter_for_source_entity: u32, + expiry_counter_for_dest_entity: u32, + } + + impl TestCheckTimerCreator { + pub fn new( + expiry_counter_for_source_entity: u32, + expiry_counter_for_dest_entity: u32, + ) -> Self { + Self { + expiry_counter_for_source_entity, + expiry_counter_for_dest_entity, + } + } + } + + impl CheckTimerCreator for TestCheckTimerCreator { + fn get_check_timer_provider( + &self, + _local_id: &UnsignedByteField, + _remote_id: &UnsignedByteField, + entity_type: crate::cfdp::EntityType, + ) -> Box { + if entity_type == crate::cfdp::EntityType::Sending { + Box::new(TestCheckTimer::new(self.expiry_counter_for_source_entity)) + } else { + Box::new(TestCheckTimer::new(self.expiry_counter_for_dest_entity)) + } + } + } + fn init_check(handler: &DestinationHandler) { assert_eq!(handler.state(), State::Idle); assert_eq!(handler.step(), TransactionStep::Idle); @@ -626,9 +703,31 @@ mod tests { (src_path, file_path) } + 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() -> DestinationHandler { + DestinationHandler::new( + REMOTE_ID, + Box::new(basic_remote_cfg_table()), + Box::new(TestCheckTimerCreator::new(2, 2)), + ) + } #[test] fn test_basic() { - let dest_handler = DestinationHandler::new(REMOTE_ID); + let dest_handler = default_dest_handler(); init_check(&dest_handler); } @@ -655,37 +754,20 @@ mod tests { ) } - fn insert_metadata_pdu( - metadata_pdu: &MetadataPdu, - buf: &mut [u8], - dest_handler: &mut DestinationHandler, - ) { - let written_len = metadata_pdu + 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"); - let packet_info = - PacketInfo::new(&buf[..written_len]).expect("generating packet info failed"); - let insert_result = dest_handler.insert_packet(&packet_info); - if let Err(e) = insert_result { - panic!("insert result error: {e}"); - } + PacketInfo::new(&buf[..written_len]).expect("generating packet info failed") } - - fn insert_eof_pdu( - file_data: &[u8], - pdu_header: &PduHeader, - buf: &mut [u8], - dest_handler: &mut DestinationHandler, - ) { + fn create_no_error_eof(file_data: &[u8], pdu_header: &PduHeader, buf: &mut [u8]) -> EofPdu { let mut digest = CRC_32.digest(); digest.update(file_data); let crc32 = digest.finalize(); - let eof_pdu = EofPdu::new_no_error(*pdu_header, crc32, file_data.len() as u64); - let result = eof_pdu.write_to_bytes(buf); - assert!(result.is_ok()); - let packet_info = PacketInfo::new(&buf).expect("generating packet info failed"); - let result = dest_handler.insert_packet(&packet_info); - assert!(result.is_ok()); + EofPdu::new_no_error(*pdu_header, crc32, file_data.len() as u64) } #[test] @@ -700,23 +782,24 @@ mod tests { expected_file_size: 0, }; // We treat the destination handler like it is a remote entity. - let mut dest_handler = DestinationHandler::new(REMOTE_ID); + let mut dest_handler = default_dest_handler(); init_check(&dest_handler); let seq_num = UbfU16::new(0); let pdu_header = create_pdu_header(seq_num); let metadata_pdu = create_metadata_pdu(&pdu_header, src_name.as_path(), dest_name.as_path(), 0); - insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); + let packet_info = create_packet_info(&metadata_pdu, &mut buf); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); if let Err(e) = result { panic!("dest handler fsm error: {e}"); } assert_ne!(dest_handler.state(), State::Idle); assert_eq!(dest_handler.step(), TransactionStep::ReceivingFileDataPdus); - insert_eof_pdu(&[], &pdu_header, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); + let eof_pdu = create_no_error_eof(&[], &pdu_header, &mut buf); + let packet_info = create_packet_info(&eof_pdu, &mut buf); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); assert!(result.is_ok()); assert_eq!(dest_handler.state(), State::Idle); assert_eq!(dest_handler.step(), TransactionStep::Idle); @@ -740,7 +823,7 @@ mod tests { expected_file_size: file_data.len(), }; // We treat the destination handler like it is a remote entity. - let mut dest_handler = DestinationHandler::new(REMOTE_ID); + let mut dest_handler = default_dest_handler(); init_check(&dest_handler); let seq_num = UbfU16::new(0); @@ -751,8 +834,8 @@ mod tests { dest_name.as_path(), file_data.len() as u64, ); - insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); + let packet_info = create_packet_info(&metadata_pdu, &mut buf); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); if let Err(e) = result { panic!("dest handler fsm error: {e}"); } @@ -769,11 +852,12 @@ mod tests { if let Err(e) = result { panic!("destination handler packet insertion error: {e}"); } - let result = dest_handler.state_machine(&mut test_user); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); assert!(result.is_ok()); - insert_eof_pdu(file_data, &pdu_header, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); + let eof_pdu = create_no_error_eof(&file_data, &pdu_header, &mut buf); + let packet_info = create_packet_info(&eof_pdu, &mut buf); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); assert!(result.is_ok()); assert_eq!(dest_handler.state(), State::Idle); assert_eq!(dest_handler.step(), TransactionStep::Idle); @@ -800,7 +884,7 @@ mod tests { }; // We treat the destination handler like it is a remote entity. - let mut dest_handler = DestinationHandler::new(REMOTE_ID); + let mut dest_handler = default_dest_handler(); init_check(&dest_handler); let seq_num = UbfU16::new(0); @@ -811,8 +895,8 @@ mod tests { dest_name.as_path(), random_data.len() as u64, ); - insert_metadata_pdu(&metadata_pdu, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); + let packet_info = create_packet_info(&metadata_pdu, &mut buf); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); if let Err(e) = result { panic!("dest handler fsm error: {e}"); } @@ -835,7 +919,7 @@ mod tests { if let Err(e) = result { panic!("destination handler packet insertion error: {e}"); } - let result = dest_handler.state_machine(&mut test_user); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); assert!(result.is_ok()); // Second file data PDU @@ -853,11 +937,12 @@ mod tests { if let Err(e) = result { panic!("destination handler packet insertion error: {e}"); } - let result = dest_handler.state_machine(&mut test_user); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); assert!(result.is_ok()); - insert_eof_pdu(&random_data, &pdu_header, &mut buf, &mut dest_handler); - let result = dest_handler.state_machine(&mut test_user); + let eof_pdu = create_no_error_eof(&random_data, &pdu_header, &mut buf); + let packet_info = create_packet_info(&eof_pdu, &mut buf); + let result = dest_handler.state_machine(&mut test_user, Some(&packet_info)); assert!(result.is_ok()); assert_eq!(dest_handler.state(), State::Idle); assert_eq!(dest_handler.step(), TransactionStep::Idle); diff --git a/satrs-core/src/cfdp/mod.rs b/satrs-core/src/cfdp/mod.rs index dc6e87a..58edbff 100644 --- a/satrs-core/src/cfdp/mod.rs +++ b/satrs-core/src/cfdp/mod.rs @@ -1,4 +1,7 @@ +use core::hash::Hash; + use crc::{Crc, CRC_32_CKSUM}; +use hashbrown::HashMap; use spacepackets::{ cfdp::{ pdu::{FileDirectiveType, PduError, PduHeader}, @@ -35,7 +38,7 @@ pub enum EntityType { /// 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. -pub trait CheckTimerProvider { +pub trait CheckTimer { fn has_expired(&self) -> bool; } @@ -50,10 +53,11 @@ pub trait CheckTimerProvider { #[cfg(feature = "alloc")] pub trait CheckTimerCreator { fn get_check_timer_provider( + &self, local_id: &UnsignedByteField, remote_id: &UnsignedByteField, entity_type: EntityType, - ) -> Box; + ) -> Box; } /// Simple implementation of the [CheckTimerProvider] trait assuming a standard runtime. @@ -75,7 +79,7 @@ impl StdCheckTimer { } #[cfg(feature = "std")] -impl CheckTimerProvider for StdCheckTimer { +impl CheckTimer for StdCheckTimer { fn has_expired(&self) -> bool { let elapsed_time = self.start_time.elapsed(); if elapsed_time.as_secs() > self.expiry_time_seconds { @@ -85,22 +89,78 @@ impl CheckTimerProvider for StdCheckTimer { } } -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct RemoteEntityConfig { pub entity_id: UnsignedByteField, pub max_file_segment_len: usize, - pub closure_requeted_by_default: bool, + pub max_packet_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 check_limit: u32, } -pub trait RemoteEntityConfigProvider { - fn get_remote_config(&self, remote_id: &UnsignedByteField) -> Option<&RemoteEntityConfig>; +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, + } + } } -#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub trait RemoteEntityConfigProvider { + /// Retrieve the remote entity configuration for the given remote ID. + fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig>; + fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>; + /// Add a new remote configuration. Return [True] if the configuration was + /// inserted successfully, and [False] if a configuration already exists. + fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool; + /// Remote a configuration. Returns [True] if the configuration was removed successfully, + /// and [False] if no configuration exists for the given remote ID. + fn remove_config(&mut self, remote_id: u64) -> bool; +} + +#[cfg(feature = "std")] +#[derive(Default)] +pub struct StdRemoteEntityConfigProvider { + remote_cfg_table: HashMap, +} + +#[cfg(feature = "std")] +impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider { + fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> { + self.remote_cfg_table.get(&remote_id) + } + fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> { + self.remote_cfg_table.get_mut(&remote_id) + } + fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool { + self.remote_cfg_table + .insert(cfg.entity_id.value(), *cfg) + .is_some() + } + fn remove_config(&mut self, remote_id: u64) -> bool { + self.remote_cfg_table.remove(&remote_id).is_some() + } +} + +#[derive(Debug, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TransactionId { source_id: UnsignedByteField, @@ -121,6 +181,20 @@ impl TransactionId { } } +impl Hash for TransactionId { + fn hash(&self, state: &mut H) { + self.source_id.value().hash(state); + self.seq_num.value().hash(state); + } +} + +impl PartialEq for TransactionId { + fn eq(&self, other: &Self) -> bool { + self.source_id.value() == other.source_id.value() + && self.seq_num.value() == other.seq_num.value() + } +} + #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TransactionStep { @@ -136,8 +210,8 @@ pub enum TransactionStep { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum State { Idle = 0, - BusyClass1Nacked = 2, - BusyClass2Acked = 3, + Busy = 1, + Suspended = 2, } pub const CRC_32: Crc = Crc::::new(&CRC_32_CKSUM); @@ -249,7 +323,7 @@ mod tests { eof::EofPdu, file_data::FileDataPdu, metadata::{MetadataGenericParams, MetadataPdu}, - CommonPduConfig, FileDirectiveType, PduHeader, + CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket, }, PduType, };