Skip to main content

leodos_protocols/datalink/framing/sdlp/
tc.rs

1//! Telecommand Space Data Link Protocol (TC-SDLP)
2//!
3//! Spec: https://ccsds.org/Pubs/232x0b4e1c1.pdf
4//!
5//! The Telecommand Transfer Frame is the "envelope" used to package `SpacePacket`s for
6//! uplink (sending commands from the ground to a satellite).
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 super::super::{FrameRead, FrameWrite, PushError};
17use crate::utils::get_bits_u16;
18use crate::utils::set_bits_u16;
19
20/// A zero-copy view over a CCSDS Telecommand (TC) Transfer Frame in a raw byte buffer.
21///
22/// This struct represents the "envelope" used to send one or more `SpacePacket`s
23/// from the ground to a spacecraft (the uplink). It provides the necessary routing
24/// (SCID, VCID) and sequencing for the radio link.
25///
26/// It is typically constructed via the ergonomic [`TelecommandTransferFrame::builder()`].
27///
28/// # Layout
29///
30/// A TC Transfer Frame consists of a 5-byte header followed by a data field.
31///
32/// ```text
33/// +------------------------------------+---------------------+
34/// | Field Name                         | Size                |
35/// +------------------------------------+---------------------+
36/// + -- Transfer Frame Header (5 bytes) |                     |
37/// |                                    |                     |
38/// | Transfer Frame Version             | 2 bits              |
39/// | Bypass Flag                        | 1 bit               |
40/// | Control Command Flag               | 1 bit               |
41/// | Reserved                           | 2 bits              |
42/// | Spacecraft ID (SCID)               | 10 bits             |
43/// | Virtual Channel ID (VCID)          | 6 bits              |
44/// | Frame Length                       | 10 bits             |
45/// | Frame Sequence Number              | 8 bits              |
46/// |                                    |                     |
47/// + -- Data Field -------------------- | 1 - 1019 bytes      |
48/// |                                    |                     |
49/// | Contains one or more Space Packets |                     |
50/// +------------------------------------+---------------------+
51/// ```
52#[repr(C, packed)]
53#[derive(FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
54pub struct TelecommandTransferFrame {
55    header: TelecommandTransferFrameHeader,
56    data_field: [u8],
57}
58
59/// The 5-byte header of a Telecommand Transfer Frame.
60#[repr(C)]
61#[derive(FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable, Debug, Copy, Clone)]
62pub struct TelecommandTransferFrameHeader {
63    /// Contains the 2-bit Version, 1-bit Bypass Flag, 1-bit Control Flag, and 10-bit SCID.
64    id_and_scid: U16,
65    /// Contains the 6-bit VCID and 10-bit Frame Length (total length - 1).
66    vcid_and_length: U16,
67    /// Contains the 8-bit Frame Sequence Number for this Virtual Channel.
68    sequence_num: u8,
69}
70
71/// Bitmasks for TC Transfer Frame header fields.
72#[rustfmt::skip]
73pub mod bitmask {
74    /// Bitmask for the 2-bit version field.
75    pub const VERSION_MASK: u16 =      0b_1100_0000_0000_0000;
76    /// Bitmask for the 1-bit bypass flag.
77    pub const BYPASS_FLAG_MASK: u16 =  0b_0010_0000_0000_0000;
78    /// Bitmask for the 1-bit control command flag.
79    pub const CONTROL_FLAG_MASK: u16 = 0b_0001_0000_0000_0000;
80    /// Bitmask for the 2-bit reserved field.
81    pub const _RESERVED_MASK: u16 =    0b_0000_1100_0000_0000;
82    /// Bitmask for the 10-bit Spacecraft ID field.
83    pub const SCID_MASK: u16 =         0b_0000_0011_1111_1111;
84
85    /// Bitmask for the 6-bit Virtual Channel ID field.
86    pub const VCID_MASK: u16 =         0b_1111_1100_0000_0000;
87    /// Bitmask for the 10-bit frame length field.
88    pub const FRAME_LEN_MASK: u16 =    0b_0000_0011_1111_1111;
89}
90
91use bitmask::*;
92use crate::ids::{Scid, Vcid};
93
94/// An error that can occur during Telecommand Transfer Frame construction.
95#[derive(Debug, Copy, Clone, Eq, PartialEq)]
96pub enum BuildError {
97    /// The provided Spacecraft ID is outside the valid 10-bit range (0-1023).
98    InvalidScid(Scid),
99    /// The provided Virtual Channel ID is outside the valid 6-bit range (0-63).
100    InvalidVcid(Vcid),
101    /// The provided data length exceeds the maximum of 1019 bytes.
102    DataTooLong(usize),
103    /// The provided buffer is too small to hold the requested frame.
104    BufferTooSmall {
105        /// Minimum number of bytes needed for the frame.
106        required: usize,
107        /// Actual buffer size provided.
108        provided: usize,
109    },
110}
111
112/// An error that can occur during Telecommand Transfer Frame parsing.
113#[derive(Debug, Copy, Clone, Eq, PartialEq)]
114pub enum ParseError {
115    /// The provided slice is shorter than the 5-byte header.
116    TooShortForHeader {
117        /// Actual number of bytes provided.
118        actual: usize,
119    },
120    /// The header's length field implies a frame larger than the provided buffer.
121    IncompleteFrame {
122        /// Total frame length declared in the header.
123        header_len: usize,
124        /// Actual buffer length provided.
125        buffer_len: usize,
126    },
127}
128
129/// The Bypass Flag, controlling the type of frame acceptance checks performed
130/// by the receiving spacecraft.
131#[derive(Debug, Copy, Clone, Eq, PartialEq)]
132#[repr(u8)]
133pub enum BypassFlag {
134    /// The normal acceptance checks shall be performed (Type-A).
135    TypeA = 0,
136    /// The acceptance checks are bypassed (Type-B).
137    TypeB = 1,
138}
139
140/// The Control Command Flag, indicating whether the frame contains user data or
141/// control information for the receiver.
142#[derive(Debug, Copy, Clone, Eq, PartialEq)]
143#[repr(u8)]
144pub enum ControlFlag {
145    /// The frame contains user data (e.g., a `SpacePacket`).
146    TypeD = 0,
147    /// The frame contains control information (Type-C).
148    TypeC = 1,
149}
150
151#[bon]
152impl TelecommandTransferFrame {
153    /// The size of the Telecommand Transfer Frame header in bytes.
154    pub const HEADER_SIZE: usize = 5;
155    /// The maximum allowed size of the data field in bytes.
156    pub const MAX_DATA_FIELD_LEN: usize = 1019;
157
158    /// Parses a raw byte slice into a zero-copy Telecommand Transfer Frame view.
159    pub fn parse(bytes: &[u8]) -> Result<&Self, ParseError> {
160        if bytes.len() < Self::HEADER_SIZE {
161            return Err(ParseError::TooShortForHeader {
162                actual: bytes.len(),
163            });
164        }
165        let (header, _) = TelecommandTransferFrameHeader::ref_from_prefix(bytes).unwrap();
166        let specified_len = header.frame_len();
167
168        if specified_len > bytes.len() {
169            return Err(ParseError::IncompleteFrame {
170                header_len: specified_len,
171                buffer_len: bytes.len(),
172            });
173        }
174
175        Ok(TelecommandTransferFrame::ref_from_bytes(&bytes[..specified_len]).unwrap())
176    }
177
178    /// Returns a reference to the frame's header.
179    pub fn header(&self) -> &TelecommandTransferFrameHeader {
180        &self.header
181    }
182
183    /// Returns a mutable reference to the frame's data field.
184    pub fn data_field_mut(&mut self) -> &mut [u8] {
185        &mut self.data_field
186    }
187
188    /// Returns a reference to the frame's data field.
189    pub fn data_field(&self) -> &[u8] {
190        &self.data_field
191    }
192
193    /// Returns the total length of the frame (header + data field) in bytes.
194    pub fn frame_len(&self) -> usize {
195        Self::HEADER_SIZE + self.data_field.len()
196    }
197
198    /// Constructs a new TC Transfer Frame in the provided buffer.
199    #[builder]
200    pub fn new(
201        buffer: &mut [u8],
202        scid: Scid,
203        vcid: Vcid,
204        bypass_flag: BypassFlag,
205        control_flag: ControlFlag,
206        seq: u8,
207        data_field_len: usize,
208    ) -> Result<&mut Self, BuildError> {
209        if scid.num_bits() > 10 {
210            return Err(BuildError::InvalidScid(scid));
211        }
212        if vcid.num_bits() > 6 {
213            return Err(BuildError::InvalidVcid(vcid));
214        }
215        if data_field_len > Self::MAX_DATA_FIELD_LEN {
216            return Err(BuildError::DataTooLong(data_field_len));
217        }
218
219        let total_len = Self::HEADER_SIZE + data_field_len;
220        if buffer.len() < total_len {
221            return Err(BuildError::BufferTooSmall {
222                required: total_len,
223                provided: buffer.len(),
224            });
225        }
226
227        let frame_buf = &mut buffer[..total_len];
228        let frame = TelecommandTransferFrame::mut_from_bytes(frame_buf).unwrap();
229
230        frame.header.set_scid(scid);
231        frame.header.set_vcid(vcid);
232        frame.header.set_bypass_flag(bypass_flag);
233        frame.header.set_control_flag(control_flag);
234        frame.header.set_sequence_num(seq);
235        frame.header.set_frame_len(total_len);
236
237        Ok(frame)
238    }
239}
240
241impl TelecommandTransferFrameHeader {
242    /// Returns the Spacecraft ID (SCID).
243    pub fn scid(&self) -> Scid {
244        Scid::new(get_bits_u16(self.id_and_scid, SCID_MASK) as u32)
245    }
246    /// Sets the Spacecraft ID (SCID).
247    pub fn set_scid(&mut self, scid: Scid) {
248        set_bits_u16(&mut self.id_and_scid, SCID_MASK, scid.get() as u16);
249    }
250
251    /// Returns the Virtual Channel ID (VCID).
252    pub fn vcid(&self) -> Vcid {
253        Vcid::new(get_bits_u16(self.vcid_and_length, VCID_MASK) as u32)
254    }
255    /// Sets the Virtual Channel ID (VCID).
256    pub fn set_vcid(&mut self, vcid: Vcid) {
257        set_bits_u16(&mut self.vcid_and_length, VCID_MASK, vcid.get() as u16);
258    }
259
260    /// Returns the total frame length in bytes as specified by the header.
261    pub fn frame_len(&self) -> usize {
262        get_bits_u16(self.vcid_and_length, FRAME_LEN_MASK) as usize + 1
263    }
264    /// Sets the total frame length in bytes as stored in the header.
265    pub fn set_frame_len(&mut self, length: usize) {
266        let len_field = (length - 1) as u16;
267        set_bits_u16(&mut self.vcid_and_length, FRAME_LEN_MASK, len_field);
268    }
269
270    /// Returns the Frame Sequence Number.
271    pub fn sequence_num(&self) -> u8 {
272        self.sequence_num
273    }
274    /// Sets the Frame Sequence Number.
275    pub fn set_sequence_num(&mut self, seq: u8) {
276        self.sequence_num = seq;
277    }
278
279    /// Returns the Bypass Flag.
280    pub fn bypass_flag(&self) -> BypassFlag {
281        if get_bits_u16(self.id_and_scid, BYPASS_FLAG_MASK) == 1 {
282            BypassFlag::TypeB
283        } else {
284            BypassFlag::TypeA
285        }
286    }
287    /// Sets the Bypass Flag.
288    pub fn set_bypass_flag(&mut self, flag: BypassFlag) {
289        set_bits_u16(&mut self.id_and_scid, BYPASS_FLAG_MASK, flag as u16);
290    }
291
292    /// Returns the Control Command Flag.
293    pub fn control_flag(&self) -> ControlFlag {
294        if get_bits_u16(self.id_and_scid, CONTROL_FLAG_MASK) == 1 {
295            ControlFlag::TypeC
296        } else {
297            ControlFlag::TypeD
298        }
299    }
300    /// Sets the Control Command Flag.
301    pub fn set_control_flag(&mut self, flag: ControlFlag) {
302        set_bits_u16(&mut self.id_and_scid, CONTROL_FLAG_MASK, flag as u16);
303    }
304}
305
306// ── FrameWrite / FrameRead implementations ──
307
308/// Configuration for building TC transfer frames.
309#[derive(Debug, Clone)]
310pub struct TcFrameWriterConfig {
311    /// Spacecraft ID.
312    pub scid: Scid,
313    /// Virtual Channel ID.
314    pub vcid: Vcid,
315    /// Bypass flag (Type-A or Type-B).
316    pub bypass: BypassFlag,
317    /// Control flag (data or control command).
318    pub control: ControlFlag,
319    /// Maximum data field length in bytes.
320    pub max_data_field_len: usize,
321}
322
323/// Accumulates packets into TC transfer frames.
324///
325/// Owns its frame buffer internally (sized by `BUF`). Packets
326/// are pushed directly into the buffer at the correct offset.
327/// [`finish()`](FrameWrite::finish) stamps the header and
328/// returns a borrow of the completed frame.
329pub struct TcFrameWriter<const BUF: usize> {
330    config: TcFrameWriterConfig,
331    sequence: u8,
332    data_len: usize,
333    buf: [u8; BUF],
334}
335
336impl<const BUF: usize> TcFrameWriter<BUF> {
337    /// Creates a new TC frame writer.
338    pub fn new(config: TcFrameWriterConfig) -> Self {
339        Self {
340            config,
341            sequence: 0,
342            data_len: 0,
343            buf: [0u8; BUF],
344        }
345    }
346}
347
348impl<const BUF: usize> TcFrameWriter<BUF> {
349    fn remaining(&self) -> usize {
350        self.config.max_data_field_len.saturating_sub(self.data_len)
351    }
352}
353
354impl<const BUF: usize> FrameWrite for TcFrameWriter<BUF> {
355    type Error = BuildError;
356
357    fn is_empty(&self) -> bool {
358        self.data_len == 0
359    }
360
361    fn push(&mut self, data: &[u8]) -> Result<(), PushError> {
362        if data.len() > self.config.max_data_field_len {
363            return Err(PushError::TooLarge);
364        }
365        if data.len() > self.remaining() {
366            return Err(PushError::Full);
367        }
368        let off = TelecommandTransferFrame::HEADER_SIZE + self.data_len;
369        self.buf[off..off + data.len()].copy_from_slice(data);
370        self.data_len += data.len();
371        Ok(())
372    }
373
374    fn finish(&mut self) -> Result<&[u8], BuildError> {
375        let total = TelecommandTransferFrame::HEADER_SIZE + self.data_len;
376        let seq = self.sequence;
377        self.sequence = self.sequence.wrapping_add(1);
378
379        TelecommandTransferFrame::builder()
380            .buffer(&mut self.buf[..total])
381            .scid(self.config.scid)
382            .vcid(self.config.vcid)
383            .bypass_flag(self.config.bypass)
384            .control_flag(self.config.control)
385            .seq(seq)
386            .data_field_len(self.data_len)
387            .build()?;
388
389        self.data_len = 0;
390        Ok(&self.buf[..total])
391    }
392}
393
394/// Extracts packets from a received TC transfer frame.
395///
396/// Owns its frame buffer internally (sized by `BUF`). The
397/// coding layer writes into
398/// [`buffer_mut()`](FrameRead::buffer_mut),
399/// [`feed()`](FrameRead::feed) validates the header, and
400/// [`next()`](FrameRead::next) returns zero-copy sub-slices.
401pub struct TcFrameReader<const BUF: usize> {
402    buf: [u8; BUF],
403    data_start: usize,
404    data_end: usize,
405}
406
407impl<const BUF: usize> TcFrameReader<BUF> {
408    /// Creates a new TC frame reader.
409    pub fn new() -> Self {
410        Self {
411            buf: [0u8; BUF],
412            data_start: 0,
413            data_end: 0,
414        }
415    }
416}
417
418impl<const BUF: usize> FrameRead for TcFrameReader<BUF> {
419    type Error = ParseError;
420
421    fn buffer_mut(&mut self) -> &mut [u8] {
422        &mut self.buf
423    }
424
425    fn feed(&mut self, len: usize) -> Result<(), ParseError> {
426        let parsed =
427            TelecommandTransferFrame::parse(&self.buf[..len])?;
428        let data = parsed.data_field();
429        self.data_start = TelecommandTransferFrame::HEADER_SIZE;
430        self.data_end = self.data_start + data.len();
431        Ok(())
432    }
433
434    fn data_field(&self) -> &[u8] {
435        &self.buf[self.data_start..self.data_end]
436    }
437}