Skip to main content

leodos_protocols/datalink/framing/
uslp.rs

1//! Unified Space Data Link Protocol (USLP)
2//!
3//! Spec: <https://ccsds.org/Pubs/732x1b3e1.pdf>
4//!
5//! USLP (CCSDS 732.1-B-3) defines a unified transfer frame format
6//! that replaces the separate TM, TC, and AOS frame protocols.
7//! It supports both fixed-length and variable-length frames with
8//! a variable-size primary header (4-14 bytes).
9//!
10//! # Frame Layout
11//!
12//! ```text
13//! +-------------------------------------+---------+
14//! | Field                               | Size    |
15//! +-------------------------------------+---------+
16//! | Primary Header (fixed part)         | 7 bytes |
17//! |   TFVN + SCID + Src/Dst + VCID      |         |
18//! |   + MAP ID + EOFPH Flag             | 4 bytes |
19//! |   Frame Length                      | 2 bytes |
20//! |   Flags + VCF Count Length          | 1 byte  |
21//! | Primary Header (VCF Count)          | 0-7 B   |
22//! +-------------------------------------+---------+
23//! | Insert Zone (optional)              | Varies  |
24//! +-------------------------------------+---------+
25//! | Transfer Frame Data Field           |         |
26//! |   TFDF Header (rules + UPID)        | 1 byte  |
27//! |   First Header / Last Octet Ptr     | 0 or 2  |
28//! |   Transfer Frame Data Zone          | Varies  |
29//! +-------------------------------------+---------+
30//! | Operational Control Field (opt)     | 4 bytes |
31//! +-------------------------------------+---------+
32//! | Frame Error Control Field (opt)     | 2 bytes |
33//! +-------------------------------------+---------+
34//! ```
35
36use bon::bon;
37use zerocopy::FromBytes;
38use zerocopy::Immutable;
39use zerocopy::IntoBytes;
40use zerocopy::KnownLayout;
41use zerocopy::Unaligned;
42use zerocopy::byteorder::network_endian::{U16, U32};
43
44use crate::ids::{Scid, Vcid};
45use crate::utils::{get_bits_u8, get_bits_u32, set_bits_u8, set_bits_u32};
46
47/// USLP Transfer Frame Version Number (`0b1100` = 12).
48pub const USLP_TFVN: u8 = 0b1100;
49
50/// VCID reserved for Only Idle Data (OID) Transfer Frames.
51pub const OID_VCID: u8 = 63;
52
53/// Bitmasks for USLP Transfer Frame header fields.
54#[rustfmt::skip]
55pub mod bitmask {
56    // --- Primary Header ID word (U32, bytes 0-3) ---
57
58    /// 4-bit Transfer Frame Version Number (CCSDS bits 0-3).
59    pub const TFVN_MASK: u32 =          0xF000_0000;
60    /// 16-bit Spacecraft Identifier (CCSDS bits 4-19).
61    pub const SCID_MASK: u32 =          0x0FFF_F000;
62    /// 1-bit Source-or-Destination Identifier (CCSDS bit 20).
63    pub const SRC_DEST_MASK: u32 =      0x0000_0800;
64    /// 6-bit Virtual Channel Identifier (CCSDS bits 21-26).
65    pub const VCID_MASK: u32 =          0x0000_07E0;
66    /// 4-bit Multiplexer Access Point ID (CCSDS bits 27-30).
67    pub const MAP_ID_MASK: u32 =        0x0000_001E;
68    /// 1-bit End of Frame Primary Header Flag (CCSDS bit 31).
69    pub const EOFPH_MASK: u32 =         0x0000_0001;
70
71    // --- Flags byte (byte 6) ---
72
73    /// 1-bit Bypass/Sequence Control Flag.
74    pub const BYPASS_FLAG_MASK: u8 =    0b_1000_0000;
75    /// 1-bit Protocol Control Command Flag.
76    pub const PCC_FLAG_MASK: u8 =       0b_0100_0000;
77    /// 2-bit Reserve Spares (shall be `00`).
78    pub const SPARE_MASK: u8 =          0b_0011_0000;
79    /// 1-bit Operational Control Field Flag.
80    pub const OCF_FLAG_MASK: u8 =       0b_0000_1000;
81    /// 3-bit VC Frame Count Length.
82    pub const VCF_COUNT_LEN_MASK: u8 =  0b_0000_0111;
83
84    // --- TFDF Header (first byte of Transfer Frame Data Field) ---
85
86    /// 3-bit TFDZ Construction Rules.
87    pub const TFDZ_RULES_MASK: u8 =     0b_1110_0000;
88    /// 5-bit USLP Protocol Identifier.
89    pub const UPID_MASK: u8 =           0b_0001_1111;
90}
91
92use bitmask::*;
93
94/// TFDZ Construction Rules (CCSDS 732.1-B-3, Table 4-3).
95#[derive(Debug, Copy, Clone, Eq, PartialEq)]
96#[repr(u8)]
97pub enum TfdzRule {
98    /// Packets spanning multiple frames (fixed-length TFDZ).
99    PacketsSpanning = 0,
100    /// Start of MAPA_SDU or VCA_SDU (fixed-length TFDZ).
101    SduStart = 1,
102    /// Continuing portion of MAPA_SDU or VCA_SDU (fixed).
103    SduContinue = 2,
104    /// Octet stream, continuous (variable-length TFDZ).
105    OctetStream = 3,
106    /// Starting segment of a large SDU (variable-length).
107    StartingSegment = 4,
108    /// Continuing segment (variable-length).
109    ContinuingSegment = 5,
110    /// Last segment (variable-length).
111    LastSegment = 6,
112    /// No segmentation (variable-length TFDZ).
113    NoSegmentation = 7,
114}
115
116impl TfdzRule {
117    /// Parses from a 3-bit raw value.
118    pub fn from_bits(bits: u8) -> Self {
119        match bits & 0x07 {
120            0 => Self::PacketsSpanning,
121            1 => Self::SduStart,
122            2 => Self::SduContinue,
123            3 => Self::OctetStream,
124            4 => Self::StartingSegment,
125            5 => Self::ContinuingSegment,
126            6 => Self::LastSegment,
127            _ => Self::NoSegmentation,
128        }
129    }
130
131    /// Whether this rule uses the optional 16-bit pointer.
132    pub fn has_pointer(self) -> bool {
133        matches!(
134            self,
135            Self::PacketsSpanning | Self::SduStart | Self::SduContinue
136        )
137    }
138}
139
140/// Well-known USLP Protocol Identifier (UPID) values.
141///
142/// These are registered in the SANA UPID registry.
143#[derive(Debug, Copy, Clone, Eq, PartialEq)]
144#[repr(u8)]
145pub enum Upid {
146    /// CCSDS Space Packets.
147    SpacePackets = 0,
148    /// CCSDS Encapsulation Packets.
149    EncapsulationPackets = 1,
150    /// COP-1 Control Commands.
151    Cop1Commands = 2,
152    /// COP-P / Proximity-1 SPDUs.
153    CopPCommands = 3,
154    /// SDLS Protocol data.
155    Sdls = 4,
156    /// Idle data in a fixed-length TFDZ.
157    IdleData = 30,
158    /// Only Idle Data (OID Transfer Frames).
159    OnlyIdleData = 31,
160}
161
162/// An error during USLP frame construction.
163#[derive(Debug, Copy, Clone, Eq, PartialEq)]
164pub enum BuildError {
165    /// VCID exceeds 6-bit range (0-63).
166    InvalidVcid(Vcid),
167    /// MAP ID exceeds 4-bit range (0-15).
168    InvalidMapId(u8),
169    /// VCF Count Length exceeds 3-bit range (0-7).
170    InvalidVcfCountLength(u8),
171    /// Buffer too small for the frame.
172    BufferTooSmall {
173        /// Minimum number of bytes needed.
174        required_len: usize,
175        /// Actual buffer size provided.
176        provided_len: usize,
177    },
178    /// Frame exceeds the 16-bit Frame Length limit (65536 bytes).
179    FrameTooLarge {
180        /// Maximum allowed length.
181        max_len: usize,
182        /// Actual buffer size provided.
183        provided_len: usize,
184    },
185}
186
187/// An error during USLP frame parsing.
188#[derive(Debug, Copy, Clone, Eq, PartialEq)]
189pub enum ParseError {
190    /// Buffer too short for the 7-byte fixed header.
191    TooShortForHeader,
192    /// Frame Length field does not match the buffer size.
193    LengthMismatch {
194        /// Expected from the Frame Length field.
195        expected: usize,
196        /// Actual buffer size.
197        actual: usize,
198    },
199    /// TFVN is not `0b1100` (Version-4).
200    InvalidVersion(u8),
201    /// Truncated header (EOFPH=1) is not supported.
202    TruncatedHeader,
203}
204
205/// The 7-byte fixed portion of a non-truncated USLP Primary Header.
206///
207/// Covers bytes 0-6: identification fields, frame length, and
208/// control flags. The variable-length VCF Count (0-7 bytes)
209/// follows immediately after this in the frame body.
210///
211/// ```text
212/// +--------+----------+------+------+------+------+
213/// | TFVN   | SCID     | S/D  | VCID | MAP  | EOFPH|
214/// | 4 bits | 16 bits  | 1b   | 6b   | 4b   | 1b   |
215/// +--------+----------+------+------+------+------+
216/// | Frame Length             | Flags byte         |
217/// | 16 bits                  | Byp PCC Sp OCF VCL |
218/// +--------+----------+------+------+------+------+
219/// ```
220#[repr(C, packed)]
221#[derive(FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable, Debug, Copy, Clone)]
222pub struct UslpPrimaryHeaderFixed {
223    /// Bytes 0-3: TFVN(4) + SCID(16) + Src/Dst(1) + VCID(6)
224    /// + MAP_ID(4) + EOFPH(1).
225    id: U32,
226    /// Bytes 4-5: Frame Length (total octets - 1).
227    frame_length: U16,
228    /// Byte 6: Bypass(1) + PCC(1) + Spare(2) + OCF(1) +
229    /// VCF Count Length(3).
230    flags: u8,
231}
232
233/// A zero-copy view over a USLP Transfer Frame.
234///
235/// The 7-byte fixed header is followed by a variable-length body
236/// containing the VCF Count, optional Insert Zone, Transfer Frame
237/// Data Field (TFDF Header + TFDZ), optional OCF, and optional
238/// FECF.
239#[repr(C, packed)]
240#[derive(IntoBytes, FromBytes, Unaligned, KnownLayout, Immutable)]
241pub struct UslpTransferFrame {
242    header: UslpPrimaryHeaderFixed,
243    body: [u8],
244}
245
246#[bon]
247impl UslpTransferFrame {
248    /// Size of the fixed portion of the primary header (bytes).
249    pub const FIXED_HEADER_SIZE: usize = 7;
250
251    /// Maximum total frame size (16-bit Frame Length field).
252    pub const MAX_FRAME_SIZE: usize = 65536;
253
254    /// Parses a byte slice into a USLP Transfer Frame view.
255    ///
256    /// Validates the TFVN (must be `0b1100`), the EOFPH flag
257    /// (truncated frames are not supported), and the Frame Length
258    /// field consistency with the buffer size.
259    pub fn parse(bytes: &[u8]) -> Result<&Self, ParseError> {
260        if bytes.len() < Self::FIXED_HEADER_SIZE {
261            return Err(ParseError::TooShortForHeader);
262        }
263
264        let frame =
265            UslpTransferFrame::ref_from_bytes(bytes).map_err(|_| ParseError::TooShortForHeader)?;
266
267        let tfvn = frame.header().tfvn();
268        if tfvn != USLP_TFVN {
269            return Err(ParseError::InvalidVersion(tfvn));
270        }
271
272        if frame.header().eofph() {
273            return Err(ParseError::TruncatedHeader);
274        }
275
276        let expected = frame.header().frame_length() as usize + 1;
277        if bytes.len() != expected {
278            return Err(ParseError::LengthMismatch {
279                expected,
280                actual: bytes.len(),
281            });
282        }
283
284        Ok(frame)
285    }
286
287    /// Returns a reference to the fixed primary header.
288    pub fn header(&self) -> &UslpPrimaryHeaderFixed {
289        &self.header
290    }
291
292    /// Returns a mutable reference to the fixed primary header.
293    pub fn header_mut(&mut self) -> &mut UslpPrimaryHeaderFixed {
294        &mut self.header
295    }
296
297    /// Returns the complete body after the 7-byte fixed header.
298    ///
299    /// Contains VCF Count + Insert Zone + TFDF + OCF + FECF.
300    pub fn body(&self) -> &[u8] {
301        &self.body
302    }
303
304    /// Returns a mutable reference to the body.
305    pub fn body_mut(&mut self) -> &mut [u8] {
306        &mut self.body
307    }
308
309    /// Total size of the primary header including VCF Count.
310    pub fn primary_header_size(&self) -> usize {
311        Self::FIXED_HEADER_SIZE + self.header().vcf_count_length() as usize
312    }
313
314    /// Returns the VCF Count value (big-endian, 0-56 bits).
315    pub fn vcf_count(&self) -> u64 {
316        let len = self.header().vcf_count_length() as usize;
317        let mut val = 0u64;
318        for &b in &self.body[..len] {
319            val = (val << 8) | b as u64;
320        }
321        val
322    }
323
324    /// Sets the VCF Count value (big-endian).
325    pub fn set_vcf_count(&mut self, count: u64) {
326        let len = self.header().vcf_count_length() as usize;
327        for i in 0..len {
328            let shift = (len - 1 - i) * 8;
329            self.body[i] = (count >> shift) as u8;
330        }
331    }
332
333    /// Returns the Insert Zone slice.
334    ///
335    /// `insert_zone_len` is a managed parameter configured for the
336    /// Physical Channel (0 if no Insert Zone).
337    pub fn insert_zone(&self, insert_zone_len: usize) -> &[u8] {
338        let start = self.header().vcf_count_length() as usize;
339        &self.body[start..start + insert_zone_len]
340    }
341
342    /// Returns a mutable reference to the Insert Zone.
343    pub fn insert_zone_mut(&mut self, insert_zone_len: usize) -> &mut [u8] {
344        let start = self.header().vcf_count_length() as usize;
345        &mut self.body[start..start + insert_zone_len]
346    }
347
348    /// Returns the Transfer Frame Data Field (TFDF Header + TFDZ).
349    ///
350    /// `insert_zone_len` is the managed Insert Zone size.
351    /// `fecf_present` indicates whether a 2-byte FECF is appended.
352    pub fn data_field(&self, insert_zone_len: usize, fecf_present: bool) -> &[u8] {
353        let start = self.header().vcf_count_length() as usize + insert_zone_len;
354        let ocf_len = if self.header().ocf_flag() { 4 } else { 0 };
355        let fecf_len = if fecf_present { 2 } else { 0 };
356        let end = self.body.len() - ocf_len - fecf_len;
357        &self.body[start..end]
358    }
359
360    /// Returns a mutable reference to the data field.
361    pub fn data_field_mut(&mut self, insert_zone_len: usize, fecf_present: bool) -> &mut [u8] {
362        let start = self.header().vcf_count_length() as usize + insert_zone_len;
363        let ocf_len = if self.header().ocf_flag() { 4 } else { 0 };
364        let fecf_len = if fecf_present { 2 } else { 0 };
365        let end = self.body.len() - ocf_len - fecf_len;
366        &mut self.body[start..end]
367    }
368
369    /// Returns the TFDZ Construction Rule from the TFDF Header.
370    pub fn tfdz_rule(&self, insert_zone_len: usize) -> TfdzRule {
371        let off = self.header().vcf_count_length() as usize + insert_zone_len;
372        TfdzRule::from_bits(get_bits_u8(self.body[off], TFDZ_RULES_MASK))
373    }
374
375    /// Returns the UPID from the TFDF Header.
376    pub fn upid(&self, insert_zone_len: usize) -> u8 {
377        let off = self.header().vcf_count_length() as usize + insert_zone_len;
378        get_bits_u8(self.body[off], UPID_MASK)
379    }
380
381    /// Returns the First Header / Last Valid Octet Pointer.
382    ///
383    /// Only present for TFDZ Construction Rules 000, 001, and 010.
384    /// Returns `None` if the current rule does not use a pointer.
385    pub fn pointer(&self, insert_zone_len: usize) -> Option<u16> {
386        let rule = self.tfdz_rule(insert_zone_len);
387        if !rule.has_pointer() {
388            return None;
389        }
390        let off = self.header().vcf_count_length() as usize + insert_zone_len + 1;
391        let hi = self.body[off] as u16;
392        let lo = self.body[off + 1] as u16;
393        Some((hi << 8) | lo)
394    }
395
396    /// Returns the Transfer Frame Data Zone (payload only).
397    ///
398    /// This is the TFDF minus the TFDF Header bytes.
399    pub fn data_zone(&self, insert_zone_len: usize, fecf_present: bool) -> &[u8] {
400        let rule = self.tfdz_rule(insert_zone_len);
401        let tfdf_hdr_len = if rule.has_pointer() { 3 } else { 1 };
402        let start = self.header().vcf_count_length() as usize + insert_zone_len + tfdf_hdr_len;
403        let ocf_len = if self.header().ocf_flag() { 4 } else { 0 };
404        let fecf_len = if fecf_present { 2 } else { 0 };
405        let end = self.body.len() - ocf_len - fecf_len;
406        &self.body[start..end]
407    }
408
409    /// Returns the OCF (4 bytes) if present.
410    pub fn ocf(&self, fecf_present: bool) -> Option<&[u8]> {
411        if !self.header().ocf_flag() {
412            return None;
413        }
414        let fecf_len = if fecf_present { 2 } else { 0 };
415        let end = self.body.len() - fecf_len;
416        Some(&self.body[end - 4..end])
417    }
418
419    /// Returns the FECF (2 bytes) if present.
420    ///
421    /// `fecf_present` is a managed parameter for the Physical
422    /// Channel.
423    pub fn fecf(&self, fecf_present: bool) -> Option<&[u8]> {
424        if !fecf_present {
425            return None;
426        }
427        let len = self.body.len();
428        Some(&self.body[len - 2..])
429    }
430
431    /// Writes the TFDF Header at the start of the data field.
432    ///
433    /// Sets the Construction Rule, UPID, and optional 16-bit
434    /// pointer in the TFDF Header bytes.
435    pub fn set_tfdf_header(
436        &mut self,
437        insert_zone_len: usize,
438        rule: TfdzRule,
439        upid: u8,
440        pointer: Option<u16>,
441    ) {
442        let off = self.header().vcf_count_length() as usize + insert_zone_len;
443        let mut byte0 = 0u8;
444        set_bits_u8(&mut byte0, TFDZ_RULES_MASK, rule as u8);
445        set_bits_u8(&mut byte0, UPID_MASK, upid);
446        self.body[off] = byte0;
447        if let Some(ptr) = pointer {
448            self.body[off + 1] = (ptr >> 8) as u8;
449            self.body[off + 2] = ptr as u8;
450        }
451    }
452
453    /// Constructs a new USLP Transfer Frame in the provided buffer.
454    ///
455    /// The frame length is determined by the buffer size. The body
456    /// after the primary header is zeroed; use [`Self::body_mut`],
457    /// [`Self::set_tfdf_header`], and [`Self::data_field_mut`] to
458    /// fill in the Insert Zone, TFDF, OCF, and FECF.
459    #[builder]
460    pub fn new(
461        buffer: &mut [u8],
462        scid: Scid,
463        vcid: Vcid,
464        #[builder(default)] source_or_dest: bool,
465        #[builder(default)] map_id: u8,
466        #[builder(default)] bypass: bool,
467        #[builder(default)] protocol_control_command: bool,
468        #[builder(default)] ocf_flag: bool,
469        #[builder(default)] vcf_count_length: u8,
470        #[builder(default)] vcf_count: u64,
471    ) -> Result<&mut Self, BuildError> {
472        if vcid.num_bits() > 6 {
473            return Err(BuildError::InvalidVcid(vcid));
474        }
475        if map_id > 15 {
476            return Err(BuildError::InvalidMapId(map_id));
477        }
478        if vcf_count_length > 7 {
479            return Err(BuildError::InvalidVcfCountLength(vcf_count_length));
480        }
481
482        let provided_len = buffer.len();
483        let min_size = Self::FIXED_HEADER_SIZE + vcf_count_length as usize;
484
485        if provided_len < min_size {
486            return Err(BuildError::BufferTooSmall {
487                required_len: min_size,
488                provided_len,
489            });
490        }
491        if provided_len > Self::MAX_FRAME_SIZE {
492            return Err(BuildError::FrameTooLarge {
493                max_len: Self::MAX_FRAME_SIZE,
494                provided_len,
495            });
496        }
497
498        let frame =
499            UslpTransferFrame::mut_from_bytes(buffer).map_err(|_| BuildError::BufferTooSmall {
500                required_len: min_size,
501                provided_len,
502            })?;
503
504        // Zero the header for a clean state.
505        frame.header.id = U32::new(0);
506        frame.header.frame_length = U16::new(0);
507        frame.header.flags = 0;
508
509        frame.header.set_tfvn(USLP_TFVN);
510        frame.header.set_scid(scid);
511        frame.header.set_source_or_dest(source_or_dest);
512        frame.header.set_vcid(vcid);
513        frame.header.set_map_id(map_id);
514        frame.header.set_eofph(false);
515        frame.header.set_frame_length((provided_len - 1) as u16);
516        frame.header.set_bypass(bypass);
517        frame
518            .header
519            .set_protocol_control_command(protocol_control_command);
520        frame.header.set_ocf_flag(ocf_flag);
521        frame.header.set_vcf_count_length(vcf_count_length);
522
523        // Write VCF Count (big-endian).
524        let vcf_len = vcf_count_length as usize;
525        for i in 0..vcf_len {
526            let shift = (vcf_len - 1 - i) * 8;
527            frame.body[i] = (vcf_count >> shift) as u8;
528        }
529
530        Ok(frame)
531    }
532}
533
534// --- UslpPrimaryHeaderFixed accessors ---
535
536impl UslpPrimaryHeaderFixed {
537    // === ID field (U32, bytes 0-3) ===
538
539    /// Returns the 4-bit Transfer Frame Version Number.
540    pub fn tfvn(&self) -> u8 {
541        get_bits_u32(self.id, TFVN_MASK) as u8
542    }
543
544    /// Sets the Transfer Frame Version Number.
545    pub fn set_tfvn(&mut self, v: u8) {
546        set_bits_u32(&mut self.id, TFVN_MASK, v as u32);
547    }
548
549    /// Returns the 16-bit Spacecraft Identifier.
550    pub fn scid(&self) -> Scid {
551        Scid::new(get_bits_u32(self.id, SCID_MASK) as u32)
552    }
553
554    /// Sets the Spacecraft Identifier.
555    pub fn set_scid(&mut self, v: Scid) {
556        set_bits_u32(&mut self.id, SCID_MASK, v.get());
557    }
558
559    /// Returns the Source-or-Destination Identifier.
560    ///
561    /// `false` = SCID is the source; `true` = SCID is the dest.
562    pub fn source_or_dest(&self) -> bool {
563        get_bits_u32(self.id, SRC_DEST_MASK) != 0
564    }
565
566    /// Sets the Source-or-Destination Identifier.
567    pub fn set_source_or_dest(&mut self, v: bool) {
568        set_bits_u32(&mut self.id, SRC_DEST_MASK, v as u32);
569    }
570
571    /// Returns the 6-bit Virtual Channel Identifier.
572    pub fn vcid(&self) -> Vcid {
573        Vcid::new(get_bits_u32(self.id, VCID_MASK) as u32)
574    }
575
576    /// Sets the Virtual Channel Identifier.
577    pub fn set_vcid(&mut self, v: Vcid) {
578        set_bits_u32(&mut self.id, VCID_MASK, v.get());
579    }
580
581    /// Returns the 4-bit Multiplexer Access Point Identifier.
582    pub fn map_id(&self) -> u8 {
583        get_bits_u32(self.id, MAP_ID_MASK) as u8
584    }
585
586    /// Sets the MAP Identifier.
587    pub fn set_map_id(&mut self, v: u8) {
588        set_bits_u32(&mut self.id, MAP_ID_MASK, v as u32);
589    }
590
591    /// Returns the End of Frame Primary Header Flag.
592    ///
593    /// `false` = non-truncated (13 fields);
594    /// `true` = truncated (6 fields only).
595    pub fn eofph(&self) -> bool {
596        get_bits_u32(self.id, EOFPH_MASK) != 0
597    }
598
599    /// Sets the End of Frame Primary Header Flag.
600    pub fn set_eofph(&mut self, v: bool) {
601        set_bits_u32(&mut self.id, EOFPH_MASK, v as u32);
602    }
603
604    // === Frame Length (U16, bytes 4-5) ===
605
606    /// Returns the Frame Length field value.
607    ///
608    /// Total frame size in bytes = `frame_length() + 1`.
609    pub fn frame_length(&self) -> u16 {
610        self.frame_length.get()
611    }
612
613    /// Sets the Frame Length field.
614    pub fn set_frame_length(&mut self, v: u16) {
615        self.frame_length.set(v);
616    }
617
618    // === Flags byte (byte 6) ===
619
620    /// Returns the Bypass/Sequence Control Flag.
621    ///
622    /// `false` = Sequence-Controlled (Type-A);
623    /// `true` = Expedited / bypass (Type-B).
624    pub fn bypass(&self) -> bool {
625        get_bits_u8(self.flags, BYPASS_FLAG_MASK) != 0
626    }
627
628    /// Sets the Bypass/Sequence Control Flag.
629    pub fn set_bypass(&mut self, v: bool) {
630        set_bits_u8(&mut self.flags, BYPASS_FLAG_MASK, v as u8);
631    }
632
633    /// Returns the Protocol Control Command Flag.
634    ///
635    /// `false` = user data; `true` = protocol control commands.
636    pub fn protocol_control_command(&self) -> bool {
637        get_bits_u8(self.flags, PCC_FLAG_MASK) != 0
638    }
639
640    /// Sets the Protocol Control Command Flag.
641    pub fn set_protocol_control_command(&mut self, v: bool) {
642        set_bits_u8(&mut self.flags, PCC_FLAG_MASK, v as u8);
643    }
644
645    /// Returns the OCF Flag.
646    ///
647    /// `true` = OCF (4 bytes) is present in this frame.
648    pub fn ocf_flag(&self) -> bool {
649        get_bits_u8(self.flags, OCF_FLAG_MASK) != 0
650    }
651
652    /// Sets the OCF Flag.
653    pub fn set_ocf_flag(&mut self, v: bool) {
654        set_bits_u8(&mut self.flags, OCF_FLAG_MASK, v as u8);
655    }
656
657    /// Returns the VCF Count Length (0-7).
658    ///
659    /// Determines how many bytes are used for the VCF Count
660    /// field following this fixed header.
661    pub fn vcf_count_length(&self) -> u8 {
662        get_bits_u8(self.flags, VCF_COUNT_LEN_MASK)
663    }
664
665    /// Sets the VCF Count Length.
666    pub fn set_vcf_count_length(&mut self, v: u8) {
667        set_bits_u8(&mut self.flags, VCF_COUNT_LEN_MASK, v);
668    }
669}
670
671impl core::fmt::Debug for UslpTransferFrame {
672    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
673        f.debug_struct("UslpTransferFrame")
674            .field("header", &self.header)
675            .field("body_len", &self.body.len())
676            .finish()
677    }
678}
679
680// ── FrameWrite / FrameRead implementations ──
681
682use super::{FrameRead, FrameWrite, PushError};
683
684/// Configuration for building USLP transfer frames.
685#[derive(Debug, Clone)]
686pub struct UslpFrameWriterConfig {
687    /// Spacecraft ID (16-bit).
688    pub scid: Scid,
689    /// Virtual Channel ID (6-bit).
690    pub vcid: Vcid,
691    /// Multiplexer Access Point ID (4-bit).
692    pub map_id: u8,
693    /// Bypass/Sequence Control Flag.
694    pub bypass: bool,
695    /// Protocol Control Command Flag.
696    pub protocol_control_command: bool,
697    /// Operational Control Field Flag.
698    pub ocf_flag: bool,
699    /// VCF Count field length in bytes (0-7).
700    pub vcf_count_length: u8,
701    /// Insert Zone length in bytes.
702    pub insert_zone_len: usize,
703    /// TFDZ Construction Rule.
704    pub tfdz_rule: TfdzRule,
705    /// USLP Protocol Identifier.
706    pub upid: u8,
707    /// Whether a 2-byte FECF is appended.
708    pub fecf_present: bool,
709    /// Maximum data zone length in bytes (payload capacity).
710    pub max_data_zone_len: usize,
711}
712
713/// Accumulates packets into USLP transfer frames.
714///
715/// Owns its frame buffer internally (sized by `BUF`). Packets
716/// are pushed directly into the buffer at the correct offset.
717/// [`finish()`](FrameWrite::finish) stamps the header and
718/// returns a borrow of the completed frame.
719pub struct UslpFrameWriter<const BUF: usize> {
720    config: UslpFrameWriterConfig,
721    vcf_count: u64,
722    data_zone_offset: usize,
723    data_len: usize,
724    buf: [u8; BUF],
725}
726
727impl<const BUF: usize> UslpFrameWriter<BUF> {
728    /// Creates a new USLP frame writer.
729    pub fn new(config: UslpFrameWriterConfig) -> Self {
730        let tfdf_header_len = if config.tfdz_rule.has_pointer() { 3 } else { 1 };
731        let data_zone_offset = UslpTransferFrame::FIXED_HEADER_SIZE
732            + config.vcf_count_length as usize
733            + config.insert_zone_len
734            + tfdf_header_len;
735        Self {
736            config,
737            vcf_count: 0,
738            data_zone_offset,
739            data_len: 0,
740            buf: [0u8; BUF],
741        }
742    }
743
744    fn remaining(&self) -> usize {
745        self.config.max_data_zone_len.saturating_sub(self.data_len)
746    }
747}
748
749impl<const BUF: usize> FrameWrite for UslpFrameWriter<BUF> {
750    type Error = BuildError;
751
752    fn is_empty(&self) -> bool {
753        self.data_len == 0
754    }
755
756    fn push(&mut self, data: &[u8]) -> Result<(), PushError> {
757        if data.len() > self.config.max_data_zone_len {
758            return Err(PushError::TooLarge);
759        }
760        if data.len() > self.remaining() {
761            return Err(PushError::Full);
762        }
763        let off = self.data_zone_offset + self.data_len;
764        self.buf[off..off + data.len()].copy_from_slice(data);
765        self.data_len += data.len();
766        Ok(())
767    }
768
769    fn finish(&mut self) -> Result<&[u8], BuildError> {
770        let ocf_len = if self.config.ocf_flag { 4 } else { 0 };
771        let fecf_len = if self.config.fecf_present { 2 } else { 0 };
772        let total = self.data_zone_offset + self.data_len + ocf_len + fecf_len;
773
774        let vcf_count = self.vcf_count;
775        self.vcf_count = self.vcf_count.wrapping_add(1);
776
777        // Stamp the primary header and VCF count. The builder
778        // zeroes the header bytes but does not touch data beyond
779        // the VCF count field, so our payload is safe.
780        UslpTransferFrame::builder()
781            .buffer(&mut self.buf[..total])
782            .scid(self.config.scid)
783            .vcid(self.config.vcid)
784            .map_id(self.config.map_id)
785            .bypass(self.config.bypass)
786            .protocol_control_command(self.config.protocol_control_command)
787            .ocf_flag(self.config.ocf_flag)
788            .vcf_count_length(self.config.vcf_count_length)
789            .vcf_count(vcf_count)
790            .build()?;
791
792        // Stamp the TFDF header (rule + UPID + optional pointer).
793        let frame = UslpTransferFrame::mut_from_bytes(&mut self.buf[..total]).unwrap();
794        frame.set_tfdf_header(
795            self.config.insert_zone_len,
796            self.config.tfdz_rule,
797            self.config.upid,
798            if self.config.tfdz_rule.has_pointer() {
799                Some(0)
800            } else {
801                None
802            },
803        );
804
805        self.data_len = 0;
806        Ok(&self.buf[..total])
807    }
808}
809
810/// Extracts packets from a received USLP transfer frame.
811///
812/// Owns its frame buffer internally (sized by `BUF`). The
813/// coding layer writes into
814/// [`buffer_mut()`](FrameRead::buffer_mut),
815/// [`feed()`](FrameRead::feed) validates the header, and
816/// [`next()`](FrameRead::next) returns zero-copy sub-slices.
817pub struct UslpFrameReader<const BUF: usize> {
818    insert_zone_len: usize,
819    fecf_present: bool,
820    buf: [u8; BUF],
821    data_start: usize,
822    data_end: usize,
823}
824
825impl<const BUF: usize> UslpFrameReader<BUF> {
826    /// Creates a new USLP frame reader.
827    pub fn new(insert_zone_len: usize, fecf_present: bool) -> Self {
828        Self {
829            insert_zone_len,
830            fecf_present,
831            buf: [0u8; BUF],
832            data_start: 0,
833            data_end: 0,
834        }
835    }
836}
837
838impl<const BUF: usize> FrameRead for UslpFrameReader<BUF> {
839    type Error = ParseError;
840
841    fn buffer_mut(&mut self) -> &mut [u8] {
842        &mut self.buf
843    }
844
845    fn feed(&mut self, len: usize) -> Result<(), ParseError> {
846        let frame = UslpTransferFrame::parse(&self.buf[..len])?;
847        let rule = frame.tfdz_rule(self.insert_zone_len);
848        let tfdf_hdr_len = if rule.has_pointer() { 3 } else { 1 };
849        let header_overhead = UslpTransferFrame::FIXED_HEADER_SIZE
850            + frame.header().vcf_count_length() as usize
851            + self.insert_zone_len
852            + tfdf_hdr_len;
853        let ocf_len = if frame.header().ocf_flag() { 4 } else { 0 };
854        let fecf_len = if self.fecf_present { 2 } else { 0 };
855        self.data_start = header_overhead;
856        self.data_end = len - ocf_len - fecf_len;
857        Ok(())
858    }
859
860    fn data_field(&self) -> &[u8] {
861        &self.buf[self.data_start..self.data_end]
862    }
863}
864
865#[cfg(test)]
866mod tests {
867    use super::*;
868    use crate::ids::{Scid, Vcid};
869
870    #[test]
871    fn build_and_parse_basic_frame() {
872        let mut buf = [0u8; 32];
873        let frame = UslpTransferFrame::builder()
874            .buffer(&mut buf)
875            .scid(Scid::new(42))
876            .vcid(Vcid::new(1))
877            .build()
878            .unwrap();
879
880        assert_eq!(frame.header().tfvn(), USLP_TFVN);
881        assert_eq!(frame.header().scid(), Scid::new(42));
882        assert_eq!(frame.header().vcid(), Vcid::new(1));
883        assert_eq!(frame.header().map_id(), 0);
884        assert!(!frame.header().source_or_dest());
885        assert!(!frame.header().eofph());
886        assert!(!frame.header().bypass());
887        assert!(!frame.header().protocol_control_command());
888        assert!(!frame.header().ocf_flag());
889        assert_eq!(frame.header().vcf_count_length(), 0);
890        assert_eq!(frame.header().frame_length(), 31);
891    }
892
893    #[test]
894    fn parse_validates_tfvn() {
895        let mut buf = [0u8; 16];
896        // Build a valid frame then corrupt the TFVN.
897        UslpTransferFrame::builder()
898            .buffer(&mut buf)
899            .scid(Scid::new(0))
900            .vcid(Vcid::new(0))
901            .build()
902            .unwrap();
903        buf[0] = 0x00; // TFVN = 0 instead of 0xC
904        let err = UslpTransferFrame::parse(&buf).unwrap_err();
905        assert_eq!(err, ParseError::InvalidVersion(0));
906    }
907
908    #[test]
909    fn parse_validates_length() {
910        let mut buf = [0u8; 16];
911        UslpTransferFrame::builder()
912            .buffer(&mut buf)
913            .scid(Scid::new(0))
914            .vcid(Vcid::new(0))
915            .build()
916            .unwrap();
917        // Parse with a truncated slice.
918        let err = UslpTransferFrame::parse(&buf[..12]).unwrap_err();
919        assert!(matches!(err, ParseError::LengthMismatch { .. }));
920    }
921
922    #[test]
923    fn roundtrip_with_vcf_count() {
924        let mut buf = [0u8; 64];
925        let frame = UslpTransferFrame::builder()
926            .buffer(&mut buf)
927            .scid(Scid::new(1000))
928            .vcid(Vcid::new(5))
929            .map_id(3)
930            .bypass(true)
931            .vcf_count_length(2)
932            .vcf_count(1234)
933            .build()
934            .unwrap();
935
936        assert_eq!(frame.header().scid(), Scid::new(1000));
937        assert_eq!(frame.header().vcid(), Vcid::new(5));
938        assert_eq!(frame.header().map_id(), 3);
939        assert!(frame.header().bypass());
940        assert_eq!(frame.header().vcf_count_length(), 2);
941        assert_eq!(frame.vcf_count(), 1234);
942        assert_eq!(frame.primary_header_size(), 9);
943    }
944
945    #[test]
946    fn roundtrip_parse() {
947        let mut buf = [0u8; 32];
948        UslpTransferFrame::builder()
949            .buffer(&mut buf)
950            .scid(Scid::new(500))
951            .vcid(Vcid::new(7))
952            .source_or_dest(true)
953            .ocf_flag(true)
954            .build()
955            .unwrap();
956
957        let frame = UslpTransferFrame::parse(&buf).unwrap();
958        assert_eq!(frame.header().scid(), Scid::new(500));
959        assert_eq!(frame.header().vcid(), Vcid::new(7));
960        assert!(frame.header().source_or_dest());
961        assert!(frame.header().ocf_flag());
962    }
963
964    #[test]
965    fn tfdf_header_roundtrip() {
966        let mut buf = [0u8; 32];
967        let frame = UslpTransferFrame::builder()
968            .buffer(&mut buf)
969            .scid(Scid::new(0))
970            .vcid(Vcid::new(0))
971            .build()
972            .unwrap();
973
974        frame.set_tfdf_header(0, TfdzRule::NoSegmentation, Upid::SpacePackets as u8, None);
975
976        assert_eq!(frame.tfdz_rule(0), TfdzRule::NoSegmentation);
977        assert_eq!(frame.upid(0), 0);
978        assert_eq!(frame.pointer(0), None);
979    }
980
981    #[test]
982    fn tfdf_header_with_pointer() {
983        let mut buf = [0u8; 32];
984        let frame = UslpTransferFrame::builder()
985            .buffer(&mut buf)
986            .scid(Scid::new(0))
987            .vcid(Vcid::new(0))
988            .build()
989            .unwrap();
990
991        frame.set_tfdf_header(
992            0,
993            TfdzRule::PacketsSpanning,
994            Upid::SpacePackets as u8,
995            Some(0x0100),
996        );
997
998        assert_eq!(frame.tfdz_rule(0), TfdzRule::PacketsSpanning);
999        assert_eq!(frame.pointer(0), Some(0x0100));
1000    }
1001
1002    #[test]
1003    fn invalid_vcid_rejected() {
1004        let mut buf = [0u8; 32];
1005        let err = UslpTransferFrame::builder()
1006            .buffer(&mut buf)
1007            .scid(Scid::new(0))
1008            .vcid(Vcid::new(64))
1009            .build()
1010            .unwrap_err();
1011        assert_eq!(err, BuildError::InvalidVcid(Vcid::new(64)));
1012    }
1013
1014    #[test]
1015    fn tfdz_rule_from_bits() {
1016        for i in 0..=7u8 {
1017            let rule = TfdzRule::from_bits(i);
1018            assert_eq!(rule as u8, i);
1019        }
1020    }
1021
1022    #[test]
1023    fn tfdz_rule_has_pointer() {
1024        assert!(TfdzRule::PacketsSpanning.has_pointer());
1025        assert!(TfdzRule::SduStart.has_pointer());
1026        assert!(TfdzRule::SduContinue.has_pointer());
1027        assert!(!TfdzRule::OctetStream.has_pointer());
1028        assert!(!TfdzRule::StartingSegment.has_pointer());
1029        assert!(!TfdzRule::ContinuingSegment.has_pointer());
1030        assert!(!TfdzRule::LastSegment.has_pointer());
1031        assert!(!TfdzRule::NoSegmentation.has_pointer());
1032    }
1033
1034    #[test]
1035    fn max_scid() {
1036        let mut buf = [0u8; 16];
1037        let frame = UslpTransferFrame::builder()
1038            .buffer(&mut buf)
1039            .scid(Scid::new(0xFFFF))
1040            .vcid(Vcid::new(0))
1041            .build()
1042            .unwrap();
1043        assert_eq!(frame.header().scid(), Scid::new(0xFFFF));
1044    }
1045}