CFDP extracted to library #201
@ -6,9 +6,9 @@ use super::{
|
|||||||
filestore::{FilestoreError, NativeFilestore, VirtualFilestore},
|
filestore::{FilestoreError, NativeFilestore, VirtualFilestore},
|
||||||
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams},
|
user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams},
|
||||||
CheckTimerProviderCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo,
|
CheckTimerProviderCreator, CountdownProvider, EntityType, LocalEntityConfig, PacketInfo,
|
||||||
PacketTarget, RemoteEntityConfig, RemoteEntityConfigProvider, State, StdCheckTimer,
|
PacketTarget, PduSendProvider, RemoteEntityConfig, RemoteEntityConfigProvider, State,
|
||||||
StdCheckTimerCreator, StdRemoteEntityConfigProvider, TimerContext, TransactionId,
|
StdCheckTimer, StdCheckTimerCreator, StdRemoteEntityConfigProvider, TimerContext,
|
||||||
TransactionStep,
|
TransactionId, UserFaultHookProvider,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use spacepackets::{
|
use spacepackets::{
|
||||||
@ -42,6 +42,18 @@ enum CompletionDisposition {
|
|||||||
Cancelled = 1,
|
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)]
|
#[derive(Debug)]
|
||||||
struct TransferState<CheckTimer: CountdownProvider> {
|
struct TransferState<CheckTimer: CountdownProvider> {
|
||||||
transaction_id: Option<TransactionId>,
|
transaction_id: Option<TransactionId>,
|
||||||
@ -140,9 +152,12 @@ impl<CheckTimer: CountdownProvider> TransactionParams<CheckTimer> {
|
|||||||
pub enum DestError {
|
pub enum DestError {
|
||||||
/// File directive expected, but none specified
|
/// File directive expected, but none specified
|
||||||
#[error("expected file directive")]
|
#[error("expected file directive")]
|
||||||
DirectiveExpected,
|
DirectiveFieldEmpty,
|
||||||
#[error("can not process packet type {0:?}")]
|
#[error("can not process packet type {pdu_type:?} with directive type {directive_type:?}")]
|
||||||
CantProcessPacketType(FileDirectiveType),
|
CantProcessPacketType {
|
||||||
|
pdu_type: PduType,
|
||||||
|
directive_type: Option<FileDirectiveType>,
|
||||||
|
},
|
||||||
#[error("can not process file data PDUs in current state")]
|
#[error("can not process file data PDUs in current state")]
|
||||||
WrongStateForFileDataAndEof,
|
WrongStateForFileDataAndEof,
|
||||||
// Received new metadata PDU while being already being busy with a file transfer.
|
// Received new metadata PDU while being already being busy with a file transfer.
|
||||||
@ -168,15 +183,6 @@ pub enum DestError {
|
|||||||
NoRemoteCfgFound(UnsignedByteField),
|
NoRemoteCfgFound(UnsignedByteField),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait CfdpPacketSender: Send {
|
|
||||||
fn send_pdu(
|
|
||||||
&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.
|
||||||
@ -192,13 +198,14 @@ pub trait CfdpPacketSender: Send {
|
|||||||
/// user and passed as a constructor parameter. The number of generated packets is returned
|
/// user and passed as a constructor parameter. The number of generated packets is returned
|
||||||
/// by the state machine call.
|
/// by the state machine call.
|
||||||
pub struct DestinationHandler<
|
pub struct DestinationHandler<
|
||||||
PduSender: CfdpPacketSender,
|
PduSender: PduSendProvider,
|
||||||
|
UserFaultHook: UserFaultHookProvider,
|
||||||
Vfs: VirtualFilestore,
|
Vfs: VirtualFilestore,
|
||||||
RemoteCfgTable: RemoteEntityConfigProvider,
|
RemoteCfgTable: RemoteEntityConfigProvider,
|
||||||
CheckTimerCreator: CheckTimerProviderCreator<CheckTimer = CheckTimerProvider>,
|
CheckTimerCreator: CheckTimerProviderCreator<CheckTimer = CheckTimerProvider>,
|
||||||
CheckTimerProvider: CountdownProvider,
|
CheckTimerProvider: CountdownProvider,
|
||||||
> {
|
> {
|
||||||
local_cfg: LocalEntityConfig,
|
local_cfg: LocalEntityConfig<UserFaultHook>,
|
||||||
step: TransactionStep,
|
step: TransactionStep,
|
||||||
state: State,
|
state: State,
|
||||||
tparams: TransactionParams<CheckTimerProvider>,
|
tparams: TransactionParams<CheckTimerProvider>,
|
||||||
@ -210,8 +217,9 @@ pub struct DestinationHandler<
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
pub type StdDestinationHandler<PduSender> = DestinationHandler<
|
pub type StdDestinationHandler<PduSender, UserFaultHook> = DestinationHandler<
|
||||||
PduSender,
|
PduSender,
|
||||||
|
UserFaultHook,
|
||||||
NativeFilestore,
|
NativeFilestore,
|
||||||
StdRemoteEntityConfigProvider,
|
StdRemoteEntityConfigProvider,
|
||||||
StdCheckTimerCreator,
|
StdCheckTimerCreator,
|
||||||
@ -219,12 +227,21 @@ pub type StdDestinationHandler<PduSender> = DestinationHandler<
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
impl<
|
impl<
|
||||||
PduSender: CfdpPacketSender,
|
PduSender: PduSendProvider,
|
||||||
|
UserFaultHook: UserFaultHookProvider,
|
||||||
Vfs: VirtualFilestore,
|
Vfs: VirtualFilestore,
|
||||||
RemoteCfgTable: RemoteEntityConfigProvider,
|
RemoteCfgTable: RemoteEntityConfigProvider,
|
||||||
CheckTimerCreator: CheckTimerProviderCreator<CheckTimer = CheckTimerProvider>,
|
CheckTimerCreator: CheckTimerProviderCreator<CheckTimer = CheckTimerProvider>,
|
||||||
CheckTimerProvider: CountdownProvider,
|
CheckTimerProvider: CountdownProvider,
|
||||||
> DestinationHandler<PduSender, Vfs, RemoteCfgTable, CheckTimerCreator, CheckTimerProvider>
|
>
|
||||||
|
DestinationHandler<
|
||||||
|
PduSender,
|
||||||
|
UserFaultHook,
|
||||||
|
Vfs,
|
||||||
|
RemoteCfgTable,
|
||||||
|
CheckTimerCreator,
|
||||||
|
CheckTimerProvider,
|
||||||
|
>
|
||||||
{
|
{
|
||||||
/// Constructs a new destination handler.
|
/// Constructs a new destination handler.
|
||||||
///
|
///
|
||||||
@ -246,9 +263,9 @@ impl<
|
|||||||
/// * `check_timer_creator` - This is used by the CFDP handler to generate timers required
|
/// * `check_timer_creator` - This is used by the CFDP handler to generate timers required
|
||||||
/// by various tasks.
|
/// by various tasks.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
local_cfg: LocalEntityConfig,
|
local_cfg: LocalEntityConfig<UserFaultHook>,
|
||||||
max_packet_len: usize,
|
max_packet_len: usize,
|
||||||
packet_sender: PduSender,
|
pdu_sender: PduSender,
|
||||||
vfs: Vfs,
|
vfs: Vfs,
|
||||||
remote_cfg_table: RemoteCfgTable,
|
remote_cfg_table: RemoteCfgTable,
|
||||||
check_timer_creator: CheckTimerCreator,
|
check_timer_creator: CheckTimerCreator,
|
||||||
@ -259,7 +276,7 @@ impl<
|
|||||||
state: State::Idle,
|
state: State::Idle,
|
||||||
tparams: Default::default(),
|
tparams: Default::default(),
|
||||||
packet_buf: alloc::vec![0; max_packet_len],
|
packet_buf: alloc::vec![0; max_packet_len],
|
||||||
pdu_sender: packet_sender,
|
pdu_sender,
|
||||||
vfs,
|
vfs,
|
||||||
remote_cfg_table,
|
remote_cfg_table,
|
||||||
check_timer_creator,
|
check_timer_creator,
|
||||||
@ -272,6 +289,8 @@ impl<
|
|||||||
/// The state machine should either be called if a packet with the appropriate destination ID
|
/// 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
|
/// is received, or periodically in IDLE periods to perform all CFDP related tasks, for example
|
||||||
/// checking for timeouts or missed file segments.
|
/// checking for timeouts or missed file segments.
|
||||||
|
///
|
||||||
|
/// The function returns the number of sent PDU packets on success.
|
||||||
pub fn state_machine(
|
pub fn state_machine(
|
||||||
&mut self,
|
&mut self,
|
||||||
cfdp_user: &mut impl CfdpUser,
|
cfdp_user: &mut impl CfdpUser,
|
||||||
@ -308,14 +327,15 @@ impl<
|
|||||||
if packet_info.target() != PacketTarget::DestEntity {
|
if packet_info.target() != PacketTarget::DestEntity {
|
||||||
// Unwrap is okay here, a PacketInfo for a file data PDU should always have the
|
// Unwrap is okay here, a PacketInfo for a file data PDU should always have the
|
||||||
// destination as the target.
|
// destination as the target.
|
||||||
return Err(DestError::CantProcessPacketType(
|
return Err(DestError::CantProcessPacketType {
|
||||||
packet_info.pdu_directive().unwrap(),
|
pdu_type: packet_info.pdu_type(),
|
||||||
));
|
directive_type: packet_info.pdu_directive(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
match packet_info.pdu_type {
|
match packet_info.pdu_type {
|
||||||
PduType::FileDirective => {
|
PduType::FileDirective => {
|
||||||
if packet_info.pdu_directive.is_none() {
|
if packet_info.pdu_directive.is_none() {
|
||||||
return Err(DestError::DirectiveExpected);
|
return Err(DestError::DirectiveFieldEmpty);
|
||||||
}
|
}
|
||||||
self.handle_file_directive(
|
self.handle_file_directive(
|
||||||
cfdp_user,
|
cfdp_user,
|
||||||
@ -338,12 +358,13 @@ impl<
|
|||||||
FileDirectiveType::FinishedPdu
|
FileDirectiveType::FinishedPdu
|
||||||
| FileDirectiveType::NakPdu
|
| FileDirectiveType::NakPdu
|
||||||
| FileDirectiveType::KeepAlivePdu => {
|
| FileDirectiveType::KeepAlivePdu => {
|
||||||
return Err(DestError::CantProcessPacketType(pdu_directive));
|
return Err(DestError::CantProcessPacketType {
|
||||||
|
pdu_type: PduType::FileDirective,
|
||||||
|
directive_type: Some(pdu_directive),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
FileDirectiveType::AckPdu => {
|
FileDirectiveType::AckPdu => {
|
||||||
todo!(
|
todo!("acknowledged mode not implemented yet")
|
||||||
"check whether ACK pdu handling is applicable by checking the acked directive field"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
FileDirectiveType::MetadataPdu => self.handle_metadata_pdu(raw_packet)?,
|
FileDirectiveType::MetadataPdu => self.handle_metadata_pdu(raw_packet)?,
|
||||||
FileDirectiveType::PromptPdu => self.handle_prompt_pdu(raw_packet)?,
|
FileDirectiveType::PromptPdu => self.handle_prompt_pdu(raw_packet)?,
|
||||||
@ -730,7 +751,7 @@ impl<
|
|||||||
let progress = self.tstate().progress;
|
let progress = self.tstate().progress;
|
||||||
let fh_code = self
|
let fh_code = self
|
||||||
.local_cfg
|
.local_cfg
|
||||||
.default_fault_handler
|
.fault_handler
|
||||||
.get_fault_handler(condition_code);
|
.get_fault_handler(condition_code);
|
||||||
match fh_code {
|
match fh_code {
|
||||||
FaultHandlerCode::NoticeOfCancellation => {
|
FaultHandlerCode::NoticeOfCancellation => {
|
||||||
@ -741,7 +762,7 @@ impl<
|
|||||||
FaultHandlerCode::AbandonTransaction => self.abandon_transaction(),
|
FaultHandlerCode::AbandonTransaction => self.abandon_transaction(),
|
||||||
}
|
}
|
||||||
self.local_cfg
|
self.local_cfg
|
||||||
.default_fault_handler
|
.fault_handler
|
||||||
.report_fault(transaction_id, condition_code, progress)
|
.report_fault(transaction_id, condition_code, progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -804,15 +825,12 @@ impl<
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use core::{
|
use core::{cell::Cell, sync::atomic::AtomicBool};
|
||||||
cell::{Cell, RefCell},
|
use std::fs;
|
||||||
sync::atomic::AtomicBool,
|
|
||||||
};
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use std::println;
|
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 rand::Rng;
|
||||||
use spacepackets::{
|
use spacepackets::{
|
||||||
cfdp::{
|
cfdp::{
|
||||||
@ -824,9 +842,11 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::cfdp::{
|
use crate::cfdp::{
|
||||||
filestore::NativeFilestore, user::OwnedMetadataRecvdParams, CheckTimerProviderCreator,
|
filestore::NativeFilestore,
|
||||||
CountdownProvider, DefaultFaultHandler, IndicationConfig, RemoteEntityConfig,
|
tests::{basic_remote_cfg_table, SentPdu, TestCfdpSender, TestFaultHandler},
|
||||||
StdRemoteEntityConfigProvider, UserFaultHandler, CRC_32,
|
user::OwnedMetadataRecvdParams,
|
||||||
|
CheckTimerProviderCreator, CountdownProvider, FaultHandler, IndicationConfig,
|
||||||
|
RemoteEntityConfig, StdRemoteEntityConfigProvider, CRC_32,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -840,41 +860,6 @@ mod tests {
|
|||||||
pub length: usize,
|
pub length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SentPdu {
|
|
||||||
pdu_type: PduType,
|
|
||||||
file_directive_type: Option<FileDirectiveType>,
|
|
||||||
raw_pdu: Vec<u8>,
|
|
||||||
}
|
|
||||||
#[derive(Default)]
|
|
||||||
struct TestCfdpSender {
|
|
||||||
packet_queue: RefCell<VecDeque<SentPdu>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CfdpPacketSender for TestCfdpSender {
|
|
||||||
fn send_pdu(
|
|
||||||
&self,
|
|
||||||
pdu_type: PduType,
|
|
||||||
file_directive_type: Option<FileDirectiveType>,
|
|
||||||
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<SentPdu> {
|
|
||||||
self.packet_queue.borrow_mut().pop_front()
|
|
||||||
}
|
|
||||||
pub fn queue_empty(&self) -> bool {
|
|
||||||
self.packet_queue.borrow_mut().is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TestCfdpUser {
|
struct TestCfdpUser {
|
||||||
next_expected_seq_num: u64,
|
next_expected_seq_num: u64,
|
||||||
@ -1000,81 +985,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
|
||||||
struct TestFaultHandler {
|
|
||||||
notice_of_suspension_queue: Arc<Mutex<VecDeque<(TransactionId, ConditionCode, u64)>>>,
|
|
||||||
notice_of_cancellation_queue: Arc<Mutex<VecDeque<(TransactionId, ConditionCode, u64)>>>,
|
|
||||||
abandoned_queue: Arc<Mutex<VecDeque<(TransactionId, ConditionCode, u64)>>>,
|
|
||||||
ignored_queue: Arc<Mutex<VecDeque<(TransactionId, ConditionCode, u64)>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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)]
|
#[derive(Debug)]
|
||||||
struct TestCheckTimer {
|
struct TestCheckTimer {
|
||||||
counter: Cell<u32>,
|
counter: Cell<u32>,
|
||||||
@ -1128,6 +1038,7 @@ mod tests {
|
|||||||
|
|
||||||
type TestDestHandler = DestinationHandler<
|
type TestDestHandler = DestinationHandler<
|
||||||
TestCfdpSender,
|
TestCfdpSender,
|
||||||
|
TestFaultHandler,
|
||||||
NativeFilestore,
|
NativeFilestore,
|
||||||
StdRemoteEntityConfigProvider,
|
StdRemoteEntityConfigProvider,
|
||||||
TestCheckTimerCreator,
|
TestCheckTimerCreator,
|
||||||
@ -1177,6 +1088,14 @@ mod tests {
|
|||||||
&self.dest_path
|
&self.dest_path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn all_fault_queues_empty(&self) -> bool {
|
||||||
|
self.handler
|
||||||
|
.local_cfg
|
||||||
|
.user_fault_hook()
|
||||||
|
.borrow()
|
||||||
|
.all_queues_empty()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn indication_cfg_mut(&mut self) -> &mut IndicationConfig {
|
fn indication_cfg_mut(&mut self) -> &mut IndicationConfig {
|
||||||
&mut self.handler.local_cfg.indication_cfg
|
&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(
|
fn default_dest_handler(
|
||||||
test_fault_handler: TestFaultHandler,
|
test_fault_handler: TestFaultHandler,
|
||||||
test_packet_sender: TestCfdpSender,
|
test_packet_sender: TestCfdpSender,
|
||||||
check_timer_expired: Arc<AtomicBool>,
|
check_timer_expired: Arc<AtomicBool>,
|
||||||
) -> DestinationHandler<
|
) -> TestDestHandler {
|
||||||
TestCfdpSender,
|
|
||||||
NativeFilestore,
|
|
||||||
StdRemoteEntityConfigProvider,
|
|
||||||
TestCheckTimerCreator,
|
|
||||||
TestCheckTimer,
|
|
||||||
> {
|
|
||||||
let local_entity_cfg = LocalEntityConfig {
|
let local_entity_cfg = LocalEntityConfig {
|
||||||
id: REMOTE_ID.into(),
|
id: REMOTE_ID.into(),
|
||||||
indication_cfg: IndicationConfig::default(),
|
indication_cfg: IndicationConfig::default(),
|
||||||
default_fault_handler: DefaultFaultHandler::new(Box::new(test_fault_handler)),
|
fault_handler: FaultHandler::new(test_fault_handler),
|
||||||
};
|
};
|
||||||
DestinationHandler::new(
|
DestinationHandler::new(
|
||||||
local_entity_cfg,
|
local_entity_cfg,
|
||||||
@ -1390,15 +1288,20 @@ mod tests {
|
|||||||
fn test_basic() {
|
fn test_basic() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
let fault_handler = TestFaultHandler::default();
|
||||||
let test_sender = TestCfdpSender::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!(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]
|
#[test]
|
||||||
fn test_empty_file_transfer_not_acked_no_closure() {
|
fn test_empty_file_transfer_not_acked_no_closure() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
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);
|
let mut test_user = testbench.test_user_from_cached_paths(0);
|
||||||
testbench
|
testbench
|
||||||
.generic_transfer_init(&mut test_user, 0)
|
.generic_transfer_init(&mut test_user, 0)
|
||||||
@ -1407,7 +1310,7 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.generic_eof_no_error(&mut test_user, Vec::new())
|
.generic_eof_no_error(&mut test_user, Vec::new())
|
||||||
.expect("EOF no error insertion failed");
|
.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());
|
assert!(testbench.handler.pdu_sender.queue_empty());
|
||||||
testbench.state_check(State::Idle, TransactionStep::Idle);
|
testbench.state_check(State::Idle, TransactionStep::Idle);
|
||||||
}
|
}
|
||||||
@ -1419,7 +1322,7 @@ mod tests {
|
|||||||
let file_size = file_data.len() as u64;
|
let file_size = file_data.len() as u64;
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = testbench.test_user_from_cached_paths(file_size);
|
||||||
testbench
|
testbench
|
||||||
.generic_transfer_init(&mut test_user, file_size)
|
.generic_transfer_init(&mut test_user, file_size)
|
||||||
@ -1431,7 +1334,7 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.generic_eof_no_error(&mut test_user, file_data.to_vec())
|
.generic_eof_no_error(&mut test_user, file_data.to_vec())
|
||||||
.expect("EOF no error insertion failed");
|
.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());
|
assert!(testbench.handler.pdu_sender.queue_empty());
|
||||||
testbench.state_check(State::Idle, TransactionStep::Idle);
|
testbench.state_check(State::Idle, TransactionStep::Idle);
|
||||||
}
|
}
|
||||||
@ -1445,7 +1348,7 @@ mod tests {
|
|||||||
let segment_len = 256;
|
let segment_len = 256;
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = testbench.test_user_from_cached_paths(file_size);
|
||||||
testbench
|
testbench
|
||||||
.generic_transfer_init(&mut test_user, file_size)
|
.generic_transfer_init(&mut test_user, file_size)
|
||||||
@ -1464,7 +1367,7 @@ mod tests {
|
|||||||
testbench
|
testbench
|
||||||
.generic_eof_no_error(&mut test_user, random_data.to_vec())
|
.generic_eof_no_error(&mut test_user, random_data.to_vec())
|
||||||
.expect("EOF no error insertion failed");
|
.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());
|
assert!(testbench.handler.pdu_sender.queue_empty());
|
||||||
testbench.state_check(State::Idle, TransactionStep::Idle);
|
testbench.state_check(State::Idle, TransactionStep::Idle);
|
||||||
}
|
}
|
||||||
@ -1478,7 +1381,7 @@ mod tests {
|
|||||||
let segment_len = 256;
|
let segment_len = 256;
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = testbench.test_user_from_cached_paths(file_size);
|
||||||
let transaction_id = testbench
|
let transaction_id = testbench
|
||||||
.generic_transfer_init(&mut test_user, file_size)
|
.generic_transfer_init(&mut test_user, file_size)
|
||||||
@ -1507,10 +1410,10 @@ mod tests {
|
|||||||
.handler
|
.handler
|
||||||
.state_machine(&mut test_user, None)
|
.state_machine(&mut test_user, None)
|
||||||
.expect("fsm failure");
|
.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!(fault_handler.ignored_queue.len(), 1);
|
||||||
assert_eq!(ignored_queue.len(), 1);
|
let cancelled = fault_handler.ignored_queue.front().unwrap();
|
||||||
let cancelled = *ignored_queue.front().unwrap();
|
|
||||||
assert_eq!(cancelled.0, transaction_id);
|
assert_eq!(cancelled.0, transaction_id);
|
||||||
assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure);
|
assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure);
|
||||||
assert_eq!(cancelled.2, segment_len as u64);
|
assert_eq!(cancelled.2, segment_len as u64);
|
||||||
@ -1527,7 +1430,7 @@ mod tests {
|
|||||||
let segment_len = 256;
|
let segment_len = 256;
|
||||||
|
|
||||||
let fault_handler = TestFaultHandler::default();
|
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 mut test_user = testbench.test_user_from_cached_paths(file_size);
|
||||||
let transaction_id = testbench
|
let transaction_id = testbench
|
||||||
.generic_transfer_init(&mut test_user, file_size)
|
.generic_transfer_init(&mut test_user, file_size)
|
||||||
@ -1560,27 +1463,25 @@ mod tests {
|
|||||||
.expect("fsm error");
|
.expect("fsm error");
|
||||||
testbench.state_check(State::Idle, TransactionStep::Idle);
|
testbench.state_check(State::Idle, TransactionStep::Idle);
|
||||||
|
|
||||||
assert!(fault_handler
|
let fault_hook = testbench.handler.local_cfg.user_fault_hook().borrow();
|
||||||
.notice_of_suspension_queue
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.is_empty());
|
|
||||||
|
|
||||||
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);
|
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.0, transaction_id);
|
||||||
assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure);
|
assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure);
|
||||||
assert_eq!(cancelled.2, segment_len as u64);
|
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);
|
assert_eq!(cancelled_queue.len(), 1);
|
||||||
let cancelled = *cancelled_queue.front().unwrap();
|
let cancelled = *cancelled_queue.front().unwrap();
|
||||||
assert_eq!(cancelled.0, transaction_id);
|
assert_eq!(cancelled.0, transaction_id);
|
||||||
assert_eq!(cancelled.1, ConditionCode::CheckLimitReached);
|
assert_eq!(cancelled.1, ConditionCode::CheckLimitReached);
|
||||||
assert_eq!(cancelled.2, segment_len as u64);
|
assert_eq!(cancelled.2, segment_len as u64);
|
||||||
|
|
||||||
drop(cancelled_queue);
|
drop(fault_hook);
|
||||||
|
|
||||||
assert!(testbench.handler.pdu_sender.queue_empty());
|
assert!(testbench.handler.pdu_sender.queue_empty());
|
||||||
|
|
||||||
@ -1610,7 +1511,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_file_transfer_with_closure() {
|
fn test_file_transfer_with_closure() {
|
||||||
let fault_handler = TestFaultHandler::default();
|
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);
|
let mut test_user = testbench.test_user_from_cached_paths(0);
|
||||||
testbench
|
testbench
|
||||||
.generic_transfer_init(&mut test_user, 0)
|
.generic_transfer_init(&mut test_user, 0)
|
||||||
@ -1620,7 +1521,7 @@ mod tests {
|
|||||||
.generic_eof_no_error(&mut test_user, Vec::new())
|
.generic_eof_no_error(&mut test_user, Vec::new())
|
||||||
.expect("EOF no error insertion failed");
|
.expect("EOF no error insertion failed");
|
||||||
assert_eq!(sent_packets, 1);
|
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.
|
// The Finished PDU was sent, so the state machine is done.
|
||||||
testbench.state_check(State::Idle, TransactionStep::Idle);
|
testbench.state_check(State::Idle, TransactionStep::Idle);
|
||||||
assert!(!testbench.handler.pdu_sender.queue_empty());
|
assert!(!testbench.handler.pdu_sender.queue_empty());
|
||||||
|
@ -12,8 +12,6 @@ use spacepackets::{
|
|||||||
util::{UnsignedByteField, UnsignedEnum},
|
util::{UnsignedByteField, UnsignedEnum},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "alloc")]
|
|
||||||
use alloc::boxed::Box;
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
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
|
/// implement some CFDP features like fault handler logging, which would not be possible
|
||||||
/// generically otherwise.
|
/// 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].
|
/// will be called depending on the [FaultHandlerCode].
|
||||||
pub trait UserFaultHandler {
|
pub trait UserFaultHookProvider {
|
||||||
fn notice_of_suspension_cb(
|
fn notice_of_suspension_cb(
|
||||||
&mut self,
|
&mut self,
|
||||||
transaction_id: TransactionId,
|
transaction_id: TransactionId,
|
||||||
@ -333,6 +331,37 @@ pub trait UserFaultHandler {
|
|||||||
fn ignore_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)]
|
||||||
|
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
|
/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP
|
||||||
/// standard.
|
/// standard.
|
||||||
///
|
///
|
||||||
@ -353,14 +382,14 @@ pub trait UserFaultHandler {
|
|||||||
/// These defaults can be overriden by using the [Self::set_fault_handler] method.
|
/// 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
|
/// Please note that in any case, fault handler overrides can be specified by the sending CFDP
|
||||||
/// entity.
|
/// entity.
|
||||||
pub struct DefaultFaultHandler {
|
pub struct FaultHandler<UserHandler: UserFaultHookProvider> {
|
||||||
handler_array: [FaultHandlerCode; 10],
|
handler_array: [FaultHandlerCode; 10],
|
||||||
// Could also change the user fault handler trait to have non mutable methods, but that limits
|
// Could also change the user fault handler trait to have non mutable methods, but that limits
|
||||||
// flexbility on the user side..
|
// flexbility on the user side..
|
||||||
user_fault_handler: RefCell<Box<dyn UserFaultHandler + Send>>,
|
pub user_hook: RefCell<UserHandler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DefaultFaultHandler {
|
impl<UserHandler: UserFaultHookProvider> FaultHandler<UserHandler> {
|
||||||
fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
|
fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
|
||||||
Some(match conditon_code {
|
Some(match conditon_code {
|
||||||
ConditionCode::PositiveAckLimitReached => 0,
|
ConditionCode::PositiveAckLimitReached => 0,
|
||||||
@ -389,7 +418,7 @@ impl DefaultFaultHandler {
|
|||||||
self.handler_array[array_idx.unwrap()] = fault_handler;
|
self.handler_array[array_idx.unwrap()] = fault_handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(user_fault_handler: Box<dyn UserFaultHandler + Send>) -> Self {
|
pub fn new(user_fault_handler: UserHandler) -> Self {
|
||||||
let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
|
let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
|
||||||
init_array
|
init_array
|
||||||
[Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
|
[Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
|
||||||
@ -398,7 +427,7 @@ impl DefaultFaultHandler {
|
|||||||
.unwrap()] = FaultHandlerCode::IgnoreError;
|
.unwrap()] = FaultHandlerCode::IgnoreError;
|
||||||
Self {
|
Self {
|
||||||
handler_array: init_array,
|
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;
|
return FaultHandlerCode::IgnoreError;
|
||||||
}
|
}
|
||||||
let fh_code = self.handler_array[array_idx.unwrap()];
|
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 {
|
match fh_code {
|
||||||
FaultHandlerCode::NoticeOfCancellation => {
|
FaultHandlerCode::NoticeOfCancellation => {
|
||||||
handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
|
handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
|
||||||
@ -462,10 +491,29 @@ impl Default for IndicationConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LocalEntityConfig {
|
pub struct LocalEntityConfig<UserFaultHook: UserFaultHookProvider> {
|
||||||
pub id: UnsignedByteField,
|
pub id: UnsignedByteField,
|
||||||
pub indication_cfg: IndicationConfig,
|
pub indication_cfg: IndicationConfig,
|
||||||
pub default_fault_handler: DefaultFaultHandler,
|
pub fault_handler: FaultHandler<UserFaultHook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<UserFaultHook: UserFaultHookProvider> LocalEntityConfig<UserFaultHook> {
|
||||||
|
pub fn user_fault_hook_mut(&mut self) -> &mut RefCell<UserFaultHook> {
|
||||||
|
&mut self.fault_handler.user_hook
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user_fault_hook(&self) -> &RefCell<UserFaultHook> {
|
||||||
|
&self.fault_handler.user_hook
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PduSendProvider {
|
||||||
|
fn send_pdu(
|
||||||
|
&self,
|
||||||
|
pdu_type: PduType,
|
||||||
|
file_directive_type: Option<FileDirectiveType>,
|
||||||
|
raw_pdu: &[u8],
|
||||||
|
) -> Result<(), PduError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence
|
/// 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)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub enum State {
|
pub enum State {
|
||||||
@ -627,21 +663,147 @@ impl<'raw> PacketInfo<'raw> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(crate) mod tests {
|
||||||
use spacepackets::cfdp::{
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use alloc::{collections::VecDeque, vec::Vec};
|
||||||
|
use spacepackets::{
|
||||||
|
cfdp::{
|
||||||
lv::Lv,
|
lv::Lv,
|
||||||
pdu::{
|
pdu::{
|
||||||
eof::EofPdu,
|
eof::EofPdu,
|
||||||
file_data::FileDataPdu,
|
file_data::FileDataPdu,
|
||||||
metadata::{MetadataGenericParams, MetadataPduCreator},
|
metadata::{MetadataGenericParams, MetadataPduCreator},
|
||||||
CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket,
|
CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket,
|
||||||
},
|
},
|
||||||
PduType,
|
ChecksumType, ConditionCode, PduType, TransmissionMode,
|
||||||
|
},
|
||||||
|
util::UnsignedByteFieldU16,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::cfdp::PacketTarget;
|
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<FileDirectiveType>,
|
||||||
|
pub raw_pdu: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TestCfdpSender {
|
||||||
|
pub packet_queue: RefCell<VecDeque<SentPdu>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PduSendProvider for TestCfdpSender {
|
||||||
|
fn send_pdu(
|
||||||
|
&self,
|
||||||
|
pdu_type: PduType,
|
||||||
|
file_directive_type: Option<FileDirectiveType>,
|
||||||
|
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<SentPdu> {
|
||||||
|
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 {
|
fn generic_pdu_header() -> PduHeader {
|
||||||
let pdu_conf = CommonPduConfig::default();
|
let pdu_conf = CommonPduConfig::default();
|
||||||
|
@ -1,15 +1,217 @@
|
|||||||
#![allow(dead_code)]
|
use spacepackets::cfdp::{pdu::FileDirectiveType, PduType};
|
||||||
use spacepackets::util::UnsignedByteField;
|
|
||||||
|
|
||||||
pub struct SourceHandler {
|
use super::{
|
||||||
id: UnsignedByteField,
|
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 struct FileParams {
|
||||||
pub fn new(id: impl Into<UnsignedByteField>) -> Self {
|
pub progress: usize,
|
||||||
Self { id: id.into() }
|
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<FileDirectiveType>,
|
||||||
|
},
|
||||||
|
#[error("unexpected file data PDU")]
|
||||||
|
UnexpectedFileDataPdu,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SourceHandler<
|
||||||
|
PduSender: PduSendProvider,
|
||||||
|
UserFaultHook: UserFaultHookProvider,
|
||||||
|
Vfs: VirtualFilestore,
|
||||||
|
RemoteCfgTable: RemoteEntityConfigProvider,
|
||||||
|
> {
|
||||||
|
local_cfg: LocalEntityConfig<UserFaultHook>,
|
||||||
|
pdu_sender: PduSender,
|
||||||
|
remote_cfg_table: RemoteCfgTable,
|
||||||
|
vfs: Vfs,
|
||||||
|
state_helper: StateHelper,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<
|
||||||
|
PduSender: PduSendProvider,
|
||||||
|
UserFaultHook: UserFaultHookProvider,
|
||||||
|
Vfs: VirtualFilestore,
|
||||||
|
RemoteCfgTable: RemoteEntityConfigProvider,
|
||||||
|
> SourceHandler<PduSender, UserFaultHook, Vfs, RemoteCfgTable>
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
cfg: LocalEntityConfig<UserFaultHook>,
|
||||||
|
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<u32, SourceError> {
|
||||||
|
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<u32, SourceError> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_finished_pdu(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_nak_pdu(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_keep_alive_pdu(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user