1
//! # CFDP Destination Entity Module
2
//!
3
//! The [DestinationHandler] is the primary component of this module which converts the PDUs sent
4
//! from a remote source entity back to a file. A file copy operation on the receiver side
5
//! is started with the reception of a Metadata PDU, for example one generated by the
6
//! [spacepackets::cfdp::pdu::metadata::MetadataPduCreator]. After that, file packet PDUs, for
7
//! example generated with the [spacepackets::cfdp::pdu::file_data] module, can be inserted into
8
//! the destination handler and will be assembled into a file.
9
//!
10
//! A destination entity might still generate packets which need to be sent back to the source
11
//! entity of the file transfer. However, this handler allows freedom of communication like the
12
//! source entity by using a user-provided [PduSendProvider] to send all generated PDUs.
13
//!
14
//! The transaction will be finished when following conditions are met:
15
//!
16
//!  1. A valid EOF PDU, for example one generated by the [spacepackets::cfdp::pdu::eof::EofPdu]
17
//!     helper, has been inserted into the class.
18
//!  2. The file checksum verification has been successful. If this is not the case for the
19
//!     unacknowledged mode, the handler will re-attempt the checksum calculation up to a certain
20
//!     threshold called the check limit. If the threshold is reached, the transaction will
21
//!     finish with a failure.
22
//!
23
//! ### Unacknowledged mode with closure
24
//!
25
//!  3. Finished PDU has been sent back to the remote side.
26
//!
27
//! ### Acknowledged mode (*not implemented yet*)
28
//!
29
//!  3. An EOF ACK PDU has been sent back to the remote side.
30
//!  4. A Finished PDU has been sent back to the remote side.
31
//!  5. A Finished PDU ACK was received.
32
use crate::{user::TransactionFinishedParams, DummyPduProvider, GenericSendError, PduProvider};
33
use core::str::{from_utf8, from_utf8_unchecked, Utf8Error};
34

            
35
use super::{
36
    filestore::{FilestoreError, NativeFilestore, VirtualFilestore},
37
    user::{CfdpUser, FileSegmentRecvdParams, MetadataReceivedParams},
38
    CountdownProvider, EntityType, LocalEntityConfig, PacketTarget, PduSendProvider,
39
    RemoteEntityConfig, RemoteEntityConfigProvider, State, StdCountdown,
40
    StdRemoteEntityConfigProvider, StdTimerCreator, TimerContext, TimerCreatorProvider,
41
    TransactionId, UserFaultHookProvider,
42
};
43
use smallvec::SmallVec;
44
use spacepackets::{
45
    cfdp::{
46
        pdu::{
47
            eof::EofPdu,
48
            file_data::FileDataPdu,
49
            finished::{DeliveryCode, FileStatus, FinishedPduCreator},
50
            metadata::{MetadataGenericParams, MetadataPduReader},
51
            CfdpPdu, CommonPduConfig, FileDirectiveType, PduError, PduHeader, WritablePduPacket,
52
        },
53
        tlv::{msg_to_user::MsgToUserTlv, EntityIdTlv, GenericTlv, ReadableTlv, TlvType},
54
        ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
55
    },
56
    util::{UnsignedByteField, UnsignedEnum},
57
};
58
use thiserror::Error;
59

            
60
#[derive(Debug)]
61
struct FileProperties {
62
    src_file_name: [u8; u8::MAX as usize],
63
    src_file_name_len: usize,
64
    dest_file_name: [u8; u8::MAX as usize],
65
    dest_file_name_len: usize,
66
    dest_path_buf: [u8; u8::MAX as usize * 2],
67
    dest_file_path_len: usize,
68
}
69

            
70
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
71
enum CompletionDisposition {
72
    Completed = 0,
73
    Cancelled = 1,
74
}
75

            
76
/// This enumeration models the different transaction steps of the destination entity handler.
77
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
78
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
80
pub enum TransactionStep {
81
    Idle = 0,
82
    TransactionStart = 1,
83
    ReceivingFileDataPdus = 2,
84
    ReceivingFileDataPdusWithCheckLimitHandling = 3,
85
    SendingAckPdu = 4,
86
    TransferCompletion = 5,
87
    SendingFinishedPdu = 6,
88
}
89

            
90
// This contains transfer state parameters for destination transaction.
91
#[derive(Debug)]
92
struct TransferState<Countdown: CountdownProvider> {
93
    transaction_id: Option<TransactionId>,
94
    metadata_params: MetadataGenericParams,
95
    progress: u64,
96
    // file_size_eof: u64,
97
    metadata_only: bool,
98
    condition_code: ConditionCode,
99
    delivery_code: DeliveryCode,
100
    fault_location_finished: Option<EntityIdTlv>,
101
    file_status: FileStatus,
102
    completion_disposition: CompletionDisposition,
103
    checksum: u32,
104
    current_check_count: u32,
105
    current_check_timer: Option<Countdown>,
106
}
107

            
108
impl<CheckTimer: CountdownProvider> Default for TransferState<CheckTimer> {
109
44
    fn default() -> Self {
110
44
        Self {
111
44
            transaction_id: None,
112
44
            metadata_params: Default::default(),
113
44
            progress: Default::default(),
114
44
            // file_size_eof: Default::default(),
115
44
            metadata_only: false,
116
44
            condition_code: ConditionCode::NoError,
117
44
            delivery_code: DeliveryCode::Incomplete,
118
44
            fault_location_finished: None,
119
44
            file_status: FileStatus::Unreported,
120
44
            completion_disposition: CompletionDisposition::Completed,
121
44
            checksum: 0,
122
44
            current_check_count: 0,
123
44
            current_check_timer: None,
124
44
        }
125
44
    }
126
}
127

            
128
// This contains parameters for destination transaction.
129
#[derive(Debug)]
130
struct TransactionParams<Countdown: CountdownProvider> {
131
    tstate: TransferState<Countdown>,
132
    pdu_conf: CommonPduConfig,
133
    file_properties: FileProperties,
134
    cksum_buf: [u8; 1024],
135
    msgs_to_user_size: usize,
136
    // TODO: Should we make this configurable?
137
    msgs_to_user_buf: [u8; 1024],
138
    remote_cfg: Option<RemoteEntityConfig>,
139
}
140

            
141
impl<CheckTimer: CountdownProvider> TransactionParams<CheckTimer> {
142
116
    fn transmission_mode(&self) -> TransmissionMode {
143
116
        self.pdu_conf.trans_mode
144
116
    }
145
}
146

            
147
impl Default for FileProperties {
148
48
    fn default() -> Self {
149
48
        Self {
150
48
            src_file_name: [0; u8::MAX as usize],
151
48
            src_file_name_len: Default::default(),
152
48
            dest_file_name: [0; u8::MAX as usize],
153
48
            dest_file_name_len: Default::default(),
154
48
            dest_path_buf: [0; u8::MAX as usize * 2],
155
48
            dest_file_path_len: Default::default(),
156
48
        }
157
48
    }
158
}
159

            
160
impl<CheckTimer: CountdownProvider> TransactionParams<CheckTimer> {
161
38
    fn file_size(&self) -> u64 {
162
38
        self.tstate.metadata_params.file_size
163
38
    }
164

            
165
90
    fn metadata_params(&self) -> &MetadataGenericParams {
166
90
        &self.tstate.metadata_params
167
90
    }
168
}
169

            
170
impl<CheckTimer: CountdownProvider> Default for TransactionParams<CheckTimer> {
171
44
    fn default() -> Self {
172
44
        Self {
173
44
            pdu_conf: Default::default(),
174
44
            cksum_buf: [0; 1024],
175
44
            msgs_to_user_size: 0,
176
44
            msgs_to_user_buf: [0; 1024],
177
44
            tstate: Default::default(),
178
44
            file_properties: Default::default(),
179
44
            remote_cfg: None,
180
44
        }
181
44
    }
182
}
183

            
184
impl<CheckTimer: CountdownProvider> TransactionParams<CheckTimer> {
185
74
    fn reset(&mut self) {
186
74
        self.tstate.condition_code = ConditionCode::NoError;
187
74
        self.tstate.delivery_code = DeliveryCode::Incomplete;
188
74
        self.tstate.file_status = FileStatus::Unreported;
189
74
    }
190
}
191

            
192
#[derive(Debug, Error)]
193
pub enum DestError {
194
    /// File directive expected, but none specified
195
    #[error("expected file directive")]
196
    DirectiveFieldEmpty,
197
    #[error("can not process packet type {pdu_type:?} with directive type {directive_type:?}")]
198
    CantProcessPacketType {
199
        pdu_type: PduType,
200
        directive_type: Option<FileDirectiveType>,
201
    },
202
    #[error("can not process file data PDUs in current state")]
203
    WrongStateForFileData,
204
    #[error("can not process EOF PDUs in current state")]
205
    WrongStateForEof,
206
    // Received new metadata PDU while being already being busy with a file transfer.
207
    #[error("busy with transfer")]
208
    RecvdMetadataButIsBusy,
209
    #[error("empty source file field")]
210
    EmptySrcFileField,
211
    #[error("empty dest file field")]
212
    EmptyDestFileField,
213
    #[error("packets to be sent are still left")]
214
    PacketToSendLeft,
215
    #[error("pdu error {0}")]
216
    Pdu(#[from] PduError),
217
    #[error("io error {0}")]
218
    Io(#[from] std::io::Error),
219
    #[error("file store error {0}")]
220
    Filestore(#[from] FilestoreError),
221
    #[error("path conversion error {0}")]
222
    PathConversion(#[from] Utf8Error),
223
    #[error("error building dest path from source file name and dest folder")]
224
    PathConcat,
225
    #[error("no remote entity configuration found for {0:?}")]
226
    NoRemoteCfgFound(UnsignedByteField),
227
    #[error("issue sending PDU: {0}")]
228
    SendError(#[from] GenericSendError),
229
    #[error("cfdp feature not implemented")]
230
    NotImplemented,
231
}
232

            
233
/// This is the primary CFDP destination handler. It models the CFDP destination entity, which is
234
/// primarily responsible for receiving files sent from another CFDP entity. It performs the
235
/// reception side of File Copy Operations.
236
///
237
/// The [DestinationHandler::state_machine] function is the primary function to drive the
238
/// destination handler. It can be used to insert packets into the destination
239
/// handler and driving the state machine, which might generate new packets to be sent to the
240
/// remote entity. Please note that the destination handler can also only process Metadata, EOF and
241
/// Prompt PDUs in addition to ACK PDUs where the acknowledged PDU is the Finished PDU.
242
/// All generated packets are sent using the user provided [PduSendProvider].
243
///
244
/// The handler requires the [alloc] feature but will allocated all required memory on construction
245
/// time. This means that the handler is still suitable for embedded systems where run-time
246
/// allocation is prohibited. Furthermore, it uses the [VirtualFilestore] abstraction to allow
247
/// usage on systems without a [std] filesystem.
248
///
249
/// This handler is able to deal with file copy operations to directories, similarly to how the
250
/// UNIX tool `cp` works. If the destination path is a directory instead of a regular  full path,
251
/// the source path base file name will be appended to the destination path to form the resulting
252
/// new full path.
253
///
254
// This handler also does not support concurrency out of the box but is flexible enough to be used
255
/// in different concurrent contexts. For example, you can dynamically create new handlers and
256
/// run them inside a thread pool, or move the newly created handler to a new thread."""
257
pub struct DestinationHandler<
258
    PduSender: PduSendProvider,
259
    UserFaultHook: UserFaultHookProvider,
260
    Vfs: VirtualFilestore,
261
    RemoteCfgTable: RemoteEntityConfigProvider,
262
    CheckTimerCreator: TimerCreatorProvider<Countdown = CheckTimerProvider>,
263
    CheckTimerProvider: CountdownProvider,
264
> {
265
    local_cfg: LocalEntityConfig<UserFaultHook>,
266
    step: TransactionStep,
267
    state: State,
268
    tparams: TransactionParams<CheckTimerProvider>,
269
    packet_buf: alloc::vec::Vec<u8>,
270
    pub pdu_sender: PduSender,
271
    pub vfs: Vfs,
272
    pub remote_cfg_table: RemoteCfgTable,
273
    pub check_timer_creator: CheckTimerCreator,
274
}
275

            
276
#[cfg(feature = "std")]
277
pub type StdDestinationHandler<PduSender, UserFaultHook> = DestinationHandler<
278
    PduSender,
279
    UserFaultHook,
280
    NativeFilestore,
281
    StdRemoteEntityConfigProvider,
282
    StdTimerCreator,
283
    StdCountdown,
284
>;
285

            
286
impl<
287
        PduSender: PduSendProvider,
288
        UserFaultHook: UserFaultHookProvider,
289
        Vfs: VirtualFilestore,
290
        RemoteCfgTable: RemoteEntityConfigProvider,
291
        TimerCreator: TimerCreatorProvider<Countdown = Countdown>,
292
        Countdown: CountdownProvider,
293
    > DestinationHandler<PduSender, UserFaultHook, Vfs, RemoteCfgTable, TimerCreator, Countdown>
294
{
295
    /// Constructs a new destination handler.
296
    ///
297
    /// # Arguments
298
    ///
299
    /// * `local_cfg` - The local CFDP entity configuration.
300
    /// * `max_packet_len` - The maximum expected generated packet size in bytes. Each time a
301
    ///    packet is sent, it will be buffered inside an internal buffer. The length of this buffer
302
    ///    will be determined by this parameter. This parameter can either be a known upper bound,
303
    ///    or it can specifically be determined by the largest packet size parameter of all remote
304
    ///    entity configurations in the passed `remote_cfg_table`.
305
    /// * `pdu_sender` - [PduSendProvider] used to send generated PDU packets.
306
    /// * `vfs` - [VirtualFilestore] implementation used by the handler, which decouples the CFDP
307
    ///    implementation from the underlying filestore/filesystem. This allows to use this handler
308
    ///    for embedded systems where a standard runtime might not be available.
309
    /// * `remote_cfg_table` - The [RemoteEntityConfigProvider] used to look up remote
310
    ///    entities and target specific configuration for file copy operations.
311
    /// * `check_timer_creator` - [TimerCreatorProvider] used by the CFDP handler to generate
312
    ///    timers required by various tasks. This allows to use this handler for embedded systems
313
    ///    where the standard time APIs might not be available.
314
44
    pub fn new(
315
44
        local_cfg: LocalEntityConfig<UserFaultHook>,
316
44
        max_packet_len: usize,
317
44
        pdu_sender: PduSender,
318
44
        vfs: Vfs,
319
44
        remote_cfg_table: RemoteCfgTable,
320
44
        timer_creator: TimerCreator,
321
44
    ) -> Self {
322
44
        Self {
323
44
            local_cfg,
324
44
            step: TransactionStep::Idle,
325
44
            state: State::Idle,
326
44
            tparams: Default::default(),
327
44
            packet_buf: alloc::vec![0; max_packet_len],
328
44
            pdu_sender,
329
44
            vfs,
330
44
            remote_cfg_table,
331
44
            check_timer_creator: timer_creator,
332
44
        }
333
44
    }
334

            
335
20
    pub fn state_machine_no_packet(
336
20
        &mut self,
337
20
        cfdp_user: &mut impl CfdpUser,
338
20
    ) -> Result<u32, DestError> {
339
20
        self.state_machine(cfdp_user, None::<&DummyPduProvider>)
340
20
    }
341

            
342
    /// This is the core function to drive the destination handler. It is also used to insert
343
    /// packets into the destination handler.
344
    ///
345
    /// The state machine should either be called if a packet with the appropriate destination ID
346
    /// is received and periodically to perform all CFDP related tasks, for example
347
    /// checking for timeouts or missed file segments.
348
    ///
349
    /// The function returns the number of sent PDU packets on success.
350
122
    pub fn state_machine(
351
122
        &mut self,
352
122
        cfdp_user: &mut impl CfdpUser,
353
122
        packet_to_insert: Option<&impl PduProvider>,
354
122
    ) -> Result<u32, DestError> {
355
122
        if let Some(packet) = packet_to_insert {
356
94
            self.insert_packet(cfdp_user, packet)?;
357
28
        }
358
118
        match self.state {
359
            State::Idle => {
360
                // TODO: In acknowledged mode, add timer handling.
361
8
                Ok(0)
362
            }
363
110
            State::Busy => self.fsm_busy(cfdp_user),
364
            State::Suspended => {
365
                // There is now way to suspend the handler currently anyway.
366
                Ok(0)
367
            }
368
        }
369
122
    }
370

            
371
    /// This function models the Cancel.request CFDP primitive and is the recommended way
372
    /// to cancel a transaction. It will cause a Notice Of Cancellation at this entity.
373
    /// Please note that the state machine might still be active because a cancelled transfer
374
    /// might still require some packets to be sent to the remote sender entity.
375
    ///
376
    /// If no unexpected errors occur, this function returns whether the current transfer
377
    /// was cancelled. It returns [false] if the state machine is currently idle or if there
378
    /// is a transaction ID missmatch.
379
12
    pub fn cancel_request(&mut self, transaction_id: &TransactionId) -> bool {
380
12
        if self.state() == super::State::Idle {
381
2
            return false;
382
10
        }
383
10
        if let Some(active_id) = self.transaction_id() {
384
10
            if active_id == *transaction_id {
385
10
                self.trigger_notice_of_completion_cancelled(
386
10
                    ConditionCode::CancelRequestReceived,
387
10
                    EntityIdTlv::new(self.local_cfg.id),
388
10
                );
389
10

            
390
10
                self.step = TransactionStep::TransferCompletion;
391
10
                return true;
392
            }
393
        }
394
        false
395
12
    }
396

            
397
    /// Returns [None] if the state machine is IDLE, and the transmission mode of the current
398
    /// request otherwise.
399
36
    pub fn transmission_mode(&self) -> Option<TransmissionMode> {
400
36
        if self.state == State::Idle {
401
2
            return None;
402
34
        }
403
34
        Some(self.tparams.transmission_mode())
404
36
    }
405

            
406
92
    pub fn transaction_id(&self) -> Option<TransactionId> {
407
92
        self.tstate().transaction_id
408
92
    }
409

            
410
94
    fn insert_packet(
411
94
        &mut self,
412
94
        cfdp_user: &mut impl CfdpUser,
413
94
        packet_to_insert: &impl PduProvider,
414
94
    ) -> Result<(), DestError> {
415
94
        if packet_to_insert.packet_target()? != PacketTarget::DestEntity {
416
            // Unwrap is okay here, a PacketInfo for a file data PDU should always have the
417
            // destination as the target.
418
2
            return Err(DestError::CantProcessPacketType {
419
2
                pdu_type: packet_to_insert.pdu_type(),
420
2
                directive_type: packet_to_insert.file_directive_type(),
421
2
            });
422
92
        }
423
92
        match packet_to_insert.pdu_type() {
424
            PduType::FileDirective => {
425
66
                if packet_to_insert.file_directive_type().is_none() {
426
                    return Err(DestError::DirectiveFieldEmpty);
427
66
                }
428
66
                self.handle_file_directive(
429
66
                    cfdp_user,
430
66
                    packet_to_insert.file_directive_type().unwrap(),
431
66
                    packet_to_insert.pdu(),
432
66
                )
433
            }
434
26
            PduType::FileData => self.handle_file_data(cfdp_user, packet_to_insert.pdu()),
435
        }
436
94
    }
437

            
438
66
    fn handle_file_directive(
439
66
        &mut self,
440
66
        cfdp_user: &mut impl CfdpUser,
441
66
        pdu_directive: FileDirectiveType,
442
66
        raw_packet: &[u8],
443
66
    ) -> Result<(), DestError> {
444
66
        match pdu_directive {
445
26
            FileDirectiveType::EofPdu => self.handle_eof_pdu(cfdp_user, raw_packet)?,
446
            FileDirectiveType::FinishedPdu
447
            | FileDirectiveType::NakPdu
448
            | FileDirectiveType::KeepAlivePdu => {
449
                return Err(DestError::CantProcessPacketType {
450
                    pdu_type: PduType::FileDirective,
451
                    directive_type: Some(pdu_directive),
452
                });
453
            }
454
            FileDirectiveType::AckPdu => {
455
                return Err(DestError::NotImplemented);
456
            }
457
40
            FileDirectiveType::MetadataPdu => self.handle_metadata_pdu(raw_packet)?,
458
            FileDirectiveType::PromptPdu => self.handle_prompt_pdu(raw_packet)?,
459
        };
460
64
        Ok(())
461
66
    }
462

            
463
40
    fn handle_metadata_pdu(&mut self, raw_packet: &[u8]) -> Result<(), DestError> {
464
40
        if self.state != State::Idle {
465
2
            return Err(DestError::RecvdMetadataButIsBusy);
466
38
        }
467
38
        let metadata_pdu = MetadataPduReader::from_bytes(raw_packet)?;
468
38
        self.tparams.reset();
469
38
        self.tparams.tstate.metadata_params = *metadata_pdu.metadata_params();
470
38
        let remote_cfg = self.remote_cfg_table.get(metadata_pdu.source_id().value());
471
38
        if remote_cfg.is_none() {
472
            return Err(DestError::NoRemoteCfgFound(metadata_pdu.dest_id()));
473
38
        }
474
38
        self.tparams.remote_cfg = Some(*remote_cfg.unwrap());
475
38

            
476
38
        // TODO: Support for metadata only PDUs.
477
38
        let src_name = metadata_pdu.src_file_name();
478
38
        let dest_name = metadata_pdu.dest_file_name();
479
38
        if src_name.is_empty() && dest_name.is_empty() {
480
            self.tparams.tstate.metadata_only = true;
481
38
        }
482
38
        if !self.tparams.tstate.metadata_only && src_name.is_empty() {
483
            return Err(DestError::EmptySrcFileField);
484
38
        }
485
38
        if !self.tparams.tstate.metadata_only && dest_name.is_empty() {
486
            return Err(DestError::EmptyDestFileField);
487
38
        }
488
38
        if !self.tparams.tstate.metadata_only {
489
38
            self.tparams.file_properties.src_file_name[..src_name.len_value()]
490
38
                .copy_from_slice(src_name.value());
491
38
            self.tparams.file_properties.src_file_name_len = src_name.len_value();
492
38
            if dest_name.is_empty() {
493
                return Err(DestError::EmptyDestFileField);
494
38
            }
495
38
            self.tparams.file_properties.dest_file_name[..dest_name.len_value()]
496
38
                .copy_from_slice(dest_name.value());
497
38
            self.tparams.file_properties.dest_file_name_len = dest_name.len_value();
498
38
            self.tparams.pdu_conf = *metadata_pdu.pdu_header().common_pdu_conf();
499
38
            self.tparams.msgs_to_user_size = 0;
500
        }
501
38
        if !metadata_pdu.options().is_empty() {
502
            for option_tlv in metadata_pdu.options_iter().unwrap() {
503
                if option_tlv.is_standard_tlv()
504
                    && option_tlv.tlv_type().unwrap() == TlvType::MsgToUser
505
                {
506
                    self.tparams
507
                        .msgs_to_user_buf
508
                        .copy_from_slice(option_tlv.raw_data().unwrap());
509
                    self.tparams.msgs_to_user_size += option_tlv.len_full();
510
                }
511
            }
512
38
        }
513
38
        self.state = State::Busy;
514
38
        self.step = TransactionStep::TransactionStart;
515
38
        Ok(())
516
40
    }
517

            
518
26
    fn handle_file_data(
519
26
        &mut self,
520
26
        user: &mut impl CfdpUser,
521
26
        raw_packet: &[u8],
522
26
    ) -> Result<(), DestError> {
523
26
        if self.state == State::Idle
524
26
            || (self.step != TransactionStep::ReceivingFileDataPdus
525
2
                && self.step != TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling)
526
        {
527
            return Err(DestError::WrongStateForFileData);
528
26
        }
529
26
        let fd_pdu = FileDataPdu::from_bytes(raw_packet)?;
530
26
        if self.local_cfg.indication_cfg.file_segment_recv {
531
26
            user.file_segment_recvd_indication(&FileSegmentRecvdParams {
532
26
                id: self.tstate().transaction_id.unwrap(),
533
26
                offset: fd_pdu.offset(),
534
26
                length: fd_pdu.file_data().len(),
535
26
                segment_metadata: fd_pdu.segment_metadata(),
536
26
            });
537
26
        }
538
26
        if let Err(e) = self.vfs.write_data(
539
26
            // Safety: It was already verified that the path is valid during the transaction start.
540
26
            unsafe {
541
26
                from_utf8_unchecked(
542
26
                    //from_utf8(
543
26
                    &self.tparams.file_properties.dest_path_buf
544
26
                        [0..self.tparams.file_properties.dest_file_path_len],
545
26
                )
546
26
            },
547
26
            fd_pdu.offset(),
548
26
            fd_pdu.file_data(),
549
26
        ) {
550
            self.declare_fault(ConditionCode::FilestoreRejection);
551
            return Err(e.into());
552
26
        }
553
26
        self.tstate_mut().progress += fd_pdu.file_data().len() as u64;
554
26
        Ok(())
555
26
    }
556

            
557
26
    fn handle_eof_pdu(
558
26
        &mut self,
559
26
        cfdp_user: &mut impl CfdpUser,
560
26
        raw_packet: &[u8],
561
26
    ) -> Result<(), DestError> {
562
26
        if self.state == State::Idle || self.step != TransactionStep::ReceivingFileDataPdus {
563
            return Err(DestError::WrongStateForEof);
564
26
        }
565
26
        let eof_pdu = EofPdu::from_bytes(raw_packet)?;
566
26
        if self.local_cfg.indication_cfg.eof_recv {
567
26
            // Unwrap is okay here, application logic ensures that transaction ID is valid here.
568
26
            cfdp_user.eof_recvd_indication(self.tparams.tstate.transaction_id.as_ref().unwrap());
569
26
        }
570
26
        let regular_transfer_finish = if eof_pdu.condition_code() == ConditionCode::NoError {
571
20
            self.handle_no_error_eof_pdu(&eof_pdu)?
572
        } else {
573
            // This is an EOF (Cancel), perform Cancel Response Procedures according to chapter
574
            // 4.6.6 of the standard.
575
6
            self.trigger_notice_of_completion_cancelled(
576
6
                eof_pdu.condition_code(),
577
6
                EntityIdTlv::new(self.tparams.remote_cfg.unwrap().entity_id),
578
6
            );
579
6
            self.tparams.tstate.progress = eof_pdu.file_size();
580
6
            if eof_pdu.file_size() > 0 {
581
4
                self.tparams.tstate.delivery_code = DeliveryCode::Incomplete;
582
4
            } else {
583
2
                self.tparams.tstate.delivery_code = DeliveryCode::Complete;
584
2
            }
585
            // TODO: The cancel EOF also supplies a checksum and a progress number. We could cross
586
            // check that checksum, but how would we deal with a checksum failure? The standard
587
            // does not specify anything for this case.. It could be part of the status report
588
            // issued to the user though.
589
6
            true
590
        };
591
26
        if regular_transfer_finish {
592
20
            self.file_transfer_complete_transition();
593
20
        }
594
26
        Ok(())
595
26
    }
596

            
597
16
    fn trigger_notice_of_completion_cancelled(
598
16
        &mut self,
599
16
        cond_code: ConditionCode,
600
16
        fault_location: EntityIdTlv,
601
16
    ) {
602
16
        self.tparams.tstate.completion_disposition = CompletionDisposition::Cancelled;
603
16
        self.tparams.tstate.condition_code = cond_code;
604
16
        self.tparams.tstate.fault_location_finished = Some(fault_location);
605
16
        // For anything larger than 0, we'd have to do a checksum check to verify whether
606
16
        // the delivery is really complete, and we need the EOF checksum for that..
607
16
        if self.tparams.tstate.progress == 0 {
608
8
            self.tparams.tstate.delivery_code = DeliveryCode::Complete;
609
8
        }
610
16
    }
611

            
612
    /// Returns whether the transfer can be completed regularly.
613
20
    fn handle_no_error_eof_pdu(&mut self, eof_pdu: &EofPdu) -> Result<bool, DestError> {
614
20
        // CFDP 4.6.1.2.9: Declare file size error if progress exceeds file size
615
20
        if self.tparams.tstate.progress > eof_pdu.file_size()
616
            && self.declare_fault(ConditionCode::FileSizeError) != FaultHandlerCode::IgnoreError
617
        {
618
            return Ok(false);
619
20
        } else if (self.tparams.tstate.progress < eof_pdu.file_size())
620
4
            && self.tparams.transmission_mode() == TransmissionMode::Acknowledged
621
        {
622
            // CFDP 4.6.4.3.1: The end offset of the last received file segment and the file
623
            // size as stated in the EOF PDU is not the same, so we need to add that segment to
624
            // the lost segments for the deferred lost segment detection procedure.
625
            // TODO: Proper lost segment handling.
626
            // self._params.acked_params.lost_seg_tracker.add_lost_segment(
627
            //  (self._params.fp.progress, self._params.fp.file_size_eof)
628
            // )
629
20
        }
630

            
631
20
        self.tparams.tstate.checksum = eof_pdu.file_checksum();
632
20
        if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged
633
20
            && !self.checksum_verify(self.tparams.tstate.checksum)
634
        {
635
6
            if self.declare_fault(ConditionCode::FileChecksumFailure)
636
6
                != FaultHandlerCode::IgnoreError
637
            {
638
                return Ok(false);
639
6
            }
640
6
            self.start_check_limit_handling();
641
6
            return Ok(false);
642
14
        }
643
14
        Ok(true)
644
20
    }
645

            
646
22
    fn file_transfer_complete_transition(&mut self) {
647
22
        if self.tparams.transmission_mode() == TransmissionMode::Unacknowledged {
648
22
            self.step = TransactionStep::TransferCompletion;
649
22
        } else {
650
            // TODO: Prepare ACK PDU somehow.
651
            self.step = TransactionStep::SendingAckPdu;
652
        }
653
22
    }
654

            
655
30
    fn checksum_verify(&mut self, checksum: u32) -> bool {
656
30
        let mut file_delivery_complete = false;
657
30
        if self.tparams.metadata_params().checksum_type == ChecksumType::NullChecksum
658
24
            || self.tparams.tstate.metadata_only
659
6
        {
660
6
            file_delivery_complete = true;
661
6
        } else {
662
24
            match self.vfs.checksum_verify(
663
24
                checksum,
664
24
                // Safety: It was already verified that the path is valid during the transaction start.
665
24
                unsafe {
666
24
                    from_utf8_unchecked(
667
24
                        &self.tparams.file_properties.dest_path_buf
668
24
                            [0..self.tparams.file_properties.dest_file_path_len],
669
24
                    )
670
24
                },
671
24
                self.tparams.metadata_params().checksum_type,
672
24
                self.tparams.tstate.progress,
673
24
                &mut self.tparams.cksum_buf,
674
24
            ) {
675
24
                Ok(checksum_success) => {
676
24
                    file_delivery_complete = checksum_success;
677
24
                    if !checksum_success {
678
14
                        self.tparams.tstate.delivery_code = DeliveryCode::Incomplete;
679
14
                        self.tparams.tstate.condition_code = ConditionCode::FileChecksumFailure;
680
18
                    }
681
                }
682
                Err(e) => match e {
683
                    FilestoreError::ChecksumTypeNotImplemented(_) => {
684
                        self.declare_fault(ConditionCode::UnsupportedChecksumType);
685
                        // For this case, the applicable algorithm shall be the the null checksum,
686
                        // which is always succesful.
687
                        file_delivery_complete = true;
688
                    }
689
                    _ => {
690
                        self.declare_fault(ConditionCode::FilestoreRejection);
691
                        // Treat this equivalent to a failed checksum procedure.
692
                    }
693
                },
694
            };
695
        }
696
30
        if file_delivery_complete {
697
16
            self.tparams.tstate.delivery_code = DeliveryCode::Complete;
698
16
            self.tparams.tstate.condition_code = ConditionCode::NoError;
699
18
        }
700
30
        file_delivery_complete
701
30
    }
702

            
703
6
    fn start_check_limit_handling(&mut self) {
704
6
        self.step = TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling;
705
6
        self.tparams.tstate.current_check_timer = Some(self.check_timer_creator.create_countdown(
706
6
            TimerContext::CheckLimit {
707
6
                local_id: self.local_cfg.id,
708
6
                remote_id: self.tparams.remote_cfg.unwrap().entity_id,
709
6
                entity_type: EntityType::Receiving,
710
6
            },
711
6
        ));
712
6
        self.tparams.tstate.current_check_count = 0;
713
6
    }
714

            
715
18
    fn check_limit_handling(&mut self) {
716
18
        if self.tparams.tstate.current_check_timer.is_none() {
717
            return;
718
18
        }
719
18
        let check_timer = self.tparams.tstate.current_check_timer.as_ref().unwrap();
720
18
        if check_timer.has_expired() {
721
10
            if self.checksum_verify(self.tparams.tstate.checksum) {
722
2
                self.file_transfer_complete_transition();
723
2
                return;
724
8
            }
725
8
            if self.tparams.tstate.current_check_count + 1
726
8
                >= self.tparams.remote_cfg.unwrap().check_limit
727
4
            {
728
4
                self.declare_fault(ConditionCode::CheckLimitReached);
729
4
            } else {
730
4
                self.tparams.tstate.current_check_count += 1;
731
4
                self.tparams
732
4
                    .tstate
733
4
                    .current_check_timer
734
4
                    .as_mut()
735
4
                    .unwrap()
736
4
                    .reset();
737
4
            }
738
8
        }
739
18
    }
740

            
741
    pub fn handle_prompt_pdu(&mut self, _raw_packet: &[u8]) -> Result<(), DestError> {
742
        Err(DestError::NotImplemented)
743
    }
744

            
745
110
    fn fsm_busy(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<u32, DestError> {
746
110
        let mut sent_packets = 0;
747
110
        if self.step == TransactionStep::TransactionStart {
748
38
            let result = self.transaction_start(cfdp_user);
749
38
            if let Err(e) = result {
750
                // If we can not even start the transaction properly, reset the handler.
751
                // We could later let the user do this optionally, but for now this is the safer
752
                // approach to prevent inconsistent states of the handler.
753
                self.reset();
754
                return Err(e);
755
38
            }
756
72
        }
757
110
        if self.step == TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling {
758
18
            self.check_limit_handling();
759
92
        }
760
110
        if self.step == TransactionStep::TransferCompletion {
761
36
            sent_packets += self.transfer_completion(cfdp_user)?;
762
74
        }
763
110
        if self.step == TransactionStep::SendingAckPdu {
764
            return Err(DestError::NotImplemented);
765
110
        }
766
110
        if self.step == TransactionStep::SendingFinishedPdu {
767
36
            self.reset();
768
74
        }
769
110
        Ok(sent_packets)
770
110
    }
771

            
772
    /// Get the step, which denotes the exact step of a pending CFDP transaction when applicable.
773
122
    pub fn step(&self) -> TransactionStep {
774
122
        self.step
775
122
    }
776

            
777
    /// Get the step, which denotes whether the CFDP handler is active, and which CFDP class
778
    /// is used if it is active.
779
134
    pub fn state(&self) -> State {
780
134
        self.state
781
134
    }
782

            
783
38
    fn transaction_start(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
784
38
        let dest_name = from_utf8(
785
38
            &self.tparams.file_properties.dest_file_name
786
38
                [..self.tparams.file_properties.dest_file_name_len],
787
38
        )?;
788
38
        self.tparams.file_properties.dest_path_buf[0..dest_name.len()]
789
38
            .copy_from_slice(dest_name.as_bytes());
790
38
        self.tparams.file_properties.dest_file_path_len = dest_name.len();
791
38
        let source_id = self.tparams.pdu_conf.source_id();
792
38
        let id = TransactionId::new(source_id, self.tparams.pdu_conf.transaction_seq_num);
793
38
        let src_name = from_utf8(
794
38
            &self.tparams.file_properties.src_file_name
795
38
                [0..self.tparams.file_properties.src_file_name_len],
796
38
        )?;
797
38
        let mut msgs_to_user = SmallVec::<[MsgToUserTlv<'_>; 16]>::new();
798
38
        let mut num_msgs_to_user = 0;
799
38
        if self.tparams.msgs_to_user_size > 0 {
800
            let mut index = 0;
801
            while index < self.tparams.msgs_to_user_size {
802
                // This should never panic as the validity of the options was checked beforehand.
803
                let msgs_to_user_tlv =
804
                    MsgToUserTlv::from_bytes(&self.tparams.msgs_to_user_buf[index..])
805
                        .expect("message to user creation failed unexpectedly");
806
                msgs_to_user.push(msgs_to_user_tlv);
807
                index += msgs_to_user_tlv.len_full();
808
                num_msgs_to_user += 1;
809
            }
810
38
        }
811
38
        let metadata_recvd_params = MetadataReceivedParams {
812
38
            id,
813
38
            source_id,
814
38
            file_size: self.tparams.file_size(),
815
38
            src_file_name: src_name,
816
38
            dest_file_name: dest_name,
817
38
            msgs_to_user: &msgs_to_user[..num_msgs_to_user],
818
38
        };
819
38
        self.tparams.tstate.transaction_id = Some(id);
820
38
        cfdp_user.metadata_recvd_indication(&metadata_recvd_params);
821
38

            
822
38
        if self.vfs.exists(dest_name)? && self.vfs.is_dir(dest_name)? {
823
            // Create new destination path by concatenating the last part of the source source
824
            // name and the destination folder. For example, for a source file of /tmp/hello.txt
825
            // and a destination name of /home/test, the resulting file name should be
826
            // /home/test/hello.txt
827
            // Safety: It was already verified that the path is valid during the transaction start.
828
            let source_file_name = self.vfs.file_name(src_name)?;
829
            if source_file_name.is_none() {
830
                return Err(DestError::PathConcat);
831
            }
832
            let source_name = source_file_name.unwrap();
833
            self.tparams.file_properties.dest_path_buf[dest_name.len()] = b'/';
834
            self.tparams.file_properties.dest_path_buf
835
                [dest_name.len() + 1..dest_name.len() + 1 + source_name.len()]
836
                .copy_from_slice(source_name.as_bytes());
837
            self.tparams.file_properties.dest_file_path_len += 1 + source_name.len();
838
38
        }
839
38
        let dest_path_str = from_utf8(
840
38
            &self.tparams.file_properties.dest_path_buf
841
38
                [0..self.tparams.file_properties.dest_file_path_len],
842
38
        )?;
843
38
        if self.vfs.exists(dest_path_str)? {
844
            self.vfs.truncate_file(dest_path_str)?;
845
        } else {
846
38
            self.vfs.create_file(dest_path_str)?;
847
        }
848
38
        self.tparams.tstate.file_status = FileStatus::Retained;
849
38
        self.step = TransactionStep::ReceivingFileDataPdus;
850
38
        Ok(())
851
38
    }
852

            
853
36
    fn transfer_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<u32, DestError> {
854
36
        let mut sent_packets = 0;
855
36
        self.notice_of_completion(cfdp_user)?;
856
36
        if self.tparams.transmission_mode() == TransmissionMode::Acknowledged
857
36
            || self.tparams.metadata_params().closure_requested
858
        {
859
12
            sent_packets += self.send_finished_pdu()?;
860
24
        }
861
36
        self.step = TransactionStep::SendingFinishedPdu;
862
36
        Ok(sent_packets)
863
36
    }
864

            
865
36
    fn notice_of_completion(&mut self, cfdp_user: &mut impl CfdpUser) -> Result<(), DestError> {
866
36
        if self.tstate().completion_disposition == CompletionDisposition::Completed {
867
16
            // TODO: Execute any filestore requests
868
24
        } else if self
869
20
            .tparams
870
20
            .remote_cfg
871
20
            .as_ref()
872
20
            .unwrap()
873
20
            .disposition_on_cancellation
874
4
            && self.tstate().delivery_code == DeliveryCode::Incomplete
875
        {
876
            // Safety: We already verified that the path is valid during the transaction start.
877
2
            let dest_path = unsafe {
878
2
                from_utf8_unchecked(
879
2
                    &self.tparams.file_properties.dest_path_buf
880
2
                        [0..self.tparams.file_properties.dest_file_path_len],
881
2
                )
882
2
            };
883
2
            if self.vfs.exists(dest_path)? && self.vfs.is_file(dest_path)? {
884
2
                self.vfs.remove_file(dest_path)?;
885
            }
886
2
            self.tstate_mut().file_status = FileStatus::DiscardDeliberately;
887
18
        }
888
36
        let tstate = self.tstate();
889
36
        let transaction_finished_params = TransactionFinishedParams {
890
36
            id: tstate.transaction_id.unwrap(),
891
36
            condition_code: tstate.condition_code,
892
36
            delivery_code: tstate.delivery_code,
893
36
            file_status: tstate.file_status,
894
36
        };
895
36
        cfdp_user.transaction_finished_indication(&transaction_finished_params);
896
36
        Ok(())
897
36
    }
898

            
899
10
    fn declare_fault(&mut self, condition_code: ConditionCode) -> FaultHandlerCode {
900
10
        // Cache those, because they might be reset when abandoning the transaction.
901
10
        let transaction_id = self.tstate().transaction_id.unwrap();
902
10
        let progress = self.tstate().progress;
903
10
        let fh_code = self
904
10
            .local_cfg
905
10
            .fault_handler
906
10
            .get_fault_handler(condition_code);
907
10
        match fh_code {
908
4
            FaultHandlerCode::NoticeOfCancellation => {
909
4
                self.notice_of_cancellation(condition_code, EntityIdTlv::new(self.local_cfg().id));
910
4
            }
911
            FaultHandlerCode::NoticeOfSuspension => self.notice_of_suspension(),
912
6
            FaultHandlerCode::IgnoreError => (),
913
            FaultHandlerCode::AbandonTransaction => self.abandon_transaction(),
914
        }
915
10
        self.local_cfg
916
10
            .fault_handler
917
10
            .report_fault(transaction_id, condition_code, progress)
918
10
    }
919

            
920
4
    fn notice_of_cancellation(
921
4
        &mut self,
922
4
        condition_code: ConditionCode,
923
4
        fault_location: EntityIdTlv,
924
4
    ) {
925
4
        self.step = TransactionStep::TransferCompletion;
926
4
        self.tstate_mut().condition_code = condition_code;
927
4
        self.tstate_mut().fault_location_finished = Some(fault_location);
928
4
        self.tstate_mut().completion_disposition = CompletionDisposition::Cancelled;
929
4
    }
930

            
931
    fn notice_of_suspension(&mut self) {
932
        // TODO: Implement suspension handling.
933
    }
934

            
935
    fn abandon_transaction(&mut self) {
936
        self.reset();
937
    }
938

            
939
    /// This function is public to allow completely resetting the handler, but it is explicitely
940
    /// discouraged to do this. CFDP has mechanism to detect issues and errors on itself.
941
    /// Resetting the handler might interfere with these mechanisms and lead to unexpected
942
    /// behaviour.
943
36
    pub fn reset(&mut self) {
944
36
        self.step = TransactionStep::Idle;
945
36
        self.state = State::Idle;
946
36
        // self.packets_to_send_ctx.packet_available = false;
947
36
        self.tparams.reset();
948
36
    }
949

            
950
12
    fn send_finished_pdu(&mut self) -> Result<u32, DestError> {
951
12
        let tstate = self.tstate();
952
12

            
953
12
        let pdu_header = PduHeader::new_no_file_data(self.tparams.pdu_conf, 0);
954
12
        let finished_pdu = if tstate.condition_code == ConditionCode::NoError
955
8
            || tstate.condition_code == ConditionCode::UnsupportedChecksumType
956
        {
957
4
            FinishedPduCreator::new_default(pdu_header, tstate.delivery_code, tstate.file_status)
958
        } else {
959
8
            FinishedPduCreator::new_generic(
960
8
                pdu_header,
961
8
                tstate.condition_code,
962
8
                tstate.delivery_code,
963
8
                tstate.file_status,
964
8
                &[],
965
8
                tstate.fault_location_finished,
966
8
            )
967
        };
968
12
        finished_pdu.write_to_bytes(&mut self.packet_buf)?;
969
12
        self.pdu_sender.send_pdu(
970
12
            finished_pdu.pdu_type(),
971
12
            finished_pdu.file_directive_type(),
972
12
            &self.packet_buf[0..finished_pdu.len_written()],
973
12
        )?;
974
12
        Ok(1)
975
12
    }
976

            
977
4
    pub fn local_cfg(&self) -> &LocalEntityConfig<UserFaultHook> {
978
4
        &self.local_cfg
979
4
    }
980

            
981
226
    fn tstate(&self) -> &TransferState<Countdown> {
982
226
        &self.tparams.tstate
983
226
    }
984

            
985
40
    fn tstate_mut(&mut self) -> &mut TransferState<Countdown> {
986
40
        &mut self.tparams.tstate
987
40
    }
988
}
989

            
990
#[cfg(test)]
991
mod tests {
992
    use core::{cell::Cell, sync::atomic::AtomicBool};
993
    #[allow(unused_imports)]
994
    use std::println;
995
    use std::{
996
        fs,
997
        path::{Path, PathBuf},
998
        string::String,
999
    };
    use alloc::{sync::Arc, vec::Vec};
    use rand::Rng;
    use spacepackets::{
        cfdp::{
            lv::Lv,
            pdu::{finished::FinishedPduReader, metadata::MetadataPduCreator, WritablePduPacket},
            ChecksumType, TransmissionMode,
        },
        util::{UbfU16, UnsignedByteFieldU8},
    };
    use crate::{
        filestore::NativeFilestore,
        tests::{
            basic_remote_cfg_table, SentPdu, TestCfdpSender, TestCfdpUser, TestFaultHandler,
            LOCAL_ID, REMOTE_ID,
        },
        CountdownProvider, FaultHandler, IndicationConfig, PduRawWithInfo,
        StdRemoteEntityConfigProvider, TimerCreatorProvider, CRC_32,
    };
    use super::*;
    #[derive(Debug)]
    struct TestCheckTimer {
        counter: Cell<u32>,
        expired: Arc<AtomicBool>,
    }
    impl CountdownProvider for TestCheckTimer {
18
        fn has_expired(&self) -> bool {
18
            self.expired.load(core::sync::atomic::Ordering::Relaxed)
18
        }
4
        fn reset(&mut self) {
4
            self.counter.set(0);
4
        }
    }
    impl TestCheckTimer {
6
        pub fn new(expired_flag: Arc<AtomicBool>) -> Self {
6
            Self {
6
                counter: Cell::new(0),
6
                expired: expired_flag,
6
            }
6
        }
    }
    struct TestCheckTimerCreator {
        check_limit_expired_flag: Arc<AtomicBool>,
    }
    impl TestCheckTimerCreator {
40
        pub fn new(expired_flag: Arc<AtomicBool>) -> Self {
40
            Self {
40
                check_limit_expired_flag: expired_flag,
40
            }
40
        }
    }
    impl TimerCreatorProvider for TestCheckTimerCreator {
        type Countdown = TestCheckTimer;
6
        fn create_countdown(&self, timer_context: TimerContext) -> Self::Countdown {
6
            match timer_context {
                TimerContext::CheckLimit { .. } => {
6
                    TestCheckTimer::new(self.check_limit_expired_flag.clone())
                }
                _ => {
                    panic!("invalid check timer creator, can only be used for check limit handling")
                }
            }
6
        }
    }
    type TestDestHandler = DestinationHandler<
        TestCfdpSender,
        TestFaultHandler,
        NativeFilestore,
        StdRemoteEntityConfigProvider,
        TestCheckTimerCreator,
        TestCheckTimer,
    >;
    struct DestHandlerTestbench {
        check_timer_expired: Arc<AtomicBool>,
        handler: TestDestHandler,
        src_path: PathBuf,
        dest_path: PathBuf,
        check_dest_file: bool,
        check_handler_idle_at_drop: bool,
        closure_requested: bool,
        pdu_header: PduHeader,
        expected_full_data: Vec<u8>,
        expected_file_size: u64,
        buf: [u8; 512],
    }
    impl DestHandlerTestbench {
34
        fn new_with_fixed_paths(fault_handler: TestFaultHandler, closure_requested: bool) -> Self {
34
            let (src_path, dest_path) = init_full_filepaths_textfile();
34
            assert!(!Path::exists(&dest_path));
34
            Self::new(fault_handler, closure_requested, true, src_path, dest_path)
34
        }
36
        fn new(
36
            fault_handler: TestFaultHandler,
36
            closure_requested: bool,
36
            check_dest_file: bool,
36
            src_path: PathBuf,
36
            dest_path: PathBuf,
36
        ) -> Self {
36
            let check_timer_expired = Arc::new(AtomicBool::new(false));
36
            let test_sender = TestCfdpSender::default();
36
            let dest_handler =
36
                default_dest_handler(fault_handler, test_sender, check_timer_expired.clone());
36
            let handler = Self {
36
                check_timer_expired,
36
                handler: dest_handler,
36
                src_path,
36
                closure_requested,
36
                dest_path,
36
                check_dest_file,
36
                check_handler_idle_at_drop: true,
36
                expected_file_size: 0,
36
                pdu_header: create_pdu_header(UbfU16::new(0)),
36
                expected_full_data: Vec::new(),
36
                buf: [0; 512],
36
            };
36
            handler.state_check(State::Idle, TransactionStep::Idle);
36
            handler
36
        }
42
        fn dest_path(&self) -> &PathBuf {
42
            &self.dest_path
42
        }
38
        fn all_fault_queues_empty(&self) -> bool {
38
            self.handler
38
                .local_cfg
38
                .user_fault_hook()
38
                .borrow()
38
                .all_queues_empty()
38
        }
        #[allow(dead_code)]
        fn indication_cfg_mut(&mut self) -> &mut IndicationConfig {
            &mut self.handler.local_cfg.indication_cfg
        }
4
        fn get_next_pdu(&mut self) -> Option<SentPdu> {
4
            self.handler.pdu_sender.retrieve_next_pdu()
4
        }
38
        fn indication_cfg(&mut self) -> &IndicationConfig {
38
            &self.handler.local_cfg.indication_cfg
38
        }
10
        fn set_check_timer_expired(&mut self) {
10
            self.check_timer_expired
10
                .store(true, core::sync::atomic::Ordering::Relaxed);
10
        }
36
        fn test_user_from_cached_paths(&self, expected_file_size: u64) -> TestCfdpUser {
36
            TestCfdpUser::new(
36
                0,
36
                self.src_path.to_string_lossy().into(),
36
                self.dest_path.to_string_lossy().into(),
36
                expected_file_size,
36
            )
36
        }
34
        fn generic_transfer_init(
34
            &mut self,
34
            user: &mut TestCfdpUser,
34
            file_size: u64,
34
        ) -> Result<TransactionId, DestError> {
34
            self.expected_file_size = file_size;
34
            assert_eq!(user.transaction_indication_call_count, 0);
34
            assert_eq!(user.metadata_recv_queue.len(), 0);
34
            let metadata_pdu = create_metadata_pdu(
34
                &self.pdu_header,
34
                self.src_path.as_path(),
34
                self.dest_path.as_path(),
34
                file_size,
34
                self.closure_requested,
34
            );
34
            let packet_info = create_packet_info(&metadata_pdu, &mut self.buf);
34
            self.handler.state_machine(user, Some(&packet_info))?;
34
            assert_eq!(user.metadata_recv_queue.len(), 1);
34
            assert_eq!(
34
                self.handler.transmission_mode().unwrap(),
34
                TransmissionMode::Unacknowledged
34
            );
34
            assert_eq!(user.transaction_indication_call_count, 0);
34
            assert_eq!(user.metadata_recv_queue.len(), 1);
34
            let metadata_recvd = user.metadata_recv_queue.pop_front().unwrap();
34
            assert_eq!(metadata_recvd.source_id, LOCAL_ID.into());
34
            assert_eq!(
34
                metadata_recvd.src_file_name,
34
                String::from(self.src_path.to_str().unwrap())
34
            );
34
            assert_eq!(
34
                metadata_recvd.dest_file_name,
34
                String::from(self.dest_path().to_str().unwrap())
34
            );
34
            assert_eq!(metadata_recvd.id, self.handler.transaction_id().unwrap());
34
            assert_eq!(metadata_recvd.file_size, file_size);
34
            assert!(metadata_recvd.msgs_to_user.is_empty());
34
            Ok(self.handler.transaction_id().unwrap())
34
        }
22
        fn generic_file_data_insert(
22
            &mut self,
22
            user: &mut TestCfdpUser,
22
            offset: u64,
22
            file_data_chunk: &[u8],
22
        ) -> Result<u32, DestError> {
22
            let filedata_pdu =
22
                FileDataPdu::new_no_seg_metadata(self.pdu_header, offset, file_data_chunk);
22
            filedata_pdu
22
                .write_to_bytes(&mut self.buf)
22
                .expect("writing file data PDU failed");
22
            let packet_info = PduRawWithInfo::new(&self.buf).expect("creating packet info failed");
22
            let result = self.handler.state_machine(user, Some(&packet_info));
22
            if self.indication_cfg().file_segment_recv {
22
                assert!(!user.file_seg_recvd_queue.is_empty());
22
                assert_eq!(user.file_seg_recvd_queue.back().unwrap().offset, offset);
22
                assert_eq!(
22
                    user.file_seg_recvd_queue.back().unwrap().length,
22
                    file_data_chunk.len()
22
                );
            }
22
            result
22
        }
16
        fn generic_eof_no_error(
16
            &mut self,
16
            user: &mut TestCfdpUser,
16
            expected_full_data: Vec<u8>,
16
        ) -> Result<u32, DestError> {
16
            self.expected_full_data = expected_full_data;
16
            assert_eq!(user.finished_indic_queue.len(), 0);
16
            let eof_pdu = create_no_error_eof(&self.expected_full_data, &self.pdu_header);
16
            let packet_info = create_packet_info(&eof_pdu, &mut self.buf);
16
            self.check_handler_idle_at_drop = true;
16
            self.check_dest_file = true;
16
            let result = self.handler.state_machine(user, Some(&packet_info));
16
            if self.indication_cfg().eof_recv {
16
                assert_eq!(user.eof_recvd_call_count, 1);
            }
16
            result
16
        }
12
        fn check_completion_indication_success(&mut self, user: &mut TestCfdpUser) {
12
            assert_eq!(user.finished_indic_queue.len(), 1);
12
            let finished_indication = user.finished_indic_queue.pop_front().unwrap();
12
            assert_eq!(
12
                finished_indication.id,
12
                self.handler.transaction_id().unwrap()
12
            );
12
            assert_eq!(finished_indication.file_status, FileStatus::Retained);
12
            assert_eq!(finished_indication.delivery_code, DeliveryCode::Complete);
12
            assert_eq!(finished_indication.condition_code, ConditionCode::NoError);
12
        }
120
        fn state_check(&self, state: State, step: TransactionStep) {
120
            assert_eq!(self.handler.state(), state);
120
            assert_eq!(self.handler.step(), step);
120
        }
    }
    // Specifying some checks in the drop method avoids some boilerplate.
    impl Drop for DestHandlerTestbench {
36
        fn drop(&mut self) {
36
            assert!(self.all_fault_queues_empty());
36
            assert!(self.handler.pdu_sender.queue_empty());
36
            if self.check_handler_idle_at_drop {
34
                self.state_check(State::Idle, TransactionStep::Idle);
34
            }
36
            if self.check_dest_file {
30
                assert!(Path::exists(&self.dest_path));
30
                let read_content = fs::read(&self.dest_path).expect("reading back string failed");
30
                assert_eq!(read_content.len() as u64, self.expected_file_size);
30
                assert_eq!(read_content, self.expected_full_data);
30
                assert!(fs::remove_file(self.dest_path.as_path()).is_ok());
6
            }
36
        }
    }
34
    fn init_full_filepaths_textfile() -> (PathBuf, PathBuf) {
34
        (
34
            tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf(),
34
            tempfile::NamedTempFile::new()
34
                .unwrap()
34
                .into_temp_path()
34
                .to_path_buf(),
34
        )
34
    }
40
    fn default_dest_handler(
40
        test_fault_handler: TestFaultHandler,
40
        test_packet_sender: TestCfdpSender,
40
        check_timer_expired: Arc<AtomicBool>,
40
    ) -> TestDestHandler {
40
        let local_entity_cfg = LocalEntityConfig {
40
            id: REMOTE_ID.into(),
40
            indication_cfg: IndicationConfig::default(),
40
            fault_handler: FaultHandler::new(test_fault_handler),
40
        };
40
        DestinationHandler::new(
40
            local_entity_cfg,
40
            2048,
40
            test_packet_sender,
40
            NativeFilestore::default(),
40
            basic_remote_cfg_table(LOCAL_ID, 1024, true),
40
            TestCheckTimerCreator::new(check_timer_expired),
40
        )
40
    }
36
    fn create_pdu_header(seq_num: impl Into<UnsignedByteField>) -> PduHeader {
36
        let mut pdu_conf =
36
            CommonPduConfig::new_with_byte_fields(LOCAL_ID, REMOTE_ID, seq_num).unwrap();
36
        pdu_conf.trans_mode = TransmissionMode::Unacknowledged;
36
        PduHeader::new_no_file_data(pdu_conf, 0)
36
    }
36
    fn create_metadata_pdu<'filename>(
36
        pdu_header: &PduHeader,
36
        src_name: &'filename Path,
36
        dest_name: &'filename Path,
36
        file_size: u64,
36
        closure_requested: bool,
36
    ) -> MetadataPduCreator<'filename, 'filename, 'static> {
36
        let checksum_type = if file_size == 0 {
18
            ChecksumType::NullChecksum
        } else {
18
            ChecksumType::Crc32
        };
36
        let metadata_params =
36
            MetadataGenericParams::new(closure_requested, checksum_type, file_size);
36
        MetadataPduCreator::new_no_opts(
36
            *pdu_header,
36
            metadata_params,
36
            Lv::new_from_str(src_name.as_os_str().to_str().unwrap()).unwrap(),
36
            Lv::new_from_str(dest_name.as_os_str().to_str().unwrap()).unwrap(),
36
        )
36
    }
52
    fn create_packet_info<'a>(
52
        pdu: &'a impl WritablePduPacket,
52
        buf: &'a mut [u8],
52
    ) -> PduRawWithInfo<'a> {
52
        let written_len = pdu
52
            .write_to_bytes(buf)
52
            .expect("writing metadata PDU failed");
52
        PduRawWithInfo::new(&buf[..written_len]).expect("generating packet info failed")
52
    }
16
    fn create_no_error_eof(file_data: &[u8], pdu_header: &PduHeader) -> EofPdu {
16
        let crc32 = if !file_data.is_empty() {
10
            let mut digest = CRC_32.digest();
10
            digest.update(file_data);
10
            digest.finalize()
        } else {
6
            0
        };
16
        EofPdu::new_no_error(*pdu_header, crc32, file_data.len() as u64)
16
    }
    #[test]
2
    fn test_basic() {
2
        let fault_handler = TestFaultHandler::default();
2
        let test_sender = TestCfdpSender::default();
2
        let dest_handler = default_dest_handler(fault_handler, test_sender, Arc::default());
2
        assert!(dest_handler.transmission_mode().is_none());
2
        assert!(dest_handler
2
            .local_cfg
2
            .fault_handler
2
            .user_hook
2
            .borrow()
2
            .all_queues_empty());
2
        assert!(dest_handler.pdu_sender.queue_empty());
2
        assert_eq!(dest_handler.state(), State::Idle);
2
        assert_eq!(dest_handler.step(), TransactionStep::Idle);
2
    }
    #[test]
2
    fn test_cancelling_idle_fsm() {
2
        let fault_handler = TestFaultHandler::default();
2
        let test_sender = TestCfdpSender::default();
2
        let mut dest_handler = default_dest_handler(fault_handler, test_sender, Arc::default());
2
        assert!(!dest_handler.cancel_request(&TransactionId::new(
2
            UnsignedByteFieldU8::new(0).into(),
2
            UnsignedByteFieldU8::new(0).into()
2
        )));
2
    }
    #[test]
2
    fn test_empty_file_transfer_not_acked_no_closure() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let mut test_user = tb.test_user_from_cached_paths(0);
2
        tb.generic_transfer_init(&mut test_user, 0)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.generic_eof_no_error(&mut test_user, Vec::new())
2
            .expect("EOF no error insertion failed");
2
        tb.check_completion_indication_success(&mut test_user);
2
    }
    #[test]
2
    fn test_small_file_transfer_not_acked() {
2
        let file_data_str = "Hello World!";
2
        let file_data = file_data_str.as_bytes();
2
        let file_size = file_data.len() as u64;
2
        let fault_handler = TestFaultHandler::default();
2

            
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let mut test_user = tb.test_user_from_cached_paths(file_size);
2
        tb.generic_transfer_init(&mut test_user, file_size)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.generic_file_data_insert(&mut test_user, 0, file_data)
2
            .expect("file data insertion failed");
2
        tb.generic_eof_no_error(&mut test_user, file_data.to_vec())
2
            .expect("EOF no error insertion failed");
2
        tb.check_completion_indication_success(&mut test_user);
2
    }
    #[test]
2
    fn test_segmented_file_transfer_not_acked() {
2
        let mut rng = rand::thread_rng();
2
        let mut random_data = [0u8; 512];
2
        rng.fill(&mut random_data);
2
        let file_size = random_data.len() as u64;
2
        let segment_len = 256;
2
        let fault_handler = TestFaultHandler::default();
2

            
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let mut test_user = tb.test_user_from_cached_paths(file_size);
2
        tb.generic_transfer_init(&mut test_user, file_size)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len])
2
            .expect("file data insertion failed");
2
        tb.generic_file_data_insert(
2
            &mut test_user,
2
            segment_len as u64,
2
            &random_data[segment_len..],
2
        )
2
        .expect("file data insertion failed");
2
        tb.generic_eof_no_error(&mut test_user, random_data.to_vec())
2
            .expect("EOF no error insertion failed");
2
        tb.check_completion_indication_success(&mut test_user);
2
    }
    #[test]
2
    fn test_check_limit_handling_transfer_success() {
2
        let mut rng = rand::thread_rng();
2
        let mut random_data = [0u8; 512];
2
        rng.fill(&mut random_data);
2
        let file_size = random_data.len() as u64;
2
        let segment_len = 256;
2
        let fault_handler = TestFaultHandler::default();
2

            
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let mut test_user = tb.test_user_from_cached_paths(file_size);
2
        let transaction_id = tb
2
            .generic_transfer_init(&mut test_user, file_size)
2
            .expect("transfer init failed");
2

            
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len])
2
            .expect("file data insertion 0 failed");
