Skip to main content

leodos_protocols/transport/cfdp/pdu/file_directive/
eof.rs

1use crate::transport::cfdp::pdu::CfdpError;
2use crate::transport::cfdp::pdu::EntityId;
3use crate::transport::cfdp::pdu::Pdu;
4use crate::transport::cfdp::pdu::TransactionSeqNum;
5use crate::transport::cfdp::pdu::file_directive::ConditionCode;
6use crate::transport::cfdp::pdu::file_directive::DirectiveCode;
7use crate::transport::cfdp::pdu::file_directive::FileDirectivePdu;
8use crate::transport::cfdp::pdu::header::Direction;
9use crate::transport::cfdp::pdu::header::PduHeaderFixedPart;
10use crate::transport::cfdp::pdu::header::PduType;
11use crate::transport::cfdp::pdu::header::TransmissionMode;
12use crate::transport::cfdp::pdu::tlv::Tlv;
13use crate::transport::cfdp::pdu::tlv::TlvIterator;
14use crate::transport::cfdp::pdu::tlv::TlvType;
15use crate::utils::get_bits_u8;
16use crate::utils::set_bits_u8;
17
18use bon::bon;
19use zerocopy::FromBytes;
20use zerocopy::Immutable;
21use zerocopy::IntoBytes;
22use zerocopy::KnownLayout;
23use zerocopy::Unaligned;
24use zerocopy::network_endian::U32;
25use zerocopy::network_endian::U64;
26
27/// A zero-copy representation of the **data field** of an End-of-File (EOF) PDU.
28///
29/// This struct's layout strictly follows the CCSDS specification (Table 5-6).
30/// It consists of a fixed-size portion and a `rest` slice. The `rest` slice
31/// contains the File-Size Sensitive (FSS) `file_size` field and any optional `Fault Location` TLV.
32///
33/// ```text
34/// +------------------------------------+----------------+--------------------------------------+
35/// | Field Name                         | Size           | Notes                                |
36/// +------------------------------------+----------------+--------------------------------------+
37/// | Condition Code                     | 4 bits         | The reason for the end of file.      |
38/// | Reserved for future use            | 4 bits         | Reserved, set to zero.               |
39/// |                                    |                | (Both are packed into one octet)     |
40/// |                                    |                |                                      |
41/// | File Checksum                      | 32 bits        | Checksum of the entire file's data.  |
42/// |                                    |                |                                      |
43/// | -- Start of `rest` slice --------- | -------------- | ------------------------------------ |
44/// | File Size                          | 32 or 64 bits  | FSS field. Size depends on header's  |
45/// |                                    | (FSS)          | `Large File Flag`.                   |
46/// |                                    |                |                                      |
47/// | Fault Location (Optional)          | Variable (TLV) | Present if Condition Code is an error|
48/// +------------------------------------+----------------+--------------------------------------+
49/// ```
50#[repr(C)]
51#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
52pub struct EofPdu {
53    /// An 8-bit field containing the 4-bit `Condition Code` and 4 `Spare` bits.
54    condition_code_and_spare: u8,
55    /// The checksum of the entire file.
56    file_checksum: U32,
57    /// Contains the variable-sized `file_size` and optional `Fault Location` TLV.
58    rest: [u8],
59}
60
61/// Bit masks for the EOF PDU's packed fields.
62mod bitmasks {
63    /// Mask for the 4-bit condition code.
64    pub const EOF_CC_MASK: u8 = 0b_11110000;
65}
66
67use bitmasks::*;
68
69impl EofPdu {
70    /// Returns the Condition Code (Table 5-5) from the bit-packed field.
71    pub fn condition_code(&self) -> Result<ConditionCode, CfdpError> {
72        // Condition Code is in the 4 most significant bits.
73        ConditionCode::try_from(get_bits_u8(self.condition_code_and_spare, EOF_CC_MASK))
74    }
75    /// Sets the condition code field.
76    pub fn set_condition_code(&mut self, code: ConditionCode) {
77        set_bits_u8(&mut self.condition_code_and_spare, EOF_CC_MASK, code as u8);
78    }
79
80    /// Returns the 32-bit file checksum.
81    pub fn file_checksum(&self) -> u32 {
82        self.file_checksum.get()
83    }
84    /// Sets the 32-bit file checksum.
85    pub fn set_file_checksum(&mut self, checksum: u32) {
86        self.file_checksum.set(checksum);
87    }
88
89    /// Correctly parses the File-Size Sensitive (FSS) `file_size` field from the `rest` slice.
90    ///
91    /// # Arguments
92    /// * `large_file_flag`: The state of the `Large File Flag` from the PDU header,
93    ///   which determines whether to parse 32 or 64 bits.
94    pub fn file_size(&self, large_file_flag: bool) -> Result<u64, CfdpError> {
95        if large_file_flag {
96            U64::ref_from_prefix(&self.rest)
97                .map(|(len, _)| len.get())
98                .map_err(|_| CfdpError::Custom("Invalid Eof File Size"))
99        } else {
100            U32::ref_from_prefix(&self.rest)
101                .map(|(len, _)| len.get() as u64)
102                .map_err(|_| CfdpError::Custom("Invalid Eof File Size"))
103        }
104    }
105    /// Sets the FSS file size field.
106    pub fn set_file_size(
107        &mut self,
108        large_file_flag: bool,
109        file_size: u64,
110    ) -> Result<(), CfdpError> {
111        if large_file_flag {
112            let size_field = U64::new(file_size);
113            let size_bytes = size_field.as_bytes();
114            self.rest
115                .get_mut(0..8)
116                .ok_or(CfdpError::Custom("Insufficient space for 64-bit file size"))?
117                .copy_from_slice(&size_bytes);
118            Ok(())
119        } else {
120            if file_size > u32::MAX as u64 {
121                return Err(CfdpError::Custom("File size too large for 32-bit field"));
122            }
123            let size_field = U32::new(file_size as u32);
124            let size_bytes = size_field.as_bytes();
125            self.rest
126                .get_mut(0..4)
127                .ok_or(CfdpError::Custom("Insufficient space for 32-bit file size"))?
128                .copy_from_slice(&size_bytes);
129            Ok(())
130        }
131    }
132
133    /// Returns an iterator over any trailing TLVs (e.g., Fault Location).
134    pub fn tlvs(&self, large_file_flag: bool) -> Result<TlvIterator<'_>, CfdpError> {
135        let file_size_len = if large_file_flag { 8 } else { 4 };
136
137        let tlv_buffer = self
138            .rest
139            .get(file_size_len..)
140            .ok_or_else(|| CfdpError::Custom("Invalid Eof PDU: insufficient data for TLVs"))?;
141
142        Ok(TlvIterator { buffer: tlv_buffer })
143    }
144
145    /// Finds and returns the Fault Location TLV's value from the data field.
146    ///
147    /// The Fault Location is an Entity ID, as defined by the standard.
148    /// This method will search for the first TLV with the correct type.
149    pub fn fault_location(&self, large_file_flag: bool) -> Result<Option<&Tlv>, CfdpError> {
150        let fault_loc_tlv = self
151            .tlvs(large_file_flag)?
152            .find(|tlv| tlv.tlv_type() == Ok(TlvType::EntityId));
153
154        Ok(fault_loc_tlv)
155    }
156
157    /// Get the raw rest field as a byte slice.
158    pub fn rest(&self) -> &[u8] {
159        &self.rest
160    }
161}
162
163#[bon]
164impl EofPdu {
165    /// Builds a new EOF PDU in the given buffer.
166    #[builder]
167    pub fn new<'a>(
168        buffer: &'a mut [u8],
169        source_entity_id: EntityId,
170        dest_entity_id: EntityId,
171        transaction_seq_num: TransactionSeqNum,
172        transmission_mode: TransmissionMode,
173        large_file_flag: bool,
174        crc_flag: bool,
175        condition_code: ConditionCode,
176        file_checksum: u32,
177        file_size: u64,
178        fault_location: Option<&'a [u8]>,
179    ) -> Result<&'a mut Pdu, CfdpError> {
180        let fixed_part_len = size_of::<u8>() + size_of::<U32>();
181        let file_size_len = if large_file_flag { 8 } else { 4 };
182        let fault_loc_len = fault_location.map_or(0, |loc| loc.len());
183
184        let specific_data_len = fixed_part_len + file_size_len + fault_loc_len;
185        let data_field_len = (DirectiveCode::size() + specific_data_len) as u16;
186
187        let header = PduHeaderFixedPart::builder()
188            .version(1)
189            .pdu_type(PduType::FileDirective)
190            .direction(Direction::TowardReceiver)
191            .tx_mode(transmission_mode)
192            .crc_flag(crc_flag)
193            .large_file_flag(large_file_flag)
194            .data_field_len(data_field_len)
195            .seg_ctrl(false)
196            .seg_meta_flag(false)
197            .build()?;
198
199        let pdu = Pdu::builder()
200            .buffer(buffer)
201            .header_fixed(header)
202            .source_entity_id(source_entity_id)
203            .destination_entity_id(dest_entity_id)
204            .transaction_seq_num(transaction_seq_num)
205            .build()?;
206
207        let data_field = pdu
208            .data_field_mut()
209            .or_else(|_| Err(CfdpError::Custom("Failed to get data field")))?;
210        let actual_data_field_len = data_field.len();
211        let directive_pdu = FileDirectivePdu::mut_from_bytes(data_field).map_err(|_| {
212            CfdpError::BufferTooSmall {
213                required: data_field_len as usize,
214                provided: actual_data_field_len,
215            }
216        })?;
217        directive_pdu.set_directive_code(DirectiveCode::Eof);
218
219        let remaining_len = directive_pdu.rest.len();
220        let rest_len = file_size_len + fault_loc_len;
221        let eof_pdu = EofPdu::mut_from_bytes_with_elems(&mut directive_pdu.rest, rest_len)
222            .map_err(|_| CfdpError::BufferTooSmall {
223                required: specific_data_len,
224                provided: remaining_len,
225            })?;
226        eof_pdu.set_condition_code(condition_code);
227        eof_pdu.set_file_checksum(file_checksum);
228        eof_pdu
229            .set_file_size(large_file_flag, file_size)
230            .map_err(|_| CfdpError::Custom("Failed to set file size"))?;
231
232        // Write TLV
233        if let Some(location) = fault_location {
234            let tlv_slice = &mut eof_pdu.rest[file_size_len..];
235            let tlv_slice_len = tlv_slice.len();
236            let (tlv, _rest) =
237                Tlv::mut_from_prefix(tlv_slice).map_err(|_| CfdpError::BufferTooSmall {
238                    required: fault_loc_len,
239                    provided: tlv_slice_len,
240                })?;
241            tlv.set_type(TlvType::EntityId);
242            tlv.set_length(location.len())
243                .map_err(|_| CfdpError::Custom("Failed to set TLV length"))?;
244            tlv.set_value(location)
245                .map_err(|_| CfdpError::Custom("Failed to set TLV value"))?;
246        }
247
248        Ok(pdu)
249    }
250}