Skip to main content

leodos_protocols/datalink/framing/sdlp/
tm.rs

1//! Telemetry Space Data Link Protocol (TM-SDLP)
2//!
3//! Spec: https://ccsds.org/Pubs/132x0b3.pdf
4//!
5//! The Telemetry Transfer Frame is the "envelope" used to package `SpacePacket`s for
6//! downlink (sending data from a satellite to the ground).
7
8use bon::bon;
9use zerocopy::FromBytes;
10use zerocopy::Immutable;
11use zerocopy::IntoBytes;
12use zerocopy::KnownLayout;
13use zerocopy::Unaligned;
14use zerocopy::byteorder::network_endian::U16;
15
16use crate::coding::randomizer::Randomizer;
17use crate::utils::get_bits_u16;
18use crate::utils::set_bits_u16;
19
20/// A zero-copy view over a CCSDS Telemetry (TM) Transfer Frame in a raw byte buffer.
21///
22/// This struct represents the "envelope" used to send one or more `SpacePacket`s
23/// from a spacecraft to the ground (the downlink). In addition to routing and sequencing,
24/// it includes a crucial "First Header Pointer" to locate the first `SpacePacket`
25/// within its data field.
26///
27/// On the ground, this view is typically created via [`TelemetryTransferFrame::parse()`],
28/// which also handles de-randomization of the raw radio data.
29///
30/// # Layout
31///
32/// A TM Transfer Frame consists of a 6-byte header followed by a data field.
33///
34/// ```text
35/// +------------------------------------+----------+
36/// | Field Name                         | Size     |
37/// +------------------------------------+----------+
38/// + -- Transfer Frame Header (6 bytes) |          |
39/// |                                    |          |
40/// | Transfer Frame Version             | 2 bits   |
41/// | Spacecraft ID (SCID)               | 10 bits  |
42/// | Virtual Channel ID (VCID)          | 3 bits   |
43/// | OCF Flag                           | 1 bit    |
44/// | Master Channel Frame Count         | 8 bits   |
45/// | Virtual Channel Frame Count        | 8 bits   |
46/// | Data Field Status                  | 16 bits  |
47/// |   ... First Header Pointer         | 11 bits  |
48/// |                                    |          |
49/// + -- Data Field -------------------- | Variable |
50/// |                                    |          |
51/// | Contains idle data and/or          |          |
52/// | one or more Space Packets          |          |
53/// +------------------------------------+----------+
54/// | (Optional) OCF                     | 4 bytes  |
55/// +------------------------------------+----------+
56/// | (Optional) Frame Error Control     | 2 bytes  |
57/// +------------------------------------+----------+
58/// ```
59#[repr(C, packed)]
60#[derive(IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable)]
61pub struct TelemetryTransferFrame {
62    header: TelemetryTransferFrameHeader,
63    data_field: [u8],
64}
65
66/// The 6-byte header of a Telemetry TransferFrame.
67#[repr(C)]
68#[derive(FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable, Debug, Copy, Clone)]
69pub struct TelemetryTransferFrameHeader {
70    /// Contains the 2-bit Version, 10-bit SCID, 3-bit VCID, and 1-bit OCF Flag.
71    version_scid_vcid_and_ocf: U16,
72    /// Contains the 8-bit Master Channel Frame Count for all frames.
73    mc_frame_count: u8,
74    /// Contains the 8-bit Virtual Channel Frame Count for this VCID.
75    vc_frame_count: u8,
76    /// Contains status flags and the 11-bit First Header Pointer to the first Space Packet.
77    data_field_status: U16,
78}
79
80/// Bitmasks for TM Transfer Frame header fields.
81#[rustfmt::skip]
82pub mod bitmask {
83    /// Bitmask for the 2-bit version field.
84    pub const VERSION_MASK: u16 =              0b_1100_0000_0000_0000;
85    /// Bitmask for the 10-bit Spacecraft ID field.
86    pub const SCID_MASK: u16 =                 0b_0011_1111_1111_0000;
87    /// Bitmask for the 3-bit Virtual Channel ID field.
88    pub const VCID_MASK: u16 =                 0b_0000_0000_0000_1110;
89    /// Bitmask for the 1-bit Operational Control Field flag.
90    pub const OCF_FLAG_MASK: u16 =             0b_0000_0000_0000_0001;
91
92    /// Bitmask for the 11-bit First Header Pointer field.
93    pub const FIRST_HEADER_POINTER_MASK: u16 = 0b_0000_0111_1111_1111;
94}
95
96use bitmask::*;
97use crate::ids::{Scid, Vcid};
98
99/// An error that can occur during Telemetry frame construction.
100#[derive(Debug, Copy, Clone, Eq, PartialEq)]
101pub enum BuildError {
102    /// The provided Spacecraft ID is outside the valid 10-bit range (0-1023).
103    InvalidScid(Scid),
104    /// The provided Virtual Channel ID is outside the valid 3-bit range (0-7).
105    InvalidVcid(Vcid),
106    /// The provided buffer is too small to hold the requested frame.
107    BufferTooSmall {
108        /// Minimum number of bytes needed.
109        required_len: usize,
110        /// Actual buffer size provided.
111        provided_len: usize,
112    },
113}
114
115/// An error that can occur during Telemetry frame parsing.
116#[derive(Debug, Copy, Clone, Eq, PartialEq)]
117pub enum ParseError {
118    /// The provided buffer is not large enough to hold the de-randomized frame,
119    /// or the input and output buffers have different lengths.
120    InvalidBufferLength,
121    /// The buffer is too small to hold a valid TM frame header.
122    TooShortForHeader,
123}
124
125#[bon]
126impl TelemetryTransferFrame {
127    /// The size of the Telemetry Transfer Frame header in bytes.
128    pub const HEADER_SIZE: usize = 6;
129
130    /// Parses a raw, possibly randomized, byte slice into a zero-copy Telemetry Transfer Frame view.
131    ///
132    /// The incoming `bytes` slice is de-randomized into the `output_buffer`. The
133    /// The returned `&TelemetryTransferFrame` is a view over this `output_buffer`.
134    pub fn parse<'a>(
135        bytes: &[u8],
136        output_buffer: &'a mut [u8],
137        randomizer: &impl Randomizer,
138    ) -> Result<&'a TelemetryTransferFrame, ParseError> {
139        if bytes.len() < Self::HEADER_SIZE {
140            return Err(ParseError::TooShortForHeader);
141        }
142        if output_buffer.len() < bytes.len() {
143            return Err(ParseError::InvalidBufferLength);
144        }
145        let frame_buf = &mut output_buffer[..bytes.len()];
146        frame_buf.copy_from_slice(bytes);
147
148        randomizer.apply(frame_buf);
149
150        TelemetryTransferFrame::ref_from_bytes(frame_buf)
151            .map_err(|_| ParseError::InvalidBufferLength)
152    }
153
154    /// Parses a transfer frame without applying derandomization.
155    ///
156    /// Use this when the coding pipeline has already handled
157    /// derandomization.
158    pub fn parse_raw(bytes: &[u8]) -> Result<&TelemetryTransferFrame, ParseError> {
159        if bytes.len() < Self::HEADER_SIZE {
160            return Err(ParseError::TooShortForHeader);
161        }
162        TelemetryTransferFrame::ref_from_bytes(bytes)
163            .map_err(|_| ParseError::InvalidBufferLength)
164    }
165
166    /// Returns a reference to the frame's header.
167    pub fn header(&self) -> &TelemetryTransferFrameHeader {
168        &self.header
169    }
170
171    /// Returns a mutable reference to the frame's data field.
172    pub fn data_field_mut(&mut self) -> &mut [u8] {
173        &mut self.data_field
174    }
175
176    /// Returns a reference to the frame's data field.
177    ///
178    /// This slice typically contains one or more `SpacePacket`s that can now be
179    /// parsed individually.
180    pub fn data_field(&self) -> &[u8] {
181        &self.data_field
182    }
183
184    /// Constructs a new TM Transfer Frame in the provided buffer.
185    #[builder]
186    pub fn new(
187        buffer: &mut [u8],
188        version: u8,
189        scid: Scid,
190        vcid: Vcid,
191        mc_frame_count: u8,
192        vc_frame_count: u8,
193        first_header_pointer: u16,
194    ) -> Result<&mut Self, BuildError> {
195        if buffer.len() < Self::HEADER_SIZE {
196            return Err(BuildError::BufferTooSmall {
197                required_len: Self::HEADER_SIZE,
198                provided_len: buffer.len(),
199            });
200        }
201        if scid.num_bits() > 10 {
202            return Err(BuildError::InvalidScid(scid));
203        }
204        if vcid.num_bits() > 3 {
205            return Err(BuildError::InvalidVcid(vcid));
206        }
207
208        let provided_len = buffer.len();
209        if provided_len < Self::HEADER_SIZE {
210            return Err(BuildError::BufferTooSmall {
211                required_len: Self::HEADER_SIZE,
212                provided_len,
213            });
214        }
215        let data_field_len = provided_len - Self::HEADER_SIZE;
216        let (frame, _) = TelemetryTransferFrame::mut_from_prefix_with_elems(buffer, data_field_len)
217            .map_err(|_| BuildError::BufferTooSmall {
218            required_len: Self::HEADER_SIZE,
219            provided_len,
220        })?;
221
222        frame.header.set_version(version);
223        frame.header.set_scid(scid);
224        frame.header.set_vcid(vcid);
225        frame.header.set_mc_frame_count(mc_frame_count);
226        frame.header.set_vc_frame_count(vc_frame_count);
227        frame.header.set_first_header_pointer(first_header_pointer);
228
229        Ok(frame)
230    }
231}
232
233impl TelemetryTransferFrameHeader {
234    /// Returns the Transfer Frame Version Number.
235    pub fn version(&self) -> u8 {
236        get_bits_u16(self.version_scid_vcid_and_ocf, VERSION_MASK) as u8
237    }
238    /// Sets the Transfer Frame Version Number.
239    pub fn set_version(&mut self, version: u8) {
240        set_bits_u16(
241            &mut self.version_scid_vcid_and_ocf,
242            VERSION_MASK,
243            version as u16,
244        );
245    }
246
247    /// Returns the Spacecraft ID (SCID).
248    pub fn scid(&self) -> Scid {
249        Scid::new(get_bits_u16(self.version_scid_vcid_and_ocf, SCID_MASK) as u32)
250    }
251    /// Sets the Spacecraft ID (SCID).
252    pub fn set_scid(&mut self, scid: Scid) {
253        set_bits_u16(&mut self.version_scid_vcid_and_ocf, SCID_MASK, scid.get() as u16);
254    }
255
256    /// Returns the Virtual Channel ID (VCID).
257    pub fn vcid(&self) -> Vcid {
258        Vcid::new(get_bits_u16(self.version_scid_vcid_and_ocf, VCID_MASK) as u32)
259    }
260    /// Sets the Virtual Channel ID (VCID).
261    pub fn set_vcid(&mut self, vcid: Vcid) {
262        set_bits_u16(&mut self.version_scid_vcid_and_ocf, VCID_MASK, vcid.get() as u16);
263    }
264
265    /// Returns the Master Channel Frame Count.
266    pub fn mc_frame_count(&self) -> u8 {
267        self.mc_frame_count
268    }
269    /// Sets the Master Channel Frame Count.
270    pub fn set_mc_frame_count(&mut self, count: u8) {
271        self.mc_frame_count = count;
272    }
273
274    /// Returns the Virtual Channel Frame Count.
275    pub fn vc_frame_count(&self) -> u8 {
276        self.vc_frame_count
277    }
278    /// Sets the Virtual Channel Frame Count.
279    pub fn set_vc_frame_count(&mut self, count: u8) {
280        self.vc_frame_count = count;
281    }
282
283    /// Returns the First Header Pointer to the first Space Packet in the data field.
284    pub fn first_header_pointer(&self) -> u16 {
285        get_bits_u16(self.data_field_status, FIRST_HEADER_POINTER_MASK)
286    }
287    /// Sets the First Header Pointer value.
288    pub fn set_first_header_pointer(&mut self, fhp: u16) {
289        set_bits_u16(&mut self.data_field_status, FIRST_HEADER_POINTER_MASK, fhp);
290    }
291}
292
293// ── FrameWrite / FrameRead implementations ──
294
295use super::super::{FrameRead, FrameWrite, PushError};
296
297/// Configuration for building TM transfer frames.
298#[derive(Debug, Clone)]
299pub struct TmFrameWriterConfig {
300    /// Spacecraft ID.
301    pub scid: Scid,
302    /// Virtual Channel ID.
303    pub vcid: Vcid,
304    /// Maximum data field length in bytes.
305    pub max_data_field_len: usize,
306}
307
308/// Accumulates packets into TM transfer frames.
309///
310/// Owns its frame buffer internally (sized by `BUF`). Packets
311/// are pushed directly into the buffer at the correct offset.
312/// [`finish()`](FrameWrite::finish) stamps the header and
313/// returns a borrow of the completed frame.
314pub struct TmFrameWriter<const BUF: usize> {
315    config: TmFrameWriterConfig,
316    mc_frame_count: u8,
317    vc_frame_count: u8,
318    data_len: usize,
319    buf: [u8; BUF],
320}
321
322impl<const BUF: usize> TmFrameWriter<BUF> {
323    /// Creates a new TM frame writer.
324    pub fn new(config: TmFrameWriterConfig) -> Self {
325        Self {
326            config,
327            mc_frame_count: 0,
328            vc_frame_count: 0,
329            data_len: 0,
330            buf: [0u8; BUF],
331        }
332    }
333}
334
335impl<const BUF: usize> TmFrameWriter<BUF> {
336    fn remaining(&self) -> usize {
337        self.config
338            .max_data_field_len
339            .saturating_sub(self.data_len)
340    }
341}
342
343impl<const BUF: usize> FrameWrite for TmFrameWriter<BUF> {
344    type Error = BuildError;
345
346    fn is_empty(&self) -> bool {
347        self.data_len == 0
348    }
349
350    fn push(&mut self, data: &[u8]) -> Result<(), PushError> {
351        if data.len() > self.config.max_data_field_len {
352            return Err(PushError::TooLarge);
353        }
354        if data.len() > self.remaining() {
355            return Err(PushError::Full);
356        }
357        let off =
358            TelemetryTransferFrame::HEADER_SIZE + self.data_len;
359        self.buf[off..off + data.len()].copy_from_slice(data);
360        self.data_len += data.len();
361        Ok(())
362    }
363
364    fn finish(&mut self) -> Result<&[u8], BuildError> {
365        let total =
366            TelemetryTransferFrame::HEADER_SIZE + self.data_len;
367        let mc = self.mc_frame_count;
368        let vc = self.vc_frame_count;
369        self.mc_frame_count =
370            self.mc_frame_count.wrapping_add(1);
371        self.vc_frame_count =
372            self.vc_frame_count.wrapping_add(1);
373
374        TelemetryTransferFrame::builder()
375            .buffer(&mut self.buf[..total])
376            .version(0)
377            .scid(self.config.scid)
378            .vcid(self.config.vcid)
379            .mc_frame_count(mc)
380            .vc_frame_count(vc)
381            .first_header_pointer(0)
382            .build()?;
383
384        self.data_len = 0;
385        Ok(&self.buf[..total])
386    }
387}
388
389/// Extracts packets from a received TM transfer frame.
390///
391/// Owns its frame buffer internally (sized by `BUF`). The
392/// coding layer writes into
393/// [`buffer_mut()`](FrameRead::buffer_mut),
394/// [`feed()`](FrameRead::feed) validates the header, and
395/// [`next()`](FrameRead::next) returns zero-copy sub-slices.
396pub struct TmFrameReader<const BUF: usize> {
397    buf: [u8; BUF],
398    data_start: usize,
399    data_end: usize,
400}
401
402impl<const BUF: usize> TmFrameReader<BUF> {
403    /// Creates a new TM frame reader.
404    pub fn new() -> Self {
405        Self {
406            buf: [0u8; BUF],
407            data_start: 0,
408            data_end: 0,
409        }
410    }
411}
412
413impl<const BUF: usize> FrameRead for TmFrameReader<BUF> {
414    type Error = ParseError;
415
416    fn buffer_mut(&mut self) -> &mut [u8] {
417        &mut self.buf
418    }
419
420    fn feed(&mut self, len: usize) -> Result<(), ParseError> {
421        let parsed =
422            TelemetryTransferFrame::parse_raw(&self.buf[..len])?;
423        let data = parsed.data_field();
424        self.data_start =
425            TelemetryTransferFrame::HEADER_SIZE;
426        self.data_end = self.data_start + data.len();
427        Ok(())
428    }
429
430    fn data_field(&self) -> &[u8] {
431        &self.buf[self.data_start..self.data_end]
432    }
433}