2
        tb.generic_eof_no_error(&mut test_user, random_data.to_vec())
2
            .expect("EOF no error insertion failed");
2
        tb.state_check(
2
            State::Busy,
2
            TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling,
2
        );
2
        tb.generic_file_data_insert(
2
            &mut test_user,
2
            segment_len as u64,
2
            &random_data[segment_len..],
2
        )
2
        .expect("file data insertion 1 failed");
2
        tb.set_check_timer_expired();
2
        tb.handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("fsm failure");
2
        let mut fault_handler = tb.handler.local_cfg.fault_handler.user_hook.borrow_mut();
2

            
2
        assert_eq!(fault_handler.ignored_queue.len(), 1);
2
        let cancelled = fault_handler.ignored_queue.pop_front().unwrap();
2
        assert_eq!(cancelled.0, transaction_id);
2
        assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure);
2
        assert_eq!(cancelled.2, segment_len as u64);
2
        drop(fault_handler);
2

            
2
        tb.check_completion_indication_success(&mut test_user);
2
    }
    #[test]
2
    fn test_check_limit_handling_limit_reached() {
2
        let mut rng = rand::thread_rng();
2
        let mut random_data = [0u8; 512];
2
        rng.fill(&mut random_data);
2
        let file_size = random_data.len() as u64;
2
        let segment_len = 256;
2

            
2
        let fault_handler = TestFaultHandler::default();
2
        let mut testbench = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let mut test_user = testbench.test_user_from_cached_paths(file_size);
2
        let transaction_id = testbench
2
            .generic_transfer_init(&mut test_user, file_size)
2
            .expect("transfer init failed");
2

            
2
        testbench.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        testbench
2
            .generic_file_data_insert(&mut test_user, 0, &random_data[0..segment_len])
2
            .expect("file data insertion 0 failed");
2
        testbench
2
            .generic_eof_no_error(&mut test_user, random_data.to_vec())
2
            .expect("EOF no error insertion failed");
2
        testbench.state_check(
2
            State::Busy,
2
            TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling,
2
        );
2
        testbench.set_check_timer_expired();
2
        testbench
2
            .handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("fsm error");
2
        testbench.state_check(
2
            State::Busy,
2
            TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling,
2
        );
2
        testbench.set_check_timer_expired();
2
        testbench
2
            .handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("fsm error");
2
        testbench.state_check(State::Idle, TransactionStep::Idle);
2

            
2
        let mut fault_hook = testbench.handler.local_cfg.user_fault_hook().borrow_mut();
2
        assert!(fault_hook.notice_of_suspension_queue.is_empty());
2
        let ignored_queue = &mut fault_hook.ignored_queue;
2
        assert_eq!(ignored_queue.len(), 1);
2
        let cancelled = ignored_queue.pop_front().unwrap();
2
        assert_eq!(cancelled.0, transaction_id);
2
        assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure);
