cfdp_simplified/pdu/
mod.rs

1pub mod error;
2pub mod header;
3pub mod ops;
4
5pub use header::*;
6pub use ops::*;
7
8use log::error;
9use std::io::Read;
10use tracing::trace;
11
12#[doc(inline)]
13pub use error::{PDUError, PDUResult};
14
15#[derive(Clone, Debug, PartialEq, Eq)]
16/// All possible payloads of a cfdp PDU.
17pub enum PDUPayload {
18    /// Any non file data related PDU
19    Directive(Operations),
20    /// File data only PDUs
21    FileData(FileDataPDU),
22}
23impl PDUPayload {
24    /// computes the total length of the payload without additional encoding/copying
25    pub fn encoded_len(&self, file_size_flag: FileSizeFlag) -> u16 {
26        match self {
27            Self::Directive(operation) => operation.encoded_len(file_size_flag),
28            Self::FileData(file_data) => file_data.encoded_len(file_size_flag),
29        }
30    }
31
32    /// Encodes the payload to a byte stream
33    pub fn encode(self, file_size_flag: FileSizeFlag) -> Vec<u8> {
34        match self {
35            Self::Directive(operation) => operation.encode(file_size_flag),
36            Self::FileData(data) => data.encode(file_size_flag),
37        }
38    }
39
40    /// Decodes from an input bytestream
41    pub fn decode<T: std::io::Read>(
42        buffer: &mut T,
43        pdu_type: PDUType,
44        file_size_flag: FileSizeFlag,
45        segmentation_flag: SegmentedData,
46    ) -> PDUResult<Self> {
47        match pdu_type {
48            PDUType::FileDirective => {
49                Ok(Self::Directive(Operations::decode(buffer, file_size_flag)?))
50            }
51            PDUType::FileData => Ok(Self::FileData(FileDataPDU::decode(
52                buffer,
53                segmentation_flag,
54                file_size_flag,
55            )?)),
56        }
57    }
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61/// The Protocol Data Unit (PDU).
62///
63/// The main packet type for CFDP interactions.
64pub struct PDU {
65    /// Header information used to decode the rest of the packet.
66    pub header: PDUHeader,
67    /// Packet payload containing filedata or a directive.
68    pub payload: PDUPayload,
69}
70impl PDUEncode for PDU {
71    type PDUType = Self;
72
73    fn encoded_len(&self) -> u16 {
74        self.header.encoded_len() + self.payload.encoded_len(self.header.large_file_flag)
75    }
76
77    fn encode(self) -> Vec<u8> {
78        trace!("Encoded PDU: {:?}", self);
79        let crc_flag = self.header.crc_flag;
80        let file_size_flag = self.header.large_file_flag;
81
82        let mut buffer = self.header.encode();
83        buffer.extend(self.payload.encode(file_size_flag));
84        match crc_flag {
85            CRCFlag::Present => buffer.extend(crc16_ibm_3740(buffer.as_slice()).to_be_bytes()),
86            CRCFlag::NotPresent => {}
87        }
88        buffer
89    }
90
91    fn decode<T: Read>(buffer: &mut T) -> PDUResult<Self::PDUType> {
92        let header = PDUHeader::decode(buffer)?;
93        trace!("Decoded header: {:?}", header);
94
95        let mut remaining_msg = vec![0_u8; header.pdu_data_field_length as usize];
96
97        buffer.read_exact(remaining_msg.as_mut_slice())?;
98        let remaining_buffer = &mut remaining_msg.as_slice();
99
100        let payload = PDUPayload::decode(
101            remaining_buffer,
102            header.pdu_type.clone(),
103            header.large_file_flag,
104            header.segment_metadata_flag,
105        )?;
106
107        let received_pdu = Self { header, payload };
108
109        match &received_pdu.header.crc_flag {
110            CRCFlag::NotPresent => {}
111            CRCFlag::Present => {
112                let mut u16_buffer = [0_u8; 2];
113                buffer.read_exact(&mut u16_buffer)?;
114                let crc16 = u16::from_be_bytes(u16_buffer);
115                let tmp_buffer = {
116                    let input_pdu = received_pdu.clone();
117
118                    let mut temp = input_pdu.encode();
119                    // remove the crc from the temporary buffer
120                    temp.truncate(temp.len() - 2);
121                    temp
122                };
123                let crc = crc16_ibm_3740(tmp_buffer.as_slice());
124                match crc == crc16 {
125                    true => {}
126                    false => {
127                        error!(
128                            "CRC FAILURE, {}, {}, {}",
129                            crc,
130                            crc16,
131                            crc.overflowing_add(crc16).0
132                        );
133                        return Err(PDUError::CRCFailure(crc16, crc));
134                    }
135                }
136            }
137        }
138
139        Ok(received_pdu)
140    }
141}
142
143fn crc16_ibm_3740(message: &[u8]) -> u16 {
144    message
145        .iter()
146        .fold(0xffff, |acc, digit| crc16(*digit as u16, acc))
147}
148
149fn crc16(in_char: u16, crc: u16) -> u16 {
150    let poly = 0x1021;
151    let shift_char = (in_char & 0x00FF) << 8;
152    let mut crc = crc ^ shift_char;
153    for _ in 0..8 {
154        match crc & 0x8000 > 0 {
155            true => crc = (crc << 1) ^ poly,
156            false => crc <<= 1,
157        };
158    }
159    crc
160}
161
162#[cfg(test)]
163mod test {
164    use super::*;
165
166    use crate::filestore::ChecksumType;
167
168    use rstest::rstest;
169
170    #[rstest]
171    #[case("123456789".as_bytes(), 0x29b1_u16)]
172    #[case(
173        &[
174            0x06, 0x00, 0x0c, 0xf0, 0x00, 0x04, 0x00, 0x55,
175            0x88, 0x73, 0xc9, 0x00, 0x00, 0x05, 0x21
176        ],
177        0x75FB
178    )]
179    fn crc16(#[case] input: &[u8], #[case] expected: u16) {
180        let recovered = crc16_ibm_3740(input);
181        assert_eq!(expected, recovered)
182    }
183
184    #[rstest]
185    #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
186        condition: Condition::NoError,
187        checksum: 13_u32,
188        file_size: 12_u64,
189    })))]
190    #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
191        condition: Condition::NoError,
192        checksum: 13_u32,
193        file_size: 12_u64,
194    })))]
195    #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
196        condition: Condition::NoError,
197        checksum: 13_u32,
198        file_size: 12_u64,
199    })))]
200    #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
201        condition: Condition::NoError,
202        checksum: 13_u32,
203        file_size: 12_u64,
204    })))]
205    #[case(PDUPayload::Directive(Operations::EoF(EndOfFile{
206        condition: Condition::NoError,
207        checksum: 13_u32,
208        file_size: 12_u64,
209    })))]
210    #[case(PDUPayload::Directive(
211        Operations::Metadata(
212            MetadataPDU {
213                checksum_type: ChecksumType::Modular,
214                file_size: 55_u64,
215                source_filename: "the input filename".into(),
216                destination_filename: "the output filename".into(),
217            }
218        )
219
220    ))]
221    #[case(PDUPayload::Directive(
222        Operations::Nak(
223            NakPDU {
224                start_of_scope: 12_u64,
225                end_of_scope: 239585_u64,
226                segment_requests: vec![
227                    SegmentRequestForm{
228                        start_offset:12,
229                        end_offset: 64
230                    },
231                    SegmentRequestForm{
232                        start_offset: 69,
233                        end_offset: 4758
234                    }
235                ]
236            }
237        )
238
239    ))]
240    #[case(PDUPayload::FileData(
241        FileDataPDU(
242            UnsegmentedFileData {
243                offset: 948,
244                file_data: (0..12).collect()
245            }
246        )
247    ))]
248    fn pdu_len(
249        #[case] payload: PDUPayload,
250        #[values(FileSizeFlag::Small, FileSizeFlag::Large)] file_size_flag: FileSizeFlag,
251    ) {
252        assert_eq!(
253            payload.encoded_len(file_size_flag),
254            payload.encode(file_size_flag).len() as u16,
255        )
256    }
257
258    #[rstest]
259    #[case(
260        PDUPayload::Directive(Operations::EoF(EndOfFile {
261            condition: Condition::NoError,
262            checksum: 123749_u32,
263            file_size: 7738949_u64,
264        }))
265    )]
266    #[case(
267        PDUPayload::FileData(FileDataPDU(UnsegmentedFileData{
268            offset: 16_u64,
269            file_data: "test some information".as_bytes().to_vec(),
270        }))
271    )]
272    fn pdu_encoding(
273        #[case] payload: PDUPayload,
274        #[values(CRCFlag::NotPresent, CRCFlag::Present)] crc_flag: CRCFlag,
275    ) -> PDUResult<()> {
276        let pdu_data_field_length = payload.encoded_len(FileSizeFlag::Large);
277        let pdu_type = match &payload {
278            PDUPayload::Directive(_) => PDUType::FileDirective,
279            PDUPayload::FileData(_) => PDUType::FileData,
280        };
281
282        let expected: PDU = PDU {
283            header: PDUHeader {
284                version: U3::Zero,
285                pdu_type,
286                direction: Direction::ToReceiver,
287                transmission_mode: TransmissionMode::Acknowledged,
288                crc_flag,
289                large_file_flag: FileSizeFlag::Large,
290                pdu_data_field_length,
291                segmentation_control: SegmentationControl::NotPreserved,
292                segment_metadata_flag: SegmentedData::NotPresent,
293                source_entity_id: EntityID::from(18_u16),
294                transaction_sequence_number: TransactionSeqNum::from(7533_u32),
295                destination_entity_id: EntityID::from(23_u16),
296            },
297            payload,
298        };
299        let buffer = expected.clone().encode();
300        let recovered = PDU::decode(&mut buffer.as_slice())?;
301        assert_eq!(expected, recovered);
302        Ok(())
303    }
304}