#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std;
#[cfg(feature = "std")]
pub mod dest;
#[cfg(feature = "alloc")]
pub mod filestore;
pub mod request;
#[cfg(feature = "std")]
pub mod source;
pub mod time;
pub mod user;
use crate::time::CountdownProvider;
use core::{cell::RefCell, fmt::Debug, hash::Hash};
use crc::{Crc, CRC_32_ISCSI, CRC_32_ISO_HDLC};
#[cfg(feature = "std")]
use hashbrown::HashMap;
#[cfg(feature = "alloc")]
pub use alloc_mod::*;
use core::time::Duration;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use spacepackets::{
cfdp::{
pdu::{FileDirectiveType, PduError, PduHeader},
ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
},
util::{UnsignedByteField, UnsignedEnum},
};
#[cfg(feature = "std")]
pub use std_mod::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum EntityType {
Sending,
Receiving,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimerContext {
CheckLimit {
local_id: UnsignedByteField,
remote_id: UnsignedByteField,
entity_type: EntityType,
},
NakActivity {
expiry_time: Duration,
},
PositiveAck {
expiry_time: Duration,
},
}
pub trait TimerCreatorProvider {
type Countdown: CountdownProvider;
fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown;
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RemoteEntityConfig {
pub entity_id: UnsignedByteField,
pub max_packet_len: usize,
pub max_file_segment_len: Option<usize>,
pub closure_requested_by_default: bool,
pub crc_on_transmission_by_default: bool,
pub default_transmission_mode: TransmissionMode,
pub default_crc_type: ChecksumType,
pub positive_ack_timer_interval_seconds: f32,
pub positive_ack_timer_expiration_limit: u32,
pub check_limit: u32,
pub disposition_on_cancellation: bool,
pub immediate_nak_mode: bool,
pub nak_timer_interval_seconds: f32,
pub nak_timer_expiration_limit: u32,
}
impl RemoteEntityConfig {
pub fn new_with_default_values(
entity_id: UnsignedByteField,
max_packet_len: usize,
closure_requested_by_default: bool,
crc_on_transmission_by_default: bool,
default_transmission_mode: TransmissionMode,
default_crc_type: ChecksumType,
) -> Self {
Self {
entity_id,
max_file_segment_len: None,
max_packet_len,
closure_requested_by_default,
crc_on_transmission_by_default,
default_transmission_mode,
default_crc_type,
check_limit: 2,
positive_ack_timer_interval_seconds: 10.0,
positive_ack_timer_expiration_limit: 2,
disposition_on_cancellation: false,
immediate_nak_mode: true,
nak_timer_interval_seconds: 10.0,
nak_timer_expiration_limit: 2,
}
}
}
pub trait RemoteEntityConfigProvider {
fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig>;
fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>;
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool;
fn remove_config(&mut self, remote_id: u64) -> bool;
}
#[cfg(feature = "std")]
#[derive(Default, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StdRemoteEntityConfigProvider(pub HashMap<u64, RemoteEntityConfig>);
#[cfg(feature = "std")]
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
self.0.get(&remote_id)
}
fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
self.0.get_mut(&remote_id)
}
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
self.0.insert(cfg.entity_id.value(), *cfg).is_some()
}
fn remove_config(&mut self, remote_id: u64) -> bool {
self.0.remove(&remote_id).is_some()
}
}
#[cfg(feature = "alloc")]
#[derive(Default, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct VecRemoteEntityConfigProvider(pub alloc::vec::Vec<RemoteEntityConfig>);
#[cfg(feature = "alloc")]
impl RemoteEntityConfigProvider for VecRemoteEntityConfigProvider {
fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
self.0
.iter()
.find(|&cfg| cfg.entity_id.value() == remote_id)
}
fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
self.0
.iter_mut()
.find(|cfg| cfg.entity_id.value() == remote_id)
}
fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
self.0.push(*cfg);
true
}
fn remove_config(&mut self, remote_id: u64) -> bool {
for (idx, cfg) in self.0.iter().enumerate() {
if cfg.entity_id.value() == remote_id {
self.0.remove(idx);
return true;
}
}
false
}
}
impl RemoteEntityConfigProvider for RemoteEntityConfig {
fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
if remote_id == self.entity_id.value() {
return Some(self);
}
None
}
fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
if remote_id == self.entity_id.value() {
return Some(self);
}
None
}
fn add_config(&mut self, _cfg: &RemoteEntityConfig) -> bool {
false
}
fn remove_config(&mut self, _remote_id: u64) -> bool {
false
}
}
pub trait UserFaultHookProvider {
fn notice_of_suspension_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
);
fn notice_of_cancellation_cb(
&mut self,
transaction_id: TransactionId,
cond: ConditionCode,
progress: u64,
);
fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
}
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
pub struct DummyFaultHook {}
impl UserFaultHookProvider for DummyFaultHook {
fn notice_of_suspension_cb(
&mut self,
_transaction_id: TransactionId,
_cond: ConditionCode,
_progress: u64,
) {
}
fn notice_of_cancellation_cb(
&mut self,
_transaction_id: TransactionId,
_cond: ConditionCode,
_progress: u64,
) {
}
fn abandoned_cb(
&mut self,
_transaction_id: TransactionId,
_cond: ConditionCode,
_progress: u64,
) {
}
fn ignore_cb(&mut self, _transaction_id: TransactionId, _cond: ConditionCode, _progress: u64) {}
}
pub struct FaultHandler<UserHandler: UserFaultHookProvider> {
handler_array: [FaultHandlerCode; 10],
pub user_hook: RefCell<UserHandler>,
}
impl<UserHandler: UserFaultHookProvider> FaultHandler<UserHandler> {
fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
Some(match conditon_code {
ConditionCode::PositiveAckLimitReached => 0,
ConditionCode::KeepAliveLimitReached => 1,
ConditionCode::InvalidTransmissionMode => 2,
ConditionCode::FilestoreRejection => 3,
ConditionCode::FileChecksumFailure => 4,
ConditionCode::FileSizeError => 5,
ConditionCode::NakLimitReached => 6,
ConditionCode::InactivityDetected => 7,
ConditionCode::CheckLimitReached => 8,
ConditionCode::UnsupportedChecksumType => 9,
_ => return None,
})
}
pub fn set_fault_handler(
&mut self,
condition_code: ConditionCode,
fault_handler: FaultHandlerCode,
) {
let array_idx = Self::condition_code_to_array_index(condition_code);
if array_idx.is_none() {
return;
}
self.handler_array[array_idx.unwrap()] = fault_handler;
}
pub fn new(user_fault_handler: UserHandler) -> Self {
let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
init_array
[Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
FaultHandlerCode::IgnoreError;
init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType)
.unwrap()] = FaultHandlerCode::IgnoreError;
Self {
handler_array: init_array,
user_hook: RefCell::new(user_fault_handler),
}
}
pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode {
let array_idx = Self::condition_code_to_array_index(condition_code);
if array_idx.is_none() {
return FaultHandlerCode::IgnoreError;
}
self.handler_array[array_idx.unwrap()]
}
pub fn report_fault(
&self,
transaction_id: TransactionId,
condition: ConditionCode,
progress: u64,
) -> FaultHandlerCode {
let array_idx = Self::condition_code_to_array_index(condition);
if array_idx.is_none() {
return FaultHandlerCode::IgnoreError;
}
let fh_code = self.handler_array[array_idx.unwrap()];
let mut handler_mut = self.user_hook.borrow_mut();
match fh_code {
FaultHandlerCode::NoticeOfCancellation => {
handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
}
FaultHandlerCode::NoticeOfSuspension => {
handler_mut.notice_of_suspension_cb(transaction_id, condition, progress);
}
FaultHandlerCode::IgnoreError => {
handler_mut.ignore_cb(transaction_id, condition, progress);
}
FaultHandlerCode::AbandonTransaction => {
handler_mut.abandoned_cb(transaction_id, condition, progress);
}
}
fh_code
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct IndicationConfig {
pub eof_sent: bool,
pub eof_recv: bool,
pub file_segment_recv: bool,
pub transaction_finished: bool,
pub suspended: bool,
pub resumed: bool,
}
impl Default for IndicationConfig {
fn default() -> Self {
Self {
eof_sent: true,
eof_recv: true,
file_segment_recv: true,
transaction_finished: true,
suspended: true,
resumed: true,
}
}
}
pub struct LocalEntityConfig<UserFaultHook: UserFaultHookProvider> {
pub id: UnsignedByteField,
pub indication_cfg: IndicationConfig,
pub fault_handler: FaultHandler<UserFaultHook>,
}
impl<UserFaultHook: UserFaultHookProvider> LocalEntityConfig<UserFaultHook> {
pub fn new(
id: UnsignedByteField,
indication_cfg: IndicationConfig,
hook: UserFaultHook,
) -> Self {
Self {
id,
indication_cfg,
fault_handler: FaultHandler::new(hook),
}
}
}
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
}
}
#[cfg(feature = "std")]
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum GenericSendError {
#[error("RX disconnected")]
RxDisconnected,
#[error("queue is full, fill count {0:?}")]
QueueFull(Option<u32>),
#[error("other send error")]
Other,
}
#[cfg(feature = "std")]
pub trait PduSendProvider {
fn send_pdu(
&self,
pdu_type: PduType,
file_directive_type: Option<FileDirectiveType>,
raw_pdu: &[u8],
) -> Result<(), GenericSendError>;
}
#[cfg(feature = "std")]
pub mod std_mod {
use std::sync::mpsc;
use super::*;
impl PduSendProvider for mpsc::Sender<PduOwnedWithInfo> {
fn send_pdu(
&self,
pdu_type: PduType,
file_directive_type: Option<FileDirectiveType>,
raw_pdu: &[u8],
) -> Result<(), GenericSendError> {
self.send(PduOwnedWithInfo::new(
pdu_type,
file_directive_type,
raw_pdu.to_vec(),
))
.map_err(|_| GenericSendError::RxDisconnected)?;
Ok(())
}
}
#[derive(Debug)]
pub struct StdCountdown {
expiry_time: Duration,
start_time: std::time::Instant,
}
impl StdCountdown {
pub fn new(expiry_time: Duration) -> Self {
Self {
expiry_time,
start_time: std::time::Instant::now(),
}
}
pub fn expiry_time_seconds(&self) -> u64 {
self.expiry_time.as_secs()
}
}
impl CountdownProvider for StdCountdown {
fn has_expired(&self) -> bool {
if self.start_time.elapsed() > self.expiry_time {
return true;
}
false
}
fn reset(&mut self) {
self.start_time = std::time::Instant::now();
}
}
pub struct StdTimerCreator {
pub check_limit_timeout: Duration,
}
impl StdTimerCreator {
pub const fn new(check_limit_timeout: Duration) -> Self {
Self {
check_limit_timeout,
}
}
}
impl Default for StdTimerCreator {
fn default() -> Self {
Self::new(Duration::from_secs(5))
}
}
impl TimerCreatorProvider for StdTimerCreator {
type Countdown = StdCountdown;
fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown {
match timer_context {
TimerContext::CheckLimit {
local_id: _,
remote_id: _,
entity_type: _,
} => StdCountdown::new(self.check_limit_timeout),
TimerContext::NakActivity { expiry_time } => StdCountdown::new(expiry_time),
TimerContext::PositiveAck { expiry_time } => StdCountdown::new(expiry_time),
}
}
}
}
#[derive(Debug, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TransactionId {
source_id: UnsignedByteField,
seq_num: UnsignedByteField,
}
impl TransactionId {
pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
Self { source_id, seq_num }
}
pub fn source_id(&self) -> &UnsignedByteField {
&self.source_id
}
pub fn seq_num(&self) -> &UnsignedByteField {
&self.seq_num
}
}
impl Hash for TransactionId {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.source_id.value().hash(state);
self.seq_num.value().hash(state);
}
}
impl PartialEq for TransactionId {
fn eq(&self, other: &Self) -> bool {
self.source_id.value() == other.source_id.value()
&& self.seq_num.value() == other.seq_num.value()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum State {
Idle = 0,
Busy = 1,
Suspended = 2,
}
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
pub const CRC_32C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PacketTarget {
SourceEntity,
DestEntity,
}
pub trait PduProvider {
fn pdu_type(&self) -> PduType;
fn file_directive_type(&self) -> Option<FileDirectiveType>;
fn pdu(&self) -> &[u8];
fn packet_target(&self) -> Result<PacketTarget, PduError>;
}
pub struct DummyPduProvider(());
impl PduProvider for DummyPduProvider {
fn pdu_type(&self) -> PduType {
PduType::FileData
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
None
}
fn pdu(&self) -> &[u8] {
&[]
}
fn packet_target(&self) -> Result<PacketTarget, PduError> {
Ok(PacketTarget::SourceEntity)
}
}
pub struct PduRawWithInfo<'raw_packet> {
pdu_type: PduType,
file_directive_type: Option<FileDirectiveType>,
packet_len: usize,
raw_packet: &'raw_packet [u8],
}
pub fn determine_packet_target(raw_pdu: &[u8]) -> Result<PacketTarget, PduError> {
let (header, header_len) = PduHeader::from_bytes(raw_pdu)?;
if header.pdu_type() == PduType::FileData {
return Ok(PacketTarget::DestEntity);
}
let file_directive_type = FileDirectiveType::try_from(raw_pdu[header_len]).map_err(|_| {
PduError::InvalidDirectiveType {
found: raw_pdu[header_len],
expected: None,
}
})?;
let packet_target =
match file_directive_type {
FileDirectiveType::NakPdu
| FileDirectiveType::FinishedPdu
| FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
FileDirectiveType::MetadataPdu
| FileDirectiveType::EofPdu
| FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
FileDirectiveType::AckPdu => {
let acked_directive = FileDirectiveType::try_from(raw_pdu[header_len + 1])
.map_err(|_| PduError::InvalidDirectiveType {
found: raw_pdu[header_len],
expected: None,
})?;
if acked_directive == FileDirectiveType::EofPdu {
PacketTarget::SourceEntity
} else if acked_directive == FileDirectiveType::FinishedPdu {
PacketTarget::DestEntity
} else {
return Err(PduError::InvalidDirectiveType {
found: raw_pdu[header_len + 1],
expected: None,
});
}
}
};
Ok(packet_target)
}
impl<'raw> PduRawWithInfo<'raw> {
pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
if pdu_header.pdu_type() == PduType::FileData {
return Ok(Self {
pdu_type: pdu_header.pdu_type(),
file_directive_type: None,
packet_len: pdu_header.pdu_len(),
raw_packet,
});
}
if pdu_header.pdu_datafield_len() < 1 {
return Err(PduError::FormatError);
}
let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
PduError::InvalidDirectiveType {
found: raw_packet[header_len],
expected: None,
}
})?;
Ok(Self {
pdu_type: pdu_header.pdu_type(),
file_directive_type: Some(directive),
packet_len: pdu_header.pdu_len(),
raw_packet,
})
}
pub fn raw_packet(&self) -> &[u8] {
&self.raw_packet[0..self.packet_len]
}
}
impl PduProvider for PduRawWithInfo<'_> {
fn pdu_type(&self) -> PduType {
self.pdu_type
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
self.file_directive_type
}
fn pdu(&self) -> &[u8] {
self.raw_packet
}
fn packet_target(&self) -> Result<PacketTarget, PduError> {
determine_packet_target(self.raw_packet)
}
}
#[cfg(feature = "alloc")]
pub mod alloc_mod {
use spacepackets::cfdp::{
pdu::{FileDirectiveType, PduError},
PduType,
};
use crate::{determine_packet_target, PacketTarget, PduProvider, PduRawWithInfo};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct PduOwnedWithInfo {
pub pdu_type: PduType,
pub file_directive_type: Option<FileDirectiveType>,
pub pdu: alloc::vec::Vec<u8>,
}
impl PduOwnedWithInfo {
pub fn new_from_raw_packet(raw_packet: &[u8]) -> Result<Self, PduError> {
Ok(PduRawWithInfo::new(raw_packet)?.into())
}
pub fn new(
pdu_type: PduType,
file_directive_type: Option<FileDirectiveType>,
pdu: alloc::vec::Vec<u8>,
) -> Self {
Self {
pdu_type,
file_directive_type,
pdu,
}
}
}
impl From<PduRawWithInfo<'_>> for PduOwnedWithInfo {
fn from(value: PduRawWithInfo) -> Self {
Self::new(
value.pdu_type(),
value.file_directive_type(),
value.raw_packet().to_vec(),
)
}
}
impl PduProvider for PduOwnedWithInfo {
fn pdu_type(&self) -> PduType {
self.pdu_type
}
fn file_directive_type(&self) -> Option<FileDirectiveType> {
self.file_directive_type
}
fn pdu(&self) -> &[u8] {
&self.pdu
}
fn packet_target(&self) -> Result<PacketTarget, PduError> {
determine_packet_target(&self.pdu)
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use core::cell::RefCell;
use alloc::{collections::VecDeque, string::String, vec::Vec};
use spacepackets::{
cfdp::{
lv::Lv,
pdu::{
eof::EofPdu,
file_data::FileDataPdu,
metadata::{MetadataGenericParams, MetadataPduCreator},
CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket,
},
ChecksumType, ConditionCode, PduType, TransmissionMode,
},
util::{UnsignedByteField, UnsignedByteFieldU16, UnsignedByteFieldU8, UnsignedEnum},
};
use user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams};
use crate::{PacketTarget, StdCountdown};
use super::*;
pub const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1);
pub const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
pub struct FileSegmentRecvdParamsNoSegMetadata {
#[allow(dead_code)]
pub id: TransactionId,
pub offset: u64,
pub length: usize,
}
#[derive(Default)]
pub struct TestCfdpUser {
pub next_expected_seq_num: u64,
pub expected_full_src_name: String,
pub expected_full_dest_name: String,
pub expected_file_size: u64,
pub transaction_indication_call_count: u32,
pub eof_sent_call_count: u32,
pub eof_recvd_call_count: u32,
pub finished_indic_queue: VecDeque<TransactionFinishedParams>,
pub metadata_recv_queue: VecDeque<OwnedMetadataRecvdParams>,
pub file_seg_recvd_queue: VecDeque<FileSegmentRecvdParamsNoSegMetadata>,
}
impl TestCfdpUser {
pub fn new(
next_expected_seq_num: u64,
expected_full_src_name: String,
expected_full_dest_name: String,
expected_file_size: u64,
) -> Self {
Self {
next_expected_seq_num,
expected_full_src_name,
expected_full_dest_name,
expected_file_size,
transaction_indication_call_count: 0,
eof_recvd_call_count: 0,
eof_sent_call_count: 0,
finished_indic_queue: VecDeque::new(),
metadata_recv_queue: VecDeque::new(),
file_seg_recvd_queue: VecDeque::new(),
}
}
pub fn generic_id_check(&self, id: &crate::TransactionId) {
assert_eq!(id.source_id, LOCAL_ID.into());
assert_eq!(id.seq_num().value(), self.next_expected_seq_num);
}
}
impl CfdpUser for TestCfdpUser {
fn transaction_indication(&mut self, id: &crate::TransactionId) {
self.generic_id_check(id);
self.transaction_indication_call_count += 1;
}
fn eof_sent_indication(&mut self, id: &crate::TransactionId) {
self.generic_id_check(id);
self.eof_sent_call_count += 1;
}
fn transaction_finished_indication(
&mut self,
finished_params: &crate::user::TransactionFinishedParams,
) {
self.generic_id_check(&finished_params.id);
self.finished_indic_queue.push_back(*finished_params);
}
fn metadata_recvd_indication(
&mut self,
md_recvd_params: &crate::user::MetadataReceivedParams,
) {
self.generic_id_check(&md_recvd_params.id);
assert_eq!(
String::from(md_recvd_params.src_file_name),
self.expected_full_src_name
);
assert_eq!(
String::from(md_recvd_params.dest_file_name),
self.expected_full_dest_name
);
assert_eq!(md_recvd_params.msgs_to_user.len(), 0);
assert_eq!(md_recvd_params.source_id, LOCAL_ID.into());
assert_eq!(md_recvd_params.file_size, self.expected_file_size);
self.metadata_recv_queue.push_back(md_recvd_params.into());
}
fn file_segment_recvd_indication(
&mut self,
segment_recvd_params: &crate::user::FileSegmentRecvdParams,
) {
self.generic_id_check(&segment_recvd_params.id);
self.file_seg_recvd_queue
.push_back(FileSegmentRecvdParamsNoSegMetadata {
id: segment_recvd_params.id,
offset: segment_recvd_params.offset,
length: segment_recvd_params.length,
})
}
fn report_indication(&mut self, _id: &crate::TransactionId) {}
fn suspended_indication(
&mut self,
_id: &crate::TransactionId,
_condition_code: ConditionCode,
) {
panic!("unexpected suspended indication");
}
fn resumed_indication(&mut self, _id: &crate::TransactionId, _progresss: u64) {}
fn fault_indication(
&mut self,
_id: &crate::TransactionId,
_condition_code: ConditionCode,
_progress: u64,
) {
panic!("unexpected fault indication");
}
fn abandoned_indication(
&mut self,
_id: &crate::TransactionId,
_condition_code: ConditionCode,
_progress: u64,
) {
panic!("unexpected abandoned indication");
}
fn eof_recvd_indication(&mut self, id: &crate::TransactionId) {
self.generic_id_check(id);
self.eof_recvd_call_count += 1;
}
}
#[derive(Default, Debug)]
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<(), GenericSendError> {
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(
dest_id: impl Into<UnsignedByteField>,
max_packet_len: usize,
crc_on_transmission_by_default: bool,
) -> StdRemoteEntityConfigProvider {
let mut table = StdRemoteEntityConfigProvider::default();
let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
dest_id.into(),
max_packet_len,
true,
crc_on_transmission_by_default,
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
table.add_config(&remote_entity_cfg);
table
}
fn generic_pdu_header() -> PduHeader {
let pdu_conf = CommonPduConfig::default();
PduHeader::new_no_file_data(pdu_conf, 0)
}
#[test]
fn test_transaction_id() {
let transaction_id = TransactionId::new(
UnsignedByteFieldU16::new(1).into(),
UnsignedByteFieldU16::new(2).into(),
);
assert_eq!(transaction_id.source_id().value(), 1);
assert_eq!(transaction_id.seq_num().value(), 2);
}
#[test]
fn test_metadata_pdu_info() {
let mut buf: [u8; 128] = [0; 128];
let pdu_header = generic_pdu_header();
let metadata_params = MetadataGenericParams::default();
let src_file_name = "hello.txt";
let dest_file_name = "hello-dest.txt";
let src_lv = Lv::new_from_str(src_file_name).unwrap();
let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
let metadata_pdu =
MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv);
metadata_pdu
.write_to_bytes(&mut buf)
.expect("writing metadata PDU failed");
let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
assert!(packet_info.file_directive_type().is_some());
assert_eq!(
packet_info.file_directive_type().unwrap(),
FileDirectiveType::MetadataPdu
);
assert_eq!(
packet_info.raw_packet(),
&buf[0..metadata_pdu.len_written()]
);
assert_eq!(
packet_info.packet_target().unwrap(),
PacketTarget::DestEntity
);
}
#[test]
fn test_filedata_pdu_info() {
let mut buf: [u8; 128] = [0; 128];
let pdu_header = generic_pdu_header();
let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
file_data_pdu
.write_to_bytes(&mut buf)
.expect("writing file data PDU failed");
let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
assert_eq!(
packet_info.raw_packet(),
&buf[0..file_data_pdu.len_written()]
);
assert_eq!(packet_info.pdu_type(), PduType::FileData);
assert!(packet_info.file_directive_type().is_none());
assert_eq!(
packet_info.packet_target().unwrap(),
PacketTarget::DestEntity
);
}
#[test]
fn test_eof_pdu_info() {
let mut buf: [u8; 128] = [0; 128];
let pdu_header = generic_pdu_header();
let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
eof_pdu
.write_to_bytes(&mut buf)
.expect("writing file data PDU failed");
let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
assert!(packet_info.file_directive_type().is_some());
assert_eq!(packet_info.raw_packet(), &buf[0..eof_pdu.len_written()]);
assert_eq!(
packet_info.file_directive_type().unwrap(),
FileDirectiveType::EofPdu
);
}
#[test]
fn test_std_check_timer() {
let mut std_check_timer = StdCountdown::new(Duration::from_secs(1));
assert!(!std_check_timer.has_expired());
assert_eq!(std_check_timer.expiry_time_seconds(), 1);
std::thread::sleep(Duration::from_millis(800));
assert!(!std_check_timer.has_expired());
std::thread::sleep(Duration::from_millis(205));
assert!(std_check_timer.has_expired());
std_check_timer.reset();
assert!(!std_check_timer.has_expired());
}
#[test]
fn test_std_check_timer_creator() {
let std_check_timer_creator = StdTimerCreator::new(Duration::from_secs(1));
let check_timer = std_check_timer_creator.create_countdown(TimerContext::NakActivity {
expiry_time: Duration::from_secs(1),
});
assert_eq!(check_timer.expiry_time_seconds(), 1);
}
#[test]
fn test_remote_cfg_provider_single() {
let mut remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
REMOTE_ID.into(),
1024,
true,
false,
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
assert_eq!(remote_entity_retrieved.max_packet_len, 1024);
assert!(remote_entity_retrieved.closure_requested_by_default);
assert!(!remote_entity_retrieved.crc_on_transmission_by_default);
assert_eq!(
remote_entity_retrieved.default_crc_type,
ChecksumType::Crc32
);
let remote_entity_mut = remote_entity_cfg.get_mut(REMOTE_ID.value()).unwrap();
assert_eq!(remote_entity_mut.entity_id, REMOTE_ID.into());
let dummy = RemoteEntityConfig::new_with_default_values(
LOCAL_ID.into(),
1024,
true,
false,
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
assert!(!remote_entity_cfg.add_config(&dummy));
assert!(!remote_entity_cfg.remove_config(REMOTE_ID.value()));
let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
assert!(remote_entity_cfg.get(LOCAL_ID.value()).is_none());
assert!(remote_entity_cfg.get_mut(LOCAL_ID.value()).is_none());
}
#[test]
fn test_remote_cfg_provider_std() {
let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
REMOTE_ID.into(),
1024,
true,
false,
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let mut remote_cfg_provider = StdRemoteEntityConfigProvider::default();
assert!(remote_cfg_provider.0.is_empty());
remote_cfg_provider.add_config(&remote_entity_cfg);
assert_eq!(remote_cfg_provider.0.len(), 1);
let remote_entity_cfg_2 = RemoteEntityConfig::new_with_default_values(
LOCAL_ID.into(),
1024,
true,
false,
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
remote_cfg_provider.add_config(&remote_entity_cfg_2);
assert_eq!(remote_cfg_provider.0.len(), 2);
let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert_eq!(remote_cfg_provider.0.len(), 1);
let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
}
#[test]
fn test_remote_cfg_provider_vector() {
let mut remote_cfg_provider = VecRemoteEntityConfigProvider::default();
let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
REMOTE_ID.into(),
1024,
true,
false,
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
assert!(remote_cfg_provider.0.is_empty());
remote_cfg_provider.add_config(&remote_entity_cfg);
assert_eq!(remote_cfg_provider.0.len(), 1);
let remote_entity_cfg_2 = RemoteEntityConfig::new_with_default_values(
LOCAL_ID.into(),
1024,
true,
false,
TransmissionMode::Unacknowledged,
ChecksumType::Crc32,
);
let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
remote_cfg_provider.add_config(&remote_entity_cfg_2);
assert_eq!(remote_cfg_provider.0.len(), 2);
let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert_eq!(remote_cfg_provider.0.len(), 1);
let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
}
#[test]
fn dummy_fault_hook_test() {
let mut user_hook_dummy = DummyFaultHook::default();
let transaction_id = TransactionId::new(
UnsignedByteFieldU8::new(0).into(),
UnsignedByteFieldU8::new(0).into(),
);
user_hook_dummy.notice_of_cancellation_cb(transaction_id, ConditionCode::NoError, 0);
user_hook_dummy.notice_of_suspension_cb(transaction_id, ConditionCode::NoError, 0);
user_hook_dummy.abandoned_cb(transaction_id, ConditionCode::NoError, 0);
user_hook_dummy.ignore_cb(transaction_id, ConditionCode::NoError, 0);
}
#[test]
fn dummy_pdu_provider_test() {
let dummy_pdu_provider = DummyPduProvider(());
assert_eq!(dummy_pdu_provider.pdu_type(), PduType::FileData);
assert!(dummy_pdu_provider.file_directive_type().is_none());
assert_eq!(dummy_pdu_provider.pdu(), &[]);
assert_eq!(
dummy_pdu_provider.packet_target(),
Ok(PacketTarget::SourceEntity)
);
}
#[test]
fn test_fault_handler_checksum_error_ignored_by_default() {
let fault_handler = FaultHandler::new(TestFaultHandler::default());
assert_eq!(
fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
FaultHandlerCode::IgnoreError
);
}
#[test]
fn test_fault_handler_unsupported_checksum_ignored_by_default() {
let fault_handler = FaultHandler::new(TestFaultHandler::default());
assert_eq!(
fault_handler.get_fault_handler(ConditionCode::UnsupportedChecksumType),
FaultHandlerCode::IgnoreError
);
}
#[test]
fn test_fault_handler_basic() {
let mut fault_handler = FaultHandler::new(TestFaultHandler::default());
assert_eq!(
fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
FaultHandlerCode::IgnoreError
);
fault_handler.set_fault_handler(
ConditionCode::FileChecksumFailure,
FaultHandlerCode::NoticeOfCancellation,
);
assert_eq!(
fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
FaultHandlerCode::NoticeOfCancellation
);
}
#[test]
fn transaction_id_hashable_usable_as_map_key() {
let mut map = HashMap::new();
let transaction_id_0 = TransactionId::new(
UnsignedByteFieldU8::new(1).into(),
UnsignedByteFieldU8::new(2).into(),
);
map.insert(transaction_id_0, 5_u32);
}
}