Skip to main content

sidereon_core/rtcm/
msm.rs

1//! RTCM 3 Multiple Signal Message (MSM) observations, types MSM4 and MSM7.
2//!
3//! The MSM family carries multi-constellation, multi-signal pseudorange,
4//! carrier-phase, phase-range-rate, lock-time, and carrier-to-noise observations
5//! in one compact message (RTCM 10403.3, Section 3.5). This module decodes and
6//! re-encodes the two highest-value members:
7//!
8//!   * **MSM4** - full pseudoranges and phase ranges with the standard
9//!     resolution (message numbers 1074 / 1084 / 1094 / 1124, and the SBAS /
10//!     QZSS / NavIC siblings).
11//!   * **MSM7** - full pseudoranges, phase ranges, phase-range-rates, and
12//!     extended resolution (message numbers 1077 / 1087 / 1097 / 1127, and the
13//!     siblings).
14//!
15//! The message number alone fixes both the constellation and the MSM type via
16//! the regular RTCM numbering (`107x` GPS, `108x` GLONASS, `109x` Galileo, `110x`
17//! SBAS, `111x` QZSS, `112x` BeiDou, `113x` NavIC; the trailing digit is the MSM
18//! type). Other MSM types (1, 2, 3, 5, 6) are left to the caller as
19//! [`super::Message::Unsupported`].
20//!
21//! ## Field-major packing
22//!
23//! MSM does not store one record per observation. The body is a common header,
24//! then the satellite block with every field laid out column-first (all the
25//! rough-range integers, then all the rough-range remainders, ...), then the
26//! signal block laid out the same way over the active cells. The cell set is the
27//! cross product of the satellite mask and signal mask, pruned by the cell mask.
28//!
29//! ## Canonical representation
30//!
31//! Field values are stored as the raw transmitted integers (the
32//! `DFxxx`-numbered quantities), not pre-scaled engineering units, so the IR is
33//! an exact, loss-free image of the wire bits and `decode` -> `encode`
34//! round-trips byte-for-byte. Each accessor documents the standard scale factor
35//! so a consumer can recover meters, milliseconds, or dB-Hz when needed.
36
37use crate::error::{Error, Result};
38use crate::id::GnssSystem;
39
40use super::bits::{BitReader, BitWriter};
41
42/// Which MSM variant a message is.
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum MsmKind {
45    /// MSM4: full pseudorange + phase range, standard resolution.
46    Msm4,
47    /// MSM7: full pseudorange + phase range + phase-range-rate, extended
48    /// resolution.
49    Msm7,
50}
51
52/// The MSM message header, common to every MSM type (RTCM 10403.3 Table 3.5-78).
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub struct MsmHeader {
55    /// Reference station identifier (DF003).
56    pub reference_station_id: u16,
57    /// GNSS epoch time, the raw 30-bit field. Its meaning is constellation
58    /// specific: milliseconds of the GPS/Galileo/BeiDou week, or, for GLONASS,
59    /// a 3-bit day-of-week joined with a 27-bit millisecond-of-day count.
60    pub epoch_time: u32,
61    /// Multiple message bit (DF393): more MSM messages share this epoch.
62    pub multiple_message: bool,
63    /// Issue of data station (DF409).
64    pub iods: u8,
65    /// Reserved field DF001 (7 bits), preserved for exact round-trip.
66    pub reserved: u8,
67    /// Clock steering indicator (DF411).
68    pub clock_steering: u8,
69    /// External clock indicator (DF412).
70    pub external_clock: u8,
71    /// Divergence-free smoothing indicator (DF417).
72    pub divergence_free_smoothing: bool,
73    /// Smoothing interval (DF418).
74    pub smoothing_interval: u8,
75}
76
77/// Per-satellite data for one MSM satellite.
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub struct MsmSatellite {
80    /// Satellite identifier: the 1-based index of the set bit in the satellite
81    /// mask (DF394). For most constellations this equals the PRN / slot number.
82    pub id: u8,
83    /// Rough range, whole milliseconds (DF397). The value 255 marks the
84    /// satellite range as invalid.
85    pub rough_range_ms: u8,
86    /// Rough range remainder, in units of 1/1024 ms (DF398, scale 2^-10 ms).
87    pub rough_range_mod1: u16,
88    /// Extended satellite info (DF419), present only in MSM7. For GLONASS this
89    /// is the frequency channel number.
90    pub extended_info: Option<u8>,
91    /// Rough phase-range-rate in whole m/s (DF399), present only in MSM7.
92    pub rough_phase_range_rate_m_s: Option<i16>,
93}
94
95/// Per-cell signal data for one active (satellite, signal) pair.
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct MsmSignal {
98    /// Owning satellite id (1-based satellite-mask index).
99    pub satellite_id: u8,
100    /// Signal id: the 1-based index of the set bit in the signal mask (DF395).
101    pub signal_id: u8,
102    /// Fine pseudorange (DF400 for MSM4, scale 2^-24 ms; DF405 for MSM7, scale
103    /// 2^-29 ms). The MSM4 invalid marker is -16384.
104    pub fine_pseudorange: i32,
105    /// Fine phase range (DF401 for MSM4, scale 2^-29 ms; DF406 for MSM7, scale
106    /// 2^-31 ms).
107    pub fine_phase_range: i32,
108    /// Phase-range lock-time indicator (DF402, 4-bit, for MSM4; DF407, 10-bit,
109    /// for MSM7).
110    pub lock_time_indicator: u16,
111    /// Half-cycle ambiguity indicator (DF420).
112    pub half_cycle_ambiguity: bool,
113    /// Carrier-to-noise density ratio (DF403, 1 dB-Hz, for MSM4; DF408, scale
114    /// 2^-4 dB-Hz, for MSM7).
115    pub cnr: u16,
116    /// Fine phase-range-rate (DF404, scale 0.0001 m/s), present only in MSM7.
117    pub fine_phase_range_rate: Option<i16>,
118}
119
120/// A decoded MSM4 or MSM7 observation message.
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct MsmMessage {
123    /// The message number (e.g. 1077).
124    pub message_number: u16,
125    /// The constellation, derived from the message number.
126    pub system: GnssSystem,
127    /// The MSM variant (MSM4 or MSM7).
128    pub kind: MsmKind,
129    /// Common MSM header.
130    pub header: MsmHeader,
131    /// Active satellites, in ascending id order.
132    pub satellites: Vec<MsmSatellite>,
133    /// Active signal cells, in satellite-major then signal order.
134    pub signals: Vec<MsmSignal>,
135}
136
137/// Map an MSM message number to its constellation and (supported) MSM type.
138///
139/// Returns `None` for numbers outside the MSM range and for MSM types this
140/// module does not decode (1, 2, 3, 5, 6).
141pub(crate) fn msm_kind(message_number: u16) -> Option<(GnssSystem, MsmKind)> {
142    if !(1071..=1137).contains(&message_number) {
143        return None;
144    }
145    let group = (message_number - 1071) / 10;
146    let system = match group {
147        0 => GnssSystem::Gps,
148        1 => GnssSystem::Glonass,
149        2 => GnssSystem::Galileo,
150        3 => GnssSystem::Sbas,
151        4 => GnssSystem::Qzss,
152        5 => GnssSystem::BeiDou,
153        6 => GnssSystem::Navic,
154        _ => return None,
155    };
156    let kind = match message_number % 10 {
157        4 => MsmKind::Msm4,
158        7 => MsmKind::Msm7,
159        _ => return None,
160    };
161    Some((system, kind))
162}
163
164/// True if `message_number` is an MSM type decoded by this module.
165pub(crate) fn is_supported_msm(message_number: u16) -> bool {
166    msm_kind(message_number).is_some()
167}
168
169impl MsmMessage {
170    /// Decode an MSM4 / MSM7 message body (without the transport frame).
171    pub fn decode(body: &[u8]) -> Result<Self> {
172        let mut r = BitReader::new(body);
173        let message_number = r.u(12)? as u16;
174        let (system, kind) = msm_kind(message_number).ok_or_else(|| {
175            Error::Parse(format!(
176                "message {message_number} is not a supported MSM4/MSM7 type"
177            ))
178        })?;
179
180        let header = MsmHeader {
181            reference_station_id: r.u(12)? as u16,
182            epoch_time: r.u(30)? as u32,
183            multiple_message: r.flag()?,
184            iods: r.u(3)? as u8,
185            reserved: r.u(7)? as u8,
186            clock_steering: r.u(2)? as u8,
187            external_clock: r.u(2)? as u8,
188            divergence_free_smoothing: r.flag()?,
189            smoothing_interval: r.u(3)? as u8,
190        };
191
192        let satellite_mask = r.u(64)?;
193        let signal_mask = r.u(32)? as u32;
194        let sat_ids = set_bits(satellite_mask, 64);
195        let sig_ids = set_bits_u32(signal_mask);
196
197        let nsat = sat_ids.len();
198        let nsig = sig_ids.len();
199
200        // Cell mask: nsat * nsig bits, satellite-major.
201        let mut cell_present = Vec::with_capacity(nsat * nsig);
202        for _ in 0..nsat * nsig {
203            cell_present.push(r.flag()?);
204        }
205
206        // Satellite block (column-major).
207        let mut rough_range_ms = Vec::with_capacity(nsat);
208        for _ in 0..nsat {
209            rough_range_ms.push(r.u(8)? as u8);
210        }
211        let extended_info = if kind == MsmKind::Msm7 {
212            let mut v = Vec::with_capacity(nsat);
213            for _ in 0..nsat {
214                v.push(Some(r.u(4)? as u8));
215            }
216            v
217        } else {
218            vec![None; nsat]
219        };
220        let mut rough_range_mod1 = Vec::with_capacity(nsat);
221        for _ in 0..nsat {
222            rough_range_mod1.push(r.u(10)? as u16);
223        }
224        let rough_prr = if kind == MsmKind::Msm7 {
225            let mut v = Vec::with_capacity(nsat);
226            for _ in 0..nsat {
227                v.push(Some(r.i(14)? as i16));
228            }
229            v
230        } else {
231            vec![None; nsat]
232        };
233
234        let satellites: Vec<MsmSatellite> = (0..nsat)
235            .map(|s| MsmSatellite {
236                id: sat_ids[s],
237                rough_range_ms: rough_range_ms[s],
238                rough_range_mod1: rough_range_mod1[s],
239                extended_info: extended_info[s],
240                rough_phase_range_rate_m_s: rough_prr[s],
241            })
242            .collect();
243
244        // The ordered list of active (satellite, signal) cells.
245        let cells = active_cells(&sat_ids, &sig_ids, &cell_present);
246        let ncell = cells.len();
247
248        // Signal block (column-major over cells).
249        let signals = match kind {
250            MsmKind::Msm4 => {
251                let fine_pr = read_vec(&mut r, ncell, |rr| rr.i(15).map(|v| v as i32))?;
252                let fine_ph = read_vec(&mut r, ncell, |rr| rr.i(22).map(|v| v as i32))?;
253                let lock = read_vec(&mut r, ncell, |rr| rr.u(4).map(|v| v as u16))?;
254                let half = read_vec(&mut r, ncell, |rr| rr.flag())?;
255                let cnr = read_vec(&mut r, ncell, |rr| rr.u(6).map(|v| v as u16))?;
256                cells
257                    .iter()
258                    .enumerate()
259                    .map(|(c, &(sat, sig))| MsmSignal {
260                        satellite_id: sat,
261                        signal_id: sig,
262                        fine_pseudorange: fine_pr[c],
263                        fine_phase_range: fine_ph[c],
264                        lock_time_indicator: lock[c],
265                        half_cycle_ambiguity: half[c],
266                        cnr: cnr[c],
267                        fine_phase_range_rate: None,
268                    })
269                    .collect()
270            }
271            MsmKind::Msm7 => {
272                let fine_pr = read_vec(&mut r, ncell, |rr| rr.i(20).map(|v| v as i32))?;
273                let fine_ph = read_vec(&mut r, ncell, |rr| rr.i(24).map(|v| v as i32))?;
274                let lock = read_vec(&mut r, ncell, |rr| rr.u(10).map(|v| v as u16))?;
275                let half = read_vec(&mut r, ncell, |rr| rr.flag())?;
276                let cnr = read_vec(&mut r, ncell, |rr| rr.u(10).map(|v| v as u16))?;
277                let fine_prr = read_vec(&mut r, ncell, |rr| rr.i(15).map(|v| v as i16))?;
278                cells
279                    .iter()
280                    .enumerate()
281                    .map(|(c, &(sat, sig))| MsmSignal {
282                        satellite_id: sat,
283                        signal_id: sig,
284                        fine_pseudorange: fine_pr[c],
285                        fine_phase_range: fine_ph[c],
286                        lock_time_indicator: lock[c],
287                        half_cycle_ambiguity: half[c],
288                        cnr: cnr[c],
289                        fine_phase_range_rate: Some(fine_prr[c]),
290                    })
291                    .collect()
292            }
293        };
294
295        Ok(Self {
296            message_number,
297            system,
298            kind,
299            header,
300            satellites,
301            signals,
302        })
303    }
304
305    /// Encode this message back into an MSM body (without the transport frame).
306    pub fn encode(&self) -> Vec<u8> {
307        let mut w = BitWriter::new();
308        w.push_u(u64::from(self.message_number), 12);
309        w.push_u(u64::from(self.header.reference_station_id), 12);
310        w.push_u(u64::from(self.header.epoch_time), 30);
311        w.push_flag(self.header.multiple_message);
312        w.push_u(u64::from(self.header.iods), 3);
313        w.push_u(u64::from(self.header.reserved), 7);
314        w.push_u(u64::from(self.header.clock_steering), 2);
315        w.push_u(u64::from(self.header.external_clock), 2);
316        w.push_flag(self.header.divergence_free_smoothing);
317        w.push_u(u64::from(self.header.smoothing_interval), 3);
318
319        // Reconstruct the satellite ids (sorted) and the satellite mask.
320        let mut sat_ids: Vec<u8> = self.satellites.iter().map(|s| s.id).collect();
321        sat_ids.sort_unstable();
322        let mut satellite_mask: u64 = 0;
323        for &id in &sat_ids {
324            satellite_mask |= 1u64 << (64 - u32::from(id));
325        }
326
327        // Signal ids = sorted union of signals referenced by the cells.
328        let mut sig_ids: Vec<u8> = self.signals.iter().map(|s| s.signal_id).collect();
329        sig_ids.sort_unstable();
330        sig_ids.dedup();
331        let mut signal_mask: u32 = 0;
332        for &id in &sig_ids {
333            signal_mask |= 1u32 << (32 - u32::from(id));
334        }
335
336        w.push_u(satellite_mask, 64);
337        w.push_u(u64::from(signal_mask), 32);
338
339        // Cell mask, satellite-major, plus the ordered active cell list.
340        let mut ordered_cells: Vec<(u8, u8)> = Vec::new();
341        for &sat in &sat_ids {
342            for &sig in &sig_ids {
343                let present = self
344                    .signals
345                    .iter()
346                    .any(|s| s.satellite_id == sat && s.signal_id == sig);
347                w.push_flag(present);
348                if present {
349                    ordered_cells.push((sat, sig));
350                }
351            }
352        }
353
354        // Satellite block, column-major, in the same sorted id order.
355        let sat_by_id = |id: u8| self.satellites.iter().find(|s| s.id == id);
356        for &id in &sat_ids {
357            let sat = sat_by_id(id);
358            w.push_u(u64::from(sat.map_or(0, |s| s.rough_range_ms)), 8);
359        }
360        if self.kind == MsmKind::Msm7 {
361            for &id in &sat_ids {
362                let ext = sat_by_id(id).and_then(|s| s.extended_info).unwrap_or(0);
363                w.push_u(u64::from(ext), 4);
364            }
365        }
366        for &id in &sat_ids {
367            let sat = sat_by_id(id);
368            w.push_u(u64::from(sat.map_or(0, |s| s.rough_range_mod1)), 10);
369        }
370        if self.kind == MsmKind::Msm7 {
371            for &id in &sat_ids {
372                let prr = sat_by_id(id)
373                    .and_then(|s| s.rough_phase_range_rate_m_s)
374                    .unwrap_or(0);
375                w.push_i(i64::from(prr), 14);
376            }
377        }
378
379        // Signal block, column-major over the ordered cells.
380        let cell_signal = |sat: u8, sig: u8| {
381            self.signals
382                .iter()
383                .find(|s| s.satellite_id == sat && s.signal_id == sig)
384                .expect("ordered cell must reference an existing signal")
385        };
386        let ordered: Vec<&MsmSignal> = ordered_cells
387            .iter()
388            .map(|&(sat, sig)| cell_signal(sat, sig))
389            .collect();
390
391        match self.kind {
392            MsmKind::Msm4 => {
393                for s in &ordered {
394                    w.push_i(i64::from(s.fine_pseudorange), 15);
395                }
396                for s in &ordered {
397                    w.push_i(i64::from(s.fine_phase_range), 22);
398                }
399                for s in &ordered {
400                    w.push_u(u64::from(s.lock_time_indicator), 4);
401                }
402                for s in &ordered {
403                    w.push_flag(s.half_cycle_ambiguity);
404                }
405                for s in &ordered {
406                    w.push_u(u64::from(s.cnr), 6);
407                }
408            }
409            MsmKind::Msm7 => {
410                for s in &ordered {
411                    w.push_i(i64::from(s.fine_pseudorange), 20);
412                }
413                for s in &ordered {
414                    w.push_i(i64::from(s.fine_phase_range), 24);
415                }
416                for s in &ordered {
417                    w.push_u(u64::from(s.lock_time_indicator), 10);
418                }
419                for s in &ordered {
420                    w.push_flag(s.half_cycle_ambiguity);
421                }
422                for s in &ordered {
423                    w.push_u(u64::from(s.cnr), 10);
424                }
425                for s in &ordered {
426                    w.push_i(i64::from(s.fine_phase_range_rate.unwrap_or(0)), 15);
427                }
428            }
429        }
430
431        w.into_bytes()
432    }
433}
434
435/// Read `n` values with `f`, collecting into a vector.
436fn read_vec<T>(
437    r: &mut BitReader<'_>,
438    n: usize,
439    mut f: impl FnMut(&mut BitReader<'_>) -> Result<T>,
440) -> Result<Vec<T>> {
441    let mut v = Vec::with_capacity(n);
442    for _ in 0..n {
443        v.push(f(r)?);
444    }
445    Ok(v)
446}
447
448/// The 1-based positions of the set bits in `mask`, scanning from the MSB of an
449/// `n`-bit field.
450fn set_bits(mask: u64, n: u32) -> Vec<u8> {
451    let mut ids = Vec::new();
452    for i in 0..n {
453        if (mask >> (n - 1 - i)) & 1 == 1 {
454            ids.push((i + 1) as u8);
455        }
456    }
457    ids
458}
459
460/// The 1-based positions of the set bits in a 32-bit signal mask.
461fn set_bits_u32(mask: u32) -> Vec<u8> {
462    let mut ids = Vec::new();
463    for i in 0..32u32 {
464        if (mask >> (31 - i)) & 1 == 1 {
465            ids.push((i + 1) as u8);
466        }
467    }
468    ids
469}
470
471/// Build the ordered active-cell list from the masks and the cell-present bits.
472fn active_cells(sat_ids: &[u8], sig_ids: &[u8], cell_present: &[bool]) -> Vec<(u8, u8)> {
473    let nsig = sig_ids.len();
474    let mut cells = Vec::new();
475    for (si, &sat) in sat_ids.iter().enumerate() {
476        for (gi, &sig) in sig_ids.iter().enumerate() {
477            if cell_present[si * nsig + gi] {
478                cells.push((sat, sig));
479            }
480        }
481    }
482    cells
483}