cfdp/
lib.rs

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