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