2
        assert_eq!(cancelled.2, segment_len as u64);
2
        let cancelled_queue = &mut fault_hook.notice_of_cancellation_queue;
2
        assert_eq!(cancelled_queue.len(), 1);
2
        let cancelled = cancelled_queue.pop_front().unwrap();
2
        assert_eq!(cancelled.0, transaction_id);
2
        assert_eq!(cancelled.1, ConditionCode::CheckLimitReached);
2
        assert_eq!(cancelled.2, segment_len as u64);
2
        drop(fault_hook);
2

            
2
        assert!(testbench.handler.pdu_sender.queue_empty());
        // Check that the broken file exists.
2
        testbench.check_dest_file = false;
2
        assert!(Path::exists(testbench.dest_path()));
2
        let read_content = fs::read(testbench.dest_path()).expect("reading back string failed");
2
        assert_eq!(read_content.len(), segment_len);
2
        assert_eq!(read_content, &random_data[0..segment_len]);
2
        assert!(fs::remove_file(testbench.dest_path().as_path()).is_ok());
2
    }
2
    fn check_finished_pdu_success(sent_pdu: &SentPdu) {
2
        assert_eq!(sent_pdu.pdu_type, PduType::FileDirective);
2
        assert_eq!(
2
            sent_pdu.file_directive_type,
2
            Some(FileDirectiveType::FinishedPdu)
2
        );
2
        let finished_pdu = FinishedPduReader::from_bytes(&sent_pdu.raw_pdu).unwrap();
2
        assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
2
        assert_eq!(finished_pdu.condition_code(), ConditionCode::NoError);
2
        assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete);
