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. Currently, the handlers still require the [std] feature until
18
//! [thiserror supports `error_in_core`](https://github.com/dtolnay/thiserror/pull/304).
19
//! It is recommended to activate the `alloc` feature at the very least to allow using the primary
20
//! components provided by this crate. These components will only allocate memory at initialization
21
//! time and thus are still viable for systems where run-time allocation is prohibited.
22
//!
23
//! The core of this library are the [crate::dest::DestinationHandler] and the
24
//! [crate::source::SourceHandler] components which model the CFDP destination and source entity
25
//! respectively. You can find high-level and API documentation for both handlers in the respective
26
//! [crate::dest] and [crate::source] module.
27
//!
28
//! # Examples
29
//!
30
//! This library currently features two example application which showcase how the provided
31
//! components could be used to provide CFDP services.
32
//!
33
//! The [end-to-end test](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/tests/end-to-end.rs)
34
//! is an integration tests which spawns a CFDP source entity and a CFDP destination entity,
35
//! moves them to separate threads and then performs a small file copy operation.
36
//! You can run the integration test for a transfer with no closure and with printout to the
37
//! standard console by running:
38
//!
39
//! ```sh
40
//! cargo test end_to_end_test_no_closure -- --nocapture
41
//! ```
42
//!
43
//! or with closure:
44
//!
45
//! ```sh
46
//! cargo test end_to_end_test_with_closure -- --nocapture
47
//! ```
48
//!
49
//! The [Python Interoperability](https://egit.irs.uni-stuttgart.de/rust/cfdp/src/branch/main/examples/python-interop)
50
//! example showcases the interoperability of the CFDP handlers written in Rust with a Python
51
//! implementation. The dedicated example documentation shows how to run this example.
52
//!
53
//! # Notes on the user hooks and scheduling
54
//!
55
//! Both examples feature implementations of the [UserFaultHookProvider] and the [user::CfdpUser]
56
//! trait which simply print some information to the console to monitor the progress of a file
57
//! copy operation. These implementations could be adapted for other handler integrations. For
58
//! example, they could signal a GUI application to display some information for the user.
59
//!
60
//! Even though both examples move the newly spawned handlers to dedicated threads, this is not
61
//! the only way they could be scheduled. For example, to support an arbitrary (or bounded)
62
//! amount of file copy operations on either source or destination side, those handlers could be
63
//! moved into a [std::collections::HashMap] structure which is then scheduled inside a thread, or
64
//! you could schedule a fixed amount of handlers inside a
65
//! [threadpool](https://docs.rs/threadpool/latest/threadpool/).
66
#![no_std]
67
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
68
#[cfg(feature = "alloc")]
69
extern crate alloc;
70
#[cfg(any(feature = "std", test))]
71
extern crate std;
72

            
73
#[cfg(feature = "std")]
74
pub mod dest;
75
#[cfg(feature = "alloc")]
76
pub mod filestore;
77
pub mod request;
78
#[cfg(feature = "std")]
79
pub mod source;
80
pub mod time;
81
pub mod user;
82

            
83
use crate::time::CountdownProvider;
84
use core::{cell::RefCell, fmt::Debug, hash::Hash};
85
use crc::{Crc, CRC_32_ISCSI, CRC_32_ISO_HDLC};
86
#[cfg(feature = "std")]
87
use hashbrown::HashMap;
88

            
89
#[cfg(feature = "alloc")]
90
pub use alloc_mod::*;
91
use core::time::Duration;
92
#[cfg(feature = "serde")]
93
use serde::{Deserialize, Serialize};
94
use spacepackets::{
95
    cfdp::{
96
        pdu::{FileDirectiveType, PduError, PduHeader},
97
        ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
98
    },
99
    util::{UnsignedByteField, UnsignedEnum},
100
};
101
#[cfg(feature = "std")]
102
pub use std_mod::*;
103

            
104
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
106
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
107
pub enum EntityType {
108
    Sending,
109
    Receiving,
110
}
111

            
112
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
114
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
115
pub enum TimerContext {
116
    CheckLimit {
117
        local_id: UnsignedByteField,
118
        remote_id: UnsignedByteField,
119
        entity_type: EntityType,
120
    },
121
    NakActivity {
122
        expiry_time: Duration,
123
    },
124
    PositiveAck {
125
        expiry_time: Duration,
126
    },
127
}
128

            
129
/// A generic trait which allows CFDP entities to create check timers which are required to
130
/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
131
/// and 4.6.3.3.
132
///
133
/// This trait also allows the creation of different check timers depending on context and purpose
134
/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
135
/// other factors.
136
///
137
/// The countdown timer is used by 3 mechanisms of the CFDP protocol.
138
///
139
/// ## 1. Check limit handling
140
///
141
/// The first mechanism is the check limit handling for unacknowledged transfers as specified
142
/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard.
143
/// For this mechanism, the timer has different functionality depending on whether
144
/// the using entity is the sending entity or the receiving entity for the unacknowledged
145
/// transmission mode.
146
///
147
/// For the sending entity, this timer determines the expiry period for declaring a check limit
148
/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer.
149
/// Also see 4.6.3.2 of the CFDP standard.
150
///
151
/// For the receiving entity, this timer determines the expiry period for incrementing a check
152
/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
153
/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
154
///
155
/// ## 2. NAK activity limit
156
///
157
/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP
158
/// standard. The expiration period will be provided by the NAK timer expiration limit of the
159
/// remote entity configuration.
160
///
161
/// ## 3. Positive ACK procedures
162
///
163
/// The timer will be used to perform the Positive Acknowledgement Procedures as specified in
164
/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
165
/// interval of the remote entity configuration.
166
pub trait TimerCreatorProvider {
167
    type Countdown: CountdownProvider;
168

            
169
    fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown;
170
}
171

            
172
/// This structure models the remote entity configuration information as specified in chapter 8.3
173
/// of the CFDP standard.
174

            
175
/// Some of the fields which were not considered necessary for the Rust implementation
176
/// were omitted. Some other fields which are not contained inside the standard but are considered
177
/// necessary for the Rust implementation are included.
178
///
179
/// ## Notes on Positive Acknowledgment Procedures
180
///
181
/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will
182
/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending
183
/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU).
184
/// Once the expected ACK response has not been received for that interval, as counter will be
185
/// incremented and the timer will be reset. Once the counter exceeds the
186
/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared.
187
///
188
/// ## Notes on Deferred Lost Segment Procedures
189
///
190
/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After
191
/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is
192
/// reset when missing segments or missing metadata is received. The timer will be deactivated if
193
/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a
194
/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared.
195
///
196
/// ## Fields
197
///
198
/// * `entity_id` - The ID of the remote entity.
199
/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition
200
///    to the `max_file_segment_len` attribute which also determines the size of file data PDUs.
201
/// * `max_file_segment_len` The maximum file segment length which determines the maximum size
202
///   of file data PDUs in addition to the `max_packet_len` attribute. If this field is set
203
///   to None, the maximum file segment length will be derived from the maximum packet length.
204
///   If this has some value which is smaller than the segment value derived from
205
///   `max_packet_len`, this value will be picked.
206
/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of
207
///    the Put Request, it will be determined from this field in the remote configuration.
208
/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put
209
///    Request, it will be determined from this field in the remote configuration.
210
/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the
211
///   Put Request, it will be determined from this field in the remote configuration.
212
/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on
213
///   transaction cancellation. Defaults to False.
214
/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to
215
///   this remote entity.
216
/// * `check_limit` - This timer determines the expiry period for incrementing a check counter
217
///   after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
218
///   reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to
219
///   2, so the check limit timer may expire twice.
220
/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment
221
///    Procedures inside the class documentation. Expected as floating point seconds. Defaults to
222
///    10 seconds.
223
/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment
224
///    Procedures inside the class documentation. Defaults to 2, so the timer may expire twice.
225
/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a
226
///    file data gap or lost metadata is detected in the acknowledged mode. Defaults to True.
227
/// * `nak_timer_interval_seconds` -  See the notes on the Deferred Lost Segment Procedure inside
228
///    the class documentation. Expected as floating point seconds. Defaults to 10 seconds.
229
/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
230
///    the class documentation. Defaults to 2, so the timer may expire two times.
231
#[derive(Debug, Copy, Clone)]
232
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
233
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
234
pub struct RemoteEntityConfig {
235
    pub entity_id: UnsignedByteField,
236
    pub max_packet_len: usize,
237
    pub max_file_segment_len: Option<usize>,
238
    pub closure_requested_by_default: bool,
239
    pub crc_on_transmission_by_default: bool,
240
    pub default_transmission_mode: TransmissionMode,
241
    pub default_crc_type: ChecksumType,
242
    pub positive_ack_timer_interval_seconds: f32,
243
    pub positive_ack_timer_expiration_limit: u32,
244
    pub check_limit: u32,
245
    pub disposition_on_cancellation: bool,
246
    pub immediate_nak_mode: bool,
247
    pub nak_timer_interval_seconds: f32,
248
    pub nak_timer_expiration_limit: u32,
249
}
250

            
251
impl RemoteEntityConfig {
252
92
    pub fn new_with_default_values(
253
92
        entity_id: UnsignedByteField,
254
92
        max_packet_len: usize,
255
92
        closure_requested_by_default: bool,
256
92
        crc_on_transmission_by_default: bool,
257
92
        default_transmission_mode: TransmissionMode,
258
92
        default_crc_type: ChecksumType,
259
92
    ) -> Self {
260
92
        Self {
261
92
            entity_id,
262
92
            max_file_segment_len: None,
263
92
            max_packet_len,
264
92
            closure_requested_by_default,
265
92
            crc_on_transmission_by_default,
266
92
            default_transmission_mode,
267
92
            default_crc_type,
268
92
            check_limit: 2,
269
92
            positive_ack_timer_interval_seconds: 10.0,
270
92
            positive_ack_timer_expiration_limit: 2,
271
92
            disposition_on_cancellation: false,
272
92
            immediate_nak_mode: true,
273
92
            nak_timer_interval_seconds: 10.0,
274
92
            nak_timer_expiration_limit: 2,
275
92
        }
276
92
    }
277
}
278

            
279
pub trait RemoteEntityConfigProvider {
280
    /// Retrieve the remote entity configuration for the given remote ID.
281
    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig>;
282
    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>;
283
    /// Add a new remote configuration. Return [true] if the configuration was
284
    /// inserted successfully, and [false] if a configuration already exists.
285
    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool;
286
    /// Remote a configuration. Returns [true] if the configuration was removed successfully,
287
    /// and [false] if no configuration exists for the given remote ID.
288
    fn remove_config(&mut self, remote_id: u64) -> bool;
289
}
290

            
291
/// This is a thin wrapper around a [HashMap] to store remote entity configurations.
292
/// It implements the full [RemoteEntityConfigProvider] trait.
293
#[cfg(feature = "std")]
294
#[derive(Default, Debug)]
295
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
296
pub struct StdRemoteEntityConfigProvider(pub HashMap<u64, RemoteEntityConfig>);
297

            
298
#[cfg(feature = "std")]
299
impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
300
60
    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
