cfdp_simplified/pdu/
header.rs

1use num_derive::FromPrimitive;
2use num_traits::FromPrimitive;
3use serde::Serialize;
4
5use std::fmt;
6use std::io::Read;
7
8use super::{
9    error::{PDUError, PDUResult},
10    ops::{EntityID, TransactionSeqNum},
11};
12
13#[repr(u8)]
14#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, Serialize)]
15/// All possible conditions a transaction may be in
16pub enum Condition {
17    /// No errors occurred yet during the transaction.
18    NoError = 0b0000,
19    /// The positive acknowledgement limit has been reached.
20    PositiveLimitReached = 0b0001,
21    /// The transaction's transmission mode is not valid.
22    InvalidTransmissionMode = 0b0011,
23    /// The file could not be written by the filestore.
24    FileStoreRejection = 0b0100,
25    /// The final file did not pass the checksum verification.
26    FileChecksumFailure = 0b0101,
27    /// Received file was a different size than expected.
28    FilesizeError = 0b0110,
29    /// The negative acknowledgement limit has been reached.
30    NakLimitReached = 0b0111,
31    /// No activity was detected within the allowed time limit.
32    InactivityDetected = 0b1000,
33    /// File did not have the correct structure.
34    InvalidFileStructure = 0b1001,
35    /// The checksum method requested is not supported by this implementation.
36    UnsupportedChecksumType = 0b1011,
37    /// A command to cancel the transaction has been issued.
38    CancelReceived = 0b1111,
39}
40
41#[repr(u8)]
42#[derive(Clone, Debug, PartialEq, Eq, FromPrimitive)]
43/// A 3 bit integer used in versioning to limit possible values.
44pub enum U3 {
45    Zero = 0b000,
46}
47
48#[repr(u8)]
49#[derive(Clone, Debug, PartialEq, Eq, FromPrimitive)]
50/// A flag to differentiate the payalod type of the PDU.
51pub enum PDUType {
52    /// Payload contains a file directive.
53    FileDirective = 0,
54    /// Payload contains file data.
55    FileData = 1,
56}
57
58#[repr(u8)]
59#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
60/// The direction in which this PDU is heading.
61pub enum Direction {
62    ToReceiver = 0,
63    ToSender = 1,
64}
65
66#[repr(u8)]
67#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
68/// The transmission mode of this transaction.
69pub enum TransmissionMode {
70    Acknowledged = 0,
71    Unacknowledged = 1,
72}
73impl PDUEncode for TransmissionMode {
74    type PDUType = Self;
75
76    fn encoded_len(&self) -> u16 {
77        1
78    }
79
80    fn decode<T: Read>(buffer: &mut T) -> PDUResult<Self::PDUType> {
81        let mut u8_buff = [0u8; 1];
82        buffer.read_exact(&mut u8_buff)?;
83        let possible_mode = u8_buff[0];
84        Self::from_u8(possible_mode).ok_or(PDUError::InvalidTransmissionMode(possible_mode))
85    }
86
87    fn encode(self) -> Vec<u8> {
88        vec![self as u8]
89    }
90}
91
92#[repr(u8)]
93#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
94/// Flag determining the presence of a CRC appended to a PDU.
95pub enum CRCFlag {
96    NotPresent = 0,
97    Present = 1,
98}
99
100#[repr(u8)]
101#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
102/// Flag indicating if the file size is less than the maximum u32 value.
103pub enum FileSizeFlag {
104    Small = 0,
105    Large = 1,
106}
107
108impl FileSizeFlag {
109    /// returns the size in bytes of the encoded file size (i.e. 4 for small and 8 for large)
110    pub fn encoded_len(&self) -> u16 {
111        match self {
112            FileSizeFlag::Small => 4,
113            FileSizeFlag::Large => 8,
114        }
115    }
116}
117#[repr(u8)]
118#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
119/// Whether segmentation control is implemented on this transaction.
120pub enum SegmentationControl {
121    NotPreserved = 0,
122    // Preserved = 1, // Not supported
123}
124
125#[repr(u8)]
126#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
127/// Flag to determine if the file data in this PDU is segmented.
128pub enum SegmentedData {
129    NotPresent = 0,
130    Present = 1,
131}
132
133#[repr(u8)]
134#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
135/// Final transaction delivery code.
136pub enum DeliveryCode {
137    Complete = 0,
138    Incomplete = 1,
139}
140
141#[repr(u8)]
142#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive)]
143/// The resulting code of a file being written during a transaction.
144pub enum FileStatusCode {
145    Discarded = 0b00,
146    FileStoreRejection = 0b01,
147    Retained = 0b10,
148    Unreported = 0b11,
149}
150
151#[repr(u8)]
152#[derive(Clone, Copy, Debug, PartialEq, Eq, FromPrimitive, Serialize)]
153/// The current status of a running transaction
154pub enum TransactionStatus {
155    Undefined = 0b00,
156    Active = 0b01,
157    Terminated = 0b10,
158    Unrecognized = 0b11,
159}
160
161/// Provides utility functions for encoding and decoding byte streams
162pub trait PDUEncode {
163    type PDUType;
164    /// Gets the encoded length must fit in a u16 for PDUs
165    fn encoded_len(&self) -> u16;
166
167    /// Encodes the PDU to a byte stream
168    fn encode(self) -> Vec<u8>;
169
170    /// Attempts to decode a PDU from a byte stream
171    fn decode<T: Read>(buffer: &mut T) -> PDUResult<Self::PDUType>;
172}
173
174/// Provides utility functions for encoding and decoding byte streams
175/// For PDUs which require knowledge of the file size
176pub trait FSSEncode {
177    type PDUType;
178
179    /// Gets the encoded length must fit in a u16 for PDUs
180    fn encoded_len(&self, file_size_flag: FileSizeFlag) -> u16;
181
182    /// Encodes the PDU to a byte stream
183    fn encode(self, file_size_flag: FileSizeFlag) -> Vec<u8>;
184
185    /// Attempts to decode a PDU from a byte stream
186    fn decode<T: Read>(buffer: &mut T, file_size_flag: FileSizeFlag) -> PDUResult<Self::PDUType>;
187}
188
189/// Provides utility functions for encoding and decoding byte streams
190/// For PDUs which require knowledge of the Segmentation state
191pub trait SegmentEncode {
192    type PDUType;
193
194    /// Gets the encoded length must fit in a u16 for PDUs
195    fn encoded_len(&self, file_size_flag: FileSizeFlag) -> u16;
196
197    /// Encodes the PDU to a byte stream
198    fn encode(self, file_size_flag: FileSizeFlag) -> Vec<u8>;
199
200    /// Attempts to decode a PDU from a byte stream
201    fn decode<T: Read>(
202        buffer: &mut T,
203        segmentation_flag: SegmentedData,
204        file_size_flag: FileSizeFlag,
205    ) -> PDUResult<Self::PDUType>;
206}
207
208#[derive(Clone, Debug, PartialEq, Eq)]
209/// The standard header attached to all CFDP PDUs.
210pub struct PDUHeader {
211    /// header version number.
212    pub version: U3,
213
214    /// The type of the underlying payload.
215    pub pdu_type: PDUType,
216
217    /// The direction in which this PDU is heading.
218    pub direction: Direction,
219
220    /// The mode of the transaction.
221    pub transmission_mode: TransmissionMode,
222
223    /// Whether a CRC is appended to the PDU byte stream.
224    pub crc_flag: CRCFlag,
225
226    /// Flag to indicate if the file size is less than the maximum u32 value.
227    pub large_file_flag: FileSizeFlag,
228
229    /// The length of attached payload.
230    ///
231    /// When the CRC flag is set to [CRCFlag::Present] this struct will automatically
232    /// account for the additional length during encoding.
233    pub pdu_data_field_length: u16,
234
235    /// Flag to indicate if segmentation control is enabled for this transaction.
236    pub segmentation_control: SegmentationControl,
237
238    /// Flag to indicate if metadata segmentation is enabled for this transaction.
239    pub segment_metadata_flag: SegmentedData,
240
241    /// Source entity identification number.
242    pub source_entity_id: EntityID,
243
244    /// The sequence number of the transaction.
245    pub transaction_sequence_number: TransactionSeqNum,
246
247    /// Destination entity identification number.
248    pub destination_entity_id: EntityID,
249}
250impl PDUEncode for PDUHeader {
251    type PDUType = Self;
252
253    fn encoded_len(&self) -> u16 {
254        // version, type, direction, mode, crc_flag, file size
255        1 +
256            // pdu data length
257            2
258            // segmentation control, entity ID len, segment metadata flag, sequence_number len
259            + 1
260            + self.source_entity_id.encoded_len()
261            + self.transaction_sequence_number.encoded_len()
262            + self.destination_entity_id.encoded_len()
263    }
264
265    fn encode(self) -> Vec<u8> {
266        let first_byte = ((self.version as u8) << 5)
267            | ((self.pdu_type as u8) << 4)
268            | ((self.direction as u8) << 3)
269            | ((self.transmission_mode as u8) << 2)
270            | ((self.crc_flag as u8) << 1)
271            | self.large_file_flag as u8;
272        let mut buffer = vec![first_byte];
273        // if the CRC is expected add 2 to the length of the "data" field
274        buffer.extend(match &self.crc_flag {
275            CRCFlag::NotPresent => self.pdu_data_field_length.to_be_bytes(),
276            CRCFlag::Present => (self.pdu_data_field_length + 2).to_be_bytes(),
277        });
278        buffer.push(
279            ((self.segmentation_control as u8) << 7)
280                | ((self.source_entity_id.encoded_len() as u8 - 1) << 4)
281                | ((self.segment_metadata_flag as u8) << 3)
282                | (self.transaction_sequence_number.encoded_len() as u8 - 1),
283        );
284        buffer.extend(self.source_entity_id.to_be_bytes());
285        buffer.extend(self.transaction_sequence_number.to_be_bytes());
286        buffer.extend(self.destination_entity_id.to_be_bytes());
287        buffer
288    }
289
290    fn decode<T: Read>(buffer: &mut T) -> PDUResult<Self::PDUType> {
291        let mut u8_buff = [0_u8; 1];
292        buffer.read_exact(&mut u8_buff)?;
293
294        let version = {
295            let possible = (u8_buff[0] & 0xE0) >> 5;
296            U3::from_u8(possible).ok_or(PDUError::InvalidVersion(possible))?
297        };
298
299        let pdu_type = {
300            let possible = (u8_buff[0] & 0x10) >> 4;
301            PDUType::from_u8(possible).ok_or(PDUError::InvalidPDUType(possible))?
302        };
303
304        let direction = {
305            let possible = (u8_buff[0] & 0x8) >> 3;
306            Direction::from_u8(possible).ok_or(PDUError::InvalidDirection(possible))?
307        };
308
309        let transmission_mode = {
310            let possible = (u8_buff[0] & 0x4) >> 2;
311            TransmissionMode::from_u8(possible)
312                .ok_or(PDUError::InvalidTransmissionMode(possible))?
313        };
314
315        let crc_flag = {
316            let possible = (u8_buff[0] & 0x2) >> 1;
317            CRCFlag::from_u8(possible).ok_or(PDUError::InvalidCRCFlag(possible))?
318        };
319
320        let large_file_flag = {
321            let possible = u8_buff[0] & 0x1;
322            FileSizeFlag::from_u8(possible).ok_or(PDUError::InvalidFileSizeFlag(possible))?
323        };
324
325        let pdu_data_field_length = {
326            let mut u16_buff = [0_u8; 2];
327            buffer.read_exact(&mut u16_buff)?;
328            // CRC length is _included_ in the data_field_length
329            // but it is not actually part of the message.
330            // strip the crc length to preserve the original message
331            match &crc_flag {
332                CRCFlag::NotPresent => u16::from_be_bytes(u16_buff),
333                CRCFlag::Present => u16::from_be_bytes(u16_buff) - 2,
334            }
335        };
336
337        buffer.read_exact(&mut u8_buff)?;
338
339        let segmentation_control = {
340            let possible = (u8_buff[0] & 0x80) >> 7;
341            SegmentationControl::from_u8(possible)
342                .ok_or(PDUError::InvalidSegmentControl(possible))?
343        };
344
345        let segment_metadata_flag = {
346            let possible = (u8_buff[0] & 8) >> 3;
347            SegmentedData::from_u8(possible)
348                .ok_or(PDUError::InvalidSegmentMetadataFlag(possible))?
349        };
350
351        // CCSDS defines the lengths to be encoded as length - 1.
352        // add one back to get actual value.
353        let entity_id_length = ((u8_buff[0] & 0x70) >> 4) + 1;
354        let transaction_sequence_length = (u8_buff[0] & 0x7) + 1;
355
356        let source_entity_id: EntityID = {
357            let mut buff = vec![0_u8; entity_id_length as usize];
358            buffer.read_exact(buff.as_mut_slice())?;
359            EntityID::try_from(buff.to_vec())?
360        };
361
362        let transaction_sequence_number = {
363            let mut buff = vec![0_u8; transaction_sequence_length as usize];
364            buffer.read_exact(buff.as_mut_slice())?;
365            TransactionSeqNum::try_from(buff.to_vec())?
366        };
367
368        let destination_entity_id = {
369            let mut buff = vec![0_u8; entity_id_length as usize];
370            buffer.read_exact(buff.as_mut_slice())?;
371            EntityID(u16::from_be_bytes(
372                buff.try_into()
373                    .expect("Unable to coerce vec into same sized array."),
374            ))
375        };
376
377        Ok(Self {
378            version,
379            pdu_type,
380            direction,
381            transmission_mode,
382            crc_flag,
383            large_file_flag,
384            pdu_data_field_length,
385            segmentation_control,
386            segment_metadata_flag,
387            source_entity_id,
388            transaction_sequence_number,
389            destination_entity_id,
390        })
391    }
392}
393
394impl fmt::Display for Condition {
395    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
396        write!(
397            f,
398            "{}",
399            match self {
400                Condition::NoError => "No Error",
401                Condition::InvalidTransmissionMode => "Invalid Transmission",
402                Condition::FileStoreRejection => "Filestore Rejection",
403                Condition::FileChecksumFailure => "Checksum Failure",
404                Condition::FilesizeError => "Unexpected File Size",
405                Condition::InactivityDetected => "Inactivity Detected",
406                Condition::InvalidFileStructure => "Invalid File Structure",
407                Condition::PositiveLimitReached => "Transaction Timeout",
408                Condition::UnsupportedChecksumType => "Unsupported Checksum",
409                _ => "Unknown",
410            }
411        )
412    }
413}
414
415impl fmt::Display for Direction {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        write!(
418            f,
419            "{}",
420            match self {
421                Direction::ToReceiver => "Outgoing",
422                Direction::ToSender => "Incoming",
423            }
424        )
425    }
426}
427
428/// Read a length-value (LV) pair from a byte stream and return the value.
429pub fn read_length_value_pair<T: Read>(buffer: &mut T) -> PDUResult<Vec<u8>> {
430    let mut u8_buff = [0u8; 1];
431    buffer.read_exact(&mut u8_buff)?;
432    let length = u8_buff[0];
433    let mut vector = vec![0u8; length as usize];
434    buffer.read_exact(vector.as_mut_slice())?;
435    Ok(vector)
436}
437
438#[cfg(test)]
439mod test {
440    #![allow(clippy::too_many_arguments)]
441
442    use std::{u16, u32};
443
444    use super::*;
445    use rstest::rstest;
446
447    #[rstest]
448    #[case(
449        12_u16,
450        EntityID::from(u16::MAX),
451        TransactionSeqNum::from(u32::MAX),
452        EntityID::from(u16::MAX)
453    )]
454    #[case(
455        8745_u16,
456        EntityID::from(u16::MAX),
457        TransactionSeqNum::from(u32::MAX),
458        EntityID::from(u16::MAX)
459    )]
460    #[case(
461        65531_u16,
462        EntityID::from(u16::MAX),
463        TransactionSeqNum::from(u32::MAX),
464        EntityID::from(u16::MAX)
465    )]
466    fn pdu_header(
467        #[values(U3::Zero)] version: U3,
468        #[values(PDUType::FileDirective, PDUType::FileData)] pdu_type: PDUType,
469        #[values(Direction::ToReceiver, Direction::ToSender)] direction: Direction,
470        #[values(TransmissionMode::Acknowledged, TransmissionMode::Unacknowledged)]
471        transmission_mode: TransmissionMode,
472        #[values(CRCFlag::NotPresent, CRCFlag::Present)] crc_flag: CRCFlag,
473        #[values(FileSizeFlag::Small, FileSizeFlag::Large)] large_file_flag: FileSizeFlag,
474        #[case] pdu_data_field_length: u16,
475        #[case] source_entity_id: EntityID,
476        #[case] transaction_sequence_number: TransactionSeqNum,
477        #[case] destination_entity_id: EntityID,
478    ) -> PDUResult<()> {
479        let (segmentation_control, segment_metadata_flag) = match &pdu_type {
480            PDUType::FileData => (SegmentationControl::NotPreserved, SegmentedData::Present),
481            PDUType::FileDirective => (SegmentationControl::NotPreserved, SegmentedData::Present),
482        };
483
484        let expected = PDUHeader {
485            version,
486            pdu_type,
487            direction,
488            transmission_mode,
489            crc_flag,
490            large_file_flag,
491            pdu_data_field_length,
492            segmentation_control,
493            segment_metadata_flag,
494            source_entity_id,
495            transaction_sequence_number,
496            destination_entity_id,
497        };
498        let buffer = expected.clone().encode();
499        let recovered = PDUHeader::decode(&mut buffer.as_slice())?;
500        assert_eq!(expected, recovered);
501
502        Ok(())
503    }
504}