2
        assert!(finished_pdu.fault_location().is_none());
2
        assert_eq!(finished_pdu.fs_responses_raw(), &[]);
2
    }
    #[test]
2
    fn test_file_transfer_with_closure() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
2
        let mut test_user = tb.test_user_from_cached_paths(0);
2
        tb.generic_transfer_init(&mut test_user, 0)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        let sent_packets = tb
2
            .generic_eof_no_error(&mut test_user, Vec::new())
2
            .expect("EOF no error insertion failed");
2
        assert_eq!(sent_packets, 1);
2
        assert!(tb.all_fault_queues_empty());
        // The Finished PDU was sent, so the state machine is done.
2
        tb.state_check(State::Idle, TransactionStep::Idle);
2
        assert!(!tb.handler.pdu_sender.queue_empty());
2
        let sent_pdu = tb.handler.pdu_sender.retrieve_next_pdu().unwrap();
2
        check_finished_pdu_success(&sent_pdu);
2
        tb.check_completion_indication_success(&mut test_user);
2
    }
    #[test]
2
    fn test_finished_pdu_insertion_rejected() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        tb.check_dest_file = false;
2
        let mut user = tb.test_user_from_cached_paths(0);
2
        let finished_pdu = FinishedPduCreator::new_default(
2
            PduHeader::new_no_file_data(CommonPduConfig::default(), 0),
2
            DeliveryCode::Complete,
2
            FileStatus::Retained,
2
        );
