1use crate::error::{SbfError, SbfResult};
6use crate::header::SbfHeader;
7
8use super::block_ids;
9use super::dnu::{f32_or_none, f64_or_none};
10use super::SbfBlockParse;
11
12#[cfg(test)]
13use super::dnu::{F32_DNU, F64_DNU};
14
15#[derive(Debug, Clone)]
23pub struct GpsNavBlock {
24 tow_ms: u32,
25 wnc: u16,
26 pub prn: u8,
28 pub wn: u16,
30 pub ca_or_p_on_l2: u8,
32 pub ura: u8,
34 pub health: u8,
36 pub l2_data_flag: u8,
38 pub iodc: u16,
40 pub iode2: u8,
42 pub iode3: u8,
44 pub fit_int_flag: u8,
46 pub t_gd: f32,
48 pub t_oc: u32,
50 pub a_f2: f32,
52 pub a_f1: f32,
54 pub a_f0: f32,
56 pub c_rs: f32,
58 pub delta_n: f32,
60 pub m_0: f64,
62 pub c_uc: f32,
64 pub e: f64,
66 pub c_us: f32,
68 pub sqrt_a: f64,
70 pub t_oe: u32,
72 pub c_ic: f32,
74 pub omega_0: f64,
76 pub c_is: f32,
78 pub i_0: f64,
80 pub c_rc: f32,
82 pub omega: f64,
84 pub omega_dot: f32,
86 pub i_dot: f32,
88 pub wnt_oc: u16,
90 pub wnt_oe: u16,
92}
93
94impl GpsNavBlock {
95 pub fn tow_seconds(&self) -> f64 {
96 self.tow_ms as f64 * 0.001
97 }
98 pub fn tow_ms(&self) -> u32 {
99 self.tow_ms
100 }
101 pub fn wnc(&self) -> u16 {
102 self.wnc
103 }
104
105 pub fn is_healthy(&self) -> bool {
107 self.health == 0
108 }
109
110 pub fn semi_major_axis_m(&self) -> f64 {
112 self.sqrt_a * self.sqrt_a
113 }
114
115 pub fn iode_consistent(&self) -> bool {
117 self.iode2 == self.iode3
118 }
119}
120
121impl SbfBlockParse for GpsNavBlock {
122 const BLOCK_ID: u16 = block_ids::GPS_NAV;
123
124 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
125 if data.len() < 140 {
126 return Err(SbfError::ParseError("GPSNav too short".into()));
127 }
128
129 let prn = data[12];
167 let wn = u16::from_le_bytes([data[14], data[15]]);
168 let ca_or_p_on_l2 = data[16];
169 let ura = data[17];
170 let health = data[18];
171 let l2_data_flag = data[19];
172 let iodc = u16::from_le_bytes([data[20], data[21]]);
173 let iode2 = data[22];
174 let iode3 = data[23];
175 let fit_int_flag = data[24];
176
177 let t_gd = f32::from_le_bytes(data[26..30].try_into().unwrap());
178 let t_oc = u32::from_le_bytes(data[30..34].try_into().unwrap());
179 let a_f2 = f32::from_le_bytes(data[34..38].try_into().unwrap());
180 let a_f1 = f32::from_le_bytes(data[38..42].try_into().unwrap());
181 let a_f0 = f32::from_le_bytes(data[42..46].try_into().unwrap());
182 let c_rs = f32::from_le_bytes(data[46..50].try_into().unwrap());
183 let delta_n = f32::from_le_bytes(data[50..54].try_into().unwrap());
184 let m_0 = f64::from_le_bytes(data[54..62].try_into().unwrap());
185 let c_uc = f32::from_le_bytes(data[62..66].try_into().unwrap());
186 let e = f64::from_le_bytes(data[66..74].try_into().unwrap());
187 let c_us = f32::from_le_bytes(data[74..78].try_into().unwrap());
188 let sqrt_a = f64::from_le_bytes(data[78..86].try_into().unwrap());
189 let t_oe = u32::from_le_bytes(data[86..90].try_into().unwrap());
190 let c_ic = f32::from_le_bytes(data[90..94].try_into().unwrap());
191 let omega_0 = f64::from_le_bytes(data[94..102].try_into().unwrap());
192 let c_is = f32::from_le_bytes(data[102..106].try_into().unwrap());
193 let i_0 = f64::from_le_bytes(data[106..114].try_into().unwrap());
194 let c_rc = f32::from_le_bytes(data[114..118].try_into().unwrap());
195 let omega = f64::from_le_bytes(data[118..126].try_into().unwrap());
196 let omega_dot = f32::from_le_bytes(data[126..130].try_into().unwrap());
197 let i_dot = f32::from_le_bytes(data[130..134].try_into().unwrap());
198 let wnt_oc = u16::from_le_bytes([data[134], data[135]]);
199 let wnt_oe = u16::from_le_bytes([data[136], data[137]]);
200
201 Ok(Self {
202 tow_ms: header.tow_ms,
203 wnc: header.wnc,
204 prn,
205 wn,
206 ca_or_p_on_l2,
207 ura,
208 health,
209 l2_data_flag,
210 iodc,
211 iode2,
212 iode3,
213 fit_int_flag,
214 t_gd,
215 t_oc,
216 a_f2,
217 a_f1,
218 a_f0,
219 c_rs,
220 delta_n,
221 m_0,
222 c_uc,
223 e,
224 c_us,
225 sqrt_a,
226 t_oe,
227 c_ic,
228 omega_0,
229 c_is,
230 i_0,
231 c_rc,
232 omega,
233 omega_dot,
234 i_dot,
235 wnt_oc,
236 wnt_oe,
237 })
238 }
239}
240
241#[derive(Debug, Clone)]
249pub struct GalNavBlock {
250 tow_ms: u32,
251 wnc: u16,
252 pub svid: u8,
254 pub source: u8,
256 pub sqrt_a: f64,
258 pub m_0: f64,
260 pub e: f64,
262 pub i_0: f64,
264 pub omega: f64,
266 pub omega_0: f64,
268 pub omega_dot: f32,
270 pub i_dot: f32,
272 pub delta_n: f32,
274 pub c_uc: f32,
276 pub c_us: f32,
278 pub c_rc: f32,
280 pub c_rs: f32,
282 pub c_ic: f32,
284 pub c_is: f32,
286 pub t_oe: u32,
288 pub t_oc: u32,
290 pub a_f2: f32,
292 pub a_f1: f32,
294 pub a_f0: f64,
296 pub wnt_oc: u16,
298 pub wnt_oe: u16,
300 pub iod_nav: u16,
302 pub health_os_sol: u16,
304 pub sisa_l1e5a: u8,
306 pub sisa_l1e5b: u8,
308 pub bgd_l1e5a: f32,
310 pub bgd_l1e5b: f32,
312}
313
314impl GalNavBlock {
315 pub fn tow_seconds(&self) -> f64 {
316 self.tow_ms as f64 * 0.001
317 }
318 pub fn tow_ms(&self) -> u32 {
319 self.tow_ms
320 }
321 pub fn wnc(&self) -> u16 {
322 self.wnc
323 }
324
325 pub fn prn(&self) -> u8 {
327 if self.svid >= 71 && self.svid <= 106 {
328 self.svid - 70
329 } else {
330 self.svid
331 }
332 }
333
334 pub fn is_fnav(&self) -> bool {
336 self.source == 16
337 }
338
339 pub fn is_inav(&self) -> bool {
341 self.source == 2
342 }
343
344 pub fn semi_major_axis_m(&self) -> f64 {
346 self.sqrt_a * self.sqrt_a
347 }
348}
349
350impl SbfBlockParse for GalNavBlock {
351 const BLOCK_ID: u16 = block_ids::GAL_NAV;
352
353 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
354 if data.len() < 144 {
355 return Err(SbfError::ParseError("GALNav too short".into()));
356 }
357
358 let svid = data[12];
359 let source = data[13];
360
361 let sqrt_a = f64::from_le_bytes(data[14..22].try_into().unwrap());
362 let m_0 = f64::from_le_bytes(data[22..30].try_into().unwrap());
363 let e = f64::from_le_bytes(data[30..38].try_into().unwrap());
364 let i_0 = f64::from_le_bytes(data[38..46].try_into().unwrap());
365 let omega = f64::from_le_bytes(data[46..54].try_into().unwrap());
366 let omega_0 = f64::from_le_bytes(data[54..62].try_into().unwrap());
367 let omega_dot = f32::from_le_bytes(data[62..66].try_into().unwrap());
368 let i_dot = f32::from_le_bytes(data[66..70].try_into().unwrap());
369 let delta_n = f32::from_le_bytes(data[70..74].try_into().unwrap());
370 let c_uc = f32::from_le_bytes(data[74..78].try_into().unwrap());
371 let c_us = f32::from_le_bytes(data[78..82].try_into().unwrap());
372 let c_rc = f32::from_le_bytes(data[82..86].try_into().unwrap());
373 let c_rs = f32::from_le_bytes(data[86..90].try_into().unwrap());
374 let c_ic = f32::from_le_bytes(data[90..94].try_into().unwrap());
375 let c_is = f32::from_le_bytes(data[94..98].try_into().unwrap());
376 let t_oe = u32::from_le_bytes(data[98..102].try_into().unwrap());
377 let t_oc = u32::from_le_bytes(data[102..106].try_into().unwrap());
378 let a_f2 = f32::from_le_bytes(data[106..110].try_into().unwrap());
379 let a_f1 = f32::from_le_bytes(data[110..114].try_into().unwrap());
380 let a_f0 = f64::from_le_bytes(data[114..122].try_into().unwrap());
381 let wnt_oc = u16::from_le_bytes([data[122], data[123]]);
382 let wnt_oe = u16::from_le_bytes([data[124], data[125]]);
383 let iod_nav = u16::from_le_bytes([data[126], data[127]]);
384 let health_os_sol = u16::from_le_bytes([data[128], data[129]]);
385 let sisa_l1e5a = data[130];
386 let sisa_l1e5b = data[131];
387 let bgd_l1e5a = f32::from_le_bytes(data[132..136].try_into().unwrap());
388 let bgd_l1e5b = f32::from_le_bytes(data[136..140].try_into().unwrap());
389
390 Ok(Self {
391 tow_ms: header.tow_ms,
392 wnc: header.wnc,
393 svid,
394 source,
395 sqrt_a,
396 m_0,
397 e,
398 i_0,
399 omega,
400 omega_0,
401 omega_dot,
402 i_dot,
403 delta_n,
404 c_uc,
405 c_us,
406 c_rc,
407 c_rs,
408 c_ic,
409 c_is,
410 t_oe,
411 t_oc,
412 a_f2,
413 a_f1,
414 a_f0,
415 wnt_oc,
416 wnt_oe,
417 iod_nav,
418 health_os_sol,
419 sisa_l1e5a,
420 sisa_l1e5b,
421 bgd_l1e5a,
422 bgd_l1e5b,
423 })
424 }
425}
426
427#[derive(Debug, Clone)]
436pub struct GloNavBlock {
437 tow_ms: u32,
438 wnc: u16,
439 pub svid: u8,
441 pub freq_nr: i8,
443 pub x_km: f64,
445 pub y_km: f64,
447 pub z_km: f64,
449 pub dx_kmps: f32,
451 pub dy_kmps: f32,
453 pub dz_kmps: f32,
455 pub ddx_kmps2: f32,
457 pub ddy_kmps2: f32,
459 pub ddz_kmps2: f32,
461 pub gamma: f32,
463 pub tau: f32,
465 pub dtau: f32,
467 pub t_oe: u32,
469 pub wn_toe: u16,
471 pub p1: u8,
473 pub p2: u8,
475 pub e_age: u8,
477 pub b_health: u8,
479 pub tb: u16,
481 pub m_type: u8,
483 pub p_mode: u8,
485 pub l_health: u8,
487 pub p4: u8,
489 pub n_t: u16,
491 pub f_t: u16,
493}
494
495impl GloNavBlock {
496 pub fn tow_seconds(&self) -> f64 {
497 self.tow_ms as f64 * 0.001
498 }
499 pub fn tow_ms(&self) -> u32 {
500 self.tow_ms
501 }
502 pub fn wnc(&self) -> u16 {
503 self.wnc
504 }
505
506 pub fn slot(&self) -> u8 {
508 if self.svid >= 38 && self.svid <= 61 {
509 self.svid - 37
510 } else {
511 self.svid
512 }
513 }
514
515 pub fn position_m(&self) -> (f64, f64, f64) {
517 (self.x_km * 1000.0, self.y_km * 1000.0, self.z_km * 1000.0)
518 }
519
520 pub fn velocity_mps(&self) -> (f64, f64, f64) {
522 (
523 self.dx_kmps as f64 * 1000.0,
524 self.dy_kmps as f64 * 1000.0,
525 self.dz_kmps as f64 * 1000.0,
526 )
527 }
528
529 pub fn acceleration_mps2(&self) -> (f64, f64, f64) {
531 (
532 self.ddx_kmps2 as f64 * 1000.0,
533 self.ddy_kmps2 as f64 * 1000.0,
534 self.ddz_kmps2 as f64 * 1000.0,
535 )
536 }
537
538 pub fn is_healthy(&self) -> bool {
540 self.b_health == 0 && self.l_health == 0
541 }
542}
543
544impl SbfBlockParse for GloNavBlock {
545 const BLOCK_ID: u16 = block_ids::GLO_NAV;
546
547 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
548 if data.len() < 96 {
549 return Err(SbfError::ParseError("GLONav too short".into()));
550 }
551
552 let svid = data[12];
553 let freq_nr = data[13] as i8;
554
555 let x_km = f64::from_le_bytes(data[14..22].try_into().unwrap());
556 let y_km = f64::from_le_bytes(data[22..30].try_into().unwrap());
557 let z_km = f64::from_le_bytes(data[30..38].try_into().unwrap());
558 let dx_kmps = f32::from_le_bytes(data[38..42].try_into().unwrap());
559 let dy_kmps = f32::from_le_bytes(data[42..46].try_into().unwrap());
560 let dz_kmps = f32::from_le_bytes(data[46..50].try_into().unwrap());
561 let ddx_kmps2 = f32::from_le_bytes(data[50..54].try_into().unwrap());
562 let ddy_kmps2 = f32::from_le_bytes(data[54..58].try_into().unwrap());
563 let ddz_kmps2 = f32::from_le_bytes(data[58..62].try_into().unwrap());
564 let gamma = f32::from_le_bytes(data[62..66].try_into().unwrap());
565 let tau = f32::from_le_bytes(data[66..70].try_into().unwrap());
566 let dtau = f32::from_le_bytes(data[70..74].try_into().unwrap());
567 let t_oe = u32::from_le_bytes(data[74..78].try_into().unwrap());
568 let wn_toe = u16::from_le_bytes([data[78], data[79]]);
569 let p1 = data[80];
570 let p2 = data[81];
571 let e_age = data[82];
572 let b_health = data[83];
573 let tb = u16::from_le_bytes([data[84], data[85]]);
574 let m_type = data[86];
575 let p_mode = data[87];
576 let l_health = data[88];
577 let p4 = data[89];
578 let n_t = u16::from_le_bytes([data[90], data[91]]);
579 let f_t = u16::from_le_bytes([data[92], data[93]]);
580
581 Ok(Self {
582 tow_ms: header.tow_ms,
583 wnc: header.wnc,
584 svid,
585 freq_nr,
586 x_km,
587 y_km,
588 z_km,
589 dx_kmps,
590 dy_kmps,
591 dz_kmps,
592 ddx_kmps2,
593 ddy_kmps2,
594 ddz_kmps2,
595 gamma,
596 tau,
597 dtau,
598 t_oe,
599 wn_toe,
600 p1,
601 p2,
602 e_age,
603 b_health,
604 tb,
605 m_type,
606 p_mode,
607 l_health,
608 p4,
609 n_t,
610 f_t,
611 })
612 }
613}
614
615#[derive(Debug, Clone)]
623pub struct GpsAlmBlock {
624 tow_ms: u32,
625 wnc: u16,
626 pub prn: u8,
628 pub e: f32,
630 pub t_oa: u32,
632 pub delta_i: f32,
634 pub omega_dot: f32,
636 pub sqrt_a: f32,
638 pub omega_0: f32,
640 pub omega: f32,
642 pub m_0: f32,
644 pub a_f1: f32,
646 pub a_f0: f32,
648 pub wn_a: u8,
650 pub as_config: u8,
652 pub health8: u8,
654 pub health6: u8,
656}
657
658impl GpsAlmBlock {
659 pub fn tow_seconds(&self) -> f64 {
660 self.tow_ms as f64 * 0.001
661 }
662 pub fn tow_ms(&self) -> u32 {
663 self.tow_ms
664 }
665 pub fn wnc(&self) -> u16 {
666 self.wnc
667 }
668
669 pub fn eccentricity(&self) -> Option<f32> {
671 f32_or_none(self.e)
672 }
673
674 pub fn semi_major_axis_m(&self) -> Option<f32> {
676 f32_or_none(self.sqrt_a).map(|value| value * value)
677 }
678
679 pub fn clock_bias_s(&self) -> Option<f32> {
681 f32_or_none(self.a_f0)
682 }
683}
684
685impl SbfBlockParse for GpsAlmBlock {
686 const BLOCK_ID: u16 = block_ids::GPS_ALM;
687
688 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
689 if data.len() < 57 {
690 return Err(SbfError::ParseError("GPSAlm too short".into()));
691 }
692
693 let prn = data[12];
694 let e = f32::from_le_bytes(data[13..17].try_into().unwrap());
695 let t_oa = u32::from_le_bytes(data[17..21].try_into().unwrap());
696 let delta_i = f32::from_le_bytes(data[21..25].try_into().unwrap());
697 let omega_dot = f32::from_le_bytes(data[25..29].try_into().unwrap());
698 let sqrt_a = f32::from_le_bytes(data[29..33].try_into().unwrap());
699 let omega_0 = f32::from_le_bytes(data[33..37].try_into().unwrap());
700 let omega = f32::from_le_bytes(data[37..41].try_into().unwrap());
701 let m_0 = f32::from_le_bytes(data[41..45].try_into().unwrap());
702 let a_f1 = f32::from_le_bytes(data[45..49].try_into().unwrap());
703 let a_f0 = f32::from_le_bytes(data[49..53].try_into().unwrap());
704 let wn_a = data[53];
705 let as_config = data[54];
706 let health8 = data[55];
707 let health6 = data[56];
708
709 Ok(Self {
710 tow_ms: header.tow_ms,
711 wnc: header.wnc,
712 prn,
713 e,
714 t_oa,
715 delta_i,
716 omega_dot,
717 sqrt_a,
718 omega_0,
719 omega,
720 m_0,
721 a_f1,
722 a_f0,
723 wn_a,
724 as_config,
725 health8,
726 health6,
727 })
728 }
729}
730
731#[derive(Debug, Clone)]
739pub struct GpsIonBlock {
740 tow_ms: u32,
741 wnc: u16,
742 pub prn: u8,
744 pub alpha_0: f32,
746 pub alpha_1: f32,
748 pub alpha_2: f32,
750 pub alpha_3: f32,
752 pub beta_0: f32,
754 pub beta_1: f32,
756 pub beta_2: f32,
758 pub beta_3: f32,
760}
761
762impl GpsIonBlock {
763 pub fn tow_seconds(&self) -> f64 {
764 self.tow_ms as f64 * 0.001
765 }
766 pub fn tow_ms(&self) -> u32 {
767 self.tow_ms
768 }
769 pub fn wnc(&self) -> u16 {
770 self.wnc
771 }
772
773 pub fn alpha_0(&self) -> Option<f32> {
774 f32_or_none(self.alpha_0)
775 }
776
777 pub fn beta_0(&self) -> Option<f32> {
778 f32_or_none(self.beta_0)
779 }
780}
781
782impl SbfBlockParse for GpsIonBlock {
783 const BLOCK_ID: u16 = block_ids::GPS_ION;
784
785 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
786 if data.len() < 45 {
787 return Err(SbfError::ParseError("GPSIon too short".into()));
788 }
789
790 let prn = data[12];
791 let alpha_0 = f32::from_le_bytes(data[13..17].try_into().unwrap());
792 let alpha_1 = f32::from_le_bytes(data[17..21].try_into().unwrap());
793 let alpha_2 = f32::from_le_bytes(data[21..25].try_into().unwrap());
794 let alpha_3 = f32::from_le_bytes(data[25..29].try_into().unwrap());
795 let beta_0 = f32::from_le_bytes(data[29..33].try_into().unwrap());
796 let beta_1 = f32::from_le_bytes(data[33..37].try_into().unwrap());
797 let beta_2 = f32::from_le_bytes(data[37..41].try_into().unwrap());
798 let beta_3 = f32::from_le_bytes(data[41..45].try_into().unwrap());
799
800 Ok(Self {
801 tow_ms: header.tow_ms,
802 wnc: header.wnc,
803 prn,
804 alpha_0,
805 alpha_1,
806 alpha_2,
807 alpha_3,
808 beta_0,
809 beta_1,
810 beta_2,
811 beta_3,
812 })
813 }
814}
815
816#[derive(Debug, Clone)]
824pub struct GpsUtcBlock {
825 tow_ms: u32,
826 wnc: u16,
827 pub prn: u8,
829 pub a_1: f32,
831 pub a_0: f64,
833 pub t_ot: u32,
835 pub wn_t: u8,
837 pub delta_t_ls: i8,
839 pub wn_lsf: u8,
841 pub dn: u8,
843 pub delta_t_lsf: i8,
845}
846
847impl GpsUtcBlock {
848 pub fn tow_seconds(&self) -> f64 {
849 self.tow_ms as f64 * 0.001
850 }
851 pub fn tow_ms(&self) -> u32 {
852 self.tow_ms
853 }
854 pub fn wnc(&self) -> u16 {
855 self.wnc
856 }
857
858 pub fn utc_bias_s(&self) -> Option<f64> {
859 f64_or_none(self.a_0)
860 }
861
862 pub fn utc_drift_s_per_s(&self) -> Option<f32> {
863 f32_or_none(self.a_1)
864 }
865}
866
867impl SbfBlockParse for GpsUtcBlock {
868 const BLOCK_ID: u16 = block_ids::GPS_UTC;
869
870 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
871 if data.len() < 34 {
872 return Err(SbfError::ParseError("GPSUtc too short".into()));
873 }
874
875 let prn = data[12];
876 let a_1 = f32::from_le_bytes(data[13..17].try_into().unwrap());
877 let a_0 = f64::from_le_bytes(data[17..25].try_into().unwrap());
878 let t_ot = u32::from_le_bytes(data[25..29].try_into().unwrap());
879 let wn_t = data[29];
880 let delta_t_ls = data[30] as i8;
881 let wn_lsf = data[31];
882 let dn = data[32];
883 let delta_t_lsf = data[33] as i8;
884
885 Ok(Self {
886 tow_ms: header.tow_ms,
887 wnc: header.wnc,
888 prn,
889 a_1,
890 a_0,
891 t_ot,
892 wn_t,
893 delta_t_ls,
894 wn_lsf,
895 dn,
896 delta_t_lsf,
897 })
898 }
899}
900
901#[derive(Debug, Clone)]
909pub struct GloAlmBlock {
910 tow_ms: u32,
911 wnc: u16,
912 pub svid: u8,
914 pub freq_nr: i8,
916 pub epsilon: f32,
918 pub t_oa: u32,
920 pub delta_i: f32,
922 pub lambda: f32,
924 pub t_ln: f32,
926 pub omega: f32,
928 pub delta_t: f32,
930 pub d_delta_t: f32,
932 pub tau: f32,
934 pub wn_a: u8,
936 pub c: u8,
938 pub n: u16,
940 pub m_type: u8,
942 pub n_4: u8,
944}
945
946impl GloAlmBlock {
947 pub fn tow_seconds(&self) -> f64 {
948 self.tow_ms as f64 * 0.001
949 }
950 pub fn tow_ms(&self) -> u32 {
951 self.tow_ms
952 }
953 pub fn wnc(&self) -> u16 {
954 self.wnc
955 }
956
957 pub fn slot(&self) -> u8 {
959 if self.svid >= 38 && self.svid <= 61 {
960 self.svid - 37
961 } else {
962 self.svid
963 }
964 }
965
966 pub fn eccentricity(&self) -> Option<f32> {
967 f32_or_none(self.epsilon)
968 }
969
970 pub fn clock_bias_s(&self) -> Option<f32> {
971 f32_or_none(self.tau)
972 }
973}
974
975impl SbfBlockParse for GloAlmBlock {
976 const BLOCK_ID: u16 = block_ids::GLO_ALM;
977
978 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
979 if data.len() < 56 {
980 return Err(SbfError::ParseError("GLOAlm too short".into()));
981 }
982
983 let svid = data[12];
984 let freq_nr = data[13] as i8;
985 let epsilon = f32::from_le_bytes(data[14..18].try_into().unwrap());
986 let t_oa = u32::from_le_bytes(data[18..22].try_into().unwrap());
987 let delta_i = f32::from_le_bytes(data[22..26].try_into().unwrap());
988 let lambda = f32::from_le_bytes(data[26..30].try_into().unwrap());
989 let t_ln = f32::from_le_bytes(data[30..34].try_into().unwrap());
990 let omega = f32::from_le_bytes(data[34..38].try_into().unwrap());
991 let delta_t = f32::from_le_bytes(data[38..42].try_into().unwrap());
992 let d_delta_t = f32::from_le_bytes(data[42..46].try_into().unwrap());
993 let tau = f32::from_le_bytes(data[46..50].try_into().unwrap());
994 let wn_a = data[50];
995 let c = data[51];
996 let n = u16::from_le_bytes([data[52], data[53]]);
997 let m_type = data[54];
998 let n_4 = data[55];
999
1000 Ok(Self {
1001 tow_ms: header.tow_ms,
1002 wnc: header.wnc,
1003 svid,
1004 freq_nr,
1005 epsilon,
1006 t_oa,
1007 delta_i,
1008 lambda,
1009 t_ln,
1010 omega,
1011 delta_t,
1012 d_delta_t,
1013 tau,
1014 wn_a,
1015 c,
1016 n,
1017 m_type,
1018 n_4,
1019 })
1020 }
1021}
1022
1023#[derive(Debug, Clone)]
1031pub struct GloTimeBlock {
1032 tow_ms: u32,
1033 wnc: u16,
1034 pub svid: u8,
1036 pub freq_nr: i8,
1038 pub n_4: u8,
1040 pub kp: u8,
1042 pub n: u16,
1044 pub tau_gps: f32,
1046 pub tau_c: f64,
1048 pub b1: f32,
1050 pub b2: f32,
1052}
1053
1054impl GloTimeBlock {
1055 pub fn tow_seconds(&self) -> f64 {
1056 self.tow_ms as f64 * 0.001
1057 }
1058 pub fn tow_ms(&self) -> u32 {
1059 self.tow_ms
1060 }
1061 pub fn wnc(&self) -> u16 {
1062 self.wnc
1063 }
1064
1065 pub fn slot(&self) -> u8 {
1067 if self.svid >= 38 && self.svid <= 61 {
1068 self.svid - 37
1069 } else {
1070 self.svid
1071 }
1072 }
1073
1074 pub fn gps_glonass_offset_s(&self) -> Option<f32> {
1075 f32_or_none(self.tau_gps)
1076 }
1077
1078 pub fn time_scale_correction_s(&self) -> Option<f64> {
1079 f64_or_none(self.tau_c)
1080 }
1081}
1082
1083impl SbfBlockParse for GloTimeBlock {
1084 const BLOCK_ID: u16 = block_ids::GLO_TIME;
1085
1086 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1087 if data.len() < 38 {
1088 return Err(SbfError::ParseError("GLOTime too short".into()));
1089 }
1090
1091 let svid = data[12];
1092 let freq_nr = data[13] as i8;
1093 let n_4 = data[14];
1094 let kp = data[15];
1095 let n = u16::from_le_bytes([data[16], data[17]]);
1096 let tau_gps = f32::from_le_bytes(data[18..22].try_into().unwrap());
1097 let tau_c = f64::from_le_bytes(data[22..30].try_into().unwrap());
1098 let b1 = f32::from_le_bytes(data[30..34].try_into().unwrap());
1099 let b2 = f32::from_le_bytes(data[34..38].try_into().unwrap());
1100
1101 Ok(Self {
1102 tow_ms: header.tow_ms,
1103 wnc: header.wnc,
1104 svid,
1105 freq_nr,
1106 n_4,
1107 kp,
1108 n,
1109 tau_gps,
1110 tau_c,
1111 b1,
1112 b2,
1113 })
1114 }
1115}
1116
1117#[derive(Debug, Clone)]
1125pub struct GalAlmBlock {
1126 tow_ms: u32,
1127 wnc: u16,
1128 pub svid: u8,
1130 pub source: u8,
1132 pub e: f32,
1134 pub t_oa: u32,
1136 pub delta_i: f32,
1138 pub omega_dot: f32,
1140 pub delta_sqrt_a: f32,
1142 pub omega_0: f32,
1144 pub omega: f32,
1146 pub m_0: f32,
1148 pub a_f1: f32,
1150 pub a_f0: f32,
1152 pub wn_a: u8,
1154 pub svid_a: u8,
1156 pub health: u16,
1158 pub ioda: u8,
1160}
1161
1162impl GalAlmBlock {
1163 pub fn tow_seconds(&self) -> f64 {
1164 self.tow_ms as f64 * 0.001
1165 }
1166 pub fn tow_ms(&self) -> u32 {
1167 self.tow_ms
1168 }
1169 pub fn wnc(&self) -> u16 {
1170 self.wnc
1171 }
1172
1173 pub fn prn(&self) -> u8 {
1175 if self.svid >= 71 && self.svid <= 106 {
1176 self.svid - 70
1177 } else {
1178 self.svid
1179 }
1180 }
1181
1182 pub fn eccentricity(&self) -> Option<f32> {
1183 f32_or_none(self.e)
1184 }
1185
1186 pub fn delta_sqrt_a(&self) -> Option<f32> {
1187 f32_or_none(self.delta_sqrt_a)
1188 }
1189}
1190
1191impl SbfBlockParse for GalAlmBlock {
1192 const BLOCK_ID: u16 = block_ids::GAL_ALM;
1193
1194 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1195 if data.len() < 59 {
1196 return Err(SbfError::ParseError("GALAlm too short".into()));
1197 }
1198
1199 let svid = data[12];
1200 let source = data[13];
1201 let e = f32::from_le_bytes(data[14..18].try_into().unwrap());
1202 let t_oa = u32::from_le_bytes(data[18..22].try_into().unwrap());
1203 let delta_i = f32::from_le_bytes(data[22..26].try_into().unwrap());
1204 let omega_dot = f32::from_le_bytes(data[26..30].try_into().unwrap());
1205 let delta_sqrt_a = f32::from_le_bytes(data[30..34].try_into().unwrap());
1206 let omega_0 = f32::from_le_bytes(data[34..38].try_into().unwrap());
1207 let omega = f32::from_le_bytes(data[38..42].try_into().unwrap());
1208 let m_0 = f32::from_le_bytes(data[42..46].try_into().unwrap());
1209 let a_f1 = f32::from_le_bytes(data[46..50].try_into().unwrap());
1210 let a_f0 = f32::from_le_bytes(data[50..54].try_into().unwrap());
1211 let wn_a = data[54];
1212 let svid_a = data[55];
1213 let health = u16::from_le_bytes([data[56], data[57]]);
1214 let ioda = data[58];
1215
1216 Ok(Self {
1217 tow_ms: header.tow_ms,
1218 wnc: header.wnc,
1219 svid,
1220 source,
1221 e,
1222 t_oa,
1223 delta_i,
1224 omega_dot,
1225 delta_sqrt_a,
1226 omega_0,
1227 omega,
1228 m_0,
1229 a_f1,
1230 a_f0,
1231 wn_a,
1232 svid_a,
1233 health,
1234 ioda,
1235 })
1236 }
1237}
1238
1239#[derive(Debug, Clone)]
1247pub struct GalIonBlock {
1248 tow_ms: u32,
1249 wnc: u16,
1250 pub svid: u8,
1252 pub source: u8,
1254 pub a_i0: f32,
1256 pub a_i1: f32,
1258 pub a_i2: f32,
1260 pub storm_flags: u8,
1262}
1263
1264impl GalIonBlock {
1265 pub fn tow_seconds(&self) -> f64 {
1266 self.tow_ms as f64 * 0.001
1267 }
1268 pub fn tow_ms(&self) -> u32 {
1269 self.tow_ms
1270 }
1271 pub fn wnc(&self) -> u16 {
1272 self.wnc
1273 }
1274
1275 pub fn is_fnav(&self) -> bool {
1277 self.source == 16
1278 }
1279
1280 pub fn is_inav(&self) -> bool {
1282 self.source == 2
1283 }
1284
1285 pub fn a_i0(&self) -> Option<f32> {
1286 f32_or_none(self.a_i0)
1287 }
1288}
1289
1290impl SbfBlockParse for GalIonBlock {
1291 const BLOCK_ID: u16 = block_ids::GAL_ION;
1292
1293 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1294 if data.len() < 27 {
1295 return Err(SbfError::ParseError("GALIon too short".into()));
1296 }
1297
1298 let svid = data[12];
1299 let source = data[13];
1300 let a_i0 = f32::from_le_bytes(data[14..18].try_into().unwrap());
1301 let a_i1 = f32::from_le_bytes(data[18..22].try_into().unwrap());
1302 let a_i2 = f32::from_le_bytes(data[22..26].try_into().unwrap());
1303 let storm_flags = data[26];
1304
1305 Ok(Self {
1306 tow_ms: header.tow_ms,
1307 wnc: header.wnc,
1308 svid,
1309 source,
1310 a_i0,
1311 a_i1,
1312 a_i2,
1313 storm_flags,
1314 })
1315 }
1316}
1317
1318#[derive(Debug, Clone)]
1326pub struct GalUtcBlock {
1327 tow_ms: u32,
1328 wnc: u16,
1329 pub svid: u8,
1331 pub source: u8,
1333 pub a_1: f32,
1335 pub a_0: f64,
1337 pub t_ot: u32,
1339 pub wn_ot: u8,
1341 pub delta_t_ls: i8,
1343 pub wn_lsf: u8,
1345 pub dn: u8,
1347 pub delta_t_lsf: i8,
1349}
1350
1351impl GalUtcBlock {
1352 pub fn tow_seconds(&self) -> f64 {
1353 self.tow_ms as f64 * 0.001
1354 }
1355 pub fn tow_ms(&self) -> u32 {
1356 self.tow_ms
1357 }
1358 pub fn wnc(&self) -> u16 {
1359 self.wnc
1360 }
1361
1362 pub fn prn(&self) -> u8 {
1364 if self.svid >= 71 && self.svid <= 106 {
1365 self.svid - 70
1366 } else {
1367 self.svid
1368 }
1369 }
1370
1371 pub fn utc_bias_s(&self) -> Option<f64> {
1372 f64_or_none(self.a_0)
1373 }
1374
1375 pub fn utc_drift_s_per_s(&self) -> Option<f32> {
1376 f32_or_none(self.a_1)
1377 }
1378}
1379
1380impl SbfBlockParse for GalUtcBlock {
1381 const BLOCK_ID: u16 = block_ids::GAL_UTC;
1382
1383 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1384 if data.len() < 35 {
1385 return Err(SbfError::ParseError("GALUtc too short".into()));
1386 }
1387
1388 let svid = data[12];
1389 let source = data[13];
1390 let a_1 = f32::from_le_bytes(data[14..18].try_into().unwrap());
1391 let a_0 = f64::from_le_bytes(data[18..26].try_into().unwrap());
1392 let t_ot = u32::from_le_bytes(data[26..30].try_into().unwrap());
1393 let wn_ot = data[30];
1394 let delta_t_ls = data[31] as i8;
1395 let wn_lsf = data[32];
1396 let dn = data[33];
1397 let delta_t_lsf = data[34] as i8;
1398
1399 Ok(Self {
1400 tow_ms: header.tow_ms,
1401 wnc: header.wnc,
1402 svid,
1403 source,
1404 a_1,
1405 a_0,
1406 t_ot,
1407 wn_ot,
1408 delta_t_ls,
1409 wn_lsf,
1410 dn,
1411 delta_t_lsf,
1412 })
1413 }
1414}
1415
1416#[derive(Debug, Clone)]
1424pub struct GalGstGpsBlock {
1425 tow_ms: u32,
1426 wnc: u16,
1427 pub svid: u8,
1429 pub source: u8,
1431 pub a_1g: f32,
1433 pub a_0g: f32,
1435 pub t_og: u32,
1437 pub wn_og: u8,
1439}
1440
1441impl GalGstGpsBlock {
1442 pub fn tow_seconds(&self) -> f64 {
1443 self.tow_ms as f64 * 0.001
1444 }
1445 pub fn tow_ms(&self) -> u32 {
1446 self.tow_ms
1447 }
1448 pub fn wnc(&self) -> u16 {
1449 self.wnc
1450 }
1451
1452 pub fn prn(&self) -> u8 {
1454 if self.svid >= 71 && self.svid <= 106 {
1455 self.svid - 70
1456 } else {
1457 self.svid
1458 }
1459 }
1460
1461 pub fn gst_gps_offset_s(&self) -> Option<f32> {
1462 f32_or_none(self.a_0g)
1463 }
1464
1465 pub fn gst_gps_drift_s_per_s(&self) -> Option<f32> {
1466 f32_or_none(self.a_1g)
1467 }
1468}
1469
1470impl SbfBlockParse for GalGstGpsBlock {
1471 const BLOCK_ID: u16 = block_ids::GAL_GST_GPS;
1472
1473 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1474 if data.len() < 27 {
1475 return Err(SbfError::ParseError("GALGstGps too short".into()));
1476 }
1477
1478 let svid = data[12];
1479 let source = data[13];
1480 let a_1g = f32::from_le_bytes(data[14..18].try_into().unwrap());
1481 let a_0g = f32::from_le_bytes(data[18..22].try_into().unwrap());
1482 let t_og = u32::from_le_bytes(data[22..26].try_into().unwrap());
1483 let wn_og = data[26];
1484
1485 Ok(Self {
1486 tow_ms: header.tow_ms,
1487 wnc: header.wnc,
1488 svid,
1489 source,
1490 a_1g,
1491 a_0g,
1492 t_og,
1493 wn_og,
1494 })
1495 }
1496}
1497
1498#[derive(Debug, Clone)]
1506pub struct GalSarRlmBlock {
1507 tow_ms: u32,
1508 wnc: u16,
1509 pub svid: u8,
1511 pub source: u8,
1513 rlm_length_bits: u8,
1515 rlm_bits_words: Vec<u32>,
1517}
1518
1519impl GalSarRlmBlock {
1520 pub fn tow_seconds(&self) -> f64 {
1521 self.tow_ms as f64 * 0.001
1522 }
1523 pub fn tow_ms(&self) -> u32 {
1524 self.tow_ms
1525 }
1526 pub fn wnc(&self) -> u16 {
1527 self.wnc
1528 }
1529
1530 pub fn prn(&self) -> u8 {
1532 if (71..=106).contains(&self.svid) {
1533 self.svid - 70
1534 } else {
1535 self.svid
1536 }
1537 }
1538
1539 pub fn rlm_length_bits(&self) -> u8 {
1540 self.rlm_length_bits
1541 }
1542
1543 pub fn rlm_bits_words(&self) -> &[u32] {
1545 &self.rlm_bits_words
1546 }
1547
1548 pub fn bit(&self, bit_index: usize) -> Option<bool> {
1550 if bit_index >= self.rlm_length_bits as usize {
1551 return None;
1552 }
1553
1554 let word_index = bit_index / 32;
1555 let bit_in_word = 31 - (bit_index % 32);
1556 self.rlm_bits_words
1557 .get(word_index)
1558 .map(|word| ((word >> bit_in_word) & 1) != 0)
1559 }
1560}
1561
1562impl SbfBlockParse for GalSarRlmBlock {
1563 const BLOCK_ID: u16 = block_ids::GAL_SAR_RLM;
1564
1565 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1566 let block_len = header.length as usize;
1567 let data_len = block_len.saturating_sub(2);
1568 if data_len < 18 || data.len() < data_len {
1569 return Err(SbfError::ParseError("GALSARRLM too short".into()));
1570 }
1571
1572 let svid = data[12];
1573 let source = data[13];
1574 let rlm_length_bits = data[14];
1575
1576 let word_count = match rlm_length_bits {
1577 80 => 3,
1578 160 => 5,
1579 0 => 0,
1580 _ => usize::from(rlm_length_bits).div_ceil(32),
1581 };
1582 let required_len = 18 + word_count * 4;
1583 if data_len < required_len {
1584 return Err(SbfError::ParseError("GALSARRLM payload too short".into()));
1585 }
1586
1587 let mut rlm_bits_words = Vec::with_capacity(word_count);
1588 let mut offset = 18;
1589 for _ in 0..word_count {
1590 rlm_bits_words.push(u32::from_le_bytes(
1591 data[offset..offset + 4].try_into().unwrap(),
1592 ));
1593 offset += 4;
1594 }
1595
1596 Ok(Self {
1597 tow_ms: header.tow_ms,
1598 wnc: header.wnc,
1599 svid,
1600 source,
1601 rlm_length_bits,
1602 rlm_bits_words,
1603 })
1604 }
1605}
1606
1607#[derive(Debug, Clone)]
1616pub struct GpsCNavBlock {
1617 tow_ms: u32,
1618 wnc: u16,
1619 pub prn: u8,
1621 pub flags: u8,
1623 pub wn: u16,
1625 pub health: u8,
1627 pub ura_ed: i8,
1629 pub t_op: u32,
1631 pub t_oe: u32,
1633 pub a: f64,
1635 pub a_dot: f64,
1637 pub delta_n: f32,
1639 pub delta_n_dot: f32,
1641 pub m_0: f64,
1643 pub e: f64,
1645 pub omega: f64,
1647 pub omega_0: f64,
1649 pub omega_dot: f64,
1651 pub i_0: f64,
1653 pub i_dot: f32,
1655 pub c_is: f32,
1657 pub c_ic: f32,
1659 pub c_rs: f32,
1661 pub c_rc: f32,
1663 pub c_us: f32,
1665 pub c_uc: f32,
1667 pub t_oc: u32,
1669 pub ura_ned0: i8,
1671 pub ura_ned1: u8,
1673 pub ura_ned2: u8,
1675 pub wn_op: u8,
1677 pub a_f2: f32,
1679 pub a_f1: f32,
1681 pub a_f0: f64,
1683 pub t_gd: f32,
1685 pub isc_l1ca: f32,
1687 pub isc_l2c: f32,
1689 pub isc_l5i5: f32,
1691 pub isc_l5q5: f32,
1693}
1694
1695impl GpsCNavBlock {
1696 pub fn tow_seconds(&self) -> f64 {
1697 self.tow_ms as f64 * 0.001
1698 }
1699 pub fn tow_ms(&self) -> u32 {
1700 self.tow_ms
1701 }
1702 pub fn wnc(&self) -> u16 {
1703 self.wnc
1704 }
1705
1706 pub fn is_alert(&self) -> bool {
1708 (self.flags & 0x01) != 0
1709 }
1710
1711 pub fn l2c_used(&self) -> bool {
1713 (self.flags & 0x40) != 0
1714 }
1715
1716 pub fn l5_used(&self) -> bool {
1718 (self.flags & 0x80) != 0
1719 }
1720
1721 pub fn is_healthy(&self) -> bool {
1723 self.health == 0
1724 }
1725
1726 pub fn group_delay_s(&self) -> Option<f32> {
1728 f32_or_none(self.t_gd)
1729 }
1730
1731 pub fn isc_l1ca_s(&self) -> Option<f32> {
1733 f32_or_none(self.isc_l1ca)
1734 }
1735
1736 pub fn isc_l2c_s(&self) -> Option<f32> {
1738 f32_or_none(self.isc_l2c)
1739 }
1740
1741 pub fn isc_l5i5_s(&self) -> Option<f32> {
1743 f32_or_none(self.isc_l5i5)
1744 }
1745
1746 pub fn isc_l5q5_s(&self) -> Option<f32> {
1748 f32_or_none(self.isc_l5q5)
1749 }
1750}
1751
1752impl SbfBlockParse for GpsCNavBlock {
1753 const BLOCK_ID: u16 = block_ids::GPS_CNAV;
1754
1755 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1756 if data.len() < 170 {
1758 return Err(SbfError::ParseError("GPSCNav too short".into()));
1759 }
1760
1761 let prn = data[12];
1801 let flags = data[13];
1802 let wn = u16::from_le_bytes([data[14], data[15]]);
1803 let health = data[16];
1804 let ura_ed = data[17] as i8;
1805 let t_op = u32::from_le_bytes(data[18..22].try_into().unwrap());
1806 let t_oe = u32::from_le_bytes(data[22..26].try_into().unwrap());
1807 let a = f64::from_le_bytes(data[26..34].try_into().unwrap());
1808 let a_dot = f64::from_le_bytes(data[34..42].try_into().unwrap());
1809 let delta_n = f32::from_le_bytes(data[42..46].try_into().unwrap());
1810 let delta_n_dot = f32::from_le_bytes(data[46..50].try_into().unwrap());
1811 let m_0 = f64::from_le_bytes(data[50..58].try_into().unwrap());
1812 let e = f64::from_le_bytes(data[58..66].try_into().unwrap());
1813 let omega = f64::from_le_bytes(data[66..74].try_into().unwrap());
1814 let omega_0 = f64::from_le_bytes(data[74..82].try_into().unwrap());
1815 let omega_dot = f64::from_le_bytes(data[82..90].try_into().unwrap());
1816 let i_0 = f64::from_le_bytes(data[90..98].try_into().unwrap());
1817 let i_dot = f32::from_le_bytes(data[98..102].try_into().unwrap());
1818 let c_is = f32::from_le_bytes(data[102..106].try_into().unwrap());
1819 let c_ic = f32::from_le_bytes(data[106..110].try_into().unwrap());
1820 let c_rs = f32::from_le_bytes(data[110..114].try_into().unwrap());
1821 let c_rc = f32::from_le_bytes(data[114..118].try_into().unwrap());
1822 let c_us = f32::from_le_bytes(data[118..122].try_into().unwrap());
1823 let c_uc = f32::from_le_bytes(data[122..126].try_into().unwrap());
1824 let t_oc = u32::from_le_bytes(data[126..130].try_into().unwrap());
1825 let ura_ned0 = data[130] as i8;
1826 let ura_ned1 = data[131];
1827 let ura_ned2 = data[132];
1828 let wn_op = data[133];
1829 let a_f2 = f32::from_le_bytes(data[134..138].try_into().unwrap());
1830 let a_f1 = f32::from_le_bytes(data[138..142].try_into().unwrap());
1831 let a_f0 = f64::from_le_bytes(data[142..150].try_into().unwrap());
1832 let t_gd = f32::from_le_bytes(data[150..154].try_into().unwrap());
1833 let isc_l1ca = f32::from_le_bytes(data[154..158].try_into().unwrap());
1834 let isc_l2c = f32::from_le_bytes(data[158..162].try_into().unwrap());
1835 let isc_l5i5 = f32::from_le_bytes(data[162..166].try_into().unwrap());
1836 let isc_l5q5 = f32::from_le_bytes(data[166..170].try_into().unwrap());
1837
1838 Ok(Self {
1839 tow_ms: header.tow_ms,
1840 wnc: header.wnc,
1841 prn,
1842 flags,
1843 wn,
1844 health,
1845 ura_ed,
1846 t_op,
1847 t_oe,
1848 a,
1849 a_dot,
1850 delta_n,
1851 delta_n_dot,
1852 m_0,
1853 e,
1854 omega,
1855 omega_0,
1856 omega_dot,
1857 i_0,
1858 i_dot,
1859 c_is,
1860 c_ic,
1861 c_rs,
1862 c_rc,
1863 c_us,
1864 c_uc,
1865 t_oc,
1866 ura_ned0,
1867 ura_ned1,
1868 ura_ned2,
1869 wn_op,
1870 a_f2,
1871 a_f1,
1872 a_f0,
1873 t_gd,
1874 isc_l1ca,
1875 isc_l2c,
1876 isc_l5i5,
1877 isc_l5q5,
1878 })
1879 }
1880}
1881
1882#[derive(Debug, Clone)]
1890pub struct BdsIonBlock {
1891 tow_ms: u32,
1892 wnc: u16,
1893 pub prn: u8,
1895 pub alpha_0: f32,
1897 pub alpha_1: f32,
1899 pub alpha_2: f32,
1901 pub alpha_3: f32,
1903 pub beta_0: f32,
1905 pub beta_1: f32,
1907 pub beta_2: f32,
1909 pub beta_3: f32,
1911}
1912
1913impl BdsIonBlock {
1914 pub fn tow_seconds(&self) -> f64 {
1915 self.tow_ms as f64 * 0.001
1916 }
1917 pub fn tow_ms(&self) -> u32 {
1918 self.tow_ms
1919 }
1920 pub fn wnc(&self) -> u16 {
1921 self.wnc
1922 }
1923
1924 pub fn alpha_0(&self) -> Option<f32> {
1925 f32_or_none(self.alpha_0)
1926 }
1927
1928 pub fn beta_0(&self) -> Option<f32> {
1929 f32_or_none(self.beta_0)
1930 }
1931}
1932
1933impl SbfBlockParse for BdsIonBlock {
1934 const BLOCK_ID: u16 = block_ids::BDS_ION;
1935
1936 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1937 if data.len() < 46 {
1939 return Err(SbfError::ParseError("BDSIon too short".into()));
1940 }
1941
1942 let prn = data[12];
1955 let alpha_0 = f32::from_le_bytes(data[14..18].try_into().unwrap());
1957 let alpha_1 = f32::from_le_bytes(data[18..22].try_into().unwrap());
1958 let alpha_2 = f32::from_le_bytes(data[22..26].try_into().unwrap());
1959 let alpha_3 = f32::from_le_bytes(data[26..30].try_into().unwrap());
1960 let beta_0 = f32::from_le_bytes(data[30..34].try_into().unwrap());
1961 let beta_1 = f32::from_le_bytes(data[34..38].try_into().unwrap());
1962 let beta_2 = f32::from_le_bytes(data[38..42].try_into().unwrap());
1963 let beta_3 = f32::from_le_bytes(data[42..46].try_into().unwrap());
1964
1965 Ok(Self {
1966 tow_ms: header.tow_ms,
1967 wnc: header.wnc,
1968 prn,
1969 alpha_0,
1970 alpha_1,
1971 alpha_2,
1972 alpha_3,
1973 beta_0,
1974 beta_1,
1975 beta_2,
1976 beta_3,
1977 })
1978 }
1979}
1980
1981#[derive(Debug, Clone)]
1989pub struct BdsCNav1Block {
1990 tow_ms: u32,
1991 wnc: u16,
1992 pub prn_idx: u8,
1994 pub flags: u8,
1996 pub t_oe: u32,
1998 pub a: f64,
2000 pub a_dot: f64,
2002 pub delta_n0: f32,
2004 pub delta_n0_dot: f32,
2006 pub m_0: f64,
2008 pub e: f64,
2010 pub omega: f64,
2012 pub omega_0: f64,
2014 pub omega_dot: f32,
2016 pub i_0: f64,
2018 pub i_dot: f32,
2020 pub c_is: f32,
2022 pub c_ic: f32,
2024 pub c_rs: f32,
2026 pub c_rc: f32,
2028 pub c_us: f32,
2030 pub c_uc: f32,
2032 pub t_oc: u32,
2034 pub a_2: f32,
2036 pub a_1: f32,
2038 pub a_0: f64,
2040 pub t_op: u32,
2042 pub sisai_ocb: u8,
2044 pub sisai_oc12: u8,
2046 pub sisai_oe: u8,
2048 pub sismai: u8,
2050 pub health_if: u8,
2052 pub iode: u8,
2054 pub iodc: u16,
2056 pub isc_b1cd: f32,
2058 pub t_gd_b1cp: f32,
2060 pub t_gd_b2ap: f32,
2062}
2063
2064impl BdsCNav1Block {
2065 pub fn tow_seconds(&self) -> f64 {
2066 self.tow_ms as f64 * 0.001
2067 }
2068 pub fn tow_ms(&self) -> u32 {
2069 self.tow_ms
2070 }
2071 pub fn wnc(&self) -> u16 {
2072 self.wnc
2073 }
2074
2075 pub fn satellite_type(&self) -> u8 {
2077 self.flags & 0x03
2078 }
2079
2080 pub fn is_geo(&self) -> bool {
2082 self.satellite_type() == 1
2083 }
2084
2085 pub fn is_igso(&self) -> bool {
2087 self.satellite_type() == 2
2088 }
2089
2090 pub fn is_meo(&self) -> bool {
2092 self.satellite_type() == 3
2093 }
2094
2095 pub fn is_healthy(&self) -> bool {
2097 (self.health_if & 0xC0) == 0
2098 }
2099
2100 pub fn isc_b1cd_s(&self) -> Option<f32> {
2102 f32_or_none(self.isc_b1cd)
2103 }
2104
2105 pub fn t_gd_b1cp_s(&self) -> Option<f32> {
2107 f32_or_none(self.t_gd_b1cp)
2108 }
2109
2110 pub fn t_gd_b2ap_s(&self) -> Option<f32> {
2112 f32_or_none(self.t_gd_b2ap)
2113 }
2114}
2115
2116impl SbfBlockParse for BdsCNav1Block {
2117 const BLOCK_ID: u16 = block_ids::BDS_CNAV1;
2118
2119 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2120 if data.len() < 158 {
2129 return Err(SbfError::ParseError("BDSCNav1 too short".into()));
2130 }
2131
2132 let prn_idx = data[12];
2170 let flags = data[13];
2171 let t_oe = u32::from_le_bytes(data[14..18].try_into().unwrap());
2172 let a = f64::from_le_bytes(data[18..26].try_into().unwrap());
2173 let a_dot = f64::from_le_bytes(data[26..34].try_into().unwrap());
2174 let delta_n0 = f32::from_le_bytes(data[34..38].try_into().unwrap());
2175 let delta_n0_dot = f32::from_le_bytes(data[38..42].try_into().unwrap());
2176 let m_0 = f64::from_le_bytes(data[42..50].try_into().unwrap());
2177 let e = f64::from_le_bytes(data[50..58].try_into().unwrap());
2178 let omega = f64::from_le_bytes(data[58..66].try_into().unwrap());
2179 let omega_0 = f64::from_le_bytes(data[66..74].try_into().unwrap());
2180 let omega_dot = f32::from_le_bytes(data[74..78].try_into().unwrap());
2181 let i_0 = f64::from_le_bytes(data[78..86].try_into().unwrap());
2182 let i_dot = f32::from_le_bytes(data[86..90].try_into().unwrap());
2183 let c_is = f32::from_le_bytes(data[90..94].try_into().unwrap());
2184 let c_ic = f32::from_le_bytes(data[94..98].try_into().unwrap());
2185 let c_rs = f32::from_le_bytes(data[98..102].try_into().unwrap());
2186 let c_rc = f32::from_le_bytes(data[102..106].try_into().unwrap());
2187 let c_us = f32::from_le_bytes(data[106..110].try_into().unwrap());
2188 let c_uc = f32::from_le_bytes(data[110..114].try_into().unwrap());
2189 let t_oc = u32::from_le_bytes(data[114..118].try_into().unwrap());
2190 let a_2 = f32::from_le_bytes(data[118..122].try_into().unwrap());
2191 let a_1 = f32::from_le_bytes(data[122..126].try_into().unwrap());
2192 let a_0 = f64::from_le_bytes(data[126..134].try_into().unwrap());
2193 let t_op = u32::from_le_bytes(data[134..138].try_into().unwrap());
2194 let sisai_ocb = data[138];
2195 let sisai_oc12 = data[139];
2196 let sisai_oe = data[140];
2197 let sismai = data[141];
2198 let health_if = data[142];
2199 let iode = data[143];
2200 let iodc = u16::from_le_bytes([data[144], data[145]]);
2201 let isc_b1cd = f32::from_le_bytes(data[146..150].try_into().unwrap());
2202 let t_gd_b1cp = f32::from_le_bytes(data[150..154].try_into().unwrap());
2203 let t_gd_b2ap = f32::from_le_bytes(data[154..158].try_into().unwrap());
2204
2205 Ok(Self {
2206 tow_ms: header.tow_ms,
2207 wnc: header.wnc,
2208 prn_idx,
2209 flags,
2210 t_oe,
2211 a,
2212 a_dot,
2213 delta_n0,
2214 delta_n0_dot,
2215 m_0,
2216 e,
2217 omega,
2218 omega_0,
2219 omega_dot,
2220 i_0,
2221 i_dot,
2222 c_is,
2223 c_ic,
2224 c_rs,
2225 c_rc,
2226 c_us,
2227 c_uc,
2228 t_oc,
2229 a_2,
2230 a_1,
2231 a_0,
2232 t_op,
2233 sisai_ocb,
2234 sisai_oc12,
2235 sisai_oe,
2236 sismai,
2237 health_if,
2238 iode,
2239 iodc,
2240 isc_b1cd,
2241 t_gd_b1cp,
2242 t_gd_b2ap,
2243 })
2244 }
2245}
2246
2247#[derive(Debug, Clone)]
2255pub struct GpsRawCaBlock {
2256 tow_ms: u32,
2257 wnc: u16,
2258 pub svid: u8,
2260 pub crc_passed: u8,
2262 pub viterbi_count: u8,
2264 pub source: u8,
2266 pub freq_nr: u8,
2268 pub nav_bits: [u8; 40],
2270}
2271
2272impl GpsRawCaBlock {
2273 pub fn tow_seconds(&self) -> f64 {
2274 self.tow_ms as f64 * 0.001
2275 }
2276 pub fn tow_ms(&self) -> u32 {
2277 self.tow_ms
2278 }
2279 pub fn wnc(&self) -> u16 {
2280 self.wnc
2281 }
2282 pub fn crc_ok(&self) -> bool {
2284 self.crc_passed != 0
2285 }
2286 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2288 &self.nav_bits
2289 }
2290}
2291
2292impl SbfBlockParse for GpsRawCaBlock {
2293 const BLOCK_ID: u16 = block_ids::GPS_RAW_CA;
2294
2295 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2296 const MIN_LEN: usize = 57;
2298 if data.len() < MIN_LEN {
2299 return Err(SbfError::ParseError("GPSRawCA too short".into()));
2300 }
2301
2302 let mut nav_bits = [0u8; 40];
2303 nav_bits.copy_from_slice(&data[17..57]);
2304
2305 Ok(Self {
2306 tow_ms: header.tow_ms,
2307 wnc: header.wnc,
2308 svid: data[12],
2309 crc_passed: data[13],
2310 viterbi_count: data[14],
2311 source: data[15],
2312 freq_nr: data[16],
2313 nav_bits,
2314 })
2315 }
2316}
2317
2318#[derive(Debug, Clone)]
2326pub struct GpsRawL2CBlock {
2327 tow_ms: u32,
2328 wnc: u16,
2329 pub svid: u8,
2331 pub crc_passed: u8,
2333 pub viterbi_count: u8,
2335 pub source: u8,
2337 pub freq_nr: u8,
2339 pub nav_bits: [u8; 40],
2341}
2342
2343impl GpsRawL2CBlock {
2344 pub fn tow_seconds(&self) -> f64 {
2345 self.tow_ms as f64 * 0.001
2346 }
2347 pub fn tow_ms(&self) -> u32 {
2348 self.tow_ms
2349 }
2350 pub fn wnc(&self) -> u16 {
2351 self.wnc
2352 }
2353 pub fn crc_ok(&self) -> bool {
2354 self.crc_passed != 0
2355 }
2356 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2357 &self.nav_bits
2358 }
2359}
2360
2361impl SbfBlockParse for GpsRawL2CBlock {
2362 const BLOCK_ID: u16 = block_ids::GPS_RAW_L2C;
2363
2364 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2365 const MIN_LEN: usize = 57;
2366 if data.len() < MIN_LEN {
2367 return Err(SbfError::ParseError("GPSRawL2C too short".into()));
2368 }
2369
2370 let mut nav_bits = [0u8; 40];
2371 nav_bits.copy_from_slice(&data[17..57]);
2372
2373 Ok(Self {
2374 tow_ms: header.tow_ms,
2375 wnc: header.wnc,
2376 svid: data[12],
2377 crc_passed: data[13],
2378 viterbi_count: data[14],
2379 source: data[15],
2380 freq_nr: data[16],
2381 nav_bits,
2382 })
2383 }
2384}
2385
2386#[derive(Debug, Clone)]
2394pub struct GpsRawL5Block {
2395 tow_ms: u32,
2396 wnc: u16,
2397 pub svid: u8,
2399 pub crc_passed: u8,
2401 pub viterbi_count: u8,
2403 pub source: u8,
2405 pub freq_nr: u8,
2407 pub nav_bits: [u8; 40],
2409}
2410
2411impl GpsRawL5Block {
2412 pub fn tow_seconds(&self) -> f64 {
2413 self.tow_ms as f64 * 0.001
2414 }
2415 pub fn tow_ms(&self) -> u32 {
2416 self.tow_ms
2417 }
2418 pub fn wnc(&self) -> u16 {
2419 self.wnc
2420 }
2421 pub fn crc_ok(&self) -> bool {
2422 self.crc_passed != 0
2423 }
2424 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2425 &self.nav_bits
2426 }
2427}
2428
2429impl SbfBlockParse for GpsRawL5Block {
2430 const BLOCK_ID: u16 = block_ids::GPS_RAW_L5;
2431
2432 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2433 const MIN_LEN: usize = 57;
2434 if data.len() < MIN_LEN {
2435 return Err(SbfError::ParseError("GPSRawL5 too short".into()));
2436 }
2437
2438 let mut nav_bits = [0u8; 40];
2439 nav_bits.copy_from_slice(&data[17..57]);
2440
2441 Ok(Self {
2442 tow_ms: header.tow_ms,
2443 wnc: header.wnc,
2444 svid: data[12],
2445 crc_passed: data[13],
2446 viterbi_count: data[14],
2447 source: data[15],
2448 freq_nr: data[16],
2449 nav_bits,
2450 })
2451 }
2452}
2453
2454#[derive(Debug, Clone)]
2462pub struct GloRawCaBlock {
2463 tow_ms: u32,
2464 wnc: u16,
2465 pub svid: u8,
2467 pub crc_passed: u8,
2469 pub viterbi_count: u8,
2471 pub source: u8,
2473 pub freq_nr: u8,
2475 pub nav_bits: [u8; 12],
2477}
2478
2479impl GloRawCaBlock {
2480 pub fn tow_seconds(&self) -> f64 {
2481 self.tow_ms as f64 * 0.001
2482 }
2483 pub fn tow_ms(&self) -> u32 {
2484 self.tow_ms
2485 }
2486 pub fn wnc(&self) -> u16 {
2487 self.wnc
2488 }
2489 pub fn crc_ok(&self) -> bool {
2490 self.crc_passed != 0
2491 }
2492 pub fn nav_bits_slice(&self) -> &[u8; 12] {
2493 &self.nav_bits
2494 }
2495}
2496
2497impl SbfBlockParse for GloRawCaBlock {
2498 const BLOCK_ID: u16 = block_ids::GLO_RAW_CA;
2499
2500 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2501 const MIN_LEN: usize = 29;
2503 if data.len() < MIN_LEN {
2504 return Err(SbfError::ParseError("GLORawCA too short".into()));
2505 }
2506
2507 let mut nav_bits = [0u8; 12];
2508 nav_bits.copy_from_slice(&data[17..29]);
2509
2510 Ok(Self {
2511 tow_ms: header.tow_ms,
2512 wnc: header.wnc,
2513 svid: data[12],
2514 crc_passed: data[13],
2515 viterbi_count: data[14],
2516 source: data[15],
2517 freq_nr: data[16],
2518 nav_bits,
2519 })
2520 }
2521}
2522
2523#[derive(Debug, Clone)]
2531pub struct GalRawFnavBlock {
2532 tow_ms: u32,
2533 wnc: u16,
2534 pub svid: u8,
2536 pub crc_passed: u8,
2538 pub viterbi_count: u8,
2540 pub source: u8,
2542 pub freq_nr: u8,
2544 pub nav_bits: [u8; 32],
2546}
2547
2548impl GalRawFnavBlock {
2549 pub fn tow_seconds(&self) -> f64 {
2550 self.tow_ms as f64 * 0.001
2551 }
2552 pub fn tow_ms(&self) -> u32 {
2553 self.tow_ms
2554 }
2555 pub fn wnc(&self) -> u16 {
2556 self.wnc
2557 }
2558 pub fn crc_ok(&self) -> bool {
2559 self.crc_passed != 0
2560 }
2561 pub fn nav_bits_slice(&self) -> &[u8; 32] {
2562 &self.nav_bits
2563 }
2564}
2565
2566impl SbfBlockParse for GalRawFnavBlock {
2567 const BLOCK_ID: u16 = block_ids::GAL_RAW_FNAV;
2568
2569 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2570 const MIN_LEN: usize = 49;
2571 if data.len() < MIN_LEN {
2572 return Err(SbfError::ParseError("GALRawFNAV too short".into()));
2573 }
2574
2575 let mut nav_bits = [0u8; 32];
2576 nav_bits.copy_from_slice(&data[17..49]);
2577
2578 Ok(Self {
2579 tow_ms: header.tow_ms,
2580 wnc: header.wnc,
2581 svid: data[12],
2582 crc_passed: data[13],
2583 viterbi_count: data[14],
2584 source: data[15],
2585 freq_nr: data[16],
2586 nav_bits,
2587 })
2588 }
2589}
2590
2591#[derive(Debug, Clone)]
2599pub struct GalRawInavBlock {
2600 tow_ms: u32,
2601 wnc: u16,
2602 pub svid: u8,
2604 pub crc_passed: u8,
2606 pub viterbi_count: u8,
2608 pub source: u8,
2610 pub freq_nr: u8,
2612 pub nav_bits: [u8; 32],
2614}
2615
2616impl GalRawInavBlock {
2617 pub fn tow_seconds(&self) -> f64 {
2618 self.tow_ms as f64 * 0.001
2619 }
2620 pub fn tow_ms(&self) -> u32 {
2621 self.tow_ms
2622 }
2623 pub fn wnc(&self) -> u16 {
2624 self.wnc
2625 }
2626 pub fn crc_ok(&self) -> bool {
2627 self.crc_passed != 0
2628 }
2629 pub fn nav_bits_slice(&self) -> &[u8; 32] {
2630 &self.nav_bits
2631 }
2632}
2633
2634impl SbfBlockParse for GalRawInavBlock {
2635 const BLOCK_ID: u16 = block_ids::GAL_RAW_INAV;
2636
2637 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2638 const MIN_LEN: usize = 49;
2639 if data.len() < MIN_LEN {
2640 return Err(SbfError::ParseError("GALRawINAV too short".into()));
2641 }
2642
2643 let mut nav_bits = [0u8; 32];
2644 nav_bits.copy_from_slice(&data[17..49]);
2645
2646 Ok(Self {
2647 tow_ms: header.tow_ms,
2648 wnc: header.wnc,
2649 svid: data[12],
2650 crc_passed: data[13],
2651 viterbi_count: data[14],
2652 source: data[15],
2653 freq_nr: data[16],
2654 nav_bits,
2655 })
2656 }
2657}
2658
2659#[derive(Debug, Clone)]
2667pub struct GalRawCnavBlock {
2668 tow_ms: u32,
2669 wnc: u16,
2670 pub svid: u8,
2672 pub crc_passed: u8,
2674 pub viterbi_count: u8,
2676 pub source: u8,
2678 pub freq_nr: u8,
2680 pub nav_bits: [u8; 64],
2682}
2683
2684impl GalRawCnavBlock {
2685 pub fn tow_seconds(&self) -> f64 {
2686 self.tow_ms as f64 * 0.001
2687 }
2688 pub fn tow_ms(&self) -> u32 {
2689 self.tow_ms
2690 }
2691 pub fn wnc(&self) -> u16 {
2692 self.wnc
2693 }
2694 pub fn crc_ok(&self) -> bool {
2695 self.crc_passed != 0
2696 }
2697 pub fn nav_bits_slice(&self) -> &[u8; 64] {
2698 &self.nav_bits
2699 }
2700}
2701
2702impl SbfBlockParse for GalRawCnavBlock {
2703 const BLOCK_ID: u16 = block_ids::GAL_RAW_CNAV;
2704
2705 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2706 const MIN_LEN: usize = 81;
2707 if data.len() < MIN_LEN {
2708 return Err(SbfError::ParseError("GALRawCNAV too short".into()));
2709 }
2710
2711 let mut nav_bits = [0u8; 64];
2712 nav_bits.copy_from_slice(&data[17..81]);
2713
2714 Ok(Self {
2715 tow_ms: header.tow_ms,
2716 wnc: header.wnc,
2717 svid: data[12],
2718 crc_passed: data[13],
2719 viterbi_count: data[14],
2720 source: data[15],
2721 freq_nr: data[16],
2722 nav_bits,
2723 })
2724 }
2725}
2726
2727#[derive(Debug, Clone)]
2735pub struct GeoRawL1Block {
2736 tow_ms: u32,
2737 wnc: u16,
2738 pub svid: u8,
2740 pub crc_passed: u8,
2742 pub viterbi_count: u8,
2744 pub source: u8,
2746 pub freq_nr: u8,
2748 pub nav_bits: [u8; 32],
2750}
2751
2752impl GeoRawL1Block {
2753 pub fn tow_seconds(&self) -> f64 {
2754 self.tow_ms as f64 * 0.001
2755 }
2756 pub fn tow_ms(&self) -> u32 {
2757 self.tow_ms
2758 }
2759 pub fn wnc(&self) -> u16 {
2760 self.wnc
2761 }
2762 pub fn crc_ok(&self) -> bool {
2763 self.crc_passed != 0
2764 }
2765 pub fn nav_bits_slice(&self) -> &[u8; 32] {
2766 &self.nav_bits
2767 }
2768}
2769
2770impl SbfBlockParse for GeoRawL1Block {
2771 const BLOCK_ID: u16 = block_ids::GEO_RAW_L1;
2772
2773 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2774 const MIN_LEN: usize = 49;
2775 if data.len() < MIN_LEN {
2776 return Err(SbfError::ParseError("GEORawL1 too short".into()));
2777 }
2778
2779 let mut nav_bits = [0u8; 32];
2780 nav_bits.copy_from_slice(&data[17..49]);
2781
2782 Ok(Self {
2783 tow_ms: header.tow_ms,
2784 wnc: header.wnc,
2785 svid: data[12],
2786 crc_passed: data[13],
2787 viterbi_count: data[14],
2788 source: data[15],
2789 freq_nr: data[16],
2790 nav_bits,
2791 })
2792 }
2793}
2794
2795#[derive(Debug, Clone)]
2803pub struct CmpRawBlock {
2804 tow_ms: u32,
2805 wnc: u16,
2806 pub svid: u8,
2808 pub crc_passed: u8,
2810 pub viterbi_count: u8,
2812 pub source: u8,
2814 pub freq_nr: u8,
2816 pub nav_bits: [u8; 40],
2818}
2819
2820impl CmpRawBlock {
2821 pub fn tow_seconds(&self) -> f64 {
2822 self.tow_ms as f64 * 0.001
2823 }
2824 pub fn tow_ms(&self) -> u32 {
2825 self.tow_ms
2826 }
2827 pub fn wnc(&self) -> u16 {
2828 self.wnc
2829 }
2830 pub fn crc_ok(&self) -> bool {
2831 self.crc_passed != 0
2832 }
2833 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2834 &self.nav_bits
2835 }
2836}
2837
2838impl SbfBlockParse for CmpRawBlock {
2839 const BLOCK_ID: u16 = block_ids::CMP_RAW;
2840
2841 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2842 const MIN_LEN: usize = 57;
2843 if data.len() < MIN_LEN {
2844 return Err(SbfError::ParseError("CMPRaw too short".into()));
2845 }
2846
2847 let mut nav_bits = [0u8; 40];
2848 nav_bits.copy_from_slice(&data[17..57]);
2849
2850 Ok(Self {
2851 tow_ms: header.tow_ms,
2852 wnc: header.wnc,
2853 svid: data[12],
2854 crc_passed: data[13],
2855 viterbi_count: data[14],
2856 source: data[15],
2857 freq_nr: data[16],
2858 nav_bits,
2859 })
2860 }
2861}
2862
2863#[derive(Debug, Clone)]
2871pub struct QzsRawL1CaBlock {
2872 tow_ms: u32,
2873 wnc: u16,
2874 pub svid: u8,
2876 pub crc_passed: u8,
2878 pub viterbi_count: u8,
2880 pub source: u8,
2882 pub freq_nr: u8,
2884 pub nav_bits: [u8; 40],
2886}
2887
2888impl QzsRawL1CaBlock {
2889 pub fn tow_seconds(&self) -> f64 {
2890 self.tow_ms as f64 * 0.001
2891 }
2892 pub fn tow_ms(&self) -> u32 {
2893 self.tow_ms
2894 }
2895 pub fn wnc(&self) -> u16 {
2896 self.wnc
2897 }
2898 pub fn crc_ok(&self) -> bool {
2899 self.crc_passed != 0
2900 }
2901 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2902 &self.nav_bits
2903 }
2904}
2905
2906impl SbfBlockParse for QzsRawL1CaBlock {
2907 const BLOCK_ID: u16 = block_ids::QZS_RAW_L1CA;
2908
2909 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2910 const MIN_LEN: usize = 57;
2911 if data.len() < MIN_LEN {
2912 return Err(SbfError::ParseError("QZSRawL1CA too short".into()));
2913 }
2914
2915 let mut nav_bits = [0u8; 40];
2916 nav_bits.copy_from_slice(&data[17..57]);
2917
2918 Ok(Self {
2919 tow_ms: header.tow_ms,
2920 wnc: header.wnc,
2921 svid: data[12],
2922 crc_passed: data[13],
2923 viterbi_count: data[14],
2924 source: data[15],
2925 freq_nr: data[16],
2926 nav_bits,
2927 })
2928 }
2929}
2930
2931#[derive(Debug, Clone)]
2939pub struct QzsRawL2CBlock {
2940 tow_ms: u32,
2941 wnc: u16,
2942 pub svid: u8,
2944 pub crc_passed: u8,
2946 pub viterbi_count: u8,
2948 pub source: u8,
2950 pub freq_nr: u8,
2952 pub nav_bits: [u8; 40],
2954}
2955
2956impl QzsRawL2CBlock {
2957 pub fn tow_seconds(&self) -> f64 {
2958 self.tow_ms as f64 * 0.001
2959 }
2960 pub fn tow_ms(&self) -> u32 {
2961 self.tow_ms
2962 }
2963 pub fn wnc(&self) -> u16 {
2964 self.wnc
2965 }
2966 pub fn crc_ok(&self) -> bool {
2967 self.crc_passed != 0
2968 }
2969 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2970 &self.nav_bits
2971 }
2972}
2973
2974impl SbfBlockParse for QzsRawL2CBlock {
2975 const BLOCK_ID: u16 = block_ids::QZS_RAW_L2C;
2976
2977 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2978 const MIN_LEN: usize = 57;
2979 if data.len() < MIN_LEN {
2980 return Err(SbfError::ParseError("QZSRawL2C too short".into()));
2981 }
2982
2983 let mut nav_bits = [0u8; 40];
2984 nav_bits.copy_from_slice(&data[17..57]);
2985
2986 Ok(Self {
2987 tow_ms: header.tow_ms,
2988 wnc: header.wnc,
2989 svid: data[12],
2990 crc_passed: data[13],
2991 viterbi_count: data[14],
2992 source: data[15],
2993 freq_nr: data[16],
2994 nav_bits,
2995 })
2996 }
2997}
2998
2999#[derive(Debug, Clone)]
3007pub struct QzsRawL5Block {
3008 tow_ms: u32,
3009 wnc: u16,
3010 pub svid: u8,
3012 pub crc_passed: u8,
3014 pub viterbi_count: u8,
3016 pub source: u8,
3018 pub freq_nr: u8,
3020 pub nav_bits: [u8; 40],
3022}
3023
3024impl QzsRawL5Block {
3025 pub fn tow_seconds(&self) -> f64 {
3026 self.tow_ms as f64 * 0.001
3027 }
3028 pub fn tow_ms(&self) -> u32 {
3029 self.tow_ms
3030 }
3031 pub fn wnc(&self) -> u16 {
3032 self.wnc
3033 }
3034 pub fn crc_ok(&self) -> bool {
3035 self.crc_passed != 0
3036 }
3037 pub fn nav_bits_slice(&self) -> &[u8; 40] {
3038 &self.nav_bits
3039 }
3040}
3041
3042impl SbfBlockParse for QzsRawL5Block {
3043 const BLOCK_ID: u16 = block_ids::QZS_RAW_L5;
3044
3045 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
3046 const MIN_LEN: usize = 57;
3047 if data.len() < MIN_LEN {
3048 return Err(SbfError::ParseError("QZSRawL5 too short".into()));
3049 }
3050
3051 let mut nav_bits = [0u8; 40];
3052 nav_bits.copy_from_slice(&data[17..57]);
3053
3054 Ok(Self {
3055 tow_ms: header.tow_ms,
3056 wnc: header.wnc,
3057 svid: data[12],
3058 crc_passed: data[13],
3059 viterbi_count: data[14],
3060 source: data[15],
3061 freq_nr: data[16],
3062 nav_bits,
3063 })
3064 }
3065}
3066
3067#[derive(Debug, Clone)]
3073pub struct GeoIonoDelayIdc {
3074 pub igp_mask_no: u8,
3076 pub givei: u8,
3078 vertical_delay_m_raw: f32,
3079}
3080
3081impl GeoIonoDelayIdc {
3082 pub fn vertical_delay_m(&self) -> Option<f32> {
3085 f32_or_none(self.vertical_delay_m_raw)
3086 }
3087
3088 pub fn vertical_delay_m_raw(&self) -> f32 {
3090 self.vertical_delay_m_raw
3091 }
3092}
3093
3094#[derive(Debug, Clone)]
3098pub struct GeoIonoDelayBlock {
3099 tow_ms: u32,
3100 wnc: u16,
3101 pub prn: u8,
3103 pub band_nbr: u8,
3105 pub iodi: u8,
3107 pub idc: Vec<GeoIonoDelayIdc>,
3109}
3110
3111impl GeoIonoDelayBlock {
3112 pub fn tow_seconds(&self) -> f64 {
3113 self.tow_ms as f64 * 0.001
3114 }
3115 pub fn tow_ms(&self) -> u32 {
3116 self.tow_ms
3117 }
3118 pub fn wnc(&self) -> u16 {
3119 self.wnc
3120 }
3121
3122 pub fn num_idc(&self) -> usize {
3123 self.idc.len()
3124 }
3125}
3126
3127impl SbfBlockParse for GeoIonoDelayBlock {
3128 const BLOCK_ID: u16 = block_ids::GEO_IONO_DELAY;
3129
3130 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
3131 if data.len() < 18 {
3133 return Err(SbfError::ParseError("GEOIonoDelay too short".into()));
3134 }
3135
3136 let prn = data[12];
3137 let band_nbr = data[13];
3138 let iodi = data[14];
3139 let n = data[15] as usize;
3140 let sb_length = data[16] as usize;
3141
3142 if sb_length < 8 {
3143 return Err(SbfError::ParseError(
3144 "GEOIonoDelay SBLength too small".into(),
3145 ));
3146 }
3147
3148 let mut idc = Vec::with_capacity(n);
3149 let mut offset = 18;
3150
3151 for _ in 0..n {
3152 if offset + sb_length > data.len() {
3153 break;
3154 }
3155
3156 let igp_mask_no = data[offset];
3157 let givei = data[offset + 1];
3158 let vertical_delay_m_raw =
3159 f32::from_le_bytes(data[offset + 4..offset + 8].try_into().unwrap());
3160
3161 idc.push(GeoIonoDelayIdc {
3162 igp_mask_no,
3163 givei,
3164 vertical_delay_m_raw,
3165 });
3166
3167 offset += sb_length;
3168 }
3169
3170 Ok(Self {
3171 tow_ms: header.tow_ms,
3172 wnc: header.wnc,
3173 prn,
3174 band_nbr,
3175 iodi,
3176 idc,
3177 })
3178 }
3179}
3180
3181#[cfg(test)]
3182mod tests {
3183 use super::*;
3184 use crate::blocks::SbfBlock;
3185 use crate::header::{SbfHeader, SBF_SYNC};
3186
3187 fn header_for(block_id: u16, data_len: usize, tow_ms: u32, wnc: u16) -> SbfHeader {
3188 SbfHeader {
3189 crc: 0,
3190 block_id,
3191 block_rev: 0,
3192 length: (data_len + 2) as u16,
3193 tow_ms,
3194 wnc,
3195 }
3196 }
3197
3198 #[test]
3199 fn test_gps_alm_accessors() {
3200 let block = GpsAlmBlock {
3201 tow_ms: 1000,
3202 wnc: 2000,
3203 prn: 5,
3204 e: F32_DNU,
3205 t_oa: 100,
3206 delta_i: 0.1,
3207 omega_dot: 0.2,
3208 sqrt_a: 5153.5,
3209 omega_0: 1.0,
3210 omega: 1.1,
3211 m_0: 0.5,
3212 a_f1: 0.0,
3213 a_f0: 0.0,
3214 wn_a: 10,
3215 as_config: 1,
3216 health8: 0,
3217 health6: 0,
3218 };
3219
3220 assert!((block.tow_seconds() - 1.0).abs() < 1e-6);
3221 assert!(block.eccentricity().is_none());
3222 assert!((block.semi_major_axis_m().unwrap() - 5153.5_f32.powi(2)).abs() < 1e-3);
3223 }
3224
3225 #[test]
3226 fn test_gps_alm_parse() {
3227 let mut data = vec![0u8; 57];
3228 data[12] = 7;
3229 data[13..17].copy_from_slice(&0.02_f32.to_le_bytes());
3230 data[17..21].copy_from_slice(&1234_u32.to_le_bytes());
3231 data[21..25].copy_from_slice(&0.1_f32.to_le_bytes());
3232 data[25..29].copy_from_slice(&0.2_f32.to_le_bytes());
3233 data[29..33].copy_from_slice(&5153.8_f32.to_le_bytes());
3234 data[33..37].copy_from_slice(&1.0_f32.to_le_bytes());
3235 data[37..41].copy_from_slice(&1.1_f32.to_le_bytes());
3236 data[41..45].copy_from_slice(&0.5_f32.to_le_bytes());
3237 data[45..49].copy_from_slice(&0.01_f32.to_le_bytes());
3238 data[49..53].copy_from_slice(&0.02_f32.to_le_bytes());
3239 data[53] = 12;
3240 data[54] = 1;
3241 data[55] = 0;
3242 data[56] = 0;
3243
3244 let header = header_for(block_ids::GPS_ALM, data.len(), 5000, 2001);
3245 let block = GpsAlmBlock::parse(&header, &data).unwrap();
3246
3247 assert_eq!(block.prn, 7);
3248 assert_eq!(block.wnc(), 2001);
3249 assert_eq!(block.t_oa, 1234);
3250 assert!((block.eccentricity().unwrap() - 0.02).abs() < 1e-6);
3251 }
3252
3253 #[test]
3254 fn test_gps_ion_accessors() {
3255 let block = GpsIonBlock {
3256 tow_ms: 2500,
3257 wnc: 2100,
3258 prn: 3,
3259 alpha_0: F32_DNU,
3260 alpha_1: 0.1,
3261 alpha_2: 0.2,
3262 alpha_3: 0.3,
3263 beta_0: 1.0,
3264 beta_1: 2.0,
3265 beta_2: 3.0,
3266 beta_3: 4.0,
3267 };
3268
3269 assert!((block.tow_seconds() - 2.5).abs() < 1e-6);
3270 assert!(block.alpha_0().is_none());
3271 assert!((block.beta_0().unwrap() - 1.0).abs() < 1e-6);
3272 }
3273
3274 #[test]
3275 fn test_gps_ion_parse() {
3276 let mut data = vec![0u8; 45];
3277 data[12] = 8;
3278 data[13..17].copy_from_slice(&0.1_f32.to_le_bytes());
3279 data[17..21].copy_from_slice(&0.2_f32.to_le_bytes());
3280 data[21..25].copy_from_slice(&0.3_f32.to_le_bytes());
3281 data[25..29].copy_from_slice(&0.4_f32.to_le_bytes());
3282 data[29..33].copy_from_slice(&1.1_f32.to_le_bytes());
3283 data[33..37].copy_from_slice(&1.2_f32.to_le_bytes());
3284 data[37..41].copy_from_slice(&1.3_f32.to_le_bytes());
3285 data[41..45].copy_from_slice(&1.4_f32.to_le_bytes());
3286
3287 let header = header_for(block_ids::GPS_ION, data.len(), 8000, 2002);
3288 let block = GpsIonBlock::parse(&header, &data).unwrap();
3289
3290 assert_eq!(block.prn, 8);
3291 assert!((block.alpha_1 - 0.2).abs() < 1e-6);
3292 assert!((block.beta_3 - 1.4).abs() < 1e-6);
3293 }
3294
3295 #[test]
3296 fn test_gps_utc_accessors() {
3297 let block = GpsUtcBlock {
3298 tow_ms: 3000,
3299 wnc: 2200,
3300 prn: 1,
3301 a_1: 0.001,
3302 a_0: F64_DNU,
3303 t_ot: 4000,
3304 wn_t: 12,
3305 delta_t_ls: 18,
3306 wn_lsf: 13,
3307 dn: 2,
3308 delta_t_lsf: 19,
3309 };
3310
3311 assert!((block.tow_seconds() - 3.0).abs() < 1e-6);
3312 assert!(block.utc_bias_s().is_none());
3313 assert!((block.utc_drift_s_per_s().unwrap() - 0.001).abs() < 1e-6);
3314 }
3315
3316 #[test]
3317 fn test_gps_utc_parse() {
3318 let mut data = vec![0u8; 34];
3319 data[12] = 2;
3320 data[13..17].copy_from_slice(&0.001_f32.to_le_bytes());
3321 data[17..25].copy_from_slice(&(-0.5_f64).to_le_bytes());
3322 data[25..29].copy_from_slice(&12345_u32.to_le_bytes());
3323 data[29] = 6;
3324 data[30] = 18u8;
3325 data[31] = 7;
3326 data[32] = 4;
3327 data[33] = 19u8;
3328
3329 let header = header_for(block_ids::GPS_UTC, data.len(), 9000, 2003);
3330 let block = GpsUtcBlock::parse(&header, &data).unwrap();
3331
3332 assert_eq!(block.prn, 2);
3333 assert_eq!(block.wn_t, 6);
3334 assert!((block.utc_bias_s().unwrap() + 0.5).abs() < 1e-9);
3335 }
3336
3337 #[test]
3338 fn test_glo_alm_accessors() {
3339 let block = GloAlmBlock {
3340 tow_ms: 500,
3341 wnc: 2300,
3342 svid: 40,
3343 freq_nr: -3,
3344 epsilon: F32_DNU,
3345 t_oa: 200,
3346 delta_i: 0.1,
3347 lambda: 0.2,
3348 t_ln: 100.0,
3349 omega: 0.3,
3350 delta_t: 0.4,
3351 d_delta_t: 0.5,
3352 tau: 0.0,
3353 wn_a: 5,
3354 c: 0,
3355 n: 10,
3356 m_type: 1,
3357 n_4: 2,
3358 };
3359
3360 assert!((block.tow_seconds() - 0.5).abs() < 1e-6);
3361 assert_eq!(block.slot(), 3);
3362 assert!(block.eccentricity().is_none());
3363 assert!(block.clock_bias_s().is_some());
3364 }
3365
3366 #[test]
3367 fn test_glo_alm_parse() {
3368 let mut data = vec![0u8; 56];
3369 data[12] = 38;
3370 data[13] = 250u8;
3371 data[14..18].copy_from_slice(&0.01_f32.to_le_bytes());
3372 data[18..22].copy_from_slice(&200_u32.to_le_bytes());
3373 data[22..26].copy_from_slice(&0.1_f32.to_le_bytes());
3374 data[26..30].copy_from_slice(&0.2_f32.to_le_bytes());
3375 data[30..34].copy_from_slice(&300.0_f32.to_le_bytes());
3376 data[34..38].copy_from_slice(&0.3_f32.to_le_bytes());
3377 data[38..42].copy_from_slice(&0.4_f32.to_le_bytes());
3378 data[42..46].copy_from_slice(&0.5_f32.to_le_bytes());
3379 data[46..50].copy_from_slice(&0.6_f32.to_le_bytes());
3380 data[50] = 3;
3381 data[51] = 0;
3382 data[52..54].copy_from_slice(&15_u16.to_le_bytes());
3383 data[54] = 1;
3384 data[55] = 2;
3385
3386 let header = header_for(block_ids::GLO_ALM, data.len(), 11000, 2301);
3387 let block = GloAlmBlock::parse(&header, &data).unwrap();
3388
3389 assert_eq!(block.svid, 38);
3390 assert_eq!(block.slot(), 1);
3391 assert_eq!(block.n, 15);
3392 assert!((block.clock_bias_s().unwrap() - 0.6).abs() < 1e-6);
3393 }
3394
3395 #[test]
3396 fn test_glo_time_accessors() {
3397 let block = GloTimeBlock {
3398 tow_ms: 750,
3399 wnc: 2400,
3400 svid: 41,
3401 freq_nr: 1,
3402 n_4: 3,
3403 kp: 2,
3404 n: 12,
3405 tau_gps: F32_DNU,
3406 tau_c: 0.2,
3407 b1: 0.01,
3408 b2: 0.02,
3409 };
3410
3411 assert!((block.tow_seconds() - 0.75).abs() < 1e-6);
3412 assert_eq!(block.slot(), 4);
3413 assert!(block.gps_glonass_offset_s().is_none());
3414 assert!((block.time_scale_correction_s().unwrap() - 0.2).abs() < 1e-9);
3415 }
3416
3417 #[test]
3418 fn test_glo_time_parse() {
3419 let mut data = vec![0u8; 38];
3420 data[12] = 39;
3421 data[13] = 1;
3422 data[14] = 5;
3423 data[15] = 1;
3424 data[16..18].copy_from_slice(&9_u16.to_le_bytes());
3425 data[18..22].copy_from_slice(&0.123_f32.to_le_bytes());
3426 data[22..30].copy_from_slice(&(-0.5_f64).to_le_bytes());
3427 data[30..34].copy_from_slice(&0.01_f32.to_le_bytes());
3428 data[34..38].copy_from_slice(&0.02_f32.to_le_bytes());
3429
3430 let header = header_for(block_ids::GLO_TIME, data.len(), 12000, 2401);
3431 let block = GloTimeBlock::parse(&header, &data).unwrap();
3432
3433 assert_eq!(block.svid, 39);
3434 assert_eq!(block.slot(), 2);
3435 assert_eq!(block.n, 9);
3436 assert!((block.time_scale_correction_s().unwrap() + 0.5).abs() < 1e-9);
3437 }
3438
3439 #[test]
3440 fn test_gal_alm_accessors() {
3441 let block = GalAlmBlock {
3442 tow_ms: 1500,
3443 wnc: 2500,
3444 svid: 71,
3445 source: 1,
3446 e: F32_DNU,
3447 t_oa: 100,
3448 delta_i: 0.1,
3449 omega_dot: 0.2,
3450 delta_sqrt_a: 0.3,
3451 omega_0: 1.0,
3452 omega: 1.1,
3453 m_0: 0.5,
3454 a_f1: 0.01,
3455 a_f0: 0.02,
3456 wn_a: 7,
3457 svid_a: 72,
3458 health: 0,
3459 ioda: 4,
3460 };
3461
3462 assert!((block.tow_seconds() - 1.5).abs() < 1e-6);
3463 assert_eq!(block.prn(), 1);
3464 assert!(block.eccentricity().is_none());
3465 assert!((block.delta_sqrt_a().unwrap() - 0.3).abs() < 1e-6);
3466 }
3467
3468 #[test]
3469 fn test_gal_alm_parse() {
3470 let mut data = vec![0u8; 59];
3471 data[12] = 72;
3472 data[13] = 2;
3473 data[14..18].copy_from_slice(&0.02_f32.to_le_bytes());
3474 data[18..22].copy_from_slice(&500_u32.to_le_bytes());
3475 data[22..26].copy_from_slice(&0.1_f32.to_le_bytes());
3476 data[26..30].copy_from_slice(&0.2_f32.to_le_bytes());
3477 data[30..34].copy_from_slice(&0.3_f32.to_le_bytes());
3478 data[34..38].copy_from_slice(&1.0_f32.to_le_bytes());
3479 data[38..42].copy_from_slice(&1.1_f32.to_le_bytes());
3480 data[42..46].copy_from_slice(&0.5_f32.to_le_bytes());
3481 data[46..50].copy_from_slice(&0.01_f32.to_le_bytes());
3482 data[50..54].copy_from_slice(&0.02_f32.to_le_bytes());
3483 data[54] = 9;
3484 data[55] = 73;
3485 data[56..58].copy_from_slice(&0x1234_u16.to_le_bytes());
3486 data[58] = 6;
3487
3488 let header = header_for(block_ids::GAL_ALM, data.len(), 13000, 2501);
3489 let block = GalAlmBlock::parse(&header, &data).unwrap();
3490
3491 assert_eq!(block.svid, 72);
3492 assert_eq!(block.prn(), 2);
3493 assert_eq!(block.wn_a, 9);
3494 assert_eq!(block.health, 0x1234);
3495 }
3496
3497 #[test]
3498 fn test_gal_ion_accessors() {
3499 let block = GalIonBlock {
3500 tow_ms: 1600,
3501 wnc: 2600,
3502 svid: 75,
3503 source: 16,
3504 a_i0: F32_DNU,
3505 a_i1: 0.1,
3506 a_i2: 0.2,
3507 storm_flags: 1,
3508 };
3509
3510 assert!((block.tow_seconds() - 1.6).abs() < 1e-6);
3511 assert!(block.is_fnav());
3512 assert!(!block.is_inav());
3513 assert!(block.a_i0().is_none());
3514 }
3515
3516 #[test]
3517 fn test_gal_ion_parse() {
3518 let mut data = vec![0u8; 27];
3519 data[12] = 80;
3520 data[13] = 1;
3521 data[14..18].copy_from_slice(&0.1_f32.to_le_bytes());
3522 data[18..22].copy_from_slice(&0.2_f32.to_le_bytes());
3523 data[22..26].copy_from_slice(&0.3_f32.to_le_bytes());
3524 data[26] = 2;
3525
3526 let header = header_for(block_ids::GAL_ION, data.len(), 14000, 2601);
3527 let block = GalIonBlock::parse(&header, &data).unwrap();
3528
3529 assert_eq!(block.svid, 80);
3530 assert!((block.a_i1 - 0.2).abs() < 1e-6);
3531 assert_eq!(block.storm_flags, 2);
3532 }
3533
3534 #[test]
3535 fn test_gal_utc_accessors() {
3536 let block = GalUtcBlock {
3537 tow_ms: 1700,
3538 wnc: 2700,
3539 svid: 76,
3540 source: 1,
3541 a_1: 0.001,
3542 a_0: F64_DNU,
3543 t_ot: 1000,
3544 wn_ot: 5,
3545 delta_t_ls: 18,
3546 wn_lsf: 6,
3547 dn: 3,
3548 delta_t_lsf: 19,
3549 };
3550
3551 assert!((block.tow_seconds() - 1.7).abs() < 1e-6);
3552 assert_eq!(block.prn(), 6);
3553 assert!(block.utc_bias_s().is_none());
3554 assert!((block.utc_drift_s_per_s().unwrap() - 0.001).abs() < 1e-6);
3555 }
3556
3557 #[test]
3558 fn test_gal_utc_parse() {
3559 let mut data = vec![0u8; 35];
3560 data[12] = 74;
3561 data[13] = 2;
3562 data[14..18].copy_from_slice(&0.002_f32.to_le_bytes());
3563 data[18..26].copy_from_slice(&1.25_f64.to_le_bytes());
3564 data[26..30].copy_from_slice(&800_u32.to_le_bytes());
3565 data[30] = 4;
3566 data[31] = 18u8;
3567 data[32] = 5;
3568 data[33] = 2;
3569 data[34] = 19u8;
3570
3571 let header = header_for(block_ids::GAL_UTC, data.len(), 15000, 2701);
3572 let block = GalUtcBlock::parse(&header, &data).unwrap();
3573
3574 assert_eq!(block.svid, 74);
3575 assert_eq!(block.wn_ot, 4);
3576 assert!((block.utc_bias_s().unwrap() - 1.25).abs() < 1e-9);
3577 }
3578
3579 #[test]
3580 fn test_gal_gst_gps_accessors() {
3581 let block = GalGstGpsBlock {
3582 tow_ms: 1800,
3583 wnc: 2800,
3584 svid: 71,
3585 source: 1,
3586 a_1g: F32_DNU,
3587 a_0g: 0.3,
3588 t_og: 7,
3589 wn_og: 8,
3590 };
3591
3592 assert!((block.tow_seconds() - 1.8).abs() < 1e-6);
3593 assert_eq!(block.prn(), 1);
3594 assert!(block.gst_gps_drift_s_per_s().is_none());
3595 assert!((block.gst_gps_offset_s().unwrap() - 0.3).abs() < 1e-6);
3596 }
3597
3598 #[test]
3599 fn test_gal_gst_gps_parse() {
3600 let mut data = vec![0u8; 27];
3601 data[12] = 72;
3602 data[13] = 0;
3603 data[14..18].copy_from_slice(&0.01_f32.to_le_bytes());
3604 data[18..22].copy_from_slice(&0.02_f32.to_le_bytes());
3605 data[22..26].copy_from_slice(&9_u32.to_le_bytes());
3606 data[26] = 10;
3607
3608 let header = header_for(block_ids::GAL_GST_GPS, data.len(), 16000, 2801);
3609 let block = GalGstGpsBlock::parse(&header, &data).unwrap();
3610
3611 assert_eq!(block.svid, 72);
3612 assert_eq!(block.t_og, 9);
3613 assert_eq!(block.wn_og, 10);
3614 }
3615
3616 #[test]
3617 fn test_gps_cnav_parse() {
3618 let mut data = vec![0u8; 170];
3619 data[12] = 12;
3620 data[13] = 0x80;
3621 data[14..16].copy_from_slice(&2045_u16.to_le_bytes());
3622 data[17] = (-2_i8) as u8;
3623 data[18..22].copy_from_slice(&1000_u32.to_le_bytes());
3624 data[22..26].copy_from_slice(&2000_u32.to_le_bytes());
3625 data[50..58].copy_from_slice(&0.5_f64.to_le_bytes());
3626 data[150..154].copy_from_slice(&(-1.25_f32).to_le_bytes());
3627 data[166..170].copy_from_slice(&0.0002_f32.to_le_bytes());
3628
3629 let header = header_for(block_ids::GPS_CNAV, data.len(), 17000, 2900);
3630 let block = GpsCNavBlock::parse(&header, &data).unwrap();
3631
3632 assert_eq!(block.prn, 12);
3633 assert_eq!(block.wn, 2045);
3634 assert_eq!(block.ura_ed, -2);
3635 assert_eq!(block.t_oe, 2000);
3636 assert!((block.m_0 - 0.5).abs() < 1e-12);
3637 assert!((block.t_gd + 1.25).abs() < 1e-6);
3638 assert!((block.isc_l5q5 - 0.0002).abs() < 1e-9);
3639 }
3640
3641 #[test]
3642 fn test_bds_ion_parse() {
3643 let mut data = vec![0u8; 46];
3644 data[12] = 7;
3645 data[14..18].copy_from_slice(&0.1_f32.to_le_bytes());
3646 data[30..34].copy_from_slice(&1.1_f32.to_le_bytes());
3647 data[42..46].copy_from_slice(&4.4_f32.to_le_bytes());
3648
3649 let header = header_for(block_ids::BDS_ION, data.len(), 18000, 2901);
3650 let block = BdsIonBlock::parse(&header, &data).unwrap();
3651
3652 assert_eq!(block.prn, 7);
3653 assert!((block.alpha_0 - 0.1).abs() < 1e-6);
3654 assert!((block.beta_0 - 1.1).abs() < 1e-6);
3655 assert!((block.beta_3 - 4.4).abs() < 1e-6);
3656 }
3657
3658 #[test]
3659 fn test_bds_cnav1_parse() {
3660 let mut data = vec![0u8; 158];
3661 data[12] = 3;
3662 data[13] = 0x02;
3663 data[14..18].copy_from_slice(&345600_u32.to_le_bytes());
3664 data[74..78].copy_from_slice(&1.25_f32.to_le_bytes());
3665 data[143] = 9;
3666 data[144..146].copy_from_slice(&512_u16.to_le_bytes());
3667 data[146..150].copy_from_slice(&0.001_f32.to_le_bytes());
3668 data[154..158].copy_from_slice(&(-0.002_f32).to_le_bytes());
3669
3670 let header = header_for(block_ids::BDS_CNAV1, data.len(), 19000, 2902);
3671 let block = BdsCNav1Block::parse(&header, &data).unwrap();
3672
3673 assert_eq!(block.prn_idx, 3);
3674 assert_eq!(block.flags & 0x03, 0x02);
3675 assert_eq!(block.t_oe, 345600);
3676 assert!((block.omega_dot - 1.25).abs() < 1e-6);
3677 assert_eq!(block.iode, 9);
3678 assert_eq!(block.iodc, 512);
3679 assert!((block.isc_b1cd - 0.001).abs() < 1e-6);
3680 assert!((block.t_gd_b2ap + 0.002).abs() < 1e-6);
3681 }
3682
3683 #[test]
3684 fn test_geo_iono_delay_accessors() {
3685 let block = GeoIonoDelayBlock {
3686 tow_ms: 2500,
3687 wnc: 2100,
3688 prn: 120,
3689 band_nbr: 3,
3690 iodi: 5,
3691 idc: vec![GeoIonoDelayIdc {
3692 igp_mask_no: 10,
3693 givei: 4,
3694 vertical_delay_m_raw: F32_DNU,
3695 }],
3696 };
3697
3698 assert!((block.tow_seconds() - 2.5).abs() < 1e-6);
3699 assert_eq!(block.num_idc(), 1);
3700 assert!(block.idc[0].vertical_delay_m().is_none());
3701 }
3702
3703 #[test]
3704 fn test_geo_iono_delay_parse() {
3705 let mut data = vec![0u8; 18 + (2 * 8)];
3706 data[12] = 120; data[13] = 3; data[14] = 5; data[15] = 2; data[16] = 8; data[17] = 0; data[18] = 10; data[19] = 4; data[22..26].copy_from_slice(&12.5_f32.to_le_bytes()); data[26] = 11; data[27] = 5; data[30..34].copy_from_slice(&F32_DNU.to_le_bytes()); let header = header_for(block_ids::GEO_IONO_DELAY, data.len(), 4321, 2024);
3724 let block = GeoIonoDelayBlock::parse(&header, &data).unwrap();
3725
3726 assert_eq!(block.prn, 120);
3727 assert_eq!(block.band_nbr, 3);
3728 assert_eq!(block.iodi, 5);
3729 assert_eq!(block.num_idc(), 2);
3730 assert_eq!(block.idc[0].igp_mask_no, 10);
3731 assert_eq!(block.idc[1].givei, 5);
3732 assert!((block.idc[0].vertical_delay_m().unwrap() - 12.5).abs() < 1e-6);
3733 assert!(block.idc[1].vertical_delay_m().is_none());
3734 }
3735
3736 #[test]
3737 fn test_geo_iono_delay_sbf_block_parse() {
3738 let total_len = 36usize; let mut data = vec![0u8; total_len];
3740 data[0..2].copy_from_slice(&SBF_SYNC);
3741 data[2..4].copy_from_slice(&0_u16.to_le_bytes()); data[4..6].copy_from_slice(&block_ids::GEO_IONO_DELAY.to_le_bytes()); data[6..8].copy_from_slice(&(total_len as u16).to_le_bytes()); data[8..12].copy_from_slice(&9876_u32.to_le_bytes()); data[12..14].copy_from_slice(&2025_u16.to_le_bytes()); data[14] = 120; data[15] = 2; data[16] = 7; data[17] = 2; data[18] = 8; data[19] = 0; data[20] = 10; data[21] = 3; data[24..28].copy_from_slice(&8.25_f32.to_le_bytes());
3759
3760 data[28] = 11; data[29] = 6; data[32..36].copy_from_slice(&9.75_f32.to_le_bytes());
3764
3765 let (block, used) = SbfBlock::parse(&data).unwrap();
3766 assert_eq!(used, total_len);
3767 assert_eq!(block.block_id(), block_ids::GEO_IONO_DELAY);
3768 match block {
3769 SbfBlock::GeoIonoDelay(geo) => {
3770 assert_eq!(geo.tow_ms(), 9876);
3771 assert_eq!(geo.wnc(), 2025);
3772 assert_eq!(geo.num_idc(), 2);
3773 assert!((geo.idc[0].vertical_delay_m().unwrap() - 8.25).abs() < 1e-6);
3774 assert!((geo.idc[1].vertical_delay_m().unwrap() - 9.75).abs() < 1e-6);
3775 }
3776 _ => panic!("Expected GeoIonoDelay block"),
3777 }
3778 }
3779
3780 #[test]
3781 fn test_gps_raw_ca_parse() {
3782 let header = header_for(4017, 57, 5000, 2150);
3783 let mut data = vec![0u8; 57];
3784 data[12] = 12; data[13] = 1; data[14] = 0; data[15] = 2; data[16] = 0; data[17..57].copy_from_slice(&[0xABu8; 40]); let block = GpsRawCaBlock::parse(&header, &data).unwrap();
3792 assert_eq!(block.tow_seconds(), 5.0);
3793 assert_eq!(block.wnc(), 2150);
3794 assert_eq!(block.svid, 12);
3795 assert!(block.crc_ok());
3796 assert_eq!(block.viterbi_count, 0);
3797 assert_eq!(block.nav_bits_slice()[0], 0xAB);
3798 }
3799
3800 #[test]
3801 fn test_gps_raw_ca_too_short() {
3802 let header = header_for(4017, 57, 0, 0);
3803 let data = [0u8; 50];
3804 assert!(GpsRawCaBlock::parse(&header, &data).is_err());
3805 }
3806
3807 #[test]
3808 fn test_gps_raw_l2c_parse() {
3809 let header = header_for(4018, 57, 6000, 2200);
3810 let mut data = vec![0u8; 57];
3811 data[12] = 8;
3812 data[13] = 1;
3813 data[17..57].copy_from_slice(&[0xCDu8; 40]);
3814
3815 let block = GpsRawL2CBlock::parse(&header, &data).unwrap();
3816 assert_eq!(block.tow_seconds(), 6.0);
3817 assert_eq!(block.svid, 8);
3818 assert!(block.crc_ok());
3819 assert_eq!(block.nav_bits_slice()[0], 0xCD);
3820 }
3821
3822 #[test]
3823 fn test_gps_raw_l5_parse() {
3824 let header = header_for(4019, 57, 7000, 2250);
3825 let mut data = vec![0u8; 57];
3826 data[12] = 15;
3827 data[13] = 0;
3828
3829 let block = GpsRawL5Block::parse(&header, &data).unwrap();
3830 assert_eq!(block.tow_seconds(), 7.0);
3831 assert_eq!(block.svid, 15);
3832 assert!(!block.crc_ok());
3833 }
3834
3835 #[test]
3836 fn test_glo_raw_ca_parse() {
3837 let header = header_for(4026, 29, 8000, 2300);
3838 let mut data = vec![0u8; 29];
3839 data[12] = 45;
3840 data[13] = 1;
3841 data[16] = 3;
3842 data[17..29].copy_from_slice(&[0x12u8; 12]);
3843
3844 let block = GloRawCaBlock::parse(&header, &data).unwrap();
3845 assert_eq!(block.tow_seconds(), 8.0);
3846 assert_eq!(block.svid, 45);
3847 assert_eq!(block.freq_nr, 3);
3848 assert_eq!(block.nav_bits_slice()[0], 0x12);
3849 }
3850
3851 #[test]
3852 fn test_glo_raw_ca_too_short() {
3853 let header = header_for(4026, 29, 0, 0);
3854 let data = [0u8; 25];
3855 assert!(GloRawCaBlock::parse(&header, &data).is_err());
3856 }
3857
3858 #[test]
3859 fn test_gal_raw_fnav_parse() {
3860 let header = header_for(4022, 49, 1000, 2100);
3861 let mut data = vec![0u8; 49];
3862 data[12] = 85; data[13] = 1;
3864 data[17..49].copy_from_slice(&[0x11u8; 32]);
3865 let block = GalRawFnavBlock::parse(&header, &data).unwrap();
3866 assert_eq!(block.tow_seconds(), 1.0);
3867 assert_eq!(block.svid, 85);
3868 assert!(block.crc_ok());
3869 assert_eq!(block.nav_bits_slice()[0], 0x11);
3870 }
3871
3872 #[test]
3873 fn test_gal_raw_inav_parse() {
3874 let header = header_for(4023, 49, 2000, 2101);
3875 let mut data = vec![0u8; 49];
3876 data[12] = 72;
3877 data[13] = 0;
3878 let block = GalRawInavBlock::parse(&header, &data).unwrap();
3879 assert_eq!(block.tow_seconds(), 2.0);
3880 assert!(!block.crc_ok());
3881 }
3882
3883 #[test]
3884 fn test_gal_raw_cnav_parse() {
3885 let header = header_for(4024, 81, 3000, 2102);
3886 let mut data = vec![0u8; 81];
3887 data[12] = 90;
3888 data[17..81].copy_from_slice(&[0x22u8; 64]);
3889 let block = GalRawCnavBlock::parse(&header, &data).unwrap();
3890 assert_eq!(block.tow_seconds(), 3.0);
3891 assert_eq!(block.nav_bits_slice()[63], 0x22);
3892 }
3893
3894 #[test]
3895 fn test_geo_raw_l1_parse() {
3896 let header = header_for(4020, 49, 4000, 2103);
3897 let mut data = vec![0u8; 49];
3898 data[12] = 135; data[13] = 1;
3900 data[17..49].copy_from_slice(&[0x33u8; 32]);
3901 let block = GeoRawL1Block::parse(&header, &data).unwrap();
3902 assert_eq!(block.tow_seconds(), 4.0);
3903 assert_eq!(block.svid, 135);
3904 }
3905
3906 #[test]
3907 fn test_cmp_raw_parse() {
3908 let header = header_for(4047, 57, 5000, 2104);
3909 let mut data = vec![0u8; 57];
3910 data[12] = 155; data[17..57].copy_from_slice(&[0x44u8; 40]);
3912 let block = CmpRawBlock::parse(&header, &data).unwrap();
3913 assert_eq!(block.tow_seconds(), 5.0);
3914 assert_eq!(block.nav_bits_slice()[39], 0x44);
3915 }
3916
3917 #[test]
3918 fn test_qzs_raw_l1ca_parse() {
3919 let header = header_for(4066, 57, 6000, 2105);
3920 let mut data = vec![0u8; 57];
3921 data[12] = 183; data[13] = 1;
3923 let block = QzsRawL1CaBlock::parse(&header, &data).unwrap();
3924 assert_eq!(block.tow_seconds(), 6.0);
3925 assert_eq!(block.svid, 183);
3926 }
3927
3928 #[test]
3929 fn test_qzs_raw_l2c_parse() {
3930 let header = header_for(4067, 57, 7000, 2106);
3931 let mut data = vec![0u8; 57];
3932 data[12] = 186;
3933 let block = QzsRawL2CBlock::parse(&header, &data).unwrap();
3934 assert_eq!(block.tow_seconds(), 7.0);
3935 }
3936
3937 #[test]
3938 fn test_qzs_raw_l5_parse() {
3939 let header = header_for(4068, 57, 8000, 2107);
3940 let mut data = vec![0u8; 57];
3941 data[12] = 181;
3942 let block = QzsRawL5Block::parse(&header, &data).unwrap();
3943 assert_eq!(block.tow_seconds(), 8.0);
3944 }
3945
3946 #[test]
3947 fn test_gal_raw_fnav_too_short() {
3948 let header = header_for(4022, 49, 0, 0);
3949 assert!(GalRawFnavBlock::parse(&header, &[0u8; 40]).is_err());
3950 }
3951
3952 #[test]
3953 fn test_gal_raw_cnav_too_short() {
3954 let header = header_for(4024, 81, 0, 0);
3955 assert!(GalRawCnavBlock::parse(&header, &[0u8; 70]).is_err());
3956 }
3957
3958 #[test]
3959 fn test_gal_sar_rlm_accessors() {
3960 let block = GalSarRlmBlock {
3961 tow_ms: 4500,
3962 wnc: 2200,
3963 svid: 74,
3964 source: 2,
3965 rlm_length_bits: 80,
3966 rlm_bits_words: vec![0x8000_0000, 0, 0x0001_0000],
3967 };
3968
3969 assert!((block.tow_seconds() - 4.5).abs() < 1e-6);
3970 assert_eq!(block.prn(), 4);
3971 assert_eq!(block.rlm_length_bits(), 80);
3972 assert_eq!(block.rlm_bits_words().len(), 3);
3973 assert_eq!(block.bit(0), Some(true));
3974 assert_eq!(block.bit(1), Some(false));
3975 assert_eq!(block.bit(79), Some(true));
3976 assert_eq!(block.bit(80), None);
3977 }
3978
3979 #[test]
3980 fn test_gal_sar_rlm_parse() {
3981 let mut data = vec![0u8; 30];
3982 data[12] = 72; data[13] = 16; data[14] = 80; data[18..22].copy_from_slice(&0x1234_5678_u32.to_le_bytes());
3986 data[22..26].copy_from_slice(&0x9abc_def0_u32.to_le_bytes());
3987 data[26..30].copy_from_slice(&0x0001_0000_u32.to_le_bytes());
3988
3989 let header = header_for(block_ids::GAL_SAR_RLM, data.len(), 3210, 2048);
3990 let block = GalSarRlmBlock::parse(&header, &data).unwrap();
3991
3992 assert_eq!(block.svid, 72);
3993 assert_eq!(block.source, 16);
3994 assert_eq!(block.rlm_length_bits(), 80);
3995 assert_eq!(
3996 block.rlm_bits_words(),
3997 &[0x1234_5678, 0x9abc_def0, 0x0001_0000]
3998 );
3999 assert_eq!(block.bit(79), Some(true));
4000 }
4001
4002 #[test]
4003 fn test_gal_sar_rlm_too_short() {
4004 let header = header_for(block_ids::GAL_SAR_RLM, 30, 0, 0);
4005 assert!(GalSarRlmBlock::parse(&header, &[0u8; 20]).is_err());
4006 }
4007}