Skip to main content

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

1use crate::transport::cfdp::pdu::CfdpError;
2use crate::transport::cfdp::pdu::EntityId;
3use crate::transport::cfdp::pdu::Pdu;
4use crate::transport::cfdp::pdu::PduHeaderFixedPart;
5use crate::transport::cfdp::pdu::TransactionSeqNum;
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::PduType;
10use crate::transport::cfdp::pdu::header::TransmissionMode;
11use crate::transport::cfdp::pdu::tlv::Tlv;
12use crate::transport::cfdp::pdu::tlv::TlvIterator;
13use crate::transport::cfdp::pdu::tlv::TlvType;
14use crate::transport::cfdp::pdu::tlv::fault_handler_override::FaultHandlerSet;
15use crate::transport::cfdp::pdu::tlv::fault_handler_override::TlvFaultHandlerOverride;
16use crate::utils::get_bits_u8;
17use crate::utils::set_bits_u8;
18
19use bon::bon;
20use zerocopy::FromBytes;
21use zerocopy::Immutable;
22use zerocopy::IntoBytes;
23use zerocopy::KnownLayout;
24use zerocopy::Unaligned;
25use zerocopy::network_endian::U32;
26use zerocopy::network_endian::U64;
27
28/// A zero-copy representation of the **data field** of a Metadata PDU.
29///
30/// This struct's layout strictly follows the CCSDS specification (Table 5-9).
31/// It consists of a fixed-size portion and a `rest` slice. The `rest` slice
32/// contains the LV-encoded file names and any optional TLVs.
33///
34/// ```text
35/// +------------------------------------+----------------+--------------------------------------+
36/// | Field Name                         | Size           | Notes                                |
37/// +------------------------------------+----------------+--------------------------------------+
38/// | Reserved for future use            | 1 bit          |                                      |
39/// | Closure requested                  | 1 bit          |                                      |
40/// | Reserved for future use            | 2 bit          |                                      |
41/// | Checksum type                      | 4 bits         | (Packed into one octet with spares)  |
42/// |                                    |                |                                      |
43/// | File Size                          | 32 or 64 bits  | FSS field.                           |
44/// |                                    | (FSS)          |                                      |
45/// | -- Start of `rest` slice --------- | -------------- | ------------------------------------ |
46/// | Source File Name                   | Variable (LV)  | Length-Value encoded.                |
47/// | Destination File Name              | Variable (LV)  | Length-Value encoded.                |
48/// | Options (Optional)                 | Variable (TLV) | Zero or more TLVs.                   |
49/// +------------------------------------+----------------+--------------------------------------+
50/// ```
51#[repr(C)]
52#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
53pub struct MetadataPdu {
54    /// An 8-bit field containing `Closure requested` (1), and `Checksum type` (4)
55    packed_flags: u8,
56    /// Contains the FSS file_size, LV file names, and optional TLVs.
57    rest: [u8],
58}
59
60#[rustfmt::skip]
61/// Bit masks for the Metadata PDU's packed fields.
62mod bitmasks {
63    /// Mask for the 1-bit reserved field (unused).
64    pub const _META_RESERVED_MASK_1: u8 =   0b_10000000;
65    /// Mask for the 1-bit closure requested flag.
66    pub const META_CLOSURE_REQ_MASK: u8 =   0b_01000000;
67    /// Mask for the 2-bit reserved field (unused).
68    pub const _META_RESERVED_MASK_2: u8 =   0b_00110000;
69    /// Mask for the 4-bit checksum type.
70    pub const META_CHECKSUM_TYPE_MASK: u8 = 0b_00001111;
71}
72
73use bitmasks::*;
74
75/// Identifies the checksum algorithm used for data integrity (Table 5-10).
76#[repr(u8)]
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum ChecksumType {
79    /// CCSDS Modular Checksum (Sum of 32-bit words).
80    Modular = 0,
81    /// CCSDS Proximity-1 CRC32.
82    Proximity1Crc32 = 1,
83    /// CRC-32C (Castagnoli), common in iSCSI.
84    Crc32c = 2,
85    /// Standard IEEE 802.3 Ethernet CRC32.
86    IeeeCrc32 = 3,
87    /// No checksum.
88    Null = 15,
89}
90
91impl TryFrom<u8> for ChecksumType {
92    type Error = CfdpError;
93    fn try_from(value: u8) -> Result<Self, Self::Error> {
94        match value {
95            0 => Ok(ChecksumType::Modular),
96            1 => Ok(ChecksumType::Proximity1Crc32),
97            2 => Ok(ChecksumType::Crc32c),
98            3 => Ok(ChecksumType::IeeeCrc32),
99            15 => Ok(ChecksumType::Null),
100            _ => Err(CfdpError::Custom("Unsupported checksum type")),
101        }
102    }
103}
104
105impl MetadataPdu {
106    /// Returns the state of the `Closure requested` flag.
107    pub fn closure_requested(&self) -> bool {
108        get_bits_u8(self.packed_flags, META_CLOSURE_REQ_MASK) == 1
109    }
110    /// Sets the `Closure requested` flag.
111    pub fn set_closure_requested(&mut self, requested: bool) {
112        set_bits_u8(
113            &mut self.packed_flags,
114            META_CLOSURE_REQ_MASK,
115            if requested { 1 } else { 0 },
116        );
117    }
118
119    /// Returns the 4-bit integer identifying the checksum algorithm to be used.
120    pub fn checksum_type(&self) -> Result<ChecksumType, CfdpError> {
121        get_bits_u8(self.packed_flags, META_CHECKSUM_TYPE_MASK).try_into()
122    }
123    /// Sets the checksum type field.
124    pub fn set_checksum_type(&mut self, checksum_type: ChecksumType) {
125        set_bits_u8(
126            &mut self.packed_flags,
127            META_CHECKSUM_TYPE_MASK,
128            checksum_type as u8,
129        );
130    }
131
132    /// Correctly parses the File-Size Sensitive (FSS) `file_size` field.
133    pub fn file_size(&self, large_file_flag: bool) -> Result<u64, CfdpError> {
134        if large_file_flag {
135            U64::ref_from_prefix(&self.rest)
136                .map_err(|_| CfdpError::Custom("Failed to parse 64-bit file size"))
137                .map(|(len, _)| len.get())
138        } else {
139            U32::ref_from_prefix(&self.rest)
140                .map_err(|_| CfdpError::Custom("Failed to parse 32-bit file size"))
141                .map(|(len, _)| len.get() as u64)
142        }
143    }
144    /// Sets the FSS file size field.
145    pub fn set_file_size(
146        &mut self,
147        large_file_flag: bool,
148        file_size: u64,
149    ) -> Result<(), CfdpError> {
150        if large_file_flag {
151            let file_size_field = U64::mut_from_prefix(&mut self.rest)
152                .map_err(|_| CfdpError::Custom("Failed to parse 64-bit file size"))?
153                .0;
154            file_size_field.set(file_size);
155            Ok(())
156        } else {
157            if file_size > u32::MAX as u64 {
158                return Err(CfdpError::Custom("File size exceeds 32-bit maximum"));
159            }
160            let file_size_field = U32::mut_from_prefix(&mut self.rest)
161                .map_err(|_| CfdpError::Custom("Failed to parse 32-bit file size"))?
162                .0;
163            file_size_field.set(file_size as u32);
164            Ok(())
165        }
166    }
167
168    /// A private helper to parse the variable-length fields after the FSS file_size.
169    /// This avoids re-parsing the LV fields in each public getter.
170    /// Returns (source_name, dest_name, options_slice)
171    pub fn variable_fields(
172        &self,
173        large_file_flag: bool,
174    ) -> Result<(&[u8], &[u8], TlvIterator<'_>), CfdpError> {
175        let file_size_len = if large_file_flag { 8 } else { 4 };
176        let mut remainder = self.rest.get(file_size_len..).ok_or_else(|| {
177            CfdpError::Custom("Invalid metadata: insufficient data for file size")
178        })?;
179
180        // Parse Source File Name (LV)
181        let source_len = *remainder
182            .first()
183            .ok_or_else(|| CfdpError::Custom("Invalid metadata: missing source name length"))?
184            as usize;
185
186        // Advance remainder past the length byte
187        remainder = remainder
188            .get(1..)
189            .ok_or(CfdpError::Custom("Invalid metadata slice"))?;
190        if remainder.len() < source_len {
191            return Err(CfdpError::Custom(
192                "Invalid metadata: insufficient data for source name",
193            ));
194        }
195        let (source_name, mut remainder) = remainder.split_at(source_len);
196
197        // Parse Destination File Name (LV)
198        let dest_len = *remainder
199            .first()
200            .ok_or_else(|| CfdpError::Custom("Invalid metadata: missing destination name length"))?
201            as usize;
202
203        // Advance remainder past the length byte
204        remainder = remainder
205            .get(1..)
206            .ok_or(CfdpError::Custom("Invalid metadata slice"))?;
207        if remainder.len() < dest_len {
208            return Err(CfdpError::Custom(
209                "Invalid metadata: insufficient data for destination name",
210            ));
211        }
212        let (dest_name, options_tlvs) = remainder.split_at(dest_len);
213
214        Ok((
215            source_name,
216            dest_name,
217            TlvIterator {
218                buffer: options_tlvs,
219            },
220        ))
221    }
222
223    /// Writes the LV-encoded file names and optional TLV options into the PDU.
224    pub fn set_variable_fields(
225        &mut self,
226        large_file_flag: bool,
227        source_file_name: &[u8],
228        dest_file_name: &[u8],
229        options: Option<&[u8]>,
230    ) -> Result<(), CfdpError> {
231        let file_size_len = if large_file_flag { 8 } else { 4 };
232        let mut cursor = file_size_len;
233
234        // Write Source File Name (LV)
235        if source_file_name.len() > 255 {
236            return Err(CfdpError::Custom("Source file name too long"));
237        }
238        // Write Destination File Name (LV)
239        if dest_file_name.len() > 255 {
240            return Err(CfdpError::Custom("Destination file name too long"));
241        }
242
243        *self
244            .rest
245            .get_mut(cursor)
246            .ok_or_else(|| CfdpError::Custom("Insufficient space for Source File Name length"))? =
247            source_file_name.len() as u8;
248        cursor += 1;
249        self.rest
250            .get_mut(cursor..cursor + source_file_name.len())
251            .ok_or_else(|| CfdpError::Custom("Insufficient space for Source File Name"))?
252            .copy_from_slice(source_file_name);
253        cursor += source_file_name.len();
254        *self.rest.get_mut(cursor).ok_or_else(|| {
255            CfdpError::Custom("Insufficient space for Destination File Name length")
256        })? = dest_file_name.len() as u8;
257        cursor += 1;
258        self.rest
259            .get_mut(cursor..cursor + dest_file_name.len())
260            .ok_or_else(|| CfdpError::Custom("Insufficient space for Destination File Name"))?
261            .copy_from_slice(dest_file_name);
262        cursor += dest_file_name.len();
263
264        // Write Options TLVs
265        if let Some(opts) = options {
266            self.rest
267                .get_mut(cursor..cursor + opts.len())
268                .ok_or_else(|| CfdpError::Custom("Insufficient space for Options TLVs"))?;
269        }
270
271        Ok(())
272    }
273
274    /// Returns a slice representing the source file name.
275    pub fn source_file_name(&self, large_file_flag: bool) -> Result<&[u8], CfdpError> {
276        self.variable_fields(large_file_flag).map(|(src, _, _)| src)
277    }
278
279    /// Returns a slice representing the destination file name.
280    pub fn dest_file_name(&self, large_file_flag: bool) -> Result<&[u8], CfdpError> {
281        self.variable_fields(large_file_flag).map(|(_, dst, _)| dst)
282    }
283
284    /// Returns an iterator over the TLV options in the metadata.
285    pub fn options(&self, large_file_flag: bool) -> Result<TlvIterator<'_>, CfdpError> {
286        self.variable_fields(large_file_flag)
287            .map(|(_, _, tlv_iter)| tlv_iter)
288    }
289
290    /// Parses the Fault Handler Override TLVs and returns a `FaultHandlerSet`.
291    /// The set is initialized with default handlers, which are then updated
292    /// by any overrides present in the PDU's options.
293    pub fn fault_handler_overrides(
294        &self,
295        large_file_flag: bool,
296    ) -> Result<FaultHandlerSet, CfdpError> {
297        // Start with a set of default handlers.
298        let mut handlers = FaultHandlerSet::default();
299        let tlv_iter = self.options(large_file_flag)?;
300
301        for tlv in tlv_iter {
302            if tlv.tlv_type()? == TlvType::FaultHandlerOverride {
303                // The value of a FaultHandlerOverride TLV is exactly one byte.
304                if tlv.value().len() != 1 {
305                    // Malformed TLV, maybe log this and continue.
306                    continue;
307                }
308                let fho_tlv = TlvFaultHandlerOverride::ref_from_bytes(tlv.value())
309                    .map_err(|_| CfdpError::Custom("Failed to parse Fault Handler Override TLV"))?;
310
311                // Use the set_handler method to update the bit-packed u32.
312                handlers.set_handler(fho_tlv.condition_code()?, fho_tlv.handler_code()?);
313            }
314        }
315        Ok(handlers)
316    }
317
318    /// Returns an iterator over the Filestore Request TLVs in the metadata.
319    pub fn filestore_requests(
320        &self,
321        large_file_flag: bool,
322    ) -> Result<impl Iterator<Item = &Tlv>, CfdpError> {
323        Ok(self
324            .options(large_file_flag)?
325            .filter(|tlv| tlv.tlv_type() == Ok(TlvType::FilestoreRequest)))
326    }
327
328    /// Get the raw rest field as a byte slice.
329    pub fn rest(&self) -> &[u8] {
330        &self.rest
331    }
332}
333
334#[bon]
335impl MetadataPdu {
336    /// Builds a new Metadata PDU in the given buffer.
337    #[builder]
338    pub fn new<'a>(
339        buffer: &'a mut [u8],
340        source_entity_id: EntityId,
341        dest_entity_id: EntityId,
342        transaction_seq_num: TransactionSeqNum,
343        transmission_mode: TransmissionMode,
344        large_file_flag: bool,
345        crc_flag: bool,
346        closure_requested: bool,
347        checksum_type: ChecksumType,
348        file_size: u64,
349        source_file_name: &'a [u8],
350        dest_file_name: &'a [u8],
351        options: Option<&'a [u8]>,
352    ) -> Result<&'a mut Pdu, CfdpError> {
353        // --- Correct Size Calculation ---
354        let fixed_part_len = size_of::<u8>(); // packed_flags
355        let file_size_len = if large_file_flag { 8 } else { 4 };
356        let src_name_lv_len = 1 + source_file_name.len(); // +1 for Length octet
357        let dst_name_lv_len = 1 + dest_file_name.len(); // +1 for Length octet
358        let options_len = options.map_or(0, |o| o.len());
359
360        let specific_data_len =
361            fixed_part_len + file_size_len + src_name_lv_len + dst_name_lv_len + options_len;
362        let data_field_len = (1 + specific_data_len) as u16;
363
364        let header = PduHeaderFixedPart::builder()
365            .version(1)
366            .pdu_type(PduType::FileDirective)
367            .direction(Direction::TowardReceiver)
368            .tx_mode(transmission_mode)
369            .crc_flag(crc_flag)
370            .large_file_flag(large_file_flag)
371            .data_field_len(data_field_len)
372            .seg_ctrl(false)
373            .seg_meta_flag(false)
374            .build()?;
375
376        let pdu = Pdu::builder()
377            .buffer(buffer)
378            .header_fixed(header)
379            .source_entity_id(source_entity_id)
380            .destination_entity_id(dest_entity_id)
381            .transaction_seq_num(transaction_seq_num)
382            .build()?;
383
384        let data_field = pdu
385            .data_field_mut()
386            .map_err(|_| CfdpError::Custom("Failed to get data field"))?;
387        let provided_len = data_field.len();
388        let directive_pdu = FileDirectivePdu::mut_from_bytes(data_field).map_err(|_| {
389            CfdpError::BufferTooSmall {
390                required: data_field_len as usize,
391                provided: provided_len,
392            }
393        })?;
394        directive_pdu.set_directive_code(DirectiveCode::Metadata);
395
396        let provided_len = directive_pdu.rest.len();
397        let rest_len = file_size_len + src_name_lv_len + dst_name_lv_len + options_len;
398        let meta_pdu =
399            MetadataPdu::mut_from_bytes_with_elems(&mut directive_pdu.rest, rest_len)
400                .map_err(|_| CfdpError::BufferTooSmall {
401                    required: specific_data_len,
402                    provided: provided_len,
403                })?;
404        meta_pdu.set_closure_requested(closure_requested);
405        meta_pdu.set_checksum_type(checksum_type);
406
407        meta_pdu
408            .set_file_size(large_file_flag, file_size)
409            .map_err(|_| CfdpError::Custom("Failed to set file size in Metadata PDU"))?;
410        meta_pdu
411            .set_variable_fields(large_file_flag, source_file_name, dest_file_name, options)
412            .map_err(|_| CfdpError::Custom("Failed to set variable fields in Metadata PDU"))?;
413
414        Ok(pdu)
415    }
416}