2
        let finished_pdu_raw = finished_pdu.to_vec().unwrap();
2
        let packet_info = PduRawWithInfo::new(&finished_pdu_raw).unwrap();
2
        let error = tb.handler.state_machine(&mut user, Some(&packet_info));
2
        assert!(error.is_err());
2
        let error = error.unwrap_err();
        if let DestError::CantProcessPacketType {
2
            pdu_type,
2
            directive_type,
2
        } = error
        {
2
            assert_eq!(pdu_type, PduType::FileDirective);
2
            assert_eq!(directive_type, Some(FileDirectiveType::FinishedPdu));
        }
2
    }
    #[test]
2
    fn test_metadata_insertion_twice_fails() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
2
        let mut user = tb.test_user_from_cached_paths(0);
2
        tb.generic_transfer_init(&mut user, 0)
2
            .expect("transfer init failed");
2
        tb.check_handler_idle_at_drop = false;
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        let metadata_pdu = create_metadata_pdu(
2
            &tb.pdu_header,
2
            tb.src_path.as_path(),
2
            tb.dest_path.as_path(),
2
            0,
2
            tb.closure_requested,
2
        );
2
        let packet_info = create_packet_info(&metadata_pdu, &mut tb.buf);
2
        let error = tb.handler.state_machine(&mut user, Some(&packet_info));
