Skip to main content

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

1use bon::bon;
2use zerocopy::FromBytes;
3use zerocopy::Immutable;
4use zerocopy::IntoBytes;
5use zerocopy::KnownLayout;
6use zerocopy::Unaligned;
7
8use crate::transport::cfdp::pdu::CfdpError;
9use crate::transport::cfdp::pdu::EntityId;
10use crate::transport::cfdp::pdu::Pdu;
11use crate::transport::cfdp::pdu::TransactionSeqNum;
12use crate::transport::cfdp::pdu::file_directive::ConditionCode;
13use crate::transport::cfdp::pdu::file_directive::DirectiveCode;
14use crate::transport::cfdp::pdu::file_directive::FileDirectivePdu;
15use crate::transport::cfdp::pdu::header::Direction;
16use crate::transport::cfdp::pdu::header::PduHeaderFixedPart;
17use crate::transport::cfdp::pdu::header::PduType;
18use crate::transport::cfdp::pdu::header::TransmissionMode;
19use crate::utils::get_bits_u8;
20use crate::utils::set_bits_u8;
21
22/// A zero-copy representation of the **data field** of an Acknowledgment (ACK) PDU.
23///
24/// This struct's layout strictly follows the CCSDS specification (Table 5-8).
25/// It consists entirely of fixed-size fields, with several values bit-packed
26/// across two octets.
27///
28/// ```text
29/// +------------------------------------+----------+--------------------------------------+
30/// | Field Name                         | Size     | Notes                                |
31/// +------------------------------------+----------+--------------------------------------+
32/// | Directive code of acked PDU        | 4 bits   | e.g., 0x04 for EOF, 0x05 for Fin.    |
33/// | Directive subtype code             | 4 bits   | (Both are packed into one octet)     |
34/// |                                    |          |                                      |
35/// | Condition code                     | 4 bits   | Condition code of the acked PDU.     |
36/// | Reserved for future use            | 2 bits   |                                      |
37/// | Transaction status                 | 2 bits   | (All three are packed into one octet)|
38/// +------------------------------------+----------+--------------------------------------+
39/// ```
40#[repr(C)]
41#[derive(FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable, Debug, PartialEq, Eq)]
42pub struct AckPdu {
43    /// An 8-bit field containing the 4-bit `Directive code of acknowledged PDU`
44    /// and the 4-bit `Directive subtype code`.
45    packed_codes: u8,
46    /// An 8-bit field containing the 4-bit `Condition code`, 2 `Spare` bits,
47    /// and the 2-bit `Transaction status`.
48    packed_status: u8,
49}
50
51#[rustfmt::skip]
52/// Bit masks for the ACK PDU's packed fields.
53mod bitmasks {
54    /// Mask for the 4-bit directive code of the acknowledged PDU.
55    pub const ACK_DIR_CODE_MASK: u8 =         0b_11110000;
56    /// Mask for the 4-bit directive subtype code.
57    pub const ACK_DIR_SUBTYPE_CODE_MASK: u8 = 0b_00001111;
58    /// Mask for the 4-bit condition code.
59    pub const ACK_CC_MASK: u8 =                 0b_11110000;
60    /// Mask for the 2-bit reserved field (unused).
61    pub const _ACK_RESERVED_MASK: u8 =          0b_00001100;
62    /// Mask for the 2-bit transaction status.
63    pub const ACK_TRANSACTION_STATUS_MASK: u8 = 0b_00000011;
64}
65
66use bitmasks::*;
67
68/// The status of a transaction at a remote entity, as reported in an ACK PDU.
69#[repr(u8)]
70#[derive(Debug, PartialEq, Eq, Copy, Clone)]
71pub enum TransactionStatus {
72    /// Transaction status is undefined.
73    Undefined = 0b00,
74    /// Transaction is currently active.
75    Active = 0b01,
76    /// Transaction has been terminated.
77    Terminated = 0b10,
78    /// Transaction is unrecognized by the remote entity.
79    Unrecognized = 0b11,
80}
81
82impl TryFrom<u8> for TransactionStatus {
83    type Error = CfdpError;
84    fn try_from(val: u8) -> Result<Self, Self::Error> {
85        let val = match val {
86            0b00 => TransactionStatus::Undefined,
87            0b01 => TransactionStatus::Active,
88            0b10 => TransactionStatus::Terminated,
89            0b11 => TransactionStatus::Unrecognized,
90            _ => return Err(CfdpError::Custom("Invalid TransactionStatus value")),
91        };
92        Ok(val)
93    }
94}
95
96/// Identifies which directive is being acknowledged.
97#[derive(Debug, PartialEq, Eq, Copy, Clone)]
98pub enum AckedDirectiveCode {
99    /// Acknowledging an EOF PDU.
100    Eof,
101    /// Acknowledging a Finished PDU.
102    Finished,
103}
104
105impl TryFrom<u8> for AckedDirectiveCode {
106    type Error = CfdpError;
107    fn try_from(val: u8) -> Result<Self, Self::Error> {
108        let val = match val {
109            0x04 => AckedDirectiveCode::Eof,
110            0x05 => AckedDirectiveCode::Finished,
111            _ => return Err(CfdpError::Custom("Invalid AckedPduDirectiveCode value")),
112        };
113        Ok(val)
114    }
115}
116
117impl AckPdu {
118    /// Returns the directive code of the PDU that is being acknowledged (e.g., EOF, Finished).
119    fn directive_code_of_acked_pdu(&self) -> Result<AckedDirectiveCode, CfdpError> {
120        AckedDirectiveCode::try_from(get_bits_u8(self.packed_codes, ACK_DIR_CODE_MASK))
121    }
122    /// Sets the directive code of the PDU being acknowledged.
123    fn set_directive_code_of_acked_pdu(&mut self, code: AckedDirectiveCode) {
124        set_bits_u8(&mut self.packed_codes, ACK_DIR_CODE_MASK, code as u8);
125    }
126
127    /// Returns the directive subtype code.
128    fn directive_subtype_code(&self) -> u8 {
129        get_bits_u8(self.packed_codes, ACK_DIR_SUBTYPE_CODE_MASK)
130    }
131    /// Sets the directive subtype code.
132    fn set_directive_subtype_code(&mut self, subtype: u8) {
133        set_bits_u8(&mut self.packed_codes, ACK_DIR_SUBTYPE_CODE_MASK, subtype);
134    }
135
136    /// Interprets the packed codes to determine what specific PDU is being acknowledged.
137    pub fn acked_directive_code(&self) -> Result<AckedDirectiveCode, CfdpError> {
138        let directive_code = self.directive_code_of_acked_pdu()?;
139        let subtype_code = self.directive_subtype_code();
140        match (directive_code, subtype_code) {
141            (AckedDirectiveCode::Eof, 0) => Ok(AckedDirectiveCode::Eof),
142            (AckedDirectiveCode::Finished, 1) => Ok(AckedDirectiveCode::Finished),
143            _ => Err(CfdpError::Custom("Unknown acknowledged PDU type")),
144        }
145    }
146
147    /// Returns the condition code of the PDU that is being acknowledged.
148    pub fn condition_code(&self) -> Result<ConditionCode, CfdpError> {
149        ConditionCode::try_from(get_bits_u8(self.packed_status, ACK_CC_MASK))
150    }
151    /// Sets the condition code of the acknowledged PDU.
152    pub fn set_condition_code(&mut self, code: ConditionCode) {
153        set_bits_u8(&mut self.packed_status, ACK_CC_MASK, code as u8);
154    }
155
156    /// Returns the status of the transaction at the acknowledging entity.
157    pub fn transaction_status(&self) -> Result<TransactionStatus, CfdpError> {
158        TransactionStatus::try_from(get_bits_u8(self.packed_status, ACK_TRANSACTION_STATUS_MASK))
159    }
160    /// Sets the transaction status field.
161    pub fn set_transaction_status(&mut self, status: TransactionStatus) {
162        set_bits_u8(
163            &mut self.packed_status,
164            ACK_TRANSACTION_STATUS_MASK,
165            status as u8,
166        );
167    }
168}
169
170#[bon]
171impl AckPdu {
172    /// Builds a new ACK PDU in the given buffer.
173    #[builder]
174    pub fn new<'a>(
175        buffer: &'a mut [u8],
176        // Transaction Context
177        source_entity_id: EntityId,
178        dest_entity_id: EntityId,
179        transaction_seq_num: TransactionSeqNum,
180        transmission_mode: TransmissionMode,
181        crc_flag: bool,
182        // ACK Specific
183        acked_directive_code: AckedDirectiveCode,
184        condition_code: ConditionCode,
185        transaction_status: TransactionStatus,
186    ) -> Result<&'a mut Pdu, CfdpError> {
187        let data_field_len = (DirectiveCode::size() + size_of::<Self>()) as u16;
188
189        let header = PduHeaderFixedPart::builder()
190            .version(1)
191            .pdu_type(PduType::FileDirective)
192            .direction(Direction::TowardSender)
193            .tx_mode(transmission_mode)
194            .crc_flag(crc_flag)
195            .large_file_flag(false) // Not relevant for ACK
196            .data_field_len(data_field_len)
197            .seg_ctrl(false)
198            .seg_meta_flag(false)
199            .build()?;
200
201        let pdu = Pdu::builder()
202            .buffer(buffer)
203            .header_fixed(header)
204            .source_entity_id(source_entity_id)
205            .destination_entity_id(dest_entity_id)
206            .transaction_seq_num(transaction_seq_num)
207            .build()?;
208
209        let data_field = pdu.data_field_mut().unwrap();
210        let directive_pdu = FileDirectivePdu::mut_from_bytes(data_field)
211            .map_err(|_| CfdpError::Custom("Failed to create FileDirectivePdu"))?;
212        directive_pdu.set_directive_code(DirectiveCode::Ack);
213
214        let ack_pdu = AckPdu::mut_from_bytes(&mut directive_pdu.rest)
215            .map_err(|_| CfdpError::Custom("Failed to create AckPdu"))?;
216        ack_pdu.set_directive_code_of_acked_pdu(acked_directive_code);
217        match acked_directive_code {
218            AckedDirectiveCode::Eof => ack_pdu.set_directive_subtype_code(0),
219            AckedDirectiveCode::Finished => ack_pdu.set_directive_subtype_code(1),
220        }
221        ack_pdu.set_condition_code(condition_code);
222        ack_pdu.set_transaction_status(transaction_status);
223
224        Ok(pdu)
225    }
226}