rework timer and packet send handling
All checks were successful
Rust/sat-rs/pipeline/pr-main This commit looks good

This commit is contained in:
Robin Müller 2023-12-22 19:24:48 +01:00
parent 4cf96ce0d5
commit 7776847364
Signed by: muellerr
GPG Key ID: A649FB78196E3849
2 changed files with 144 additions and 103 deletions

View File

@ -6,7 +6,8 @@ use super::{
filestore::{FilestoreError, VirtualFilestore}, filestore::{FilestoreError, VirtualFilestore},
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams}, user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams},
CheckTimer, CheckTimerCreator, EntityType, LocalEntityConfig, PacketInfo, PacketTarget, CheckTimer, CheckTimerCreator, EntityType, LocalEntityConfig, PacketInfo, PacketTarget,
RemoteEntityConfig, RemoteEntityConfigProvider, State, TransactionId, TransactionStep, RemoteEntityConfig, RemoteEntityConfigProvider, State, TimerContext, TransactionId,
TransactionStep,
}; };
use alloc::boxed::Box; use alloc::boxed::Box;
use smallvec::SmallVec; use smallvec::SmallVec;
@ -26,12 +27,6 @@ use spacepackets::{
}; };
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Default)]
struct PacketsToSendContext {
packet_available: bool,
directive: Option<FileDirectiveType>,
}
#[derive(Debug)] #[derive(Debug)]
struct FileProperties { struct FileProperties {
src_file_name: [u8; u8::MAX as usize], src_file_name: [u8; u8::MAX as usize],
@ -171,6 +166,15 @@ pub enum DestError {
NoRemoteCfgFound(UnsignedByteField), NoRemoteCfgFound(UnsignedByteField),
} }
pub trait CfdpPacketSender: Send {
fn send_pdu(
&mut self,
pdu_type: PduType,
file_directive_type: Option<FileDirectiveType>,
raw_pdu: &[u8],
) -> Result<(), PduError>;
}
/// This is the primary CFDP destination handler. It models the CFDP destination entity, which is /// 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 /// primarily responsible for receiving files sent from another CFDP entity. It performs the
/// reception side of File Copy Operations. /// reception side of File Copy Operations.
@ -194,7 +198,8 @@ pub struct DestinationHandler {
step: TransactionStep, step: TransactionStep,
state: State, state: State,
tparams: TransactionParams, tparams: TransactionParams,
packets_to_send_ctx: PacketsToSendContext, packet_buf: alloc::vec::Vec<u8>,
packet_sender: Box<dyn CfdpPacketSender>,
vfs: Box<dyn VirtualFilestore>, vfs: Box<dyn VirtualFilestore>,
remote_cfg_table: Box<dyn RemoteEntityConfigProvider>, remote_cfg_table: Box<dyn RemoteEntityConfigProvider>,
check_timer_creator: Box<dyn CheckTimerCreator>, check_timer_creator: Box<dyn CheckTimerCreator>,
@ -203,6 +208,8 @@ pub struct DestinationHandler {
impl DestinationHandler { impl DestinationHandler {
pub fn new( pub fn new(
local_cfg: LocalEntityConfig, local_cfg: LocalEntityConfig,
max_packet_len: usize,
packet_sender: Box<dyn CfdpPacketSender>,
vfs: Box<dyn VirtualFilestore>, vfs: Box<dyn VirtualFilestore>,
remote_cfg_table: Box<dyn RemoteEntityConfigProvider>, remote_cfg_table: Box<dyn RemoteEntityConfigProvider>,
check_timer_creator: Box<dyn CheckTimerCreator>, check_timer_creator: Box<dyn CheckTimerCreator>,
@ -212,7 +219,8 @@ impl DestinationHandler {
step: TransactionStep::Idle, step: TransactionStep::Idle,
state: State::Idle, state: State::Idle,
tparams: Default::default(), tparams: Default::default(),
packets_to_send_ctx: Default::default(), packet_buf: alloc::vec![0; max_packet_len],
packet_sender,
vfs, vfs,
remote_cfg_table, remote_cfg_table,
check_timer_creator, check_timer_creator,
@ -232,10 +240,7 @@ impl DestinationHandler {
&mut self, &mut self,
cfdp_user: &mut impl CfdpUser, cfdp_user: &mut impl CfdpUser,
packet_to_insert: Option<&PacketInfo>, packet_to_insert: Option<&PacketInfo>,
) -> Result<(), DestError> { ) -> Result<u32, DestError> {
if self.packet_to_send_ready() {
return Err(DestError::PacketToSendLeft);
}
if let Some(packet) = packet_to_insert { if let Some(packet) = packet_to_insert {
self.insert_packet(cfdp_user, packet)?; self.insert_packet(cfdp_user, packet)?;
} }
@ -259,54 +264,6 @@ impl DestinationHandler {
self.tstate().transaction_id self.tstate().transaction_id
} }
pub fn packet_to_send_ready(&self) -> bool {
self.packets_to_send_ctx.packet_available
}
pub fn get_next_packet(
&self,
buf: &mut [u8],
) -> Result<Option<(FileDirectiveType, usize)>, DestError> {
if !self.packet_to_send_ready() {
return Ok(None);
}
let directive = self.packets_to_send_ctx.directive.unwrap();
let tstate = self.tstate();
let written_size = match directive {
FileDirectiveType::FinishedPdu => {
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(buf)?
}
FileDirectiveType::AckPdu => todo!(),
FileDirectiveType::NakPdu => todo!(),
FileDirectiveType::KeepAlivePdu => todo!(),
_ => {
// This should never happen and is considered an internal impl error
panic!("invalid file directive {directive:?} for dest handler send packet");
}
};
Ok(Some((directive, written_size)))
}
fn insert_packet( fn insert_packet(
&mut self, &mut self,
cfdp_user: &mut impl CfdpUser, cfdp_user: &mut impl CfdpUser,
@ -532,12 +489,14 @@ impl DestinationHandler {
fn start_check_limit_handling(&mut self) { fn start_check_limit_handling(&mut self) {
self.step = TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling; self.step = TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling;
self.tparams.tstate.current_check_timer = self.tparams.tstate.current_check_timer = Some(
Some(self.check_timer_creator.get_check_timer_provider( self.check_timer_creator
&self.local_cfg.id, .get_check_timer_provider(TimerContext::CheckLimit {
&self.tparams.remote_cfg.unwrap().entity_id, local_id: self.local_cfg.id,
EntityType::Receiving, remote_id: self.tparams.remote_cfg.unwrap().entity_id,
)); entity_type: EntityType::Receiving,
}),
);
self.tparams.tstate.current_check_count = 0; self.tparams.tstate.current_check_count = 0;
} }
@ -571,7 +530,8 @@ impl DestinationHandler {
todo!(); todo!();
} }
fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<u32, DestError> {
let mut sent_packets = 0;
if self.step == TransactionStep::TransactionStart { if self.step == TransactionStep::TransactionStart {
self.transaction_start(cfdp_user)?; self.transaction_start(cfdp_user)?;
} }
@ -579,7 +539,7 @@ impl DestinationHandler {
self.check_limit_handling(); self.check_limit_handling();
} }
if self.step == TransactionStep::TransferCompletion { if self.step == TransactionStep::TransferCompletion {
self.transfer_completion(cfdp_user)?; sent_packets += self.transfer_completion(cfdp_user)?;
} }
if self.step == TransactionStep::SendingAckPdu { if self.step == TransactionStep::SendingAckPdu {
todo!("no support for acknowledged mode yet"); todo!("no support for acknowledged mode yet");
@ -587,7 +547,7 @@ impl DestinationHandler {
if self.step == TransactionStep::SendingFinishedPdu { if self.step == TransactionStep::SendingFinishedPdu {
self.reset(); self.reset();
} }
Ok(()) Ok(sent_packets)
} }
/// Get the step, which denotes the exact step of a pending CFDP transaction when applicable. /// Get the step, which denotes the exact step of a pending CFDP transaction when applicable.
@ -669,17 +629,18 @@ impl DestinationHandler {
Ok(()) Ok(())
} }
fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<u32, DestError> {
let mut sent_packets = 0;
self.notice_of_completion(cfdp_user)?; self.notice_of_completion(cfdp_user)?;
if self.tparams.transmission_mode() == TransmissionMode::Acknowledged if self.tparams.transmission_mode() == TransmissionMode::Acknowledged
|| self.tparams.metadata_params().closure_requested || self.tparams.metadata_params().closure_requested
{ {
self.prepare_finished_pdu()?; sent_packets += self.send_finished_pdu()?;
self.step = TransactionStep::SendingFinishedPdu; self.step = TransactionStep::SendingFinishedPdu;
} else { } else {
self.reset(); self.reset();
} }
Ok(()) Ok(sent_packets)
} }
fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> { fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
@ -745,15 +706,36 @@ impl DestinationHandler {
fn reset(&mut self) { fn reset(&mut self) {
self.step = TransactionStep::Idle; self.step = TransactionStep::Idle;
self.state = State::Idle; self.state = State::Idle;
self.packets_to_send_ctx.packet_available = false; // self.packets_to_send_ctx.packet_available = false;
self.tparams.reset(); self.tparams.reset();
} }
fn prepare_finished_pdu(&mut self) -> Result<(), DestError> { fn send_finished_pdu(&mut self) -> Result<u32, DestError> {
self.packets_to_send_ctx.packet_available = true; let tstate = self.tstate();
self.packets_to_send_ctx.directive = Some(FileDirectiveType::FinishedPdu);
self.step = TransactionStep::SendingFinishedPdu; let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0);
Ok(()) let finished_pdu = if tstate.condition_code == ConditionCode::NoError
|| tstate.condition_code == ConditionCode::UnsupportedChecksumType
{
FinishedPduCreator::new_default(pdu_header, tstate.delivery_code, tstate.file_status)
} else {
// TODO: Are there cases where this ID is actually the source entity ID?
let entity_id = EntityIdTlv::new(self.local_cfg.id);
FinishedPduCreator::new_with_error(
pdu_header,
tstate.condition_code,
tstate.delivery_code,
tstate.file_status,
entity_id,
)
};
finished_pdu.write_to_bytes(&mut self.packet_buf)?;
self.packet_sender.send_pdu(
finished_pdu.pdu_type(),
finished_pdu.file_directive_type(),
&self.packet_buf[0..finished_pdu.len_written()],
)?;
Ok(1)
} }
fn tstate(&self) -> &TransferState { fn tstate(&self) -> &TransferState {
@ -800,6 +782,27 @@ mod tests {
pub length: usize, pub length: usize,
} }
type SharedPduPacketQueue = Arc<Mutex<VecDeque<(PduType, Option<FileDirectiveType>, Vec<u8>)>>>;
#[derive(Default, Clone)]
struct TestCfdpSender {
packet_queue: SharedPduPacketQueue,
}
impl CfdpPacketSender for TestCfdpSender {
fn send_pdu(
&mut self,
pdu_type: PduType,
file_directive_type: Option<FileDirectiveType>,
raw_pdu: &[u8],
) -> Result<(), PduError> {
self.packet_queue.lock().unwrap().push_back((
pdu_type,
file_directive_type,
raw_pdu.to_vec(),
));
Ok(())
}
}
#[derive(Default)] #[derive(Default)]
struct TestCfdpUser { struct TestCfdpUser {
next_expected_seq_num: u64, next_expected_seq_num: u64,
@ -1025,28 +1028,33 @@ mod tests {
} }
struct TestCheckTimerCreator { struct TestCheckTimerCreator {
expired_flag: Arc<AtomicBool>, check_limit_expired_flag: Arc<AtomicBool>,
} }
impl TestCheckTimerCreator { impl TestCheckTimerCreator {
pub fn new(expired_flag: Arc<AtomicBool>) -> Self { pub fn new(expired_flag: Arc<AtomicBool>) -> Self {
Self { expired_flag } Self {
check_limit_expired_flag: expired_flag,
}
} }
} }
impl CheckTimerCreator for TestCheckTimerCreator { impl CheckTimerCreator for TestCheckTimerCreator {
fn get_check_timer_provider( fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CheckTimer> {
&self, match timer_context {
_local_id: &UnsignedByteField, TimerContext::CheckLimit { .. } => {
_remote_id: &UnsignedByteField, Box::new(TestCheckTimer::new(self.check_limit_expired_flag.clone()))
_entity_type: crate::cfdp::EntityType, }
) -> Box<dyn CheckTimer> { _ => {
Box::new(TestCheckTimer::new(self.expired_flag.clone())) panic!("invalid check timer creator, can only be used for check limit handling")
}
}
} }
} }
struct DestHandlerTester { struct DestHandlerTester {
check_timer_expired: Arc<AtomicBool>, check_timer_expired: Arc<AtomicBool>,
test_sender: TestCfdpSender,
handler: DestinationHandler, handler: DestinationHandler,
src_path: PathBuf, src_path: PathBuf,
dest_path: PathBuf, dest_path: PathBuf,
@ -1061,11 +1069,17 @@ mod tests {
impl DestHandlerTester { impl DestHandlerTester {
fn new(fault_handler: TestFaultHandler) -> Self { fn new(fault_handler: TestFaultHandler) -> Self {
let check_timer_expired = Arc::new(AtomicBool::new(false)); let check_timer_expired = Arc::new(AtomicBool::new(false));
let dest_handler = default_dest_handler(fault_handler, check_timer_expired.clone()); let test_sender = TestCfdpSender::default();
let dest_handler = default_dest_handler(
fault_handler,
test_sender.clone(),
check_timer_expired.clone(),
);
let (src_path, dest_path) = init_full_filenames(); let (src_path, dest_path) = init_full_filenames();
assert!(!Path::exists(&dest_path)); assert!(!Path::exists(&dest_path));
let handler = Self { let handler = Self {
check_timer_expired, check_timer_expired,
test_sender,
handler: dest_handler, handler: dest_handler,
src_path, src_path,
dest_path, dest_path,
@ -1134,7 +1148,7 @@ mod tests {
user: &mut TestCfdpUser, user: &mut TestCfdpUser,
offset: u64, offset: u64,
file_data_chunk: &[u8], file_data_chunk: &[u8],
) -> Result<(), DestError> { ) -> Result<u32, DestError> {
let filedata_pdu = let filedata_pdu =
FileDataPdu::new_no_seg_metadata(self.pdu_header, offset, file_data_chunk); FileDataPdu::new_no_seg_metadata(self.pdu_header, offset, file_data_chunk);
filedata_pdu filedata_pdu
@ -1157,7 +1171,7 @@ mod tests {
&mut self, &mut self,
user: &mut TestCfdpUser, user: &mut TestCfdpUser,
expected_full_data: Vec<u8>, expected_full_data: Vec<u8>,
) -> Result<(), DestError> { ) -> Result<u32, DestError> {
self.expected_full_data = expected_full_data; self.expected_full_data = expected_full_data;
let eof_pdu = create_no_error_eof(&self.expected_full_data, &self.pdu_header); 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); let packet_info = create_packet_info(&eof_pdu, &mut self.buf);
@ -1218,6 +1232,7 @@ mod tests {
fn default_dest_handler( fn default_dest_handler(
test_fault_handler: TestFaultHandler, test_fault_handler: TestFaultHandler,
test_packet_sender: TestCfdpSender,
check_timer_expired: Arc<AtomicBool>, check_timer_expired: Arc<AtomicBool>,
) -> DestinationHandler { ) -> DestinationHandler {
let local_entity_cfg = LocalEntityConfig { let local_entity_cfg = LocalEntityConfig {
@ -1227,6 +1242,8 @@ mod tests {
}; };
DestinationHandler::new( DestinationHandler::new(
local_entity_cfg, local_entity_cfg,
2048,
Box::new(test_packet_sender),
Box::<NativeFilestore>::default(), Box::<NativeFilestore>::default(),
Box::new(basic_remote_cfg_table()), Box::new(basic_remote_cfg_table()),
Box::new(TestCheckTimerCreator::new(check_timer_expired)), Box::new(TestCheckTimerCreator::new(check_timer_expired)),
@ -1284,7 +1301,8 @@ mod tests {
#[test] #[test]
fn test_basic() { fn test_basic() {
let fault_handler = TestFaultHandler::default(); let fault_handler = TestFaultHandler::default();
let dest_handler = default_dest_handler(fault_handler.clone(), Arc::default()); let test_sender = TestCfdpSender::default();
let dest_handler = default_dest_handler(fault_handler.clone(), test_sender, Arc::default());
assert!(dest_handler.transmission_mode().is_none()); assert!(dest_handler.transmission_mode().is_none());
assert!(fault_handler.all_queues_empty()); assert!(fault_handler.all_queues_empty());
} }

View File

@ -31,7 +31,23 @@ pub enum EntityType {
Receiving, Receiving,
} }
/// Generic abstraction for a check timer which has different functionality depending on whether pub enum TimerContext {
CheckLimit {
local_id: UnsignedByteField,
remote_id: UnsignedByteField,
entity_type: EntityType,
},
NakActivity(f32),
PositiveAck(f32),
}
/// Generic abstraction for a check timer which 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 /// the using entity is the sending entity or the receiving entity for the unacknowledged
/// transmission mode. /// transmission mode.
/// ///
@ -42,6 +58,18 @@ pub enum EntityType {
/// For the receiving entity, this timer determines the expiry period for incrementing a check /// 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 /// 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. /// 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 CheckTimer: Debug { pub trait CheckTimer: Debug {
fn has_expired(&self) -> bool; fn has_expired(&self) -> bool;
fn reset(&mut self); fn reset(&mut self);
@ -50,19 +78,14 @@ pub trait CheckTimer: Debug {
/// A generic trait which allows CFDP entities to create check timers which are required to /// 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 /// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
/// and 4.6.3.3. The [CheckTimer] documentation provides more information about the purpose of the /// and 4.6.3.3. The [CheckTimer] documentation provides more information about the purpose of the
/// check timer. /// check timer in the context of CFDP.
/// ///
/// This trait also allows the creation of different check timers depending on /// This trait also allows the creation of different check timers depending on context and purpose
/// the ID of the local entity, the ID of the remote entity for a given transaction, and the /// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
/// type of entity. /// other factors.
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
pub trait CheckTimerCreator { pub trait CheckTimerCreator {
fn get_check_timer_provider( fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CheckTimer>;
&self,
local_id: &UnsignedByteField,
remote_id: &UnsignedByteField,
entity_type: EntityType,
) -> Box<dyn CheckTimer>;
} }
/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime. /// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.