Skip to main content

sidereon_core/rtcm/
framing.rs

1//! RTCM 3 transport framing: preamble sync, length, and CRC-24Q verification.
2//!
3//! Every RTCM 3 message is wrapped in the transport frame defined in Section 4:
4//!
5//! ```text
6//! +----------+------------------+------------------+-------------------+
7//! | 0xD3 (8) | reserved(6) len(10) | message body (len bytes) | CRC-24Q (24) |
8//! +----------+------------------+------------------+-------------------+
9//! ```
10//!
11//! The 8-bit preamble is `0xD3`, the six bits after it are reserved (zero), and
12//! the next ten bits give the body length in bytes (0..=1023). The trailing
13//! 24-bit CRC-24Q covers the preamble, the length word, and the body. The body
14//! itself is the input to [`crate::rtcm::Message::decode`].
15//!
16//! The scanner ([`FrameScanner`]) is forgiving in the sans-I/O sense: it slides
17//! over an arbitrary byte buffer, resynchronizes on the next `0xD3` whenever the
18//! length runs past the buffer or the CRC fails, and yields only frames whose
19//! CRC verifies. This is how a real receiver locks onto a noisy serial stream.
20
21use crate::error::{Error, Result};
22
23use super::crc::crc24q;
24
25/// The RTCM 3 frame preamble byte.
26pub const PREAMBLE: u8 = 0xD3;
27/// Maximum message-body length, in bytes (the length field is 10 bits).
28pub const MAX_BODY_LEN: usize = 0x3FF;
29/// Overhead a frame adds around its body: 3 header bytes plus 3 CRC bytes.
30pub const FRAME_OVERHEAD: usize = 6;
31
32/// A single decoded frame: the borrowed message body plus the full frame size.
33#[derive(Clone, Copy, Debug, PartialEq, Eq)]
34pub struct DecodedFrame<'a> {
35    /// The message body (the bytes between the length word and the CRC).
36    pub body: &'a [u8],
37    /// Total length of the frame in bytes, including preamble, length, and CRC.
38    pub frame_len: usize,
39}
40
41/// Wrap a message body in an RTCM 3 transport frame with a fresh CRC-24Q.
42///
43/// Returns [`Error::InvalidInput`] if the body exceeds [`MAX_BODY_LEN`].
44pub fn encode_frame(body: &[u8]) -> Result<Vec<u8>> {
45    if body.len() > MAX_BODY_LEN {
46        return Err(Error::InvalidInput(format!(
47            "RTCM body of {} bytes exceeds the 1023-byte frame limit",
48            body.len()
49        )));
50    }
51    let len = body.len() as u16;
52    let mut out = Vec::with_capacity(body.len() + FRAME_OVERHEAD);
53    out.push(PREAMBLE);
54    // Six reserved bits (zero) followed by the high two bits of the length.
55    out.push((len >> 8) as u8 & 0x03);
56    out.push((len & 0xFF) as u8);
57    out.extend_from_slice(body);
58    let crc = crc24q(&out);
59    out.push((crc >> 16) as u8);
60    out.push((crc >> 8) as u8);
61    out.push(crc as u8);
62    Ok(out)
63}
64
65/// Decode the single frame that begins at the start of `bytes`.
66///
67/// Verifies the preamble and the CRC-24Q. Returns [`Error::Parse`] if the
68/// preamble is missing, the buffer is shorter than the declared frame, or the
69/// CRC does not match.
70pub fn decode_frame(bytes: &[u8]) -> Result<DecodedFrame<'_>> {
71    if bytes.len() < FRAME_OVERHEAD {
72        return Err(Error::Parse(format!(
73            "RTCM frame too short: {} bytes, need at least {FRAME_OVERHEAD}",
74            bytes.len()
75        )));
76    }
77    if bytes[0] != PREAMBLE {
78        return Err(Error::Parse(format!(
79            "RTCM frame missing 0xD3 preamble (found {:#04x})",
80            bytes[0]
81        )));
82    }
83    let len = ((usize::from(bytes[1] & 0x03)) << 8) | usize::from(bytes[2]);
84    let total = 3 + len + 3;
85    if bytes.len() < total {
86        return Err(Error::Parse(format!(
87            "RTCM frame truncated: declares {len}-byte body needing {total} bytes, have {}",
88            bytes.len()
89        )));
90    }
91    let crc_computed = crc24q(&bytes[..3 + len]);
92    let crc_framed = (u32::from(bytes[3 + len]) << 16)
93        | (u32::from(bytes[3 + len + 1]) << 8)
94        | u32::from(bytes[3 + len + 2]);
95    if crc_computed != crc_framed {
96        return Err(Error::Parse(format!(
97            "RTCM CRC-24Q mismatch: computed {crc_computed:#08x}, frame carries {crc_framed:#08x}"
98        )));
99    }
100    Ok(DecodedFrame {
101        body: &bytes[3..3 + len],
102        frame_len: total,
103    })
104}
105
106/// A forgiving iterator that yields every CRC-valid frame in a byte buffer.
107///
108/// Bytes that are not a valid frame start (a stray `0xD3`, a truncated tail, or
109/// a body whose CRC fails) are skipped one at a time, exactly as a hardware
110/// receiver resynchronizes on a serial stream.
111pub struct FrameScanner<'a> {
112    bytes: &'a [u8],
113    pos: usize,
114}
115
116impl<'a> FrameScanner<'a> {
117    /// Begin scanning `bytes` from the start.
118    pub fn new(bytes: &'a [u8]) -> Self {
119        Self { bytes, pos: 0 }
120    }
121}
122
123impl<'a> Iterator for FrameScanner<'a> {
124    type Item = DecodedFrame<'a>;
125
126    fn next(&mut self) -> Option<Self::Item> {
127        while self.pos < self.bytes.len() {
128            if self.bytes[self.pos] != PREAMBLE {
129                self.pos += 1;
130                continue;
131            }
132            match decode_frame(&self.bytes[self.pos..]) {
133                Ok(frame) => {
134                    self.pos += frame.frame_len;
135                    return Some(frame);
136                }
137                Err(_) => {
138                    // Not a real frame here; slide past this 0xD3 and resync.
139                    self.pos += 1;
140                }
141            }
142        }
143        None
144    }
145}