Skip to main content

leodos_protocols/network/isl/routing/
packet.rs

1//! Defines the Inter-Satellite Link (ISL) message structure and builders.
2//!
3//! This module builds upon the `Telecommand` structure to create a specialized
4//! packet type for the ISL protocol, which includes routing and application-level
5//! action information.
6
7use crate::network::cfe::tc::Telecommand;
8use crate::network::cfe::tc::TelecommandError;
9use crate::network::cfe::tc::TelecommandSecondaryHeader;
10use crate::network::isl::address::Address;
11use crate::network::isl::address::RawAddress;
12use crate::network::spp::Apid;
13use crate::network::spp::PrimaryHeader;
14use crate::network::spp::SequenceCount;
15use crate::utils::checksum_u8;
16use crate::utils::validate_checksum_u8;
17use bon::bon;
18use core::mem::size_of;
19use zerocopy::FromBytes;
20use zerocopy::Immutable;
21use zerocopy::IntoBytes;
22use zerocopy::KnownLayout;
23use zerocopy::Unaligned;
24
25/// A zero-copy view over a complete `IslTelecommand` in a raw byte buffer.
26///
27/// This represents the highest level of packet specialization in the protocol stack.
28/// It is a `Telecommand` where the payload is guaranteed to contain a structured
29/// `IslHeader` followed by the variable-length `payload` for the application.
30///
31/// ```text
32/// +----------------------------+-----------+
33/// | Field Name                 | Size      |
34/// +----------------------------+-----------+
35/// + -- cFE Telecommand Hdrs -- | --------- |
36/// |                            |           |
37/// | Primary Header             | 6 bytes   |
38/// | Secondary Header           | 2 bytes   |
39/// |                            |           |
40/// | -- ISL Header ------------ | --------- |
41/// |                            |           |
42/// | Target Orbit               | 1 byte    |
43/// | Target Satellite           | 1 byte    |
44/// |                            |           |
45/// | -- Payload (Variable) ---- | --------- |
46/// |                            |           |
47/// | Application Data           | 0-65528 B |
48/// +----------------------------+-----------+
49/// ```
50#[repr(C, packed)]
51#[derive(FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable)]
52pub struct IslRoutingTelecommand {
53    /// CCSDS SPP primary header.
54    pub primary: PrimaryHeader,
55    /// CFE command secondary header.
56    pub secondary: TelecommandSecondaryHeader,
57    pub(crate) isl_header: IslRoutingTelecommandHeader,
58    /// Variable-length application payload.
59    pub payload: [u8],
60}
61
62/// The ISL-specific header that contains routing and action information.
63/// This structure is placed at the beginning of the `Telecommand`'s payload.
64#[repr(C, packed)]
65#[derive(FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable, Copy, Clone, Debug)]
66pub(crate) struct IslRoutingTelecommandHeader {
67    target: RawAddress,
68}
69
70/// An error that can occur when building or parsing an ISL message.
71#[derive(Debug, Copy, Clone, Eq, PartialEq, thiserror::Error)]
72pub enum IslMessageError {
73    /// An error occurred during the underlying CFE Telecommand construction.
74    #[error("CFE Telecommand error: {0}")]
75    Cfe(#[from] TelecommandError),
76    /// A received packet was expected to be an ISL message but its payload was
77    /// too small to contain a valid `IslHeader`.
78    #[error("ISL message payload is too small to contain a valid ISL header")]
79    PayloadTooSmall,
80    /// The payload exceeds the maximum allowed size.
81    #[error("ISL message payload size {provided} exceeds maximum allowed {max}")]
82    PayloadTooLarge {
83        /// Maximum allowed payload size.
84        max: usize,
85        /// Actual payload size provided.
86        provided: usize,
87    },
88}
89
90impl IslRoutingTelecommandHeader {
91    pub fn target(&self) -> Address {
92        self.target.parse()
93    }
94
95    pub fn set_target(&mut self, target: Address) {
96        self.target = RawAddress::from(target);
97    }
98}
99
100#[bon]
101impl IslRoutingTelecommand {
102    /// A high-level builder for creating a complete, routable ISL message.
103    #[builder]
104    pub fn new<'a>(
105        buffer: &'a mut [u8],
106        apid: Apid,
107        function_code: u8,
108        target: Address,
109        payload_len: usize,
110    ) -> Result<&'a mut Self, IslMessageError> {
111        let tc = Telecommand::builder()
112            .buffer(buffer)
113            .apid(apid)
114            .sequence_count(SequenceCount::new())
115            .function_code(function_code)
116            .payload_len(size_of::<IslRoutingTelecommandHeader>() + payload_len)
117            .build()
118            .map_err(IslMessageError::Cfe)?;
119
120        let buffer = tc.as_mut_bytes();
121        let provided_len = buffer.len();
122        let isl_tc = Self::mut_from_bytes_with_elems(buffer, payload_len).map_err(|_| {
123            TelecommandError::BufferTooSmall {
124                required_len: size_of::<PrimaryHeader>()
125                    + size_of::<TelecommandSecondaryHeader>()
126                    + size_of::<IslRoutingTelecommandHeader>()
127                    + payload_len,
128                provided_len,
129            }
130        })?;
131
132        isl_tc.isl_header.set_target(target);
133
134        isl_tc.set_cfe_checksum();
135
136        Ok(isl_tc)
137    }
138}
139
140impl IslRoutingTelecommand {
141    /// Calculates and sets the 8-bit cFE checksum for this command packet.
142    ///
143    /// The algorithm is a byte-wise XOR sum of the entire packet,
144    /// with the checksum field itself treated as zero during calculation.
145    pub fn set_cfe_checksum(&mut self) {
146        self.secondary.set_checksum(0);
147        self.secondary.set_checksum(checksum_u8(self.as_bytes()));
148    }
149
150    /// Validates the 8-bit cFE checksum.
151    ///
152    /// Returns `true` if the checksum is valid, `false` otherwise.
153    pub fn validate_cfe_checksum(&self) -> bool {
154        validate_checksum_u8(self.as_bytes())
155    }
156
157    /// Safely parses a generic `Telecommand` as an `IslTelecommand`.
158    pub fn from_telecommand(tc: &Telecommand) -> Result<&Self, IslMessageError> {
159        if tc.payload().len() < size_of::<IslRoutingTelecommandHeader>() {
160            return Err(IslMessageError::PayloadTooSmall);
161        }
162        // The layouts are compatible, so we can safely cast.
163        Ok(Self::ref_from_bytes(tc.as_bytes()).unwrap())
164    }
165
166    /// Returns a reference to the underlying `Telecommand` view.
167    pub fn as_telecommand(&self) -> &Telecommand {
168        Telecommand::ref_from_bytes(self.as_bytes()).unwrap()
169    }
170
171    /// Returns a slice containing the application-specific data.
172    pub fn payload(&self) -> &[u8] {
173        &self.payload
174    }
175
176    /// Returns a mutable slice of the application payload.
177    pub fn payload_mut(&mut self) -> &mut [u8] {
178        &mut self.payload
179    }
180
181    /// Parses a byte slice as an ISL routing telecommand.
182    /// Returns the target address from the ISL routing header.
183    pub fn target(&self) -> Address {
184        self.isl_header.target()
185    }
186
187    /// Returns the APID from the SPP primary header.
188    pub fn apid(&self) -> Apid {
189        self.primary.apid()
190    }
191
192    /// Parses a byte slice as an ISL routing telecommand.
193    pub fn parse<'a>(bytes: &'a [u8]) -> Result<&'a IslRoutingTelecommand, IslMessageError> {
194        let tc = Telecommand::parse(bytes).map_err(IslMessageError::Cfe)?;
195        Self::from_telecommand(tc)
196    }
197}