301
60
        self.0.get(&remote_id)
302
60
    }
303
8
    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
304
8
        self.0.get_mut(&remote_id)
305
8
    }
306
68
    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
307
68
        self.0.insert(cfg.entity_id.value(), *cfg).is_some()
308
68
    }
309
4
    fn remove_config(&mut self, remote_id: u64) -> bool {
310
4
        self.0.remove(&remote_id).is_some()
311
4
    }
312
}
313

            
314
/// This is a thin wrapper around a [alloc::vec::Vec] to store remote entity configurations.
315
/// It implements the full [RemoteEntityConfigProvider] trait.
316
#[cfg(feature = "alloc")]
317
#[derive(Default, Debug)]
318
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
319
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
320
pub struct VecRemoteEntityConfigProvider(pub alloc::vec::Vec<RemoteEntityConfig>);
321

            
322
#[cfg(feature = "alloc")]
323
impl RemoteEntityConfigProvider for VecRemoteEntityConfigProvider {
324
4
    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
325
4
        self.0
326
4
            .iter()
327
8
            .find(|&cfg| cfg.entity_id.value() == remote_id)
328
4
    }
329

            
330
4
    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
331
4
        self.0
332
4
            .iter_mut()
333
6
            .find(|cfg| cfg.entity_id.value() == remote_id)
334
4
    }
335

            
336
4
    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
337
4
        self.0.push(*cfg);
338
4
        true
339
4
    }
340

            
341
4
    fn remove_config(&mut self, remote_id: u64) -> bool {
342
4
        for (idx, cfg) in self.0.iter().enumerate() {
343
4
            if cfg.entity_id.value() == remote_id {
344
2
                self.0.remove(idx);
345
2
                return true;
346
2
            }
347
        }
348
2
        false
349
4
    }
350
}
351

            
352
/// A remote entity configurations also implements the [RemoteEntityConfigProvider], but the
353
/// [RemoteEntityConfigProvider::add_config] and [RemoteEntityConfigProvider::remove_config]
354
/// are no-ops and always returns [false].
355
impl RemoteEntityConfigProvider for RemoteEntityConfig {
356
22
    fn get(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
357
22
        if remote_id == self.entity_id.value() {
358
20
            return Some(self);
359
2
        }
360
2
        None
361
22
    }
362

            
363
4
    fn get_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
364
4
        if remote_id == self.entity_id.value() {
365
2
            return Some(self);
366
2
        }
367
2
        None
368
4
    }
369

            
370
2
    fn add_config(&mut self, _cfg: &RemoteEntityConfig) -> bool {
371
2
        false
372
2
    }
373

            
374
2
    fn remove_config(&mut self, _remote_id: u64) -> bool {
375
2
        false
376
2
    }
