Skip to main content

leodos_protocols/network/cfe/
tm.rs

1//! CFE-specific telemetry packet definitions and builder.
2
3use crate::network::spp::Apid;
4use crate::network::spp::PacketType;
5use crate::network::spp::PrimaryHeader;
6use crate::network::spp::SecondaryHeaderFlag;
7use crate::network::spp::SequenceCount;
8use crate::network::spp::SequenceFlag;
9use crate::network::spp::SpacePacket;
10
11use bon::bon;
12use core::mem::size_of;
13use core::ops::Deref;
14use core::ops::DerefMut;
15use zerocopy::FromBytes;
16use zerocopy::Immutable;
17use zerocopy::IntoBytes;
18use zerocopy::KnownLayout;
19use zerocopy::Unaligned;
20use zerocopy::network_endian::U64;
21
22/// A zero-copy view over a complete CFE telemetry packet (headers + payload).
23/// This is the primary struct you will use to represent telemetry.
24/// ```text
25/// +------------------------------------+---------+
26/// | Field Name                         | Size    |
27/// +------------------------------------+---------+
28/// + -- Primary Header (6 bytes) ------ | ------- |
29/// |                                    |         |
30/// | - Packet Type is always Telemetry  |         |
31/// | - Sec. Hdr. Flag is always Present |         |
32/// |                                    |         |
33/// + -- cFE Secondary Header (2 bytes)  | ------- |
34/// |                                    |         |
35/// | Time                               | 6 bytes |
36/// | Spare                              | 4 bytes |
37/// |                                    |         |
38/// + -- User Data Field (Variable) ---- | ------- |
39/// |                                    |         |
40/// | Payload                            | 1-65534 |
41/// |                                    | bytes   |
42/// +------------------------------------+---------+
43/// ```
44#[repr(C)]
45#[derive(Debug, FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
46pub struct Telemetry {
47    /// CCSDS SPP primary header.
48    pub primary: PrimaryHeader,
49    /// CFE telemetry secondary header containing timestamp.
50    pub secondary: TelemetrySecondaryHeader,
51    /// Variable-length telemetry payload.
52    pub payload: [u8],
53}
54
55/// The CFE telemetry secondary header (6-byte time + 4-byte padding).
56#[repr(C)]
57#[derive(IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable, Default, Copy, Clone, Debug)]
58pub struct TelemetrySecondaryHeader {
59    time: U64,
60    spare: [u8; 2],
61}
62
63/// Bitmask constants for the telemetry secondary header fields.
64pub mod bitmask {
65    /// Bitmask for the time field in the telemetry secondary header.
66    pub const TIME_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
67}
68
69use bitmask::*;
70
71/// An error that can occur when building a CFE telemetry packet.
72#[derive(Debug, Copy, Clone, Eq, PartialEq)]
73pub enum TelemetryError {
74    /// The provided buffer is too small to hold the packet.
75    BufferTooSmall {
76        /// Minimum number of bytes needed.
77        required: usize,
78        /// Actual buffer size provided.
79        provided: usize,
80    },
81    /// The time value exceeds the 6-byte CDS range.
82    InvalidTimeValue,
83    /// The underlying SPP builder returned an error.
84    SpacePacketError(crate::network::spp::BuildError),
85    /// The secondary header flag is not set to Present.
86    MissingSecondaryHeader,
87    /// The packet data field does not match the expected layout.
88    PayloadMismatch,
89    /// The packet type does not match (e.g. telecommand instead of telemetry).
90    TypeMismatch,
91}
92
93impl Deref for Telemetry {
94    type Target = SpacePacket;
95
96    fn deref(&self) -> &Self::Target {
97        SpacePacket::ref_from_bytes(self.as_bytes())
98            .expect("Telemetry should always be a valid SpacePacket")
99    }
100}
101
102impl DerefMut for Telemetry {
103    fn deref_mut(&mut self) -> &mut Self::Target {
104        SpacePacket::mut_from_bytes(self.as_mut_bytes())
105            .expect("Telemetry should always be a valid SpacePacket")
106    }
107}
108
109impl<'a> TryFrom<&'a SpacePacket> for &'a Telemetry {
110    type Error = TelemetryError;
111
112    fn try_from(sp: &'a SpacePacket) -> Result<Self, Self::Error> {
113        if sp.secondary_header_flag() != SecondaryHeaderFlag::Present {
114            return Err(TelemetryError::MissingSecondaryHeader);
115        }
116
117        let bytes = sp.as_bytes();
118
119        match sp.packet_type() {
120            PacketType::Telecommand => Err(TelemetryError::TypeMismatch),
121            PacketType::Telemetry => {
122                Telemetry::ref_from_bytes(bytes).map_err(|_| TelemetryError::PayloadMismatch)
123            }
124        }
125    }
126}
127
128#[bon]
129impl Telemetry {
130    /// Creates a new telemetry packet view over the provided buffer.
131    #[builder]
132    pub fn new<'a>(
133        buffer: &'a mut [u8],
134        apid: Apid,
135        sequence_count: SequenceCount,
136        time: u64,
137        payload_len: usize,
138    ) -> Result<&'a mut Telemetry, TelemetryError> {
139        let sp = SpacePacket::builder()
140            .buffer(buffer)
141            .apid(apid)
142            .sequence_count(sequence_count)
143            .packet_type(PacketType::Telemetry)
144            .secondary_header(SecondaryHeaderFlag::Present)
145            .sequence_flag(SequenceFlag::Unsegmented)
146            .data_len(size_of::<TelemetrySecondaryHeader>() + payload_len)
147            .build()
148            .map_err(TelemetryError::SpacePacketError)?;
149
150        let buffer = sp.as_mut_bytes();
151        let provided_len = buffer.len();
152        let required_len = payload_len;
153
154        let tm = Telemetry::mut_from_bytes_with_elems(buffer, required_len).map_err(|_| {
155            TelemetryError::BufferTooSmall {
156                required: required_len,
157                provided: provided_len,
158            }
159        })?;
160
161        tm.set_time(time)?;
162
163        Ok(tm)
164    }
165
166    /// 6-byte CCSDS Day Segmented (CDS) time value.
167    pub fn time(&self) -> u64 {
168        self.secondary.time.get() & TIME_MASK
169    }
170    /// Sets the 6-byte time value, returning an error if out of range.
171    pub fn set_time(&mut self, time: u64) -> Result<(), TelemetryError> {
172        if time & !TIME_MASK != 0 {
173            return Err(TelemetryError::InvalidTimeValue);
174        }
175        self.secondary.time.set(time);
176        Ok(())
177    }
178
179    /// Returns the telemetry payload bytes.
180    pub fn payload(&self) -> &[u8] {
181        self.payload.as_bytes()
182    }
183    /// Returns a mutable reference to the telemetry payload bytes.
184    pub fn payload_mut(&mut self) -> &mut [u8] {
185        self.payload.as_mut_bytes()
186    }
187
188    /// Parses a byte slice as a CFE telemetry packet.
189    pub fn parse<'a>(bytes: &'a [u8]) -> Result<&'a Telemetry, TelemetryError> {
190        let sp = SpacePacket::ref_from_bytes(bytes).map_err(|_| TelemetryError::PayloadMismatch)?;
191        <&Telemetry>::try_from(sp)
192    }
193}