2
        assert!(error.is_err());
2
        let error = error.unwrap_err();
2
        if let DestError::RecvdMetadataButIsBusy = error {
2
        } else {
            panic!("unexpected error: {:?}", error);
        }
2
    }
    #[test]
2
    fn test_checksum_failure_not_acked() {
2
        let file_data_str = "Hello World!";
2
        let file_data = file_data_str.as_bytes();
2
        let file_size = file_data.len() as u64;
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
2
        let mut user = tb.test_user_from_cached_paths(file_size);
2
        tb.generic_transfer_init(&mut user, file_size)
2
            .expect("transfer init failed");
2
        let faulty_file_data = b"Hemlo World!";
2
        assert_eq!(
2
            tb.generic_file_data_insert(&mut user, 0, faulty_file_data)
2
                .expect("file data insertion failed"),
2
            0
2
        );
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        let sent_packets = tb
2
            .generic_eof_no_error(&mut user, file_data.into())
2
            .expect("EOF no error insertion failed");
2
        // FSM enters check limit algorithm here, so no finished PDU is created.
2
        assert_eq!(sent_packets, 0);
2
        let transaction_id = tb.handler.transaction_id().unwrap();
2
        let mut fault_hook = tb.handler.local_cfg.user_fault_hook().borrow_mut();
2
        assert!(fault_hook.notice_of_suspension_queue.is_empty());
        // The file checksum failure is ignored by default and check limit handling is now
        // performed.
2
        let ignored_queue = &mut fault_hook.ignored_queue;
2
        assert_eq!(ignored_queue.len(), 1);
2
        let cancelled = ignored_queue.pop_front().unwrap();
2
        assert_eq!(cancelled.0, transaction_id);
2
        assert_eq!(cancelled.1, ConditionCode::FileChecksumFailure);
2
        assert_eq!(cancelled.2, file_size);
2
        drop(fault_hook);
2

            
2
        tb.state_check(
2
            State::Busy,
2
            TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling,
2
        );
2
        tb.set_check_timer_expired();
2
        tb.handler
2
            .state_machine_no_packet(&mut user)
2
            .expect("fsm error");
2
        tb.state_check(
2
            State::Busy,
2
            TransactionStep::ReceivingFileDataPdusWithCheckLimitHandling,
2
        );
2
        tb.set_check_timer_expired();
2
        tb.handler
2
            .state_machine_no_packet(&mut user)
2
            .expect("fsm error");
2
        tb.state_check(State::Idle, TransactionStep::Idle);
2

            
2
        // Transaction is cancelled because the check limit is reached.
2
        let mut fault_hook = tb.handler.local_cfg.user_fault_hook().borrow_mut();
2
        let cancelled_queue = &mut fault_hook.notice_of_cancellation_queue;
2
        assert_eq!(cancelled_queue.len(), 1);
2
        let cancelled = cancelled_queue.pop_front().unwrap();
2
        assert_eq!(cancelled.0, transaction_id);
2
        assert_eq!(cancelled.1, ConditionCode::CheckLimitReached);
2
        assert_eq!(cancelled.2, file_size);
2
        drop(fault_hook);
2

            
2
        let sent_pdu = tb.handler.pdu_sender.retrieve_next_pdu().unwrap();
2
        assert_eq!(sent_pdu.pdu_type, PduType::FileDirective);
2
        assert_eq!(
2
            sent_pdu.file_directive_type,
2
            Some(FileDirectiveType::FinishedPdu)
2
        );
2
        let finished_pdu = FinishedPduReader::from_bytes(&sent_pdu.raw_pdu).unwrap();
2
        assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
2
        assert_eq!(
2
            finished_pdu.condition_code(),
2
            ConditionCode::CheckLimitReached
2
        );
2
        assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Incomplete);