377
}
378

            
379
/// This trait introduces some callbacks which will be called when a particular CFDP fault
380
/// handler is called.
381
///
382
/// It is passed into the CFDP handlers as part of the [UserFaultHookProvider] and the local entity
383
/// configuration and provides a way to specify custom user error handlers. This allows to
384
/// implement some CFDP features like fault handler logging, which would not be possible
385
/// generically otherwise.
386
///
387
/// For each error reported by the [FaultHandler], the appropriate fault handler callback
388
/// will be called depending on the [FaultHandlerCode].
389
pub trait UserFaultHookProvider {
390
    fn notice_of_suspension_cb(
391
        &mut self,
392
        transaction_id: TransactionId,
393
        cond: ConditionCode,
394
        progress: u64,
395
    );
396

            
397
    fn notice_of_cancellation_cb(
398
        &mut self,
399
        transaction_id: TransactionId,
400
        cond: ConditionCode,
401
        progress: u64,
402
    );
403

            
404
    fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
405

            
406
    fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
407
}
408

            
409
/// Dummy fault hook which implements [UserFaultHookProvider] but only provides empty
410
/// implementations.
411
#[derive(Default, Debug, PartialEq, Eq, Copy, Clone)]
412
pub struct DummyFaultHook {}
413

            
414
impl UserFaultHookProvider for DummyFaultHook {
415
2
    fn notice_of_suspension_cb(
416
2
        &mut self,
417
2
        _transaction_id: TransactionId,
418
2
        _cond: ConditionCode,
419
2
        _progress: u64,
420
2
    ) {
421
2
    }
422

            
423
2
    fn notice_of_cancellation_cb(
424
2
        &mut self,
425
2
        _transaction_id: TransactionId,
426
2
        _cond: ConditionCode,
427
2
        _progress: u64,
428
2
    ) {
429
2
    }
430

            
431
2
    fn abandoned_cb(
432
2
        &mut self,
433
2
        _transaction_id: TransactionId,
434
2
        _cond: ConditionCode,
435
2
        _progress: u64,
436
2
    ) {
437
2
    }
438

            
439
2
    fn ignore_cb(&mut self, _transaction_id: TransactionId, _cond: ConditionCode, _progress: u64) {}
440
}
441

            
442
/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP
443
/// standard.
444
///
445
/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
446
/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
447
/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
448
/// user-provided callback function provided by the [UserFaultHookProvider].
449
///
450
/// Some note on the provided default settings:
451
///
452
/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers,
453
///   cancelling the transfer immediately would interfere with the check limit mechanism specified
454
///   in chapter 4.6.3.3.
455
/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is
456
///   not supported the file transfer might still have worked properly.
457
///
458
/// For all other faults, the default fault handling operation will be to cancel the transaction.
459
/// These defaults can be overriden by using the [Self::set_fault_handler] method.
460
/// Please note that in any case, fault handler overrides can be specified by the sending CFDP
461
/// entity.
462
pub struct FaultHandler<UserHandler: UserFaultHookProvider> {
463
    handler_array: [FaultHandlerCode; 10],
464
    // Could also change the user fault handler trait to have non mutable methods, but that limits
465
    // flexbility on the user side..
466
    pub user_hook: RefCell<UserHandler>,
467
}
468

            
469
impl<UserHandler: UserFaultHookProvider> FaultHandler<UserHandler> {
470
190
    fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
471
190
        Some(match conditon_code {
472
            ConditionCode::PositiveAckLimitReached => 0,
473
            ConditionCode::KeepAliveLimitReached => 1,
474
            ConditionCode::InvalidTransmissionMode => 2,
475
            ConditionCode::FilestoreRejection => 3,
476
98
            ConditionCode::FileChecksumFailure => 4,
477
            ConditionCode::FileSizeError => 5,
478
            ConditionCode::NakLimitReached => 6,
479
            ConditionCode::InactivityDetected => 7,
480
12
            ConditionCode::CheckLimitReached => 8,
481
80
            ConditionCode::UnsupportedChecksumType => 9,
482
            _ => return None,
483
        })
484
190
    }
485

            
486
2
    pub fn set_fault_handler(
487
2
        &mut self,
488
2
        condition_code: ConditionCode,
489
2
        fault_handler: FaultHandlerCode,
490
2
    ) {
491
2
        let array_idx = Self::condition_code_to_array_index(condition_code);
492
2
        if array_idx.is_none() {
493
            return;
494
2
        }
495
2
        self.handler_array[array_idx.unwrap()] = fault_handler;
496
2
    }
497

            
498
78
    pub fn new(user_fault_handler: UserHandler) -> Self {
499
78
        let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
500
78
        init_array
501
78
            [Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
502
78
            FaultHandlerCode::IgnoreError;
503
78
        init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType)
504
78
            .unwrap()] = FaultHandlerCode::IgnoreError;
505
78
        Self {
506
78
            handler_array: init_array,
507
78
            user_hook: RefCell::new(user_fault_handler),
508
78
        }
509
78
    }
510

            
511
20
    pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode {
512
20
        let array_idx = Self::condition_code_to_array_index(condition_code);
513
20
        if array_idx.is_none() {
514
            return FaultHandlerCode::IgnoreError;
515
20
        }
516
20
        self.handler_array[array_idx.unwrap()]
517
20
    }
518

            
519
12
    pub fn report_fault(
520
12
        &self,
521
12
        transaction_id: TransactionId,
522
12
        condition: ConditionCode,
523
12
        progress: u64,
524
12
    ) -> FaultHandlerCode {
525
12
        let array_idx = Self::condition_code_to_array_index(condition);
526
12
        if array_idx.is_none() {
527
            return FaultHandlerCode::IgnoreError;
528
12
        }
529
12
        let fh_code = self.handler_array[array_idx.unwrap()];
530
12
        let mut handler_mut = self.user_hook.borrow_mut();
531
12
        match fh_code {
532
6
            FaultHandlerCode::NoticeOfCancellation => {
533
6
                handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
534
6
            }
535
            FaultHandlerCode::NoticeOfSuspension => {
536
                handler_mut.notice_of_suspension_cb(transaction_id, condition, progress);
537
            }
538
6
            FaultHandlerCode::IgnoreError => {
539
6
                handler_mut.ignore_cb(transaction_id, condition, progress);
540
6
            }
541
            FaultHandlerCode::AbandonTransaction => {
542
                handler_mut.abandoned_cb(transaction_id, condition, progress);
543
            }
544
        }
545
12
        fh_code
546
12
    }
