1use crate::error::{SbfError, SbfResult};
8use crate::header::SbfHeader;
9
10use super::block_ids;
11use super::SbfBlockParse;
12
13#[derive(Debug, Clone)]
19pub struct Meas3RangesBlock {
20 tow_ms: u32,
21 wnc: u16,
22 pub common_flags: u8,
23 pub cum_clk_jumps: u8,
24 pub constellations: u16,
25 pub misc: u8,
26 pub reserved: u8,
27 pub data: Vec<u8>,
29}
30
31impl Meas3RangesBlock {
32 pub fn tow_seconds(&self) -> f64 {
33 self.tow_ms as f64 * 0.001
34 }
35
36 pub fn tow_ms(&self) -> u32 {
37 self.tow_ms
38 }
39
40 pub fn wnc(&self) -> u16 {
41 self.wnc
42 }
43
44 pub fn antenna_id(&self) -> u8 {
46 self.misc & 0x07
47 }
48
49 pub fn reference_epoch_interval_ms(&self) -> u32 {
50 match self.misc >> 4 {
51 0 => 1,
52 1 => 500,
53 2 => 1000,
54 3 => 2000,
55 4 => 5000,
56 5 => 10_000,
57 6 => 15_000,
58 7 => 30_000,
59 8 => 60_000,
60 9 => 120_000,
61 _ => 1,
62 }
63 }
64
65 pub fn is_reference_epoch(&self) -> bool {
66 self.tow_ms.checked_rem(self.reference_epoch_interval_ms()) == Some(0)
67 }
68
69 pub fn reference_epoch_contains_pr_rate(&self) -> bool {
70 (self.misc & 0x08) != 0
71 }
72
73 pub fn has_scrambled_measurements(&self) -> bool {
74 (self.common_flags & 0x80) != 0
75 }
76}
77
78impl SbfBlockParse for Meas3RangesBlock {
79 const BLOCK_ID: u16 = block_ids::MEAS3_RANGES;
80
81 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
82 let full_len = header.length as usize;
83 if data.len() < full_len.saturating_sub(2) {
84 return Err(SbfError::IncompleteBlock {
85 needed: full_len,
86 have: data.len() + 2,
87 });
88 }
89 if data.len() < 18 {
90 return Err(SbfError::ParseError("Meas3Ranges too short".into()));
91 }
92
93 let common_flags = data[12];
94 let cum_clk_jumps = data[13];
95 let constellations = u16::from_le_bytes([data[14], data[15]]);
96 let misc = data[16];
97 let reserved = data[17];
98 let payload = data[18..].to_vec();
99
100 Ok(Self {
101 tow_ms: header.tow_ms,
102 wnc: header.wnc,
103 common_flags,
104 cum_clk_jumps,
105 constellations,
106 misc,
107 reserved,
108 data: payload,
109 })
110 }
111}
112
113macro_rules! impl_meas3_ext {
118 (
119 $name:ident,
120 $block_id:expr,
121 $doc:literal
122 ) => {
123 #[doc = $doc]
124 #[derive(Debug, Clone)]
125 pub struct $name {
126 tow_ms: u32,
127 wnc: u16,
128 pub flags: u8,
129 pub data: Vec<u8>,
130 }
131
132 impl $name {
133 pub fn tow_seconds(&self) -> f64 {
134 self.tow_ms as f64 * 0.001
135 }
136
137 pub fn tow_ms(&self) -> u32 {
138 self.tow_ms
139 }
140
141 pub fn wnc(&self) -> u16 {
142 self.wnc
143 }
144
145 pub fn antenna_id(&self) -> u8 {
147 (self.flags & 0x07) as u8
148 }
149 }
150
151 impl SbfBlockParse for $name {
152 const BLOCK_ID: u16 = $block_id;
153
154 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
155 let full_len = header.length as usize;
156 if data.len() < full_len.saturating_sub(2) {
157 return Err(SbfError::IncompleteBlock {
158 needed: full_len,
159 have: data.len() + 2,
160 });
161 }
162 if data.len() < 13 {
163 return Err(SbfError::ParseError(
164 concat!(stringify!($name), " too short").into(),
165 ));
166 }
167
168 let flags = data[12];
169 let payload = data[13..].to_vec();
170
171 Ok(Self {
172 tow_ms: header.tow_ms,
173 wnc: header.wnc,
174 flags,
175 data: payload,
176 })
177 }
178 }
179 };
180}
181
182impl_meas3_ext!(
183 Meas3Cn0HiResBlock,
184 block_ids::MEAS3_CN0_HI_RES,
185 "Meas3CN0HiRes — fractional C/N0 extension (paired with Meas3Ranges)."
186);
187
188impl_meas3_ext!(
189 Meas3DopplerBlock,
190 block_ids::MEAS3_DOPPLER,
191 "Meas3Doppler — Doppler extension (paired with Meas3Ranges)."
192);
193
194impl_meas3_ext!(
195 Meas3PpBlock,
196 block_ids::MEAS3_PP,
197 "Meas3PP — post-processing flags extension (paired with Meas3Ranges)."
198);
199
200impl_meas3_ext!(
201 Meas3MpBlock,
202 block_ids::MEAS3_MP,
203 "Meas3MP — multipath correction extension (paired with Meas3Ranges)."
204);
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::blocks::SbfBlock;
210
211 fn build_block(block_id: u16, block_rev: u8, body: &[u8]) -> Vec<u8> {
213 let block_data_len = 12 + body.len();
214 let mut total_len = (2 + block_data_len) as u16;
215 while (total_len as usize & 0x03) != 0 {
216 total_len += 1;
217 }
218 let mut data = vec![0u8; total_len as usize];
219 data[0] = 0x24;
220 data[1] = 0x40;
221
222 let id_rev = block_id | ((block_rev as u16 & 0x07) << 13);
223 data[4..6].copy_from_slice(&id_rev.to_le_bytes());
224 data[6..8].copy_from_slice(&total_len.to_le_bytes());
225 data[8..12].copy_from_slice(&123_456u32.to_le_bytes());
226 data[12..14].copy_from_slice(&2150u16.to_le_bytes());
227 data[14..14 + body.len()].copy_from_slice(body);
228 data
229 }
230
231 #[test]
232 fn meas3_ranges_parses_header_and_payload() {
233 let body = [
235 0u8, 0, 0, 0, 0x03, 0xAA, 0x01, 0x02, 0x03, 0x04, ];
239 let raw = build_block(block_ids::MEAS3_RANGES, 0, &body);
240
241 let (block, consumed) = SbfBlock::parse(&raw).unwrap();
242 assert_eq!(consumed, raw.len());
243 match block {
244 SbfBlock::Meas3Ranges(m) => {
245 assert_eq!(m.tow_ms(), 123_456);
246 assert_eq!(m.wnc(), 2150);
247 assert_eq!(m.common_flags, 0);
248 assert_eq!(m.cum_clk_jumps, 0);
249 assert_eq!(m.constellations, 0);
250 assert_eq!(m.misc, 0x03);
251 assert_eq!(m.reserved, 0xAA);
252 assert_eq!(m.antenna_id(), 0x03);
253 assert_eq!(m.data, vec![0x01, 0x02, 0x03, 0x04]);
254 }
255 other => panic!("expected Meas3Ranges, got {:?}", other),
256 }
257 }
258
259 #[test]
260 fn meas3_cn0_hi_res_parses_flags_and_bytes() {
261 let mut ext = vec![0u8; 1 + 5];
263 ext[0] = 0x05;
264 ext[1..].copy_from_slice(&[0xAAu8, 10, 20, 30, 40]);
265 let raw = build_block(block_ids::MEAS3_CN0_HI_RES, 1, &ext);
266
267 let (block, _) = SbfBlock::parse(&raw).unwrap();
268 match block {
269 SbfBlock::Meas3Cn0HiRes(m) => {
270 assert_eq!(m.flags, 0x05);
271 assert_eq!(m.antenna_id(), 0x05);
272 assert_eq!(m.data, vec![0xAA, 10, 20, 30, 40]);
273 }
274 other => panic!("expected Meas3Cn0HiRes, got {:?}", other),
275 }
276 }
277
278 #[test]
279 fn meas3_doppler_pp_mp_parse() {
280 for (id, label) in [
281 (block_ids::MEAS3_DOPPLER, "doppler"),
282 (block_ids::MEAS3_PP, "pp"),
283 (block_ids::MEAS3_MP, "mp"),
284 ] {
285 let mut ext = vec![0u8; 1 + 5];
287 ext[0] = 0x02;
288 ext[1..].copy_from_slice(&[0xCCu8, 1, 2, 3, 4]);
289 let raw = build_block(id, 0, &ext);
290
291 let (block, _) = SbfBlock::parse(&raw).unwrap();
292 match (label, block) {
293 ("doppler", SbfBlock::Meas3Doppler(m)) => {
294 assert_eq!(m.antenna_id(), 2);
295 assert_eq!(m.data, vec![0xCC, 1, 2, 3, 4]);
296 }
297 ("pp", SbfBlock::Meas3Pp(m)) => {
298 assert_eq!(m.data, vec![0xCC, 1, 2, 3, 4]);
299 }
300 ("mp", SbfBlock::Meas3Mp(m)) => {
301 assert_eq!(m.data, vec![0xCC, 1, 2, 3, 4]);
302 }
303 (_, other) => panic!("{}: unexpected {:?}", label, other),
304 }
305 }
306 }
307}