Skip to main content

leodos_protocols/transport/cfdp/pdu/tlv/
filestore_response.rs

1use zerocopy::FromBytes;
2use zerocopy::Immutable;
3use zerocopy::IntoBytes;
4use zerocopy::KnownLayout;
5use zerocopy::Unaligned;
6
7use crate::transport::cfdp::CfdpError;
8use crate::transport::cfdp::filestore::FileId;
9use crate::transport::cfdp::pdu::tlv::FilestoreAction;
10use crate::utils::get_bits_u8;
11
12/// A zero-copy view of the Value of a Filestore Response TLV.
13#[repr(C)]
14#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
15pub struct TlvFilestoreResponse {
16    /// Packed byte with the 4-bit action code and 4-bit status code.
17    action_and_status_code: u8,
18    /// Contains LV-encoded file names and an LV-encoded message.
19    rest: [u8],
20}
21
22/// A parsed filestore response containing the action and file names.
23#[derive(Debug, PartialEq, Eq, Clone)]
24pub struct FilestoreResponse {
25    /// The filestore action that was requested.
26    pub action: FilestoreAction,
27    /// The primary file name from the original request.
28    pub first_file_name: FileId,
29    /// The secondary file name, used by rename/append/replace actions.
30    pub second_file_name: FileId,
31}
32
33#[rustfmt::skip]
34mod bitmasks {
35    pub const ACTION_CODE_MASK: u8 = 0b1111_0000;
36    pub const STATUS_CODE_MASK: u8 = 0b0000_1111;
37}
38
39impl TlvFilestoreResponse {
40    /// Returns the filestore action code from the packed byte.
41    pub fn action(&self) -> Result<FilestoreAction, CfdpError> {
42        get_bits_u8(self.action_and_status_code, bitmasks::ACTION_CODE_MASK).try_into()
43    }
44
45    /// Returns the 4-bit status code from the packed byte.
46    pub fn status_code(&self) -> u8 {
47        get_bits_u8(self.action_and_status_code, bitmasks::STATUS_CODE_MASK)
48    }
49
50    // NOTE: The parsing logic here can get complex. This implementation assumes a fixed order.
51    // The spec is slightly ambiguous on whether all fields are always present.
52    // This implementation follows the implied structure from Table 5-17.
53
54    /// Parses and returns the first file name from the response.
55    pub fn first_file_name(&self) -> Result<&[u8], CfdpError> {
56        let len = *self
57            .rest
58            .first()
59            .ok_or(CfdpError::Custom("Missing first file name length"))? as usize;
60        self.rest
61            .get(1..1 + len)
62            .ok_or(CfdpError::Custom("Invalid first file name slice"))
63    }
64
65    /// Parses the rest of the TLV value to find the second file name and the message.
66    /// Returns `(second_file_name, message, remainder)`
67    /// Parses the second file name and status message from after the first LV name.
68    fn parse_after_first_name(&self) -> Result<(Option<&[u8]>, &[u8]), CfdpError> {
69        let first_lv_len = 1 + self.first_file_name()?.len();
70        let mut remainder = self
71            .rest
72            .get(first_lv_len..)
73            .ok_or(CfdpError::Custom("Invalid slice after first file name"))?;
74
75        let has_second = matches!(
76            self.action()?,
77            FilestoreAction::RenameFile
78                | FilestoreAction::AppendFile
79                | FilestoreAction::ReplaceFile
80        );
81
82        let second_file_name = if has_second {
83            let len = *remainder
84                .first()
85                .ok_or(CfdpError::Custom("Missing second name len"))?
86                as usize;
87            remainder = remainder
88                .get(1..)
89                .ok_or(CfdpError::Custom("Invalid FS Response"))?;
90            let (name, rest) = remainder.split_at(len);
91            remainder = rest;
92            Some(name)
93        } else {
94            None
95        };
96
97        let msg_len = *remainder
98            .first()
99            .ok_or(CfdpError::Custom("Missing message len"))? as usize;
100        let message = remainder
101            .get(1..1 + msg_len)
102            .ok_or(CfdpError::Custom("Invalid message slice"))?;
103
104        Ok((second_file_name, message))
105    }
106
107    /// Returns the second file name, if present for the action type.
108    pub fn second_file_name(&self) -> Result<Option<&[u8]>, CfdpError> {
109        self.parse_after_first_name().map(|(name, _)| name)
110    }
111
112    /// Returns the filestore status message bytes.
113    pub fn message(&self) -> Result<&[u8], CfdpError> {
114        self.parse_after_first_name().map(|(_, msg)| msg)
115    }
116}