547
}
548

            
549
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
550
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
551
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
552
pub struct IndicationConfig {
553
    pub eof_sent: bool,
554
    pub eof_recv: bool,
555
    pub file_segment_recv: bool,
556
    pub transaction_finished: bool,
557
    pub suspended: bool,
558
    pub resumed: bool,
559
}
560

            
561
impl Default for IndicationConfig {
562
80
    fn default() -> Self {
563
80
        Self {
564
80
            eof_sent: true,
565
80
            eof_recv: true,
566
80
            file_segment_recv: true,
567
80
            transaction_finished: true,
568
80
            suspended: true,
569
80
            resumed: true,
570
80
        }
571
80
    }
572
}
573

            
574
/// Each CFDP entity handler has a [LocalEntityConfig]uration.
575
pub struct LocalEntityConfig<UserFaultHook: UserFaultHookProvider> {
576
    pub id: UnsignedByteField,
577
    pub indication_cfg: IndicationConfig,
578
    pub fault_handler: FaultHandler<UserFaultHook>,
579
}
580

            
581
impl<UserFaultHook: UserFaultHookProvider> LocalEntityConfig<UserFaultHook> {
582
8
    pub fn new(
583
8
        id: UnsignedByteField,
584
8
        indication_cfg: IndicationConfig,
585
8
        hook: UserFaultHook,
586
8
    ) -> Self {
587
8
        Self {
588
8
            id,
589
8
            indication_cfg,
590
8
            fault_handler: FaultHandler::new(hook),
591
8
        }
592
8
    }
593
}
594

            
595
impl<UserFaultHook: UserFaultHookProvider> LocalEntityConfig<UserFaultHook> {
596
2
    pub fn user_fault_hook_mut(&mut self) -> &mut RefCell<UserFaultHook> {
597
2
        &mut self.fault_handler.user_hook
598
2
    }
599

            
600
82
    pub fn user_fault_hook(&self) -> &RefCell<UserFaultHook> {
601
82
        &self.fault_handler.user_hook
602
82
    }
603
}
604

            
605
/// Generic error type for sending a PDU via a message queue.
606
#[cfg(feature = "std")]
607
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
608
#[non_exhaustive]
609
pub enum GenericSendError {
610
    #[error("RX disconnected")]
611
    RxDisconnected,
612
    #[error("queue is full, fill count {0:?}")]
613
    QueueFull(Option<u32>),
614
    #[error("other send error")]
615
    Other,
616
}
617

            
618
#[cfg(feature = "std")]
619
pub trait PduSendProvider {
620
    fn send_pdu(
621
        &self,
622
        pdu_type: PduType,
623
        file_directive_type: Option<FileDirectiveType>,
624
        raw_pdu: &[u8],
625
    ) -> Result<(), GenericSendError>;
626
}
627

            
628
#[cfg(feature = "std")]
629
pub mod std_mod {
630
    use std::sync::mpsc;
631

            
632
    use super::*;
633

            
634
    impl PduSendProvider for mpsc::Sender<PduOwnedWithInfo> {
635
28
        fn send_pdu(
636
28
            &self,
637
28
            pdu_type: PduType,
638
28
            file_directive_type: Option<FileDirectiveType>,
639
28
            raw_pdu: &[u8],
640
28
        ) -> Result<(), GenericSendError> {
641
28
            self.send(PduOwnedWithInfo::new(
642
28
                pdu_type,
643
28
                file_directive_type,
644
28
                raw_pdu.to_vec(),
645
28
            ))
646
28
            .map_err(|_| GenericSendError::RxDisconnected)?;
647
28
            Ok(())
648
28
        }
649
    }
650

            
651
    /// Simple implementation of the [CountdownProvider] trait assuming a standard runtime.
652
    #[derive(Debug)]
653
    pub struct StdCountdown {
654
        expiry_time: Duration,
655
        start_time: std::time::Instant,
656
    }
657

            
658
    impl StdCountdown {
659
16
        pub fn new(expiry_time: Duration) -> Self {
660
16
            Self {
661
16
                expiry_time,
662
16
                start_time: std::time::Instant::now(),
663
16
            }
664
16
        }
665

            
666
4
        pub fn expiry_time_seconds(&self) -> u64 {
667
4
            self.expiry_time.as_secs()
668
4
        }
669
    }
670

            
671
    impl CountdownProvider for StdCountdown {
672
34
        fn has_expired(&self) -> bool {
673
34
            if self.start_time.elapsed() > self.expiry_time {
674
4
                return true;
675
30
            }
676
30
            false
677
34
        }
678

            
679
2
        fn reset(&mut self) {
680
2
            self.start_time = std::time::Instant::now();
681
2
        }
682
    }
683

            
684
    pub struct StdTimerCreator {
685
        pub check_limit_timeout: Duration,
686
    }
687

            
688
    impl StdTimerCreator {
689
42
        pub const fn new(check_limit_timeout: Duration) -> Self {
690
42
            Self {
691
42
                check_limit_timeout,
692
42
            }
693
42
        }
694
    }
695

            
696
    impl Default for StdTimerCreator {
697
16
        fn default() -> Self {
698
16
            Self::new(Duration::from_secs(5))
699
16
        }
700
    }
701

            
702
    impl TimerCreatorProvider for StdTimerCreator {
703
        type Countdown = StdCountdown;
704

            
705
14
        fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown {
706
14
            match timer_context {
707
                TimerContext::CheckLimit {
708
                    local_id: _,
709
                    remote_id: _,
710
                    entity_type: _,
711
12
                } => StdCountdown::new(self.check_limit_timeout),
712
2
                TimerContext::NakActivity { expiry_time } => StdCountdown::new(expiry_time),
713
                TimerContext::PositiveAck { expiry_time } => StdCountdown::new(expiry_time),
714
            }
715
14
        }
716
    }
717
}
718

            
719
/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence
720
/// number of that transfer which is also determined by the CFDP source entity.
721
#[derive(Debug, Eq, Copy, Clone)]
722
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
723
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
724
pub struct TransactionId {
725
    source_id: UnsignedByteField,
726
    seq_num: UnsignedByteField,
727
}
728

            
729
impl TransactionId {
730
76
    pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
731
76
        Self { source_id, seq_num }
732
76
    }
733

            
734
2
    pub fn source_id(&self) -> &UnsignedByteField {
735
2
        &self.source_id
736
2
    }
737

            
738
186
    pub fn seq_num(&self) -> &UnsignedByteField {
739
186
        &self.seq_num
740
186
    }
741
}
742

            
743
impl Hash for TransactionId {
744
2
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
745
2
        self.source_id.value().hash(state);
746
2
        self.seq_num.value().hash(state);
747
2
    }
748
}
749

            
750
impl PartialEq for TransactionId {
751
72
    fn eq(&self, other: &Self) -> bool {
752
72
        self.source_id.value() == other.source_id.value()
753
72
            && self.seq_num.value() == other.seq_num.value()
754
72
    }
755
}
756

            
757
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
758
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
759
pub enum State {
760
    Idle = 0,
761
    Busy = 1,
762
    Suspended = 2,
763
}
764

            
765
/// [crc::Crc] instance using [crc::CRC_32_ISO_HDLC].
766
///
767
/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/4>,
768
/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32>
769
pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
770
/// [crc::Crc] instance using [crc::CRC_32_ISCSI].
771
///
772
/// SANA registry entry: <https://sanaregistry.org/r/checksum_identifiers/records/3>,
773
/// Entry in CRC catalogue: <https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-32-iscsi>
774
pub const CRC_32C: Crc<u32> = Crc::<u32>::new(&CRC_32_ISCSI);
775

            
776
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
777
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
778
pub enum PacketTarget {
779
    SourceEntity,
780
    DestEntity,
781
}
782

            
783
/// Generic trait which models a raw CFDP packet data unit (PDU) block with some additional context
784
/// information.
785
pub trait PduProvider {
786
    fn pdu_type(&self) -> PduType;
787
    fn file_directive_type(&self) -> Option<FileDirectiveType>;
788
    fn pdu(&self) -> &[u8];
789
    fn packet_target(&self) -> Result<PacketTarget, PduError>;
790
}
791

            
792
pub struct DummyPduProvider(());
793

            
794
impl PduProvider for DummyPduProvider {
795
2
    fn pdu_type(&self) -> PduType {
796
2
        PduType::FileData
797
2
    }
798

            
799
2
    fn file_directive_type(&self) -> Option<FileDirectiveType> {
800
2
        None
801
2
    }
802

            
803
2
    fn pdu(&self) -> &[u8] {
804
2
        &[]
805
2
    }
806

            
807
2
    fn packet_target(&self) -> Result<PacketTarget, PduError> {
808
2
        Ok(PacketTarget::SourceEntity)
809
2
    }
810
}
811

            
812
/// This is a helper struct which contains base information about a particular PDU packet.
813
/// This is also necessary information for CFDP packet routing. For example, some packet types
814
/// like file data PDUs can only be used by CFDP source entities.
815
pub struct PduRawWithInfo<'raw_packet> {
816
    pdu_type: PduType,
