cfdp/
lib.rs

1//! # `cfdp` - High-level support for implementing CFDP
2//!
3//! This is a high-level support library for implementing the CCSDS File Delivery Protocol
4//! according to the CCSDS 727.0-B-5 standard.
5//!
6//! # Features
7//!
8//! `cfdp-rs` currently supports following high-level features:
9//!
10//! - Unacknowledged (class 1) file transfers for both source and destination side.
11//! - Acknowledged (class 2) file transfers for both source and destination side.
12//!
13//! The following features have not been implemented yet. PRs or notifications for demand are welcome!
14//!
15//! - Suspending transfers
16//! - Inactivity handling
17//! - Start and end of transmission and reception opportunity handling
18//! - Keep Alive and Prompt PDU handling
19//!
20//! # Overview
21//!
22//! The basic idea of CFDP is to convert files of any size into a stream of packets called packet
23//! data units (PDU). CFPD has an unacknowledged and acknowledged mode, with the option to request
24//! a transaction closure for the unacknowledged mode. Using the unacknowledged mode with no
25//! transaction closure is applicable for simplex communication paths, while the unacknowledged
26//! mode with closure is the easiest way to get a confirmation of a successful file transfer,
27//! including a CRC check on the remote side to verify file integrity. The acknowledged mode is
28//! the most complex mode which includes multiple mechanism to ensure succesfull packet transaction
29//! even for unreliable connections, including lost segment detection. As such, it can be compared
30//! to a specialized TCP for file transfers with remote systems.
31//!
32//! The goal of this library is to be flexible enough to support the use-cases of both on-board
33//! software and of ground software. It has support to make integration on [std] systems as simple
34//! as possible, but also has sufficient abstraction to allow for integration on `no_std`
35//! environments and can be used on these systems as well.
36//!
37//! The core handlers inside this library do not allocate memory dynamically. The internal buffer
38//! size used for PDU generation and checksum calculation is statically determined via a Rust
39//! feature and defaults to 2048 bytes.
40//!
41//! The core of this library are the [crate::dest::DestinationHandler] and the
42//! [crate::source::SourceHandler] components which model the CFDP destination and source entity
43//! respectively. You can find high-level and API documentation for both handlers in the respective
44//! [crate::dest] and [crate::source] module.
45//!
46//! # Rust Features
47//!
48//! ## Default features
49//!
50//!  - [`std`](https://doc.rust-lang.org/std/): Enables functionality relying on the standard library.
51//!  - [`alloc`](https://doc.rust-lang.org/alloc/): Enables features which require allocation support.
52//!    Enabled by the `std` feature.
53//!
54//! ## Optional Features
55//!
56//!  - [`serde`](https://serde.rs/): Adds `serde` support for most types by adding `Serialize` and `Deserialize` `derive`s
57//!  - [`defmt`](https://defmt.ferrous-systems.com/): Add support for the `defmt` by adding the
58//!    [`defmt::Format`](https://defmt.ferrous-systems.com/format) derive on many types.
59//!
60//! ## Buffer size selection
61//!
62//! The following features can be used to select the internal buffer size used for PDU generation
63//! and checksum calculation. Selection of this value possibly limits the size of the generated PDU
64//! packets. Only one feature may be enabled.
65//!
66//! - `packet-buf-256` for 256 bytes
67//! - `packet-buf-512` for 512 bytes
68//! - `packet-buf-1k` for 1024 bytes
69//! - `packet-buf-2k` for 2048 bytes. This is the default.
70//! - `packet-buf-4k` for 4096 bytes
71//!
72//! # Examples
73//!
74//! This library currently features two example application which showcase how the provided
75//! components could be used to provide CFDP services.
76//!
77//! The [end-to-end test](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/tests/end-to-end.rs)
78//! is an integration tests which spawns a CFDP source entity and a CFDP destination entity,
79//! moves them to separate threads and then performs a small file copy operation.
80//! You can run the integration test for a transfer with no closure and with printout to the
81//! standard console by running:
82//!
83//! ```sh
84//! cargo test end_to_end_test_no_closure -- --nocapture
85//! ```
86//!
87//! or with closure:
88//!
89//! ```sh
90//! cargo test end_to_end_test_with_closure -- --nocapture
91//! ```
92//!
93//! The [Python Interoperability](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/examples/python-interop)
94//! example showcases the interoperability of the CFDP handlers written in Rust with a Python
95//! implementation. The dedicated example documentation shows how to run this example.
96//!
97//! # Notes on the user hooks and scheduling
98//!
99//! Both examples feature implementations of the [UserFaultHook] and the [user::CfdpUser]
100//! trait which simply print some information to the console to monitor the progress of a file
101//! copy operation. These implementations could be adapted for other handler integrations. For
102//! example, they could signal a GUI application to display some information for the user.
103//!
104//! Even though both examples move the newly spawned handlers to dedicated threads, this is not
105//! the only way they could be scheduled. For example, to support an arbitrary (or bounded)
106//! amount of file copy operations on either source or destination side, those handlers could be
107//! moved into a [std::collections::HashMap] structure which is then scheduled inside a thread, or
108//! you could schedule a fixed amount of handlers inside a
109//! [threadpool](https://docs.rs/threadpool/latest/threadpool/).
110#![no_std]
111// Show which crate feature enables conditionally compiled APIs in documentation.
112#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
113#[cfg(feature = "alloc")]
114extern crate alloc;
115#[cfg(any(feature = "std", test))]
116extern crate std;
117
118pub mod buf_len;
119pub mod dest;
120pub mod filestore;
121pub mod lost_segments;
122pub mod request;
123pub mod source;
124pub mod time;
125pub mod user;
126
127use crate::time::Countdown;
128use core::{cell::RefCell, fmt::Debug, hash::Hash};
129use crc::{CRC_32_ISCSI, CRC_32_ISO_HDLC, Crc};
130
131#[cfg(feature = "alloc")]
132pub use alloc_mod::*;
133use core::time::Duration;
134#[cfg(feature = "serde")]
135use serde::{Deserialize, Serialize};
136use spacepackets::{
137    cfdp::{
138        ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
139        pdu::{FileDirectiveType, PduError, PduHeader},
140    },
141    util::{UnsignedByteField, UnsignedEnum},
142};
143#[cfg(feature = "std")]
144pub use std_mod::*;
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147#[cfg_attr(feature = "defmt", derive(defmt::Format))]
148#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
149pub enum EntityType {
150    Sending,
151    Receiving,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq)]
155#[cfg_attr(feature = "defmt", derive(defmt::Format))]
156#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
157pub enum TimerContext {
158    CheckLimit {
159        local_id: UnsignedByteField,
160        remote_id: UnsignedByteField,
161        entity_type: EntityType,
162    },
163    NakActivity {
164        expiry_time: Duration,
165    },
166    PositiveAck {
167        expiry_time: Duration,
168    },
169}
170
171/// A generic trait which allows CFDP entities to create check timers which are required to
172/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
173/// and 4.6.3.3.
174///
175/// This trait also allows the creation of different check timers depending on context and purpose
176/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
177/// other factors.
178///
179/// The countdown timer is used by 3 mechanisms of the CFDP protocol.
180///
181/// ## 1. Check limit handling
182///
183/// The first mechanism is the check limit handling for unacknowledged transfers as specified
184/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard.
185/// For this mechanism, the timer has different functionality depending on whether
186/// the using entity is the sending entity or the receiving entity for the unacknowledged
187/// transmission mode.
188///
189/// For the sending entity, this timer determines the expiry period for declaring a check limit
190/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer.
191/// Also see 4.6.3.2 of the CFDP standard.
192///
193/// For the receiving entity, this timer determines the expiry period for incrementing a check
194/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
195/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
196///
197/// ## 2. NAK activity limit
198///
199/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP
200/// standard. The expiration period will be provided by the NAK timer expiration limit of the
201/// remote entity configuration.
202///
203/// ## 3. Positive ACK procedures
204///
205/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
206/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
207/// interval of the remote entity configuration.
208pub trait TimerCreator {
209    type Countdown: Countdown;
210
211    fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown;
212}
213
214/// This structure models the remote entity configuration information as specified in chapter 8.3
215/// of the CFDP standard.
216///
217/// Some of the fields which were not considered necessary for the Rust implementation
218/// were omitted. Some other fields which are not contained inside the standard but are considered
219/// necessary for the Rust implementation are included.
220///
221/// ## Notes on Positive Acknowledgment Procedures
222///
223/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will
224/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending
225/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU).
226/// Once the expected ACK response has not been received for that interval, as counter will be
227/// incremented and the timer will be reset. Once the counter exceeds the
228/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared.
229///
230/// ## Notes on Deferred Lost Segment Procedures
231///
232/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After
233/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is
234/// reset when missing segments or missing metadata is received. The timer will be deactivated if
235/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a
236/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared.
237///
238/// ## Fields
239///
240/// * `entity_id` - The ID of the remote entity.
241/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition
242///   to the `max_file_segment_len` attribute which also determines the size of file data PDUs.
243/// * `max_file_segment_len` The maximum file segment length which determines the maximum size
244///   of file data PDUs in addition to the `max_packet_len` attribute. If this field is set
245///   to None, the maximum file segment length will be derived from the maximum packet length.
246///   If this has some value which is smaller than the segment value derived from
247///   `max_packet_len`, this value will be picked.
248/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of
249///   the Put Request, it will be determined from this field in the remote configuration.
250/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put
251///   Request, it will be determined from this field in the remote configuration.
252/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the
253///   Put Request, it will be determined from this field in the remote configuration.
254/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on
255///   transaction cancellation. Defaults to False.
256/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to
257///   this remote entity.
258/// * `check_limit` - This timer determines the expiry period for incrementing a check counter
259///   after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
260///   reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to
261///   2, so the check limit timer may expire twice.
262/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment
263///   Procedures inside the class documentation. Expected as floating point seconds. Defaults to
264///   10 seconds.
265/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment
266///   Procedures inside the class documentation. Defaults to 2, so the timer may expire twice.
267/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a
268///   file data gap or lost metadata is detected in the acknowledged mode. Defaults to True.
269/// * `nak_timer_interval_seconds` -  See the notes on the Deferred Lost Segment Procedure inside
270///   the class documentation. Expected as floating point seconds. Defaults to 10 seconds.
271/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
272///   the class documentation. Defaults to 2, so the timer may expire two times.
273#[derive(Debug, Copy, Clone)]
274#[cfg_attr(feature = "defmt", derive(defmt::Format))]
275#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
276pub struct RemoteEntityConfig {
277    pub entity_id: UnsignedByteField,
278    pub max_packet_len: usize,
279    pub max_file_segment_len: Option<usize>,
280    pub closure_requested_by_default: bool,
281    pub crc_on_transmission_by_default: bool,
282    pub default_transmission_mode: TransmissionMode,
283    pub default_crc_type: ChecksumType,
284    pub positive_ack_timer_interval: Duration,
285    pub positive_ack_timer_expiration_limit: u32,
286    pub check_limit: u32,
287    pub disposition_on_cancellation: bool,
288    pub immediate_nak_mode: bool,
289    pub nak_timer_interval: Duration,
290    pub nak_timer_expiration_limit: u32,
291}
292
293impl RemoteEntityConfig {
294    pub fn new_with_default_values(
295        entity_id: UnsignedByteField,
296        max_packet_len: usize,
297        closure_requested_by_default: bool,
298        crc_on_transmission_by_default: bool,
299        default_transmission_mode: TransmissionMode,
300        default_crc_type: ChecksumType,
301    ) -> Self {
302        Self {
303            entity_id,
304            max_file_segment_len: None,
305            max_packet_len,
306            closure_requested_by_default,
307            crc_on_transmission_by_default,
308            default_transmission_mode,
309            default_crc_type,
310            check_limit: 2,
311            positive_ack_timer_interval: Duration::from_secs(10),
312            positive_ack_timer_expiration_limit: 2,
313            disposition_on_cancellation: false,
314            immediate_nak_mode: true,
315            nak_timer_interval: Duration::from_secs(10),
316            nak_timer_expiration_limit: 2,
317        }
318    }
319}
320
321#[derive(Debug, PartialEq, Eq, thiserror::Error)]
322#[cfg_attr(feature = "defmt", derive(defmt::Format))]
323#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
324pub enum RemoteConfigStoreError {
325    #[error("store is full")]
326    Full,
327}
328
329pub trait RemoteConfigStore {
330    /// Retrieve the remote entity configuration for the given remote ID.
331    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig>;
332
333    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>;
334
335    /// Add a new remote configuration. Return [true] if the configuration was
336    /// inserted successfully, and [false] if a configuration already exists.
337    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> Result<bool, RemoteConfigStoreError>;
338}
339
340/// This is a thin wrapper around a [hashbrown::HashMap] to store remote entity configurations.
341/// It implements the full [RemoteEntityConfig] trait.
342#[cfg(feature = "alloc")]
343#[derive(Default, Debug)]
344#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
345pub struct RemoteConfigStoreStd(pub hashbrown::HashMap<u64, RemoteEntityConfig>);
346
347#[cfg(feature = "std")]
348impl RemoteConfigStore for RemoteConfigStoreStd {
349    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
350        self.0.get(&remote_id)
351    }
352
353    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
354        self.0.get_mut(&remote_id)
355    }
356
357    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> Result<bool, RemoteConfigStoreError> {
358        Ok(self.0.insert(cfg.entity_id.value(), *cfg).is_some())
359    }
360}
361
362#[cfg(feature = "std")]
363impl RemoteConfigStoreStd {
364    pub fn remove_config(&mut self, remote_id: u64) -> bool {
365        self.0.remove(&remote_id).is_some()
366    }
367}
368
369/// This is a thin wrapper around a [alloc::vec::Vec] to store remote entity configurations.
370/// It implements the full [RemoteEntityConfig] trait.
371#[cfg(feature = "alloc")]
372#[derive(Default, Debug)]
373#[cfg_attr(feature = "defmt", derive(defmt::Format))]
374#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
375pub struct RemoteConfigList(pub alloc::vec::Vec<RemoteEntityConfig>);
376
377#[cfg(feature = "alloc")]
378impl RemoteConfigStore for RemoteConfigList {
379    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
380        self.0
381            .iter()
382            .find(|&cfg| cfg.entity_id.value() == remote_id)
383    }
384
385    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
386        self.0
387            .iter_mut()
388            .find(|cfg| cfg.entity_id.value() == remote_id)
389    }
390
391    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> Result<bool, RemoteConfigStoreError> {
392        for other_cfg in self.0.iter() {
393            if cfg.entity_id.value() == other_cfg.entity_id.value() {
394                return Ok(false);
395            }
396        }
397        self.0.push(*cfg);
398        Ok(true)
399    }
400}
401
402#[cfg(feature = "alloc")]
403impl RemoteConfigList {
404    pub fn remove_config(&mut self, remote_id: u64) -> bool {
405        for (idx, cfg) in self.0.iter().enumerate() {
406            if cfg.entity_id.value() == remote_id {
407                self.0.remove(idx);
408                return true;
409            }
410        }
411        false
412    }
413}
414
415/// This is a thin wrapper around a [heapless::vec::Vec] to store remote entity configurations.
416/// It implements the full [RemoteEntityConfig] trait.
417#[derive(Default, Debug)]
418#[cfg_attr(feature = "defmt", derive(defmt::Format))]
419#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
420pub struct RemoteConfigListHeapless<const N: usize>(pub heapless::vec::Vec<RemoteEntityConfig, N>);
421
422impl<const N: usize> RemoteConfigStore for RemoteConfigListHeapless<N> {
423    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
424        self.0
425            .iter()
426            .find(|&cfg| cfg.entity_id.value() == remote_id)
427    }
428
429    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
430        self.0
431            .iter_mut()
432            .find(|cfg| cfg.entity_id.value() == remote_id)
433    }
434
435    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> Result<bool, RemoteConfigStoreError> {
436        if self.0.is_full() {
437            return Err(RemoteConfigStoreError::Full);
438        }
439        for other_cfg in self.0.iter() {
440            if cfg.entity_id.value() == other_cfg.entity_id.value() {
441                return Ok(false);
442            }
443        }
444        self.0.push(*cfg).unwrap();
445        Ok(true)
446    }
447}
448
449impl<const N: usize> RemoteConfigListHeapless<N> {
450    pub fn remove_config(&mut self, remote_id: u64) -> bool {
451        for (idx, cfg) in self.0.iter().enumerate() {
452            if cfg.entity_id.value() == remote_id {
453                self.0.remove(idx);
454                return true;
455            }
456        }
457        false
458    }
459}
460
461/// A remote entity configurations also implements the [RemoteConfigStore], but the
462/// [RemoteConfigStore::add_config] always returns [RemoteConfigStoreError::Full].
463impl RemoteConfigStore for RemoteEntityConfig {
464    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
465        if remote_id == self.entity_id.value() {
466            return Some(self);
467        }
468        None
469    }
470
471    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
472        if remote_id == self.entity_id.value() {
473            return Some(self);
474        }
475        None
476    }
477
478    fn add_config(&mut self, _cfg: &RemoteEntityConfig) -> Result<bool, RemoteConfigStoreError> {
479        Err(RemoteConfigStoreError::Full)
480    }
481}
482
483/// This trait introduces some callbacks which will be called when a particular CFDP fault
484/// handler is called.
485///
486/// It is passed into the CFDP handlers as part of the [UserFaultHook] and the local entity
487/// configuration and provides a way to specify custom user error handlers. This allows to
488/// implement some CFDP features like fault handler logging, which would not be possible
489/// generically otherwise.
490///
491/// For each error reported by the [FaultHandler], the appropriate fault handler callback
492/// will be called depending on the [FaultHandlerCode].
493pub trait UserFaultHook {
494    fn notice_of_suspension_cb(&mut self, fault_info: FaultInfo);
495
496    fn notice_of_cancellation_cb(&mut self, fault_info: FaultInfo);
497
498    fn abandoned_cb(&mut self, fault_info: FaultInfo);
499
500    fn ignore_cb(&mut self, fault_info: FaultInfo);
501}
502
503/// Dummy fault hook which implements [UserFaultHook] but only provides empty
504/// implementations.
505#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
506pub struct DummyFaultHook {}
507
508impl UserFaultHook for DummyFaultHook {
509    fn notice_of_suspension_cb(&mut self, _fault_info: FaultInfo) {}
510
511    fn notice_of_cancellation_cb(&mut self, _fault_info: FaultInfo) {}
512
513    fn abandoned_cb(&mut self, _fault_info: FaultInfo) {}
514
515    fn ignore_cb(&mut self, _fault_info: FaultInfo) {}
516}
517
518/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP
519/// standard.
520///
521/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
522/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
523/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
524/// user-provided callback function provided by the [UserFaultHook].
525///
526/// Some note on the provided default settings:
527///
528/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers,
529///   cancelling the transfer immediately would interfere with the check limit mechanism specified
530///   in chapter 4.6.3.3.
531/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is
532///   not supported the file transfer might still have worked properly.
533///
534/// For all other faults, the default fault handling operation will be to cancel the transaction.
535/// These defaults can be overriden by using the [Self::set_fault_handler] method.
536/// Please note that in any case, fault handler overrides can be specified by the sending CFDP
537/// entity.
538pub struct FaultHandler<UserHandler: UserFaultHook> {
539    handler_array: [FaultHandlerCode; 10],
540    // Could also change the user fault handler trait to have non mutable methods, but that limits
541    // flexbility on the user side..
542    pub user_hook: RefCell<UserHandler>,
543}
544
545#[derive(Debug, Copy, Clone, PartialEq, Eq)]
546#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
547pub struct FaultInfo {
548    transaction_id: TransactionId,
549    condition_code: ConditionCode,
550    progress: u64,
551}
552
553impl FaultInfo {
554    pub const fn new(
555        transaction_id: TransactionId,
556        condition_code: ConditionCode,
557        progress: u64,
558    ) -> Self {
559        Self {
560            transaction_id,
561            condition_code,
562            progress,
563        }
564    }
565
566    #[inline]
567    pub const fn transaction_id(&self) -> TransactionId {
568        self.transaction_id
569    }
570
571    #[inline]
572    pub const fn condition_code(&self) -> ConditionCode {
573        self.condition_code
574    }
575
576    #[inline]
577    pub const fn progress(&self) -> u64 {
578        self.progress
579    }
580}
581
582impl<UserHandler: UserFaultHook> FaultHandler<UserHandler> {
583    fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
584        Some(match conditon_code {
585            ConditionCode::PositiveAckLimitReached => 0,
586            ConditionCode::KeepAliveLimitReached => 1,
587            ConditionCode::InvalidTransmissionMode => 2,
588            ConditionCode::FilestoreRejection => 3,
589            ConditionCode::FileChecksumFailure => 4,
590            ConditionCode::FileSizeError => 5,
591            ConditionCode::NakLimitReached => 6,
592            ConditionCode::InactivityDetected => 7,
593            ConditionCode::CheckLimitReached => 8,
594            ConditionCode::UnsupportedChecksumType => 9,
595            _ => return None,
596        })
597    }
598
599    pub fn set_fault_handler(
600        &mut self,
601        condition_code: ConditionCode,
602        fault_handler: FaultHandlerCode,
603    ) {
604        let array_idx = Self::condition_code_to_array_index(condition_code);
605        if array_idx.is_none() {
606            return;
607        }
608        self.handler_array[array_idx.unwrap()] = fault_handler;
609    }
610
611    pub fn new(user_fault_handler: UserHandler) -> Self {
612        let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
613        init_array
614            [Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
615            FaultHandlerCode::IgnoreError;
616        init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType)
617            .unwrap()] = FaultHandlerCode::IgnoreError;
618        Self {
619            handler_array: init_array,
620            user_hook: RefCell::new(user_fault_handler),
621        }
622    }
623
624    pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode {
625        let array_idx = Self::condition_code_to_array_index(condition_code);
626        if array_idx.is_none() {
627            return FaultHandlerCode::IgnoreError;
628        }
629        self.handler_array[array_idx.unwrap()]
630    }
631
632    pub fn report_fault(&self, code: FaultHandlerCode, fault_info: FaultInfo) -> FaultHandlerCode {
633        let mut handler_mut = self.user_hook.borrow_mut();
634        match code {
635            FaultHandlerCode::NoticeOfCancellation => {
636                handler_mut.notice_of_cancellation_cb(fault_info);
637            }
638            FaultHandlerCode::NoticeOfSuspension => {
639                handler_mut.notice_of_suspension_cb(fault_info);
640            }
641            FaultHandlerCode::IgnoreError => {
642                handler_mut.ignore_cb(fault_info);
643            }
644            FaultHandlerCode::AbandonTransaction => {
645                handler_mut.abandoned_cb(fault_info);
646            }
647        }
648        code
649    }
650}
651
652#[derive(Debug, Clone, Copy, PartialEq, Eq)]
653#[cfg_attr(feature = "defmt", derive(defmt::Format))]
654#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
655pub struct IndicationConfig {
656    pub eof_sent: bool,
657    pub eof_recv: bool,
658    pub file_segment_recv: bool,
659    pub transaction_finished: bool,
660    pub suspended: bool,
661    pub resumed: bool,
662}
663
664impl Default for IndicationConfig {
665    fn default() -> Self {
666        Self {
667            eof_sent: true,
668            eof_recv: true,
669            file_segment_recv: true,
670            transaction_finished: true,
671            suspended: true,
672            resumed: true,
673        }
674    }
675}
676
677/// Each CFDP entity handler has a [LocalEntityConfig]uration.
678pub struct LocalEntityConfig<UserFaultHookInstance: UserFaultHook> {
679    pub id: UnsignedByteField,
680    pub indication_cfg: IndicationConfig,
681    pub fault_handler: FaultHandler<UserFaultHookInstance>,
682}
683
684impl<UserFaultHookInstance: UserFaultHook> LocalEntityConfig<UserFaultHookInstance> {
685    pub fn new(
686        id: UnsignedByteField,
687        indication_cfg: IndicationConfig,
688        hook: UserFaultHookInstance,
689    ) -> Self {
690        Self {
691            id,
692            indication_cfg,
693            fault_handler: FaultHandler::new(hook),
694        }
695    }
696}
697
698impl<UserFaultHookInstance: UserFaultHook> LocalEntityConfig<UserFaultHookInstance> {
699    pub fn user_fault_hook_mut(&mut self) -> &mut RefCell<UserFaultHookInstance> {
700        &mut self.fault_handler.user_hook
701    }
702
703    pub fn user_fault_hook(&self) -> &RefCell<UserFaultHookInstance> {
704        &self.fault_handler.user_hook
705    }
706}
707
708/// Generic error type for sending a PDU via a message queue.
709#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
710#[non_exhaustive]
711pub enum GenericSendError {
712    #[error("RX disconnected")]
713    RxDisconnected,
714    #[error("queue is full, fill count {0:?}")]
715    QueueFull(Option<u32>),
716    #[error("other send error")]
717    Other,
718}
719
720pub trait PduSender {
721    fn send_pdu(
722        &self,
723        pdu_type: PduType,
724        file_directive_type: Option<FileDirectiveType>,
725        raw_pdu: &[u8],
726    ) -> Result<(), GenericSendError>;
727
728    fn send_file_directive_pdu(
729        &self,
730        file_directive_type: FileDirectiveType,
731        raw_pdu: &[u8],
732    ) -> Result<(), GenericSendError> {
733        self.send_pdu(PduType::FileDirective, Some(file_directive_type), raw_pdu)
734    }
735}
736
737#[cfg(feature = "std")]
738pub mod std_mod {
739    use std::sync::mpsc;
740
741    use super::*;
742
743    impl PduSender for mpsc::Sender<PduOwnedWithInfo> {
744        fn send_pdu(
745            &self,
746            pdu_type: PduType,
747            file_directive_type: Option<FileDirectiveType>,
748            raw_pdu: &[u8],
749        ) -> Result<(), GenericSendError> {
750            self.send(PduOwnedWithInfo::new(
751                pdu_type,
752                file_directive_type,
753                raw_pdu.to_vec(),
754            ))
755            .map_err(|_| GenericSendError::RxDisconnected)?;
756            Ok(())
757        }
758    }
759
760    /// Simple implementation of the [Countdown] trait assuming a standard runtime.
761    #[derive(Debug)]
762    pub struct StdCountdown {
763        expiry_time: Duration,
764        start_time: std::time::Instant,
765    }
766
767    impl StdCountdown {
768        pub fn new(expiry_time: Duration) -> Self {
769            Self {
770                expiry_time,
771                start_time: std::time::Instant::now(),
772            }
773        }
774
775        pub fn expiry_time_seconds(&self) -> u64 {
776            self.expiry_time.as_secs()
777        }
778    }
779
780    impl Countdown for StdCountdown {
781        fn has_expired(&self) -> bool {
782            if self.start_time.elapsed() > self.expiry_time {
783                return true;
784            }
785            false
786        }
787
788        fn reset(&mut self) {
789            self.start_time = std::time::Instant::now();
790        }
791    }
792
793    pub struct StdTimerCreator {
794        pub check_limit_timeout: Duration,
795    }
796
797    impl StdTimerCreator {
798        pub const fn new(check_limit_timeout: Duration) -> Self {
799            Self {
800                check_limit_timeout,
801            }
802        }
803    }
804
805    impl Default for StdTimerCreator {
806        fn default() -> Self {
807            Self::new(Duration::from_secs(5))
808        }
809    }
810
811    impl TimerCreator for StdTimerCreator {
812        type Countdown = StdCountdown;
813
814        fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown {
815            match timer_context {
816                TimerContext::CheckLimit {
817                    local_id: _,
818                    remote_id: _,
819                    entity_type: _,
820                } => StdCountdown::new(self.check_limit_timeout),
821                TimerContext::NakActivity { expiry_time } => StdCountdown::new(expiry_time),
822                TimerContext::PositiveAck { expiry_time } => StdCountdown::new(expiry_time),
823            }
824        }
825    }
826}
827
828/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence
829/// number of that transfer which is also determined by the CFDP source entity.
830#[derive(Debug, Eq, Copy, Clone)]
831#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
832#[cfg_attr(feature = "defmt", derive(defmt::Format))]
833pub struct TransactionId {
834    source_id: UnsignedByteField,
835    seq_num: UnsignedByteField,
836}
837
838impl TransactionId {
839    pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
840        Self { source_id, seq_num }
841    }
842
843    pub fn source_id(&self) -> &UnsignedByteField {
844        &self.source_id
845    }
846
847    pub fn seq_num(&self) -> &UnsignedByteField {
848        &self.seq_num
849    }
850}
851
852impl Hash for TransactionId {
853    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
854        self.source_id.value().hash(state);
855        self.seq_num.value().hash(state);
856    }
857}
858
859impl PartialEq for TransactionId {
860    fn eq(&self, other: &Self) -> bool {
861        self.source_id.value() == other.source_id.value()
862            && self.seq_num.value() == other.seq_num.value()
863    }
864}
865
866#[derive(Debug, Copy, Clone, PartialEq, Eq)]
867#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
868pub enum State {
869    Idle = 0,
870    Busy = 1,
871    Suspended = 2,
872}
873
874/// [crc::Crc] instance using [crc::CRC_32_ISO_HDLC].
875///
876/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/4>,
877/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32>
878pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
879/// [crc::Crc] instance using [crc::CRC_32_ISCSI].
880///
881/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/3>,
882/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi>
883pub const CRC_32C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
884
885#[derive(Debug, PartialEq, Eq, Copy, Clone)]
886#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
887pub enum PacketTarget {
888    SourceEntity,
889    DestEntity,
890}
891
892/// Generic trait which models a raw CFDP packet data unit (PDU) block with some additional context
893/// information.
894pub trait PduProvider {
895    fn pdu_type(&self) -> PduType;
896    fn file_directive_type(&self) -> Option<FileDirectiveType>;
897    fn raw_pdu(&self) -> &[u8];
898    fn packet_target(&self) -> Result<PacketTarget, PduError>;
899}
900
901pub struct DummyPduProvider(());
902
903impl PduProvider for DummyPduProvider {
904    fn pdu_type(&self) -> PduType {
905        PduType::FileData
906    }
907
908    fn file_directive_type(&self) -> Option<FileDirectiveType> {
909        None
910    }
911
912    fn raw_pdu(&self) -> &[u8] {
913        &[]
914    }
915
916    fn packet_target(&self) -> Result<PacketTarget, PduError> {
917        Ok(PacketTarget::SourceEntity)
918    }
919}
920
921/// This is a helper struct which contains base information about a particular PDU packet.
922/// This is also necessary information for CFDP packet routing. For example, some packet types
923/// like file data PDUs can only be used by CFDP source entities.
924pub struct PduRawWithInfo<'raw_packet> {
925    pdu_type: PduType,
926    file_directive_type: Option<FileDirectiveType>,
927    packet_len: usize,
928    raw_packet: &'raw_packet [u8],
929}
930
931pub fn determine_packet_target(raw_pdu: &[u8]) -> Result<PacketTarget, PduError> {
932    let (header, header_len) = PduHeader::from_bytes(raw_pdu)?;
933    if header.pdu_type() == PduType::FileData {
934        return Ok(PacketTarget::DestEntity);
935    }
936    let file_directive_type = FileDirectiveType::try_from(raw_pdu[header_len]).map_err(|_| {
937        PduError::InvalidDirectiveType {
938            found: raw_pdu[header_len],
939            expected: None,
940        }
941    })?;
942    let packet_target = match file_directive_type {
943        // Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
944        // the source handler
945        FileDirectiveType::NakPdu
946        | FileDirectiveType::FinishedPdu
947        | FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
948        // Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
949        // the destination handler
950        FileDirectiveType::MetadataPdu
951        | FileDirectiveType::EofPdu
952        | FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
953        // Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
954        // extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
955        // the source handler, for a Finished PDU, it is passed to the destination handler.
956        FileDirectiveType::AckPdu => {
957            let acked_directive = FileDirectiveType::try_from(raw_pdu[header_len + 1] >> 4)
958                .map_err(|_| PduError::InvalidDirectiveType {
959                    found: (raw_pdu[header_len + 1] >> 4),
960                    expected: None,
961                })?;
962            if acked_directive == FileDirectiveType::EofPdu {
963                PacketTarget::SourceEntity
964            } else if acked_directive == FileDirectiveType::FinishedPdu {
965                PacketTarget::DestEntity
966            } else {
967                // TODO: Maybe a better error? This might be confusing..
968                return Err(PduError::InvalidDirectiveType {
969                    found: raw_pdu[header_len + 1],
970                    expected: None,
971                });
972            }
973        }
974    };
975    Ok(packet_target)
976}
977
978impl<'raw> PduRawWithInfo<'raw> {
979    pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
980        let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
981        if pdu_header.pdu_type() == PduType::FileData {
982            return Ok(Self {
983                pdu_type: pdu_header.pdu_type(),
984                file_directive_type: None,
985                packet_len: pdu_header.pdu_len(),
986                raw_packet,
987            });
988        }
989        if pdu_header.pdu_datafield_len() < 1 {
990            return Err(PduError::Format);
991        }
992        // Route depending on PDU type and directive type if applicable. Retrieve directive type
993        // from the raw stream for better performance (with sanity and directive code check).
994        // The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding
995        // procedure.
996        let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
997            PduError::InvalidDirectiveType {
998                found: raw_packet[header_len],
999                expected: None,
1000            }
1001        })?;
1002        Ok(Self {
1003            pdu_type: pdu_header.pdu_type(),
1004            file_directive_type: Some(directive),
1005            packet_len: pdu_header.pdu_len(),
1006            raw_packet,
1007        })
1008    }
1009
1010    pub fn raw_packet(&self) -> &[u8] {
1011        &self.raw_packet[0..self.packet_len]
1012    }
1013}
1014
1015impl PduProvider for PduRawWithInfo<'_> {
1016    fn pdu_type(&self) -> PduType {
1017        self.pdu_type
1018    }
1019
1020    fn file_directive_type(&self) -> Option<FileDirectiveType> {
1021        self.file_directive_type
1022    }
1023
1024    fn raw_pdu(&self) -> &[u8] {
1025        self.raw_packet
1026    }
1027
1028    fn packet_target(&self) -> Result<PacketTarget, PduError> {
1029        determine_packet_target(self.raw_packet)
1030    }
1031}
1032
1033#[cfg(feature = "alloc")]
1034pub mod alloc_mod {
1035    use spacepackets::cfdp::{
1036        PduType,
1037        pdu::{FileDirectiveType, PduError},
1038    };
1039
1040    use crate::{PacketTarget, PduProvider, PduRawWithInfo, determine_packet_target};
1041
1042    #[derive(Debug, PartialEq, Eq, Clone)]
1043    pub struct PduOwnedWithInfo {
1044        pub pdu_type: PduType,
1045        pub file_directive_type: Option<FileDirectiveType>,
1046        pub pdu: alloc::vec::Vec<u8>,
1047    }
1048
1049    impl PduOwnedWithInfo {
1050        pub fn new_from_raw_packet(raw_packet: &[u8]) -> Result<Self, PduError> {
1051            Ok(PduRawWithInfo::new(raw_packet)?.into())
1052        }
1053
1054        pub fn new(
1055            pdu_type: PduType,
1056            file_directive_type: Option<FileDirectiveType>,
1057            pdu: alloc::vec::Vec<u8>,
1058        ) -> Self {
1059            Self {
1060                pdu_type,
1061                file_directive_type,
1062                pdu,
1063            }
1064        }
1065    }
1066
1067    impl From<PduRawWithInfo<'_>> for PduOwnedWithInfo {
1068        fn from(value: PduRawWithInfo) -> Self {
1069            Self::new(
1070                value.pdu_type(),
1071                value.file_directive_type(),
1072                value.raw_packet().to_vec(),
1073            )
1074        }
1075    }
1076
1077    impl PduProvider for PduOwnedWithInfo {
1078        fn pdu_type(&self) -> PduType {
1079            self.pdu_type
1080        }
1081
1082        fn file_directive_type(&self) -> Option<FileDirectiveType> {
1083            self.file_directive_type
1084        }
1085
1086        fn raw_pdu(&self) -> &[u8] {
1087            &self.pdu
1088        }
1089
1090        fn packet_target(&self) -> Result<PacketTarget, PduError> {
1091            determine_packet_target(&self.pdu)
1092        }
1093    }
1094}
1095
1096#[derive(Debug, Clone, Copy)]
1097struct PositiveAckParams {
1098    ack_counter: u32,
1099    positive_ack_of_cancellation: bool,
1100}
1101
1102#[cfg(test)]
1103pub(crate) mod tests {
1104    use core::{
1105        cell::{Cell, RefCell},
1106        sync::atomic::AtomicBool,
1107    };
1108    use std::{println, sync::Arc};
1109
1110    use alloc::{collections::VecDeque, string::String, vec::Vec};
1111    use spacepackets::{
1112        cfdp::{
1113            ChecksumType, ConditionCode, PduType, TransmissionMode,
1114            lv::Lv,
1115            pdu::{
1116                CommonPduConfig, FileDirectiveType, PduHeader,
1117                eof::EofPdu,
1118                file_data::FileDataPdu,
1119                finished::{DeliveryCode, FileStatus},
1120                metadata::{MetadataGenericParams, MetadataPduCreator},
1121            },
1122        },
1123        util::{UnsignedByteField, UnsignedByteFieldU8, UnsignedByteFieldU16, UnsignedEnum},
1124    };
1125    use user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams};
1126
1127    use crate::{PacketTarget, StdCountdown};
1128
1129    use super::*;
1130
1131    pub const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1);
1132    pub const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
1133
1134    // This test structure allows to precisely control the expiry of CFDP timers.
1135    #[derive(Debug, Default, Clone)]
1136    pub(crate) struct TimerExpiryControl {
1137        pub(crate) check_limit: Arc<AtomicBool>,
1138        pub(crate) positive_ack: Arc<AtomicBool>,
1139        pub(crate) nak_activity: Arc<AtomicBool>,
1140    }
1141
1142    impl TimerExpiryControl {
1143        pub fn set_check_limit_expired(&mut self) {
1144            self.check_limit
1145                .store(true, core::sync::atomic::Ordering::Release);
1146        }
1147
1148        pub fn set_positive_ack_expired(&mut self) {
1149            self.positive_ack
1150                .store(true, core::sync::atomic::Ordering::Release);
1151        }
1152
1153        pub fn set_nak_activity_expired(&mut self) {
1154            self.nak_activity
1155                .store(true, core::sync::atomic::Ordering::Release);
1156        }
1157    }
1158
1159    #[derive(Debug)]
1160    pub(crate) struct TestCheckTimer {
1161        counter: Cell<u32>,
1162        context: TimerContext,
1163        expiry_control: TimerExpiryControl,
1164    }
1165
1166    impl Countdown for TestCheckTimer {
1167        fn has_expired(&self) -> bool {
1168            match self.context {
1169                TimerContext::CheckLimit {
1170                    local_id: _,
1171                    remote_id: _,
1172                    entity_type: _,
1173                } => self
1174                    .expiry_control
1175                    .check_limit
1176                    .load(core::sync::atomic::Ordering::Acquire),
1177                TimerContext::PositiveAck { expiry_time: _ } => self
1178                    .expiry_control
1179                    .positive_ack
1180                    .load(core::sync::atomic::Ordering::Acquire),
1181                TimerContext::NakActivity { expiry_time: _ } => self
1182                    .expiry_control
1183                    .nak_activity
1184                    .load(core::sync::atomic::Ordering::Acquire),
1185            }
1186        }
1187        fn reset(&mut self) {
1188            match self.context {
1189                TimerContext::CheckLimit {
1190                    local_id: _,
1191                    remote_id: _,
1192                    entity_type: _,
1193                } => self
1194                    .expiry_control
1195                    .check_limit
1196                    .store(false, core::sync::atomic::Ordering::Release),
1197                TimerContext::NakActivity { expiry_time: _ } => self
1198                    .expiry_control
1199                    .nak_activity
1200                    .store(false, core::sync::atomic::Ordering::Release),
1201                TimerContext::PositiveAck { expiry_time: _ } => self
1202                    .expiry_control
1203                    .positive_ack
1204                    .store(false, core::sync::atomic::Ordering::Release),
1205            }
1206            self.counter.set(0);
1207        }
1208    }
1209
1210    impl TestCheckTimer {
1211        pub fn new(context: TimerContext, expiry_control: &TimerExpiryControl) -> Self {
1212            Self {
1213                counter: Cell::new(0),
1214                context,
1215                expiry_control: expiry_control.clone(),
1216            }
1217        }
1218    }
1219
1220    pub(crate) struct TestCheckTimerCreator {
1221        expiry_control: TimerExpiryControl,
1222    }
1223
1224    impl TestCheckTimerCreator {
1225        pub fn new(expiry_control: &TimerExpiryControl) -> Self {
1226            Self {
1227                expiry_control: expiry_control.clone(),
1228            }
1229        }
1230    }
1231
1232    impl TimerCreator for TestCheckTimerCreator {
1233        type Countdown = TestCheckTimer;
1234
1235        fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown {
1236            match timer_context {
1237                TimerContext::CheckLimit { .. } => {
1238                    TestCheckTimer::new(timer_context, &self.expiry_control)
1239                }
1240                TimerContext::PositiveAck { expiry_time: _ } => {
1241                    TestCheckTimer::new(timer_context, &self.expiry_control)
1242                }
1243                TimerContext::NakActivity { expiry_time: _ } => {
1244                    TestCheckTimer::new(timer_context, &self.expiry_control)
1245                }
1246            }
1247        }
1248    }
1249
1250    #[derive(Debug)]
1251    pub struct FileSegmentRecvdParamsNoSegMetadata {
1252        #[allow(dead_code)]
1253        pub id: TransactionId,
1254        pub offset: u64,
1255        pub length: usize,
1256    }
1257
1258    #[derive(Default, Debug)]
1259    pub struct TestCfdpUser {
1260        pub check_queues_empty_on_drop: bool,
1261        pub next_expected_seq_num: u64,
1262        pub expected_full_src_name: String,
1263        pub expected_full_dest_name: String,
1264        pub expected_file_size: u64,
1265        pub transaction_indication_call_count: u32,
1266        pub eof_sent_call_count: u32,
1267        pub eof_recvd_call_count: u32,
1268        pub finished_indic_queue: VecDeque<TransactionFinishedParams>,
1269        pub metadata_recv_queue: VecDeque<OwnedMetadataRecvdParams>,
1270        pub file_seg_recvd_queue: VecDeque<FileSegmentRecvdParamsNoSegMetadata>,
1271    }
1272
1273    impl TestCfdpUser {
1274        pub fn new(
1275            next_expected_seq_num: u64,
1276            expected_full_src_name: String,
1277            expected_full_dest_name: String,
1278            expected_file_size: u64,
1279        ) -> Self {
1280            Self {
1281                check_queues_empty_on_drop: true,
1282                next_expected_seq_num,
1283                expected_full_src_name,
1284                expected_full_dest_name,
1285                expected_file_size,
1286                transaction_indication_call_count: 0,
1287                eof_recvd_call_count: 0,
1288                eof_sent_call_count: 0,
1289                finished_indic_queue: VecDeque::new(),
1290                metadata_recv_queue: VecDeque::new(),
1291                file_seg_recvd_queue: VecDeque::new(),
1292            }
1293        }
1294
1295        pub fn generic_id_check(&self, id: &crate::TransactionId) {
1296            assert_eq!(id.source_id, LOCAL_ID.into());
1297            assert_eq!(id.seq_num().value(), self.next_expected_seq_num);
1298        }
1299
1300        pub fn indication_queues_empty(&self) -> bool {
1301            self.finished_indic_queue.is_empty()
1302                && self.metadata_recv_queue.is_empty()
1303                && self.file_seg_recvd_queue.is_empty()
1304        }
1305
1306        pub fn verify_finished_indication_retained(
1307            &mut self,
1308            delivery_code: DeliveryCode,
1309            cond_code: ConditionCode,
1310            id: TransactionId,
1311        ) {
1312            self.verify_finished_indication(delivery_code, cond_code, id, FileStatus::Retained);
1313        }
1314
1315        pub fn verify_finished_indication(
1316            &mut self,
1317            delivery_code: DeliveryCode,
1318            cond_code: ConditionCode,
1319            id: TransactionId,
1320            file_status: FileStatus,
1321        ) {
1322            assert_eq!(self.finished_indic_queue.len(), 1);
1323            let finished_indication = self.finished_indic_queue.pop_front().unwrap();
1324            assert_eq!(finished_indication.id, id);
1325            assert_eq!(finished_indication.condition_code, cond_code);
1326            assert_eq!(finished_indication.delivery_code, delivery_code);
1327            assert_eq!(finished_indication.file_status, file_status);
1328        }
1329    }
1330
1331    impl CfdpUser for TestCfdpUser {
1332        fn transaction_indication(&mut self, id: &crate::TransactionId) {
1333            self.generic_id_check(id);
1334            self.transaction_indication_call_count += 1;
1335        }
1336
1337        fn eof_sent_indication(&mut self, id: &crate::TransactionId) {
1338            self.generic_id_check(id);
1339            self.eof_sent_call_count += 1;
1340        }
1341
1342        fn transaction_finished_indication(
1343            &mut self,
1344            finished_params: &crate::user::TransactionFinishedParams,
1345        ) {
1346            self.generic_id_check(&finished_params.id);
1347            self.finished_indic_queue.push_back(*finished_params);
1348        }
1349
1350        fn metadata_recvd_indication(
1351            &mut self,
1352            md_recvd_params: &crate::user::MetadataReceivedParams,
1353        ) {
1354            self.generic_id_check(&md_recvd_params.id);
1355            assert_eq!(
1356                String::from(md_recvd_params.src_file_name),
1357                self.expected_full_src_name
1358            );
1359            assert_eq!(
1360                String::from(md_recvd_params.dest_file_name),
1361                self.expected_full_dest_name
1362            );
1363            assert_eq!(md_recvd_params.msgs_to_user.len(), 0);
1364            assert_eq!(md_recvd_params.source_id, LOCAL_ID.into());
1365            assert_eq!(md_recvd_params.file_size, self.expected_file_size);
1366            self.metadata_recv_queue.push_back(md_recvd_params.into());
1367        }
1368
1369        fn file_segment_recvd_indication(
1370            &mut self,
1371            segment_recvd_params: &crate::user::FileSegmentRecvdParams,
1372        ) {
1373            self.generic_id_check(&segment_recvd_params.id);
1374            self.file_seg_recvd_queue
1375                .push_back(FileSegmentRecvdParamsNoSegMetadata {
1376                    id: segment_recvd_params.id,
1377                    offset: segment_recvd_params.offset,
1378                    length: segment_recvd_params.length,
1379                })
1380        }
1381
1382        fn report_indication(&mut self, _id: &crate::TransactionId) {}
1383
1384        fn suspended_indication(
1385            &mut self,
1386            _id: &crate::TransactionId,
1387            _condition_code: ConditionCode,
1388        ) {
1389            panic!("unexpected suspended indication");
1390        }
1391
1392        fn resumed_indication(&mut self, _id: &crate::TransactionId, _progresss: u64) {}
1393
1394        fn fault_indication(
1395            &mut self,
1396            _id: &crate::TransactionId,
1397            _condition_code: ConditionCode,
1398            _progress: u64,
1399        ) {
1400            panic!("unexpected fault indication");
1401        }
1402
1403        fn abandoned_indication(
1404            &mut self,
1405            _id: &crate::TransactionId,
1406            _condition_code: ConditionCode,
1407            _progress: u64,
1408        ) {
1409            panic!("unexpected abandoned indication");
1410        }
1411
1412        fn eof_recvd_indication(&mut self, id: &crate::TransactionId) {
1413            self.generic_id_check(id);
1414            self.eof_recvd_call_count += 1;
1415        }
1416    }
1417
1418    impl Drop for TestCfdpUser {
1419        fn drop(&mut self) {
1420            if self.check_queues_empty_on_drop {
1421                assert!(
1422                    self.indication_queues_empty(),
1423                    "indication queues not empty on drop: finished: {}, metadata: {}, file seg: {}",
1424                    self.finished_indic_queue.len(),
1425                    self.metadata_recv_queue.len(),
1426                    self.file_seg_recvd_queue.len()
1427                );
1428            }
1429        }
1430    }
1431
1432    #[derive(Default, Debug)]
1433    pub(crate) struct TestFaultHandler {
1434        pub notice_of_suspension_queue: VecDeque<FaultInfo>,
1435        pub notice_of_cancellation_queue: VecDeque<FaultInfo>,
1436        pub abandoned_queue: VecDeque<FaultInfo>,
1437        pub ignored_queue: VecDeque<FaultInfo>,
1438    }
1439
1440    impl UserFaultHook for TestFaultHandler {
1441        fn notice_of_suspension_cb(&mut self, fault_info: FaultInfo) {
1442            self.notice_of_suspension_queue.push_back(fault_info)
1443        }
1444
1445        fn notice_of_cancellation_cb(&mut self, fault_info: FaultInfo) {
1446            self.notice_of_cancellation_queue.push_back(fault_info)
1447        }
1448
1449        fn abandoned_cb(&mut self, fault_info: FaultInfo) {
1450            self.abandoned_queue.push_back(fault_info)
1451        }
1452
1453        fn ignore_cb(&mut self, fault_info: FaultInfo) {
1454            self.ignored_queue.push_back(fault_info)
1455        }
1456    }
1457
1458    impl TestFaultHandler {
1459        pub(crate) fn suspension_queue_empty(&self) -> bool {
1460            self.notice_of_suspension_queue.is_empty()
1461        }
1462        pub(crate) fn cancellation_queue_empty(&self) -> bool {
1463            self.notice_of_cancellation_queue.is_empty()
1464        }
1465        pub(crate) fn ignored_queue_empty(&self) -> bool {
1466            self.ignored_queue.is_empty()
1467        }
1468        pub(crate) fn abandoned_queue_empty(&self) -> bool {
1469            self.abandoned_queue.is_empty()
1470        }
1471        pub(crate) fn all_queues_empty(&self) -> bool {
1472            self.suspension_queue_empty()
1473                && self.cancellation_queue_empty()
1474                && self.ignored_queue_empty()
1475                && self.abandoned_queue_empty()
1476        }
1477    }
1478
1479    pub struct SentPdu {
1480        pub pdu_type: PduType,
1481        pub file_directive_type: Option<FileDirectiveType>,
1482        pub raw_pdu: Vec<u8>,
1483    }
1484
1485    #[derive(Default)]
1486    pub struct TestCfdpSender {
1487        pub packet_queue: RefCell<VecDeque<SentPdu>>,
1488    }
1489
1490    impl PduSender for TestCfdpSender {
1491        fn send_pdu(
1492            &self,
1493            pdu_type: PduType,
1494            file_directive_type: Option<FileDirectiveType>,
1495            raw_pdu: &[u8],
1496        ) -> Result<(), GenericSendError> {
1497            println!(
1498                "sent pdu: {:?}, directive: {:?}, len: {}",
1499                pdu_type,
1500                file_directive_type,
1501                raw_pdu.len()
1502            );
1503            self.packet_queue.borrow_mut().push_back(SentPdu {
1504                pdu_type,
1505                file_directive_type,
1506                raw_pdu: raw_pdu.to_vec(),
1507            });
1508            Ok(())
1509        }
1510    }
1511
1512    impl TestCfdpSender {
1513        pub fn queue_len(&self) -> usize {
1514            self.packet_queue.borrow_mut().len()
1515        }
1516
1517        pub fn retrieve_next_pdu(&self) -> Option<SentPdu> {
1518            self.packet_queue.borrow_mut().pop_front()
1519        }
1520
1521        pub fn queue_empty(&self) -> bool {
1522            self.packet_queue.borrow_mut().is_empty()
1523        }
1524    }
1525
1526    pub fn basic_remote_cfg_table(
1527        dest_id: impl Into<UnsignedByteField>,
1528        max_packet_len: usize,
1529        crc_on_transmission_by_default: bool,
1530    ) -> RemoteConfigStoreStd {
1531        let mut table = RemoteConfigStoreStd::default();
1532        let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
1533            dest_id.into(),
1534            max_packet_len,
1535            true,
1536            crc_on_transmission_by_default,
1537            TransmissionMode::Unacknowledged,
1538            ChecksumType::Crc32,
1539        );
1540        table.add_config(&remote_entity_cfg).unwrap();
1541        table
1542    }
1543
1544    fn generic_pdu_header() -> PduHeader {
1545        let pdu_conf = CommonPduConfig::default();
1546        PduHeader::new_for_file_directive(pdu_conf, 0)
1547    }
1548
1549    #[test]
1550    fn test_transaction_id() {
1551        let transaction_id = TransactionId::new(
1552            UnsignedByteFieldU16::new(1).into(),
1553            UnsignedByteFieldU16::new(2).into(),
1554        );
1555        assert_eq!(transaction_id.source_id().value(), 1);
1556        assert_eq!(transaction_id.seq_num().value(), 2);
1557    }
1558
1559    #[test]
1560    fn test_metadata_pdu_info() {
1561        let mut buf: [u8; 128] = [0; 128];
1562        let pdu_header = generic_pdu_header();
1563        let metadata_params = MetadataGenericParams::default();
1564        let src_file_name = "hello.txt";
1565        let dest_file_name = "hello-dest.txt";
1566        let src_lv = Lv::new_from_str(src_file_name).unwrap();
1567        let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
1568        let metadata_pdu =
1569            MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv);
1570        metadata_pdu
1571            .write_to_bytes(&mut buf)
1572            .expect("writing metadata PDU failed");
1573
1574        let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
1575        assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
1576        assert!(packet_info.file_directive_type().is_some());
1577        assert_eq!(
1578            packet_info.file_directive_type().unwrap(),
1579            FileDirectiveType::MetadataPdu
1580        );
1581        assert_eq!(
1582            packet_info.raw_packet(),
1583            &buf[0..metadata_pdu.len_written()]
1584        );
1585        assert_eq!(
1586            packet_info.packet_target().unwrap(),
1587            PacketTarget::DestEntity
1588        );
1589    }
1590
1591    #[test]
1592    fn test_filedata_pdu_info() {
1593        let mut buf: [u8; 128] = [0; 128];
1594        let pdu_header = generic_pdu_header();
1595        let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
1596        file_data_pdu
1597            .write_to_bytes(&mut buf)
1598            .expect("writing file data PDU failed");
1599        let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
1600        assert_eq!(
1601            packet_info.raw_packet(),
1602            &buf[0..file_data_pdu.len_written()]
1603        );
1604        assert_eq!(packet_info.pdu_type(), PduType::FileData);
1605        assert!(packet_info.file_directive_type().is_none());
1606        assert_eq!(
1607            packet_info.packet_target().unwrap(),
1608            PacketTarget::DestEntity
1609        );
1610    }
1611
1612    #[test]
1613    fn test_eof_pdu_info() {
1614        let mut buf: [u8; 128] = [0; 128];
1615        let pdu_header = generic_pdu_header();
1616        let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
1617        eof_pdu
1618            .write_to_bytes(&mut buf)
1619            .expect("writing file data PDU failed");
1620        let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
1621        assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
1622        assert!(packet_info.file_directive_type().is_some());
1623        assert_eq!(packet_info.raw_packet(), &buf[0..eof_pdu.len_written()]);
1624        assert_eq!(
1625            packet_info.file_directive_type().unwrap(),
1626            FileDirectiveType::EofPdu
1627        );
1628    }
1629
1630    #[test]
1631    fn test_std_check_timer() {
1632        let mut std_check_timer = StdCountdown::new(Duration::from_secs(1));
1633        assert!(!std_check_timer.has_expired());
1634        assert_eq!(std_check_timer.expiry_time_seconds(), 1);
1635        std::thread::sleep(Duration::from_millis(800));
1636        assert!(!std_check_timer.has_expired());
1637        std::thread::sleep(Duration::from_millis(205));
1638        assert!(std_check_timer.has_expired());
1639        std_check_timer.reset();
1640        assert!(!std_check_timer.has_expired());
1641    }
1642
1643    #[test]
1644    fn test_std_check_timer_creator() {
1645        let std_check_timer_creator = StdTimerCreator::new(Duration::from_secs(1));
1646        let check_timer = std_check_timer_creator.create_countdown(TimerContext::NakActivity {
1647            expiry_time: Duration::from_secs(1),
1648        });
1649        assert_eq!(check_timer.expiry_time_seconds(), 1);
1650    }
1651
1652    #[test]
1653    fn test_remote_cfg_provider_single() {
1654        let mut remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
1655            REMOTE_ID.into(),
1656            1024,
1657            true,
1658            false,
1659            TransmissionMode::Unacknowledged,
1660            ChecksumType::Crc32,
1661        );
1662        let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
1663        assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
1664        assert_eq!(remote_entity_retrieved.max_packet_len, 1024);
1665        assert!(remote_entity_retrieved.closure_requested_by_default);
1666        assert!(!remote_entity_retrieved.crc_on_transmission_by_default);
1667        assert_eq!(
1668            remote_entity_retrieved.default_crc_type,
1669            ChecksumType::Crc32
1670        );
1671        let remote_entity_mut = remote_entity_cfg.get_mut(REMOTE_ID.value()).unwrap();
1672        assert_eq!(remote_entity_mut.entity_id, REMOTE_ID.into());
1673        let dummy = RemoteEntityConfig::new_with_default_values(
1674            LOCAL_ID.into(),
1675            1024,
1676            true,
1677            false,
1678            TransmissionMode::Unacknowledged,
1679            ChecksumType::Crc32,
1680        );
1681        assert_eq!(
1682            remote_entity_cfg.add_config(&dummy).unwrap_err(),
1683            RemoteConfigStoreError::Full
1684        );
1685        let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
1686        assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
1687        // Does not exist.
1688        assert!(remote_entity_cfg.get(LOCAL_ID.value()).is_none());
1689        assert!(remote_entity_cfg.get_mut(LOCAL_ID.value()).is_none());
1690    }
1691
1692    #[test]
1693    fn test_remote_cfg_provider_std() {
1694        let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
1695            REMOTE_ID.into(),
1696            1024,
1697            true,
1698            false,
1699            TransmissionMode::Unacknowledged,
1700            ChecksumType::Crc32,
1701        );
1702        let mut remote_cfg_provider = RemoteConfigStoreStd::default();
1703        assert!(remote_cfg_provider.0.is_empty());
1704        remote_cfg_provider.add_config(&remote_entity_cfg).unwrap();
1705        assert_eq!(remote_cfg_provider.0.len(), 1);
1706        let remote_entity_cfg_2 = RemoteEntityConfig::new_with_default_values(
1707            LOCAL_ID.into(),
1708            1024,
1709            true,
1710            false,
1711            TransmissionMode::Unacknowledged,
1712            ChecksumType::Crc32,
1713        );
1714        let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
1715        assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
1716        remote_cfg_provider
1717            .add_config(&remote_entity_cfg_2)
1718            .unwrap();
1719        assert_eq!(remote_cfg_provider.0.len(), 2);
1720        let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
1721        assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
1722        assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
1723        assert_eq!(remote_cfg_provider.0.len(), 1);
1724        let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
1725        cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
1726        assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
1727        assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
1728    }
1729
1730    #[test]
1731    fn test_remote_cfg_provider_vector() {
1732        let mut remote_cfg_provider = RemoteConfigList::default();
1733        let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
1734            REMOTE_ID.into(),
1735            1024,
1736            true,
1737            false,
1738            TransmissionMode::Unacknowledged,
1739            ChecksumType::Crc32,
1740        );
1741        assert!(remote_cfg_provider.0.is_empty());
1742        remote_cfg_provider.add_config(&remote_entity_cfg).unwrap();
1743        assert_eq!(remote_cfg_provider.0.len(), 1);
1744        let remote_entity_cfg_2 = RemoteEntityConfig::new_with_default_values(
1745            LOCAL_ID.into(),
1746            1024,
1747            true,
1748            false,
1749            TransmissionMode::Unacknowledged,
1750            ChecksumType::Crc32,
1751        );
1752        let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
1753        assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
1754        assert!(
1755            remote_cfg_provider
1756                .add_config(&remote_entity_cfg_2)
1757                .unwrap()
1758        );
1759        assert_eq!(remote_cfg_provider.0.len(), 2);
1760        let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
1761        assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
1762        assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
1763        assert_eq!(remote_cfg_provider.0.len(), 1);
1764        let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
1765        cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
1766        assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
1767        assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
1768    }
1769
1770    #[test]
1771    fn dummy_fault_hook_test() {
1772        let mut user_hook_dummy = DummyFaultHook::default();
1773        let transaction_id = TransactionId::new(
1774            UnsignedByteFieldU8::new(0).into(),
1775            UnsignedByteFieldU8::new(0).into(),
1776        );
1777        user_hook_dummy.notice_of_cancellation_cb(FaultInfo::new(
1778            transaction_id,
1779            ConditionCode::NoError,
1780            0,
1781        ));
1782        user_hook_dummy.notice_of_suspension_cb(FaultInfo::new(
1783            transaction_id,
1784            ConditionCode::NoError,
1785            0,
1786        ));
1787        user_hook_dummy.abandoned_cb(FaultInfo::new(transaction_id, ConditionCode::NoError, 0));
1788        user_hook_dummy.ignore_cb(FaultInfo::new(transaction_id, ConditionCode::NoError, 0));
1789    }
1790
1791    #[test]
1792    fn dummy_pdu_provider_test() {
1793        let dummy_pdu_provider = DummyPduProvider(());
1794        assert_eq!(dummy_pdu_provider.pdu_type(), PduType::FileData);
1795        assert!(dummy_pdu_provider.file_directive_type().is_none());
1796        assert_eq!(dummy_pdu_provider.raw_pdu(), &[]);
1797        assert_eq!(
1798            dummy_pdu_provider.packet_target(),
1799            Ok(PacketTarget::SourceEntity)
1800        );
1801    }
1802
1803    #[test]
1804    fn test_fault_handler_checksum_error_ignored_by_default() {
1805        let fault_handler = FaultHandler::new(TestFaultHandler::default());
1806        assert_eq!(
1807            fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
1808            FaultHandlerCode::IgnoreError
1809        );
1810    }
1811
1812    #[test]
1813    fn test_fault_handler_unsupported_checksum_ignored_by_default() {
1814        let fault_handler = FaultHandler::new(TestFaultHandler::default());
1815        assert_eq!(
1816            fault_handler.get_fault_handler(ConditionCode::UnsupportedChecksumType),
1817            FaultHandlerCode::IgnoreError
1818        );
1819    }
1820
1821    #[test]
1822    fn test_fault_handler_basic() {
1823        let mut fault_handler = FaultHandler::new(TestFaultHandler::default());
1824        assert_eq!(
1825            fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
1826            FaultHandlerCode::IgnoreError
1827        );
1828        fault_handler.set_fault_handler(
1829            ConditionCode::FileChecksumFailure,
1830            FaultHandlerCode::NoticeOfCancellation,
1831        );
1832        assert_eq!(
1833            fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
1834            FaultHandlerCode::NoticeOfCancellation
1835        );
1836    }
1837
1838    #[test]
1839    fn transaction_id_hashable_usable_as_map_key() {
1840        let mut map = hashbrown::HashMap::new();
1841        let transaction_id_0 = TransactionId::new(
1842            UnsignedByteFieldU8::new(1).into(),
1843            UnsignedByteFieldU8::new(2).into(),
1844        );
1845        map.insert(transaction_id_0, 5_u32);
1846    }
1847}