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}