1use std::collections::HashMap;
8
9use crate::error::{SbfError, SbfResult};
10use crate::types::{SatelliteId, SignalType};
11
12use super::{
13 Meas3Cn0HiResBlock, Meas3DopplerBlock, Meas3MpBlock, Meas3PpBlock, Meas3RangesBlock, SbfBlock,
14};
15
16const C84: f64 = 299_792_458.0;
17const E5_FREQ: f64 = 1_191.795e6;
18const E5A_FREQ: f64 = 1_176.45e6;
19const E5B_FREQ: f64 = 1_207.14e6;
20const E6_FREQ: f64 = 1_278.75e6;
21const L1_FREQ: f64 = 1_575.42e6;
22const L2_FREQ: f64 = 1_227.60e6;
23const L5_FREQ: f64 = 1_176.45e6;
24const E2_FREQ: f64 = 1_561.098e6;
25const L1_GLO_FREQ: f64 = 1_602.00e6;
26const L2_GLO_FREQ: f64 = 1_246.00e6;
27const L3_GLO_FREQ: f64 = 1_202.025e6;
28const B3_FREQ: f64 = 1_268.52e6;
29const S1_FREQ: f64 = 2_492.028e6;
30
31const L1_WAVELENGTH: f64 = C84 / L1_FREQ;
32const L2_WAVELENGTH: f64 = C84 / L2_FREQ;
33const L3_WAVELENGTH: f64 = C84 / L3_GLO_FREQ;
34const L5_WAVELENGTH: f64 = C84 / L5_FREQ;
35const E2_WAVELENGTH: f64 = C84 / E2_FREQ;
36const E5_WAVELENGTH: f64 = C84 / E5_FREQ;
37const E5A_WAVELENGTH: f64 = C84 / E5A_FREQ;
38const E5B_WAVELENGTH: f64 = C84 / E5B_FREQ;
39const E6_WAVELENGTH: f64 = C84 / E6_FREQ;
40const B3_WAVELENGTH: f64 = C84 / B3_FREQ;
41const S1_WAVELENGTH: f64 = C84 / S1_FREQ;
42
43const F64_NOTVALID: f64 = -2e10;
44const F32_NOTVALID: f32 = -2e10;
45
46const MEAS3_SIG_MAX: usize = 16;
47const MEAS3_SAT_MAX: usize = 64;
48const MAX_ANTENNAS: usize = 3;
49
50const MEASFLAG_SMOOTHING: u8 = 1 << 0;
51const MEASFLAG_HALFCYCLEAMBIGUITY: u8 = 1 << 2;
52const MEASFLAG_FIRSTMEAS: u8 = 1 << 3;
53const MEASFLAG_APMEINSYNC: u8 = 1 << 5;
54const MEASFLAG_VALIDITY: u8 = 1 << 7;
55
56const PR_BASE_M: [f64; 7] = [19e6, 19e6, 22e6, 20e6, 34e6, 34e6, 34e6];
57const LOCK_INDICATOR_TO_MS: [u32; 16] = [
58 0, 60_000, 30_000, 15_000, 10_000, 5_000, 2_000, 1_000, 500, 200, 100, 50, 40, 20, 10, 0,
59];
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
62enum Meas3SatSystem {
63 Gps = 0,
64 Glo = 1,
65 Gal = 2,
66 Bds = 3,
67 Sbas = 4,
68 Qzs = 5,
69 Irn = 6,
70}
71
72impl Meas3SatSystem {
73 fn from_index(index: usize) -> Option<Self> {
74 match index {
75 0 => Some(Self::Gps),
76 1 => Some(Self::Glo),
77 2 => Some(Self::Gal),
78 3 => Some(Self::Bds),
79 4 => Some(Self::Sbas),
80 5 => Some(Self::Qzs),
81 6 => Some(Self::Irn),
82 _ => None,
83 }
84 }
85
86 fn base_svid(self) -> u8 {
87 match self {
88 Self::Gps => 1,
89 Self::Glo => 38,
90 Self::Gal => 71,
91 Self::Bds => 141,
92 Self::Sbas => 120,
93 Self::Qzs => 181,
94 Self::Irn => 191,
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
100struct InternalMeasurement {
101 signal_index: u8,
102 signal_type: SignalType,
103 flags: u8,
104 pr_m: f64,
105 l_cycles: f64,
106 doppler_hz: f32,
107 cn0_dbhz: f32,
108 raw_cn0_dbhz: u8,
109 pll_timer_ms: u32,
110 mp_mm: i16,
111 carrier_mp_1_512c: i8,
112 smoothing_corr_mm: i16,
113 lock_count: u8,
114}
115
116impl Default for InternalMeasurement {
117 fn default() -> Self {
118 Self {
119 signal_index: 0,
120 signal_type: SignalType::Other(u8::MAX),
121 flags: 0,
122 pr_m: F64_NOTVALID,
123 l_cycles: F64_NOTVALID,
124 doppler_hz: F32_NOTVALID,
125 cn0_dbhz: F32_NOTVALID,
126 raw_cn0_dbhz: 0,
127 pll_timer_ms: 0,
128 mp_mm: 0,
129 carrier_mp_1_512c: 0,
130 smoothing_corr_mm: 0,
131 lock_count: 0,
132 }
133 }
134}
135
136impl InternalMeasurement {
137 fn into_public(self) -> Meas3Measurement {
138 Meas3Measurement {
139 signal_index: self.signal_index,
140 signal_type: self.signal_type,
141 flags: self.flags,
142 pseudorange_m: if self.flags & MEASFLAG_VALIDITY != 0 && self.pr_m != F64_NOTVALID {
143 Some(self.pr_m)
144 } else {
145 None
146 },
147 carrier_phase_cycles: if self.flags & MEASFLAG_VALIDITY != 0
148 && self.l_cycles != F64_NOTVALID
149 {
150 Some(self.l_cycles)
151 } else {
152 None
153 },
154 doppler_hz: if self.flags & MEASFLAG_VALIDITY != 0 && self.doppler_hz != F32_NOTVALID {
155 Some(self.doppler_hz)
156 } else {
157 None
158 },
159 cn0_dbhz: if self.flags & MEASFLAG_VALIDITY != 0 && self.cn0_dbhz != F32_NOTVALID {
160 Some(self.cn0_dbhz)
161 } else {
162 None
163 },
164 raw_cn0_dbhz: if self.flags & MEASFLAG_VALIDITY != 0 {
165 Some(self.raw_cn0_dbhz)
166 } else {
167 None
168 },
169 lock_time_ms: if self.flags & MEASFLAG_VALIDITY != 0 && self.pll_timer_ms != 0 {
170 Some(self.pll_timer_ms)
171 } else {
172 None
173 },
174 multipath_mm: self.mp_mm,
175 carrier_multipath_1_512c: self.carrier_mp_1_512c,
176 smoothing_correction_mm: self.smoothing_corr_mm,
177 lock_count: if self.lock_count == 0 {
178 None
179 } else {
180 Some(self.lock_count)
181 },
182 }
183 }
184}
185
186#[derive(Debug, Clone, Default)]
187struct ReferenceSatellite {
188 sig_order: Vec<u8>,
189 measurements: HashMap<u8, InternalMeasurement>,
190 slave_sig_mask: u32,
191 pr_rate_64mm_s: i16,
192}
193
194#[derive(Debug, Clone, Default)]
195struct ReferenceConstellation {
196 m3satdata_copy: Vec<u8>,
197 satellites: HashMap<u8, ReferenceSatellite>,
198}
199
200#[derive(Debug, Clone, Default)]
201struct AntennaReferenceEpoch {
202 tow_ms: Option<u32>,
203 constellations: HashMap<Meas3SatSystem, ReferenceConstellation>,
204}
205
206#[derive(Debug, Clone, Copy)]
207struct MasterDecodeContext<'a> {
208 signal_table: &'a [SignalType; MEAS3_SIG_MAX],
209 glonass_fn: i8,
210 short_pr_base_m: f64,
211 sig_idx_master_short: u8,
212 reference_sat: &'a ReferenceSatellite,
213 time_since_ref_epoch_ms: u32,
214 pr_rate_available: bool,
215}
216
217#[derive(Debug, Clone, Copy)]
218struct SlaveDecodeContext<'a> {
219 signal_table: &'a [SignalType; MEAS3_SIG_MAX],
220 sig_idx: u8,
221 glonass_fn: i8,
222 master: &'a InternalMeasurement,
223 master_sig_idx: u8,
224 master_ref: Option<&'a InternalMeasurement>,
225 slave_ref: Option<&'a InternalMeasurement>,
226}
227
228#[derive(Debug, Clone)]
230pub struct Meas3Measurement {
231 pub signal_index: u8,
232 pub signal_type: SignalType,
233 flags: u8,
234 pseudorange_m: Option<f64>,
235 carrier_phase_cycles: Option<f64>,
236 doppler_hz: Option<f32>,
237 cn0_dbhz: Option<f32>,
238 raw_cn0_dbhz: Option<u8>,
239 lock_time_ms: Option<u32>,
240 pub multipath_mm: i16,
241 pub carrier_multipath_1_512c: i8,
242 pub smoothing_correction_mm: i16,
243 pub lock_count: Option<u8>,
244}
245
246impl Meas3Measurement {
247 pub fn pseudorange_m(&self) -> Option<f64> {
248 self.pseudorange_m
249 }
250 pub fn carrier_phase_cycles(&self) -> Option<f64> {
251 self.carrier_phase_cycles
252 }
253 pub fn doppler_hz(&self) -> Option<f32> {
254 self.doppler_hz
255 }
256 pub fn cn0_dbhz(&self) -> Option<f32> {
257 self.cn0_dbhz
258 }
259 pub fn raw_cn0_dbhz(&self) -> Option<u8> {
260 self.raw_cn0_dbhz
261 }
262 pub fn lock_time_ms(&self) -> Option<u32> {
263 self.lock_time_ms
264 }
265 pub fn is_valid(&self) -> bool {
266 (self.flags & MEASFLAG_VALIDITY) != 0
267 }
268 pub fn smoothing_active(&self) -> bool {
269 (self.flags & MEASFLAG_SMOOTHING) != 0
270 }
271 pub fn has_half_cycle_ambiguity(&self) -> bool {
272 (self.flags & MEASFLAG_HALFCYCLEAMBIGUITY) != 0
273 }
274 pub fn is_first_measurement(&self) -> bool {
275 (self.flags & MEASFLAG_FIRSTMEAS) != 0
276 }
277 pub fn apme_in_sync(&self) -> bool {
278 (self.flags & MEASFLAG_APMEINSYNC) != 0
279 }
280 pub fn raw_pseudorange_m(&self) -> Option<f64> {
281 self.pseudorange_m.map(|value| {
282 value + self.multipath_mm as f64 * 0.001 + self.smoothing_correction_mm as f64 * 0.001
283 })
284 }
285 pub fn raw_carrier_phase_cycles(&self) -> Option<f64> {
286 self.carrier_phase_cycles
287 .map(|value| value + self.carrier_multipath_1_512c as f64 / 512.0)
288 }
289}
290
291#[derive(Debug, Clone)]
293pub struct Meas3Satellite {
294 pub sat_id: SatelliteId,
295 pub glonass_frequency_number: Option<i8>,
296 pub measurements: Vec<Meas3Measurement>,
297}
298
299#[derive(Debug, Clone)]
301pub struct Meas3DecodedEpoch {
302 tow_ms: u32,
303 wnc: u16,
304 pub antenna_id: u8,
305 pub common_flags: u8,
306 pub total_clock_jump_ms: i32,
307 pub reference_epoch_interval_ms: u32,
308 pub is_reference_epoch: bool,
309 pub reference_epoch_contains_pr_rate: bool,
310 pub satellites: Vec<Meas3Satellite>,
311}
312
313impl Meas3DecodedEpoch {
314 pub fn tow_seconds(&self) -> f64 {
315 self.tow_ms as f64 * 0.001
316 }
317 pub fn tow_ms(&self) -> u32 {
318 self.tow_ms
319 }
320 pub fn wnc(&self) -> u16 {
321 self.wnc
322 }
323 pub fn num_satellites(&self) -> usize {
324 self.satellites.len()
325 }
326 pub fn num_measurements(&self) -> usize {
327 self.satellites
328 .iter()
329 .map(|sat| sat.measurements.len())
330 .sum()
331 }
332}
333
334#[derive(Debug, Clone, Default)]
336pub struct Meas3BlockSet {
337 pub ranges: Option<Meas3RangesBlock>,
338 pub cn0_hi_res: Option<Meas3Cn0HiResBlock>,
339 pub doppler: Option<Meas3DopplerBlock>,
340 pub pp: Option<Meas3PpBlock>,
341 pub mp: Option<Meas3MpBlock>,
342}
343
344impl Meas3BlockSet {
345 pub fn tow_ms(&self) -> Option<u32> {
346 self.ranges.as_ref().map(Meas3RangesBlock::tow_ms)
347 }
348
349 pub fn wnc(&self) -> Option<u16> {
350 self.ranges.as_ref().map(Meas3RangesBlock::wnc)
351 }
352
353 pub fn antenna_id(&self) -> Option<u8> {
354 self.ranges.as_ref().map(Meas3RangesBlock::antenna_id)
355 }
356
357 pub fn insert_block(&mut self, block: &SbfBlock) -> bool {
358 match block {
359 SbfBlock::Meas3Ranges(value) => {
360 self.ranges = Some(value.clone());
361 true
362 }
363 SbfBlock::Meas3Cn0HiRes(value) => {
364 self.cn0_hi_res = Some(value.clone());
365 true
366 }
367 SbfBlock::Meas3Doppler(value) => {
368 self.doppler = Some(value.clone());
369 true
370 }
371 SbfBlock::Meas3Pp(value) => {
372 self.pp = Some(value.clone());
373 true
374 }
375 SbfBlock::Meas3Mp(value) => {
376 self.mp = Some(value.clone());
377 true
378 }
379 _ => false,
380 }
381 }
382
383 pub fn clear(&mut self) {
384 *self = Self::default();
385 }
386}
387
388#[derive(Debug, Clone, Default)]
390pub struct Meas3Decoder {
391 references: Vec<AntennaReferenceEpoch>,
392}
393
394impl Meas3Decoder {
395 pub fn new() -> Self {
396 Self {
397 references: vec![AntennaReferenceEpoch::default(); MAX_ANTENNAS],
398 }
399 }
400
401 pub fn decode_block_set(&mut self, set: &Meas3BlockSet) -> SbfResult<Meas3DecodedEpoch> {
402 let ranges = set
403 .ranges
404 .as_ref()
405 .ok_or_else(|| SbfError::ParseError("Meas3 block set is missing Meas3Ranges".into()))?;
406 self.decode(
407 ranges,
408 set.cn0_hi_res.as_ref(),
409 set.doppler.as_ref(),
410 set.pp.as_ref(),
411 set.mp.as_ref(),
412 )
413 }
414
415 pub fn decode(
416 &mut self,
417 ranges: &Meas3RangesBlock,
418 cn0_hi_res: Option<&Meas3Cn0HiResBlock>,
419 doppler: Option<&Meas3DopplerBlock>,
420 pp: Option<&Meas3PpBlock>,
421 mp: Option<&Meas3MpBlock>,
422 ) -> SbfResult<Meas3DecodedEpoch> {
423 if ranges.has_scrambled_measurements() {
424 return Err(SbfError::ParseError(
425 "Meas3 scrambling is not supported by this crate".into(),
426 ));
427 }
428 if ranges.reserved > 31 {
429 return Err(SbfError::ParseError(
430 "Meas3Ranges revision marker is not supported".into(),
431 ));
432 }
433
434 let antenna_id = ranges.antenna_id() as usize;
435 if antenna_id >= self.references.len() {
436 return Err(SbfError::ParseError(
437 "Meas3 antenna index out of range".into(),
438 ));
439 }
440
441 for block in [
442 cn0_hi_res.map(|b| (b.tow_ms(), b.wnc(), b.antenna_id(), "Meas3CN0HiRes")),
443 doppler.map(|b| (b.tow_ms(), b.wnc(), b.antenna_id(), "Meas3Doppler")),
444 pp.map(|b| (b.tow_ms(), b.wnc(), b.antenna_id(), "Meas3PP")),
445 mp.map(|b| (b.tow_ms(), b.wnc(), b.antenna_id(), "Meas3MP")),
446 ]
447 .into_iter()
448 .flatten()
449 {
450 if block.0 != ranges.tow_ms()
451 || block.1 != ranges.wnc()
452 || block.2 != ranges.antenna_id()
453 {
454 return Err(SbfError::ParseError(format!(
455 "{} does not match the Meas3Ranges epoch",
456 block.3
457 )));
458 }
459 }
460
461 let ref_interval_ms = ranges.reference_epoch_interval_ms();
462 let is_reference_epoch = ranges.is_reference_epoch();
463 let ref_epoch_contains_pr_rate = ranges.reference_epoch_contains_pr_rate();
464 let ant_ref = &mut self.references[antenna_id];
465
466 if is_reference_epoch {
467 *ant_ref = AntennaReferenceEpoch::default();
468 ant_ref.tow_ms = Some(ranges.tow_ms());
469 } else {
470 let required_ref_tow = (ranges.tow_ms() / ref_interval_ms) * ref_interval_ms;
471 if ant_ref.tow_ms != Some(required_ref_tow) {
472 return Err(SbfError::ParseError(
473 "Meas3 delta epoch received before its reference epoch".into(),
474 ));
475 }
476 }
477
478 let mut payload = ranges.data.as_slice();
479 let mut cn0_idx = 0usize;
480 let mut doppler_idx = 0usize;
481 let mut pp1_idx = 0usize;
482 let mut pp2_idx = 0usize;
483 let mut mp_idx = 0usize;
484 let mut satellites = Vec::new();
485
486 for index in 0..7 {
487 if (ranges.constellations & (1 << index)) == 0 {
488 continue;
489 }
490 let system = Meas3SatSystem::from_index(index).unwrap();
491 let (consumed, mut decoded) = Self::decode_constellation(
492 payload,
493 system,
494 ant_ref,
495 ranges.tow_ms(),
496 ref_interval_ms,
497 ref_epoch_contains_pr_rate,
498 cn0_hi_res,
499 &mut cn0_idx,
500 doppler,
501 &mut doppler_idx,
502 pp,
503 &mut pp1_idx,
504 &mut pp2_idx,
505 mp,
506 &mut mp_idx,
507 is_reference_epoch,
508 )?;
509 satellites.append(&mut decoded);
510 payload = &payload[consumed..];
511 }
512
513 let total_clock_jump_ms = if ranges.cum_clk_jumps >= 128 {
514 ranges.cum_clk_jumps as i32 - 256
515 } else {
516 ranges.cum_clk_jumps as i32
517 };
518
519 Ok(Meas3DecodedEpoch {
520 tow_ms: ranges.tow_ms(),
521 wnc: ranges.wnc(),
522 antenna_id: ranges.antenna_id(),
523 common_flags: ranges.common_flags,
524 total_clock_jump_ms,
525 reference_epoch_interval_ms: ref_interval_ms,
526 is_reference_epoch,
527 reference_epoch_contains_pr_rate: ref_epoch_contains_pr_rate,
528 satellites,
529 })
530 }
531
532 #[allow(clippy::too_many_arguments)]
533 fn decode_constellation(
534 buf: &[u8],
535 system: Meas3SatSystem,
536 antenna_ref: &mut AntennaReferenceEpoch,
537 tow_ms: u32,
538 ref_interval_ms: u32,
539 ref_epoch_contains_pr_rate: bool,
540 cn0_hi_res: Option<&Meas3Cn0HiResBlock>,
541 cn0_idx: &mut usize,
542 doppler: Option<&Meas3DopplerBlock>,
543 doppler_idx: &mut usize,
544 pp: Option<&Meas3PpBlock>,
545 pp1_idx: &mut usize,
546 pp2_idx: &mut usize,
547 mp: Option<&Meas3MpBlock>,
548 mp_idx: &mut usize,
549 is_reference_epoch: bool,
550 ) -> SbfResult<(usize, Vec<Meas3Satellite>)> {
551 if buf.is_empty() {
552 return Err(SbfError::ParseError(
553 "Meas3 constellation data is empty".into(),
554 ));
555 }
556
557 let start = buf;
558 let ref_const = antenna_ref.constellations.entry(system).or_default();
559 let sat_data_buf = if buf[0] == 0 {
560 if ref_const.m3satdata_copy.is_empty() {
561 return Err(SbfError::ParseError(
562 "Meas3 delta constellation is missing its reference layout".into(),
563 ));
564 }
565 ref_const.m3satdata_copy.as_slice()
566 } else {
567 buf
568 };
569
570 let mut n = 0usize;
571 let mut sat_mask = 0u64;
572 let mut nsats = 0usize;
573 let mut glo_fn_list = [0u8; 32];
574
575 let bf1 = sat_data_buf[0];
576 let mut nb = (bf1 & 0x07) as usize;
577 if nb == 7 {
578 nb = 8;
579 }
580 let sig_idx_master_short = (bf1 >> 3) & 0x0f;
581 let sig_excluded_present = (bf1 & 0x80) != 0;
582 n += 1;
583
584 if sat_data_buf.len() < n + nb {
585 return Err(SbfError::ParseError("Meas3 SatMask is truncated".into()));
586 }
587 for ii in 0..nb {
588 let byte = sat_data_buf[n + ii];
589 sat_mask |= (byte as u64) << (ii * 8);
590 nsats += bit_count(byte) as usize;
591 }
592 n += nb;
593
594 if system == Meas3SatSystem::Glo {
595 let len = nsats.div_ceil(2);
596 if sat_data_buf.len() < n + len {
597 return Err(SbfError::ParseError(
598 "Meas3 GLO frequency list is truncated".into(),
599 ));
600 }
601 glo_fn_list[..len].copy_from_slice(&sat_data_buf[n..n + len]);
602 n += len;
603 }
604
605 let bds_long_range = if system == Meas3SatSystem::Bds {
606 if sat_data_buf.len() < n + 2 {
607 return Err(SbfError::ParseError(
608 "Meas3 BDS long-range flags are truncated".into(),
609 ));
610 }
611 let value = u16::from_le_bytes(sat_data_buf[n..n + 2].try_into().unwrap());
612 n += 2;
613 value
614 } else {
615 0
616 };
617
618 let sig_excluded = if sig_excluded_present {
619 if sat_data_buf.len() <= n {
620 return Err(SbfError::ParseError(
621 "Meas3 SigExcluded is truncated".into(),
622 ));
623 }
624 let value = sat_data_buf[n];
625 n += 1;
626 value
627 } else {
628 0
629 };
630
631 let mut buf = if start[0] == 0 {
632 &start[1..]
633 } else {
634 &start[n..]
635 };
636
637 if is_reference_epoch {
638 ref_const.m3satdata_copy = start[..n].to_vec();
639 }
640
641 let signal_table = prepare_signal_table(system, sig_excluded);
642 let mut satellites = Vec::new();
643 let mut sat_count = 0usize;
644
645 for sat_idx in 0..MEAS3_SAT_MAX {
646 if sat_count >= nsats {
647 break;
648 }
649 if (sat_mask & (1u64 << sat_idx)) == 0 {
650 continue;
651 }
652
653 let glonass_fn = if system == Meas3SatSystem::Glo {
654 let nibble = (glo_fn_list[sat_count / 2] >> (4 * (sat_count % 2))) & 0x0f;
655 nibble as i8 - 8
656 } else {
657 0
658 };
659 let short_pr_base =
660 if system == Meas3SatSystem::Bds && (bds_long_range & (1 << sat_count)) != 0 {
661 34e6
662 } else {
663 PR_BASE_M[system as usize]
664 };
665
666 let reference_sat = ref_const.satellites.entry(sat_idx as u8).or_default();
667 let time_since_ref_epoch_ms = tow_ms % ref_interval_ms;
668 let (mut master, master_sig_idx, mut slave_sig_mask, pr_rate_64mm_s, master_size) =
669 decode_master(
670 buf,
671 MasterDecodeContext {
672 signal_table: &signal_table,
673 glonass_fn,
674 short_pr_base_m: short_pr_base,
675 sig_idx_master_short,
676 reference_sat,
677 time_since_ref_epoch_ms,
678 pr_rate_available: ref_epoch_contains_pr_rate,
679 },
680 )?;
681 buf = &buf[master_size..];
682
683 if is_reference_epoch {
684 reference_sat.sig_order.clear();
685 reference_sat.sig_order.push(master_sig_idx);
686 reference_sat.slave_sig_mask = slave_sig_mask;
687 reference_sat.pr_rate_64mm_s = pr_rate_64mm_s;
688 reference_sat
689 .measurements
690 .insert(master_sig_idx, master.clone());
691 }
692 if let Some(existing) = reference_sat.measurements.get_mut(&master_sig_idx) {
693 if master.pll_timer_ms > existing.pll_timer_ms {
694 existing.pll_timer_ms = master.pll_timer_ms;
695 }
696 }
697
698 let master_wavelength = wavelength_for_signal(master.signal_type, glonass_fn);
699 add_master_doppler(
700 &mut master,
701 doppler,
702 master_wavelength,
703 reference_sat.pr_rate_64mm_s,
704 doppler_idx,
705 );
706 add_pp_info(&mut master, pp, pp1_idx, pp2_idx);
707 add_mp_info(&mut master, mp, mp_idx);
708
709 let mut master_hi_res_adjust = 0.0f32;
710 if let Some(cn0) = cn0_hi_res {
711 master_hi_res_adjust = decode_cn0_hires(&cn0.data, cn0_idx)?;
712 }
713
714 let sat_id = SatelliteId::from_svid(system.base_svid() + sat_idx as u8)
715 .ok_or_else(|| SbfError::ParseError("Meas3 produced an invalid SVID".into()))?;
716
717 let mut current_measurements = Vec::new();
718 let mut slave_count = 0usize;
719 for sig_idx in 1..MEAS3_SIG_MAX {
720 if slave_sig_mask == 0 {
721 break;
722 }
723 if (slave_sig_mask & (1 << sig_idx)) == 0 {
724 continue;
725 }
726
727 let slave_uses_reference = buf
728 .first()
729 .is_some_and(|first| (first & 1) == 0 && (first & 3) != 0);
730 let (master_ref, slave_ref) = if slave_uses_reference {
731 let master_ref_idx = *reference_sat.sig_order.first().ok_or_else(|| {
732 SbfError::ParseError(
733 "Meas3 reference epoch is missing the master signal".into(),
734 )
735 })?;
736 let master_ref = reference_sat
737 .measurements
738 .get(&master_ref_idx)
739 .ok_or_else(|| {
740 SbfError::ParseError(
741 "Meas3 reference master measurement is missing".into(),
742 )
743 })?
744 .clone();
745 let slave_ref_idx =
746 *reference_sat
747 .sig_order
748 .get(slave_count + 1)
749 .ok_or_else(|| {
750 SbfError::ParseError(
751 "Meas3 reference slave measurement is missing".into(),
752 )
753 })?;
754 let slave_ref = reference_sat
755 .measurements
756 .get(&slave_ref_idx)
757 .ok_or_else(|| {
758 SbfError::ParseError(
759 "Meas3 reference slave measurement is missing".into(),
760 )
761 })?
762 .clone();
763 (Some(master_ref), Some(slave_ref))
764 } else {
765 (None, None)
766 };
767
768 let (mut slave, consumed) = decode_slave(
769 buf,
770 SlaveDecodeContext {
771 signal_table: &signal_table,
772 sig_idx: sig_idx as u8,
773 glonass_fn,
774 master: &master,
775 master_sig_idx,
776 master_ref: master_ref.as_ref(),
777 slave_ref: slave_ref.as_ref(),
778 },
779 )?;
780 buf = &buf[consumed..];
781
782 let slave_wavelength = wavelength_for_signal(slave.signal_type, glonass_fn);
783 add_slave_doppler(
784 &mut slave,
785 &master,
786 doppler,
787 master_wavelength,
788 slave_wavelength,
789 doppler_idx,
790 );
791 add_pp_info(&mut slave, pp, pp1_idx, pp2_idx);
792 add_mp_info(&mut slave, mp, mp_idx);
793
794 if is_reference_epoch {
795 if reference_sat.sig_order.len() <= slave_count + 1 {
796 reference_sat.sig_order.push(sig_idx as u8);
797 }
798 reference_sat
799 .measurements
800 .insert(sig_idx as u8, slave.clone());
801 }
802 if let Some(existing) = reference_sat.measurements.get_mut(&(sig_idx as u8)) {
803 if slave.pll_timer_ms > existing.pll_timer_ms {
804 existing.pll_timer_ms = slave.pll_timer_ms;
805 }
806 }
807
808 if cn0_hi_res.is_some() {
809 slave.cn0_dbhz +=
810 decode_cn0_hires(cn0_hi_res.unwrap().data.as_slice(), cn0_idx)?;
811 }
812
813 current_measurements.push(slave.into_public());
814 slave_count += 1;
815 slave_sig_mask ^= 1 << sig_idx;
816 }
817
818 master.cn0_dbhz += master_hi_res_adjust;
819 current_measurements.insert(0, master.into_public());
820
821 satellites.push(Meas3Satellite {
822 sat_id,
823 glonass_frequency_number: if system == Meas3SatSystem::Glo {
824 Some(glonass_fn)
825 } else {
826 None
827 },
828 measurements: current_measurements,
829 });
830
831 sat_count += 1;
832 }
833
834 let consumed = start.len() - buf.len();
835 Ok((consumed, satellites))
836 }
837}
838
839fn bit_count(value: u8) -> u32 {
840 value.count_ones()
841}
842
843fn lsb_pos(value: u32) -> u8 {
844 if value == 0 {
845 32
846 } else {
847 value.trailing_zeros() as u8
848 }
849}
850
851fn decode_cn0_hires(data: &[u8], idx: &mut usize) -> SbfResult<f32> {
852 let byte = *data
853 .get(*idx / 2)
854 .ok_or_else(|| SbfError::ParseError("Meas3CN0HiRes payload is truncated".into()))?;
855 let nibble = (byte >> ((*idx % 2) * 4)) & 0x0f;
856 *idx += 1;
857 Ok(nibble as f32 * 0.0625 - 0.5)
858}
859
860fn prepare_signal_table(system: Meas3SatSystem, sig_excluded: u8) -> [SignalType; MEAS3_SIG_MAX] {
861 let defaults = default_signal_table(system);
862 let mut result = [SignalType::Other(u8::MAX); MEAS3_SIG_MAX];
863 let mut positions = Vec::with_capacity(MEAS3_SIG_MAX);
864 for i in 0..MEAS3_SIG_MAX {
865 if i >= 8 || (sig_excluded & (1 << i)) == 0 {
866 positions.push(i);
867 }
868 }
869 for (dst, src) in positions.into_iter().enumerate().take(MEAS3_SIG_MAX) {
870 result[dst] = defaults[src];
871 }
872 result
873}
874
875fn default_signal_table(system: Meas3SatSystem) -> [SignalType; MEAS3_SIG_MAX] {
876 let invalid = SignalType::Other(u8::MAX);
877 match system {
878 Meas3SatSystem::Gps => [
879 SignalType::L1CA,
880 SignalType::L2C,
881 SignalType::L5,
882 SignalType::L1PY,
883 SignalType::L2P,
884 SignalType::L1C,
885 invalid,
886 invalid,
887 invalid,
888 invalid,
889 invalid,
890 invalid,
891 invalid,
892 invalid,
893 invalid,
894 invalid,
895 ],
896 Meas3SatSystem::Glo => [
897 SignalType::G1CA,
898 SignalType::G2CA,
899 SignalType::G1P,
900 SignalType::G2P,
901 SignalType::G3,
902 invalid,
903 invalid,
904 invalid,
905 invalid,
906 invalid,
907 invalid,
908 invalid,
909 invalid,
910 invalid,
911 invalid,
912 invalid,
913 ],
914 Meas3SatSystem::Gal => [
915 SignalType::E1,
916 SignalType::E5a,
917 SignalType::E5b,
918 SignalType::E6,
919 SignalType::E5AltBOC,
920 SignalType::Other(16),
921 invalid,
922 invalid,
923 invalid,
924 invalid,
925 invalid,
926 invalid,
927 invalid,
928 invalid,
929 invalid,
930 invalid,
931 ],
932 Meas3SatSystem::Bds => [
933 SignalType::B1I,
934 SignalType::B2I,
935 SignalType::B3I,
936 SignalType::B1C,
937 SignalType::B2a,
938 SignalType::B2b,
939 invalid,
940 invalid,
941 invalid,
942 invalid,
943 invalid,
944 invalid,
945 invalid,
946 invalid,
947 invalid,
948 invalid,
949 ],
950 Meas3SatSystem::Sbas => [
951 SignalType::SBASL1,
952 SignalType::SBASL5,
953 invalid,
954 invalid,
955 invalid,
956 invalid,
957 invalid,
958 invalid,
959 invalid,
960 invalid,
961 invalid,
962 invalid,
963 invalid,
964 invalid,
965 invalid,
966 invalid,
967 ],
968 Meas3SatSystem::Qzs => [
969 SignalType::QZSSL1CA,
970 SignalType::QZSSL2C,
971 SignalType::QZSSL5,
972 SignalType::QZSSL6,
973 SignalType::QZSSL1C,
974 SignalType::QZSSL1S,
975 SignalType::QZSSL5S,
976 SignalType::QZSSL1CB,
977 invalid,
978 invalid,
979 invalid,
980 invalid,
981 invalid,
982 invalid,
983 invalid,
984 invalid,
985 ],
986 Meas3SatSystem::Irn => [
987 SignalType::NavICL5,
988 SignalType::NavICL1,
989 SignalType::Other(36),
990 invalid,
991 invalid,
992 invalid,
993 invalid,
994 invalid,
995 invalid,
996 invalid,
997 invalid,
998 invalid,
999 invalid,
1000 invalid,
1001 invalid,
1002 invalid,
1003 ],
1004 }
1005}
1006
1007fn wavelength_for_signal(signal_type: SignalType, glonass_fn: i8) -> f64 {
1008 match signal_type {
1009 SignalType::L1CA
1010 | SignalType::L1PY
1011 | SignalType::L1C
1012 | SignalType::E1
1013 | SignalType::SBASL1
1014 | SignalType::QZSSL1CA
1015 | SignalType::QZSSL1CB
1016 | SignalType::QZSSL1C
1017 | SignalType::QZSSL1S
1018 | SignalType::B1C
1019 | SignalType::NavICL1 => L1_WAVELENGTH,
1020 SignalType::L2P | SignalType::L2C | SignalType::QZSSL2C => L2_WAVELENGTH,
1021 SignalType::E5AltBOC => E5_WAVELENGTH,
1022 SignalType::E5a | SignalType::Other(16) => E5A_WAVELENGTH,
1023 SignalType::E5b | SignalType::B2I | SignalType::B2b => E5B_WAVELENGTH,
1024 SignalType::E6 | SignalType::QZSSL6 => E6_WAVELENGTH,
1025 SignalType::L5
1026 | SignalType::SBASL5
1027 | SignalType::QZSSL5
1028 | SignalType::QZSSL5S
1029 | SignalType::NavICL5
1030 | SignalType::B2a => L5_WAVELENGTH,
1031 SignalType::B1I => E2_WAVELENGTH,
1032 SignalType::B3I => B3_WAVELENGTH,
1033 SignalType::G1CA | SignalType::G1P => {
1034 if (-7..=6).contains(&glonass_fn) {
1035 C84 / (L1_GLO_FREQ + glonass_fn as f64 * 562_500.0)
1036 } else {
1037 F64_NOTVALID
1038 }
1039 }
1040 SignalType::G2CA | SignalType::G2P => {
1041 if (-7..=6).contains(&glonass_fn) {
1042 C84 / (L2_GLO_FREQ + glonass_fn as f64 * 437_500.0)
1043 } else {
1044 F64_NOTVALID
1045 }
1046 }
1047 SignalType::G3 => L3_WAVELENGTH,
1048 SignalType::Other(36) => S1_WAVELENGTH,
1049 SignalType::LBand => L1_WAVELENGTH,
1050 SignalType::L2PY | SignalType::Other(_) => F64_NOTVALID,
1051 }
1052}
1053
1054fn is_gps_p_code(signal_type: SignalType) -> bool {
1055 matches!(signal_type, SignalType::L1PY | SignalType::L2P)
1056}
1057
1058fn read_u16(bytes: &[u8], offset: usize) -> SbfResult<u16> {
1059 let slice = bytes
1060 .get(offset..offset + 2)
1061 .ok_or_else(|| SbfError::ParseError("Meas3 payload is truncated".into()))?;
1062 Ok(u16::from_le_bytes(slice.try_into().unwrap()))
1063}
1064
1065fn read_u32(bytes: &[u8], offset: usize) -> SbfResult<u32> {
1066 let slice = bytes
1067 .get(offset..offset + 4)
1068 .ok_or_else(|| SbfError::ParseError("Meas3 payload is truncated".into()))?;
1069 Ok(u32::from_le_bytes(slice.try_into().unwrap()))
1070}
1071
1072fn read_i16(bytes: &[u8], offset: usize) -> SbfResult<i16> {
1073 let slice = bytes
1074 .get(offset..offset + 2)
1075 .ok_or_else(|| SbfError::ParseError("Meas3 payload is truncated".into()))?;
1076 Ok(i16::from_le_bytes(slice.try_into().unwrap()))
1077}
1078
1079fn read_u8(bytes: &[u8], offset: usize) -> SbfResult<u8> {
1080 bytes
1081 .get(offset)
1082 .copied()
1083 .ok_or_else(|| SbfError::ParseError("Meas3 payload is truncated".into()))
1084}
1085
1086fn decode_master(
1087 buf: &[u8],
1088 ctx: MasterDecodeContext<'_>,
1089) -> SbfResult<(InternalMeasurement, u8, u32, i16, usize)> {
1090 let MasterDecodeContext {
1091 signal_table,
1092 glonass_fn,
1093 short_pr_base_m,
1094 sig_idx_master_short,
1095 reference_sat,
1096 time_since_ref_epoch_ms,
1097 pr_rate_available,
1098 } = ctx;
1099
1100 if buf.is_empty() {
1101 return Err(SbfError::ParseError(
1102 "Meas3 master sub-block is missing".into(),
1103 ));
1104 }
1105
1106 let mut meas = InternalMeasurement::default();
1107
1108 if (buf[0] & 1) == 1 {
1109 if buf.len() < if pr_rate_available { 10 } else { 8 } {
1110 return Err(SbfError::ParseError(
1111 "Meas3 master-short is truncated".into(),
1112 ));
1113 }
1114 let bf1 = read_u32(buf, 0)?;
1115 let pr_lsb = read_u32(buf, 4)?;
1116 let cmc = (bf1 >> 1) & 0x3ffff;
1117 let pr_msb = (bf1 >> 19) & 1;
1118 let lti = ((bf1 >> 20) & 0x7) as usize;
1119 let cn0 = (bf1 >> 23) & 0x1f;
1120 let sig_list = (bf1 >> 28) & 0x0f;
1121 let master_sig_idx = sig_idx_master_short;
1122 let signal_type = signal_table[master_sig_idx as usize];
1123 let wavelength = wavelength_for_signal(signal_type, glonass_fn);
1124
1125 meas.signal_index = master_sig_idx;
1126 meas.signal_type = signal_type;
1127 meas.flags = MEASFLAG_VALIDITY;
1128 meas.pr_m = short_pr_base_m + (pr_lsb as f64 + 4_294_967_296.0 * pr_msb as f64) * 0.001;
1129 meas.l_cycles = if cmc == 0 {
1130 F64_NOTVALID
1131 } else {
1132 meas.pr_m / wavelength - 131.072 + cmc as f64 * 0.001
1133 };
1134 meas.cn0_dbhz = cn0 as f32 + 24.0;
1135 meas.pll_timer_ms = LOCK_INDICATOR_TO_MS[lti];
1136 if lti == 0 {
1137 meas.flags |= MEASFLAG_HALFCYCLEAMBIGUITY;
1138 }
1139
1140 let pr_rate_64mm_s = if pr_rate_available {
1141 read_i16(buf, 8)?
1142 } else {
1143 0
1144 };
1145 let slave_sig_mask = sig_list << (master_sig_idx + 1);
1146 Ok((
1147 meas,
1148 master_sig_idx,
1149 slave_sig_mask,
1150 pr_rate_64mm_s,
1151 if pr_rate_available { 10 } else { 8 },
1152 ))
1153 } else if (buf[0] & 3) == 0 {
1154 if buf.len() < 10 {
1155 return Err(SbfError::ParseError(
1156 "Meas3 master-long is truncated".into(),
1157 ));
1158 }
1159 let bf1 = read_u32(buf, 0)?;
1160 let pr_lsb = read_u32(buf, 4)?;
1161 let bf2 = read_u16(buf, 8)?;
1162 let pr_msb = (bf1 >> 2) & 0x0f;
1163 let cmc = (bf1 >> 6) & 0x3fffff;
1164 let lti = ((bf1 >> 28) & 0x0f) as usize;
1165 let cn0 = (bf2 & 0x3f) as u32;
1166 let mut sig_mask = ((bf2 >> 6) & 0x1ff) as u32;
1167 let cont = ((bf2 >> 15) & 1) != 0;
1168 let pr_rate_offset = 10 + cont as usize;
1169 let consumed = if pr_rate_available {
1170 pr_rate_offset + 2
1171 } else {
1172 pr_rate_offset
1173 };
1174 if buf.len() < consumed {
1175 return Err(SbfError::ParseError(
1176 "Meas3 master-long is truncated".into(),
1177 ));
1178 }
1179 if cont {
1180 let bf3 = read_u8(buf, 10)?;
1181 sig_mask |= ((bf3 & 0x7f) as u32) << 9;
1182 }
1183 if sig_mask == 0 {
1184 return Err(SbfError::ParseError(
1185 "Meas3 master-long has an empty signal mask".into(),
1186 ));
1187 }
1188 let master_sig_idx = lsb_pos(sig_mask);
1189 let signal_type = signal_table[master_sig_idx as usize];
1190 let wavelength = wavelength_for_signal(signal_type, glonass_fn);
1191
1192 meas.signal_index = master_sig_idx;
1193 meas.signal_type = signal_type;
1194 meas.flags = MEASFLAG_VALIDITY;
1195 meas.pr_m = (pr_lsb as f64 + 4_294_967_296.0 * pr_msb as f64) * 0.001;
1196 meas.l_cycles = if cmc == 0 {
1197 F64_NOTVALID
1198 } else {
1199 meas.pr_m / wavelength - 2_097.152 + cmc as f64 * 0.001
1200 };
1201 meas.cn0_dbhz = if is_gps_p_code(signal_type) {
1202 cn0 as f32
1203 } else {
1204 cn0 as f32 + 10.0
1205 };
1206 meas.pll_timer_ms = LOCK_INDICATOR_TO_MS[lti];
1207 if lti == 0 {
1208 meas.flags |= MEASFLAG_HALFCYCLEAMBIGUITY;
1209 }
1210
1211 let pr_rate_64mm_s = if pr_rate_available {
1212 read_i16(buf, pr_rate_offset)?
1213 } else {
1214 0
1215 };
1216 let slave_sig_mask = sig_mask ^ (1 << master_sig_idx);
1217 Ok((
1218 meas,
1219 master_sig_idx,
1220 slave_sig_mask,
1221 pr_rate_64mm_s,
1222 consumed,
1223 ))
1224 } else if (buf[0] & 0x0c) == 0x0c {
1225 if buf.len() < 5 {
1226 return Err(SbfError::ParseError(
1227 "Meas3 master delta-long is truncated".into(),
1228 ));
1229 }
1230 let bf1 = read_u8(buf, 0)?;
1231 let bf2 = read_u32(buf, 1)?;
1232 let pr = (((bf1 >> 4) as u32) << 13) | (bf2 & 0x1fff);
1233 let cn0 = (bf2 >> 13) & 0x07;
1234 let cmc = bf2 >> 16;
1235 let master_sig_idx = *reference_sat.sig_order.first().ok_or_else(|| {
1236 SbfError::ParseError("Meas3 master delta-long is missing reference data".into())
1237 })?;
1238 let master_ref = reference_sat
1239 .measurements
1240 .get(&master_sig_idx)
1241 .ok_or_else(|| {
1242 SbfError::ParseError("Meas3 master delta-long is missing reference data".into())
1243 })?;
1244 let signal_type = signal_table[master_sig_idx as usize];
1245 let wavelength = wavelength_for_signal(signal_type, glonass_fn);
1246
1247 meas.signal_index = master_sig_idx;
1248 meas.signal_type = signal_type;
1249 meas.flags = master_ref.flags & (MEASFLAG_VALIDITY | MEASFLAG_HALFCYCLEAMBIGUITY);
1250 meas.pll_timer_ms = master_ref.pll_timer_ms;
1251 meas.pr_m = master_ref.pr_m
1252 + ((reference_sat.pr_rate_64mm_s as i64 * 64 * time_since_ref_epoch_ms as i64 / 1000)
1253 as f64)
1254 * 0.001
1255 + pr as f64 * 0.001
1256 - 65.536;
1257 meas.l_cycles = if cmc == 0 {
1258 F64_NOTVALID
1259 } else {
1260 (meas.pr_m - master_ref.pr_m) / wavelength + master_ref.l_cycles - 32.768
1261 + cmc as f64 * 0.001
1262 };
1263 meas.cn0_dbhz = master_ref.cn0_dbhz - 4.0 + cn0 as f32;
1264 Ok((meas, master_sig_idx, reference_sat.slave_sig_mask, 0, 5))
1265 } else {
1266 if buf.len() < 4 {
1267 return Err(SbfError::ParseError(
1268 "Meas3 master delta-short is truncated".into(),
1269 ));
1270 }
1271 let bf1 = read_u32(buf, 0)?;
1272 let pr = (bf1 >> 4) & 0x3fff;
1273 let cmc = (bf1 >> 18) & 0x3fff;
1274 let cn0 = (bf1 >> 2) & 0x03;
1275 let master_sig_idx = *reference_sat.sig_order.first().ok_or_else(|| {
1276 SbfError::ParseError("Meas3 master delta-short is missing reference data".into())
1277 })?;
1278 let master_ref = reference_sat
1279 .measurements
1280 .get(&master_sig_idx)
1281 .ok_or_else(|| {
1282 SbfError::ParseError("Meas3 master delta-short is missing reference data".into())
1283 })?;
1284 let signal_type = signal_table[master_sig_idx as usize];
1285 let wavelength = wavelength_for_signal(signal_type, glonass_fn);
1286
1287 meas.signal_index = master_sig_idx;
1288 meas.signal_type = signal_type;
1289 meas.flags = master_ref.flags & (MEASFLAG_VALIDITY | MEASFLAG_HALFCYCLEAMBIGUITY);
1290 meas.pll_timer_ms = master_ref.pll_timer_ms;
1291 meas.pr_m = master_ref.pr_m
1292 + ((reference_sat.pr_rate_64mm_s as i64 * 64 * time_since_ref_epoch_ms as i64 / 1000)
1293 as f64)
1294 * 0.001
1295 + pr as f64 * 0.001
1296 - 8.192;
1297 meas.l_cycles = if cmc == 0 {
1298 F64_NOTVALID
1299 } else {
1300 (meas.pr_m - master_ref.pr_m) / wavelength + master_ref.l_cycles - 8.192
1301 + cmc as f64 * 0.001
1302 };
1303 meas.cn0_dbhz = master_ref.cn0_dbhz - 1.0 + cn0 as f32;
1304 Ok((meas, master_sig_idx, reference_sat.slave_sig_mask, 0, 4))
1305 }
1306}
1307
1308fn decode_slave(
1309 buf: &[u8],
1310 ctx: SlaveDecodeContext<'_>,
1311) -> SbfResult<(InternalMeasurement, usize)> {
1312 let SlaveDecodeContext {
1313 signal_table,
1314 sig_idx,
1315 glonass_fn,
1316 master,
1317 master_sig_idx,
1318 master_ref,
1319 slave_ref,
1320 } = ctx;
1321
1322 if buf.is_empty() {
1323 return Err(SbfError::ParseError(
1324 "Meas3 slave sub-block is missing".into(),
1325 ));
1326 }
1327
1328 let mut meas = InternalMeasurement::default();
1329 let wavelength_master =
1330 wavelength_for_signal(signal_table[master_sig_idx as usize], glonass_fn);
1331 let signal_type = signal_table[sig_idx as usize];
1332 let wavelength_slave = wavelength_for_signal(signal_type, glonass_fn);
1333
1334 meas.flags = MEASFLAG_VALIDITY;
1335 meas.signal_index = sig_idx;
1336 meas.signal_type = signal_type;
1337
1338 if (buf[0] & 1) == 1 {
1339 if buf.len() < 5 {
1340 return Err(SbfError::ParseError(
1341 "Meas3 slave-short is truncated".into(),
1342 ));
1343 }
1344 let bf1 = read_u32(buf, 0)?;
1345 let bf2 = read_u8(buf, 4)?;
1346 let cmc_res = (bf1 >> 1) & 0xffff;
1347 let pr_rel = bf1 >> 17;
1348 let lti = (bf2 & 0x07) as usize;
1349 let cn0 = (bf2 >> 3) as u32;
1350
1351 if wavelength_master < wavelength_slave {
1352 meas.pr_m = master.pr_m + pr_rel as f64 * 0.001 - 10.0;
1353 } else {
1354 meas.pr_m = master.pr_m - pr_rel as f64 * 0.001 + 10.0;
1355 }
1356
1357 meas.l_cycles = if cmc_res == 0 {
1358 F64_NOTVALID
1359 } else {
1360 meas.pr_m / wavelength_slave
1361 + (master.l_cycles - master.pr_m / wavelength_master) * wavelength_slave
1362 / wavelength_master
1363 - 32.768
1364 + cmc_res as f64 * 0.001
1365 };
1366
1367 meas.cn0_dbhz = if is_gps_p_code(signal_type) {
1368 master.cn0_dbhz - 3.0 - cn0 as f32
1369 } else {
1370 cn0 as f32 + 24.0
1371 };
1372 meas.pll_timer_ms = LOCK_INDICATOR_TO_MS[lti];
1373 if lti == 0 {
1374 meas.flags |= MEASFLAG_HALFCYCLEAMBIGUITY;
1375 }
1376 Ok((meas, 5))
1377 } else if (buf[0] & 3) == 0 {
1378 if buf.len() < 7 {
1379 return Err(SbfError::ParseError("Meas3 slave-long is truncated".into()));
1380 }
1381 let bf1 = read_u32(buf, 0)?;
1382 let pr_lsb_rel = read_u16(buf, 4)?;
1383 let bf3 = read_u8(buf, 6)?;
1384 let cmc = (bf1 >> 2) & 0x3fffff;
1385 let lti = ((bf1 >> 24) & 0x0f) as usize;
1386 let pr_msb_rel = (bf1 >> 28) & 0x07;
1387 let cn0 = (bf3 & 0x3f) as u32;
1388
1389 meas.pr_m =
1390 master.pr_m + (pr_msb_rel * 65_536 + pr_lsb_rel as u32) as f64 * 0.001 - 262.144;
1391 meas.l_cycles = if cmc == 0 {
1392 F64_NOTVALID
1393 } else {
1394 meas.pr_m / wavelength_slave - 2_097.152 + cmc as f64 * 0.001
1395 };
1396 meas.cn0_dbhz = if is_gps_p_code(signal_type) {
1397 cn0 as f32
1398 } else {
1399 cn0 as f32 + 10.0
1400 };
1401 meas.pll_timer_ms = LOCK_INDICATOR_TO_MS[lti];
1402 if lti == 0 {
1403 meas.flags |= MEASFLAG_HALFCYCLEAMBIGUITY;
1404 }
1405 Ok((meas, 7))
1406 } else {
1407 if buf.len() < 3 {
1408 return Err(SbfError::ParseError(
1409 "Meas3 slave delta is truncated".into(),
1410 ));
1411 }
1412 let master_ref = master_ref.ok_or_else(|| {
1413 SbfError::ParseError("Meas3 slave delta is missing reference data".into())
1414 })?;
1415 let slave_ref = slave_ref.ok_or_else(|| {
1416 SbfError::ParseError("Meas3 slave delta is missing reference data".into())
1417 })?;
1418 let bf1 = read_u16(buf, 0)?;
1419 let d_carrier = read_u8(buf, 2)?;
1420 let d_pr = ((bf1 >> 2) & 0x0fff) as u32;
1421 let cn0 = (bf1 >> 14) as u32;
1422
1423 meas.flags = slave_ref.flags & (MEASFLAG_VALIDITY | MEASFLAG_HALFCYCLEAMBIGUITY);
1424 meas.l_cycles = slave_ref.l_cycles
1425 + (master.l_cycles - master_ref.l_cycles) * wavelength_master / wavelength_slave
1426 - 0.128
1427 + d_carrier as f64 * 0.001;
1428 meas.pr_m = slave_ref.pr_m + (meas.l_cycles - slave_ref.l_cycles) * wavelength_slave
1429 - 2.048
1430 + d_pr as f64 * 0.001;
1431 meas.cn0_dbhz = slave_ref.cn0_dbhz - 2.0 + cn0 as f32;
1432 meas.pll_timer_ms = slave_ref.pll_timer_ms;
1433 Ok((meas, 3))
1434 }
1435}
1436
1437fn get_pr_rate_mm_s(
1438 doppler: Option<&Meas3DopplerBlock>,
1439 idx: &mut usize,
1440) -> SbfResult<Option<i32>> {
1441 let Some(doppler) = doppler else {
1442 return Ok(None);
1443 };
1444
1445 let value = read_u32(doppler.data.as_slice(), *idx)?;
1446 let (abs_prr_mm_s, consumed, magnitude_bits) = if (value & 2) == 0 {
1447 (((value & 0xff) >> 2) as i32, 1usize, 6u8)
1448 } else if (value & 6) == 2 {
1449 (((value & 0xffff) >> 3) as i32, 2usize, 13u8)
1450 } else if (value & 0x0e) == 6 {
1451 (((value & 0x00ff_ffff) >> 4) as i32, 3usize, 20u8)
1452 } else {
1453 ((value >> 4) as i32, 4usize, 28u8)
1454 };
1455 *idx += consumed;
1456
1457 let negative = (value & 1) != 0;
1458 let invalid_abs = (1i32 << magnitude_bits) - 1;
1460 if negative && abs_prr_mm_s == invalid_abs {
1461 return Ok(None);
1462 }
1463
1464 Ok(Some(if negative {
1465 -abs_prr_mm_s
1466 } else {
1467 abs_prr_mm_s
1468 }))
1469}
1470
1471fn add_master_doppler(
1472 meas: &mut InternalMeasurement,
1473 doppler: Option<&Meas3DopplerBlock>,
1474 wavelength_m: f64,
1475 ref_pr_rate_64mm_s: i16,
1476 idx: &mut usize,
1477) {
1478 let Ok(Some(prr_mm_s)) = get_pr_rate_mm_s(doppler, idx) else {
1479 meas.doppler_hz = F32_NOTVALID;
1480 return;
1481 };
1482 meas.doppler_hz =
1483 -((prr_mm_s + ref_pr_rate_64mm_s as i32 * 64) as f64 * 0.001 / wavelength_m) as f32;
1484}
1485
1486fn add_slave_doppler(
1487 meas: &mut InternalMeasurement,
1488 master: &InternalMeasurement,
1489 doppler: Option<&Meas3DopplerBlock>,
1490 wavelength_master_m: f64,
1491 wavelength_slave_m: f64,
1492 idx: &mut usize,
1493) {
1494 let Ok(Some(prr_mm_s)) = get_pr_rate_mm_s(doppler, idx) else {
1495 meas.doppler_hz = F32_NOTVALID;
1496 return;
1497 };
1498 meas.doppler_hz = ((master.doppler_hz as f64 * wavelength_master_m * 1000.0 - prr_mm_s as f64)
1499 * 0.001
1500 / wavelength_slave_m) as f32;
1501}
1502
1503fn add_pp_info(
1504 meas: &mut InternalMeasurement,
1505 pp: Option<&Meas3PpBlock>,
1506 pp1_idx: &mut usize,
1507 pp2_idx: &mut usize,
1508) {
1509 meas.raw_cn0_dbhz = ((meas.cn0_dbhz as i32 / 2) * 2).max(0) as u8;
1510
1511 let Some(pp) = pp else {
1512 return;
1513 };
1514 let Some((section2_offset_units, payload)) = pp.data.split_first() else {
1518 return;
1519 };
1520
1521 let Some(first_lo) = payload.get(*pp1_idx / 8) else {
1522 return;
1523 };
1524 let Some(first_hi) = payload.get(*pp1_idx / 8 + 1) else {
1525 return;
1526 };
1527 let dummy = *first_lo as u16 | (*first_hi as u16) << 8;
1528 let val = dummy >> (*pp1_idx % 8);
1529 let start2 = (*section2_offset_units as usize) * 4;
1530
1531 if (val & 1) != 0 {
1532 meas.flags |= MEASFLAG_APMEINSYNC;
1533 }
1534 meas.lock_count = ((val >> 1) & 0x0f) as u8;
1535 *pp1_idx += 5;
1536
1537 if start2 != 0 && payload.get(start2).map(|value| value & 0x0f) == Some(0) {
1538 if let (Some(lo), Some(hi)) = (
1539 payload.get(*pp2_idx / 8 + start2 + 2),
1540 payload.get(*pp2_idx / 8 + start2 + 3),
1541 ) {
1542 let dummy = *lo as u16 | (*hi as u16) << 8;
1543 let val = dummy >> (*pp2_idx % 8);
1544 if (val & 1) == 0 {
1545 *pp2_idx += 1;
1546 } else {
1547 meas.raw_cn0_dbhz = (((val >> 1) & 0x1f) * 2) as u8;
1548 if !is_gps_p_code(meas.signal_type) {
1549 meas.raw_cn0_dbhz = meas.raw_cn0_dbhz.saturating_add(10);
1550 }
1551 *pp2_idx += 6;
1552 }
1553 }
1554 }
1555}
1556
1557fn add_mp_info(meas: &mut InternalMeasurement, mp: Option<&Meas3MpBlock>, idx: &mut usize) {
1558 let Some(mp) = mp else {
1559 meas.mp_mm = 0;
1560 meas.carrier_mp_1_512c = 0;
1561 return;
1562 };
1563 let Some(base) = mp.data.get(*idx / 8) else {
1564 meas.mp_mm = 0;
1565 meas.carrier_mp_1_512c = 0;
1566 return;
1567 };
1568 let value = *base as u32
1569 | (mp.data.get(*idx / 8 + 1).copied().unwrap_or(0) as u32) << 8
1570 | (mp.data.get(*idx / 8 + 2).copied().unwrap_or(0) as u32) << 16
1571 | (mp.data.get(*idx / 8 + 3).copied().unwrap_or(0) as u32) << 24;
1572 let value = value >> (*idx % 8);
1573 match value & 3 {
1574 0 => {
1575 meas.mp_mm = 0;
1576 meas.carrier_mp_1_512c = 0;
1577 *idx += 2;
1578 }
1579 1 | 2 => {
1580 let code = ((value >> 2) & 0x7f) as i16 * 10;
1581 let carrier = ((value >> 9) & 0x1f) as i16;
1582 meas.mp_mm = if (value & 3) == 1 { code } else { -code };
1583 meas.carrier_mp_1_512c = if carrier < 16 {
1584 carrier as i8
1585 } else {
1586 (carrier - 32) as i8
1587 };
1588 *idx += 14;
1589 }
1590 _ => {
1591 let code = ((value >> 2) & 0x7ff) as i16;
1592 let carrier = ((value >> 13) & 0xff) as i16;
1593 meas.mp_mm = if code < 1024 {
1594 code * 10
1595 } else {
1596 (code - 2048) * 10
1597 };
1598 meas.carrier_mp_1_512c = if carrier < 128 {
1599 carrier as i8
1600 } else {
1601 (carrier - 256) as i8
1602 };
1603 *idx += 21;
1604 }
1605 }
1606}
1607
1608#[cfg(test)]
1609mod tests {
1610 use super::*;
1611 use crate::blocks::block_ids;
1612
1613 fn build_sbf_block(block_id: u16, body: &[u8]) -> Vec<u8> {
1614 let block_data_len = 12 + body.len();
1615 let mut total_len = (2 + block_data_len) as u16;
1616 while (total_len as usize & 0x03) != 0 {
1617 total_len += 1;
1618 }
1619 let mut data = vec![0u8; total_len as usize];
1620 data[0] = 0x24;
1621 data[1] = 0x40;
1622 data[4..6].copy_from_slice(&block_id.to_le_bytes());
1623 data[6..8].copy_from_slice(&total_len.to_le_bytes());
1624 data[8..12].copy_from_slice(&1000u32.to_le_bytes());
1625 data[12..14].copy_from_slice(&2200u16.to_le_bytes());
1626 data[14..14 + body.len()].copy_from_slice(body);
1627 data
1628 }
1629
1630 fn parse_doppler_block(payload: &[u8]) -> Meas3DopplerBlock {
1631 let mut body = vec![0u8; 1];
1632 body.extend_from_slice(payload);
1633 let raw = build_sbf_block(block_ids::MEAS3_DOPPLER, &body);
1634 let (block, _) = SbfBlock::parse(&raw).unwrap();
1635 let SbfBlock::Meas3Doppler(doppler) = block else {
1636 panic!("expected Meas3Doppler");
1637 };
1638 doppler
1639 }
1640
1641 fn encode_pr_rate(abs_prr_mm_s: u32, width_bytes: usize, negative: bool) -> Vec<u8> {
1642 let sign = u32::from(negative);
1643 let value = match width_bytes {
1644 1 => (abs_prr_mm_s << 2) | sign,
1645 2 => (abs_prr_mm_s << 3) | 0b010 | sign,
1646 3 => (abs_prr_mm_s << 4) | 0b0110 | sign,
1647 4 => (abs_prr_mm_s << 4) | 0b1110 | sign,
1648 _ => panic!("unsupported Meas3Doppler width"),
1649 };
1650 value.to_le_bytes()[..width_bytes].to_vec()
1651 }
1652
1653 #[test]
1654 fn meas3_decoder_decodes_single_reference_measurement() {
1655 let constellation_header = [0x01u8, 0x01];
1656 let bf1 = 1u32 | (1000u32 << 1) | (1u32 << 20) | (10u32 << 23);
1657 let mut master = Vec::new();
1658 master.extend_from_slice(&bf1.to_le_bytes());
1659 master.extend_from_slice(&10_000u32.to_le_bytes());
1660
1661 let mut body = vec![0u8; 6];
1662 body[2..4].copy_from_slice(&(1u16).to_le_bytes());
1663 body.extend_from_slice(&constellation_header);
1664 body.extend_from_slice(&master);
1665
1666 let raw = build_sbf_block(block_ids::MEAS3_RANGES, &body);
1667 let (block, _) = SbfBlock::parse(&raw).unwrap();
1668 let SbfBlock::Meas3Ranges(ranges) = block else {
1669 panic!("expected Meas3Ranges");
1670 };
1671
1672 let mut decoder = Meas3Decoder::new();
1673 let epoch = decoder.decode(&ranges, None, None, None, None).unwrap();
1674 assert_eq!(epoch.antenna_id, 0);
1675 assert!(epoch.is_reference_epoch);
1676 assert_eq!(epoch.num_satellites(), 1);
1677 assert_eq!(epoch.num_measurements(), 1);
1678 let sat = &epoch.satellites[0];
1679 assert_eq!(sat.sat_id.to_string(), "G01");
1680 let meas = &sat.measurements[0];
1681 assert_eq!(meas.signal_type, SignalType::L1CA);
1682 assert!((meas.cn0_dbhz().unwrap() - 34.0).abs() < 1e-6);
1683 assert_eq!(meas.lock_time_ms(), Some(60_000));
1684 assert!(meas.pseudorange_m().unwrap() > 19_000_000.0);
1685 }
1686
1687 #[test]
1688 fn meas3_decoder_decodes_reference_epoch_with_slave_signal() {
1689 let constellation_header = [0x01u8, 0x01];
1690 let master_bf1 = 1u32 | (1000u32 << 1) | (1u32 << 20) | (10u32 << 23) | (1u32 << 28);
1691 let mut master = Vec::new();
1692 master.extend_from_slice(&master_bf1.to_le_bytes());
1693 master.extend_from_slice(&10_000u32.to_le_bytes());
1694
1695 let slave_bf1 = 1u32 | (1000u32 << 1);
1696 let slave_bf2 = 1u8 | (10u8 << 3);
1697 let mut slave = Vec::new();
1698 slave.extend_from_slice(&slave_bf1.to_le_bytes());
1699 slave.push(slave_bf2);
1700
1701 let mut body = vec![0u8; 6];
1702 body[2..4].copy_from_slice(&(1u16).to_le_bytes());
1703 body.extend_from_slice(&constellation_header);
1704 body.extend_from_slice(&master);
1705 body.extend_from_slice(&slave);
1706
1707 let raw = build_sbf_block(block_ids::MEAS3_RANGES, &body);
1708 let (block, _) = SbfBlock::parse(&raw).unwrap();
1709 let SbfBlock::Meas3Ranges(ranges) = block else {
1710 panic!("expected Meas3Ranges");
1711 };
1712
1713 let mut decoder = Meas3Decoder::new();
1714 let epoch = decoder.decode(&ranges, None, None, None, None).unwrap();
1715 assert!(epoch.is_reference_epoch);
1716 assert_eq!(epoch.num_satellites(), 1);
1717 assert_eq!(epoch.num_measurements(), 2);
1718
1719 let sat = &epoch.satellites[0];
1720 assert_eq!(sat.measurements.len(), 2);
1721 assert_eq!(sat.measurements[0].signal_type, SignalType::L1CA);
1722 assert_eq!(sat.measurements[1].signal_type, SignalType::L2C);
1723 assert!(sat.measurements[1].pseudorange_m().is_some());
1724 }
1725
1726 #[test]
1727 fn decode_master_long_reads_pr_rate_after_continuation_byte() {
1728 let signal_table = prepare_signal_table(Meas3SatSystem::Gps, 0);
1729 let reference_sat = ReferenceSatellite::default();
1730 let bf1 = (1u32 << 6) | (1u32 << 28);
1731 let pr_rate_64mm_s = 0x1234i16;
1732
1733 let mut buf = Vec::new();
1734 buf.extend_from_slice(&bf1.to_le_bytes());
1735 buf.extend_from_slice(&10_000u32.to_le_bytes());
1736 buf.extend_from_slice(&((1u16 << 6) | (20u16) | (1u16 << 15)).to_le_bytes());
1737 buf.push(0x55);
1738 buf.extend_from_slice(&pr_rate_64mm_s.to_le_bytes());
1739
1740 let (_, _, _, decoded_pr_rate, consumed) = decode_master(
1741 &buf,
1742 MasterDecodeContext {
1743 signal_table: &signal_table,
1744 glonass_fn: 0,
1745 short_pr_base_m: PR_BASE_M[Meas3SatSystem::Gps as usize],
1746 sig_idx_master_short: 0,
1747 reference_sat: &reference_sat,
1748 time_since_ref_epoch_ms: 0,
1749 pr_rate_available: true,
1750 },
1751 )
1752 .unwrap();
1753
1754 assert_eq!(decoded_pr_rate, pr_rate_64mm_s);
1755 assert_eq!(consumed, 13);
1756 }
1757
1758 #[test]
1759 fn decode_master_long_with_cont_requires_full_pr_rate_field() {
1760 let signal_table = prepare_signal_table(Meas3SatSystem::Gps, 0);
1761 let reference_sat = ReferenceSatellite::default();
1762 let bf1 = (1u32 << 6) | (1u32 << 28);
1763
1764 let mut buf = Vec::new();
1765 buf.extend_from_slice(&bf1.to_le_bytes());
1766 buf.extend_from_slice(&10_000u32.to_le_bytes());
1767 buf.extend_from_slice(&((1u16 << 6) | (20u16) | (1u16 << 15)).to_le_bytes());
1768 buf.push(0x55);
1769 buf.push(0x34);
1770
1771 let err = decode_master(
1772 &buf,
1773 MasterDecodeContext {
1774 signal_table: &signal_table,
1775 glonass_fn: 0,
1776 short_pr_base_m: PR_BASE_M[Meas3SatSystem::Gps as usize],
1777 sig_idx_master_short: 0,
1778 reference_sat: &reference_sat,
1779 time_since_ref_epoch_ms: 0,
1780 pr_rate_available: true,
1781 },
1782 )
1783 .unwrap_err();
1784
1785 assert!(err.to_string().contains("Meas3 master-long is truncated"));
1786 }
1787
1788 #[test]
1789 fn master_doppler_marks_three_byte_dnu_as_invalid() {
1790 let doppler = parse_doppler_block(&encode_pr_rate((1 << 20) - 1, 3, true));
1791 let mut meas = InternalMeasurement::default();
1792 let mut idx = 0usize;
1793
1794 add_master_doppler(&mut meas, Some(&doppler), L1_WAVELENGTH, 0, &mut idx);
1795
1796 assert_eq!(idx, 3);
1797 assert_eq!(meas.doppler_hz, F32_NOTVALID);
1798 }
1799
1800 #[test]
1801 fn slave_doppler_marks_four_byte_dnu_as_invalid() {
1802 let doppler = parse_doppler_block(&encode_pr_rate((1 << 28) - 1, 4, true));
1803 let master = InternalMeasurement {
1804 doppler_hz: 125.0,
1805 ..Default::default()
1806 };
1807 let mut meas = InternalMeasurement::default();
1808 let mut idx = 0usize;
1809
1810 add_slave_doppler(
1811 &mut meas,
1812 &master,
1813 Some(&doppler),
1814 L1_WAVELENGTH,
1815 L2_WAVELENGTH,
1816 &mut idx,
1817 );
1818
1819 assert_eq!(idx, 4);
1820 assert_eq!(meas.doppler_hz, F32_NOTVALID);
1821 }
1822
1823 #[test]
1824 fn meas3_block_set_collects_blocks() {
1825 let mut set = Meas3BlockSet::default();
1826 let body = [0u8; 6];
1827 let raw = build_sbf_block(block_ids::MEAS3_RANGES, &body);
1828 let (block, _) = SbfBlock::parse(&raw).unwrap();
1829 assert!(set.insert_block(&block));
1830 assert_eq!(set.tow_ms(), Some(1000));
1831 }
1832
1833 #[test]
1834 fn meas3_pp_keeps_section2_offset_in_payload() {
1835 let body = [
1836 0x05u8, 0x01, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00,
1842 ];
1843 let raw = build_sbf_block(block_ids::MEAS3_PP, &body);
1844 let (block, _) = SbfBlock::parse(&raw).unwrap();
1845 let SbfBlock::Meas3Pp(pp) = block else {
1846 panic!("expected Meas3Pp");
1847 };
1848
1849 let mut meas = InternalMeasurement {
1850 cn0_dbhz: 34.0,
1851 signal_type: SignalType::L1CA,
1852 ..Default::default()
1853 };
1854 let mut pp1_idx = 0usize;
1855 let mut pp2_idx = 0usize;
1856
1857 add_pp_info(&mut meas, Some(&pp), &mut pp1_idx, &mut pp2_idx);
1858
1859 assert_eq!(pp.flags, 0x05);
1860 assert_eq!(pp.antenna_id(), 0x05);
1861 assert_eq!(meas.lock_count, 9);
1862 assert_eq!(meas.raw_cn0_dbhz, 42);
1863 assert_ne!(meas.flags & MEASFLAG_APMEINSYNC, 0);
1864 assert_eq!(pp1_idx, 5);
1865 assert_eq!(pp2_idx, 6);
1866 }
1867}