1#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
71
72use crate::gps::{BroadcastEphemeris, GnssSystem};
73
74const SEMICIRCLE: f64 = core::f64::consts::PI;
76
77pub const SPEED_OF_LIGHT: f64 = 299_792_458.0;
79
80pub const PREAMBLE: u8 = 0xD3;
82
83pub const SSR_UPDATE_INTERVALS: [f64; 16] = [
85 1.0, 2.0, 5.0, 10.0, 15.0, 30.0, 60.0, 120.0, 240.0, 300.0, 600.0, 900.0, 1800.0, 3600.0,
86 7200.0, 10800.0,
87];
88
89pub const MAX_SSR_SATELLITES: usize = 63;
91
92#[must_use]
98pub fn crc24q(bytes: &[u8]) -> u32 {
99 const POLYNOMIAL: u32 = 0x0186_4CFB;
100 let mut crc = 0_u32;
101 for byte in bytes {
102 crc ^= u32::from(*byte) << 16;
103 for _ in 0..8 {
104 crc <<= 1;
105 if crc & 0x0100_0000 != 0 {
106 crc ^= POLYNOMIAL;
107 }
108 }
109 }
110 crc & 0x00FF_FFFF
111}
112
113#[derive(Debug, Clone)]
119struct Bits<'a> {
120 bytes: &'a [u8],
121 offset: usize,
122}
123
124impl<'a> Bits<'a> {
125 const fn new(bytes: &'a [u8]) -> Self {
126 Self { bytes, offset: 0 }
127 }
128
129 const fn consumed(&self) -> usize {
130 self.offset
131 }
132
133 const fn len(&self) -> usize {
135 self.bytes.len() * 8
136 }
137
138 fn u64(&mut self, width: usize) -> Option<u64> {
140 if width > 64 || self.offset + width > self.bytes.len() * 8 {
141 return None;
142 }
143 let mut value = 0_u64;
144 for i in 0..width {
145 let bit = self.offset + i;
146 let byte = self.bytes[bit / 8];
147 value = (value << 1) | u64::from((byte >> (7 - bit % 8)) & 1);
148 }
149 self.offset += width;
150 Some(value)
151 }
152
153 fn u32(&mut self, width: usize) -> Option<u32> {
154 self.u64(width).map(|v| v as u32)
155 }
156
157 fn u8(&mut self, width: usize) -> Option<u8> {
158 self.u64(width).map(|v| v as u8)
159 }
160
161 fn i64(&mut self, width: usize) -> Option<i64> {
163 let raw = self.u64(width)?;
164 if width == 64 {
165 return Some(raw.cast_signed());
166 }
167 let sign = 1_u64 << (width - 1);
168 Some(if raw & sign == 0 {
169 raw.cast_signed()
170 } else {
171 raw.cast_signed() - (1_i64 << width)
172 })
173 }
174
175 #[allow(clippy::cast_precision_loss)] fn scaled(&mut self, width: usize, power: i32) -> Option<f64> {
182 self.i64(width).map(|v| libm::ldexp(v as f64, power))
183 }
184
185 #[allow(clippy::cast_precision_loss)]
187 fn scaled_unsigned(&mut self, width: usize, power: i32) -> Option<f64> {
188 self.u64(width).map(|v| libm::ldexp(v as f64, power))
189 }
190}
191
192#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct StationReferencePoint {
195 pub station_id: u16,
197 pub itrf_year: u8,
199 pub ecef: [f64; 3],
201 pub antenna_height: Option<f64>,
203 pub services: [bool; 3],
205}
206
207#[derive(Debug, Clone, Copy, PartialEq)]
209pub struct OrbitCorrection {
210 pub prn: u8,
212 pub iode: u8,
215 pub reference: f64,
218 pub delta: [f64; 3],
220 pub delta_rate: [f64; 3],
222}
223
224impl OrbitCorrection {
225 #[must_use]
227 pub fn at(&self, tow: f64) -> [f64; 3] {
228 let dt = tow - self.reference;
229 core::array::from_fn(|i| self.delta_rate[i].mul_add(dt, self.delta[i]))
230 }
231}
232
233#[derive(Debug, Clone, Copy, PartialEq)]
235pub struct ClockCorrection {
236 pub prn: u8,
238 pub reference: f64,
240 pub coefficients: [f64; 3],
242}
243
244impl ClockCorrection {
245 #[must_use]
247 pub fn at(&self, tow: f64) -> f64 {
248 let dt = tow - self.reference;
249 self.coefficients[2]
250 .mul_add(dt, self.coefficients[1])
251 .mul_add(dt, self.coefficients[0])
252 }
253
254 #[must_use]
257 pub fn seconds_at(&self, tow: f64) -> f64 {
258 self.at(tow) / SPEED_OF_LIGHT
259 }
260}
261
262#[must_use]
273pub fn apply_orbit(
274 position: [f64; 3],
275 velocity: [f64; 3],
276 correction: [f64; 3],
277) -> Option<[f64; 3]> {
278 let radial = normalise(position)?;
279 let cross = normalise(cross_product(position, velocity))?;
280 let along = cross_product(cross, radial);
281 Some(core::array::from_fn(|i| {
282 position[i]
283 - (radial[i] * correction[0] + along[i] * correction[1] + cross[i] * correction[2])
284 }))
285}
286
287fn cross_product(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
288 [
289 a[1] * b[2] - a[2] * b[1],
290 a[2] * b[0] - a[0] * b[2],
291 a[0] * b[1] - a[1] * b[0],
292 ]
293}
294
295fn normalise(v: [f64; 3]) -> Option<[f64; 3]> {
296 let norm = libm::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
297 if norm <= 0.0 || !norm.is_finite() {
298 return None;
299 }
300 Some([v[0] / norm, v[1] / norm, v[2] / norm])
301}
302
303#[derive(Debug, Clone, PartialEq)]
305pub struct SsrOrbitBlock {
306 satellites: [OrbitCorrection; MAX_SSR_SATELLITES],
307 count: usize,
308 pub iod_ssr: u8,
310}
311
312#[derive(Debug, Clone, PartialEq)]
314pub struct SsrClockBlock {
315 satellites: [ClockCorrection; MAX_SSR_SATELLITES],
316 count: usize,
317 pub iod_ssr: u8,
319}
320
321impl SsrOrbitBlock {
322 #[must_use]
324 pub fn satellites(&self) -> &[OrbitCorrection] {
325 &self.satellites[..self.count]
326 }
327}
328
329impl SsrClockBlock {
330 #[must_use]
332 pub fn satellites(&self) -> &[ClockCorrection] {
333 &self.satellites[..self.count]
334 }
335}
336
337#[allow(clippy::large_enum_variant)]
347#[derive(Debug, Clone, PartialEq)]
348pub enum Message {
349 StationReferencePoint(StationReferencePoint),
351 GpsEphemeris(BroadcastEphemeris),
353 SsrOrbit(SsrOrbitBlock),
355 SsrClock(SsrClockBlock),
357 SsrOrbitAndClock(SsrOrbitBlock, SsrClockBlock),
359 Unsupported(u16),
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum DecodeError {
366 Truncated {
368 message: u16,
370 },
371 WrongLength {
373 message: u16,
375 payload_bits: usize,
377 expected: usize,
379 },
380 TooManySatellites {
382 message: u16,
384 count: usize,
386 },
387}
388
389pub fn decode(payload: &[u8]) -> Result<Message, DecodeError> {
395 let mut bits = Bits::new(payload);
396 let Some(number) = bits.u32(12) else {
397 return Err(DecodeError::Truncated { message: 0 });
398 };
399 let number = number as u16;
400 match number {
401 1005 | 1006 => decode_station(&mut bits, number).map(Message::StationReferencePoint),
402 1019 => decode_gps_ephemeris(&mut bits).map(Message::GpsEphemeris),
403 1057 => decode_ssr_orbit(&mut bits, number).map(Message::SsrOrbit),
404 1058 => decode_ssr_clock(&mut bits, number).map(Message::SsrClock),
405 1060 => decode_ssr_combined(&mut bits, number),
406 _ => Ok(Message::Unsupported(number)),
407 }
408}
409
410fn decode_station(bits: &mut Bits, number: u16) -> Result<StationReferencePoint, DecodeError> {
411 let truncated = || DecodeError::Truncated { message: number };
412 let station_id = bits.u32(12).ok_or_else(truncated)? as u16;
413 let itrf_year = bits.u8(6).ok_or_else(truncated)?;
414 let gps = bits.u8(1).ok_or_else(truncated)? == 1;
415 let glonass = bits.u8(1).ok_or_else(truncated)? == 1;
416 let galileo = bits.u8(1).ok_or_else(truncated)? == 1;
417 let _reference_station = bits.u8(1).ok_or_else(truncated)?;
418 let x = bits.i64(38).ok_or_else(truncated)?;
419 let _oscillator = bits.u8(1).ok_or_else(truncated)?;
420 let _reserved = bits.u8(1).ok_or_else(truncated)?;
421 let y = bits.i64(38).ok_or_else(truncated)?;
422 let _quarter_cycle = bits.u8(2).ok_or_else(truncated)?;
423 let z = bits.i64(38).ok_or_else(truncated)?;
424 let antenna_height = if number == 1006 {
425 Some(f64::from(bits.u32(16).ok_or_else(truncated)?) * 1e-4)
426 } else {
427 None
428 };
429
430 let expected = if number == 1006 { 168 } else { 152 };
431 check_length(number, bits.len(), expected)?;
432
433 #[allow(clippy::cast_precision_loss)] Ok(StationReferencePoint {
435 station_id,
436 itrf_year,
437 ecef: [x as f64 * 1e-4, y as f64 * 1e-4, z as f64 * 1e-4],
438 antenna_height,
439 services: [gps, glonass, galileo],
440 })
441}
442
443fn decode_gps_ephemeris(bits: &mut Bits) -> Result<BroadcastEphemeris, DecodeError> {
444 let truncated = || DecodeError::Truncated { message: 1019 };
445 let prn = bits.u8(6).ok_or_else(truncated)?;
446 let week = bits.u32(10).ok_or_else(truncated)? as u16;
447 let _accuracy = bits.u8(4).ok_or_else(truncated)?;
448 let _code_on_l2 = bits.u8(2).ok_or_else(truncated)?;
449 let idot = bits.scaled(14, -43).ok_or_else(truncated)? * SEMICIRCLE;
450 let _iode = bits.u8(8).ok_or_else(truncated)?;
451 let toc = bits.scaled_unsigned(16, 4).ok_or_else(truncated)?;
452 let af2 = bits.scaled(8, -55).ok_or_else(truncated)?;
453 let af1 = bits.scaled(16, -43).ok_or_else(truncated)?;
454 let af0 = bits.scaled(22, -31).ok_or_else(truncated)?;
455 let _iodc = bits.u32(10).ok_or_else(truncated)?;
456 let crs = bits.scaled(16, -5).ok_or_else(truncated)?;
457 let delta_n = bits.scaled(16, -43).ok_or_else(truncated)? * SEMICIRCLE;
458 let m0 = bits.scaled(32, -31).ok_or_else(truncated)? * SEMICIRCLE;
459 let cuc = bits.scaled(16, -29).ok_or_else(truncated)?;
460 let e = bits.scaled_unsigned(32, -33).ok_or_else(truncated)?;
461 let cus = bits.scaled(16, -29).ok_or_else(truncated)?;
462 let sqrt_a = bits.scaled_unsigned(32, -19).ok_or_else(truncated)?;
463 let toe = bits.scaled_unsigned(16, 4).ok_or_else(truncated)?;
464 let cic = bits.scaled(16, -29).ok_or_else(truncated)?;
465 let omega0 = bits.scaled(32, -31).ok_or_else(truncated)? * SEMICIRCLE;
466 let cis = bits.scaled(16, -29).ok_or_else(truncated)?;
467 let i0 = bits.scaled(32, -31).ok_or_else(truncated)? * SEMICIRCLE;
468 let crc_harmonic = bits.scaled(16, -5).ok_or_else(truncated)?;
469 let omega = bits.scaled(32, -31).ok_or_else(truncated)? * SEMICIRCLE;
470 let omega_dot = bits.scaled(24, -43).ok_or_else(truncated)? * SEMICIRCLE;
471 let tgd = bits.scaled(8, -31).ok_or_else(truncated)?;
472 let health = bits.u8(6).ok_or_else(truncated)?;
473 let _l2p_data = bits.u8(1).ok_or_else(truncated)?;
474 let _fit_interval = bits.u8(1).ok_or_else(truncated)?;
475
476 check_length(1019, bits.len(), 488)?;
477
478 Ok(BroadcastEphemeris {
479 system: GnssSystem::Gps,
480 prn,
481 week,
482 toe,
483 toc,
484 sqrt_a,
485 e,
486 i0,
487 omega0,
488 omega,
489 m0,
490 delta_n,
491 idot,
492 omega_dot,
493 cuc,
494 cus,
495 crc: crc_harmonic,
496 crs,
497 cic,
498 cis,
499 af0,
500 af1,
501 af2,
502 tgd,
503 health,
504 })
505}
506
507fn decode_ssr_header(
515 bits: &mut Bits,
516 number: u16,
517 has_datum: bool,
518) -> Result<(f64, u8, usize), DecodeError> {
519 let truncated = || DecodeError::Truncated { message: number };
520 let epoch = f64::from(bits.u32(20).ok_or_else(truncated)?);
521 let interval = bits.u8(4).ok_or_else(truncated)? as usize;
522 let _multiple_message = bits.u8(1).ok_or_else(truncated)?;
523 if has_datum {
524 let _datum = bits.u8(1).ok_or_else(truncated)?;
525 }
526 let iod_ssr = bits.u8(4).ok_or_else(truncated)?;
527 let _provider = bits.u32(16).ok_or_else(truncated)?;
528 let _solution = bits.u8(4).ok_or_else(truncated)?;
529 let count = bits.u8(6).ok_or_else(truncated)? as usize;
530 if count > MAX_SSR_SATELLITES {
531 return Err(DecodeError::TooManySatellites {
532 message: number,
533 count,
534 });
535 }
536 let reference = epoch + SSR_UPDATE_INTERVALS[interval] / 2.0;
538 Ok((reference, iod_ssr, count))
539}
540
541#[allow(clippy::cast_precision_loss)] fn read_orbit(
543 bits: &mut Bits,
544 number: u16,
545 reference: f64,
546) -> Result<OrbitCorrection, DecodeError> {
547 let truncated = || DecodeError::Truncated { message: number };
548 Ok(OrbitCorrection {
549 prn: bits.u8(6).ok_or_else(truncated)?,
550 iode: bits.u8(8).ok_or_else(truncated)?,
551 reference,
552 delta: [
553 bits.i64(22).ok_or_else(truncated)? as f64 * 1e-4,
554 bits.i64(20).ok_or_else(truncated)? as f64 * 4e-4,
555 bits.i64(20).ok_or_else(truncated)? as f64 * 4e-4,
556 ],
557 delta_rate: [
558 bits.i64(21).ok_or_else(truncated)? as f64 * 1e-6,
559 bits.i64(19).ok_or_else(truncated)? as f64 * 4e-6,
560 bits.i64(19).ok_or_else(truncated)? as f64 * 4e-6,
561 ],
562 })
563}
564
565#[allow(clippy::cast_precision_loss)]
568fn read_clock(
569 bits: &mut Bits,
570 number: u16,
571 reference: f64,
572 prn: Option<u8>,
573) -> Result<ClockCorrection, DecodeError> {
574 let truncated = || DecodeError::Truncated { message: number };
575 let prn = match prn {
576 Some(prn) => prn,
577 None => bits.u8(6).ok_or_else(truncated)?,
578 };
579 Ok(ClockCorrection {
580 prn,
581 reference,
582 coefficients: [
583 bits.i64(22).ok_or_else(truncated)? as f64 * 1e-4,
584 bits.i64(21).ok_or_else(truncated)? as f64 * 1e-6,
585 bits.i64(27).ok_or_else(truncated)? as f64 * 2e-8,
586 ],
587 })
588}
589
590const EMPTY_ORBIT: OrbitCorrection = OrbitCorrection {
591 prn: 0,
592 iode: 0,
593 reference: 0.0,
594 delta: [0.0; 3],
595 delta_rate: [0.0; 3],
596};
597
598const EMPTY_CLOCK: ClockCorrection = ClockCorrection {
599 prn: 0,
600 reference: 0.0,
601 coefficients: [0.0; 3],
602};
603
604fn decode_ssr_orbit(bits: &mut Bits, number: u16) -> Result<SsrOrbitBlock, DecodeError> {
605 let (reference, iod_ssr, count) = decode_ssr_header(bits, number, true)?;
606 let mut satellites = [EMPTY_ORBIT; MAX_SSR_SATELLITES];
607 for satellite in satellites.iter_mut().take(count) {
608 *satellite = read_orbit(bits, number, reference)?;
609 }
610 check_length(number, bits.len(), bits.consumed())?;
611 Ok(SsrOrbitBlock {
612 satellites,
613 count,
614 iod_ssr,
615 })
616}
617
618fn decode_ssr_clock(bits: &mut Bits, number: u16) -> Result<SsrClockBlock, DecodeError> {
619 let (reference, iod_ssr, count) = decode_ssr_header(bits, number, false)?;
620 let mut satellites = [EMPTY_CLOCK; MAX_SSR_SATELLITES];
621 for satellite in satellites.iter_mut().take(count) {
622 *satellite = read_clock(bits, number, reference, None)?;
623 }
624 check_length(number, bits.len(), bits.consumed())?;
625 Ok(SsrClockBlock {
626 satellites,
627 count,
628 iod_ssr,
629 })
630}
631
632fn decode_ssr_combined(bits: &mut Bits, number: u16) -> Result<Message, DecodeError> {
633 let (reference, iod_ssr, count) = decode_ssr_header(bits, number, true)?;
634 let mut orbits = [EMPTY_ORBIT; MAX_SSR_SATELLITES];
635 let mut clocks = [EMPTY_CLOCK; MAX_SSR_SATELLITES];
636 for i in 0..count {
637 let orbit = read_orbit(bits, number, reference)?;
638 clocks[i] = read_clock(bits, number, reference, Some(orbit.prn))?;
639 orbits[i] = orbit;
640 }
641 check_length(number, bits.len(), bits.consumed())?;
642 Ok(Message::SsrOrbitAndClock(
643 SsrOrbitBlock {
644 satellites: orbits,
645 count,
646 iod_ssr,
647 },
648 SsrClockBlock {
649 satellites: clocks,
650 count,
651 iod_ssr,
652 },
653 ))
654}
655
656fn check_length(message: u16, payload_bits: usize, expected: usize) -> Result<(), DecodeError> {
669 if payload_bits >= expected && payload_bits - expected < 8 {
670 Ok(())
671 } else {
672 Err(DecodeError::WrongLength {
673 message,
674 payload_bits,
675 expected,
676 })
677 }
678}
679
680#[derive(Debug, Clone, Copy, PartialEq, Eq)]
682pub struct Frame<'a> {
683 pub offset: usize,
685 pub payload: &'a [u8],
687}
688
689#[derive(Debug, Clone)]
696pub struct RtcmDecoder<'a> {
697 bytes: &'a [u8],
698 cursor: usize,
699 rejected: usize,
700}
701
702impl<'a> RtcmDecoder<'a> {
703 #[must_use]
705 pub const fn new(bytes: &'a [u8]) -> Self {
706 Self {
707 bytes,
708 cursor: 0,
709 rejected: 0,
710 }
711 }
712
713 #[must_use]
717 pub const fn rejected(&self) -> usize {
718 self.rejected
719 }
720
721 #[must_use]
723 pub const fn remaining(&self) -> usize {
724 self.bytes.len() - self.cursor
725 }
726}
727
728impl<'a> Iterator for RtcmDecoder<'a> {
729 type Item = Frame<'a>;
730
731 fn next(&mut self) -> Option<Frame<'a>> {
732 while self.cursor + 3 <= self.bytes.len() {
733 if self.bytes[self.cursor] != PREAMBLE {
734 self.cursor += 1;
735 continue;
736 }
737 let header = &self.bytes[self.cursor..self.cursor + 3];
738 if header[1] & 0xFC != 0 {
741 self.cursor += 1;
742 self.rejected += 1;
743 continue;
744 }
745 let length = (usize::from(header[1] & 0x03) << 8) | usize::from(header[2]);
746 let total = 3 + length + 3;
747 if self.cursor + total > self.bytes.len() {
748 return None;
750 }
751 let frame = &self.bytes[self.cursor..self.cursor + total];
752 let stated = (u32::from(frame[total - 3]) << 16)
753 | (u32::from(frame[total - 2]) << 8)
754 | u32::from(frame[total - 1]);
755 if crc24q(&frame[..total - 3]) == stated {
756 let payload = &self.bytes[self.cursor + 3..self.cursor + 3 + length];
757 let offset = self.cursor;
758 self.cursor += total;
759 return Some(Frame { offset, payload });
760 }
761 self.cursor += 1;
764 self.rejected += 1;
765 }
766 None
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 #![allow(clippy::expect_used, clippy::panic)]
773
774 use super::*;
775
776 #[derive(Default)]
781 struct Writer {
782 bytes: Vec<u8>,
783 bits: usize,
784 }
785
786 impl Writer {
787 fn put(&mut self, value: u64, width: usize) {
788 for i in (0..width).rev() {
789 if self.bits.is_multiple_of(8) {
790 self.bytes.push(0);
791 }
792 if (value >> i) & 1 == 1 {
793 let last = self.bytes.len() - 1;
794 self.bytes[last] |= 1 << (7 - self.bits % 8);
795 }
796 self.bits += 1;
797 }
798 }
799
800 fn put_signed(&mut self, value: i64, width: usize) {
801 let mask = if width == 64 {
802 u64::MAX
803 } else {
804 (1 << width) - 1
805 };
806 self.put((value as u64) & mask, width);
807 }
808
809 fn put_scaled(&mut self, value: f64, width: usize, power: i32) {
811 self.put_signed(libm::round(libm::ldexp(value, -power)) as i64, width);
812 }
813
814 fn put_scaled_unsigned(&mut self, value: f64, width: usize, power: i32) {
815 self.put(libm::round(libm::ldexp(value, -power)) as u64, width);
816 }
817
818 fn put_metric(&mut self, value: f64, width: usize, step: f64) {
820 self.put_signed(libm::round(value / step) as i64, width);
821 }
822
823 fn frame(mut self) -> Vec<u8> {
824 let pad = (8 - self.bits % 8) % 8;
826 self.put(0, pad);
827 let length = self.bytes.len();
828 let mut out = vec![PREAMBLE, (length >> 8) as u8 & 0x03, length as u8];
829 out.extend_from_slice(&self.bytes);
830 let crc = crc24q(&out);
831 out.push((crc >> 16) as u8);
832 out.push((crc >> 8) as u8);
833 out.push(crc as u8);
834 out
835 }
836 }
837
838 const SQRT_A: f64 = 5153.65;
842 const ECCENTRICITY: f64 = 0.008_6;
843 const INCLINATION: f64 = 0.961_7;
844 const TOE: f64 = 302_400.0;
845
846 fn semicircles(radians: f64) -> f64 {
847 radians / SEMICIRCLE
848 }
849
850 fn gps_ephemeris_frame() -> Vec<u8> {
851 let mut w = Writer::default();
852 w.put(1019, 12);
853 w.put(7, 6); w.put(252, 10); w.put(0, 4); w.put(1, 2); w.put_scaled(semicircles(1.0e-10), 14, -43); w.put(41, 8); w.put_scaled_unsigned(TOE, 16, 4); w.put_scaled(5.0e-17, 8, -55); w.put_scaled(1.0e-12, 16, -43); w.put_scaled(1.0e-4, 22, -31); w.put(41, 10); w.put_scaled(20.0, 16, -5); w.put_scaled(semicircles(4.6e-9), 16, -43); w.put_scaled(semicircles(0.5), 32, -31); w.put_scaled(1.0e-6, 16, -29); w.put_scaled_unsigned(ECCENTRICITY, 32, -33);
869 w.put_scaled(5.0e-6, 16, -29); w.put_scaled_unsigned(SQRT_A, 32, -19);
871 w.put_scaled_unsigned(TOE, 16, 4);
872 w.put_scaled(-1.0e-7, 16, -29); w.put_scaled(semicircles(-1.0), 32, -31); w.put_scaled(1.0e-7, 16, -29); w.put_scaled(semicircles(INCLINATION), 32, -31); w.put_scaled(250.0, 16, -5); w.put_scaled(semicircles(0.9), 32, -31); w.put_scaled(semicircles(-8.0e-9), 24, -43); w.put_scaled(-5.0e-9, 8, -31); w.put(0, 6); w.put(0, 1); w.put(0, 1); w.frame()
884 }
885
886 fn ssr_frame(number: u16) -> Vec<u8> {
888 let mut w = Writer::default();
889 w.put(u64::from(number), 12);
890 w.put(302_400, 20); w.put(6, 4); w.put(0, 1); if number != 1058 {
894 w.put(0, 1); }
896 w.put(5, 4); w.put(0, 16); w.put(0, 4); w.put(2, 6); for (prn, iode, radial) in [(7_u64, 41_u64, 0.312_5), (9, 17, -1.406_25)] {
901 w.put(prn, 6);
902 if number == 1058 {
903 w.put_metric(3.5, 22, 1e-4);
904 w.put_metric(-0.01, 21, 1e-6);
905 w.put_metric(2.0e-6, 27, 2e-8);
906 continue;
907 }
908 w.put(iode, 8);
909 w.put_metric(radial, 22, 1e-4);
910 w.put_metric(0.2, 20, 4e-4);
911 w.put_metric(-0.4, 20, 4e-4);
912 w.put_metric(1.0e-3, 21, 1e-6);
913 w.put_metric(-2.0e-3, 19, 4e-6);
914 w.put_metric(4.0e-3, 19, 4e-6);
915 if number == 1060 {
916 w.put_metric(3.5, 22, 1e-4);
917 w.put_metric(-0.01, 21, 1e-6);
918 w.put_metric(2.0e-6, 27, 2e-8);
919 }
920 }
921 w.frame()
922 }
923
924 fn decode_first(stream: &[u8]) -> Message {
925 let frame = RtcmDecoder::new(stream).next().expect("one framed message");
926 decode(frame.payload).expect("a decodable payload")
927 }
928
929 #[test]
930 fn the_crc_reproduces_the_published_check_value() {
931 assert_eq!(crc24q(b"123456789"), 0x00CD_E703);
936 assert_eq!(crc24q(b""), 0);
939 assert_eq!(crc24q(&[0x00]), 0);
940 assert_ne!(crc24q(&[0x01]), 0);
941 }
942
943 #[test]
944 fn a_flipped_bit_anywhere_in_a_frame_is_refused() {
945 let good = ssr_frame(1057);
949 assert_eq!(RtcmDecoder::new(&good).count(), 1);
950 assert_eq!(good.len(), 49, "the 1057 fixture is 3 + 43 + 3 bytes");
952 let payload = RtcmDecoder::new(&good)
953 .next()
954 .expect("a frame")
955 .payload
956 .to_vec();
957 for byte in 0..good.len() {
958 for bit in 0..8 {
959 let mut corrupted = good.clone();
960 corrupted[byte] ^= 1 << bit;
961 let found = RtcmDecoder::new(&corrupted).next();
962 assert!(
963 found.is_none_or(|f| f.payload != payload.as_slice()),
964 "byte {byte} bit {bit} passed the CRC unchanged"
965 );
966 }
967 }
968 }
969
970 #[test]
971 fn the_scanner_resynchronises_past_noise_and_false_preambles() {
972 let first = gps_ephemeris_frame();
973 let second = ssr_frame(1058);
974 let mut stream = vec![0xD3, 0xFF, 0x01, 0x99]; stream.extend_from_slice(&first);
976 stream.extend_from_slice(&[0x01, 0x02, 0xD3, 0x00, 0x04, 0xAA, 0xBB]); stream.extend_from_slice(&second);
978
979 let mut decoder = RtcmDecoder::new(&stream);
980 let a = decoder.next().expect("the first frame");
981 let b = decoder.next().expect("the second frame");
982 assert_eq!(a.offset, 4);
983 assert!(matches!(decode(a.payload), Ok(Message::GpsEphemeris(_))));
984 assert!(matches!(decode(b.payload), Ok(Message::SsrClock(_))));
985 assert!(decoder.next().is_none());
986 assert!(decoder.rejected() >= 2, "rejected {}", decoder.rejected());
987 }
988
989 #[test]
990 fn a_frame_that_has_not_fully_arrived_is_not_a_rejection() {
991 let complete = gps_ephemeris_frame();
995 for cut in 1..complete.len() {
996 let mut decoder = RtcmDecoder::new(&complete[..cut]);
997 assert!(decoder.next().is_none(), "cut {cut} yielded a frame");
998 assert_eq!(decoder.rejected(), 0, "cut {cut} counted a rejection");
999 assert_eq!(
1000 decoder.remaining(),
1001 cut,
1002 "cut {cut} consumed bytes it should have kept"
1003 );
1004 }
1005 }
1006
1007 #[test]
1008 fn the_ephemeris_decodes_to_an_orbit_of_the_right_size_and_period() {
1009 let Message::GpsEphemeris(eph) = decode_first(&gps_ephemeris_frame()) else {
1014 panic!("1019 must decode as an ephemeris");
1015 };
1016 let a = eph.semi_major_axis();
1017 assert!(
1018 (a - SQRT_A * SQRT_A).abs() < 1.0,
1019 "semi-major axis {a} m, expected {} m",
1020 SQRT_A * SQRT_A
1021 );
1022 let period = 2.0 * core::f64::consts::PI / eph.mean_motion();
1026 assert!((period - 43_082.0).abs() < 20.0, "period {period} s");
1027
1028 let p = eph.position_ecef(TOE);
1031 let radius = libm::sqrt(p[0] * p[0] + p[1] * p[1] + p[2] * p[2]);
1032 assert!(
1033 radius > a * (1.0 - ECCENTRICITY) - 500.0 && radius < a * (1.0 + ECCENTRICITY) + 500.0,
1034 "radius {radius} m outside [{}, {}]",
1035 a * (1.0 - ECCENTRICITY),
1036 a * (1.0 + ECCENTRICITY)
1037 );
1038
1039 assert_eq!(eph.prn, 7);
1041 assert_eq!(eph.week, 252);
1042 assert_eq!(eph.system, GnssSystem::Gps);
1043 assert!((eph.toe - TOE).abs() < 1e-9);
1044 assert!((eph.toc - TOE).abs() < 1e-9);
1045 assert!((eph.e - ECCENTRICITY).abs() < 1.2e-10);
1050 assert!((eph.i0 - INCLINATION).abs() < 1.5e-9);
1051 assert!((eph.omega0 + 1.0).abs() < 1.5e-9);
1052 assert!((eph.omega - 0.9).abs() < 1.5e-9);
1053 assert!((eph.m0 - 0.5).abs() < 1.5e-9);
1054 assert!((eph.crc - 250.0).abs() < 0.032);
1055 assert!((eph.crs - 20.0).abs() < 0.032);
1056 assert!((eph.tgd + 5.0e-9).abs() < 4.7e-10);
1057 assert!((eph.omega_dot + 8.0e-9).abs() < 3.6e-13);
1058
1059 assert!((eph.af0 - 1.0e-4).abs() < 4.7e-10);
1064 assert!((eph.af1 - 1.0e-12).abs() < 1.2e-13);
1065 assert!((eph.af2 - 5.0e-17).abs() < 2.8e-17);
1066 assert!((eph.cuc - 1.0e-6).abs() < 1.9e-9);
1067 assert!((eph.cus - 5.0e-6).abs() < 1.9e-9);
1068 assert!((eph.cic + 1.0e-7).abs() < 1.9e-9);
1069 assert!((eph.cis - 1.0e-7).abs() < 1.9e-9);
1070 assert!((eph.idot - 1.0e-10).abs() < 3.6e-13);
1073 assert!((eph.delta_n - 4.6e-9).abs() < 3.6e-13);
1074 }
1075
1076 #[test]
1077 fn the_ssr_orbit_message_round_trips_and_is_referenced_to_the_interval_midpoint() {
1078 let Message::SsrOrbit(block) = decode_first(&ssr_frame(1057)) else {
1079 panic!("1057 must decode as an orbit block");
1080 };
1081 assert_eq!(block.iod_ssr, 5);
1082 let satellites = block.satellites();
1083 assert_eq!(satellites.len(), 2);
1084 assert_eq!(satellites[0].prn, 7);
1085 assert_eq!(satellites[0].iode, 41);
1086 assert_eq!(satellites[1].prn, 9);
1087 assert_eq!(satellites[1].iode, 17);
1088 assert!((satellites[0].delta[0] - 0.312_5).abs() < 1e-4);
1089 assert!((satellites[1].delta[0] + 1.406_25).abs() < 1e-4);
1090 assert!((satellites[0].delta[1] - 0.2).abs() < 4e-4);
1091 assert!((satellites[0].delta[2] + 0.4).abs() < 4e-4);
1092 assert!((satellites[0].delta_rate[0] - 1.0e-3).abs() < 1e-6);
1093 assert!((satellites[0].delta_rate[1] + 2.0e-3).abs() < 4e-6);
1094 assert!((satellites[0].delta_rate[2] - 4.0e-3).abs() < 4e-6);
1095
1096 assert!((satellites[0].reference - 302_430.0).abs() < 1e-9);
1098 let at_epoch = satellites[0].at(302_400.0);
1100 assert!(
1101 (at_epoch[0] - (0.312_5 - 30.0e-3)).abs() < 1e-4,
1102 "{at_epoch:?}"
1103 );
1104 }
1105
1106 #[test]
1107 fn the_clock_only_message_has_no_datum_bit_and_still_aligns() {
1108 let Message::SsrClock(block) = decode_first(&ssr_frame(1058)) else {
1111 panic!("1058 must decode as a clock block");
1112 };
1113 assert_eq!(block.iod_ssr, 5);
1114 assert_eq!(block.satellites().len(), 2);
1115 for (satellite, prn) in block.satellites().iter().zip([7, 9]) {
1116 assert_eq!(satellite.prn, prn);
1117 assert!((satellite.coefficients[0] - 3.5).abs() < 1e-4);
1118 assert!((satellite.coefficients[1] + 0.01).abs() < 1e-6);
1119 assert!((satellite.coefficients[2] - 2.0e-6).abs() < 2e-8);
1120 assert!((satellite.reference - 302_430.0).abs() < 1e-9);
1121 }
1122 }
1123
1124 #[test]
1125 fn the_combined_message_names_each_satellite_once_for_both_corrections() {
1126 let Message::SsrOrbitAndClock(orbit, clock) = decode_first(&ssr_frame(1060)) else {
1129 panic!("1060 must decode as both blocks");
1130 };
1131 assert_eq!(orbit.satellites().len(), 2);
1132 assert_eq!(clock.satellites().len(), 2);
1133 assert_eq!(orbit.iod_ssr, 5);
1134 assert_eq!(clock.iod_ssr, 5);
1135 for (o, c) in orbit.satellites().iter().zip(clock.satellites()) {
1136 assert_eq!(o.prn, c.prn);
1137 assert!((o.reference - c.reference).abs() < 1e-9);
1138 assert!((c.coefficients[0] - 3.5).abs() < 1e-4);
1139 assert!((c.coefficients[1] + 0.01).abs() < 1e-6);
1140 assert!((c.coefficients[2] - 2.0e-6).abs() < 2e-8);
1141 }
1142 assert_eq!(orbit.satellites()[0].iode, 41);
1143 assert_eq!(orbit.satellites()[1].iode, 17);
1144 }
1145
1146 #[test]
1147 fn the_station_message_round_trips_and_only_1006_carries_a_height() {
1148 for number in [1005_u16, 1006] {
1149 let mut w = Writer::default();
1150 w.put(u64::from(number), 12);
1151 w.put(1234, 12); w.put(20, 6); w.put(1, 1); w.put(0, 1); w.put(1, 1); w.put(0, 1); w.put_signed(38_378_286_400, 38); w.put(0, 1); w.put(0, 1); w.put_signed(45_534_215_000, 38); w.put(0, 2); w.put_signed(-6_186_112_000, 38); if number == 1006 {
1164 w.put(21_340, 16); }
1166 let Message::StationReferencePoint(station) = decode_first(&w.frame()) else {
1167 panic!("{number} must decode as a station reference point");
1168 };
1169 assert_eq!(station.station_id, 1234);
1170 assert_eq!(station.itrf_year, 20);
1171 assert_eq!(station.services, [true, false, true]);
1172 assert!(
1173 (station.ecef[0] - 3_837_828.64).abs() < 1e-4,
1174 "{:?}",
1175 station.ecef
1176 );
1177 assert!(
1178 (station.ecef[1] - 4_553_421.50).abs() < 1e-4,
1179 "{:?}",
1180 station.ecef
1181 );
1182 assert!(
1183 (station.ecef[2] + 618_611.20).abs() < 1e-4,
1184 "{:?}",
1185 station.ecef
1186 );
1187 match (number, station.antenna_height) {
1188 (1006, Some(h)) => assert!((h - 2.134).abs() < 1e-4, "{h}"),
1189 (1005, None) => {}
1190 other => panic!("{other:?} is the wrong height for {number}"),
1191 }
1192 }
1193 }
1194
1195 #[test]
1196 fn a_message_that_is_not_its_own_declared_length_is_rejected() {
1197 let full = gps_ephemeris_frame();
1201 let payload = RtcmDecoder::new(&full)
1202 .next()
1203 .expect("a frame")
1204 .payload
1205 .to_vec();
1206 assert_eq!(
1207 decode(&payload[..40]),
1208 Err(DecodeError::Truncated { message: 1019 })
1209 );
1210 let mut padded = payload;
1211 padded.push(0);
1212 assert_eq!(
1213 decode(&padded),
1214 Err(DecodeError::WrongLength {
1215 message: 1019,
1216 payload_bits: 496,
1217 expected: 488
1218 })
1219 );
1220 }
1221
1222 #[test]
1223 fn an_unmodelled_message_is_reported_rather_than_failed() {
1224 let mut w = Writer::default();
1227 w.put(1074, 12);
1228 w.put(0, 20);
1229 assert_eq!(decode_first(&w.frame()), Message::Unsupported(1074));
1230 assert_eq!(decode(&[0x43]), Err(DecodeError::Truncated { message: 0 }));
1232 }
1233
1234 #[test]
1235 fn a_satellite_count_larger_than_the_field_can_hold_is_refused() {
1236 let mut w = Writer::default();
1239 w.put(1057, 12);
1240 w.put(302_400, 20);
1241 w.put(6, 4);
1242 w.put(0, 1);
1243 w.put(0, 1);
1244 w.put(5, 4);
1245 w.put(0, 16);
1246 w.put(0, 4);
1247 w.put(63, 6);
1248 assert_eq!(
1249 decode(
1250 RtcmDecoder::new(&w.frame())
1251 .next()
1252 .expect("a frame")
1253 .payload
1254 ),
1255 Err(DecodeError::Truncated { message: 1057 })
1256 );
1257 }
1258
1259 #[test]
1260 fn the_orbit_correction_is_subtracted_and_the_clock_correction_added() {
1261 let corrected = apply_orbit(
1265 [26_000_000.0, 0.0, 0.0],
1266 [0.0, 3_000.0, 0.0],
1267 [1.0, 2.0, 3.0],
1268 )
1269 .expect("a non-degenerate state");
1270 assert!((corrected[0] - 25_999_999.0).abs() < 1e-9, "{corrected:?}");
1271 assert!((corrected[1] + 2.0).abs() < 1e-9, "{corrected:?}");
1272 assert!((corrected[2] + 3.0).abs() < 1e-9, "{corrected:?}");
1273
1274 let unchanged = apply_orbit([1.0e7, 2.0e7, 3.0e6], [-1.0e3, 2.0e3, 5.0e2], [0.0; 3])
1277 .expect("a non-degenerate state");
1278 assert!((unchanged[0] - 1.0e7).abs() < 1e-6, "{unchanged:?}");
1279 assert!((unchanged[1] - 2.0e7).abs() < 1e-6, "{unchanged:?}");
1280 assert!((unchanged[2] - 3.0e6).abs() < 1e-6, "{unchanged:?}");
1281
1282 let clock = ClockCorrection {
1283 prn: 1,
1284 reference: 100.0,
1285 coefficients: [SPEED_OF_LIGHT * 1e-6, 0.0, 0.0],
1286 };
1287 assert!((clock.seconds_at(100.0) - 1e-6).abs() < 1e-18);
1288 }
1289
1290 #[test]
1291 fn a_degenerate_state_has_no_orbital_basis_and_says_so() {
1292 assert!(apply_orbit([7e6, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 1.0]).is_none());
1295 assert!(apply_orbit([0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 1.0]).is_none());
1296 assert!(apply_orbit([f64::NAN, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 1.0]).is_none());
1297 }
1298
1299 #[test]
1300 fn the_correction_polynomials_are_the_ones_the_standard_defines() {
1301 let orbit = OrbitCorrection {
1302 prn: 5,
1303 iode: 42,
1304 reference: 60.0,
1305 delta: [1.0, 2.0, 3.0],
1306 delta_rate: [0.1, 0.2, 0.3],
1307 };
1308 let at = orbit.at(70.0);
1309 assert!((at[0] - 2.0).abs() < 1e-12, "{at:?}");
1310 assert!((at[1] - 4.0).abs() < 1e-12, "{at:?}");
1311 assert!((at[2] - 6.0).abs() < 1e-12, "{at:?}");
1312
1313 let clock = ClockCorrection {
1315 prn: 5,
1316 reference: 60.0,
1317 coefficients: [1.0, 0.5, 0.25],
1318 };
1319 assert!((clock.at(70.0) - 31.0).abs() < 1e-12, "{}", clock.at(70.0));
1320 assert!((clock.at(50.0) - 21.0).abs() < 1e-12, "{}", clock.at(50.0));
1322 }
1323
1324 #[test]
1325 fn the_update_interval_table_is_the_one_df391_names() {
1326 assert!((SSR_UPDATE_INTERVALS[0] - 1.0).abs() < 1e-12);
1329 assert!((SSR_UPDATE_INTERVALS[6] - 60.0).abs() < 1e-12);
1330 assert!((SSR_UPDATE_INTERVALS[7] - 120.0).abs() < 1e-12);
1331 assert!((SSR_UPDATE_INTERVALS[15] - 10_800.0).abs() < 1e-12);
1332 assert!(
1333 SSR_UPDATE_INTERVALS.windows(2).all(|w| w[1] > w[0]),
1334 "the table must be strictly increasing"
1335 );
1336 }
1337
1338 #[test]
1339 fn the_bit_reader_sign_extends_and_refuses_to_read_past_the_end() {
1340 let bytes = [0b1000_0000_u8, 0b0000_0001];
1341 let mut bits = Bits::new(&bytes);
1342 assert_eq!(bits.i64(16), Some(-32_767));
1344 assert_eq!(bits.u64(1), None);
1345
1346 let mut bits = Bits::new(&bytes);
1347 assert_eq!(bits.u64(16), Some(0x8001));
1348 assert_eq!(bits.consumed(), 16);
1349
1350 let mut bits = Bits::new(&[0b1000_0000]);
1352 assert_eq!(bits.i64(1), Some(-1));
1353 assert_eq!(bits.i64(1), Some(0));
1354
1355 assert_eq!(Bits::new(&bytes).u64(65), None);
1357
1358 let mut bits = Bits::new(&[0x40, 0x00, 0x00, 0x00]);
1360 assert_eq!(bits.scaled(32, -31), Some(0.5));
1361 }
1362
1363 #[test]
1364 fn the_width_guard_is_separate_from_the_buffer_guard() {
1365 let wide = [0xFF_u8; 9];
1369 assert_eq!(Bits::new(&wide).u64(65), None);
1370 assert_eq!(Bits::new(&wide).u64(64), Some(u64::MAX));
1372 }
1373
1374 #[test]
1375 fn the_radial_along_cross_basis_is_generic_and_not_a_permutation_of_the_axes() {
1376 let corrected = apply_orbit(
1381 [7.0e6, 2.0e6, 3.0e6],
1382 [-1.0e3, 6.0e3, 2.0e3],
1383 [1.0, 2.0, 3.0],
1384 )
1385 .expect("a non-degenerate state");
1386 let expected = [
1387 7_000_000.682_143_165,
1388 1_999_998.975_730_011_2,
1389 2_999_996.466_509_984,
1390 ];
1391 for (got, want) in corrected.iter().zip(expected) {
1392 assert!((got - want).abs() < 1e-6, "{corrected:?} vs {expected:?}");
1394 }
1395 }
1396
1397 #[test]
1398 fn the_scanner_reports_how_much_it_has_not_scanned() {
1399 let stream = ssr_frame(1057);
1400 let mut decoder = RtcmDecoder::new(&stream);
1401 assert_eq!(decoder.remaining(), stream.len());
1402 assert!(decoder.next().is_some());
1403 assert_eq!(decoder.remaining(), 0);
1404 }
1405
1406 #[test]
1407 fn a_frame_past_255_bytes_needs_both_halves_of_the_length_field() {
1408 let mut w = Writer::default();
1412 w.put(1077, 12); for i in 0..300_u64 {
1414 w.put(i & 0xFF, 8);
1415 }
1416 let frame = w.frame();
1417 assert_eq!(frame.len(), 308, "3 header + 302 payload + 3 CRC");
1418 let framed: Vec<Frame> = RtcmDecoder::new(&frame).collect();
1419 assert_eq!(framed.len(), 1);
1420 assert_eq!(framed[0].payload.len(), 302);
1421 assert!(matches!(
1422 decode(framed[0].payload),
1423 Ok(Message::Unsupported(1077))
1424 ));
1425 }
1426}