817
    file_directive_type: Option<FileDirectiveType>,
818
    packet_len: usize,
819
    raw_packet: &'raw_packet [u8],
820
}
821

            
822
120
pub fn determine_packet_target(raw_pdu: &[u8]) -> Result<PacketTarget, PduError> {
823
120
    let (header, header_len) = PduHeader::from_bytes(raw_pdu)?;
824
120
    if header.pdu_type() == PduType::FileData {
825
32
        return Ok(PacketTarget::DestEntity);
826
88
    }
827
88
    let file_directive_type = FileDirectiveType::try_from(raw_pdu[header_len]).map_err(|_| {
828
        PduError::InvalidDirectiveType {
829
            found: raw_pdu[header_len],
830
            expected: None,
831
        }
832
88
    })?;
833
88
    let packet_target =
834
88
        match file_directive_type {
835
            // Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
836
            // the source handler
837
            FileDirectiveType::NakPdu
838
            | FileDirectiveType::FinishedPdu
839
12
            | FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
840
            // Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
841
            // the destination handler
842
            FileDirectiveType::MetadataPdu
843
            | FileDirectiveType::EofPdu
844
76
            | FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
845
            // Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
846
            // extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
847
            // the source handler, for a Finished PDU, it is passed to the destination handler.
848
            FileDirectiveType::AckPdu => {
849
                let acked_directive = FileDirectiveType::try_from(raw_pdu[header_len + 1])
850
                    .map_err(|_| PduError::InvalidDirectiveType {
851
                        found: raw_pdu[header_len],
852
                        expected: None,
853
                    })?;
854
                if acked_directive == FileDirectiveType::EofPdu {
855
                    PacketTarget::SourceEntity
856
                } else if acked_directive == FileDirectiveType::FinishedPdu {
857
                    PacketTarget::DestEntity
858
                } else {
859
                    // TODO: Maybe a better error? This might be confusing..
860
                    return Err(PduError::InvalidDirectiveType {
861
                        found: raw_pdu[header_len + 1],
862
                        expected: None,
863
                    });
864
                }
865
            }
866
        };
867
88
    Ok(packet_target)
868
120
}
869

            
870
impl<'raw> PduRawWithInfo<'raw> {
871
94
    pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
872
94
        let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
873
94
        if pdu_header.pdu_type() == PduType::FileData {
874
24
            return Ok(Self {
875
24
                pdu_type: pdu_header.pdu_type(),
876
24
                file_directive_type: None,
877
24
                packet_len: pdu_header.pdu_len(),
878
24
                raw_packet,
879
24
            });
880
70
        }
881
70
        if pdu_header.pdu_datafield_len() < 1 {
882
            return Err(PduError::FormatError);
883
70
        }
884
        // Route depending on PDU type and directive type if applicable. Retrieve directive type
885
        // from the raw stream for better performance (with sanity and directive code check).
886
        // The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding
887
        // procedure.
888
70
        let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
889
            PduError::InvalidDirectiveType {
890
                found: raw_packet[header_len],
891
                expected: None,
892
            }
893
70
        })?;
894
70
        Ok(Self {
895
70
            pdu_type: pdu_header.pdu_type(),
896
70
            file_directive_type: Some(directive),
897
70
            packet_len: pdu_header.pdu_len(),
898
70
            raw_packet,
899
70
        })
900
94
    }
901

            
902
6
    pub fn raw_packet(&self) -> &[u8] {
903
6
        &self.raw_packet[0..self.packet_len]
904
6
    }
905
}
906

            
907
impl PduProvider for PduRawWithInfo<'_> {
908
94
    fn pdu_type(&self) -> PduType {
909
94
        self.pdu_type
910
94
    }
911

            
912
134
    fn file_directive_type(&self) -> Option<FileDirectiveType> {
913
134
        self.file_directive_type
914
134
    }
915

            
916
86
    fn pdu(&self) -> &[u8] {
917
86
        self.raw_packet
918
86
    }
919

            
920
92
    fn packet_target(&self) -> Result<PacketTarget, PduError> {
921
92
        determine_packet_target(self.raw_packet)
922
92
    }
923
}
924

            
925
#[cfg(feature = "alloc")]
926
pub mod alloc_mod {
927
    use spacepackets::cfdp::{
928
        pdu::{FileDirectiveType, PduError},
929
        PduType,
930
    };
931

            
932
    use crate::{determine_packet_target, PacketTarget, PduProvider, PduRawWithInfo};
933

            
934
    #[derive(Debug, PartialEq, Eq, Clone)]
935
    pub struct PduOwnedWithInfo {
936
        pub pdu_type: PduType,
937
        pub file_directive_type: Option<FileDirectiveType>,
938
        pub pdu: alloc::vec::Vec<u8>,
939
    }
940

            
941
    impl PduOwnedWithInfo {
942
        pub fn new_from_raw_packet(raw_packet: &[u8]) -> Result<Self, PduError> {
943
            Ok(PduRawWithInfo::new(raw_packet)?.into())
944
        }
945

            
946
28
        pub fn new(
947
28
            pdu_type: PduType,
948
28
            file_directive_type: Option<FileDirectiveType>,
949
28
            pdu: alloc::vec::Vec<u8>,
950
28
        ) -> Self {
951
28
            Self {
952
28
                pdu_type,
953
28
                file_directive_type,
954
28
                pdu,
955
28
            }
956
28
        }
957
    }
958

            
959
    impl From<PduRawWithInfo<'_>> for PduOwnedWithInfo {
960
        fn from(value: PduRawWithInfo) -> Self {
961
            Self::new(
962
                value.pdu_type(),
963
                value.file_directive_type(),
964
                value.raw_packet().to_vec(),
965
            )
966
        }
967
    }
968

            
969
    impl PduProvider for PduOwnedWithInfo {
970
28
        fn pdu_type(&self) -> PduType {
971
28
            self.pdu_type
972
28
        }
973

            
974
36
        fn file_directive_type(&self) -> Option<FileDirectiveType> {
975
36
            self.file_directive_type
976
36
        }
977

            
978
28
        fn pdu(&self) -> &[u8] {
979
28
            &self.pdu
980
28
        }
981

            
982
28
        fn packet_target(&self) -> Result<PacketTarget, PduError> {
983
28
            determine_packet_target(&self.pdu)
984
28
        }
985
    }