2
        assert!(finished_pdu.fault_location().is_some());
2
        assert_eq!(
2
            *finished_pdu.fault_location().unwrap().entity_id(),
2
            REMOTE_ID.into()
2
        );
2
        assert_eq!(finished_pdu.fs_responses_raw(), &[]);
2
        assert!(tb.handler.pdu_sender.queue_empty());
2
        tb.expected_full_data = faulty_file_data.to_vec();
2
    }
    #[test]
2
    fn test_file_copy_to_directory() {
2
        let fault_handler = TestFaultHandler::default();
2
        let src_path = tempfile::TempPath::from_path("/tmp/test.txt").to_path_buf();
2
        let dest_path = tempfile::TempDir::new().unwrap();
2
        let mut dest_path_buf = dest_path.into_path();
2
        let mut tb = DestHandlerTestbench::new(
2
            fault_handler,
2
            false,
2
            false,
2
            src_path.clone(),
2
            dest_path_buf.clone(),
2
        );
2
        dest_path_buf.push(src_path.file_name().unwrap());
2
        tb.dest_path = dest_path_buf;
2
        let mut test_user = tb.test_user_from_cached_paths(0);
2
        tb.generic_transfer_init(&mut test_user, 0)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.generic_eof_no_error(&mut test_user, Vec::new())
2
            .expect("EOF no error insertion failed");
2
        tb.check_completion_indication_success(&mut test_user);
2
    }
    #[test]
