Skip to main content

leodos_protocols/datalink/framing/sdlp/
proximity1.rs

1//! CCSDS Proximity-1 Space Link Protocol — Data Link Layer (CCSDS 211.0-B-6)
2//!
3//! Implements the Version-3 Transfer Frame used for short-range,
4//! bi-directional space links (e.g., orbiter-to-lander, rover-to-relay).
5
6use bon::bon;
7use zerocopy::FromBytes;
8use zerocopy::Immutable;
9use zerocopy::IntoBytes;
10use zerocopy::KnownLayout;
11use zerocopy::Unaligned;
12use zerocopy::byteorder::network_endian::U16;
13
14use crate::ids::Scid;
15use crate::utils::get_bits_u8;
16use crate::utils::get_bits_u16;
17use crate::utils::set_bits_u8;
18use crate::utils::set_bits_u16;
19
20/// A zero-copy view over a Proximity-1 Version-3 Transfer Frame.
21///
22/// # Layout
23///
24/// ```text
25/// +--------------------------------------+---------------------+
26/// | Field Name                           | Size                |
27/// +--------------------------------------+---------------------+
28/// | -- Transfer Frame Header (5 bytes) --|                     |
29/// |                                      |                     |
30/// | Transfer Frame Version Number        | 2 bits              |
31/// | Quality of Service (QoS) Indicator   | 1 bit               |
32/// | PDU Type ID                          | 1 bit               |
33/// | Data Field Construction ID (DFC ID)  | 2 bits              |
34/// | Spacecraft Identifier (SCID)         | 10 bits             |
35/// | Physical Channel Identifier (PCID)   | 1 bit               |
36/// | Port Identifier                      | 3 bits              |
37/// | Source-or-Destination Identifier     | 1 bit               |
38/// | Frame Length                         | 11 bits             |
39/// | Frame Sequence Number (FSN)          | 8 bits              |
40/// |                                      |                     |
41/// | -- Data Field ------------------------| 0 - 2043 bytes      |
42/// +--------------------------------------+---------------------+
43/// ```
44#[repr(C, packed)]
45#[derive(FromBytes, IntoBytes, Unaligned, KnownLayout, Immutable)]
46pub struct Proximity1TransferFrame {
47    header: Proximity1Header,
48    data_field: [u8],
49}
50
51/// The 5-byte header of a Proximity-1 Version-3 Transfer Frame.
52#[repr(C)]
53#[derive(
54    FromBytes, IntoBytes, KnownLayout, Unaligned, Immutable,
55    Debug, Copy, Clone,
56)]
57pub struct Proximity1Header {
58    /// Version(2) | QoS(1) | PDU Type(1) | DFC ID(2) | SCID(10).
59    version_qos_pdu_dfc_scid: U16,
60    /// PCID(1) | Port ID(3) | Src/Dest(1) | Frame Length(11).
61    pcid_port_srcdst_len: U16,
62    /// Frame Sequence Number (8 bits).
63    fsn: u8,
64}
65
66/// Bitmasks for the first header word.
67#[rustfmt::skip]
68pub mod bitmask {
69    // -- first U16: version_qos_pdu_dfc_scid --
70    /// 2-bit Transfer Frame Version Number (bits 0–1).
71    pub const VERSION_MASK: u16  = 0b_1100_0000_0000_0000;
72    /// 1-bit Quality of Service indicator (bit 2).
73    pub const QOS_MASK: u16      = 0b_0010_0000_0000_0000;
74    /// 1-bit PDU Type ID (bit 3).
75    pub const PDU_TYPE_MASK: u16 = 0b_0001_0000_0000_0000;
76    /// 2-bit Data Field Construction ID (bits 4–5).
77    pub const DFC_ID_MASK: u16   = 0b_0000_1100_0000_0000;
78    /// 10-bit Spacecraft Identifier (bits 6–15).
79    pub const SCID_MASK: u16     = 0b_0000_0011_1111_1111;
80
81    // -- second U16: pcid_port_srcdst_len --
82    /// 1-bit Physical Channel Identifier (bit 16).
83    pub const PCID_MASK: u16     = 0b_1000_0000_0000_0000;
84    /// 3-bit Port Identifier (bits 17–19).
85    pub const PORT_ID_MASK: u16  = 0b_0111_0000_0000_0000;
86    /// 1-bit Source-or-Destination Identifier (bit 20).
87    pub const SRC_DEST_MASK: u16 = 0b_0000_1000_0000_0000;
88    /// 11-bit Frame Length field (bits 21–31).
89    pub const FRAME_LEN_MASK: u16 = 0b_0000_0111_1111_1111;
90}
91
92use bitmask::*;
93
94/// Quality of Service indicator.
95#[derive(Debug, Copy, Clone, Eq, PartialEq)]
96#[repr(u8)]
97pub enum QoS {
98    /// Sequence Controlled service (reliable, in-order delivery).
99    SequenceControlled = 0,
100    /// Expedited service (best-effort, no ARQ).
101    Expedited = 1,
102}
103
104/// PDU Type — distinguishes user data from supervisory frames.
105#[derive(Debug, Copy, Clone, Eq, PartialEq)]
106#[repr(u8)]
107pub enum PduType {
108    /// U-frame: Transfer Frame Data field contains user data.
109    UserData = 0,
110    /// P-frame: Transfer Frame Data field contains SPDUs.
111    Supervisory = 1,
112}
113
114/// Data Field Construction Identifier.
115///
116/// Indicates how the data field of a U-frame is organized.
117/// In a P-frame, this shall be set to `Packets` (binary '00').
118#[derive(Debug, Copy, Clone, Eq, PartialEq)]
119#[repr(u8)]
120pub enum DfcId {
121    /// Integer number of unsegmented packets (binary '00').
122    Packets = 0b00,
123    /// A complete or segmented packet (binary '01').
124    Segments = 0b01,
125    /// Reserved for future CCSDS definition (binary '10').
126    Reserved = 0b10,
127    /// User-defined data (binary '11').
128    UserDefined = 0b11,
129}
130
131/// Source-or-Destination Identifier.
132#[derive(Debug, Copy, Clone, Eq, PartialEq)]
133#[repr(u8)]
134pub enum SrcDest {
135    /// SCID identifies the source spacecraft.
136    Source = 0,
137    /// SCID identifies the destination spacecraft.
138    Destination = 1,
139}
140
141/// An error that can occur during frame construction.
142#[derive(Debug, Copy, Clone, Eq, PartialEq)]
143pub enum BuildError {
144    /// SCID exceeds the 10-bit range (0–1023).
145    InvalidScid(Scid),
146    /// Port ID exceeds the 3-bit range (0–7).
147    InvalidPortId(u8),
148    /// Data field exceeds the maximum of 2043 bytes.
149    DataTooLong(usize),
150    /// The provided buffer is too small for the frame.
151    BufferTooSmall {
152        /// Minimum number of bytes needed for the frame.
153        required: usize,
154        /// Actual buffer size provided.
155        provided: usize,
156    },
157}
158
159/// An error that can occur during frame parsing.
160#[derive(Debug, Copy, Clone, Eq, PartialEq)]
161pub enum ParseError {
162    /// Slice is shorter than the 5-byte header.
163    TooShortForHeader {
164        /// Actual number of bytes provided.
165        actual: usize,
166    },
167    /// Header length field implies a larger frame than provided.
168    IncompleteFrame {
169        /// Total frame length declared in the header.
170        header_len: usize,
171        /// Actual buffer length provided.
172        buffer_len: usize,
173    },
174    /// Version field is not binary '10'.
175    InvalidVersion(u8),
176}
177
178/// The Proximity-1 Version-3 Transfer Frame version (binary '10').
179pub const PROXIMITY1_VERSION: u8 = 0b10;
180
181/// Maximum total frame size in bytes (header + data).
182pub const MAX_FRAME_LEN: usize = 2048;
183
184/// Maximum data field size in bytes.
185pub const MAX_DATA_FIELD_LEN: usize = 2043;
186
187#[bon]
188impl Proximity1TransferFrame {
189    /// Header size in bytes.
190    pub const HEADER_SIZE: usize = 5;
191
192    /// Parses a raw byte slice into a Proximity-1 Transfer Frame.
193    pub fn parse(bytes: &[u8]) -> Result<&Self, ParseError> {
194        if bytes.len() < Self::HEADER_SIZE {
195            return Err(ParseError::TooShortForHeader {
196                actual: bytes.len(),
197            });
198        }
199
200        let (header, _) =
201            Proximity1Header::ref_from_prefix(bytes).unwrap();
202        let total = header.frame_len();
203
204        if total > bytes.len() {
205            return Err(ParseError::IncompleteFrame {
206                header_len: total,
207                buffer_len: bytes.len(),
208            });
209        }
210
211        let version = header.version();
212        if version != PROXIMITY1_VERSION {
213            return Err(ParseError::InvalidVersion(version));
214        }
215
216        Ok(Self::ref_from_bytes(&bytes[..total]).unwrap())
217    }
218
219    /// Parses a mutable byte slice into a Proximity-1 Transfer Frame.
220    pub fn parse_mut(
221        bytes: &mut [u8],
222    ) -> Result<&mut Self, ParseError> {
223        if bytes.len() < Self::HEADER_SIZE {
224            return Err(ParseError::TooShortForHeader {
225                actual: bytes.len(),
226            });
227        }
228
229        let (header, _) =
230            Proximity1Header::ref_from_prefix(bytes).unwrap();
231        let total = header.frame_len();
232        let version = header.version();
233
234        if total > bytes.len() {
235            return Err(ParseError::IncompleteFrame {
236                header_len: total,
237                buffer_len: bytes.len(),
238            });
239        }
240        if version != PROXIMITY1_VERSION {
241            return Err(ParseError::InvalidVersion(version));
242        }
243
244        Ok(Self::mut_from_bytes(&mut bytes[..total]).unwrap())
245    }
246
247    /// Returns a reference to the header.
248    pub fn header(&self) -> &Proximity1Header {
249        &self.header
250    }
251
252    /// Returns the data field contents.
253    pub fn data_field(&self) -> &[u8] {
254        &self.data_field
255    }
256
257    /// Returns a mutable reference to the data field.
258    pub fn data_field_mut(&mut self) -> &mut [u8] {
259        &mut self.data_field
260    }
261
262    /// Total frame length in bytes (header + data).
263    pub fn frame_len(&self) -> usize {
264        Self::HEADER_SIZE + self.data_field.len()
265    }
266
267    /// Constructs a new Proximity-1 Version-3 Transfer Frame.
268    #[builder]
269    pub fn new(
270        buffer: &mut [u8],
271        scid: Scid,
272        qos: QoS,
273        pdu_type: PduType,
274        dfc_id: DfcId,
275        pcid: bool,
276        port_id: u8,
277        src_dest: SrcDest,
278        fsn: u8,
279        data_field_len: usize,
280    ) -> Result<&mut Self, BuildError> {
281        if scid.num_bits() > 10 {
282            return Err(BuildError::InvalidScid(scid));
283        }
284        if port_id > 0x07 {
285            return Err(BuildError::InvalidPortId(port_id));
286        }
287        if data_field_len > MAX_DATA_FIELD_LEN {
288            return Err(BuildError::DataTooLong(data_field_len));
289        }
290
291        let total_len = Self::HEADER_SIZE + data_field_len;
292        if buffer.len() < total_len {
293            return Err(BuildError::BufferTooSmall {
294                required: total_len,
295                provided: buffer.len(),
296            });
297        }
298
299        let frame_buf = &mut buffer[..total_len];
300        let frame = Self::mut_from_bytes(frame_buf).unwrap();
301
302        frame.header.set_version(PROXIMITY1_VERSION);
303        frame.header.set_qos(qos);
304        frame.header.set_pdu_type(pdu_type);
305        frame.header.set_dfc_id(dfc_id);
306        frame.header.set_scid(scid);
307        frame.header.set_pcid(pcid);
308        frame.header.set_port_id(port_id);
309        frame.header.set_src_dest(src_dest);
310        frame.header.set_frame_len(total_len);
311        frame.header.set_fsn(fsn);
312
313        Ok(frame)
314    }
315}
316
317// ── Header field accessors ──────────────────────────────────────
318
319impl Proximity1Header {
320    /// Returns the 2-bit Transfer Frame Version Number.
321    pub fn version(&self) -> u8 {
322        get_bits_u16(self.version_qos_pdu_dfc_scid, VERSION_MASK)
323            as u8
324    }
325    /// Sets the 2-bit Transfer Frame Version Number.
326    pub fn set_version(&mut self, v: u8) {
327        set_bits_u16(
328            &mut self.version_qos_pdu_dfc_scid,
329            VERSION_MASK,
330            v as u16,
331        );
332    }
333
334    /// Returns the Quality of Service indicator.
335    pub fn qos(&self) -> QoS {
336        if get_bits_u16(self.version_qos_pdu_dfc_scid, QOS_MASK) == 1
337        {
338            QoS::Expedited
339        } else {
340            QoS::SequenceControlled
341        }
342    }
343    /// Sets the Quality of Service indicator.
344    pub fn set_qos(&mut self, qos: QoS) {
345        set_bits_u16(
346            &mut self.version_qos_pdu_dfc_scid,
347            QOS_MASK,
348            qos as u16,
349        );
350    }
351
352    /// Returns the PDU Type ID.
353    pub fn pdu_type(&self) -> PduType {
354        if get_bits_u16(self.version_qos_pdu_dfc_scid, PDU_TYPE_MASK)
355            == 1
356        {
357            PduType::Supervisory
358        } else {
359            PduType::UserData
360        }
361    }
362    /// Sets the PDU Type ID.
363    pub fn set_pdu_type(&mut self, t: PduType) {
364        set_bits_u16(
365            &mut self.version_qos_pdu_dfc_scid,
366            PDU_TYPE_MASK,
367            t as u16,
368        );
369    }
370
371    /// Returns the 2-bit Data Field Construction ID.
372    pub fn dfc_id(&self) -> DfcId {
373        let v = get_bits_u16(
374            self.version_qos_pdu_dfc_scid,
375            DFC_ID_MASK,
376        );
377        match v {
378            0b00 => DfcId::Packets,
379            0b01 => DfcId::Segments,
380            0b10 => DfcId::Reserved,
381            _ => DfcId::UserDefined,
382        }
383    }
384    /// Sets the 2-bit Data Field Construction ID.
385    pub fn set_dfc_id(&mut self, dfc: DfcId) {
386        set_bits_u16(
387            &mut self.version_qos_pdu_dfc_scid,
388            DFC_ID_MASK,
389            dfc as u16,
390        );
391    }
392
393    /// Returns the 10-bit Spacecraft Identifier.
394    pub fn scid(&self) -> Scid {
395        Scid::new(get_bits_u16(self.version_qos_pdu_dfc_scid, SCID_MASK) as u32)
396    }
397    /// Sets the 10-bit Spacecraft Identifier.
398    pub fn set_scid(&mut self, scid: Scid) {
399        set_bits_u16(
400            &mut self.version_qos_pdu_dfc_scid,
401            SCID_MASK,
402            scid.get() as u16,
403        );
404    }
405
406    /// Returns the Physical Channel Identifier.
407    pub fn pcid(&self) -> bool {
408        get_bits_u16(self.pcid_port_srcdst_len, PCID_MASK) != 0
409    }
410    /// Sets the Physical Channel Identifier.
411    pub fn set_pcid(&mut self, pcid: bool) {
412        set_bits_u16(
413            &mut self.pcid_port_srcdst_len,
414            PCID_MASK,
415            u16::from(pcid),
416        );
417    }
418
419    /// Returns the 3-bit Port Identifier.
420    pub fn port_id(&self) -> u8 {
421        get_bits_u16(self.pcid_port_srcdst_len, PORT_ID_MASK) as u8
422    }
423    /// Sets the 3-bit Port Identifier.
424    pub fn set_port_id(&mut self, id: u8) {
425        set_bits_u16(
426            &mut self.pcid_port_srcdst_len,
427            PORT_ID_MASK,
428            id as u16,
429        );
430    }
431
432    /// Returns the Source-or-Destination Identifier.
433    pub fn src_dest(&self) -> SrcDest {
434        if get_bits_u16(self.pcid_port_srcdst_len, SRC_DEST_MASK) == 1
435        {
436            SrcDest::Destination
437        } else {
438            SrcDest::Source
439        }
440    }
441    /// Sets the Source-or-Destination Identifier.
442    pub fn set_src_dest(&mut self, sd: SrcDest) {
443        set_bits_u16(
444            &mut self.pcid_port_srcdst_len,
445            SRC_DEST_MASK,
446            sd as u16,
447        );
448    }
449
450    /// Returns the total frame length in bytes.
451    ///
452    /// The header stores `C = total_octets - 1`.
453    pub fn frame_len(&self) -> usize {
454        get_bits_u16(self.pcid_port_srcdst_len, FRAME_LEN_MASK)
455            as usize
456            + 1
457    }
458    /// Sets the frame length from total byte count.
459    pub fn set_frame_len(&mut self, len: usize) {
460        set_bits_u16(
461            &mut self.pcid_port_srcdst_len,
462            FRAME_LEN_MASK,
463            (len - 1) as u16,
464        );
465    }
466
467    /// Returns the 8-bit Frame Sequence Number.
468    pub fn fsn(&self) -> u8 {
469        self.fsn
470    }
471    /// Sets the 8-bit Frame Sequence Number.
472    pub fn set_fsn(&mut self, fsn: u8) {
473        self.fsn = fsn;
474    }
475}
476
477// ── Proximity Link Control Word (PLCW) ──────────────────────────
478//
479// The PLCW is a 16-bit fixed-length SPDU (Type F1) carried in the
480// data field of a P-frame. It reports the receiver's state back to
481// the sender for COP-P sequence control.
482//
483// Layout (Figure 3-5, CCSDS 211.0-B-6):
484//
485//   Bit 0:      SPDU Format ID       (1 = fixed-length)
486//   Bit 1:      SPDU Type Identifier (0 = PLCW)
487//   Bit 2:      Retransmit Flag
488//   Bit 3:      PCID
489//   Bit 4:      Reserved Spare       (always 0)
490//   Bits 5-7:   Expedited Frame Counter (mod-8)
491//   Bits 8-15:  Report Value V(R)
492
493/// Bitmasks for the 16-bit PLCW.
494#[rustfmt::skip]
495pub mod plcw_bitmask {
496    /// SPDU Format ID (bit 0) — always 1 for fixed-length.
497    pub const FORMAT_ID_MASK: u16     = 0b_1000_0000_0000_0000;
498    /// SPDU Type Identifier (bit 1) — 0 = PLCW.
499    pub const TYPE_ID_MASK: u16       = 0b_0100_0000_0000_0000;
500    /// Retransmit Flag (bit 2).
501    pub const RETRANSMIT_MASK: u16    = 0b_0010_0000_0000_0000;
502    /// PCID (bit 3).
503    pub const PLCW_PCID_MASK: u16     = 0b_0001_0000_0000_0000;
504    /// Reserved Spare (bit 4) — always 0.
505    pub const _RESERVED_MASK: u16     = 0b_0000_1000_0000_0000;
506    /// Expedited Frame Counter (bits 5-7), mod-8.
507    pub const EXP_COUNTER_MASK: u16   = 0b_0000_0111_0000_0000;
508    /// Report Value V(R) (bits 8-15).
509    pub const REPORT_VALUE_MASK: u16  = 0b_0000_0000_1111_1111;
510}
511
512/// A Proximity Link Control Word (PLCW).
513///
514/// This is a 16-bit fixed-length SPDU that reports the receiver's
515/// state (V(R), retransmit flag, expedited frame counter) back to
516/// the sender via a P-frame.
517#[derive(Debug, Copy, Clone, Eq, PartialEq)]
518pub struct Plcw(u16);
519
520impl Plcw {
521    /// Creates a new PLCW with the given fields.
522    pub fn new(
523        retransmit: bool,
524        pcid: bool,
525        expedited_counter: u8,
526        report_value: u8,
527    ) -> Self {
528        use plcw_bitmask::*;
529        let mut val = 0u16;
530        // Format ID = 1 (fixed-length SPDU)
531        val |= FORMAT_ID_MASK;
532        // Type ID = 0 (PLCW) — already 0
533        if retransmit {
534            val |= RETRANSMIT_MASK;
535        }
536        if pcid {
537            val |= PLCW_PCID_MASK;
538        }
539        val |= ((expedited_counter & 0x07) as u16)
540            << EXP_COUNTER_MASK.trailing_zeros();
541        val |= report_value as u16;
542        Self(val)
543    }
544
545    /// Parses a PLCW from a 2-byte big-endian slice.
546    pub fn from_bytes(bytes: &[u8; 2]) -> Self {
547        Self(u16::from_be_bytes(*bytes))
548    }
549
550    /// Encodes the PLCW as 2 big-endian bytes.
551    pub fn to_bytes(self) -> [u8; 2] {
552        self.0.to_be_bytes()
553    }
554
555    /// Returns the raw 16-bit value.
556    pub fn raw(self) -> u16 {
557        self.0
558    }
559
560    /// Returns true if the Retransmit Flag is set.
561    ///
562    /// When set, the sender should retransmit the expected frame.
563    pub fn retransmit(&self) -> bool {
564        self.0 & plcw_bitmask::RETRANSMIT_MASK != 0
565    }
566
567    /// Returns the PCID field.
568    pub fn pcid(&self) -> bool {
569        self.0 & plcw_bitmask::PLCW_PCID_MASK != 0
570    }
571
572    /// Returns the 3-bit Expedited Frame Counter (mod-8).
573    pub fn expedited_counter(&self) -> u8 {
574        ((self.0 & plcw_bitmask::EXP_COUNTER_MASK)
575            >> plcw_bitmask::EXP_COUNTER_MASK.trailing_zeros())
576            as u8
577    }
578
579    /// Returns the 8-bit Report Value V(R).
580    ///
581    /// This is the next expected sequence-controlled FSN.
582    pub fn report_value(&self) -> u8 {
583        (self.0 & plcw_bitmask::REPORT_VALUE_MASK) as u8
584    }
585}
586
587impl core::fmt::Display for Plcw {
588    fn fmt(
589        &self,
590        f: &mut core::fmt::Formatter<'_>,
591    ) -> core::fmt::Result {
592        write!(
593            f,
594            "PLCW[rt={} pcid={} exp={} vr={}]",
595            self.retransmit() as u8,
596            self.pcid() as u8,
597            self.expedited_counter(),
598            self.report_value(),
599        )
600    }
601}
602
603// ── Segment Header ──────────────────────────────────────────────
604//
605// When DFC ID = 01 (Segments), the data field starts with an 8-bit
606// segment header (§3.2.3.3):
607//
608//   Bits 0-1: Sequence Flags
609//   Bits 2-7: Pseudo Packet ID
610
611/// Bitmasks for the 8-bit segment header.
612#[rustfmt::skip]
613pub mod segment_bitmask {
614    /// 2-bit Sequence Flags (bits 0-1).
615    pub const FLAGS_MASK: u8           = 0b_1100_0000;
616    /// 6-bit Pseudo Packet ID (bits 2-7).
617    pub const PSEUDO_PACKET_ID_MASK: u8 = 0b_0011_1111;
618}
619
620/// Sequence flags for segmented data units.
621#[derive(Debug, Copy, Clone, Eq, PartialEq)]
622#[repr(u8)]
623pub enum SequenceFlag {
624    /// First segment of a packet (binary '01').
625    First = 0b01,
626    /// Continuation segment (binary '00').
627    Continuation = 0b00,
628    /// Last segment of a packet (binary '10').
629    Last = 0b10,
630    /// No segmentation — entire packet (binary '11').
631    Unsegmented = 0b11,
632}
633
634/// An 8-bit segment header prepended to the data when DFC ID = 01.
635#[derive(Debug, Copy, Clone, Eq, PartialEq)]
636pub struct SegmentHeader(u8);
637
638impl SegmentHeader {
639    /// Creates a new segment header.
640    pub fn new(flags: SequenceFlag, pseudo_packet_id: u8) -> Self {
641        let mut val = 0u8;
642        set_bits_u8(&mut val, segment_bitmask::FLAGS_MASK, flags as u8);
643        set_bits_u8(&mut val, segment_bitmask::PSEUDO_PACKET_ID_MASK, pseudo_packet_id);
644        Self(val)
645    }
646
647    /// Parses a segment header from a byte.
648    pub fn from_byte(b: u8) -> Self {
649        Self(b)
650    }
651
652    /// Returns the raw byte value.
653    pub fn to_byte(self) -> u8 {
654        self.0
655    }
656
657    /// Returns the sequence flags.
658    pub fn flags(&self) -> SequenceFlag {
659        match get_bits_u8(self.0, segment_bitmask::FLAGS_MASK) {
660            0b01 => SequenceFlag::First,
661            0b00 => SequenceFlag::Continuation,
662            0b10 => SequenceFlag::Last,
663            _ => SequenceFlag::Unsegmented,
664        }
665    }
666
667    /// Returns the 6-bit pseudo packet identifier.
668    pub fn pseudo_packet_id(&self) -> u8 {
669        get_bits_u8(self.0, segment_bitmask::PSEUDO_PACKET_ID_MASK)
670    }
671}
672
673// ── Display ─────────────────────────────────────────────────────
674
675impl core::fmt::Display for Proximity1Header {
676    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
677        write!(
678            f,
679            "Prox1[v={} qos={:?} pdu={:?} dfc={:?} scid={} \
680             pcid={} port={} sd={:?} len={} fsn={}]",
681            self.version(),
682            self.qos(),
683            self.pdu_type(),
684            self.dfc_id(),
685            self.scid(),
686            self.pcid() as u8,
687            self.port_id(),
688            self.src_dest(),
689            self.frame_len(),
690            self.fsn(),
691        )
692    }
693}
694
695impl core::fmt::Display for Proximity1TransferFrame {
696    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
697        write!(
698            f,
699            "{} data=[{}B]",
700            self.header,
701            self.data_field.len(),
702        )
703    }
704}
705
706// ── FrameWrite / FrameRead implementations ──
707
708use super::super::{FrameRead, FrameWrite, PushError};
709
710/// Configuration for building Proximity-1 transfer frames.
711#[derive(Debug, Clone)]
712pub struct Prox1FrameWriterConfig {
713    /// Spacecraft ID (10-bit).
714    pub scid: Scid,
715    /// Quality of Service indicator.
716    pub qos: QoS,
717    /// PDU type (user data or supervisory).
718    pub pdu_type: PduType,
719    /// Data field construction identifier.
720    pub dfc_id: DfcId,
721    /// Physical channel identifier.
722    pub pcid: bool,
723    /// Port identifier (3-bit).
724    pub port_id: u8,
725    /// Source-or-destination identifier.
726    pub src_dest: SrcDest,
727    /// Maximum data field length in bytes.
728    pub max_data_field_len: usize,
729}
730
731/// Accumulates packets into Proximity-1 transfer frames.
732///
733/// Owns its frame buffer internally (sized by `BUF`). Packets
734/// are pushed directly into the buffer at the correct offset.
735/// [`finish()`](FrameWrite::finish) stamps the header and
736/// returns a borrow of the completed frame.
737pub struct Prox1FrameWriter<const BUF: usize> {
738    config: Prox1FrameWriterConfig,
739    fsn: u8,
740    data_len: usize,
741    buf: [u8; BUF],
742}
743
744impl<const BUF: usize> Prox1FrameWriter<BUF> {
745    /// Creates a new Proximity-1 frame writer.
746    pub fn new(config: Prox1FrameWriterConfig) -> Self {
747        Self {
748            config,
749            fsn: 0,
750            data_len: 0,
751            buf: [0u8; BUF],
752        }
753    }
754}
755
756impl<const BUF: usize> Prox1FrameWriter<BUF> {
757    fn remaining(&self) -> usize {
758        self.config.max_data_field_len.saturating_sub(self.data_len)
759    }
760}
761
762impl<const BUF: usize> FrameWrite for Prox1FrameWriter<BUF> {
763    type Error = BuildError;
764
765    fn is_empty(&self) -> bool {
766        self.data_len == 0
767    }
768
769    fn push(&mut self, data: &[u8]) -> Result<(), PushError> {
770        if data.len() > self.config.max_data_field_len {
771            return Err(PushError::TooLarge);
772        }
773        if data.len() > self.remaining() {
774            return Err(PushError::Full);
775        }
776        let off =
777            Proximity1TransferFrame::HEADER_SIZE + self.data_len;
778        self.buf[off..off + data.len()].copy_from_slice(data);
779        self.data_len += data.len();
780        Ok(())
781    }
782
783    fn finish(&mut self) -> Result<&[u8], BuildError> {
784        let total =
785            Proximity1TransferFrame::HEADER_SIZE + self.data_len;
786        let fsn = self.fsn;
787        self.fsn = self.fsn.wrapping_add(1);
788
789        Proximity1TransferFrame::builder()
790            .buffer(&mut self.buf[..total])
791            .scid(self.config.scid)
792            .qos(self.config.qos)
793            .pdu_type(self.config.pdu_type)
794            .dfc_id(self.config.dfc_id)
795            .pcid(self.config.pcid)
796            .port_id(self.config.port_id)
797            .src_dest(self.config.src_dest)
798            .fsn(fsn)
799            .data_field_len(self.data_len)
800            .build()?;
801
802        self.data_len = 0;
803        Ok(&self.buf[..total])
804    }
805}
806
807/// Extracts packets from a received Proximity-1 transfer frame.
808///
809/// Owns its frame buffer internally (sized by `BUF`). The
810/// coding layer writes into
811/// [`buffer_mut()`](FrameRead::buffer_mut),
812/// [`feed()`](FrameRead::feed) validates the header, and
813/// [`next()`](FrameRead::next) returns zero-copy sub-slices.
814pub struct Prox1FrameReader<const BUF: usize> {
815    buf: [u8; BUF],
816    data_start: usize,
817    data_end: usize,
818}
819
820impl<const BUF: usize> Prox1FrameReader<BUF> {
821    /// Creates a new Proximity-1 frame reader.
822    pub fn new() -> Self {
823        Self {
824            buf: [0u8; BUF],
825            data_start: 0,
826            data_end: 0,
827        }
828    }
829}
830
831impl<const BUF: usize> FrameRead for Prox1FrameReader<BUF> {
832    type Error = ParseError;
833
834    fn buffer_mut(&mut self) -> &mut [u8] {
835        &mut self.buf
836    }
837
838    fn feed(&mut self, len: usize) -> Result<(), ParseError> {
839        let parsed =
840            Proximity1TransferFrame::parse(&self.buf[..len])?;
841        let data = parsed.data_field();
842        self.data_start = Proximity1TransferFrame::HEADER_SIZE;
843        self.data_end = self.data_start + data.len();
844        Ok(())
845    }
846
847    fn data_field(&self) -> &[u8] {
848        &self.buf[self.data_start..self.data_end]
849    }
850}
851
852#[cfg(test)]
853mod tests {
854    use super::*;
855    use crate::ids::Scid;
856
857    #[test]
858    fn build_and_parse_u_frame() {
859        let mut buf = [0u8; 256];
860        let payload = [0xDE, 0xAD, 0xBE, 0xEF];
861
862        let frame = Proximity1TransferFrame::builder()
863            .buffer(&mut buf)
864            .scid(Scid::new(42))
865            .qos(QoS::SequenceControlled)
866            .pdu_type(PduType::UserData)
867            .dfc_id(DfcId::Packets)
868            .pcid(false)
869            .port_id(3)
870            .src_dest(SrcDest::Source)
871            .fsn(0x7F)
872            .data_field_len(payload.len())
873            .build()
874            .unwrap();
875
876        frame.data_field_mut().copy_from_slice(&payload);
877
878        assert_eq!(frame.header().version(), PROXIMITY1_VERSION);
879        assert_eq!(frame.header().qos(), QoS::SequenceControlled);
880        assert_eq!(frame.header().pdu_type(), PduType::UserData);
881        assert_eq!(frame.header().dfc_id(), DfcId::Packets);
882        assert_eq!(frame.header().scid(), Scid::new(42));
883        assert!(!frame.header().pcid());
884        assert_eq!(frame.header().port_id(), 3);
885        assert_eq!(frame.header().src_dest(), SrcDest::Source);
886        assert_eq!(frame.header().frame_len(), 5 + payload.len());
887        assert_eq!(frame.header().fsn(), 0x7F);
888        assert_eq!(frame.data_field(), &payload);
889    }
890
891    #[test]
892    fn build_and_parse_p_frame() {
893        let mut buf = [0u8; 64];
894
895        let frame = Proximity1TransferFrame::builder()
896            .buffer(&mut buf)
897            .scid(Scid::new(1023))
898            .qos(QoS::Expedited)
899            .pdu_type(PduType::Supervisory)
900            .dfc_id(DfcId::Packets) // must be 00 for P-frames
901            .pcid(true)
902            .port_id(0) // must be 0 for P-frames
903            .src_dest(SrcDest::Destination)
904            .fsn(255)
905            .data_field_len(8)
906            .build()
907            .unwrap();
908
909        assert_eq!(frame.header().scid(), Scid::new(1023));
910        assert_eq!(frame.header().qos(), QoS::Expedited);
911        assert_eq!(frame.header().pdu_type(), PduType::Supervisory);
912        assert!(frame.header().pcid());
913        assert_eq!(frame.header().src_dest(), SrcDest::Destination);
914        assert_eq!(frame.header().fsn(), 255);
915    }
916
917    #[test]
918    fn parse_roundtrip() {
919        let mut buf = [0u8; 128];
920
921        let frame = Proximity1TransferFrame::builder()
922            .buffer(&mut buf)
923            .scid(Scid::new(500))
924            .qos(QoS::Expedited)
925            .pdu_type(PduType::UserData)
926            .dfc_id(DfcId::UserDefined)
927            .pcid(false)
928            .port_id(7)
929            .src_dest(SrcDest::Source)
930            .fsn(42)
931            .data_field_len(10)
932            .build()
933            .unwrap();
934
935        let total = frame.frame_len();
936
937        let parsed =
938            Proximity1TransferFrame::parse(&buf[..total]).unwrap();
939        assert_eq!(parsed.header().scid(), Scid::new(500));
940        assert_eq!(parsed.header().qos(), QoS::Expedited);
941        assert_eq!(parsed.header().dfc_id(), DfcId::UserDefined);
942        assert_eq!(parsed.header().port_id(), 7);
943        assert_eq!(parsed.header().fsn(), 42);
944        assert_eq!(parsed.data_field().len(), 10);
945    }
946
947    #[test]
948    fn invalid_scid_rejected() {
949        let mut buf = [0u8; 64];
950        let err = Proximity1TransferFrame::builder()
951            .buffer(&mut buf)
952            .scid(Scid::new(1024)) // exceeds 10-bit max
953            .qos(QoS::SequenceControlled)
954            .pdu_type(PduType::UserData)
955            .dfc_id(DfcId::Packets)
956            .pcid(false)
957            .port_id(0)
958            .src_dest(SrcDest::Source)
959            .fsn(0)
960            .data_field_len(1)
961            .build();
962        assert!(matches!(err, Err(BuildError::InvalidScid(s)) if s == Scid::new(1024)));
963    }
964
965    #[test]
966    fn invalid_port_id_rejected() {
967        let mut buf = [0u8; 64];
968        let err = Proximity1TransferFrame::builder()
969            .buffer(&mut buf)
970            .scid(Scid::new(0))
971            .qos(QoS::SequenceControlled)
972            .pdu_type(PduType::UserData)
973            .dfc_id(DfcId::Packets)
974            .pcid(false)
975            .port_id(8) // exceeds 3-bit max
976            .src_dest(SrcDest::Source)
977            .fsn(0)
978            .data_field_len(1)
979            .build();
980        assert!(matches!(err, Err(BuildError::InvalidPortId(8))));
981    }
982
983    #[test]
984    fn data_too_long_rejected() {
985        let mut buf = [0u8; 64];
986        let err = Proximity1TransferFrame::builder()
987            .buffer(&mut buf)
988            .scid(Scid::new(0))
989            .qos(QoS::SequenceControlled)
990            .pdu_type(PduType::UserData)
991            .dfc_id(DfcId::Packets)
992            .pcid(false)
993            .port_id(0)
994            .src_dest(SrcDest::Source)
995            .fsn(0)
996            .data_field_len(2044) // exceeds 2043
997            .build();
998        assert!(matches!(err, Err(BuildError::DataTooLong(2044))));
999    }
1000
1001    #[test]
1002    fn buffer_too_small_rejected() {
1003        let mut buf = [0u8; 4]; // less than header
1004        let err = Proximity1TransferFrame::builder()
1005            .buffer(&mut buf)
1006            .scid(Scid::new(0))
1007            .qos(QoS::SequenceControlled)
1008            .pdu_type(PduType::UserData)
1009            .dfc_id(DfcId::Packets)
1010            .pcid(false)
1011            .port_id(0)
1012            .src_dest(SrcDest::Source)
1013            .fsn(0)
1014            .data_field_len(1)
1015            .build();
1016        assert!(matches!(
1017            err,
1018            Err(BuildError::BufferTooSmall {
1019                required: 6,
1020                provided: 4,
1021            })
1022        ));
1023    }
1024
1025    #[test]
1026    fn parse_too_short() {
1027        let buf = [0u8; 3];
1028        let err = Proximity1TransferFrame::parse(&buf);
1029        assert!(matches!(
1030            err,
1031            Err(ParseError::TooShortForHeader { actual: 3 })
1032        ));
1033    }
1034
1035    #[test]
1036    fn parse_invalid_version() {
1037        let mut buf = [0u8; 16];
1038        // Manually set version to 0b00 (wrong) and length
1039        buf[0] = 0x00;
1040        buf[1] = 0x00;
1041        buf[2] = 0x00;
1042        buf[3] = 0x0F; // frame length C=15 → 16 bytes total
1043        let err = Proximity1TransferFrame::parse(&buf);
1044        assert!(matches!(
1045            err,
1046            Err(ParseError::InvalidVersion(0))
1047        ));
1048    }
1049
1050    #[test]
1051    fn max_frame_size() {
1052        let mut buf = [0u8; MAX_FRAME_LEN];
1053        let frame = Proximity1TransferFrame::builder()
1054            .buffer(&mut buf)
1055            .scid(Scid::new(0))
1056            .qos(QoS::Expedited)
1057            .pdu_type(PduType::UserData)
1058            .dfc_id(DfcId::UserDefined)
1059            .pcid(false)
1060            .port_id(0)
1061            .src_dest(SrcDest::Source)
1062            .fsn(0)
1063            .data_field_len(MAX_DATA_FIELD_LEN)
1064            .build()
1065            .unwrap();
1066
1067        assert_eq!(frame.frame_len(), MAX_FRAME_LEN);
1068        assert_eq!(frame.data_field().len(), MAX_DATA_FIELD_LEN);
1069    }
1070
1071    #[test]
1072    fn all_dfc_id_variants() {
1073        for (dfc, expected_raw) in [
1074            (DfcId::Packets, 0b00u8),
1075            (DfcId::Segments, 0b01),
1076            (DfcId::Reserved, 0b10),
1077            (DfcId::UserDefined, 0b11),
1078        ] {
1079            let mut buf = [0u8; 16];
1080            let frame = Proximity1TransferFrame::builder()
1081                .buffer(&mut buf)
1082                .scid(Scid::new(0))
1083                .qos(QoS::SequenceControlled)
1084                .pdu_type(PduType::UserData)
1085                .dfc_id(dfc)
1086                .pcid(false)
1087                .port_id(0)
1088                .src_dest(SrcDest::Source)
1089                .fsn(0)
1090                .data_field_len(1)
1091                .build()
1092                .unwrap();
1093
1094            assert_eq!(frame.header().dfc_id(), dfc);
1095            let raw = get_bits_u16(
1096                frame.header().version_qos_pdu_dfc_scid,
1097                DFC_ID_MASK,
1098            );
1099            assert_eq!(raw as u8, expected_raw);
1100        }
1101    }
1102
1103    #[test]
1104    fn display_format() {
1105        let mut buf = [0u8; 16];
1106        let frame = Proximity1TransferFrame::builder()
1107            .buffer(&mut buf)
1108            .scid(Scid::new(100))
1109            .qos(QoS::Expedited)
1110            .pdu_type(PduType::Supervisory)
1111            .dfc_id(DfcId::Packets)
1112            .pcid(true)
1113            .port_id(5)
1114            .src_dest(SrcDest::Destination)
1115            .fsn(77)
1116            .data_field_len(4)
1117            .build()
1118            .unwrap();
1119
1120        let mut out = [0u8; 128];
1121        let n = crate::fmt!(&mut out, "{}", frame).unwrap();
1122        let s = core::str::from_utf8(&out[..n]).unwrap();
1123        assert!(s.contains("scid=100"));
1124        assert!(s.contains("fsn=77"));
1125    }
1126
1127    // ── PLCW tests ──────────────────────────────────────────────
1128
1129    #[test]
1130    fn plcw_roundtrip() {
1131        let plcw = Plcw::new(true, false, 5, 0xAB);
1132        assert!(plcw.retransmit());
1133        assert!(!plcw.pcid());
1134        assert_eq!(plcw.expedited_counter(), 5);
1135        assert_eq!(plcw.report_value(), 0xAB);
1136
1137        let bytes = plcw.to_bytes();
1138        let parsed = Plcw::from_bytes(&bytes);
1139        assert_eq!(parsed, plcw);
1140    }
1141
1142    #[test]
1143    fn plcw_format_id_always_set() {
1144        let plcw = Plcw::new(false, false, 0, 0);
1145        // Bit 0 (MSB) should always be 1
1146        assert!(plcw.raw() & plcw_bitmask::FORMAT_ID_MASK != 0);
1147        // Type ID should be 0 for PLCW
1148        assert!(plcw.raw() & plcw_bitmask::TYPE_ID_MASK == 0);
1149    }
1150
1151    #[test]
1152    fn plcw_all_fields() {
1153        let plcw = Plcw::new(false, true, 7, 255);
1154        assert!(!plcw.retransmit());
1155        assert!(plcw.pcid());
1156        assert_eq!(plcw.expedited_counter(), 7);
1157        assert_eq!(plcw.report_value(), 255);
1158    }
1159
1160    #[test]
1161    fn plcw_display() {
1162        let plcw = Plcw::new(true, false, 3, 42);
1163        let mut out = [0u8; 64];
1164        let n = crate::fmt!(&mut out, "{}", plcw).unwrap();
1165        let s = core::str::from_utf8(&out[..n]).unwrap();
1166        assert!(s.contains("rt=1"));
1167        assert!(s.contains("vr=42"));
1168    }
1169
1170    // ── Segment Header tests ────────────────────────────────────
1171
1172    #[test]
1173    fn segment_header_roundtrip() {
1174        let hdr = SegmentHeader::new(SequenceFlag::First, 42);
1175        assert_eq!(hdr.flags(), SequenceFlag::First);
1176        assert_eq!(hdr.pseudo_packet_id(), 42);
1177
1178        let b = hdr.to_byte();
1179        let parsed = SegmentHeader::from_byte(b);
1180        assert_eq!(parsed, hdr);
1181    }
1182
1183    #[test]
1184    fn segment_header_all_flags() {
1185        for (flag, expected_bits) in [
1186            (SequenceFlag::First, 0b01),
1187            (SequenceFlag::Continuation, 0b00),
1188            (SequenceFlag::Last, 0b10),
1189            (SequenceFlag::Unsegmented, 0b11),
1190        ] {
1191            let hdr = SegmentHeader::new(flag, 0);
1192            assert_eq!(hdr.flags(), flag);
1193            assert_eq!(hdr.to_byte() >> 6, expected_bits);
1194        }
1195    }
1196
1197    #[test]
1198    fn segment_header_pseudo_id_masked() {
1199        // Only 6 bits should be kept
1200        let hdr = SegmentHeader::new(SequenceFlag::Unsegmented, 0xFF);
1201        assert_eq!(hdr.pseudo_packet_id(), 0x3F);
1202    }
1203}