2389 lines
92 KiB
Rust
2389 lines
92 KiB
Rust
//! # CFDP Source Entity Module
|
|
//!
|
|
//! The [SourceHandler] is the primary component of this module which converts a
|
|
//! [ReadablePutRequest] into all packet data units (PDUs) which need to be sent to a remote
|
|
//! CFDP entity to perform a File Copy operation to a remote entity.
|
|
//!
|
|
//! The source entity allows freedom communication by using a user-provided [PduSender] instance
|
|
//! to send all generated PDUs. It should be noted that for regular file transfers, each
|
|
//! [SourceHandler::state_machine] call will map to one generated file data PDU. This allows
|
|
//! flow control for the user of the state machine.
|
|
//!
|
|
//! The [SourceHandler::state_machine] will generally perform the following steps after a valid
|
|
//! put request was received through the [SourceHandler::put_request] method:
|
|
//!
|
|
//! 1. Generate the Metadata PDU to be sent to a remote CFDP entity. You can use the
|
|
//! [spacepackets::cfdp::pdu::metadata::MetadataPduReader] to inspect the generated PDU.
|
|
//! 2. Generate all File Data PDUs to be sent to a remote CFDP entity if applicable (file not
|
|
//! empty). The PDU(s) can be inspected using the [spacepackets::cfdp::pdu::file_data::FileDataPdu] reader.
|
|
//! 3. Generate an EOF PDU to be sent to a remote CFDP entity. The PDU can be inspected using
|
|
//! the [spacepackets::cfdp::pdu::eof::EofPdu] reader.
|
|
//!
|
|
//! If this is an unacknowledged transfer with no transaction closure, the file transfer will be
|
|
//! done after these steps. In any other case:
|
|
//!
|
|
//! ### Unacknowledged transfer with requested closure
|
|
//!
|
|
//! 4. A Finished PDU will be awaited, for example one generated using
|
|
//! [spacepackets::cfdp::pdu::finished::FinishedPduCreator].
|
|
//!
|
|
//! ### Acknowledged transfer (*not implemented yet*)
|
|
//!
|
|
//! 4. A EOF ACK packet will be awaited, for example one generated using
|
|
//! [spacepackets::cfdp::pdu::ack::AckPdu].
|
|
//! 5. A Finished PDU will be awaited, for example one generated using
|
|
//! [spacepackets::cfdp::pdu::finished::FinishedPduCreator].
|
|
//! 6. A finished PDU ACK packet will be generated to be sent to the remote CFDP entity.
|
|
//! The [spacepackets::cfdp::pdu::finished::FinishedPduReader] can be used to inspect the
|
|
//! generated PDU.
|
|
use core::{
|
|
cell::{Cell, RefCell},
|
|
ops::ControlFlow,
|
|
str::Utf8Error,
|
|
};
|
|
|
|
use spacepackets::{
|
|
ByteConversionError,
|
|
cfdp::{
|
|
ConditionCode, Direction, FaultHandlerCode, LargeFileFlag, PduType, SegmentMetadataFlag,
|
|
SegmentationControl, TransactionStatus, TransmissionMode,
|
|
lv::Lv,
|
|
pdu::{
|
|
CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket,
|
|
ack::AckPdu,
|
|
eof::EofPdu,
|
|
file_data::{
|
|
FileDataPduCreatorWithReservedDatafield,
|
|
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header,
|
|
},
|
|
finished::{DeliveryCode, FileStatus, FinishedPduReader},
|
|
metadata::{MetadataGenericParams, MetadataPduCreator},
|
|
nak::NakPduReader,
|
|
},
|
|
},
|
|
util::{UnsignedByteField, UnsignedEnum},
|
|
};
|
|
|
|
use spacepackets::seq_count::SequenceCounter;
|
|
|
|
use crate::{
|
|
DummyPduProvider, EntityType, FaultInfo, GenericSendError, PduProvider, PositiveAckParams,
|
|
TimerCreator, time::Countdown,
|
|
};
|
|
|
|
use super::{
|
|
LocalEntityConfig, PacketTarget, PduSender, RemoteConfigStore, RemoteEntityConfig, State,
|
|
TransactionId, UserFaultHook,
|
|
filestore::{FilestoreError, VirtualFilestore},
|
|
request::{ReadablePutRequest, StaticPutRequestCacher},
|
|
user::{CfdpUser, TransactionFinishedParams},
|
|
};
|
|
|
|
/// This enumeration models the different transaction steps of the source entity handler.
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
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,
|
|
NoticeOfCompletion = 10,
|
|
}
|
|
|
|
#[derive(Default, Debug, Copy, Clone)]
|
|
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,
|
|
/// The checksum is cached to avoid expensive re-calculation when the EOF PDU needs to be
|
|
/// re-sent.
|
|
pub checksum_completed_file: Option<u32>,
|
|
}
|
|
|
|
// Explicit choice to put all simple internal fields into Cells.
|
|
// I think this is more efficient than wrapping the whole helper into a RefCell, especially
|
|
// because some of the individual fields are used frequently.
|
|
struct StateHelper {
|
|
step: Cell<TransactionStep>,
|
|
state: Cell<super::State>,
|
|
num_packets_ready: Cell<u32>,
|
|
}
|
|
|
|
impl Default for StateHelper {
|
|
fn default() -> Self {
|
|
Self {
|
|
state: Cell::new(super::State::Idle),
|
|
step: Cell::new(TransactionStep::Idle),
|
|
num_packets_ready: Cell::new(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl StateHelper {
|
|
#[allow(dead_code)]
|
|
pub fn reset(&self) {
|
|
self.step.set(TransactionStep::Idle);
|
|
self.state.set(super::State::Idle);
|
|
self.num_packets_ready.set(0);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct FinishedParams {
|
|
condition_code: ConditionCode,
|
|
delivery_code: DeliveryCode,
|
|
file_status: FileStatus,
|
|
}
|
|
|
|
#[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 PDU")]
|
|
UnexpectedPdu {
|
|
pdu_type: PduType,
|
|
directive_type: Option<FileDirectiveType>,
|
|
},
|
|
#[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("invalid NAK PDU received")]
|
|
InvalidNakPdu,
|
|
#[error("error related to PDU creation: {0}")]
|
|
Pdu(#[from] PduError),
|
|
#[error("cfdp feature not implemented")]
|
|
NotImplemented,
|
|
#[error("issue sending PDU: {0}")]
|
|
SendError(#[from] GenericSendError),
|
|
}
|
|
|
|
#[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),
|
|
#[error("source file does not have valid UTF8 format: {0}")]
|
|
SourceFileNotValidUtf8(#[from] Utf8Error),
|
|
#[error("source file does not exist")]
|
|
FileDoesNotExist,
|
|
#[error("filestore error: {0}")]
|
|
FilestoreError(#[from] FilestoreError),
|
|
}
|
|
|
|
#[derive(Debug, Default, Clone, Copy)]
|
|
pub struct AnomalyTracker {
|
|
invalid_ack_directive_code: u8,
|
|
}
|
|
|
|
#[derive(Debug, Default, PartialEq, Eq)]
|
|
pub enum FsmContext {
|
|
#[default]
|
|
None,
|
|
ResetWhenPossible,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct TransactionParams<CountdownInstance: Countdown> {
|
|
transaction_id: Option<TransactionId>,
|
|
remote_cfg: Option<RemoteEntityConfig>,
|
|
transmission_mode: Option<super::TransmissionMode>,
|
|
closure_requested: bool,
|
|
cond_code_eof: Cell<Option<ConditionCode>>,
|
|
finished_params: Option<FinishedParams>,
|
|
// File specific transfer fields
|
|
file_params: FileParams,
|
|
// PDU configuration is cached so it can be re-used for all PDUs generated for file transfers.
|
|
pdu_conf: CommonPduConfig,
|
|
check_timer: Option<CountdownInstance>,
|
|
positive_ack_params: Cell<Option<PositiveAckParams>>,
|
|
ack_timer: RefCell<Option<CountdownInstance>>,
|
|
}
|
|
|
|
impl<CountdownInstance: Countdown> Default for TransactionParams<CountdownInstance> {
|
|
fn default() -> Self {
|
|
Self {
|
|
transaction_id: Default::default(),
|
|
remote_cfg: Default::default(),
|
|
transmission_mode: Default::default(),
|
|
closure_requested: Default::default(),
|
|
cond_code_eof: Default::default(),
|
|
finished_params: Default::default(),
|
|
file_params: Default::default(),
|
|
pdu_conf: Default::default(),
|
|
check_timer: Default::default(),
|
|
positive_ack_params: Default::default(),
|
|
ack_timer: Default::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<CountdownInstance: Countdown> TransactionParams<CountdownInstance> {
|
|
#[inline]
|
|
fn reset(&mut self) {
|
|
self.transaction_id = None;
|
|
self.transmission_mode = None;
|
|
}
|
|
}
|
|
|
|
/// This is the primary CFDP source handler. It models the CFDP source entity, which is
|
|
/// primarily responsible for handling put requests to send files to another CFDP destination
|
|
/// entity.
|
|
///
|
|
/// As such, it contains a state machine to perform all operations necessary to perform a
|
|
/// source-to-destination file transfer. This class uses the user provides [PduSender] to
|
|
/// send the CFDP PDU packets generated by the state machine.
|
|
///
|
|
/// The following core functions are the primary interface:
|
|
///
|
|
/// 1. [Self::put_request] can be used to start transactions, most notably to start
|
|
/// and perform a Copy File procedure to send a file or to send a Proxy Put Request to request
|
|
/// a file.
|
|
/// 2. [Self::state_machine] is the primary interface to execute an
|
|
/// active file transfer. It generates the necessary CFDP PDUs for this process.
|
|
/// This method is also used to insert received packets with the appropriate destination ID
|
|
/// and target handler type into the state machine.
|
|
///
|
|
/// A put request will only be accepted if the handler is in the idle state.
|
|
///
|
|
/// The handler requires the [alloc] feature but will allocated all required memory on construction
|
|
/// time. This means that the handler is still suitable for embedded systems where run-time
|
|
/// allocation is prohibited. Furthermore, it uses the [VirtualFilestore] abstraction to allow
|
|
/// usage on systems without a [std] filesystem.
|
|
/// This handler does not support concurrency out of the box. Instead, if concurrent handling
|
|
/// is required, it is recommended to create a new handler and run all active handlers inside a
|
|
/// thread pool, or move the newly created handler to a new thread.
|
|
pub struct SourceHandler<
|
|
PduSenderInstance: PduSender,
|
|
UserFaultHookInstance: UserFaultHook,
|
|
Vfs: VirtualFilestore,
|
|
RemoteConfigStoreInstance: RemoteConfigStore,
|
|
TimerCreatorInstance: TimerCreator<Countdown = CountdownInstance>,
|
|
CountdownInstance: Countdown,
|
|
SequenceCounterInstance: SequenceCounter,
|
|
> {
|
|
local_cfg: LocalEntityConfig<UserFaultHookInstance>,
|
|
pdu_sender: PduSenderInstance,
|
|
pdu_and_cksum_buffer: RefCell<alloc::vec::Vec<u8>>,
|
|
put_request_cacher: StaticPutRequestCacher,
|
|
remote_cfg_table: RemoteConfigStoreInstance,
|
|
vfs: Vfs,
|
|
state_helper: StateHelper,
|
|
transaction_params: TransactionParams<CountdownInstance>,
|
|
timer_creator: TimerCreatorInstance,
|
|
seq_count_provider: SequenceCounterInstance,
|
|
anomalies: AnomalyTracker,
|
|
}
|
|
|
|
impl<
|
|
PduSenderInstance: PduSender,
|
|
UserFaultHookInstance: UserFaultHook,
|
|
Vfs: VirtualFilestore,
|
|
RemoteConfigStoreInstance: RemoteConfigStore,
|
|
TimerCreatorInstance: TimerCreator<Countdown = CountdownInstance>,
|
|
CountdownInstance: Countdown,
|
|
SequenceCounterInstance: SequenceCounter,
|
|
>
|
|
SourceHandler<
|
|
PduSenderInstance,
|
|
UserFaultHookInstance,
|
|
Vfs,
|
|
RemoteConfigStoreInstance,
|
|
TimerCreatorInstance,
|
|
CountdownInstance,
|
|
SequenceCounterInstance,
|
|
>
|
|
{
|
|
/// Creates a new instance of a source handler.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `cfg` - The local entity configuration for this source handler.
|
|
/// * `pdu_sender` - [PduSender] used to send CFDP PDUs generated by the handler.
|
|
/// * `vfs` - [VirtualFilestore] implementation used by the handler, which decouples 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.
|
|
/// * `put_request_cacher` - The put request cacher is used cache put requests without
|
|
/// requiring run-time allocation.
|
|
/// * `pdu_and_cksum_buf_size` - The handler requires a buffer to generate PDUs and perform
|
|
/// checksum calculations. The user can specify the size of this buffer, so this should be
|
|
/// set to the maximum expected PDU size or a conservative upper bound for this size, for
|
|
/// example 2048 or 4096 bytes.
|
|
/// * `remote_cfg_table` - The [RemoteEntityConfig] used to look up remote
|
|
/// entities and target specific configuration for file copy operations.
|
|
/// * `timer_creator` - [TimerCreator] used by the CFDP handler to generate
|
|
/// timers required by various tasks. This allows to use this handler for embedded systems
|
|
/// where the standard time APIs might not be available.
|
|
/// * `seq_count_provider` - The [SequenceCounter] used to generate the [TransactionId]
|
|
/// which contains an incrementing counter.
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn new(
|
|
cfg: LocalEntityConfig<UserFaultHookInstance>,
|
|
pdu_sender: PduSenderInstance,
|
|
vfs: Vfs,
|
|
put_request_cacher: StaticPutRequestCacher,
|
|
pdu_and_cksum_buf_size: usize,
|
|
remote_cfg_table: RemoteConfigStoreInstance,
|
|
timer_creator: TimerCreatorInstance,
|
|
seq_count_provider: SequenceCounterInstance,
|
|
) -> 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(),
|
|
transaction_params: Default::default(),
|
|
anomalies: Default::default(),
|
|
timer_creator,
|
|
seq_count_provider,
|
|
}
|
|
}
|
|
|
|
/// Calls [Self::state_machine], without inserting a packet.
|
|
pub fn state_machine_no_packet(
|
|
&mut self,
|
|
cfdp_user: &mut impl CfdpUser,
|
|
) -> Result<u32, SourceError> {
|
|
self.state_machine(cfdp_user, None::<&DummyPduProvider>)
|
|
}
|
|
|
|
/// 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,
|
|
pdu: Option<&impl PduProvider>,
|
|
) -> Result<u32, SourceError> {
|
|
let mut sent_packets = 0;
|
|
if let Some(packet) = pdu {
|
|
sent_packets += self.insert_packet(cfdp_user, packet)?;
|
|
}
|
|
match self.state() {
|
|
super::State::Idle => {
|
|
// TODO: In acknowledged mode, add timer handling.
|
|
Ok(0)
|
|
}
|
|
super::State::Busy => {
|
|
sent_packets += self.fsm_busy(cfdp_user, pdu)?;
|
|
Ok(sent_packets)
|
|
}
|
|
super::State::Suspended => {
|
|
// There is now way to suspend the handler currently anyway.
|
|
Ok(0)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn transaction_id(&self) -> Option<TransactionId> {
|
|
self.transaction_params.transaction_id
|
|
}
|
|
|
|
/// Returns the [TransmissionMode] for the active file operation.
|
|
#[inline]
|
|
pub fn transmission_mode(&self) -> Option<super::TransmissionMode> {
|
|
self.transaction_params.transmission_mode
|
|
}
|
|
|
|
/// Get the [TransactionStep], which denotes the exact step of a pending CFDP transaction when
|
|
/// applicable.
|
|
#[inline]
|
|
pub fn step(&self) -> TransactionStep {
|
|
self.state_helper.step.get()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn state(&self) -> State {
|
|
self.state_helper.state.get()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn local_cfg(&self) -> &LocalEntityConfig<UserFaultHookInstance> {
|
|
&self.local_cfg
|
|
}
|
|
|
|
/// This function is used to pass a put request to the source handler, which is
|
|
/// also used to start a file copy operation. As such, this function models the Put.request
|
|
/// CFDP primtiive.
|
|
///
|
|
/// Please note that the source handler can also process one put request at a time.
|
|
/// The caller is responsible of creating a new source handler, one handler can only handle
|
|
/// one file copy request at a time.
|
|
pub fn put_request(
|
|
&mut self,
|
|
put_request: &impl ReadablePutRequest,
|
|
) -> Result<(), PutRequestError> {
|
|
if self.state() != super::State::Idle {
|
|
return Err(PutRequestError::AlreadyBusy);
|
|
}
|
|
self.put_request_cacher.set(put_request)?;
|
|
let remote_cfg = self.remote_cfg_table.get(
|
|
self.put_request_cacher
|
|
.static_fields
|
|
.destination_id
|
|
.value_const(),
|
|
);
|
|
if remote_cfg.is_none() {
|
|
return Err(PutRequestError::NoRemoteCfgFound(
|
|
self.put_request_cacher.static_fields.destination_id,
|
|
));
|
|
}
|
|
let remote_cfg = remote_cfg.unwrap();
|
|
self.state_helper.num_packets_ready.set(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
|
|
};
|
|
if self.put_request_cacher.has_source_file()
|
|
&& !self.vfs.exists(self.put_request_cacher.source_file()?)?
|
|
{
|
|
return Err(PutRequestError::FileDoesNotExist);
|
|
}
|
|
|
|
let transaction_id = TransactionId::new(
|
|
self.local_cfg().id,
|
|
UnsignedByteField::new(
|
|
SequenceCounterInstance::MAX_BIT_WIDTH / 8,
|
|
self.seq_count_provider.get_and_increment().into(),
|
|
),
|
|
);
|
|
// 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
|
|
}
|
|
};
|
|
|
|
// Set PDU configuration fields which are important for generating PDUs.
|
|
self.transaction_params
|
|
.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.transaction_params.pdu_conf.direction = Direction::TowardsReceiver;
|
|
self.transaction_params.pdu_conf.crc_flag =
|
|
remote_cfg.crc_on_transmission_by_default.into();
|
|
self.transaction_params.pdu_conf.transaction_seq_num = *transaction_id.seq_num();
|
|
self.transaction_params.pdu_conf.trans_mode = transmission_mode;
|
|
self.transaction_params.file_params.segment_len =
|
|
self.calculate_max_file_seg_len(remote_cfg);
|
|
|
|
self.transaction_params.transaction_id = Some(transaction_id);
|
|
self.transaction_params.remote_cfg = Some(*remote_cfg);
|
|
self.transaction_params.transmission_mode = Some(transmission_mode);
|
|
self.transaction_params.closure_requested = closure_requested;
|
|
self.transaction_params.cond_code_eof.set(None);
|
|
self.transaction_params.finished_params = None;
|
|
|
|
self.state_helper.state.set(super::State::Busy);
|
|
Ok(())
|
|
}
|
|
|
|
fn insert_packet(
|
|
&mut self,
|
|
_cfdp_user: &mut impl CfdpUser,
|
|
packet_to_insert: &impl PduProvider,
|
|
) -> Result<u32, SourceError> {
|
|
if packet_to_insert.packet_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_to_insert.pdu_type(),
|
|
directive_type: packet_to_insert.file_directive_type(),
|
|
});
|
|
}
|
|
if packet_to_insert.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,
|
|
});
|
|
}
|
|
let mut sent_packets = 0;
|
|
|
|
// Unwrap is okay here, the [PacketInfo] API should ensure that the directive type is
|
|
// always a valid value.
|
|
match packet_to_insert
|
|
.file_directive_type()
|
|
.expect("PDU directive type unexpectedly not set")
|
|
{
|
|
FileDirectiveType::FinishedPdu => {
|
|
let finished_pdu = FinishedPduReader::new(packet_to_insert.raw_pdu())?;
|
|
self.handle_finished_pdu(&finished_pdu)?
|
|
}
|
|
FileDirectiveType::NakPdu => {
|
|
let nak_pdu = NakPduReader::new(packet_to_insert.raw_pdu())?;
|
|
sent_packets += self.handle_nak_pdu(&nak_pdu)?;
|
|
}
|
|
FileDirectiveType::KeepAlivePdu => self.handle_keep_alive_pdu(),
|
|
FileDirectiveType::AckPdu => {
|
|
let ack_pdu = AckPdu::from_bytes(packet_to_insert.raw_pdu())?;
|
|
self.handle_ack_pdu(&ack_pdu)?
|
|
}
|
|
FileDirectiveType::EofPdu
|
|
| FileDirectiveType::PromptPdu
|
|
| FileDirectiveType::MetadataPdu => {
|
|
return Err(SourceError::CantProcessPacketType {
|
|
pdu_type: packet_to_insert.pdu_type(),
|
|
directive_type: packet_to_insert.file_directive_type(),
|
|
});
|
|
}
|
|
}
|
|
Ok(sent_packets)
|
|
}
|
|
|
|
/// This functions models the Cancel.request CFDP primitive and is the recommended way to
|
|
/// cancel a transaction.
|
|
///
|
|
/// This method will cause a Notice of Cancellation at this entity if a transaction is active
|
|
/// and the passed transaction ID matches the currently active transaction ID. Please note
|
|
/// that the state machine might still be active because a cancelled transfer might still
|
|
/// require some packets to be sent to the remote receiver entity.
|
|
///
|
|
/// If not unexpected errors occur, this method returns [true] if the transfer was cancelled
|
|
/// propery and [false] if there is no transaction active or the passed transaction ID and the
|
|
/// active ID do not match.
|
|
pub fn cancel_request(
|
|
&mut self,
|
|
user: &mut impl CfdpUser,
|
|
transaction_id: &TransactionId,
|
|
) -> Result<bool, SourceError> {
|
|
if self.state() == super::State::Idle {
|
|
return Ok(false);
|
|
}
|
|
if let Some(active_id) = self.transaction_id() {
|
|
if active_id == *transaction_id {
|
|
// Control flow result can be ignored here for the cancel request.
|
|
self.notice_of_cancellation(user, ConditionCode::CancelRequestReceived)?;
|
|
return Ok(true);
|
|
}
|
|
}
|
|
Ok(false)
|
|
}
|
|
|
|
/// 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.transaction_params.reset();
|
|
}
|
|
|
|
#[inline]
|
|
fn set_step(&mut self, step: TransactionStep) {
|
|
self.set_step_internal(step);
|
|
}
|
|
|
|
#[inline]
|
|
fn set_step_internal(&self, step: TransactionStep) {
|
|
self.state_helper.step.set(step);
|
|
}
|
|
|
|
fn fsm_busy(
|
|
&mut self,
|
|
user: &mut impl CfdpUser,
|
|
_pdu: Option<&impl PduProvider>,
|
|
) -> Result<u32, SourceError> {
|
|
let mut sent_packets = 0;
|
|
if self.step() == TransactionStep::Idle {
|
|
self.set_step(TransactionStep::TransactionStart);
|
|
}
|
|
if self.step() == TransactionStep::TransactionStart {
|
|
self.handle_transaction_start(user)?;
|
|
self.set_step(TransactionStep::SendingMetadata);
|
|
}
|
|
if self.step() == TransactionStep::SendingMetadata {
|
|
self.prepare_and_send_metadata_pdu()?;
|
|
self.set_step(TransactionStep::SendingFileData);
|
|
sent_packets += 1;
|
|
}
|
|
if self.step() == TransactionStep::SendingFileData {
|
|
if let ControlFlow::Break(packets) = self.file_data_fsm()? {
|
|
sent_packets += packets;
|
|
// Exit for each file data PDU to allow flow control.
|
|
return Ok(sent_packets);
|
|
}
|
|
}
|
|
if self.step() == TransactionStep::SendingEof {
|
|
self.eof_fsm(user)?;
|
|
sent_packets += 1;
|
|
}
|
|
if self.step() == TransactionStep::WaitingForEofAck {
|
|
sent_packets += self.handle_waiting_for_ack_pdu(user)?;
|
|
}
|
|
if self.step() == TransactionStep::WaitingForFinished {
|
|
sent_packets += self.handle_waiting_for_finished_pdu(user)?;
|
|
}
|
|
if self.step() == TransactionStep::NoticeOfCompletion {
|
|
self.notice_of_completion(user);
|
|
self.reset();
|
|
}
|
|
Ok(sent_packets)
|
|
}
|
|
|
|
fn handle_waiting_for_ack_pdu(&mut self, user: &mut impl CfdpUser) -> Result<u32, SourceError> {
|
|
self.handle_positive_ack_procedures(user)
|
|
}
|
|
|
|
fn handle_positive_ack_procedures(
|
|
&mut self,
|
|
user: &mut impl CfdpUser,
|
|
) -> Result<u32, SourceError> {
|
|
let mut sent_packets = 0;
|
|
let current_params = self.transaction_params.positive_ack_params.get();
|
|
if let Some(mut positive_ack_params) = current_params {
|
|
if self
|
|
.transaction_params
|
|
.ack_timer
|
|
.borrow_mut()
|
|
.as_ref()
|
|
.unwrap()
|
|
.has_expired()
|
|
{
|
|
let ack_timer_exp_limit = self
|
|
.transaction_params
|
|
.remote_cfg
|
|
.as_ref()
|
|
.unwrap()
|
|
.positive_ack_timer_expiration_limit;
|
|
if positive_ack_params.ack_counter + 1 >= ack_timer_exp_limit {
|
|
let (fault_packets_sent, ctx) =
|
|
self.declare_fault(user, ConditionCode::PositiveAckLimitReached)?;
|
|
sent_packets += fault_packets_sent;
|
|
if ctx == FsmContext::ResetWhenPossible {
|
|
self.reset();
|
|
} else {
|
|
positive_ack_params.ack_counter = 0;
|
|
positive_ack_params.positive_ack_of_cancellation = true;
|
|
}
|
|
} else {
|
|
self.transaction_params
|
|
.ack_timer
|
|
.borrow_mut()
|
|
.as_mut()
|
|
.unwrap()
|
|
.reset();
|
|
positive_ack_params.ack_counter += 1;
|
|
self.prepare_and_send_eof_pdu(
|
|
user,
|
|
self.transaction_params
|
|
.file_params
|
|
.checksum_completed_file
|
|
.unwrap(),
|
|
)?;
|
|
sent_packets += 1;
|
|
}
|
|
}
|
|
|
|
self.transaction_params
|
|
.positive_ack_params
|
|
.set(Some(positive_ack_params));
|
|
}
|
|
Ok(sent_packets)
|
|
}
|
|
|
|
fn handle_retransmission(&mut self, nak_pdu: &NakPduReader) -> Result<u32, SourceError> {
|
|
let mut sent_packets = 0;
|
|
let segment_req_iter = nak_pdu.get_segment_requests_iterator().unwrap();
|
|
for segment_req in segment_req_iter {
|
|
// Special case: Metadata PDU is re-requested.
|
|
if segment_req.0 == 0 && segment_req.1 == 0 {
|
|
self.prepare_and_send_metadata_pdu()?;
|
|
sent_packets += 1;
|
|
continue;
|
|
} else {
|
|
if (segment_req.1 < segment_req.0)
|
|
|| (segment_req.0 > self.transaction_params.file_params.progress)
|
|
{
|
|
return Err(SourceError::InvalidNakPdu);
|
|
}
|
|
let mut missing_chunk_len = segment_req.1 - segment_req.0;
|
|
let current_offset = segment_req.0;
|
|
while missing_chunk_len > 0 {
|
|
let chunk_size = core::cmp::min(
|
|
missing_chunk_len,
|
|
self.transaction_params.file_params.segment_len,
|
|
);
|
|
self.prepare_and_send_file_data_pdu(current_offset, chunk_size)?;
|
|
sent_packets += 1;
|
|
missing_chunk_len -= missing_chunk_len;
|
|
}
|
|
}
|
|
}
|
|
Ok(sent_packets)
|
|
}
|
|
|
|
fn handle_waiting_for_finished_pdu(
|
|
&mut self,
|
|
user: &mut impl CfdpUser,
|
|
) -> Result<u32, SourceError> {
|
|
// If we reach this state, countdown definitely is set.
|
|
#[allow(clippy::collapsible_if)]
|
|
if self.transmission_mode().unwrap() == TransmissionMode::Unacknowledged
|
|
&& self
|
|
.transaction_params
|
|
.check_timer
|
|
.as_ref()
|
|
.unwrap()
|
|
.has_expired()
|
|
{
|
|
let (sent_packets, ctx) = self.declare_fault(user, ConditionCode::CheckLimitReached)?;
|
|
if ctx == FsmContext::ResetWhenPossible {
|
|
self.reset();
|
|
}
|
|
return Ok(sent_packets);
|
|
}
|
|
Ok(0)
|
|
}
|
|
|
|
fn eof_fsm(&mut self, user: &mut impl CfdpUser) -> Result<(), SourceError> {
|
|
let checksum = self.vfs.calculate_checksum(
|
|
self.put_request_cacher.source_file().unwrap(),
|
|
self.transaction_params
|
|
.remote_cfg
|
|
.as_ref()
|
|
.unwrap()
|
|
.default_crc_type,
|
|
self.transaction_params.file_params.file_size,
|
|
&mut self.pdu_and_cksum_buffer.borrow_mut(),
|
|
)?;
|
|
self.transaction_params.file_params.checksum_completed_file = Some(checksum);
|
|
self.prepare_and_send_eof_pdu(user, checksum)?;
|
|
if self.transmission_mode().unwrap() == TransmissionMode::Unacknowledged {
|
|
if self.transaction_params.closure_requested {
|
|
self.transaction_params.check_timer = Some(
|
|
self.timer_creator
|
|
.create_countdown(crate::TimerContext::CheckLimit {
|
|
local_id: self.local_cfg.id,
|
|
remote_id: self
|
|
.transaction_params
|
|
.remote_cfg
|
|
.as_ref()
|
|
.unwrap()
|
|
.entity_id,
|
|
entity_type: EntityType::Sending,
|
|
}),
|
|
);
|
|
self.set_step(TransactionStep::WaitingForFinished);
|
|
} else {
|
|
self.set_step(TransactionStep::NoticeOfCompletion);
|
|
}
|
|
} else {
|
|
self.start_positive_ack_procedure();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn start_positive_ack_procedure(&self) {
|
|
self.set_step_internal(TransactionStep::WaitingForEofAck);
|
|
match self.transaction_params.positive_ack_params.get() {
|
|
Some(mut current) => {
|
|
current.ack_counter = 0;
|
|
self.transaction_params
|
|
.positive_ack_params
|
|
.set(Some(current));
|
|
}
|
|
None => self
|
|
.transaction_params
|
|
.positive_ack_params
|
|
.set(Some(PositiveAckParams {
|
|
ack_counter: 0,
|
|
positive_ack_of_cancellation: false,
|
|
})),
|
|
}
|
|
|
|
*self.transaction_params.ack_timer.borrow_mut() = Some(
|
|
self.timer_creator
|
|
.create_countdown(crate::TimerContext::PositiveAck {
|
|
expiry_time: self
|
|
.transaction_params
|
|
.remote_cfg
|
|
.as_ref()
|
|
.unwrap()
|
|
.positive_ack_timer_interval,
|
|
}),
|
|
);
|
|
}
|
|
|
|
fn handle_transaction_start(
|
|
&mut self,
|
|
cfdp_user: &mut impl CfdpUser,
|
|
) -> Result<(), SourceError> {
|
|
if !self.put_request_cacher.has_source_file() {
|
|
self.transaction_params.file_params.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)?;
|
|
self.transaction_params.file_params.file_size = self.vfs.file_size(source_file)?;
|
|
if self.transaction_params.file_params.file_size > u32::MAX as u64 {
|
|
self.transaction_params.pdu_conf.file_flag = LargeFileFlag::Large
|
|
} else {
|
|
if self.transaction_params.file_params.file_size == 0 {
|
|
self.transaction_params.file_params.empty_file = true;
|
|
}
|
|
self.transaction_params.pdu_conf.file_flag = LargeFileFlag::Normal
|
|
}
|
|
}
|
|
cfdp_user.transaction_indication(&self.transaction_id().unwrap());
|
|
Ok(())
|
|
}
|
|
|
|
fn prepare_and_send_ack_pdu(
|
|
&mut self,
|
|
condition_code: ConditionCode,
|
|
transaction_status: TransactionStatus,
|
|
) -> Result<(), SourceError> {
|
|
let ack_pdu = AckPdu::new(
|
|
PduHeader::new_for_file_directive(self.transaction_params.pdu_conf, 0),
|
|
FileDirectiveType::FinishedPdu,
|
|
condition_code,
|
|
transaction_status,
|
|
)
|
|
.map_err(PduError::from)?;
|
|
self.pdu_send_helper(&ack_pdu)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn prepare_and_send_metadata_pdu(&mut self) -> Result<(), SourceError> {
|
|
let metadata_params = MetadataGenericParams::new(
|
|
self.transaction_params.closure_requested,
|
|
self.transaction_params
|
|
.remote_cfg
|
|
.as_ref()
|
|
.unwrap()
|
|
.default_crc_type,
|
|
self.transaction_params.file_params.file_size,
|
|
);
|
|
if self.transaction_params.file_params.metadata_only {
|
|
let metadata_pdu = MetadataPduCreator::new(
|
|
PduHeader::new_for_file_directive(self.transaction_params.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_for_file_directive(self.transaction_params.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<ControlFlow<u32>, SourceError> {
|
|
if !self.transaction_params.file_params.metadata_only
|
|
&& self.transaction_params.file_params.progress
|
|
< self.transaction_params.file_params.file_size
|
|
&& self.send_progressing_file_data_pdu()?
|
|
{
|
|
return Ok(ControlFlow::Break(1));
|
|
}
|
|
if self.transaction_params.file_params.empty_file
|
|
|| self.transaction_params.file_params.progress
|
|
>= self.transaction_params.file_params.file_size
|
|
{
|
|
// EOF is still expected.
|
|
self.set_step(TransactionStep::SendingEof);
|
|
self.transaction_params
|
|
.cond_code_eof
|
|
.set(Some(ConditionCode::NoError));
|
|
} else if self.transaction_params.file_params.metadata_only {
|
|
// Special case: Metadata Only, no EOF required.
|
|
if self.transaction_params.closure_requested {
|
|
self.set_step(TransactionStep::WaitingForFinished);
|
|
} else {
|
|
self.set_step(TransactionStep::NoticeOfCompletion);
|
|
}
|
|
}
|
|
Ok(ControlFlow::Continue(()))
|
|
}
|
|
|
|
fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) {
|
|
if self.local_cfg.indication_cfg.transaction_finished {
|
|
// The first case happens for unacknowledged file copy operation with no closure.
|
|
let finished_params = match self.transaction_params.finished_params {
|
|
Some(finished_params) => TransactionFinishedParams {
|
|
id: self.transaction_id().unwrap(),
|
|
condition_code: finished_params.condition_code,
|
|
delivery_code: finished_params.delivery_code,
|
|
file_status: finished_params.file_status,
|
|
},
|
|
None => TransactionFinishedParams {
|
|
id: self.transaction_id().unwrap(),
|
|
condition_code: ConditionCode::NoError,
|
|
delivery_code: DeliveryCode::Complete,
|
|
file_status: FileStatus::Unreported,
|
|
},
|
|
};
|
|
cfdp_user.transaction_finished_indication(&finished_params);
|
|
}
|
|
}
|
|
|
|
fn calculate_max_file_seg_len(&self, remote_cfg: &RemoteEntityConfig) -> u64 {
|
|
let mut derived_max_seg_len = calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(
|
|
&PduHeader::new_for_file_directive(self.transaction_params.pdu_conf, 0),
|
|
remote_cfg.max_packet_len,
|
|
None,
|
|
);
|
|
if remote_cfg.max_file_segment_len.is_some() {
|
|
derived_max_seg_len = core::cmp::min(
|
|
remote_cfg.max_file_segment_len.unwrap(),
|
|
derived_max_seg_len,
|
|
);
|
|
}
|
|
derived_max_seg_len as u64
|
|
}
|
|
|
|
fn send_progressing_file_data_pdu(&mut self) -> Result<bool, SourceError> {
|
|
// Should never be called, but use defensive programming here.
|
|
if self.transaction_params.file_params.progress
|
|
>= self.transaction_params.file_params.file_size
|
|
{
|
|
return Ok(false);
|
|
}
|
|
let read_len = self.transaction_params.file_params.segment_len.min(
|
|
self.transaction_params.file_params.file_size
|
|
- self.transaction_params.file_params.progress,
|
|
);
|
|
self.prepare_and_send_file_data_pdu(
|
|
self.transaction_params.file_params.progress,
|
|
read_len,
|
|
)?;
|
|
Ok(true)
|
|
}
|
|
|
|
fn prepare_and_send_file_data_pdu(
|
|
&mut self,
|
|
offset: u64,
|
|
size: u64,
|
|
) -> Result<(), SourceError> {
|
|
let pdu_creator = FileDataPduCreatorWithReservedDatafield::new_no_seg_metadata(
|
|
PduHeader::new_for_file_data(
|
|
self.transaction_params.pdu_conf,
|
|
0,
|
|
SegmentMetadataFlag::NotPresent,
|
|
SegmentationControl::NoRecordBoundaryPreservation,
|
|
),
|
|
offset,
|
|
size,
|
|
);
|
|
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(),
|
|
offset,
|
|
size,
|
|
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.transaction_params.file_params.progress += size;
|
|
Ok(())
|
|
}
|
|
|
|
fn prepare_and_send_eof_pdu(
|
|
&self,
|
|
cfdp_user: &mut impl CfdpUser,
|
|
checksum: u32,
|
|
) -> Result<(), SourceError> {
|
|
let eof_pdu = EofPdu::new(
|
|
PduHeader::new_for_file_directive(self.transaction_params.pdu_conf, 0),
|
|
self.transaction_params
|
|
.cond_code_eof
|
|
.get()
|
|
.unwrap_or(ConditionCode::NoError),
|
|
checksum,
|
|
self.transaction_params.file_params.progress,
|
|
None,
|
|
);
|
|
self.pdu_send_helper(&eof_pdu)?;
|
|
if self.local_cfg.indication_cfg.eof_sent {
|
|
cfdp_user.eof_sent_indication(&self.transaction_id().unwrap());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn pdu_send_helper(&self, pdu: &(impl WritablePduPacket + CfdpPdu)) -> Result<(), SourceError> {
|
|
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, finished_pdu: &FinishedPduReader) -> Result<(), SourceError> {
|
|
// Ignore this packet when we are idle.
|
|
if self.state() == State::Idle {
|
|
return Ok(());
|
|
}
|
|
if self.step() != TransactionStep::WaitingForFinished {
|
|
return Err(SourceError::UnexpectedPdu {
|
|
pdu_type: PduType::FileDirective,
|
|
directive_type: Some(FileDirectiveType::FinishedPdu),
|
|
});
|
|
}
|
|
// Unwrapping should be fine here, the transfer state is valid when we are not in IDLE
|
|
// mode.
|
|
self.transaction_params.finished_params = Some(FinishedParams {
|
|
condition_code: finished_pdu.condition_code(),
|
|
delivery_code: finished_pdu.delivery_code(),
|
|
file_status: finished_pdu.file_status(),
|
|
});
|
|
if let Some(TransmissionMode::Acknowledged) = self.transmission_mode() {
|
|
self.prepare_and_send_ack_pdu(
|
|
finished_pdu.condition_code(),
|
|
TransactionStatus::Active,
|
|
)?;
|
|
}
|
|
self.set_step(TransactionStep::NoticeOfCompletion);
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_nak_pdu(&mut self, nak_pdu: &NakPduReader) -> Result<u32, SourceError> {
|
|
self.handle_retransmission(nak_pdu)
|
|
}
|
|
|
|
fn handle_ack_pdu(&mut self, ack_pdu: &AckPdu) -> Result<(), SourceError> {
|
|
if self.step() != TransactionStep::WaitingForEofAck {
|
|
// Drop the packet, wrong state to handle it..
|
|
return Err(SourceError::UnexpectedPdu {
|
|
pdu_type: PduType::FileDirective,
|
|
directive_type: Some(FileDirectiveType::AckPdu),
|
|
});
|
|
}
|
|
if ack_pdu.directive_code_of_acked_pdu() == FileDirectiveType::EofPdu {
|
|
self.set_step(TransactionStep::WaitingForFinished);
|
|
} else {
|
|
self.anomalies.invalid_ack_directive_code =
|
|
self.anomalies.invalid_ack_directive_code.wrapping_add(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn notice_of_cancellation(
|
|
&mut self,
|
|
user: &mut impl CfdpUser,
|
|
condition_code: ConditionCode,
|
|
) -> Result<u32, SourceError> {
|
|
let mut sent_packets = 0;
|
|
let ctx = self.notice_of_cancellation_internal(user, condition_code, &mut sent_packets)?;
|
|
if ctx == FsmContext::ResetWhenPossible {
|
|
self.reset();
|
|
}
|
|
Ok(sent_packets)
|
|
}
|
|
|
|
fn notice_of_cancellation_internal(
|
|
&self,
|
|
user: &mut impl CfdpUser,
|
|
condition_code: ConditionCode,
|
|
sent_packets: &mut u32,
|
|
) -> Result<FsmContext, SourceError> {
|
|
self.transaction_params
|
|
.cond_code_eof
|
|
.set(Some(condition_code));
|
|
// As specified in 4.11.2.2, prepare an EOF PDU to be sent to the remote entity. Supply
|
|
// the checksum for the file copy progress sent so far.
|
|
let checksum = self.vfs.calculate_checksum(
|
|
self.put_request_cacher.source_file().unwrap(),
|
|
self.transaction_params
|
|
.remote_cfg
|
|
.as_ref()
|
|
.unwrap()
|
|
.default_crc_type,
|
|
self.transaction_params.file_params.progress,
|
|
&mut self.pdu_and_cksum_buffer.borrow_mut(),
|
|
)?;
|
|
self.prepare_and_send_eof_pdu(user, checksum)?;
|
|
*sent_packets += 1;
|
|
if self.transmission_mode().unwrap() == TransmissionMode::Unacknowledged {
|
|
// We are done.
|
|
Ok(FsmContext::ResetWhenPossible)
|
|
} else {
|
|
self.start_positive_ack_procedure();
|
|
Ok(FsmContext::default())
|
|
}
|
|
}
|
|
|
|
pub fn notice_of_suspension(&mut self) {
|
|
self.notice_of_suspension_internal();
|
|
}
|
|
|
|
fn notice_of_suspension_internal(&self) {}
|
|
|
|
pub fn abandon_transaction(&mut self) {
|
|
// I guess an abandoned transaction just stops whatever the handler is doing and resets
|
|
// it to a clean state.. The implementation for this is quite easy.
|
|
self.reset();
|
|
}
|
|
|
|
// Returns the number of packets sent and a FSM context structure.
|
|
fn declare_fault(
|
|
&self,
|
|
user: &mut impl CfdpUser,
|
|
cond: ConditionCode,
|
|
) -> Result<(u32, FsmContext), SourceError> {
|
|
let mut sent_packets = 0;
|
|
let mut fh = self.local_cfg.fault_handler.get_fault_handler(cond);
|
|
// CFDP standard 4.11.2.2.3: Any fault declared in the course of transferring
|
|
// the EOF (cancel) PDU must result in abandonment of the transaction.
|
|
if let Some(positive_ack_params) = self.transaction_params.positive_ack_params.get() {
|
|
if positive_ack_params.positive_ack_of_cancellation {
|
|
fh = FaultHandlerCode::AbandonTransaction;
|
|
}
|
|
}
|
|
let mut ctx = FsmContext::default();
|
|
match fh {
|
|
FaultHandlerCode::NoticeOfCancellation => {
|
|
ctx = self.notice_of_cancellation_internal(user, cond, &mut sent_packets)?;
|
|
}
|
|
FaultHandlerCode::NoticeOfSuspension => {
|
|
self.notice_of_suspension_internal();
|
|
}
|
|
FaultHandlerCode::IgnoreError => (),
|
|
FaultHandlerCode::AbandonTransaction => {
|
|
ctx = FsmContext::ResetWhenPossible;
|
|
}
|
|
}
|
|
self.local_cfg.fault_handler.report_fault(
|
|
fh,
|
|
FaultInfo::new(
|
|
self.transaction_id().unwrap(),
|
|
cond,
|
|
self.transaction_params.file_params.progress,
|
|
),
|
|
);
|
|
Ok((sent_packets, ctx))
|
|
}
|
|
|
|
fn handle_keep_alive_pdu(&mut self) {}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::{fs::OpenOptions, io::Write, path::PathBuf, vec::Vec};
|
|
|
|
use alloc::string::String;
|
|
use rand::Rng;
|
|
use spacepackets::{
|
|
cfdp::{
|
|
ChecksumType, CrcFlag,
|
|
pdu::{
|
|
file_data::FileDataPdu, finished::FinishedPduCreator, metadata::MetadataPduReader,
|
|
nak::NakPduCreator,
|
|
},
|
|
},
|
|
util::UnsignedByteFieldU16,
|
|
};
|
|
use tempfile::TempPath;
|
|
|
|
use super::*;
|
|
use crate::{
|
|
CRC_32, FaultHandler, IndicationConfig, PduRawWithInfo, RemoteConfigStoreStd,
|
|
filestore::NativeFilestore,
|
|
request::PutRequestOwned,
|
|
source::TransactionStep,
|
|
tests::{
|
|
SentPdu, TestCfdpSender, TestCfdpUser, TestCheckTimer, TestCheckTimerCreator,
|
|
TestFaultHandler, TimerExpiryControl, basic_remote_cfg_table,
|
|
},
|
|
};
|
|
use spacepackets::seq_count::SequenceCounterSimple;
|
|
|
|
const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1);
|
|
const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
|
|
const INVALID_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(5);
|
|
|
|
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,
|
|
RemoteConfigStoreStd,
|
|
TestCheckTimerCreator,
|
|
TestCheckTimer,
|
|
SequenceCounterSimple<u16>,
|
|
>;
|
|
|
|
struct SourceHandlerTestbench {
|
|
handler: TestSourceHandler,
|
|
expiry_control: TimerExpiryControl,
|
|
transmission_mode: TransmissionMode,
|
|
#[allow(dead_code)]
|
|
srcfile_handle: TempPath,
|
|
srcfile: String,
|
|
destfile: String,
|
|
max_packet_len: usize,
|
|
check_idle_on_drop: bool,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
struct TransferInfo {
|
|
id: TransactionId,
|
|
file_size: u64,
|
|
closure_requested: bool,
|
|
pdu_header: PduHeader,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct EofParams {
|
|
file_size: u64,
|
|
file_checksum: u32,
|
|
condition_code: ConditionCode,
|
|
}
|
|
|
|
impl EofParams {
|
|
pub const fn new_success(file_size: u64, file_checksum: u32) -> Self {
|
|
Self {
|
|
file_size,
|
|
file_checksum,
|
|
condition_code: ConditionCode::NoError,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SourceHandlerTestbench {
|
|
fn new(
|
|
transmission_mode: TransmissionMode,
|
|
crc_on_transmission_by_default: bool,
|
|
max_packet_len: usize,
|
|
) -> Self {
|
|
let local_entity_cfg = LocalEntityConfig {
|
|
id: LOCAL_ID.into(),
|
|
indication_cfg: IndicationConfig::default(),
|
|
fault_handler: FaultHandler::new(TestFaultHandler::default()),
|
|
};
|
|
let static_put_request_cacher = StaticPutRequestCacher::new(2048);
|
|
let (srcfile_handle, destfile) = init_full_filepaths_textfile();
|
|
let srcfile = String::from(srcfile_handle.to_path_buf().to_str().unwrap());
|
|
let expiry_control = TimerExpiryControl::default();
|
|
let sender = TestCfdpSender::default();
|
|
Self {
|
|
handler: SourceHandler::new(
|
|
local_entity_cfg,
|
|
sender,
|
|
NativeFilestore::default(),
|
|
static_put_request_cacher,
|
|
1024,
|
|
basic_remote_cfg_table(
|
|
REMOTE_ID,
|
|
max_packet_len,
|
|
crc_on_transmission_by_default,
|
|
),
|
|
TestCheckTimerCreator::new(&expiry_control),
|
|
SequenceCounterSimple::default(),
|
|
),
|
|
transmission_mode,
|
|
expiry_control,
|
|
srcfile_handle,
|
|
srcfile,
|
|
destfile: String::from(destfile.to_path_buf().to_str().unwrap()),
|
|
max_packet_len,
|
|
check_idle_on_drop: true,
|
|
}
|
|
}
|
|
|
|
fn create_user(&self, next_expected_seq_num: u64, filesize: u64) -> TestCfdpUser {
|
|
TestCfdpUser::new(
|
|
next_expected_seq_num,
|
|
self.srcfile.clone(),
|
|
self.destfile.clone(),
|
|
filesize,
|
|
)
|
|
}
|
|
|
|
fn put_request(
|
|
&mut self,
|
|
put_request: &impl ReadablePutRequest,
|
|
) -> Result<(), PutRequestError> {
|
|
self.handler.put_request(put_request)
|
|
}
|
|
|
|
fn all_fault_queues_empty(&self) -> bool {
|
|
self.handler
|
|
.local_cfg
|
|
.user_fault_hook()
|
|
.borrow()
|
|
.all_queues_empty()
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn test_fault_handler(&self) -> &RefCell<TestFaultHandler> {
|
|
self.handler.local_cfg.user_fault_hook()
|
|
}
|
|
|
|
fn test_fault_handler_mut(&mut self) -> &mut RefCell<TestFaultHandler> {
|
|
self.handler.local_cfg.user_fault_hook_mut()
|
|
}
|
|
|
|
fn pdu_queue_empty(&self) -> bool {
|
|
self.handler.pdu_sender.queue_empty()
|
|
}
|
|
|
|
fn get_next_sent_pdu(&self) -> Option<SentPdu> {
|
|
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,
|
|
self.transmission_mode
|
|
);
|
|
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);
|
|
}
|
|
|
|
fn nak_for_file_segments(
|
|
&mut self,
|
|
cfdp_user: &mut TestCfdpUser,
|
|
transfer_info: &TransferInfo,
|
|
seg_reqs: &[(u32, u32)],
|
|
) {
|
|
let nak_pdu = NakPduCreator::new_normal_file_size(
|
|
transfer_info.pdu_header,
|
|
0,
|
|
transfer_info.file_size as u32,
|
|
seg_reqs,
|
|
)
|
|
.unwrap();
|
|
let nak_pdu_vec = nak_pdu.to_vec().unwrap();
|
|
let packet_info = PduRawWithInfo::new(&nak_pdu_vec).unwrap();
|
|
self.handler
|
|
.state_machine(cfdp_user, Some(&packet_info))
|
|
.unwrap();
|
|
}
|
|
|
|
fn generic_file_transfer(
|
|
&mut self,
|
|
cfdp_user: &mut TestCfdpUser,
|
|
with_closure: bool,
|
|
file_data: Vec<u8>,
|
|
) -> (TransferInfo, u32) {
|
|
let mut digest = CRC_32.digest();
|
|
digest.update(&file_data);
|
|
let checksum = digest.finalize();
|
|
cfdp_user.expected_full_src_name = self.srcfile.clone();
|
|
cfdp_user.expected_full_dest_name = self.destfile.clone();
|
|
cfdp_user.expected_file_size = file_data.len() as u64;
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&self.srcfile,
|
|
&self.destfile,
|
|
Some(self.transmission_mode),
|
|
Some(with_closure),
|
|
)
|
|
.expect("creating put request failed");
|
|
let transaction_info = self.common_file_transfer_init_with_metadata_check(
|
|
cfdp_user,
|
|
put_request,
|
|
cfdp_user.expected_file_size,
|
|
);
|
|
let mut current_offset = 0;
|
|
let chunks = file_data.chunks(
|
|
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(
|
|
&transaction_info.pdu_header,
|
|
self.max_packet_len,
|
|
None,
|
|
),
|
|
);
|
|
let mut fd_pdus = 0;
|
|
for segment in chunks {
|
|
self.check_next_file_pdu(current_offset, segment);
|
|
self.handler.state_machine_no_packet(cfdp_user).unwrap();
|
|
fd_pdus += 1;
|
|
current_offset += segment.len() as u64;
|
|
}
|
|
self.common_eof_pdu_check(
|
|
cfdp_user,
|
|
transaction_info.closure_requested,
|
|
EofParams {
|
|
file_size: cfdp_user.expected_file_size,
|
|
file_checksum: checksum,
|
|
condition_code: ConditionCode::NoError,
|
|
},
|
|
1,
|
|
);
|
|
(transaction_info, fd_pdus)
|
|
}
|
|
|
|
fn common_file_transfer_init_with_metadata_check(
|
|
&mut self,
|
|
cfdp_user: &mut TestCfdpUser,
|
|
put_request: PutRequestOwned,
|
|
file_size: u64,
|
|
) -> TransferInfo {
|
|
assert_eq!(cfdp_user.transaction_indication_call_count, 0);
|
|
assert_eq!(cfdp_user.eof_sent_call_count, 0);
|
|
|
|
self.put_request(&put_request)
|
|
.expect("put_request call failed");
|
|
assert_eq!(self.handler.state(), State::Busy);
|
|
assert_eq!(self.handler.step(), TransactionStep::Idle);
|
|
let transaction_id = self.handler.transaction_id().unwrap();
|
|
let sent_packets = self
|
|
.handler
|
|
.state_machine_no_packet(cfdp_user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 2);
|
|
assert!(!self.pdu_queue_empty());
|
|
let next_pdu = self.get_next_sent_pdu().unwrap();
|
|
assert!(!self.pdu_queue_empty());
|
|
let metadata_pdu_reader = self.metadata_check(&next_pdu, file_size);
|
|
let closure_requested = if let Some(closure_requested) = put_request.closure_requested {
|
|
assert_eq!(
|
|
metadata_pdu_reader.metadata_params().closure_requested,
|
|
closure_requested
|
|
);
|
|
closure_requested
|
|
} else {
|
|
assert!(metadata_pdu_reader.metadata_params().closure_requested);
|
|
metadata_pdu_reader.metadata_params().closure_requested
|
|
};
|
|
TransferInfo {
|
|
pdu_header: *metadata_pdu_reader.pdu_header(),
|
|
closure_requested,
|
|
file_size,
|
|
id: transaction_id,
|
|
}
|
|
}
|
|
|
|
fn metadata_check<'a>(
|
|
&self,
|
|
next_pdu: &'a SentPdu,
|
|
file_size: u64,
|
|
) -> MetadataPduReader<'a> {
|
|
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");
|
|
self.common_pdu_check_for_file_transfer(metadata_pdu.pdu_header(), CrcFlag::NoCrc);
|
|
assert_eq!(
|
|
metadata_pdu
|
|
.src_file_name()
|
|
.value_as_str()
|
|
.unwrap()
|
|
.unwrap(),
|
|
self.srcfile
|
|
);
|
|
assert_eq!(
|
|
metadata_pdu
|
|
.dest_file_name()
|
|
.value_as_str()
|
|
.unwrap()
|
|
.unwrap(),
|
|
self.destfile
|
|
);
|
|
assert_eq!(metadata_pdu.metadata_params().file_size, file_size);
|
|
assert_eq!(
|
|
metadata_pdu.metadata_params().checksum_type,
|
|
ChecksumType::Crc32
|
|
);
|
|
assert_eq!(metadata_pdu.transmission_mode(), self.transmission_mode);
|
|
assert_eq!(metadata_pdu.options(), &[]);
|
|
metadata_pdu
|
|
}
|
|
|
|
fn check_next_file_pdu(&mut self, expected_offset: u64, expected_data: &[u8]) {
|
|
let next_pdu = self.get_next_sent_pdu().unwrap();
|
|
assert_eq!(next_pdu.pdu_type, PduType::FileData);
|
|
assert!(next_pdu.file_directive_type.is_none());
|
|
let fd_pdu =
|
|
FileDataPdu::from_bytes(&next_pdu.raw_pdu).expect("reading file data PDU failed");
|
|
assert_eq!(fd_pdu.offset(), expected_offset);
|
|
assert_eq!(fd_pdu.file_data(), expected_data);
|
|
assert!(fd_pdu.segment_metadata().is_none());
|
|
}
|
|
|
|
fn acknowledge_eof_pdu(
|
|
&mut self,
|
|
cfdp_user: &mut impl CfdpUser,
|
|
transaction_info: &TransferInfo,
|
|
) {
|
|
let ack_pdu = AckPdu::new(
|
|
transaction_info.pdu_header,
|
|
FileDirectiveType::EofPdu,
|
|
ConditionCode::NoError,
|
|
TransactionStatus::Active,
|
|
)
|
|
.expect("creating ACK PDU failed");
|
|
let ack_pdu_vec = ack_pdu.to_vec().unwrap();
|
|
let packet_info = PduRawWithInfo::new(&ack_pdu_vec).unwrap();
|
|
self.handler
|
|
.state_machine(cfdp_user, Some(&packet_info))
|
|
.expect("state machine failed");
|
|
}
|
|
|
|
fn common_finished_pdu_ack_check(&mut self) {
|
|
assert!(!self.pdu_queue_empty());
|
|
let next_pdu = self.get_next_sent_pdu().unwrap();
|
|
assert!(self.pdu_queue_empty());
|
|
assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
|
|
assert_eq!(
|
|
next_pdu.file_directive_type,
|
|
Some(FileDirectiveType::AckPdu)
|
|
);
|
|
let ack_pdu = AckPdu::from_bytes(&next_pdu.raw_pdu).unwrap();
|
|
self.common_pdu_check_for_file_transfer(ack_pdu.pdu_header(), CrcFlag::NoCrc);
|
|
assert_eq!(ack_pdu.condition_code(), ConditionCode::NoError);
|
|
assert_eq!(
|
|
ack_pdu.directive_code_of_acked_pdu(),
|
|
FileDirectiveType::FinishedPdu
|
|
);
|
|
assert_eq!(ack_pdu.transaction_status(), TransactionStatus::Active);
|
|
}
|
|
|
|
fn common_eof_pdu_check(
|
|
&mut self,
|
|
cfdp_user: &mut TestCfdpUser,
|
|
closure_requested: bool,
|
|
eof_params: EofParams,
|
|
eof_sent_call_count: u32,
|
|
) {
|
|
let next_pdu = self.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 EOF PDU format");
|
|
self.common_pdu_check_for_file_transfer(eof_pdu.pdu_header(), CrcFlag::NoCrc);
|
|
assert_eq!(eof_pdu.condition_code(), eof_params.condition_code);
|
|
assert_eq!(eof_pdu.file_size(), eof_params.file_size);
|
|
assert_eq!(eof_pdu.file_checksum(), eof_params.file_checksum);
|
|
assert_eq!(
|
|
eof_pdu
|
|
.pdu_header()
|
|
.common_pdu_conf()
|
|
.transaction_seq_num
|
|
.value_const(),
|
|
0
|
|
);
|
|
if self.transmission_mode == TransmissionMode::Unacknowledged {
|
|
if !closure_requested {
|
|
assert_eq!(self.handler.state(), State::Idle);
|
|
assert_eq!(self.handler.step(), TransactionStep::Idle);
|
|
} else {
|
|
assert_eq!(self.handler.state(), State::Busy);
|
|
assert_eq!(self.handler.step(), TransactionStep::WaitingForFinished);
|
|
}
|
|
} else {
|
|
assert_eq!(self.handler.state(), State::Busy);
|
|
assert_eq!(self.handler.step(), TransactionStep::WaitingForEofAck);
|
|
}
|
|
|
|
assert_eq!(cfdp_user.transaction_indication_call_count, 1);
|
|
assert_eq!(cfdp_user.eof_sent_call_count, eof_sent_call_count);
|
|
self.all_fault_queues_empty();
|
|
}
|
|
|
|
fn common_tiny_file_transfer(
|
|
&mut self,
|
|
cfdp_user: &mut TestCfdpUser,
|
|
with_closure: bool,
|
|
) -> (&'static str, TransferInfo) {
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.open(&self.srcfile)
|
|
.expect("opening file failed");
|
|
let content_str = "Hello World!";
|
|
file.write_all(content_str.as_bytes())
|
|
.expect("writing file content failed");
|
|
drop(file);
|
|
let (transfer_info, fd_pdus) = self.generic_file_transfer(
|
|
cfdp_user,
|
|
with_closure,
|
|
content_str.as_bytes().to_vec(),
|
|
);
|
|
assert_eq!(fd_pdus, 1);
|
|
(content_str, transfer_info)
|
|
}
|
|
|
|
// Finish handling: Simulate completion from the destination side by insert finished PDU.
|
|
fn finish_handling(&mut self, user: &mut TestCfdpUser, transfer_info: &TransferInfo) {
|
|
let finished_pdu = FinishedPduCreator::new_no_error(
|
|
transfer_info.pdu_header,
|
|
DeliveryCode::Complete,
|
|
FileStatus::Retained,
|
|
);
|
|
let finished_pdu_vec = finished_pdu.to_vec().unwrap();
|
|
let packet_info = PduRawWithInfo::new(&finished_pdu_vec).unwrap();
|
|
self.handler
|
|
.state_machine(user, Some(&packet_info))
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
impl Drop for SourceHandlerTestbench {
|
|
fn drop(&mut self) {
|
|
self.all_fault_queues_empty();
|
|
if self.check_idle_on_drop {
|
|
assert_eq!(self.handler.state(), State::Idle);
|
|
assert_eq!(self.handler.step(), TransactionStep::Idle);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_basic() {
|
|
let tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
assert!(tb.handler.transmission_mode().is_none());
|
|
assert!(tb.pdu_queue_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_file_transfer_not_acked_no_closure() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
let file_size = 0;
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Unacknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transfer_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
tb.common_eof_pdu_check(
|
|
&mut user,
|
|
transfer_info.closure_requested,
|
|
EofParams::new_success(file_size, CRC_32.digest().finalize()),
|
|
1,
|
|
);
|
|
user.verify_finished_indication(
|
|
DeliveryCode::Complete,
|
|
ConditionCode::NoError,
|
|
transfer_info.id,
|
|
FileStatus::Unreported,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_file_transfer_acked() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 512);
|
|
let file_size = 0;
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Acknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transaction_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
tb.common_eof_pdu_check(
|
|
&mut user,
|
|
transaction_info.closure_requested,
|
|
EofParams::new_success(file_size, CRC_32.digest().finalize()),
|
|
1,
|
|
);
|
|
|
|
tb.acknowledge_eof_pdu(&mut user, &transaction_info);
|
|
tb.finish_handling(&mut user, &transaction_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
user.verify_finished_indication_retained(
|
|
DeliveryCode::Complete,
|
|
ConditionCode::NoError,
|
|
transaction_info.id,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tiny_file_transfer_not_acked_no_closure() {
|
|
let mut user = TestCfdpUser::default();
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
tb.common_tiny_file_transfer(&mut user, false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tiny_file_transfer_acked() {
|
|
let mut user = TestCfdpUser::default();
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 512);
|
|
let (_data, transfer_info) = tb.common_tiny_file_transfer(&mut user, false);
|
|
tb.acknowledge_eof_pdu(&mut user, &transfer_info);
|
|
tb.finish_handling(&mut user, &transfer_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
}
|
|
|
|
#[test]
|
|
fn test_tiny_file_transfer_not_acked_with_closure() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
let mut user = TestCfdpUser::default();
|
|
let (_data, transfer_info) = tb.common_tiny_file_transfer(&mut user, true);
|
|
tb.finish_handling(&mut user, &transfer_info)
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_segment_file_transfer_not_acked_no_closure() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 128);
|
|
let mut user = TestCfdpUser::default();
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.open(&tb.srcfile)
|
|
.expect("opening file failed");
|
|
let mut rand_data = [0u8; 140];
|
|
rand::rng().fill(&mut rand_data[..]);
|
|
file.write_all(&rand_data)
|
|
.expect("writing file content failed");
|
|
drop(file);
|
|
let (_, fd_pdus) = tb.generic_file_transfer(&mut user, false, rand_data.to_vec());
|
|
assert_eq!(fd_pdus, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_segment_file_transfer_not_acked_with_closure() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 128);
|
|
let mut user = TestCfdpUser::default();
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.open(&tb.srcfile)
|
|
.expect("opening file failed");
|
|
let mut rand_data = [0u8; 140];
|
|
rand::rng().fill(&mut rand_data[..]);
|
|
file.write_all(&rand_data)
|
|
.expect("writing file content failed");
|
|
drop(file);
|
|
let (transfer_info, fd_pdus) =
|
|
tb.generic_file_transfer(&mut user, true, rand_data.to_vec());
|
|
assert_eq!(fd_pdus, 2);
|
|
tb.finish_handling(&mut user, &transfer_info)
|
|
}
|
|
|
|
#[test]
|
|
fn test_two_segment_file_transfer_acked() {
|
|
let mut user = TestCfdpUser::default();
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 128);
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.open(&tb.srcfile)
|
|
.expect("opening file failed");
|
|
let mut rand_data = [0u8; 140];
|
|
rand::rng().fill(&mut rand_data[..]);
|
|
file.write_all(&rand_data)
|
|
.expect("writing file content failed");
|
|
drop(file);
|
|
let (transfer_info, fd_pdus) =
|
|
tb.generic_file_transfer(&mut user, true, rand_data.to_vec());
|
|
assert_eq!(fd_pdus, 2);
|
|
tb.acknowledge_eof_pdu(&mut user, &transfer_info);
|
|
tb.finish_handling(&mut user, &transfer_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
}
|
|
|
|
#[test]
|
|
fn test_empty_file_transfer_not_acked_with_closure() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
let file_size = 0;
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Unacknowledged),
|
|
Some(true),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transaction_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
tb.common_eof_pdu_check(
|
|
&mut user,
|
|
transaction_info.closure_requested,
|
|
EofParams::new_success(file_size, CRC_32.digest().finalize()),
|
|
1,
|
|
);
|
|
tb.finish_handling(&mut user, &transaction_info);
|
|
user.verify_finished_indication_retained(
|
|
DeliveryCode::Complete,
|
|
ConditionCode::NoError,
|
|
transaction_info.id,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_put_request_no_remote_cfg() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
|
|
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(
|
|
INVALID_ID.into(),
|
|
&srcfile_str,
|
|
&destfile_str,
|
|
Some(TransmissionMode::Unacknowledged),
|
|
Some(true),
|
|
)
|
|
.expect("creating put request failed");
|
|
let error = tb.handler.put_request(&put_request);
|
|
assert!(error.is_err());
|
|
let error = error.unwrap_err();
|
|
if let PutRequestError::NoRemoteCfgFound(id) = error {
|
|
assert_eq!(id, INVALID_ID.into());
|
|
} else {
|
|
panic!("unexpected error type: {:?}", error);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_put_request_file_does_not_exist() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
|
|
let file_which_does_not_exist = "/tmp/this_file_does_not_exist.txt";
|
|
let destfile = "/tmp/tmp.txt";
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
file_which_does_not_exist,
|
|
destfile,
|
|
Some(TransmissionMode::Unacknowledged),
|
|
Some(true),
|
|
)
|
|
.expect("creating put request failed");
|
|
let error = tb.put_request(&put_request);
|
|
assert!(error.is_err());
|
|
let error = error.unwrap_err();
|
|
if !matches!(error, PutRequestError::FileDoesNotExist) {
|
|
panic!("unexpected error type: {:?}", error);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_finished_pdu_check_timeout() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
let file_size = 0;
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Unacknowledged),
|
|
Some(true),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transaction_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
let expected_id = tb.handler.transaction_id().unwrap();
|
|
tb.common_eof_pdu_check(
|
|
&mut user,
|
|
transaction_info.closure_requested,
|
|
EofParams::new_success(file_size, CRC_32.digest().finalize()),
|
|
1,
|
|
);
|
|
assert!(tb.pdu_queue_empty());
|
|
|
|
// Enforce a check limit error by expiring the check limit timer -> leads to a notice of
|
|
// cancellation -> leads to an EOF PDU with the appropriate error code.
|
|
tb.expiry_control.set_check_limit_expired();
|
|
|
|
assert_eq!(tb.handler.state_machine_no_packet(&mut user).unwrap(), 1);
|
|
assert!(!tb.pdu_queue_empty());
|
|
let next_pdu = tb.get_next_sent_pdu().unwrap();
|
|
let eof_pdu = EofPdu::from_bytes(&next_pdu.raw_pdu).expect("invalid EOF PDU format");
|
|
tb.common_pdu_check_for_file_transfer(eof_pdu.pdu_header(), CrcFlag::NoCrc);
|
|
assert_eq!(eof_pdu.condition_code(), ConditionCode::CheckLimitReached);
|
|
assert_eq!(eof_pdu.file_size(), 0);
|
|
assert_eq!(eof_pdu.file_checksum(), 0);
|
|
|
|
// Cancellation fault should have been triggered.
|
|
let fault_handler = tb.test_fault_handler_mut();
|
|
let fh_ref_mut = fault_handler.get_mut();
|
|
assert!(!fh_ref_mut.cancellation_queue_empty());
|
|
assert_eq!(fh_ref_mut.notice_of_cancellation_queue.len(), 1);
|
|
let FaultInfo {
|
|
transaction_id,
|
|
condition_code,
|
|
progress,
|
|
} = fh_ref_mut.notice_of_cancellation_queue.pop_back().unwrap();
|
|
assert_eq!(transaction_id, expected_id);
|
|
assert_eq!(condition_code, ConditionCode::CheckLimitReached);
|
|
assert_eq!(progress, 0);
|
|
fh_ref_mut.all_queues_empty();
|
|
}
|
|
|
|
#[test]
|
|
fn test_cancelled_transfer_empty_file() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 512);
|
|
let filesize = 0;
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Unacknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, filesize);
|
|
assert_eq!(user.transaction_indication_call_count, 0);
|
|
assert_eq!(user.eof_sent_call_count, 0);
|
|
|
|
tb.put_request(&put_request)
|
|
.expect("put_request call failed");
|
|
assert_eq!(tb.handler.state(), State::Busy);
|
|
assert_eq!(tb.handler.step(), TransactionStep::Idle);
|
|
assert!(tb.get_next_sent_pdu().is_none());
|
|
let id = tb.handler.transaction_id().unwrap();
|
|
tb.handler
|
|
.cancel_request(&mut user, &id)
|
|
.expect("transaction cancellation failed");
|
|
assert_eq!(tb.handler.state(), State::Idle);
|
|
assert_eq!(tb.handler.step(), TransactionStep::Idle);
|
|
// EOF (Cancel) PDU will be generated
|
|
let eof_pdu = tb
|
|
.get_next_sent_pdu()
|
|
.expect("no EOF PDU generated like expected");
|
|
assert_eq!(
|
|
eof_pdu.file_directive_type.unwrap(),
|
|
FileDirectiveType::EofPdu
|
|
);
|
|
let eof_pdu = EofPdu::from_bytes(&eof_pdu.raw_pdu).unwrap();
|
|
assert_eq!(
|
|
eof_pdu.condition_code(),
|
|
ConditionCode::CancelRequestReceived
|
|
);
|
|
assert_eq!(eof_pdu.file_checksum(), 0);
|
|
assert_eq!(eof_pdu.file_size(), 0);
|
|
tb.common_pdu_check_for_file_transfer(eof_pdu.pdu_header(), CrcFlag::NoCrc);
|
|
}
|
|
|
|
#[test]
|
|
fn test_cancelled_transfer_mid_transfer() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Unacknowledged, false, 128);
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.open(&tb.srcfile)
|
|
.expect("opening file failed");
|
|
let mut rand_data = [0u8; 140];
|
|
rand::rng().fill(&mut rand_data[..]);
|
|
file.write_all(&rand_data)
|
|
.expect("writing file content failed");
|
|
drop(file);
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Unacknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let file_size = rand_data.len() as u64;
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transaction_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
let mut chunks = rand_data.chunks(
|
|
calculate_max_file_seg_len_for_max_packet_len_and_pdu_header(
|
|
&transaction_info.pdu_header,
|
|
tb.max_packet_len,
|
|
None,
|
|
),
|
|
);
|
|
let mut digest = CRC_32.digest();
|
|
let first_chunk = chunks.next().expect("no chunk found");
|
|
digest.update(first_chunk);
|
|
let checksum = digest.finalize();
|
|
let next_packet = tb.get_next_sent_pdu().unwrap();
|
|
assert_eq!(next_packet.pdu_type, PduType::FileData);
|
|
let fd_pdu = FileDataPdu::from_bytes(&next_packet.raw_pdu).unwrap();
|
|
assert_eq!(fd_pdu.file_data(), &rand_data[0..first_chunk.len()]);
|
|
let expected_id = tb.handler.transaction_id().unwrap();
|
|
assert!(
|
|
tb.handler
|
|
.cancel_request(&mut user, &expected_id)
|
|
.expect("cancellation failed")
|
|
);
|
|
assert_eq!(tb.handler.state(), State::Idle);
|
|
assert_eq!(tb.handler.step(), TransactionStep::Idle);
|
|
let next_packet = tb.get_next_sent_pdu().unwrap();
|
|
assert_eq!(next_packet.pdu_type, PduType::FileDirective);
|
|
assert_eq!(
|
|
next_packet.file_directive_type.unwrap(),
|
|
FileDirectiveType::EofPdu
|
|
);
|
|
// As specified in 4.11.2.2 of the standard, the file size will be the progress of the
|
|
// file copy operation so far, and the checksum is calculated for that progress.
|
|
let eof_pdu = EofPdu::from_bytes(&next_packet.raw_pdu).expect("EOF PDU creation failed");
|
|
assert_eq!(eof_pdu.file_size(), first_chunk.len() as u64);
|
|
assert_eq!(eof_pdu.file_checksum(), checksum);
|
|
assert_eq!(
|
|
eof_pdu.condition_code(),
|
|
ConditionCode::CancelRequestReceived
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_positive_ack_procedure() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 512);
|
|
let file_size = 0;
|
|
let eof_params = EofParams {
|
|
file_size,
|
|
file_checksum: CRC_32.digest().finalize(),
|
|
condition_code: ConditionCode::NoError,
|
|
};
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Acknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transfer_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 1);
|
|
|
|
assert!(tb.pdu_queue_empty());
|
|
|
|
// Enforce a postive ack timer expiry -> leads to a re-send of the EOF PDU.
|
|
tb.expiry_control.set_positive_ack_expired();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine_no_packet(&mut user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 1);
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 2);
|
|
|
|
tb.acknowledge_eof_pdu(&mut user, &transfer_info);
|
|
tb.finish_handling(&mut user, &transfer_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
user.verify_finished_indication_retained(
|
|
DeliveryCode::Complete,
|
|
ConditionCode::NoError,
|
|
transfer_info.id,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_positive_ack_procedure_ack_limit_reached() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 512);
|
|
let file_size = 0;
|
|
let mut eof_params = EofParams::new_success(file_size, CRC_32.digest().finalize());
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Acknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transfer_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 1);
|
|
|
|
assert!(tb.pdu_queue_empty());
|
|
|
|
// Enforce a postive ack timer expiry -> leads to a re-send of the EOF PDU.
|
|
tb.expiry_control.set_positive_ack_expired();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine_no_packet(&mut user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 1);
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 2);
|
|
// Enforce a postive ack timer expiry -> leads to a re-send of the EOF PDU.
|
|
tb.expiry_control.set_positive_ack_expired();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine_no_packet(&mut user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 1);
|
|
eof_params.condition_code = ConditionCode::PositiveAckLimitReached;
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 3);
|
|
// This boilerplate handling is still expected. In a real-life use-case I would expect
|
|
// this to fail as well, leading to a transaction abandonment. This is tested separately.
|
|
tb.acknowledge_eof_pdu(&mut user, &transfer_info);
|
|
tb.finish_handling(&mut user, &transfer_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
user.verify_finished_indication_retained(
|
|
DeliveryCode::Complete,
|
|
ConditionCode::NoError,
|
|
transfer_info.id,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_positive_ack_procedure_ack_limit_reached_abandonment() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 512);
|
|
let file_size = 0;
|
|
let mut eof_params = EofParams::new_success(file_size, CRC_32.digest().finalize());
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Acknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transfer_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 1);
|
|
|
|
assert!(tb.pdu_queue_empty());
|
|
|
|
// Enforce a postive ack timer expiry -> leads to a re-send of the EOF PDU.
|
|
tb.expiry_control.set_positive_ack_expired();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine_no_packet(&mut user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 1);
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 2);
|
|
// Enforce a postive ack timer expiry -> positive ACK limit reached -> Cancel EOF sent.
|
|
tb.expiry_control.set_positive_ack_expired();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine_no_packet(&mut user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 1);
|
|
eof_params.condition_code = ConditionCode::PositiveAckLimitReached;
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 3);
|
|
// Cancellation fault should have been triggered.
|
|
let fault_handler = tb.test_fault_handler_mut();
|
|
let fh_ref_mut = fault_handler.get_mut();
|
|
assert!(!fh_ref_mut.cancellation_queue_empty());
|
|
assert_eq!(fh_ref_mut.notice_of_cancellation_queue.len(), 1);
|
|
let FaultInfo {
|
|
transaction_id,
|
|
condition_code,
|
|
progress,
|
|
} = fh_ref_mut.notice_of_cancellation_queue.pop_back().unwrap();
|
|
assert_eq!(transaction_id, transfer_info.id);
|
|
assert_eq!(condition_code, ConditionCode::PositiveAckLimitReached);
|
|
assert_eq!(progress, file_size);
|
|
fh_ref_mut.all_queues_empty();
|
|
|
|
// Enforce a postive ack timer expiry -> leads to a re-send of the EOF Cancel PDU.
|
|
tb.expiry_control.set_positive_ack_expired();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine_no_packet(&mut user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 1);
|
|
tb.common_eof_pdu_check(&mut user, transfer_info.closure_requested, eof_params, 4);
|
|
|
|
// Enforce a postive ack timer expiry -> positive ACK limit reached -> Transaction
|
|
// abandoned
|
|
tb.expiry_control.set_positive_ack_expired();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine_no_packet(&mut user)
|
|
.expect("source handler FSM failure");
|
|
assert_eq!(sent_packets, 0);
|
|
// Abandonment fault should have been triggered.
|
|
let fault_handler = tb.test_fault_handler_mut();
|
|
let fh_ref_mut = fault_handler.get_mut();
|
|
assert!(!fh_ref_mut.abandoned_queue_empty());
|
|
assert_eq!(fh_ref_mut.abandoned_queue.len(), 1);
|
|
let FaultInfo {
|
|
transaction_id,
|
|
condition_code,
|
|
progress,
|
|
} = fh_ref_mut.abandoned_queue.pop_back().unwrap();
|
|
assert_eq!(transaction_id, transfer_info.id);
|
|
assert_eq!(condition_code, ConditionCode::PositiveAckLimitReached);
|
|
assert_eq!(progress, file_size);
|
|
fh_ref_mut.all_queues_empty();
|
|
}
|
|
|
|
#[test]
|
|
fn test_nak_for_whole_file() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 512);
|
|
let mut user = TestCfdpUser::default();
|
|
let (data, transfer_info) = tb.common_tiny_file_transfer(&mut user, true);
|
|
let seg_reqs = &[(0, transfer_info.file_size as u32)];
|
|
tb.nak_for_file_segments(&mut user, &transfer_info, seg_reqs);
|
|
tb.check_next_file_pdu(0, data.as_bytes());
|
|
tb.all_fault_queues_empty();
|
|
|
|
tb.acknowledge_eof_pdu(&mut user, &transfer_info);
|
|
tb.finish_handling(&mut user, &transfer_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
}
|
|
|
|
#[test]
|
|
fn test_nak_for_file_segment() {
|
|
let mut user = TestCfdpUser::default();
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 128);
|
|
let mut file = OpenOptions::new()
|
|
.write(true)
|
|
.open(&tb.srcfile)
|
|
.expect("opening file failed");
|
|
let mut rand_data = [0u8; 140];
|
|
rand::rng().fill(&mut rand_data[..]);
|
|
file.write_all(&rand_data)
|
|
.expect("writing file content failed");
|
|
drop(file);
|
|
let (transfer_info, fd_pdus) =
|
|
tb.generic_file_transfer(&mut user, false, rand_data.to_vec());
|
|
assert_eq!(fd_pdus, 2);
|
|
tb.nak_for_file_segments(&mut user, &transfer_info, &[(0, 90)]);
|
|
tb.check_next_file_pdu(0, &rand_data[0..90]);
|
|
tb.all_fault_queues_empty();
|
|
|
|
tb.acknowledge_eof_pdu(&mut user, &transfer_info);
|
|
tb.finish_handling(&mut user, &transfer_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
}
|
|
|
|
#[test]
|
|
fn test_nak_for_metadata() {
|
|
let mut tb = SourceHandlerTestbench::new(TransmissionMode::Acknowledged, false, 512);
|
|
let file_size = 0;
|
|
let put_request = PutRequestOwned::new_regular_request(
|
|
REMOTE_ID.into(),
|
|
&tb.srcfile,
|
|
&tb.destfile,
|
|
Some(TransmissionMode::Acknowledged),
|
|
Some(false),
|
|
)
|
|
.expect("creating put request failed");
|
|
let mut user = tb.create_user(0, file_size);
|
|
let transfer_info =
|
|
tb.common_file_transfer_init_with_metadata_check(&mut user, put_request, file_size);
|
|
tb.common_eof_pdu_check(
|
|
&mut user,
|
|
transfer_info.closure_requested,
|
|
EofParams::new_success(file_size, CRC_32.digest().finalize()),
|
|
1,
|
|
);
|
|
|
|
// NAK to cause re-transmission of metadata PDU.
|
|
let nak_pdu = NakPduCreator::new_normal_file_size(
|
|
transfer_info.pdu_header,
|
|
0,
|
|
transfer_info.file_size as u32,
|
|
&[(0, 0)],
|
|
)
|
|
.unwrap();
|
|
let nak_pdu_vec = nak_pdu.to_vec().unwrap();
|
|
let packet_info = PduRawWithInfo::new(&nak_pdu_vec).unwrap();
|
|
let sent_packets = tb
|
|
.handler
|
|
.state_machine(&mut user, Some(&packet_info))
|
|
.unwrap();
|
|
assert_eq!(sent_packets, 1);
|
|
let next_pdu = tb.get_next_sent_pdu().unwrap();
|
|
// Check the metadata PDU.
|
|
tb.metadata_check(&next_pdu, file_size);
|
|
tb.all_fault_queues_empty();
|
|
|
|
tb.acknowledge_eof_pdu(&mut user, &transfer_info);
|
|
tb.finish_handling(&mut user, &transfer_info);
|
|
tb.common_finished_pdu_ack_check();
|
|
user.verify_finished_indication_retained(
|
|
DeliveryCode::Complete,
|
|
ConditionCode::NoError,
|
|
transfer_info.id,
|
|
);
|
|
}
|
|
}
|