986
}
987

            
988
#[cfg(test)]
989
pub(crate) mod tests {
990
    use core::cell::RefCell;
991

            
992
    use alloc::{collections::VecDeque, string::String, vec::Vec};
993
    use spacepackets::{
994
        cfdp::{
995
            lv::Lv,
996
            pdu::{
997
                eof::EofPdu,
998
                file_data::FileDataPdu,
999
                metadata::{MetadataGenericParams, MetadataPduCreator},
                CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket,
            },
            ChecksumType, ConditionCode, PduType, TransmissionMode,
        },
        util::{UnsignedByteField, UnsignedByteFieldU16, UnsignedByteFieldU8, UnsignedEnum},
    };
    use user::{CfdpUser, OwnedMetadataRecvdParams, TransactionFinishedParams};
    use crate::{PacketTarget, StdCountdown};
    use super::*;
    pub const LOCAL_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(1);
    pub const REMOTE_ID: UnsignedByteFieldU16 = UnsignedByteFieldU16::new(2);
    pub struct FileSegmentRecvdParamsNoSegMetadata {
        #[allow(dead_code)]
        pub id: TransactionId,
        pub offset: u64,
        pub length: usize,
    }
    #[derive(Default)]
    pub struct TestCfdpUser {
        pub next_expected_seq_num: u64,
        pub expected_full_src_name: String,
        pub expected_full_dest_name: String,
        pub expected_file_size: u64,
        pub transaction_indication_call_count: u32,
        pub eof_sent_call_count: u32,
        pub eof_recvd_call_count: u32,
        pub finished_indic_queue: VecDeque<TransactionFinishedParams>,
        pub metadata_recv_queue: VecDeque<OwnedMetadataRecvdParams>,
        pub file_seg_recvd_queue: VecDeque<FileSegmentRecvdParamsNoSegMetadata>,
    }
    impl TestCfdpUser {
46
        pub fn new(
46
            next_expected_seq_num: u64,
46
            expected_full_src_name: String,
46
            expected_full_dest_name: String,
46
            expected_file_size: u64,
46
        ) -> Self {
46
            Self {
46
                next_expected_seq_num,
46
                expected_full_src_name,
46
                expected_full_dest_name,
46
                expected_file_size,
46
                transaction_indication_call_count: 0,
46
                eof_recvd_call_count: 0,
46
                eof_sent_call_count: 0,
46
                finished_indic_queue: VecDeque::new(),
46
                metadata_recv_queue: VecDeque::new(),
46
                file_seg_recvd_queue: VecDeque::new(),
46
            }
46
        }
158
        pub fn generic_id_check(&self, id: &crate::TransactionId) {
158
            assert_eq!(id.source_id, LOCAL_ID.into());
158
            assert_eq!(id.seq_num().value(), self.next_expected_seq_num);
158
        }
    }
    impl CfdpUser for TestCfdpUser {
16
        fn transaction_indication(&mut self, id: &crate::TransactionId) {
16
            self.generic_id_check(id);
16
            self.transaction_indication_call_count += 1;
16
        }
20
        fn eof_sent_indication(&mut self, id: &crate::TransactionId) {
20
            self.generic_id_check(id);
20
            self.eof_sent_call_count += 1;
20
        }
44
        fn transaction_finished_indication(
44
            &mut self,
44
            finished_params: &crate::user::TransactionFinishedParams,
44
        ) {
44
            self.generic_id_check(&finished_params.id);
44
            self.finished_indic_queue.push_back(*finished_params);
44
        }
34
        fn metadata_recvd_indication(
34
            &mut self,
34
            md_recvd_params: &crate::user::MetadataReceivedParams,
34
        ) {
34
            self.generic_id_check(&md_recvd_params.id);
34
            assert_eq!(
34
                String::from(md_recvd_params.src_file_name),
34
                self.expected_full_src_name
34
            );
34
            assert_eq!(
34
                String::from(md_recvd_params.dest_file_name),
34
                self.expected_full_dest_name
34
            );
34
            assert_eq!(md_recvd_params.msgs_to_user.len(), 0);
34
            assert_eq!(md_recvd_params.source_id, LOCAL_ID.into());
34
            assert_eq!(md_recvd_params.file_size, self.expected_file_size);
34
            self.metadata_recv_queue.push_back(md_recvd_params.into());
34
        }
22
        fn file_segment_recvd_indication(
22
            &mut self,
22
            segment_recvd_params: &crate::user::FileSegmentRecvdParams,
22
        ) {
22
            self.generic_id_check(&segment_recvd_params.id);
22
            self.file_seg_recvd_queue
22
                .push_back(FileSegmentRecvdParamsNoSegMetadata {
22
                    id: segment_recvd_params.id,
22
                    offset: segment_recvd_params.offset,
22
                    length: segment_recvd_params.length,
22
                })
22
        }
        fn report_indication(&mut self, _id: &crate::TransactionId) {}
        fn suspended_indication(
            &mut self,
            _id: &crate::TransactionId,
            _condition_code: ConditionCode,
        ) {
            panic!("unexpected suspended indication");
        }
        fn resumed_indication(&mut self, _id: &crate::TransactionId, _progresss: u64) {}
        fn fault_indication(
            &mut self,
            _id: &crate::TransactionId,
            _condition_code: ConditionCode,
            _progress: u64,
        ) {
            panic!("unexpected fault indication");
        }
        fn abandoned_indication(
            &mut self,
            _id: &crate::TransactionId,
            _condition_code: ConditionCode,
            _progress: u64,
        ) {
            panic!("unexpected abandoned indication");
        }
22
        fn eof_recvd_indication(&mut self, id: &crate::TransactionId) {
22
            self.generic_id_check(id);
22
            self.eof_recvd_call_count += 1;
22
        }
    }
    #[derive(Default, Debug)]
    pub(crate) struct TestFaultHandler {
        pub notice_of_suspension_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
        pub notice_of_cancellation_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
        pub abandoned_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
        pub ignored_queue: VecDeque<(TransactionId, ConditionCode, u64)>,
    }
    impl UserFaultHookProvider for TestFaultHandler {
        fn notice_of_suspension_cb(
            &mut self,
            transaction_id: TransactionId,
            cond: ConditionCode,
            progress: u64,
        ) {
            self.notice_of_suspension_queue
                .push_back((transaction_id, cond, progress))
        }
6
        fn notice_of_cancellation_cb(
6
            &mut self,
6
            transaction_id: TransactionId,
6
            cond: ConditionCode,
6
            progress: u64,
6
        ) {
6
            self.notice_of_cancellation_queue
6
                .push_back((transaction_id, cond, progress))
6
        }
        fn abandoned_cb(
            &mut self,
            transaction_id: TransactionId,
            cond: ConditionCode,
            progress: u64,
        ) {
            self.abandoned_queue
                .push_back((transaction_id, cond, progress))
        }
6
        fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64) {
6
            self.ignored_queue
6
                .push_back((transaction_id, cond, progress))
6
        }
    }
    impl TestFaultHandler {
80
        pub(crate) fn suspension_queue_empty(&self) -> bool {
80
            self.notice_of_suspension_queue.is_empty()
80
        }
82
        pub(crate) fn cancellation_queue_empty(&self) -> bool {
82
            self.notice_of_cancellation_queue.is_empty()
82
        }
80
        pub(crate) fn ignored_queue_empty(&self) -> bool {
80
            self.ignored_queue.is_empty()
80
        }
80
        pub(crate) fn abandoned_queue_empty(&self) -> bool {
80
            self.abandoned_queue.is_empty()
80
        }
80
        pub(crate) fn all_queues_empty(&self) -> bool {
80
            self.suspension_queue_empty()
80
                && self.cancellation_queue_empty()
80
                && self.ignored_queue_empty()
80
                && self.abandoned_queue_empty()
80
        }
    }
    pub struct SentPdu {
        pub pdu_type: PduType,
        pub file_directive_type: Option<FileDirectiveType>,
        pub raw_pdu: Vec<u8>,
    }
    #[derive(Default)]
    pub struct TestCfdpSender {
        pub packet_queue: RefCell<VecDeque<SentPdu>>,
    }
    impl PduSendProvider for TestCfdpSender {
60
        fn send_pdu(
60
            &self,
60
            pdu_type: PduType,
60
            file_directive_type: Option<FileDirectiveType>,
60
            raw_pdu: &[u8],
60
        ) -> Result<(), GenericSendError> {
60
            self.packet_queue.borrow_mut().push_back(SentPdu {
60
                pdu_type,
60
                file_directive_type,
60
                raw_pdu: raw_pdu.to_vec(),
60
            });
60
            Ok(())
60
        }
    }
    impl TestCfdpSender {
62
        pub fn retrieve_next_pdu(&self) -> Option<SentPdu> {
62
            self.packet_queue.borrow_mut().pop_front()
62
        }
78
        pub fn queue_empty(&self) -> bool {
78
            self.packet_queue.borrow_mut().is_empty()
78
        }
    }
