spacepackets/cfdp/pdu/
eof.rs

1//! # End-of-File (EOF) PDU packet implementation.
2use crate::cfdp::pdu::{
3    add_pdu_crc, generic_length_checks_pdu_deserialization, read_fss_field, write_fss_field,
4    FileDirectiveType, PduError, PduHeader,
5};
6use crate::cfdp::tlv::{EntityIdTlv, WritableTlv};
7use crate::cfdp::{ConditionCode, CrcFlag, Direction, LargeFileFlag};
8use crate::ByteConversionError;
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use super::{CfdpPdu, WritablePduPacket};
13
14/// Finished PDU abstraction.
15///
16/// For more information, refer to CFDP chapter 5.2.2.
17#[derive(Debug, Copy, Clone, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19#[cfg_attr(feature = "defmt", derive(defmt::Format))]
20pub struct EofPdu {
21    pdu_header: PduHeader,
22    condition_code: ConditionCode,
23    file_checksum: u32,
24    file_size: u64,
25    fault_location: Option<EntityIdTlv>,
26}
27
28impl EofPdu {
29    /// Constructor.
30    pub fn new(
31        mut pdu_header: PduHeader,
32        condition_code: ConditionCode,
33        file_checksum: u32,
34        file_size: u64,
35        fault_location: Option<EntityIdTlv>,
36    ) -> Self {
37        // Force correct direction flag.
38        pdu_header.pdu_conf.direction = Direction::TowardsReceiver;
39        let mut eof_pdu = Self {
40            pdu_header,
41            condition_code,
42            file_checksum,
43            file_size,
44            fault_location,
45        };
46        eof_pdu.pdu_header.pdu_datafield_len = eof_pdu.calc_pdu_datafield_len() as u16;
47        eof_pdu
48    }
49
50    /// Constructor for no error EOF PDUs.
51    pub fn new_no_error(pdu_header: PduHeader, file_checksum: u32, file_size: u64) -> Self {
52        Self::new(
53            pdu_header,
54            ConditionCode::NoError,
55            file_checksum,
56            file_size,
57            None,
58        )
59    }
60
61    /// PDU header.
62    #[inline]
63    pub fn pdu_header(&self) -> &PduHeader {
64        &self.pdu_header
65    }
66
67    /// Condition code.
68    #[inline]
69    pub fn condition_code(&self) -> ConditionCode {
70        self.condition_code
71    }
72
73    /// File checksum.
74    #[inline]
75    pub fn file_checksum(&self) -> u32 {
76        self.file_checksum
77    }
78
79    /// File size.
80    #[inline]
81    pub fn file_size(&self) -> u64 {
82        self.file_size
83    }
84
85    fn calc_pdu_datafield_len(&self) -> usize {
86        // One directive type octet, 4 bits condition code, 4 spare bits.
87        let mut len = 2 + core::mem::size_of::<u32>() + 4;
88        if self.pdu_header.pdu_conf.file_flag == LargeFileFlag::Large {
89            len += 4;
90        }
91        if let Some(fault_location) = self.fault_location {
92            len += fault_location.len_full();
93        }
94        if self.crc_flag() == CrcFlag::WithCrc {
95            len += 2;
96        }
97        len
98    }
99
100    /// Construct [Self] from the provided byte slice.
101    pub fn from_bytes(buf: &[u8]) -> Result<EofPdu, PduError> {
102        let (pdu_header, mut current_idx) = PduHeader::from_bytes(buf)?;
103        let full_len_without_crc = pdu_header.verify_length_and_checksum(buf)?;
104        let is_large_file = pdu_header.pdu_conf.file_flag == LargeFileFlag::Large;
105        let mut min_expected_len = 2 + 4 + 4;
106        if is_large_file {
107            min_expected_len += 4;
108        }
109        generic_length_checks_pdu_deserialization(buf, min_expected_len, full_len_without_crc)?;
110        let directive_type = FileDirectiveType::try_from(buf[current_idx]).map_err(|_| {
111            PduError::InvalidDirectiveType {
112                found: buf[current_idx],
113                expected: Some(FileDirectiveType::Eof),
114            }
115        })?;
116        if directive_type != FileDirectiveType::Eof {
117            return Err(PduError::WrongDirectiveType {
118                found: directive_type,
119                expected: FileDirectiveType::Eof,
120            });
121        }
122        current_idx += 1;
123        let condition_code = ConditionCode::try_from((buf[current_idx] >> 4) & 0b1111)
124            .map_err(|_| PduError::InvalidConditionCode((buf[current_idx] >> 4) & 0b1111))?;
125        current_idx += 1;
126        let file_checksum =
127            u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap());
128        current_idx += 4;
129        let (fss_field_len, file_size) =
130            read_fss_field(pdu_header.pdu_conf.file_flag, &buf[current_idx..]);
131        current_idx += fss_field_len;
132        let mut fault_location = None;
133        if condition_code != ConditionCode::NoError && current_idx < full_len_without_crc {
134            fault_location = Some(EntityIdTlv::from_bytes(&buf[current_idx..])?);
135        }
136        Ok(Self {
137            pdu_header,
138            condition_code,
139            file_checksum,
140            file_size,
141            fault_location,
142        })
143    }
144
145    /// Write [Self] to the provided buffer and returns the written size.
146    pub fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
147        let expected_len = self.len_written();
148        if buf.len() < expected_len {
149            return Err(ByteConversionError::ToSliceTooSmall {
150                found: buf.len(),
151                expected: expected_len,
152            }
153            .into());
154        }
155        let mut current_idx = self.pdu_header.write_to_bytes(buf)?;
156        buf[current_idx] = FileDirectiveType::Eof as u8;
157        current_idx += 1;
158        buf[current_idx] = (self.condition_code as u8) << 4;
159        current_idx += 1;
160        buf[current_idx..current_idx + 4].copy_from_slice(&self.file_checksum.to_be_bytes());
161        current_idx += 4;
162        current_idx += write_fss_field(
163            self.pdu_header.pdu_conf.file_flag,
164            self.file_size,
165            &mut buf[current_idx..],
166        )?;
167        if let Some(fault_location) = self.fault_location {
168            current_idx += fault_location.write_to_bytes(&mut buf[current_idx..])?;
169        }
170        if self.crc_flag() == CrcFlag::WithCrc {
171            current_idx = add_pdu_crc(buf, current_idx);
172        }
173        Ok(current_idx)
174    }
175
176    /// Length of the written PDU in bytes.
177    pub fn len_written(&self) -> usize {
178        self.pdu_header.header_len() + self.calc_pdu_datafield_len()
179    }
180}
181
182impl CfdpPdu for EofPdu {
183    #[inline]
184    fn pdu_header(&self) -> &PduHeader {
185        self.pdu_header()
186    }
187
188    #[inline]
189    fn file_directive_type(&self) -> Option<FileDirectiveType> {
190        Some(FileDirectiveType::Eof)
191    }
192}
193
194impl WritablePduPacket for EofPdu {
195    fn write_to_bytes(&self, buf: &mut [u8]) -> Result<usize, PduError> {
196        self.write_to_bytes(buf)
197    }
198
199    fn len_written(&self) -> usize {
200        self.len_written()
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207    use crate::cfdp::pdu::tests::{
208        common_pdu_conf, verify_raw_header, TEST_DEST_ID, TEST_SEQ_NUM, TEST_SRC_ID,
209    };
210    use crate::cfdp::pdu::{FileDirectiveType, PduHeader};
211    use crate::cfdp::{ConditionCode, CrcFlag, LargeFileFlag, PduType, TransmissionMode};
212    #[cfg(feature = "serde")]
213    use crate::tests::generic_serde_test;
214    use crate::util::{UnsignedByteFieldU16, UnsignedEnum};
215
216    fn verify_state_no_error_no_crc(eof_pdu: &EofPdu, file_flag: LargeFileFlag) {
217        verify_state(eof_pdu, CrcFlag::NoCrc, file_flag, ConditionCode::NoError);
218    }
219
220    fn verify_state(
221        eof_pdu: &EofPdu,
222        crc_flag: CrcFlag,
223        file_flag: LargeFileFlag,
224        cond_code: ConditionCode,
225    ) {
226        assert_eq!(eof_pdu.file_checksum(), 0x01020304);
227        assert_eq!(eof_pdu.file_size(), 12);
228        assert_eq!(eof_pdu.condition_code(), cond_code);
229
230        assert_eq!(eof_pdu.crc_flag(), crc_flag);
231        assert_eq!(eof_pdu.file_flag(), file_flag);
232        assert_eq!(eof_pdu.pdu_type(), PduType::FileDirective);
233        assert_eq!(eof_pdu.file_directive_type(), Some(FileDirectiveType::Eof));
234        assert_eq!(eof_pdu.transmission_mode(), TransmissionMode::Acknowledged);
235        assert_eq!(eof_pdu.direction(), Direction::TowardsReceiver);
236        assert_eq!(eof_pdu.source_id(), TEST_SRC_ID.into());
237        assert_eq!(eof_pdu.dest_id(), TEST_DEST_ID.into());
238        assert_eq!(eof_pdu.transaction_seq_num(), TEST_SEQ_NUM.into());
239    }
240
241    #[test]
242    fn test_basic() {
243        let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
244        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
245        let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
246        assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 4 + 4);
247        verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Normal);
248    }
249
250    #[test]
251    fn test_serialization() {
252        let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
253        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
254        let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
255        let mut buf: [u8; 64] = [0; 64];
256        let res = eof_pdu.write_to_bytes(&mut buf);
257        assert!(res.is_ok());
258        let written = res.unwrap();
259        assert_eq!(written, eof_pdu.len_written());
260        verify_raw_header(eof_pdu.pdu_header(), &buf);
261        let mut current_idx = eof_pdu.pdu_header().header_len();
262        buf[current_idx] = FileDirectiveType::Eof as u8;
263        current_idx += 1;
264        assert_eq!(
265            (buf[current_idx] >> 4) & 0b1111,
266            ConditionCode::NoError as u8
267        );
268        current_idx += 1;
269        assert_eq!(
270            u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()),
271            0x01020304
272        );
273        current_idx += 4;
274        assert_eq!(
275            u32::from_be_bytes(buf[current_idx..current_idx + 4].try_into().unwrap()),
276            12
277        );
278        current_idx += 4;
279        assert_eq!(current_idx, written);
280    }
281
282    #[test]
283    fn test_deserialization() {
284        let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
285        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
286        let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
287        let mut buf: [u8; 64] = [0; 64];
288        eof_pdu.write_to_bytes(&mut buf).unwrap();
289        let eof_read_back = EofPdu::from_bytes(&buf);
290        if let Err(e) = eof_read_back {
291            panic!("deserialization failed with: {e}")
292        }
293        let eof_read_back = eof_read_back.unwrap();
294        assert_eq!(eof_read_back, eof_pdu);
295    }
296
297    #[test]
298    fn test_write_to_vec() {
299        let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
300        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
301        let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
302        let mut buf: [u8; 64] = [0; 64];
303        let written = eof_pdu.write_to_bytes(&mut buf).unwrap();
304        let pdu_vec = eof_pdu.to_vec().unwrap();
305        assert_eq!(buf[0..written], pdu_vec);
306    }
307
308    #[test]
309    fn test_with_crc() {
310        let pdu_conf = common_pdu_conf(CrcFlag::WithCrc, LargeFileFlag::Normal);
311        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
312        let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
313        let mut buf: [u8; 64] = [0; 64];
314        let written = eof_pdu.write_to_bytes(&mut buf).unwrap();
315        assert_eq!(written, eof_pdu.len_written());
316        let eof_from_raw = EofPdu::from_bytes(&buf).expect("creating EOF PDU failed");
317        assert_eq!(eof_from_raw, eof_pdu);
318        buf[written - 1] -= 1;
319        let crc: u16 = ((buf[written - 2] as u16) << 8) as u16 | buf[written - 1] as u16;
320        let error = EofPdu::from_bytes(&buf).unwrap_err();
321        if let PduError::Checksum(e) = error {
322            assert_eq!(e, crc);
323        } else {
324            panic!("expected crc error");
325        }
326    }
327
328    #[test]
329    fn test_with_large_file_flag() {
330        let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Large);
331        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
332        let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
333        verify_state_no_error_no_crc(&eof_pdu, LargeFileFlag::Large);
334        assert_eq!(eof_pdu.len_written(), pdu_header.header_len() + 2 + 8 + 4);
335    }
336
337    #[test]
338    #[cfg(feature = "serde")]
339    fn test_eof_serde() {
340        let pdu_conf = common_pdu_conf(CrcFlag::NoCrc, LargeFileFlag::Normal);
341        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
342        let eof_pdu = EofPdu::new_no_error(pdu_header, 0x01020304, 12);
343        generic_serde_test(eof_pdu);
344    }
345
346    fn generic_test_with_fault_location_and_error(crc: CrcFlag) {
347        let pdu_conf = common_pdu_conf(crc, LargeFileFlag::Normal);
348        let pdu_header = PduHeader::new_for_file_directive(pdu_conf, 0);
349        let eof_pdu = EofPdu::new(
350            pdu_header,
351            ConditionCode::FileChecksumFailure,
352            0x01020304,
353            12,
354            Some(EntityIdTlv::new(UnsignedByteFieldU16::new(5).into())),
355        );
356        let mut expected_len = pdu_header.header_len() + 2 + 4 + 4 + 4;
357        if crc == CrcFlag::WithCrc {
358            expected_len += 2;
359        }
360        // Entity ID TLV increaes length by 4.
361        assert_eq!(eof_pdu.len_written(), expected_len);
362        verify_state(
363            &eof_pdu,
364            crc,
365            LargeFileFlag::Normal,
366            ConditionCode::FileChecksumFailure,
367        );
368        let eof_vec = eof_pdu.to_vec().unwrap();
369        let eof_read_back = EofPdu::from_bytes(&eof_vec);
370        if let Err(e) = eof_read_back {
371            panic!("deserialization failed with: {e}")
372        }
373        let eof_read_back = eof_read_back.unwrap();
374        assert_eq!(eof_read_back, eof_pdu);
375        assert!(eof_read_back.fault_location.is_some());
376        assert_eq!(eof_read_back.fault_location.unwrap().entity_id().value(), 5);
377        assert_eq!(eof_read_back.fault_location.unwrap().entity_id().size(), 2);
378    }
379
380    #[test]
381    fn test_with_fault_location_and_error() {
382        generic_test_with_fault_location_and_error(CrcFlag::NoCrc);
383    }
384
385    #[test]
386    fn test_with_fault_location_and_error_and_crc() {
387        generic_test_with_fault_location_and_error(CrcFlag::WithCrc);
388    }
389}