satrs/cfdp/
mod.rs

1//! This module contains the implementation of the CFDP high level classes as specified in the
2//! CCSDS 727.0-B-5.
3use core::{cell::RefCell, fmt::Debug, hash::Hash};
4
5use crc::{Crc, CRC_32_CKSUM};
6use hashbrown::HashMap;
7use spacepackets::{
8    cfdp::{
9        pdu::{FileDirectiveType, PduError, PduHeader},
10        ChecksumType, ConditionCode, FaultHandlerCode, PduType, TransmissionMode,
11    },
12    util::{UnsignedByteField, UnsignedEnum},
13};
14
15#[cfg(feature = "alloc")]
16use alloc::boxed::Box;
17#[cfg(feature = "serde")]
18use serde::{Deserialize, Serialize};
19
20use crate::time::CountdownProvider;
21
22#[cfg(feature = "std")]
23pub mod dest;
24#[cfg(feature = "alloc")]
25pub mod filestore;
26#[cfg(feature = "std")]
27pub mod source;
28pub mod user;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum EntityType {
32    Sending,
33    Receiving,
34}
35
36pub enum TimerContext {
37    CheckLimit {
38        local_id: UnsignedByteField,
39        remote_id: UnsignedByteField,
40        entity_type: EntityType,
41    },
42    NakActivity {
43        expiry_time_seconds: f32,
44    },
45    PositiveAck {
46        expiry_time_seconds: f32,
47    },
48}
49
50/// A generic trait which allows CFDP entities to create check timers which are required to
51/// implement special procedures in unacknowledged transmission mode, as specified in 4.6.3.2
52/// and 4.6.3.3.
53///
54/// This trait also allows the creation of different check timers depending on context and purpose
55/// of the timer, the runtime environment (e.g. standard clock timer vs. timer using a RTC) or
56/// other factors.
57///
58/// The countdown timer is used by 3 mechanisms of the CFDP protocol.
59///
60/// ## 1. Check limit handling
61///
62/// The first mechanism is the check limit handling for unacknowledged transfers as specified
63/// in 4.6.3.2 and 4.6.3.3 of the CFDP standard.
64/// For this mechanism, the timer has different functionality depending on whether
65/// the using entity is the sending entity or the receiving entity for the unacknowledged
66/// transmission mode.
67///
68/// For the sending entity, this timer determines the expiry period for declaring a check limit
69/// fault after sending an EOF PDU with requested closure. This allows a timeout of the transfer.
70/// Also see 4.6.3.2 of the CFDP standard.
71///
72/// For the receiving entity, this timer determines the expiry period for incrementing a check
73/// counter after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
74/// reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard.
75///
76/// ## 2. NAK activity limit
77///
78/// The timer will be used to perform the NAK activity check as specified in 4.6.4.7 of the CFDP
79/// standard. The expiration period will be provided by the NAK timer expiration limit of the
80/// remote entity configuration.
81///
82/// ## 3. Positive ACK procedures
83///
84/// The timer will be used to perform the Positive Acknowledgement Procedures as  specified in
85/// 4.7. 1of the CFDP standard. The expiration period will be provided by the Positive ACK timer
86/// interval of the remote entity configuration.
87#[cfg(feature = "alloc")]
88pub trait CheckTimerCreator {
89    fn get_check_timer_provider(&self, timer_context: TimerContext) -> Box<dyn CountdownProvider>;
90}
91
92/// Simple implementation of the [CheckTimerCreator] trait assuming a standard runtime.
93/// It also assumes that a second accuracy of the check timer period is sufficient.
94#[cfg(feature = "std")]
95#[derive(Debug)]
96pub struct StdCheckTimer {
97    expiry_time_seconds: u64,
98    start_time: std::time::Instant,
99}
100
101#[cfg(feature = "std")]
102impl StdCheckTimer {
103    pub fn new(expiry_time_seconds: u64) -> Self {
104        Self {
105            expiry_time_seconds,
106            start_time: std::time::Instant::now(),
107        }
108    }
109}
110
111#[cfg(feature = "std")]
112impl CountdownProvider for StdCheckTimer {
113    fn has_expired(&self) -> bool {
114        let elapsed_time = self.start_time.elapsed();
115        if elapsed_time.as_secs() > self.expiry_time_seconds {
116            return true;
117        }
118        false
119    }
120
121    fn reset(&mut self) {
122        self.start_time = std::time::Instant::now();
123    }
124}
125
126/// This structure models the remote entity configuration information as specified in chapter 8.3
127/// of the CFDP standard.
128
129/// Some of the fields which were not considered necessary for the Rust implementation
130/// were omitted. Some other fields which are not contained inside the standard but are considered
131/// necessary for the Rust implementation are included.
132///
133/// ## Notes on Positive Acknowledgment Procedures
134///
135/// The `positive_ack_timer_interval_seconds` and `positive_ack_timer_expiration_limit` will
136/// be used for positive acknowledgement procedures as specified in CFDP chapter 4.7. The sending
137/// entity will start the timer for any PDUs where an acknowledgment is required (e.g. EOF PDU).
138/// Once the expected ACK response has not been received for that interval, as counter will be
139/// incremented and the timer will be reset. Once the counter exceeds the
140/// `positive_ack_timer_expiration_limit`, a Positive ACK Limit Reached fault will be declared.
141///
142/// ## Notes on Deferred Lost Segment Procedures
143///
144/// This procedure will be active if an EOF (No Error) PDU is received in acknowledged mode. After
145/// issuing the NAK sequence which has the whole file scope, a timer will be started. The timer is
146/// reset when missing segments or missing metadata is received. The timer will be deactivated if
147/// all missing data is received. If the timer expires, a new NAK sequence will be issued and a
148/// counter will be incremented, which can lead to a NAK Limit Reached fault being declared.
149///
150/// ## Fields
151///
152/// * `entity_id` - The ID of the remote entity.
153/// * `max_packet_len` - This determines of all PDUs generated for that remote entity in addition
154///    to the `max_file_segment_len` attribute which also determines the size of file data PDUs.
155/// * `max_file_segment_len` The maximum file segment length which determines the maximum size
156///   of file data PDUs in addition to the `max_packet_len` attribute. If this field is set
157///   to None, the maximum file segment length will be derived from the maximum packet length.
158///   If this has some value which is smaller than the segment value derived from
159///   `max_packet_len`, this value will be picked.
160/// * `closure_requested_by_default` - If the closure requested field is not supplied as part of
161///    the Put Request, it will be determined from this field in the remote configuration.
162/// * `crc_on_transmission_by_default` - If the CRC option is not supplied as part of the Put
163///    Request, it will be determined from this field in the remote configuration.
164/// * `default_transmission_mode` - If the transmission mode is not supplied as part of the
165///   Put Request, it will be determined from this field in the remote configuration.
166/// * `disposition_on_cancellation` - Determines whether an incomplete received file is discard on
167///   transaction cancellation. Defaults to False.
168/// * `default_crc_type` - Default checksum type used to calculate for all file transmissions to
169///   this remote entity.
170/// * `check_limit` - This timer determines the expiry period for incrementing a check counter
171///   after an EOF PDU is received for an incomplete file transfer. This allows out-of-order
172///   reception of file data PDUs and EOF PDUs. Also see 4.6.3.3 of the CFDP standard. Defaults to
173///   2, so the check limit timer may expire twice.
174/// * `positive_ack_timer_interval_seconds`- See the notes on the Positive Acknowledgment
175///    Procedures inside the class documentation. Expected as floating point seconds. Defaults to
176///    10 seconds.
177/// * `positive_ack_timer_expiration_limit` - See the notes on the Positive Acknowledgment
178///    Procedures inside the class documentation. Defaults to 2, so the timer may expire twice.
179/// * `immediate_nak_mode` - Specifies whether a NAK sequence should be issued immediately when a
180///    file data gap or lost metadata is detected in the acknowledged mode. Defaults to True.
181/// * `nak_timer_interval_seconds` -  See the notes on the Deferred Lost Segment Procedure inside
182///    the class documentation. Expected as floating point seconds. Defaults to 10 seconds.
183/// * `nak_timer_expiration_limit` - See the notes on the Deferred Lost Segment Procedure inside
184///    the class documentation. Defaults to 2, so the timer may expire two times.
185#[derive(Debug, Copy, Clone)]
186pub struct RemoteEntityConfig {
187    pub entity_id: UnsignedByteField,
188    pub max_packet_len: usize,
189    pub max_file_segment_len: usize,
190    pub closure_requested_by_default: bool,
191    pub crc_on_transmission_by_default: bool,
192    pub default_transmission_mode: TransmissionMode,
193    pub default_crc_type: ChecksumType,
194    pub positive_ack_timer_interval_seconds: f32,
195    pub positive_ack_timer_expiration_limit: u32,
196    pub check_limit: u32,
197    pub disposition_on_cancellation: bool,
198    pub immediate_nak_mode: bool,
199    pub nak_timer_interval_seconds: f32,
200    pub nak_timer_expiration_limit: u32,
201}
202
203impl RemoteEntityConfig {
204    pub fn new_with_default_values(
205        entity_id: UnsignedByteField,
206        max_file_segment_len: usize,
207        max_packet_len: usize,
208        closure_requested_by_default: bool,
209        crc_on_transmission_by_default: bool,
210        default_transmission_mode: TransmissionMode,
211        default_crc_type: ChecksumType,
212    ) -> Self {
213        Self {
214            entity_id,
215            max_file_segment_len,
216            max_packet_len,
217            closure_requested_by_default,
218            crc_on_transmission_by_default,
219            default_transmission_mode,
220            default_crc_type,
221            check_limit: 2,
222            positive_ack_timer_interval_seconds: 10.0,
223            positive_ack_timer_expiration_limit: 2,
224            disposition_on_cancellation: false,
225            immediate_nak_mode: true,
226            nak_timer_interval_seconds: 10.0,
227            nak_timer_expiration_limit: 2,
228        }
229    }
230}
231
232pub trait RemoteEntityConfigProvider {
233    /// Retrieve the remote entity configuration for the given remote ID.
234    fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig>;
235    fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig>;
236    /// Add a new remote configuration. Return [true] if the configuration was
237    /// inserted successfully, and [false] if a configuration already exists.
238    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool;
239    /// Remote a configuration. Returns [true] if the configuration was removed successfully,
240    /// and [false] if no configuration exists for the given remote ID.
241    fn remove_config(&mut self, remote_id: u64) -> bool;
242}
243
244#[cfg(feature = "std")]
245#[derive(Default)]
246pub struct StdRemoteEntityConfigProvider {
247    remote_cfg_table: HashMap<u64, RemoteEntityConfig>,
248}
249
250#[cfg(feature = "std")]
251impl RemoteEntityConfigProvider for StdRemoteEntityConfigProvider {
252    fn get_remote_config(&self, remote_id: u64) -> Option<&RemoteEntityConfig> {
253        self.remote_cfg_table.get(&remote_id)
254    }
255    fn get_remote_config_mut(&mut self, remote_id: u64) -> Option<&mut RemoteEntityConfig> {
256        self.remote_cfg_table.get_mut(&remote_id)
257    }
258    fn add_config(&mut self, cfg: &RemoteEntityConfig) -> bool {
259        self.remote_cfg_table
260            .insert(cfg.entity_id.value(), *cfg)
261            .is_some()
262    }
263    fn remove_config(&mut self, remote_id: u64) -> bool {
264        self.remote_cfg_table.remove(&remote_id).is_some()
265    }
266}
267
268/// This trait introduces some callbacks which will be called when a particular CFDP fault
269/// handler is called.
270///
271/// It is passed into the CFDP handlers as part of the [DefaultFaultHandler] and the local entity
272/// configuration and provides a way to specify custom user error handlers. This allows to
273/// implement some CFDP features like fault handler logging, which would not be possible
274/// generically otherwise.
275///
276/// For each error reported by the [DefaultFaultHandler], the appropriate fault handler callback
277/// will be called depending on the [FaultHandlerCode].
278pub trait UserFaultHandler {
279    fn notice_of_suspension_cb(
280        &mut self,
281        transaction_id: TransactionId,
282        cond: ConditionCode,
283        progress: u64,
284    );
285
286    fn notice_of_cancellation_cb(
287        &mut self,
288        transaction_id: TransactionId,
289        cond: ConditionCode,
290        progress: u64,
291    );
292
293    fn abandoned_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
294
295    fn ignore_cb(&mut self, transaction_id: TransactionId, cond: ConditionCode, progress: u64);
296}
297
298/// This structure is used to implement the fault handling as specified in chapter 4.8 of the CFDP
299/// standard.
300///
301/// It does so by mapping each applicable [spacepackets::cfdp::ConditionCode] to a fault handler
302/// which is denoted by the four [spacepackets::cfdp::FaultHandlerCode]s. This code is used
303/// to select the error handling inside the CFDP handler itself in addition to dispatching to a
304/// user-provided callback function provided by the [UserFaultHandler].
305///
306/// Some note on the provided default settings:
307///
308/// - Checksum failures will be ignored by default. This is because for unacknowledged transfers,
309///   cancelling the transfer immediately would interfere with the check limit mechanism specified
310///   in chapter 4.6.3.3.
311/// - Unsupported checksum types will also be ignored by default. Even if the checksum type is
312///   not supported the file transfer might still have worked properly.
313///
314/// For all other faults, the default fault handling operation will be to cancel the transaction.
315/// These defaults can be overriden by using the [Self::set_fault_handler] method.
316/// Please note that in any case, fault handler overrides can be specified by the sending CFDP
317/// entity.
318pub struct DefaultFaultHandler {
319    handler_array: [FaultHandlerCode; 10],
320    // Could also change the user fault handler trait to have non mutable methods, but that limits
321    // flexbility on the user side..
322    user_fault_handler: RefCell<Box<dyn UserFaultHandler + Send>>,
323}
324
325impl DefaultFaultHandler {
326    fn condition_code_to_array_index(conditon_code: ConditionCode) -> Option<usize> {
327        Some(match conditon_code {
328            ConditionCode::PositiveAckLimitReached => 0,
329            ConditionCode::KeepAliveLimitReached => 1,
330            ConditionCode::InvalidTransmissionMode => 2,
331            ConditionCode::FilestoreRejection => 3,
332            ConditionCode::FileChecksumFailure => 4,
333            ConditionCode::FileSizeError => 5,
334            ConditionCode::NakLimitReached => 6,
335            ConditionCode::InactivityDetected => 7,
336            ConditionCode::CheckLimitReached => 8,
337            ConditionCode::UnsupportedChecksumType => 9,
338            _ => return None,
339        })
340    }
341
342    pub fn set_fault_handler(
343        &mut self,
344        condition_code: ConditionCode,
345        fault_handler: FaultHandlerCode,
346    ) {
347        let array_idx = Self::condition_code_to_array_index(condition_code);
348        if array_idx.is_none() {
349            return;
350        }
351        self.handler_array[array_idx.unwrap()] = fault_handler;
352    }
353
354    pub fn new(user_fault_handler: Box<dyn UserFaultHandler + Send>) -> Self {
355        let mut init_array = [FaultHandlerCode::NoticeOfCancellation; 10];
356        init_array
357            [Self::condition_code_to_array_index(ConditionCode::FileChecksumFailure).unwrap()] =
358            FaultHandlerCode::IgnoreError;
359        init_array[Self::condition_code_to_array_index(ConditionCode::UnsupportedChecksumType)
360            .unwrap()] = FaultHandlerCode::IgnoreError;
361        Self {
362            handler_array: init_array,
363            user_fault_handler: RefCell::new(user_fault_handler),
364        }
365    }
366
367    pub fn get_fault_handler(&self, condition_code: ConditionCode) -> FaultHandlerCode {
368        let array_idx = Self::condition_code_to_array_index(condition_code);
369        if array_idx.is_none() {
370            return FaultHandlerCode::IgnoreError;
371        }
372        self.handler_array[array_idx.unwrap()]
373    }
374
375    pub fn report_fault(
376        &self,
377        transaction_id: TransactionId,
378        condition: ConditionCode,
379        progress: u64,
380    ) -> FaultHandlerCode {
381        let array_idx = Self::condition_code_to_array_index(condition);
382        if array_idx.is_none() {
383            return FaultHandlerCode::IgnoreError;
384        }
385        let fh_code = self.handler_array[array_idx.unwrap()];
386        let mut handler_mut = self.user_fault_handler.borrow_mut();
387        match fh_code {
388            FaultHandlerCode::NoticeOfCancellation => {
389                handler_mut.notice_of_cancellation_cb(transaction_id, condition, progress);
390            }
391            FaultHandlerCode::NoticeOfSuspension => {
392                handler_mut.notice_of_suspension_cb(transaction_id, condition, progress);
393            }
394            FaultHandlerCode::IgnoreError => {
395                handler_mut.ignore_cb(transaction_id, condition, progress);
396            }
397            FaultHandlerCode::AbandonTransaction => {
398                handler_mut.abandoned_cb(transaction_id, condition, progress);
399            }
400        }
401        fh_code
402    }
403}
404
405pub struct IndicationConfig {
406    pub eof_sent: bool,
407    pub eof_recv: bool,
408    pub file_segment_recv: bool,
409    pub transaction_finished: bool,
410    pub suspended: bool,
411    pub resumed: bool,
412}
413
414impl Default for IndicationConfig {
415    fn default() -> Self {
416        Self {
417            eof_sent: true,
418            eof_recv: true,
419            file_segment_recv: true,
420            transaction_finished: true,
421            suspended: true,
422            resumed: true,
423        }
424    }
425}
426
427pub struct LocalEntityConfig {
428    pub id: UnsignedByteField,
429    pub indication_cfg: IndicationConfig,
430    pub default_fault_handler: DefaultFaultHandler,
431}
432
433/// The CFDP transaction ID of a CFDP transaction consists of the source entity ID and the sequence
434/// number of that transfer which is also determined by the CFDP source entity.
435#[derive(Debug, Eq, Copy, Clone)]
436#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
437pub struct TransactionId {
438    source_id: UnsignedByteField,
439    seq_num: UnsignedByteField,
440}
441
442impl TransactionId {
443    pub fn new(source_id: UnsignedByteField, seq_num: UnsignedByteField) -> Self {
444        Self { source_id, seq_num }
445    }
446
447    pub fn source_id(&self) -> &UnsignedByteField {
448        &self.source_id
449    }
450
451    pub fn seq_num(&self) -> &UnsignedByteField {
452        &self.seq_num
453    }
454}
455
456impl Hash for TransactionId {
457    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
458        self.source_id.value().hash(state);
459        self.seq_num.value().hash(state);
460    }
461}
462
463impl PartialEq for TransactionId {
464    fn eq(&self, other: &Self) -> bool {
465        self.source_id.value() == other.source_id.value()
466            && self.seq_num.value() == other.seq_num.value()
467    }
468}
469
470#[derive(Debug, Copy, Clone, PartialEq, Eq)]
471#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
472pub enum TransactionStep {
473    Idle = 0,
474    TransactionStart = 1,
475    ReceivingFileDataPdus = 2,
476    ReceivingFileDataPdusWithCheckLimitHandling = 3,
477    SendingAckPdu = 4,
478    TransferCompletion = 5,
479    SendingFinishedPdu = 6,
480}
481
482#[derive(Debug, Copy, Clone, PartialEq, Eq)]
483#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
484pub enum State {
485    Idle = 0,
486    Busy = 1,
487    Suspended = 2,
488}
489
490pub const CRC_32: Crc<u32> = Crc::<u32>::new(&CRC_32_CKSUM);
491
492#[derive(Debug, PartialEq, Eq, Copy, Clone)]
493#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
494pub enum PacketTarget {
495    SourceEntity,
496    DestEntity,
497}
498
499/// This is a helper struct which contains base information about a particular PDU packet.
500/// This is also necessary information for CFDP packet routing. For example, some packet types
501/// like file data PDUs can only be used by CFDP source entities.
502pub struct PacketInfo<'raw_packet> {
503    pdu_type: PduType,
504    pdu_directive: Option<FileDirectiveType>,
505    target: PacketTarget,
506    raw_packet: &'raw_packet [u8],
507}
508
509impl<'raw> PacketInfo<'raw> {
510    pub fn new(raw_packet: &'raw [u8]) -> Result<Self, PduError> {
511        let (pdu_header, header_len) = PduHeader::from_bytes(raw_packet)?;
512        if pdu_header.pdu_type() == PduType::FileData {
513            return Ok(Self {
514                pdu_type: pdu_header.pdu_type(),
515                pdu_directive: None,
516                target: PacketTarget::DestEntity,
517                raw_packet,
518            });
519        }
520        if pdu_header.pdu_datafield_len() < 1 {
521            return Err(PduError::FormatError);
522        }
523        // Route depending on PDU type and directive type if applicable. Retrieve directive type
524        // from the raw stream for better performance (with sanity and directive code check).
525        // The routing is based on section 4.5 of the CFDP standard which specifies the PDU forwarding
526        // procedure.
527        let directive = FileDirectiveType::try_from(raw_packet[header_len]).map_err(|_| {
528            PduError::InvalidDirectiveType {
529                found: raw_packet[header_len],
530                expected: None,
531            }
532        })?;
533        let packet_target = match directive {
534            // Section c) of 4.5.3: These PDUs should always be targeted towards the file sender a.k.a.
535            // the source handler
536            FileDirectiveType::NakPdu
537            | FileDirectiveType::FinishedPdu
538            | FileDirectiveType::KeepAlivePdu => PacketTarget::SourceEntity,
539            // Section b) of 4.5.3: These PDUs should always be targeted towards the file receiver a.k.a.
540            // the destination handler
541            FileDirectiveType::MetadataPdu
542            | FileDirectiveType::EofPdu
543            | FileDirectiveType::PromptPdu => PacketTarget::DestEntity,
544            // Section a): Recipient depends of the type of PDU that is being acknowledged. We can simply
545            // extract the PDU type from the raw stream. If it is an EOF PDU, this packet is passed to
546            // the source handler, for a Finished PDU, it is passed to the destination handler.
547            FileDirectiveType::AckPdu => {
548                let acked_directive = FileDirectiveType::try_from(raw_packet[header_len + 1])
549                    .map_err(|_| PduError::InvalidDirectiveType {
550                        found: raw_packet[header_len],
551                        expected: None,
552                    })?;
553                if acked_directive == FileDirectiveType::EofPdu {
554                    PacketTarget::SourceEntity
555                } else if acked_directive == FileDirectiveType::FinishedPdu {
556                    PacketTarget::DestEntity
557                } else {
558                    // TODO: Maybe a better error? This might be confusing..
559                    return Err(PduError::InvalidDirectiveType {
560                        found: raw_packet[header_len + 1],
561                        expected: None,
562                    });
563                }
564            }
565        };
566        Ok(Self {
567            pdu_type: pdu_header.pdu_type(),
568            pdu_directive: Some(directive),
569            target: packet_target,
570            raw_packet,
571        })
572    }
573
574    pub fn pdu_type(&self) -> PduType {
575        self.pdu_type
576    }
577
578    pub fn pdu_directive(&self) -> Option<FileDirectiveType> {
579        self.pdu_directive
580    }
581
582    pub fn target(&self) -> PacketTarget {
583        self.target
584    }
585
586    pub fn raw_packet(&self) -> &[u8] {
587        self.raw_packet
588    }
589}
590
591#[cfg(test)]
592mod tests {
593    use spacepackets::cfdp::{
594        lv::Lv,
595        pdu::{
596            eof::EofPdu,
597            file_data::FileDataPdu,
598            metadata::{MetadataGenericParams, MetadataPduCreator},
599            CommonPduConfig, FileDirectiveType, PduHeader, WritablePduPacket,
600        },
601        PduType,
602    };
603
604    use crate::cfdp::PacketTarget;
605
606    use super::PacketInfo;
607
608    fn generic_pdu_header() -> PduHeader {
609        let pdu_conf = CommonPduConfig::default();
610        PduHeader::new_no_file_data(pdu_conf, 0)
611    }
612
613    #[test]
614    fn test_metadata_pdu_info() {
615        let mut buf: [u8; 128] = [0; 128];
616        let pdu_header = generic_pdu_header();
617        let metadata_params = MetadataGenericParams::default();
618        let src_file_name = "hello.txt";
619        let dest_file_name = "hello-dest.txt";
620        let src_lv = Lv::new_from_str(src_file_name).unwrap();
621        let dest_lv = Lv::new_from_str(dest_file_name).unwrap();
622        let metadata_pdu =
623            MetadataPduCreator::new_no_opts(pdu_header, metadata_params, src_lv, dest_lv);
624        metadata_pdu
625            .write_to_bytes(&mut buf)
626            .expect("writing metadata PDU failed");
627
628        let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
629        assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
630        assert!(packet_info.pdu_directive().is_some());
631        assert_eq!(
632            packet_info.pdu_directive().unwrap(),
633            FileDirectiveType::MetadataPdu
634        );
635        assert_eq!(packet_info.target(), PacketTarget::DestEntity);
636    }
637
638    #[test]
639    fn test_filedata_pdu_info() {
640        let mut buf: [u8; 128] = [0; 128];
641        let pdu_header = generic_pdu_header();
642        let file_data_pdu = FileDataPdu::new_no_seg_metadata(pdu_header, 0, &[]);
643        file_data_pdu
644            .write_to_bytes(&mut buf)
645            .expect("writing file data PDU failed");
646        let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
647        assert_eq!(packet_info.pdu_type(), PduType::FileData);
648        assert!(packet_info.pdu_directive().is_none());
649        assert_eq!(packet_info.target(), PacketTarget::DestEntity);
650    }
651
652    #[test]
653    fn test_eof_pdu_info() {
654        let mut buf: [u8; 128] = [0; 128];
655        let pdu_header = generic_pdu_header();
656        let eof_pdu = EofPdu::new_no_error(pdu_header, 0, 0);
657        eof_pdu
658            .write_to_bytes(&mut buf)
659            .expect("writing file data PDU failed");
660        let packet_info = PacketInfo::new(&buf).expect("creating packet info failed");
661        assert_eq!(packet_info.pdu_type(), PduType::FileDirective);
662        assert!(packet_info.pdu_directive().is_some());
663        assert_eq!(
664            packet_info.pdu_directive().unwrap(),
665            FileDirectiveType::EofPdu
666        );
667    }
668}