64
    pub fn basic_remote_cfg_table(
64
        dest_id: impl Into<UnsignedByteField>,
64
        max_packet_len: usize,
64
        crc_on_transmission_by_default: bool,
64
    ) -> StdRemoteEntityConfigProvider {
64
        let mut table = StdRemoteEntityConfigProvider::default();
64
        let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
64
            dest_id.into(),
64
            max_packet_len,
64
            true,
64
            crc_on_transmission_by_default,
64
            TransmissionMode::Unacknowledged,
64
            ChecksumType::Crc32,
64
        );
64
        table.add_config(&remote_entity_cfg);
64
        table
64
    }
6
    fn generic_pdu_header() -> PduHeader {
6
        let pdu_conf = CommonPduConfig::default();
6
        PduHeader::new_no_file_data(pdu_conf, 0)
6
    }
    #[test]
2
    fn test_transaction_id() {
2
        let transaction_id = TransactionId::new(
2
            UnsignedByteFieldU16::new(1).into(),
2
            UnsignedByteFieldU16::new(2).into(),
2
        );
2
        assert_eq!(transaction_id.source_id().value(), 1);
2
        assert_eq!(transaction_id.seq_num().value(), 2);
2
    }
    #[test]
2
    fn test_metadata_pdu_info() {
2
        let mut buf: [u8; 128] = [0; 128];
2
        let pdu_header = generic_pdu_header();
2
        let metadata_params = MetadataGenericParams::default();
2
        let src_file_name = "hello.txt";
2
        let dest_file_name = "hello-dest.txt";
2
        let src_lv = Lv::new_from_str(src_file_name).unwrap();
2
        let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
2
        let metadata_pdu =
2
            MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv);
2
        metadata_pdu
2
            .write_to_bytes(&mut buf)
2
            .expect("writing metadata PDU failed");
2

            
2
        let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
2
        assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
2
        assert!(packet_info.file_directive_type().is_some());
2
        assert_eq!(
2
            packet_info.file_directive_type().unwrap(),
2
            FileDirectiveType::MetadataPdu
2
        );
2
        assert_eq!(
2
            packet_info.raw_packet(),
2
            &buf[0..metadata_pdu.len_written()]
2
        );
2
        assert_eq!(
2
            packet_info.packet_target().unwrap(),
2
            PacketTarget::DestEntity
2
        );
2
    }
    #[test]
2
    fn test_filedata_pdu_info() {
2
        let mut buf: [u8; 128] = [0; 128];
2
        let pdu_header = generic_pdu_header();
2
        let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
2
        file_data_pdu
2
            .write_to_bytes(&mut buf)
2
            .expect("writing file data PDU failed");
2
        let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
2
        assert_eq!(
2
            packet_info.raw_packet(),
2
            &buf[0..file_data_pdu.len_written()]
2
        );
2
        assert_eq!(packet_info.pdu_type(), PduType::FileData);
2
        assert!(packet_info.file_directive_type().is_none());
2
        assert_eq!(
2
            packet_info.packet_target().unwrap(),
2
            PacketTarget::DestEntity
2
        );
2
    }
    #[test]
2
    fn test_eof_pdu_info() {
2
        let mut buf: [u8; 128] = [0; 128];
2
        let pdu_header = generic_pdu_header();
2
        let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
2
        eof_pdu
2
            .write_to_bytes(&mut buf)
2
            .expect("writing file data PDU failed");
2
        let packet_info = PduRawWithInfo::new(&buf).expect("creating packet info failed");
2
        assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
2
        assert!(packet_info.file_directive_type().is_some());
2
        assert_eq!(packet_info.raw_packet(), &buf[0..eof_pdu.len_written()]);
2
        assert_eq!(
2
            packet_info.file_directive_type().unwrap(),
2
            FileDirectiveType::EofPdu
2
        );
2
    }
    #[test]
2
    fn test_std_check_timer() {
2
        let mut std_check_timer = StdCountdown::new(Duration::from_secs(1));
2
        assert!(!std_check_timer.has_expired());
2
        assert_eq!(std_check_timer.expiry_time_seconds(), 1);
2
        std::thread::sleep(Duration::from_millis(800));
2
        assert!(!std_check_timer.has_expired());
2
        std::thread::sleep(Duration::from_millis(205));
2
        assert!(std_check_timer.has_expired());
2
        std_check_timer.reset();
2
        assert!(!std_check_timer.has_expired());
2
    }
    #[test]
2
    fn test_std_check_timer_creator() {
2
        let std_check_timer_creator = StdTimerCreator::new(Duration::from_secs(1));
2
        let check_timer = std_check_timer_creator.create_countdown(TimerContext::NakActivity {
2
            expiry_time: Duration::from_secs(1),
2
        });
2
        assert_eq!(check_timer.expiry_time_seconds(), 1);
2
    }
    #[test]
2
    fn test_remote_cfg_provider_single() {
2
        let mut remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
2
            REMOTE_ID.into(),
2
            1024,
2
            true,
2
            false,
2
            TransmissionMode::Unacknowledged,
2
            ChecksumType::Crc32,
2
        );
2
        let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
2
        assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
2
        assert_eq!(remote_entity_retrieved.max_packet_len, 1024);
2
        assert!(remote_entity_retrieved.closure_requested_by_default);
2
        assert!(!remote_entity_retrieved.crc_on_transmission_by_default);
2
        assert_eq!(
2
            remote_entity_retrieved.default_crc_type,
2
            ChecksumType::Crc32
2
        );
