Skip to main content

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

1use crate::transport::cfdp::pdu::Pdu;
2use crate::transport::cfdp::pdu::file_directive::ConditionCode;
3use crate::transport::cfdp::pdu::header::TransmissionMode;
4use crate::transport::cfdp::CfdpError;
5use crate::transport::cfdp::pdu::EntityId;
6use crate::transport::cfdp::pdu::TransactionSeqNum;
7use crate::transport::cfdp::pdu::file_directive::DirectiveCode;
8use crate::transport::cfdp::pdu::file_directive::FileDirectivePdu;
9use crate::transport::cfdp::pdu::header::Direction;
10use crate::transport::cfdp::pdu::header::PduHeaderFixedPart;
11use crate::transport::cfdp::pdu::header::PduType;
12use crate::transport::cfdp::pdu::tlv::TlvIterator;
13use crate::transport::cfdp::pdu::tlv::TlvType;
14use crate::utils::get_bits_u8;
15use crate::utils::set_bits_u8;
16
17use bon::bon;
18use zerocopy::FromBytes;
19use zerocopy::Immutable;
20use zerocopy::IntoBytes;
21use zerocopy::KnownLayout;
22use zerocopy::Unaligned;
23
24/// A zero-copy representation of the **data field** of a Finished PDU.
25///
26/// This struct's layout strictly follows the CCSDS specification (Table 5-7).
27/// It consists of a fixed-size portion and a `rest` slice. The `rest` slice
28/// contains any optional `Filestore Responses` and `Fault Location` TLVs.
29///
30/// This struct can be created from a PDU's data field using `FinishedPdu::ref_from_bytes()`.
31/// Access to the bit-packed fields and optional TLVs is provided via parser methods.
32///
33/// ```text
34/// +------------------------------------+----------------+--------------------------------------+
35/// | Field Name                         | Size           | Notes                                |
36/// +------------------------------------+----------------+--------------------------------------+
37/// | Condition Code                     | 4 bits         |                                      |
38/// | Reserved for future use            | 1 bit          |                                      |
39/// | Delivery Code                      | 1 bit          |                                      |
40/// | File Status                        | 2 bits         | (All four are packed into one octet) |
41/// |                                    |                |                                      |
42/// | -- Start of `rest` slice --------- | -------------- | ------------------------------------ |
43/// | Filestore Responses (Optional)     | Variable (TLV) | Zero or more Filestore Response TLVs |
44/// | Fault Location (Optional)          | Variable (TLV) | Present if Condition Code is an error|
45/// +------------------------------------+----------------+--------------------------------------+
46/// ```
47#[repr(C)]
48#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
49pub struct FinishedPdu {
50    /// An 8-bit field containing the 4-bit `Condition Code`, 1 `Spare` bit,
51    /// 1-bit `Delivery Code`, and 2-bit `File Status`.
52    packed_flags: u8,
53    /// Contains any optional `Filestore Responses` and `Fault Location` TLVs.
54    rest: [u8],
55}
56
57#[rustfmt::skip]
58/// Bit masks for the Finished PDU's packed fields.
59mod bitmasks {
60    /// Mask for the 4-bit condition code.
61    pub const FINISHED_CONDITION_CODE_MASK: u8 = 0b_11110000;
62    /// Mask for the 1-bit reserved field (unused).
63    pub const _FINISHED_RESERVED_MASK: u8 =      0b_00001000;
64    /// Mask for the 1-bit delivery code.
65    pub const FINISHED_DELIVERY_CODE_MASK: u8 =  0b_00000100;
66    /// Mask for the 2-bit file status.
67    pub const FINISHED_FILE_STATUS_MASK: u8 =    0b_00000011;
68}
69
70use bitmasks::*;
71
72/// Final status of the delivered file at the receiver.
73#[repr(u8)]
74#[derive(Debug, PartialEq, Eq, Copy, Clone)]
75pub enum FileStatus {
76    /// File was discarded deliberately.
77    DiscardedDeliberately = 0b00,
78    /// File was discarded due to a filestore rejection.
79    DiscardedFileStoreRejection = 0b01,
80    /// File was retained successfully.
81    Retained = 0b10,
82    /// File status is not reported.
83    Unreported = 0b11,
84}
85
86impl TryFrom<u8> for FileStatus {
87    type Error = ();
88
89    fn try_from(val: u8) -> Result<Self, ()> {
90        let res = match val {
91            0b00 => FileStatus::DiscardedDeliberately,
92            0b01 => FileStatus::DiscardedFileStoreRejection,
93            0b10 => FileStatus::Retained,
94            0b11 => FileStatus::Unreported,
95            _ => return Err(()),
96        };
97        Ok(res)
98    }
99}
100
101impl FinishedPdu {
102    /// Returns the Condition Code (Table 5-5) from the bit-packed field.
103    pub fn condition_code(&self) -> Result<ConditionCode, CfdpError> {
104        ConditionCode::try_from(get_bits_u8(self.packed_flags, FINISHED_CONDITION_CODE_MASK))
105    }
106    /// Sets the condition code field.
107    pub fn set_condition_code(&mut self, code: ConditionCode) {
108        set_bits_u8(&mut self.packed_flags, FINISHED_CONDITION_CODE_MASK, code as u8);
109    }
110
111    /// Returns the Delivery Code from the bit-packed field. `true` means Data Complete.
112    pub fn delivery_code(&self) -> bool {
113        // Delivery code is '0' for Data Complete, '1' for Incomplete.
114        // We'll return a bool where `true` means complete.
115        get_bits_u8(self.packed_flags, FINISHED_DELIVERY_CODE_MASK) == 0
116    }
117    /// Sets the delivery code. `true` means Data Complete.
118    pub fn set_delivery_code(&mut self, complete: bool) {
119        set_bits_u8(
120            &mut self.packed_flags,
121            FINISHED_DELIVERY_CODE_MASK,
122            (!complete) as u8,
123        );
124    }
125
126    /// Returns the File Status (Table 5-7) from the bit-packed field.
127    pub fn file_status(&self) -> Option<FileStatus> {
128        FileStatus::try_from(get_bits_u8(self.packed_flags, FINISHED_FILE_STATUS_MASK)).ok()
129    }
130    /// Sets the file status field.
131    pub fn set_file_status(&mut self, status: FileStatus) {
132        set_bits_u8(&mut self.packed_flags, FINISHED_FILE_STATUS_MASK, status as u8);
133    }
134
135    /// Returns an iterator over all TLVs in the data field.
136    pub fn tlvs(&self) -> TlvIterator<'_> {
137        TlvIterator { buffer: &self.rest }
138    }
139
140    /// Returns an iterator that filters for only Filestore Response TLVs.
141    /// The value of each yielded TLV is a complete Filestore Response record.
142    pub fn filestore_responses(&self) -> impl Iterator<Item = &[u8]> + '_ {
143        self.tlvs()
144            .filter(|tlv| tlv.tlv_type() == Ok(TlvType::FilestoreResponse))
145            .map(|tlv| tlv.value())
146    }
147
148    /// Finds and returns the Fault Location TLV's value, which is an Entity ID.
149    pub fn fault_location(&self) -> Option<&[u8]> {
150        self.tlvs()
151            .find(|tlv| tlv.tlv_type() == Ok(TlvType::EntityId))
152            .map(|tlv| tlv.value())
153    }
154
155    /// Writes optional Filestore Response and Fault Location TLVs into the rest slice.
156    pub fn set_tlvs(
157        &mut self,
158        filestore_responses: Option<&[u8]>,
159        fault_location: Option<&[u8]>,
160    ) -> Result<(), CfdpError> {
161        let mut cursor = 0;
162        if let Some(responses) = filestore_responses {
163            let len = responses.len();
164            self.rest
165                .get_mut(cursor..cursor + len)
166                .ok_or_else(|| CfdpError::Custom("Insufficient space for Filestore Responses"))?
167                .copy_from_slice(responses);
168            cursor += len;
169        }
170        if let Some(location) = fault_location {
171            let len = location.len();
172            self.rest
173                .get_mut(cursor..cursor + len)
174                .ok_or_else(|| CfdpError::Custom("Insufficient space for Fault Location"))?
175                .copy_from_slice(location);
176        }
177        Ok(())
178    }
179
180    /// Get the raw rest field as a byte slice.
181    pub fn rest(&self) -> &[u8] {
182        &self.rest
183    }
184}
185
186#[bon]
187impl FinishedPdu {
188    /// Builds a new Finished PDU in the given buffer.
189    #[builder]
190    pub fn new<'a>(
191        buffer: &'a mut [u8],
192        source_entity_id: EntityId,
193        dest_entity_id: EntityId,
194        transaction_seq_num: TransactionSeqNum,
195        transmission_mode: TransmissionMode,
196        large_file_flag: bool,
197        crc_flag: bool,
198        condition_code: ConditionCode,
199        delivery_code_complete: bool,
200        file_status: FileStatus,
201        filestore_responses: Option<&'a [u8]>,
202        fault_location: Option<&'a [u8]>,
203    ) -> Result<&'a mut Pdu, CfdpError> {
204        let fixed_part_len = size_of::<u8>();
205        let fs_responses_len = filestore_responses.map_or(0, |r| r.len());
206        let fault_loc_len = fault_location.map_or(0, |loc| loc.len());
207        let specific_data_len = fixed_part_len + fs_responses_len + fault_loc_len;
208        let data_field_len = (1 + specific_data_len) as u16;
209
210        let header = PduHeaderFixedPart::builder()
211            .version(1)
212            .pdu_type(PduType::FileDirective)
213            .direction(Direction::TowardSender)
214            .tx_mode(transmission_mode)
215            .crc_flag(crc_flag)
216            .large_file_flag(large_file_flag)
217            .data_field_len(data_field_len)
218            .seg_ctrl(false)
219            .seg_meta_flag(false)
220            .build()?;
221
222        let pdu = Pdu::builder()
223            .buffer(buffer)
224            .header_fixed(header)
225            .source_entity_id(source_entity_id)
226            .destination_entity_id(dest_entity_id)
227            .transaction_seq_num(transaction_seq_num)
228            .build()?;
229
230        let data_field = pdu.data_field_mut()?;
231        let directive_pdu = FileDirectivePdu::mut_from_bytes(data_field)
232            .map_err(|_| CfdpError::Custom("Failed to parse File Directive PDU from data field"))?;
233        directive_pdu.set_directive_code(DirectiveCode::Finished);
234        let remaining_len = directive_pdu.rest.len();
235
236        let rest_len = fs_responses_len + fault_loc_len;
237        let (fin_pdu, _rest) =
238            FinishedPdu::mut_from_prefix_with_elems(&mut directive_pdu.rest, rest_len)
239                .map_err(|_| CfdpError::BufferTooSmall {
240                    provided: remaining_len,
241                    required: specific_data_len,
242                })?;
243        fin_pdu.set_condition_code(condition_code);
244        fin_pdu.set_delivery_code(delivery_code_complete);
245        fin_pdu.set_file_status(file_status);
246        fin_pdu.set_tlvs(filestore_responses, fault_location)?;
247
248        Ok(pdu)
249    }
250}