2
    fn test_tranfer_cancellation_empty_file_with_eof_pdu() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let mut test_user = tb.test_user_from_cached_paths(0);
2
        tb.generic_transfer_init(&mut test_user, 0)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        let cancel_eof = EofPdu::new(
2
            tb.pdu_header,
2
            ConditionCode::CancelRequestReceived,
2
            0,
2
            0,
2
            Some(EntityIdTlv::new(LOCAL_ID.into())),
2
        );
2
        let packets = tb
2
            .handler
2
            .state_machine(
2
                &mut test_user,
2
                Some(&PduRawWithInfo::new(&cancel_eof.to_vec().unwrap()).unwrap()),
2
            )
2
            .expect("state machine call with EOF insertion failed");
2
        assert_eq!(packets, 0);
2
    }
4
    fn generic_tranfer_cancellation_partial_file_with_eof_pdu(with_closure: bool) {
4
        let file_data_str = "Hello World!";
4
        let file_data = file_data_str.as_bytes();
4
        let file_size = 5;
4

            
4
        let fault_handler = TestFaultHandler::default();
4
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, with_closure);
4
        let mut test_user = tb.test_user_from_cached_paths(file_size);
4
        tb.generic_transfer_init(&mut test_user, file_size)
4
            .expect("transfer init failed");
4
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
4
        tb.generic_file_data_insert(&mut test_user, 0, &file_data[0..5])
4
            .expect("file data insertion failed");
4
        // Checksum is currently ignored on remote side.. we still supply it, according to the
4
        // standard.
4
        let mut digest = CRC_32.digest();
4
        digest.update(&file_data[0..5]);
4
        let checksum = digest.finalize();
4
        let cancel_eof = EofPdu::new(
4
            tb.pdu_header,
4
            ConditionCode::CancelRequestReceived,
4
            checksum,
4
            5,
4
            Some(EntityIdTlv::new(LOCAL_ID.into())),
4
        );
4
        let packets = tb
4
            .handler
4
            .state_machine(
4
                &mut test_user,
4
                Some(&PduRawWithInfo::new(&cancel_eof.to_vec().unwrap()).unwrap()),
4
            )
4
            .expect("state machine call with EOF insertion failed");
4
        if with_closure {
2
            assert_eq!(packets, 1);
2
            let finished_pdu = tb.handler.pdu_sender.retrieve_next_pdu().unwrap();
2
            assert_eq!(finished_pdu.pdu_type, PduType::FileDirective);
2
            assert_eq!(
2
                finished_pdu.file_directive_type.unwrap(),
2
                FileDirectiveType::FinishedPdu
2
            );
2
            let finished_pdu = FinishedPduReader::from_bytes(&finished_pdu.raw_pdu).unwrap();
2
            assert_eq!(
2
                finished_pdu.condition_code(),
2
                ConditionCode::CancelRequestReceived
2
            );
2
            assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Incomplete);
2
            assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
2
            assert_eq!(
2
                finished_pdu
2
                    .fault_location()
2
                    .expect("no fault location set"),
2
                EntityIdTlv::new(LOCAL_ID.into())
2
            );
        } else {
2
            assert_eq!(packets, 0);
        }
4
        tb.expected_file_size = file_size;
4
        tb.expected_full_data = file_data[0..file_size as usize].to_vec();
4
    }
    #[test]
2
    fn test_tranfer_cancellation_partial_file_with_eof_pdu_no_closure() {
2
        generic_tranfer_cancellation_partial_file_with_eof_pdu(false);
2
    }
    #[test]
2
    fn test_tranfer_cancellation_partial_file_with_eof_pdu_with_closure() {
2
        generic_tranfer_cancellation_partial_file_with_eof_pdu(true);
2
    }
    #[test]
2
    fn test_tranfer_cancellation_empty_file_with_cancel_api() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let mut test_user = tb.test_user_from_cached_paths(0);
2
        let transaction_id = tb
2
            .generic_transfer_init(&mut test_user, 0)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.handler.cancel_request(&transaction_id);
2
        let packets = tb
2
            .handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("state machine call with EOF insertion failed");
2
        assert_eq!(packets, 0);
2
    }
    #[test]
2
    fn test_tranfer_cancellation_empty_file_with_cancel_api_and_closure() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
2
        let mut test_user = tb.test_user_from_cached_paths(0);
2
        let transaction_id = tb
2
            .generic_transfer_init(&mut test_user, 0)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.handler.cancel_request(&transaction_id);
2
        let packets = tb
2
            .handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("state machine call with EOF insertion failed");
2
        assert_eq!(packets, 1);
2
        let next_pdu = tb.get_next_pdu().unwrap();
2
        assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
2
        assert_eq!(
2
            next_pdu.file_directive_type.unwrap(),
2
            FileDirectiveType::FinishedPdu
2
        );
2
        let finished_pdu =
2
            FinishedPduReader::new(&next_pdu.raw_pdu).expect("finished pdu read failed");
2
        assert_eq!(
2
            finished_pdu.condition_code(),
2
            ConditionCode::CancelRequestReceived
2
        );
2
        assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
        // Empty file, so still complete.
2
        assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Complete);
2
        assert_eq!(
2
            finished_pdu.fault_location(),
2
            Some(EntityIdTlv::new(REMOTE_ID.into()))
2
        );
2
    }
    #[test]
2
    fn test_tranfer_cancellation_partial_file_with_cancel_api_and_closure() {
2
        let file_data_str = "Hello World!";
2
        let file_data = file_data_str.as_bytes();
2
        let file_size = 5;
2

            
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, true);
2
        let mut test_user = tb.test_user_from_cached_paths(file_size);
2
        let transaction_id = tb
2
            .generic_transfer_init(&mut test_user, file_size)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.generic_file_data_insert(&mut test_user, 0, &file_data[0..5])
2
            .expect("file data insertion failed");
2

            
2
        tb.handler.cancel_request(&transaction_id);
2
        let packets = tb
2
            .handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("state machine call with EOF insertion failed");
2
        assert_eq!(packets, 1);
2
        let next_pdu = tb.get_next_pdu().unwrap();
2
        assert_eq!(next_pdu.pdu_type, PduType::FileDirective);
2
        assert_eq!(
2
            next_pdu.file_directive_type.unwrap(),
2
            FileDirectiveType::FinishedPdu
2
        );
2
        let finished_pdu =
2
            FinishedPduReader::new(&next_pdu.raw_pdu).expect("finished pdu read failed");
2
        assert_eq!(
2
            finished_pdu.condition_code(),
2
            ConditionCode::CancelRequestReceived
2
        );
2
        assert_eq!(finished_pdu.file_status(), FileStatus::Retained);
2
        assert_eq!(finished_pdu.delivery_code(), DeliveryCode::Incomplete);
2
        assert_eq!(
2
            finished_pdu.fault_location(),
2
            Some(EntityIdTlv::new(REMOTE_ID.into()))
2
        );
2
        tb.expected_file_size = file_size;
2
        tb.expected_full_data = file_data[0..file_size as usize].to_vec();
2
    }
    // Only incomplete received files will be removed.
    #[test]
2
    fn test_tranfer_cancellation_file_disposition_not_done_for_empty_file() {
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        let remote_cfg_mut = tb
2
            .handler
2
            .remote_cfg_table
2
            .get_mut(LOCAL_ID.value())
2
            .unwrap();
2
        remote_cfg_mut.disposition_on_cancellation = true;
2
        let mut test_user = tb.test_user_from_cached_paths(0);
2
        let transaction_id = tb
2
            .generic_transfer_init(&mut test_user, 0)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2

            
2
        tb.handler.cancel_request(&transaction_id);
2
        let packets = tb
2
            .handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("state machine call with EOF insertion failed");
2
        assert_eq!(packets, 0);
2
    }
    #[test]
2
    fn test_tranfer_cancellation_file_disposition_not_done_for_incomplete_file() {
2
        let file_data_str = "Hello World!";
2
        let file_data = file_data_str.as_bytes();
2
        let file_size = file_data.len() as u64;
2

            
2
        let fault_handler = TestFaultHandler::default();
2
        let mut tb = DestHandlerTestbench::new_with_fixed_paths(fault_handler, false);
2
        tb.check_dest_file = false;
2
        let remote_cfg_mut = tb
2
            .handler
2
            .remote_cfg_table
2
            .get_mut(LOCAL_ID.value())
2
            .unwrap();
2
        remote_cfg_mut.disposition_on_cancellation = true;
2
        let mut test_user = tb.test_user_from_cached_paths(file_size);
2
        let transaction_id = tb
2
            .generic_transfer_init(&mut test_user, file_size)
2
            .expect("transfer init failed");
2
        tb.state_check(State::Busy, TransactionStep::ReceivingFileDataPdus);
2
        tb.generic_file_data_insert(&mut test_user, 0, &file_data[0..5])
2
            .expect("file data insertion failed");
2

            
2
        tb.handler.cancel_request(&transaction_id);
2
        let packets = tb
2
            .handler
2
            .state_machine_no_packet(&mut test_user)
2
            .expect("state machine call with EOF insertion failed");
2
        assert_eq!(packets, 0);
        // File was disposed.
2
        assert!(!Path::exists(tb.dest_path()));
2
    }
}