2
        let remote_entity_mut = remote_entity_cfg.get_mut(REMOTE_ID.value()).unwrap();
2
        assert_eq!(remote_entity_mut.entity_id, REMOTE_ID.into());
2
        let dummy = RemoteEntityConfig::new_with_default_values(
2
            LOCAL_ID.into(),
2
            1024,
2
            true,
2
            false,
2
            TransmissionMode::Unacknowledged,
2
            ChecksumType::Crc32,
2
        );
2
        assert!(!remote_entity_cfg.add_config(&dummy));
        // Removal is no-op.
2
        assert!(!remote_entity_cfg.remove_config(REMOTE_ID.value()));
2
        let remote_entity_retrieved = remote_entity_cfg.get(REMOTE_ID.value()).unwrap();
2
        assert_eq!(remote_entity_retrieved.entity_id, REMOTE_ID.into());
        // Does not exist.
2
        assert!(remote_entity_cfg.get(LOCAL_ID.value()).is_none());
2
        assert!(remote_entity_cfg.get_mut(LOCAL_ID.value()).is_none());
2
    }
    #[test]
2
    fn test_remote_cfg_provider_std() {
2
        let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
2
            REMOTE_ID.into(),
2
            1024,
2
            true,
2
            false,
2
            TransmissionMode::Unacknowledged,
2
            ChecksumType::Crc32,
2
        );
2
        let mut remote_cfg_provider = StdRemoteEntityConfigProvider::default();
2
        assert!(remote_cfg_provider.0.is_empty());
2
        remote_cfg_provider.add_config(&remote_entity_cfg);
2
        assert_eq!(remote_cfg_provider.0.len(), 1);
2
        let remote_entity_cfg_2 = RemoteEntityConfig::new_with_default_values(
2
            LOCAL_ID.into(),
2
            1024,
2
            true,
2
            false,
2
            TransmissionMode::Unacknowledged,
2
            ChecksumType::Crc32,
2
        );
2
        let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
2
        assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
2
        remote_cfg_provider.add_config(&remote_entity_cfg_2);
2
        assert_eq!(remote_cfg_provider.0.len(), 2);
2
        let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
2
        assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
2
        assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
2
        assert_eq!(remote_cfg_provider.0.len(), 1);
2
        let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
2
        cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
2
        assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
2
        assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
2
    }
    #[test]
2
    fn test_remote_cfg_provider_vector() {
2
        let mut remote_cfg_provider = VecRemoteEntityConfigProvider::default();
2
        let remote_entity_cfg = RemoteEntityConfig::new_with_default_values(
2
            REMOTE_ID.into(),
2
            1024,
2
            true,
2
            false,
2
            TransmissionMode::Unacknowledged,
2
            ChecksumType::Crc32,
2
        );
2
        assert!(remote_cfg_provider.0.is_empty());
2
        remote_cfg_provider.add_config(&remote_entity_cfg);
2
        assert_eq!(remote_cfg_provider.0.len(), 1);
2
        let remote_entity_cfg_2 = RemoteEntityConfig::new_with_default_values(
2
            LOCAL_ID.into(),
2
            1024,
2
            true,
2
            false,
2
            TransmissionMode::Unacknowledged,
2
            ChecksumType::Crc32,
2
        );
2
        let cfg_0 = remote_cfg_provider.get(REMOTE_ID.value()).unwrap();
2
        assert_eq!(cfg_0.entity_id, REMOTE_ID.into());
2
        remote_cfg_provider.add_config(&remote_entity_cfg_2);
2
        assert_eq!(remote_cfg_provider.0.len(), 2);
2
        let cfg_1 = remote_cfg_provider.get(LOCAL_ID.value()).unwrap();
2
        assert_eq!(cfg_1.entity_id, LOCAL_ID.into());
2
        assert!(remote_cfg_provider.remove_config(REMOTE_ID.value()));
2
        assert_eq!(remote_cfg_provider.0.len(), 1);
2
        let cfg_1_mut = remote_cfg_provider.get_mut(LOCAL_ID.value()).unwrap();
2
        cfg_1_mut.default_crc_type = ChecksumType::Crc32C;
2
        assert!(!remote_cfg_provider.remove_config(REMOTE_ID.value()));
2
        assert!(remote_cfg_provider.get_mut(REMOTE_ID.value()).is_none());
2
    }
    #[test]
2
    fn dummy_fault_hook_test() {
2
        let mut user_hook_dummy = DummyFaultHook::default();
2
        let transaction_id = TransactionId::new(
2
            UnsignedByteFieldU8::new(0).into(),
2
            UnsignedByteFieldU8::new(0).into(),
2
        );
2
        user_hook_dummy.notice_of_cancellation_cb(transaction_id, ConditionCode::NoError, 0);
2
        user_hook_dummy.notice_of_suspension_cb(transaction_id, ConditionCode::NoError, 0);
2
        user_hook_dummy.abandoned_cb(transaction_id, ConditionCode::NoError, 0);
2
        user_hook_dummy.ignore_cb(transaction_id, ConditionCode::NoError, 0);
2
    }
    #[test]
2
    fn dummy_pdu_provider_test() {
2
        let dummy_pdu_provider = DummyPduProvider(());
2
        assert_eq!(dummy_pdu_provider.pdu_type(), PduType::FileData);
2
        assert!(dummy_pdu_provider.file_directive_type().is_none());
2
        assert_eq!(dummy_pdu_provider.pdu(), &[]);
2
        assert_eq!(
2
            dummy_pdu_provider.packet_target(),
2
            Ok(PacketTarget::SourceEntity)
2
        );
2
    }
    #[test]
2
    fn test_fault_handler_checksum_error_ignored_by_default() {
2
        let fault_handler = FaultHandler::new(TestFaultHandler::default());
2
        assert_eq!(
2
            fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
2
            FaultHandlerCode::IgnoreError
2
        );
2
    }
    #[test]
2
    fn test_fault_handler_unsupported_checksum_ignored_by_default() {
2
        let fault_handler = FaultHandler::new(TestFaultHandler::default());
2
        assert_eq!(
2
            fault_handler.get_fault_handler(ConditionCode::UnsupportedChecksumType),
2
            FaultHandlerCode::IgnoreError
2
        );
2
    }
    #[test]
2
    fn test_fault_handler_basic() {
2
        let mut fault_handler = FaultHandler::new(TestFaultHandler::default());
2
        assert_eq!(
2
            fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
2
            FaultHandlerCode::IgnoreError
2
        );
2
        fault_handler.set_fault_handler(
2
            ConditionCode::FileChecksumFailure,
2
            FaultHandlerCode::NoticeOfCancellation,
2
        );
2
        assert_eq!(
2
            fault_handler.get_fault_handler(ConditionCode::FileChecksumFailure),
2
            FaultHandlerCode::NoticeOfCancellation
2
        );
2
    }
    #[test]
2
    fn transaction_id_hashable_usable_as_map_key() {
2
        let mut map = HashMap::new();
2
        let transaction_id_0 = TransactionId::new(
2
            UnsignedByteFieldU8::new(1).into(),
2
            UnsignedByteFieldU8::new(2).into(),
2
        );
2
        map.insert(transaction_id_0, 5_u32);
2
    }
}