Skip to main content

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

1use crate::transport::cfdp::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::utils::get_bits_u8;
12use crate::utils::set_bits_u8;
13
14use bon::bon;
15use zerocopy::FromBytes;
16use zerocopy::Immutable;
17use zerocopy::IntoBytes;
18use zerocopy::KnownLayout;
19use zerocopy::Unaligned;
20
21/// A zero-copy representation of the data field of a Prompt PDU.
22///
23/// This struct's layout strictly follows the CCSDS specification (Table 5-12).
24/// It has a fixed size of 2 bytes.
25///
26/// ```text
27/// +------------------------------------+----------+------------------------------------+
28/// | Field Name                         | Size     | Notes                              |
29/// +------------------------------------+----------+------------------------------------+
30/// | Response required                  | 1 bit    | 0=NAK, 1=Keep Alive                |
31/// | Spare                              | 7 bits   | (Both are packed into one octet)   |
32/// +------------------------------------+----------+------------------------------------+
33/// ```
34#[repr(C)]
35#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
36pub struct PromptPdu {
37    /// Packed byte containing the response-required bit and spare bits.
38    packed_flags: u8,
39}
40
41#[rustfmt::skip]
42/// Bit masks for the Prompt PDU's packed fields.
43mod bitmasks {
44    /// Mask for the 1-bit response required flag.
45    pub const PROMPT_RESPONSE_REQUIRED_MASK: u8 = 0b_10000000;
46    /// Mask for the 7-bit spare field (unused).
47    pub const _PROMPT_RESPONSE_RESERVED: u8 =     0b_01111111;
48}
49
50use bitmasks::*;
51
52/// The type of response requested by a Prompt PDU.
53#[derive(Debug, PartialEq, Eq)]
54pub enum PromptResponse {
55    /// Request a Keep Alive response.
56    KeepAlive,
57    /// Request a NAK response.
58    Nak,
59}
60
61impl PromptPdu {
62    /// Returns whether a response is required for this Prompt PDU.
63    pub fn prompt_response(&self) -> PromptResponse {
64        if get_bits_u8(self.packed_flags, PROMPT_RESPONSE_REQUIRED_MASK) == 1 {
65            PromptResponse::KeepAlive
66        } else {
67            PromptResponse::Nak
68        }
69    }
70
71    /// Sets the response type for this Prompt PDU.
72    pub fn set_prompt_response(&mut self, response: PromptResponse) {
73        let response_bit = match response {
74            PromptResponse::Nak => 0,
75            PromptResponse::KeepAlive => 1,
76        };
77        set_bits_u8(
78            &mut self.packed_flags,
79            PROMPT_RESPONSE_REQUIRED_MASK,
80            response_bit,
81        );
82    }
83}
84
85#[bon]
86impl PromptPdu {
87    /// Builds a new Prompt PDU in the given buffer.
88    #[builder]
89    pub fn new<'a>(
90        buffer: &'a mut [u8],
91        source_entity_id: EntityId,
92        dest_entity_id: EntityId,
93        transaction_seq_num: TransactionSeqNum,
94        transmission_mode: TransmissionMode,
95        crc_flag: bool,
96        response_required: PromptResponse,
97    ) -> Result<&'a mut Pdu, CfdpError> {
98        let data_field_len = (DirectiveCode::size() + size_of::<Self>()) as u16;
99
100        let header = PduHeaderFixedPart::builder()
101            .version(1)
102            .pdu_type(PduType::FileDirective)
103            .direction(Direction::TowardReceiver) // Prompt is always sent to the receiver
104            .tx_mode(transmission_mode)
105            .crc_flag(crc_flag)
106            .large_file_flag(false) // Not relevant
107            .data_field_len(data_field_len)
108            .seg_ctrl(false)
109            .seg_meta_flag(false)
110            .build()?;
111
112        let pdu = Pdu::builder()
113            .buffer(buffer)
114            .header_fixed(header)
115            .source_entity_id(source_entity_id)
116            .destination_entity_id(dest_entity_id)
117            .transaction_seq_num(transaction_seq_num)
118            .build()?;
119
120        let data_field = pdu.data_field_mut().unwrap();
121        let directive_pdu = FileDirectivePdu::mut_from_bytes(data_field).unwrap();
122        directive_pdu.set_directive_code(DirectiveCode::Prompt);
123
124        let prompt_pdu = PromptPdu::mut_from_bytes(&mut directive_pdu.rest).unwrap();
125        prompt_pdu.set_prompt_response(response_required);
126
127        Ok(pdu)
128    }
129}