Skip to main content

sbf_tools/blocks/
meas3_decoder.rs

1//! High-level Meas3 decoding.
2//!
3//! Septentrio does not publish the packed Meas3 payload layout in the public reference guide.
4//! The decoder in this module follows the `sbf2asc` / `sbfread_meas.c` sources shipped with
5//! RxTools.
6
7use 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/// A single decoded measurement from a Meas3 epoch.
229#[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/// All decoded measurements for one satellite in a Meas3 epoch.
292#[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/// One decoded Meas3 epoch for a single antenna.
300#[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/// One same-epoch bundle of Meas3 blocks for a single antenna.
335#[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/// Stateful Meas3 decoder.
389#[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    // Meas3Doppler uses a width-dependent negative all-ones magnitude as the DNU marker.
1459    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    // The first PP payload byte is the section-2 offset in 4-byte units. Older
1515    // parsing accidentally folded it into `flags`; keep the decoder aligned to
1516    // the wire format by splitting it out explicitly.
1517    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, // Flags
1837            0x01,   // Section-2 offset in 4-byte units
1838            0x13,   // APMEInSync=1, LockCount=9
1839            0x00, 0x00, 0x00, 0x00, // start2 marker low nibble == 0
1840            0x00, 0x21, // section-2 present, raw CN0 field = 16 -> 32 + 10 offset
1841            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}