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)]
1504pub struct GalAuthStatusBlock {
1505 tow_ms: u32,
1506 wnc: u16,
1507 pub osnma_status: u16,
1509 pub trusted_time_delta: f32,
1511 pub gal_active_mask: u64,
1513 pub gal_authentic_mask: u64,
1515 pub gps_active_mask: u64,
1517 pub gps_authentic_mask: u64,
1519}
1520
1521impl GalAuthStatusBlock {
1522 pub fn tow_ms(&self) -> u32 {
1523 self.tow_ms
1524 }
1525 pub fn wnc(&self) -> u16 {
1526 self.wnc
1527 }
1528 pub fn tow_seconds(&self) -> f64 {
1529 self.tow_ms as f64 * 0.001
1530 }
1531
1532 pub fn trusted_time_delta_s(&self) -> Option<f32> {
1533 f32_or_none(self.trusted_time_delta)
1534 }
1535}
1536
1537impl SbfBlockParse for GalAuthStatusBlock {
1538 const BLOCK_ID: u16 = block_ids::GAL_AUTH_STATUS;
1539
1540 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1541 if data.len() < 50 {
1542 return Err(SbfError::ParseError("GALAuthStatus too short".into()));
1543 }
1544
1545 let osnma_status = u16::from_le_bytes([data[12], data[13]]);
1546 let trusted_time_delta = f32::from_le_bytes(data[14..18].try_into().unwrap());
1547 let gal_active_mask = u64::from_le_bytes(data[18..26].try_into().unwrap());
1548 let gal_authentic_mask = u64::from_le_bytes(data[26..34].try_into().unwrap());
1549 let gps_active_mask = u64::from_le_bytes(data[34..42].try_into().unwrap());
1550 let gps_authentic_mask = u64::from_le_bytes(data[42..50].try_into().unwrap());
1551
1552 Ok(Self {
1553 tow_ms: header.tow_ms,
1554 wnc: header.wnc,
1555 osnma_status,
1556 trusted_time_delta,
1557 gal_active_mask,
1558 gal_authentic_mask,
1559 gps_active_mask,
1560 gps_authentic_mask,
1561 })
1562 }
1563}
1564
1565#[derive(Debug, Clone)]
1573pub struct GalSarRlmBlock {
1574 tow_ms: u32,
1575 wnc: u16,
1576 pub svid: u8,
1578 pub source: u8,
1580 rlm_length_bits: u8,
1582 rlm_bits_words: Vec<u32>,
1584}
1585
1586impl GalSarRlmBlock {
1587 pub fn tow_seconds(&self) -> f64 {
1588 self.tow_ms as f64 * 0.001
1589 }
1590 pub fn tow_ms(&self) -> u32 {
1591 self.tow_ms
1592 }
1593 pub fn wnc(&self) -> u16 {
1594 self.wnc
1595 }
1596
1597 pub fn prn(&self) -> u8 {
1599 if (71..=106).contains(&self.svid) {
1600 self.svid - 70
1601 } else {
1602 self.svid
1603 }
1604 }
1605
1606 pub fn rlm_length_bits(&self) -> u8 {
1607 self.rlm_length_bits
1608 }
1609
1610 pub fn rlm_bits_words(&self) -> &[u32] {
1612 &self.rlm_bits_words
1613 }
1614
1615 pub fn bit(&self, bit_index: usize) -> Option<bool> {
1617 if bit_index >= self.rlm_length_bits as usize {
1618 return None;
1619 }
1620
1621 let word_index = bit_index / 32;
1622 let bit_in_word = 31 - (bit_index % 32);
1623 self.rlm_bits_words
1624 .get(word_index)
1625 .map(|word| ((word >> bit_in_word) & 1) != 0)
1626 }
1627}
1628
1629impl SbfBlockParse for GalSarRlmBlock {
1630 const BLOCK_ID: u16 = block_ids::GAL_SAR_RLM;
1631
1632 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1633 let block_len = header.length as usize;
1634 let data_len = block_len.saturating_sub(2);
1635 if data_len < 18 || data.len() < data_len {
1636 return Err(SbfError::ParseError("GALSARRLM too short".into()));
1637 }
1638
1639 let svid = data[12];
1640 let source = data[13];
1641 let rlm_length_bits = data[14];
1642
1643 let word_count = match rlm_length_bits {
1644 80 => 3,
1645 160 => 5,
1646 0 => 0,
1647 _ => usize::from(rlm_length_bits).div_ceil(32),
1648 };
1649 let required_len = 18 + word_count * 4;
1650 if data_len < required_len {
1651 return Err(SbfError::ParseError("GALSARRLM payload too short".into()));
1652 }
1653
1654 let mut rlm_bits_words = Vec::with_capacity(word_count);
1655 let mut offset = 18;
1656 for _ in 0..word_count {
1657 rlm_bits_words.push(u32::from_le_bytes(
1658 data[offset..offset + 4].try_into().unwrap(),
1659 ));
1660 offset += 4;
1661 }
1662
1663 Ok(Self {
1664 tow_ms: header.tow_ms,
1665 wnc: header.wnc,
1666 svid,
1667 source,
1668 rlm_length_bits,
1669 rlm_bits_words,
1670 })
1671 }
1672}
1673
1674#[derive(Debug, Clone)]
1683pub struct GpsCNavBlock {
1684 tow_ms: u32,
1685 wnc: u16,
1686 pub prn: u8,
1688 pub flags: u8,
1690 pub wn: u16,
1692 pub health: u8,
1694 pub ura_ed: i8,
1696 pub t_op: u32,
1698 pub t_oe: u32,
1700 pub a: f64,
1702 pub a_dot: f64,
1704 pub delta_n: f32,
1706 pub delta_n_dot: f32,
1708 pub m_0: f64,
1710 pub e: f64,
1712 pub omega: f64,
1714 pub omega_0: f64,
1716 pub omega_dot: f64,
1718 pub i_0: f64,
1720 pub i_dot: f32,
1722 pub c_is: f32,
1724 pub c_ic: f32,
1726 pub c_rs: f32,
1728 pub c_rc: f32,
1730 pub c_us: f32,
1732 pub c_uc: f32,
1734 pub t_oc: u32,
1736 pub ura_ned0: i8,
1738 pub ura_ned1: u8,
1740 pub ura_ned2: u8,
1742 pub wn_op: u8,
1744 pub a_f2: f32,
1746 pub a_f1: f32,
1748 pub a_f0: f64,
1750 pub t_gd: f32,
1752 pub isc_l1ca: f32,
1754 pub isc_l2c: f32,
1756 pub isc_l5i5: f32,
1758 pub isc_l5q5: f32,
1760}
1761
1762impl GpsCNavBlock {
1763 pub fn tow_seconds(&self) -> f64 {
1764 self.tow_ms as f64 * 0.001
1765 }
1766 pub fn tow_ms(&self) -> u32 {
1767 self.tow_ms
1768 }
1769 pub fn wnc(&self) -> u16 {
1770 self.wnc
1771 }
1772
1773 pub fn is_alert(&self) -> bool {
1775 (self.flags & 0x01) != 0
1776 }
1777
1778 pub fn l2c_used(&self) -> bool {
1780 (self.flags & 0x40) != 0
1781 }
1782
1783 pub fn l5_used(&self) -> bool {
1785 (self.flags & 0x80) != 0
1786 }
1787
1788 pub fn is_healthy(&self) -> bool {
1790 self.health == 0
1791 }
1792
1793 pub fn group_delay_s(&self) -> Option<f32> {
1795 f32_or_none(self.t_gd)
1796 }
1797
1798 pub fn isc_l1ca_s(&self) -> Option<f32> {
1800 f32_or_none(self.isc_l1ca)
1801 }
1802
1803 pub fn isc_l2c_s(&self) -> Option<f32> {
1805 f32_or_none(self.isc_l2c)
1806 }
1807
1808 pub fn isc_l5i5_s(&self) -> Option<f32> {
1810 f32_or_none(self.isc_l5i5)
1811 }
1812
1813 pub fn isc_l5q5_s(&self) -> Option<f32> {
1815 f32_or_none(self.isc_l5q5)
1816 }
1817}
1818
1819impl SbfBlockParse for GpsCNavBlock {
1820 const BLOCK_ID: u16 = block_ids::GPS_CNAV;
1821
1822 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1823 if data.len() < 170 {
1825 return Err(SbfError::ParseError("GPSCNav too short".into()));
1826 }
1827
1828 let prn = data[12];
1868 let flags = data[13];
1869 let wn = u16::from_le_bytes([data[14], data[15]]);
1870 let health = data[16];
1871 let ura_ed = data[17] as i8;
1872 let t_op = u32::from_le_bytes(data[18..22].try_into().unwrap());
1873 let t_oe = u32::from_le_bytes(data[22..26].try_into().unwrap());
1874 let a = f64::from_le_bytes(data[26..34].try_into().unwrap());
1875 let a_dot = f64::from_le_bytes(data[34..42].try_into().unwrap());
1876 let delta_n = f32::from_le_bytes(data[42..46].try_into().unwrap());
1877 let delta_n_dot = f32::from_le_bytes(data[46..50].try_into().unwrap());
1878 let m_0 = f64::from_le_bytes(data[50..58].try_into().unwrap());
1879 let e = f64::from_le_bytes(data[58..66].try_into().unwrap());
1880 let omega = f64::from_le_bytes(data[66..74].try_into().unwrap());
1881 let omega_0 = f64::from_le_bytes(data[74..82].try_into().unwrap());
1882 let omega_dot = f64::from_le_bytes(data[82..90].try_into().unwrap());
1883 let i_0 = f64::from_le_bytes(data[90..98].try_into().unwrap());
1884 let i_dot = f32::from_le_bytes(data[98..102].try_into().unwrap());
1885 let c_is = f32::from_le_bytes(data[102..106].try_into().unwrap());
1886 let c_ic = f32::from_le_bytes(data[106..110].try_into().unwrap());
1887 let c_rs = f32::from_le_bytes(data[110..114].try_into().unwrap());
1888 let c_rc = f32::from_le_bytes(data[114..118].try_into().unwrap());
1889 let c_us = f32::from_le_bytes(data[118..122].try_into().unwrap());
1890 let c_uc = f32::from_le_bytes(data[122..126].try_into().unwrap());
1891 let t_oc = u32::from_le_bytes(data[126..130].try_into().unwrap());
1892 let ura_ned0 = data[130] as i8;
1893 let ura_ned1 = data[131];
1894 let ura_ned2 = data[132];
1895 let wn_op = data[133];
1896 let a_f2 = f32::from_le_bytes(data[134..138].try_into().unwrap());
1897 let a_f1 = f32::from_le_bytes(data[138..142].try_into().unwrap());
1898 let a_f0 = f64::from_le_bytes(data[142..150].try_into().unwrap());
1899 let t_gd = f32::from_le_bytes(data[150..154].try_into().unwrap());
1900 let isc_l1ca = f32::from_le_bytes(data[154..158].try_into().unwrap());
1901 let isc_l2c = f32::from_le_bytes(data[158..162].try_into().unwrap());
1902 let isc_l5i5 = f32::from_le_bytes(data[162..166].try_into().unwrap());
1903 let isc_l5q5 = f32::from_le_bytes(data[166..170].try_into().unwrap());
1904
1905 Ok(Self {
1906 tow_ms: header.tow_ms,
1907 wnc: header.wnc,
1908 prn,
1909 flags,
1910 wn,
1911 health,
1912 ura_ed,
1913 t_op,
1914 t_oe,
1915 a,
1916 a_dot,
1917 delta_n,
1918 delta_n_dot,
1919 m_0,
1920 e,
1921 omega,
1922 omega_0,
1923 omega_dot,
1924 i_0,
1925 i_dot,
1926 c_is,
1927 c_ic,
1928 c_rs,
1929 c_rc,
1930 c_us,
1931 c_uc,
1932 t_oc,
1933 ura_ned0,
1934 ura_ned1,
1935 ura_ned2,
1936 wn_op,
1937 a_f2,
1938 a_f1,
1939 a_f0,
1940 t_gd,
1941 isc_l1ca,
1942 isc_l2c,
1943 isc_l5i5,
1944 isc_l5q5,
1945 })
1946 }
1947}
1948
1949#[derive(Debug, Clone)]
1957pub struct BdsIonBlock {
1958 tow_ms: u32,
1959 wnc: u16,
1960 pub prn: u8,
1962 pub alpha_0: f32,
1964 pub alpha_1: f32,
1966 pub alpha_2: f32,
1968 pub alpha_3: f32,
1970 pub beta_0: f32,
1972 pub beta_1: f32,
1974 pub beta_2: f32,
1976 pub beta_3: f32,
1978}
1979
1980impl BdsIonBlock {
1981 pub fn tow_seconds(&self) -> f64 {
1982 self.tow_ms as f64 * 0.001
1983 }
1984 pub fn tow_ms(&self) -> u32 {
1985 self.tow_ms
1986 }
1987 pub fn wnc(&self) -> u16 {
1988 self.wnc
1989 }
1990
1991 pub fn alpha_0(&self) -> Option<f32> {
1992 f32_or_none(self.alpha_0)
1993 }
1994
1995 pub fn beta_0(&self) -> Option<f32> {
1996 f32_or_none(self.beta_0)
1997 }
1998}
1999
2000impl SbfBlockParse for BdsIonBlock {
2001 const BLOCK_ID: u16 = block_ids::BDS_ION;
2002
2003 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2004 if data.len() < 46 {
2006 return Err(SbfError::ParseError("BDSIon too short".into()));
2007 }
2008
2009 let prn = data[12];
2022 let alpha_0 = f32::from_le_bytes(data[14..18].try_into().unwrap());
2024 let alpha_1 = f32::from_le_bytes(data[18..22].try_into().unwrap());
2025 let alpha_2 = f32::from_le_bytes(data[22..26].try_into().unwrap());
2026 let alpha_3 = f32::from_le_bytes(data[26..30].try_into().unwrap());
2027 let beta_0 = f32::from_le_bytes(data[30..34].try_into().unwrap());
2028 let beta_1 = f32::from_le_bytes(data[34..38].try_into().unwrap());
2029 let beta_2 = f32::from_le_bytes(data[38..42].try_into().unwrap());
2030 let beta_3 = f32::from_le_bytes(data[42..46].try_into().unwrap());
2031
2032 Ok(Self {
2033 tow_ms: header.tow_ms,
2034 wnc: header.wnc,
2035 prn,
2036 alpha_0,
2037 alpha_1,
2038 alpha_2,
2039 alpha_3,
2040 beta_0,
2041 beta_1,
2042 beta_2,
2043 beta_3,
2044 })
2045 }
2046}
2047
2048#[derive(Debug, Clone)]
2056pub struct BdsCNav1Block {
2057 tow_ms: u32,
2058 wnc: u16,
2059 pub prn_idx: u8,
2061 pub flags: u8,
2063 pub t_oe: u32,
2065 pub a: f64,
2067 pub a_dot: f64,
2069 pub delta_n0: f32,
2071 pub delta_n0_dot: f32,
2073 pub m_0: f64,
2075 pub e: f64,
2077 pub omega: f64,
2079 pub omega_0: f64,
2081 pub omega_dot: f32,
2083 pub i_0: f64,
2085 pub i_dot: f32,
2087 pub c_is: f32,
2089 pub c_ic: f32,
2091 pub c_rs: f32,
2093 pub c_rc: f32,
2095 pub c_us: f32,
2097 pub c_uc: f32,
2099 pub t_oc: u32,
2101 pub a_2: f32,
2103 pub a_1: f32,
2105 pub a_0: f64,
2107 pub t_op: u32,
2109 pub sisai_ocb: u8,
2111 pub sisai_oc12: u8,
2113 pub sisai_oe: u8,
2115 pub sismai: u8,
2117 pub health_if: u8,
2119 pub iode: u8,
2121 pub iodc: u16,
2123 pub isc_b1cd: f32,
2125 pub t_gd_b1cp: f32,
2127 pub t_gd_b2ap: f32,
2129}
2130
2131impl BdsCNav1Block {
2132 pub fn tow_seconds(&self) -> f64 {
2133 self.tow_ms as f64 * 0.001
2134 }
2135 pub fn tow_ms(&self) -> u32 {
2136 self.tow_ms
2137 }
2138 pub fn wnc(&self) -> u16 {
2139 self.wnc
2140 }
2141
2142 pub fn satellite_type(&self) -> u8 {
2144 self.flags & 0x03
2145 }
2146
2147 pub fn is_geo(&self) -> bool {
2149 self.satellite_type() == 1
2150 }
2151
2152 pub fn is_igso(&self) -> bool {
2154 self.satellite_type() == 2
2155 }
2156
2157 pub fn is_meo(&self) -> bool {
2159 self.satellite_type() == 3
2160 }
2161
2162 pub fn is_healthy(&self) -> bool {
2164 (self.health_if & 0xC0) == 0
2165 }
2166
2167 pub fn isc_b1cd_s(&self) -> Option<f32> {
2169 f32_or_none(self.isc_b1cd)
2170 }
2171
2172 pub fn t_gd_b1cp_s(&self) -> Option<f32> {
2174 f32_or_none(self.t_gd_b1cp)
2175 }
2176
2177 pub fn t_gd_b2ap_s(&self) -> Option<f32> {
2179 f32_or_none(self.t_gd_b2ap)
2180 }
2181}
2182
2183impl SbfBlockParse for BdsCNav1Block {
2184 const BLOCK_ID: u16 = block_ids::BDS_CNAV1;
2185
2186 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2187 if data.len() < 158 {
2196 return Err(SbfError::ParseError("BDSCNav1 too short".into()));
2197 }
2198
2199 let prn_idx = data[12];
2237 let flags = data[13];
2238 let t_oe = u32::from_le_bytes(data[14..18].try_into().unwrap());
2239 let a = f64::from_le_bytes(data[18..26].try_into().unwrap());
2240 let a_dot = f64::from_le_bytes(data[26..34].try_into().unwrap());
2241 let delta_n0 = f32::from_le_bytes(data[34..38].try_into().unwrap());
2242 let delta_n0_dot = f32::from_le_bytes(data[38..42].try_into().unwrap());
2243 let m_0 = f64::from_le_bytes(data[42..50].try_into().unwrap());
2244 let e = f64::from_le_bytes(data[50..58].try_into().unwrap());
2245 let omega = f64::from_le_bytes(data[58..66].try_into().unwrap());
2246 let omega_0 = f64::from_le_bytes(data[66..74].try_into().unwrap());
2247 let omega_dot = f32::from_le_bytes(data[74..78].try_into().unwrap());
2248 let i_0 = f64::from_le_bytes(data[78..86].try_into().unwrap());
2249 let i_dot = f32::from_le_bytes(data[86..90].try_into().unwrap());
2250 let c_is = f32::from_le_bytes(data[90..94].try_into().unwrap());
2251 let c_ic = f32::from_le_bytes(data[94..98].try_into().unwrap());
2252 let c_rs = f32::from_le_bytes(data[98..102].try_into().unwrap());
2253 let c_rc = f32::from_le_bytes(data[102..106].try_into().unwrap());
2254 let c_us = f32::from_le_bytes(data[106..110].try_into().unwrap());
2255 let c_uc = f32::from_le_bytes(data[110..114].try_into().unwrap());
2256 let t_oc = u32::from_le_bytes(data[114..118].try_into().unwrap());
2257 let a_2 = f32::from_le_bytes(data[118..122].try_into().unwrap());
2258 let a_1 = f32::from_le_bytes(data[122..126].try_into().unwrap());
2259 let a_0 = f64::from_le_bytes(data[126..134].try_into().unwrap());
2260 let t_op = u32::from_le_bytes(data[134..138].try_into().unwrap());
2261 let sisai_ocb = data[138];
2262 let sisai_oc12 = data[139];
2263 let sisai_oe = data[140];
2264 let sismai = data[141];
2265 let health_if = data[142];
2266 let iode = data[143];
2267 let iodc = u16::from_le_bytes([data[144], data[145]]);
2268 let isc_b1cd = f32::from_le_bytes(data[146..150].try_into().unwrap());
2269 let t_gd_b1cp = f32::from_le_bytes(data[150..154].try_into().unwrap());
2270 let t_gd_b2ap = f32::from_le_bytes(data[154..158].try_into().unwrap());
2271
2272 Ok(Self {
2273 tow_ms: header.tow_ms,
2274 wnc: header.wnc,
2275 prn_idx,
2276 flags,
2277 t_oe,
2278 a,
2279 a_dot,
2280 delta_n0,
2281 delta_n0_dot,
2282 m_0,
2283 e,
2284 omega,
2285 omega_0,
2286 omega_dot,
2287 i_0,
2288 i_dot,
2289 c_is,
2290 c_ic,
2291 c_rs,
2292 c_rc,
2293 c_us,
2294 c_uc,
2295 t_oc,
2296 a_2,
2297 a_1,
2298 a_0,
2299 t_op,
2300 sisai_ocb,
2301 sisai_oc12,
2302 sisai_oe,
2303 sismai,
2304 health_if,
2305 iode,
2306 iodc,
2307 isc_b1cd,
2308 t_gd_b1cp,
2309 t_gd_b2ap,
2310 })
2311 }
2312}
2313
2314#[derive(Debug, Clone)]
2322pub struct GpsRawCaBlock {
2323 tow_ms: u32,
2324 wnc: u16,
2325 pub svid: u8,
2327 pub crc_passed: u8,
2329 pub viterbi_count: u8,
2331 pub source: u8,
2333 pub freq_nr: u8,
2335 pub nav_bits: [u8; 40],
2337}
2338
2339impl GpsRawCaBlock {
2340 pub fn tow_seconds(&self) -> f64 {
2341 self.tow_ms as f64 * 0.001
2342 }
2343 pub fn tow_ms(&self) -> u32 {
2344 self.tow_ms
2345 }
2346 pub fn wnc(&self) -> u16 {
2347 self.wnc
2348 }
2349 pub fn crc_ok(&self) -> bool {
2351 self.crc_passed != 0
2352 }
2353 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2355 &self.nav_bits
2356 }
2357}
2358
2359impl SbfBlockParse for GpsRawCaBlock {
2360 const BLOCK_ID: u16 = block_ids::GPS_RAW_CA;
2361
2362 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2363 const MIN_LEN: usize = 57;
2365 if data.len() < MIN_LEN {
2366 return Err(SbfError::ParseError("GPSRawCA too short".into()));
2367 }
2368
2369 let mut nav_bits = [0u8; 40];
2370 nav_bits.copy_from_slice(&data[17..57]);
2371
2372 Ok(Self {
2373 tow_ms: header.tow_ms,
2374 wnc: header.wnc,
2375 svid: data[12],
2376 crc_passed: data[13],
2377 viterbi_count: data[14],
2378 source: data[15],
2379 freq_nr: data[16],
2380 nav_bits,
2381 })
2382 }
2383}
2384
2385#[derive(Debug, Clone)]
2393pub struct GpsRawL2CBlock {
2394 tow_ms: u32,
2395 wnc: u16,
2396 pub svid: u8,
2398 pub crc_passed: u8,
2400 pub viterbi_count: u8,
2402 pub source: u8,
2404 pub freq_nr: u8,
2406 pub nav_bits: [u8; 40],
2408}
2409
2410impl GpsRawL2CBlock {
2411 pub fn tow_seconds(&self) -> f64 {
2412 self.tow_ms as f64 * 0.001
2413 }
2414 pub fn tow_ms(&self) -> u32 {
2415 self.tow_ms
2416 }
2417 pub fn wnc(&self) -> u16 {
2418 self.wnc
2419 }
2420 pub fn crc_ok(&self) -> bool {
2421 self.crc_passed != 0
2422 }
2423 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2424 &self.nav_bits
2425 }
2426}
2427
2428impl SbfBlockParse for GpsRawL2CBlock {
2429 const BLOCK_ID: u16 = block_ids::GPS_RAW_L2C;
2430
2431 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2432 const MIN_LEN: usize = 57;
2433 if data.len() < MIN_LEN {
2434 return Err(SbfError::ParseError("GPSRawL2C too short".into()));
2435 }
2436
2437 let mut nav_bits = [0u8; 40];
2438 nav_bits.copy_from_slice(&data[17..57]);
2439
2440 Ok(Self {
2441 tow_ms: header.tow_ms,
2442 wnc: header.wnc,
2443 svid: data[12],
2444 crc_passed: data[13],
2445 viterbi_count: data[14],
2446 source: data[15],
2447 freq_nr: data[16],
2448 nav_bits,
2449 })
2450 }
2451}
2452
2453#[derive(Debug, Clone)]
2461pub struct GpsRawL5Block {
2462 tow_ms: u32,
2463 wnc: u16,
2464 pub svid: u8,
2466 pub crc_passed: u8,
2468 pub viterbi_count: u8,
2470 pub source: u8,
2472 pub freq_nr: u8,
2474 pub nav_bits: [u8; 40],
2476}
2477
2478impl GpsRawL5Block {
2479 pub fn tow_seconds(&self) -> f64 {
2480 self.tow_ms as f64 * 0.001
2481 }
2482 pub fn tow_ms(&self) -> u32 {
2483 self.tow_ms
2484 }
2485 pub fn wnc(&self) -> u16 {
2486 self.wnc
2487 }
2488 pub fn crc_ok(&self) -> bool {
2489 self.crc_passed != 0
2490 }
2491 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2492 &self.nav_bits
2493 }
2494}
2495
2496impl SbfBlockParse for GpsRawL5Block {
2497 const BLOCK_ID: u16 = block_ids::GPS_RAW_L5;
2498
2499 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2500 const MIN_LEN: usize = 57;
2501 if data.len() < MIN_LEN {
2502 return Err(SbfError::ParseError("GPSRawL5 too short".into()));
2503 }
2504
2505 let mut nav_bits = [0u8; 40];
2506 nav_bits.copy_from_slice(&data[17..57]);
2507
2508 Ok(Self {
2509 tow_ms: header.tow_ms,
2510 wnc: header.wnc,
2511 svid: data[12],
2512 crc_passed: data[13],
2513 viterbi_count: data[14],
2514 source: data[15],
2515 freq_nr: data[16],
2516 nav_bits,
2517 })
2518 }
2519}
2520
2521#[derive(Debug, Clone)]
2529pub struct GloRawCaBlock {
2530 tow_ms: u32,
2531 wnc: u16,
2532 pub svid: u8,
2534 pub crc_passed: u8,
2536 pub viterbi_count: u8,
2538 pub source: u8,
2540 pub freq_nr: u8,
2542 pub nav_bits: [u8; 12],
2544}
2545
2546impl GloRawCaBlock {
2547 pub fn tow_seconds(&self) -> f64 {
2548 self.tow_ms as f64 * 0.001
2549 }
2550 pub fn tow_ms(&self) -> u32 {
2551 self.tow_ms
2552 }
2553 pub fn wnc(&self) -> u16 {
2554 self.wnc
2555 }
2556 pub fn crc_ok(&self) -> bool {
2557 self.crc_passed != 0
2558 }
2559 pub fn nav_bits_slice(&self) -> &[u8; 12] {
2560 &self.nav_bits
2561 }
2562}
2563
2564impl SbfBlockParse for GloRawCaBlock {
2565 const BLOCK_ID: u16 = block_ids::GLO_RAW_CA;
2566
2567 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2568 const MIN_LEN: usize = 29;
2570 if data.len() < MIN_LEN {
2571 return Err(SbfError::ParseError("GLORawCA too short".into()));
2572 }
2573
2574 let mut nav_bits = [0u8; 12];
2575 nav_bits.copy_from_slice(&data[17..29]);
2576
2577 Ok(Self {
2578 tow_ms: header.tow_ms,
2579 wnc: header.wnc,
2580 svid: data[12],
2581 crc_passed: data[13],
2582 viterbi_count: data[14],
2583 source: data[15],
2584 freq_nr: data[16],
2585 nav_bits,
2586 })
2587 }
2588}
2589
2590#[derive(Debug, Clone)]
2598pub struct GalRawFnavBlock {
2599 tow_ms: u32,
2600 wnc: u16,
2601 pub svid: u8,
2603 pub crc_passed: u8,
2605 pub viterbi_count: u8,
2607 pub source: u8,
2609 pub freq_nr: u8,
2611 pub nav_bits: [u8; 32],
2613}
2614
2615impl GalRawFnavBlock {
2616 pub fn tow_seconds(&self) -> f64 {
2617 self.tow_ms as f64 * 0.001
2618 }
2619 pub fn tow_ms(&self) -> u32 {
2620 self.tow_ms
2621 }
2622 pub fn wnc(&self) -> u16 {
2623 self.wnc
2624 }
2625 pub fn crc_ok(&self) -> bool {
2626 self.crc_passed != 0
2627 }
2628 pub fn nav_bits_slice(&self) -> &[u8; 32] {
2629 &self.nav_bits
2630 }
2631}
2632
2633impl SbfBlockParse for GalRawFnavBlock {
2634 const BLOCK_ID: u16 = block_ids::GAL_RAW_FNAV;
2635
2636 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2637 const MIN_LEN: usize = 49;
2638 if data.len() < MIN_LEN {
2639 return Err(SbfError::ParseError("GALRawFNAV too short".into()));
2640 }
2641
2642 let mut nav_bits = [0u8; 32];
2643 nav_bits.copy_from_slice(&data[17..49]);
2644
2645 Ok(Self {
2646 tow_ms: header.tow_ms,
2647 wnc: header.wnc,
2648 svid: data[12],
2649 crc_passed: data[13],
2650 viterbi_count: data[14],
2651 source: data[15],
2652 freq_nr: data[16],
2653 nav_bits,
2654 })
2655 }
2656}
2657
2658#[derive(Debug, Clone)]
2666pub struct GalRawInavBlock {
2667 tow_ms: u32,
2668 wnc: u16,
2669 pub svid: u8,
2671 pub crc_passed: u8,
2673 pub viterbi_count: u8,
2675 pub source: u8,
2677 pub freq_nr: u8,
2679 pub nav_bits: [u8; 32],
2681}
2682
2683impl GalRawInavBlock {
2684 pub fn tow_seconds(&self) -> f64 {
2685 self.tow_ms as f64 * 0.001
2686 }
2687 pub fn tow_ms(&self) -> u32 {
2688 self.tow_ms
2689 }
2690 pub fn wnc(&self) -> u16 {
2691 self.wnc
2692 }
2693 pub fn crc_ok(&self) -> bool {
2694 self.crc_passed != 0
2695 }
2696 pub fn nav_bits_slice(&self) -> &[u8; 32] {
2697 &self.nav_bits
2698 }
2699}
2700
2701impl SbfBlockParse for GalRawInavBlock {
2702 const BLOCK_ID: u16 = block_ids::GAL_RAW_INAV;
2703
2704 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2705 const MIN_LEN: usize = 49;
2706 if data.len() < MIN_LEN {
2707 return Err(SbfError::ParseError("GALRawINAV too short".into()));
2708 }
2709
2710 let mut nav_bits = [0u8; 32];
2711 nav_bits.copy_from_slice(&data[17..49]);
2712
2713 Ok(Self {
2714 tow_ms: header.tow_ms,
2715 wnc: header.wnc,
2716 svid: data[12],
2717 crc_passed: data[13],
2718 viterbi_count: data[14],
2719 source: data[15],
2720 freq_nr: data[16],
2721 nav_bits,
2722 })
2723 }
2724}
2725
2726#[derive(Debug, Clone)]
2734pub struct GalRawCnavBlock {
2735 tow_ms: u32,
2736 wnc: u16,
2737 pub svid: u8,
2739 pub crc_passed: u8,
2741 pub viterbi_count: u8,
2743 pub source: u8,
2745 pub freq_nr: u8,
2747 pub nav_bits: [u8; 64],
2749}
2750
2751impl GalRawCnavBlock {
2752 pub fn tow_seconds(&self) -> f64 {
2753 self.tow_ms as f64 * 0.001
2754 }
2755 pub fn tow_ms(&self) -> u32 {
2756 self.tow_ms
2757 }
2758 pub fn wnc(&self) -> u16 {
2759 self.wnc
2760 }
2761 pub fn crc_ok(&self) -> bool {
2762 self.crc_passed != 0
2763 }
2764 pub fn nav_bits_slice(&self) -> &[u8; 64] {
2765 &self.nav_bits
2766 }
2767}
2768
2769impl SbfBlockParse for GalRawCnavBlock {
2770 const BLOCK_ID: u16 = block_ids::GAL_RAW_CNAV;
2771
2772 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2773 const MIN_LEN: usize = 81;
2774 if data.len() < MIN_LEN {
2775 return Err(SbfError::ParseError("GALRawCNAV too short".into()));
2776 }
2777
2778 let mut nav_bits = [0u8; 64];
2779 nav_bits.copy_from_slice(&data[17..81]);
2780
2781 Ok(Self {
2782 tow_ms: header.tow_ms,
2783 wnc: header.wnc,
2784 svid: data[12],
2785 crc_passed: data[13],
2786 viterbi_count: data[14],
2787 source: data[15],
2788 freq_nr: data[16],
2789 nav_bits,
2790 })
2791 }
2792}
2793
2794#[derive(Debug, Clone)]
2802pub struct GeoRawL1Block {
2803 tow_ms: u32,
2804 wnc: u16,
2805 pub svid: u8,
2807 pub crc_passed: u8,
2809 pub viterbi_count: u8,
2811 pub source: u8,
2813 pub freq_nr: u8,
2815 pub nav_bits: [u8; 32],
2817}
2818
2819impl GeoRawL1Block {
2820 pub fn tow_seconds(&self) -> f64 {
2821 self.tow_ms as f64 * 0.001
2822 }
2823 pub fn tow_ms(&self) -> u32 {
2824 self.tow_ms
2825 }
2826 pub fn wnc(&self) -> u16 {
2827 self.wnc
2828 }
2829 pub fn crc_ok(&self) -> bool {
2830 self.crc_passed != 0
2831 }
2832 pub fn nav_bits_slice(&self) -> &[u8; 32] {
2833 &self.nav_bits
2834 }
2835}
2836
2837impl SbfBlockParse for GeoRawL1Block {
2838 const BLOCK_ID: u16 = block_ids::GEO_RAW_L1;
2839
2840 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2841 const MIN_LEN: usize = 49;
2842 if data.len() < MIN_LEN {
2843 return Err(SbfError::ParseError("GEORawL1 too short".into()));
2844 }
2845
2846 let mut nav_bits = [0u8; 32];
2847 nav_bits.copy_from_slice(&data[17..49]);
2848
2849 Ok(Self {
2850 tow_ms: header.tow_ms,
2851 wnc: header.wnc,
2852 svid: data[12],
2853 crc_passed: data[13],
2854 viterbi_count: data[14],
2855 source: data[15],
2856 freq_nr: data[16],
2857 nav_bits,
2858 })
2859 }
2860}
2861
2862#[derive(Debug, Clone)]
2870pub struct CmpRawBlock {
2871 tow_ms: u32,
2872 wnc: u16,
2873 pub svid: u8,
2875 pub crc_passed: u8,
2877 pub viterbi_count: u8,
2879 pub source: u8,
2881 pub freq_nr: u8,
2883 pub nav_bits: [u8; 40],
2885}
2886
2887impl CmpRawBlock {
2888 pub fn tow_seconds(&self) -> f64 {
2889 self.tow_ms as f64 * 0.001
2890 }
2891 pub fn tow_ms(&self) -> u32 {
2892 self.tow_ms
2893 }
2894 pub fn wnc(&self) -> u16 {
2895 self.wnc
2896 }
2897 pub fn crc_ok(&self) -> bool {
2898 self.crc_passed != 0
2899 }
2900 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2901 &self.nav_bits
2902 }
2903}
2904
2905impl SbfBlockParse for CmpRawBlock {
2906 const BLOCK_ID: u16 = block_ids::CMP_RAW;
2907
2908 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2909 const MIN_LEN: usize = 57;
2910 if data.len() < MIN_LEN {
2911 return Err(SbfError::ParseError("CMPRaw too short".into()));
2912 }
2913
2914 let mut nav_bits = [0u8; 40];
2915 nav_bits.copy_from_slice(&data[17..57]);
2916
2917 Ok(Self {
2918 tow_ms: header.tow_ms,
2919 wnc: header.wnc,
2920 svid: data[12],
2921 crc_passed: data[13],
2922 viterbi_count: data[14],
2923 source: data[15],
2924 freq_nr: data[16],
2925 nav_bits,
2926 })
2927 }
2928}
2929
2930#[derive(Debug, Clone)]
2938pub struct QzsRawL1CaBlock {
2939 tow_ms: u32,
2940 wnc: u16,
2941 pub svid: u8,
2943 pub crc_passed: u8,
2945 pub viterbi_count: u8,
2947 pub source: u8,
2949 pub freq_nr: u8,
2951 pub nav_bits: [u8; 40],
2953}
2954
2955impl QzsRawL1CaBlock {
2956 pub fn tow_seconds(&self) -> f64 {
2957 self.tow_ms as f64 * 0.001
2958 }
2959 pub fn tow_ms(&self) -> u32 {
2960 self.tow_ms
2961 }
2962 pub fn wnc(&self) -> u16 {
2963 self.wnc
2964 }
2965 pub fn crc_ok(&self) -> bool {
2966 self.crc_passed != 0
2967 }
2968 pub fn nav_bits_slice(&self) -> &[u8; 40] {
2969 &self.nav_bits
2970 }
2971}
2972
2973impl SbfBlockParse for QzsRawL1CaBlock {
2974 const BLOCK_ID: u16 = block_ids::QZS_RAW_L1CA;
2975
2976 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
2977 const MIN_LEN: usize = 57;
2978 if data.len() < MIN_LEN {
2979 return Err(SbfError::ParseError("QZSRawL1CA too short".into()));
2980 }
2981
2982 let mut nav_bits = [0u8; 40];
2983 nav_bits.copy_from_slice(&data[17..57]);
2984
2985 Ok(Self {
2986 tow_ms: header.tow_ms,
2987 wnc: header.wnc,
2988 svid: data[12],
2989 crc_passed: data[13],
2990 viterbi_count: data[14],
2991 source: data[15],
2992 freq_nr: data[16],
2993 nav_bits,
2994 })
2995 }
2996}
2997
2998#[derive(Debug, Clone)]
3006pub struct QzsRawL2CBlock {
3007 tow_ms: u32,
3008 wnc: u16,
3009 pub svid: u8,
3011 pub crc_passed: u8,
3013 pub viterbi_count: u8,
3015 pub source: u8,
3017 pub freq_nr: u8,
3019 pub nav_bits: [u8; 40],
3021}
3022
3023impl QzsRawL2CBlock {
3024 pub fn tow_seconds(&self) -> f64 {
3025 self.tow_ms as f64 * 0.001
3026 }
3027 pub fn tow_ms(&self) -> u32 {
3028 self.tow_ms
3029 }
3030 pub fn wnc(&self) -> u16 {
3031 self.wnc
3032 }
3033 pub fn crc_ok(&self) -> bool {
3034 self.crc_passed != 0
3035 }
3036 pub fn nav_bits_slice(&self) -> &[u8; 40] {
3037 &self.nav_bits
3038 }
3039}
3040
3041impl SbfBlockParse for QzsRawL2CBlock {
3042 const BLOCK_ID: u16 = block_ids::QZS_RAW_L2C;
3043
3044 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
3045 const MIN_LEN: usize = 57;
3046 if data.len() < MIN_LEN {
3047 return Err(SbfError::ParseError("QZSRawL2C too short".into()));
3048 }
3049
3050 let mut nav_bits = [0u8; 40];
3051 nav_bits.copy_from_slice(&data[17..57]);
3052
3053 Ok(Self {
3054 tow_ms: header.tow_ms,
3055 wnc: header.wnc,
3056 svid: data[12],
3057 crc_passed: data[13],
3058 viterbi_count: data[14],
3059 source: data[15],
3060 freq_nr: data[16],
3061 nav_bits,
3062 })
3063 }
3064}
3065
3066#[derive(Debug, Clone)]
3074pub struct QzsRawL5Block {
3075 tow_ms: u32,
3076 wnc: u16,
3077 pub svid: u8,
3079 pub crc_passed: u8,
3081 pub viterbi_count: u8,
3083 pub source: u8,
3085 pub freq_nr: u8,
3087 pub nav_bits: [u8; 40],
3089}
3090
3091impl QzsRawL5Block {
3092 pub fn tow_seconds(&self) -> f64 {
3093 self.tow_ms as f64 * 0.001
3094 }
3095 pub fn tow_ms(&self) -> u32 {
3096 self.tow_ms
3097 }
3098 pub fn wnc(&self) -> u16 {
3099 self.wnc
3100 }
3101 pub fn crc_ok(&self) -> bool {
3102 self.crc_passed != 0
3103 }
3104 pub fn nav_bits_slice(&self) -> &[u8; 40] {
3105 &self.nav_bits
3106 }
3107}
3108
3109impl SbfBlockParse for QzsRawL5Block {
3110 const BLOCK_ID: u16 = block_ids::QZS_RAW_L5;
3111
3112 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
3113 const MIN_LEN: usize = 57;
3114 if data.len() < MIN_LEN {
3115 return Err(SbfError::ParseError("QZSRawL5 too short".into()));
3116 }
3117
3118 let mut nav_bits = [0u8; 40];
3119 nav_bits.copy_from_slice(&data[17..57]);
3120
3121 Ok(Self {
3122 tow_ms: header.tow_ms,
3123 wnc: header.wnc,
3124 svid: data[12],
3125 crc_passed: data[13],
3126 viterbi_count: data[14],
3127 source: data[15],
3128 freq_nr: data[16],
3129 nav_bits,
3130 })
3131 }
3132}
3133
3134#[derive(Debug, Clone)]
3140pub struct GeoIonoDelayIdc {
3141 pub igp_mask_no: u8,
3143 pub givei: u8,
3145 vertical_delay_m_raw: f32,
3146}
3147
3148impl GeoIonoDelayIdc {
3149 pub fn vertical_delay_m(&self) -> Option<f32> {
3152 f32_or_none(self.vertical_delay_m_raw)
3153 }
3154
3155 pub fn vertical_delay_m_raw(&self) -> f32 {
3157 self.vertical_delay_m_raw
3158 }
3159}
3160
3161#[derive(Debug, Clone)]
3165pub struct GeoIonoDelayBlock {
3166 tow_ms: u32,
3167 wnc: u16,
3168 pub prn: u8,
3170 pub band_nbr: u8,
3172 pub iodi: u8,
3174 pub idc: Vec<GeoIonoDelayIdc>,
3176}
3177
3178impl GeoIonoDelayBlock {
3179 pub fn tow_seconds(&self) -> f64 {
3180 self.tow_ms as f64 * 0.001
3181 }
3182 pub fn tow_ms(&self) -> u32 {
3183 self.tow_ms
3184 }
3185 pub fn wnc(&self) -> u16 {
3186 self.wnc
3187 }
3188
3189 pub fn num_idc(&self) -> usize {
3190 self.idc.len()
3191 }
3192}
3193
3194impl SbfBlockParse for GeoIonoDelayBlock {
3195 const BLOCK_ID: u16 = block_ids::GEO_IONO_DELAY;
3196
3197 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
3198 if data.len() < 18 {
3200 return Err(SbfError::ParseError("GEOIonoDelay too short".into()));
3201 }
3202
3203 let prn = data[12];
3204 let band_nbr = data[13];
3205 let iodi = data[14];
3206 let n = data[15] as usize;
3207 let sb_length = data[16] as usize;
3208
3209 if sb_length < 8 {
3210 return Err(SbfError::ParseError(
3211 "GEOIonoDelay SBLength too small".into(),
3212 ));
3213 }
3214
3215 let mut idc = Vec::with_capacity(n);
3216 let mut offset = 18;
3217
3218 for _ in 0..n {
3219 if offset + sb_length > data.len() {
3220 break;
3221 }
3222
3223 let igp_mask_no = data[offset];
3224 let givei = data[offset + 1];
3225 let vertical_delay_m_raw =
3226 f32::from_le_bytes(data[offset + 4..offset + 8].try_into().unwrap());
3227
3228 idc.push(GeoIonoDelayIdc {
3229 igp_mask_no,
3230 givei,
3231 vertical_delay_m_raw,
3232 });
3233
3234 offset += sb_length;
3235 }
3236
3237 Ok(Self {
3238 tow_ms: header.tow_ms,
3239 wnc: header.wnc,
3240 prn,
3241 band_nbr,
3242 iodi,
3243 idc,
3244 })
3245 }
3246}
3247
3248#[cfg(test)]
3249mod tests {
3250 use super::*;
3251 use crate::blocks::SbfBlock;
3252 use crate::header::{SbfHeader, SBF_SYNC};
3253
3254 fn header_for(block_id: u16, data_len: usize, tow_ms: u32, wnc: u16) -> SbfHeader {
3255 SbfHeader {
3256 crc: 0,
3257 block_id,
3258 block_rev: 0,
3259 length: (data_len + 2) as u16,
3260 tow_ms,
3261 wnc,
3262 }
3263 }
3264
3265 #[test]
3266 fn test_gps_alm_accessors() {
3267 let block = GpsAlmBlock {
3268 tow_ms: 1000,
3269 wnc: 2000,
3270 prn: 5,
3271 e: F32_DNU,
3272 t_oa: 100,
3273 delta_i: 0.1,
3274 omega_dot: 0.2,
3275 sqrt_a: 5153.5,
3276 omega_0: 1.0,
3277 omega: 1.1,
3278 m_0: 0.5,
3279 a_f1: 0.0,
3280 a_f0: 0.0,
3281 wn_a: 10,
3282 as_config: 1,
3283 health8: 0,
3284 health6: 0,
3285 };
3286
3287 assert!((block.tow_seconds() - 1.0).abs() < 1e-6);
3288 assert!(block.eccentricity().is_none());
3289 assert!((block.semi_major_axis_m().unwrap() - 5153.5_f32.powi(2)).abs() < 1e-3);
3290 }
3291
3292 #[test]
3293 fn test_gps_alm_parse() {
3294 let mut data = vec![0u8; 57];
3295 data[12] = 7;
3296 data[13..17].copy_from_slice(&0.02_f32.to_le_bytes());
3297 data[17..21].copy_from_slice(&1234_u32.to_le_bytes());
3298 data[21..25].copy_from_slice(&0.1_f32.to_le_bytes());
3299 data[25..29].copy_from_slice(&0.2_f32.to_le_bytes());
3300 data[29..33].copy_from_slice(&5153.8_f32.to_le_bytes());
3301 data[33..37].copy_from_slice(&1.0_f32.to_le_bytes());
3302 data[37..41].copy_from_slice(&1.1_f32.to_le_bytes());
3303 data[41..45].copy_from_slice(&0.5_f32.to_le_bytes());
3304 data[45..49].copy_from_slice(&0.01_f32.to_le_bytes());
3305 data[49..53].copy_from_slice(&0.02_f32.to_le_bytes());
3306 data[53] = 12;
3307 data[54] = 1;
3308 data[55] = 0;
3309 data[56] = 0;
3310
3311 let header = header_for(block_ids::GPS_ALM, data.len(), 5000, 2001);
3312 let block = GpsAlmBlock::parse(&header, &data).unwrap();
3313
3314 assert_eq!(block.prn, 7);
3315 assert_eq!(block.wnc(), 2001);
3316 assert_eq!(block.t_oa, 1234);
3317 assert!((block.eccentricity().unwrap() - 0.02).abs() < 1e-6);
3318 }
3319
3320 #[test]
3321 fn test_gps_ion_accessors() {
3322 let block = GpsIonBlock {
3323 tow_ms: 2500,
3324 wnc: 2100,
3325 prn: 3,
3326 alpha_0: F32_DNU,
3327 alpha_1: 0.1,
3328 alpha_2: 0.2,
3329 alpha_3: 0.3,
3330 beta_0: 1.0,
3331 beta_1: 2.0,
3332 beta_2: 3.0,
3333 beta_3: 4.0,
3334 };
3335
3336 assert!((block.tow_seconds() - 2.5).abs() < 1e-6);
3337 assert!(block.alpha_0().is_none());
3338 assert!((block.beta_0().unwrap() - 1.0).abs() < 1e-6);
3339 }
3340
3341 #[test]
3342 fn test_gps_ion_parse() {
3343 let mut data = vec![0u8; 45];
3344 data[12] = 8;
3345 data[13..17].copy_from_slice(&0.1_f32.to_le_bytes());
3346 data[17..21].copy_from_slice(&0.2_f32.to_le_bytes());
3347 data[21..25].copy_from_slice(&0.3_f32.to_le_bytes());
3348 data[25..29].copy_from_slice(&0.4_f32.to_le_bytes());
3349 data[29..33].copy_from_slice(&1.1_f32.to_le_bytes());
3350 data[33..37].copy_from_slice(&1.2_f32.to_le_bytes());
3351 data[37..41].copy_from_slice(&1.3_f32.to_le_bytes());
3352 data[41..45].copy_from_slice(&1.4_f32.to_le_bytes());
3353
3354 let header = header_for(block_ids::GPS_ION, data.len(), 8000, 2002);
3355 let block = GpsIonBlock::parse(&header, &data).unwrap();
3356
3357 assert_eq!(block.prn, 8);
3358 assert!((block.alpha_1 - 0.2).abs() < 1e-6);
3359 assert!((block.beta_3 - 1.4).abs() < 1e-6);
3360 }
3361
3362 #[test]
3363 fn test_gps_utc_accessors() {
3364 let block = GpsUtcBlock {
3365 tow_ms: 3000,
3366 wnc: 2200,
3367 prn: 1,
3368 a_1: 0.001,
3369 a_0: F64_DNU,
3370 t_ot: 4000,
3371 wn_t: 12,
3372 delta_t_ls: 18,
3373 wn_lsf: 13,
3374 dn: 2,
3375 delta_t_lsf: 19,
3376 };
3377
3378 assert!((block.tow_seconds() - 3.0).abs() < 1e-6);
3379 assert!(block.utc_bias_s().is_none());
3380 assert!((block.utc_drift_s_per_s().unwrap() - 0.001).abs() < 1e-6);
3381 }
3382
3383 #[test]
3384 fn test_gps_utc_parse() {
3385 let mut data = vec![0u8; 34];
3386 data[12] = 2;
3387 data[13..17].copy_from_slice(&0.001_f32.to_le_bytes());
3388 data[17..25].copy_from_slice(&(-0.5_f64).to_le_bytes());
3389 data[25..29].copy_from_slice(&12345_u32.to_le_bytes());
3390 data[29] = 6;
3391 data[30] = 18u8;
3392 data[31] = 7;
3393 data[32] = 4;
3394 data[33] = 19u8;
3395
3396 let header = header_for(block_ids::GPS_UTC, data.len(), 9000, 2003);
3397 let block = GpsUtcBlock::parse(&header, &data).unwrap();
3398
3399 assert_eq!(block.prn, 2);
3400 assert_eq!(block.wn_t, 6);
3401 assert!((block.utc_bias_s().unwrap() + 0.5).abs() < 1e-9);
3402 }
3403
3404 #[test]
3405 fn test_glo_alm_accessors() {
3406 let block = GloAlmBlock {
3407 tow_ms: 500,
3408 wnc: 2300,
3409 svid: 40,
3410 freq_nr: -3,
3411 epsilon: F32_DNU,
3412 t_oa: 200,
3413 delta_i: 0.1,
3414 lambda: 0.2,
3415 t_ln: 100.0,
3416 omega: 0.3,
3417 delta_t: 0.4,
3418 d_delta_t: 0.5,
3419 tau: 0.0,
3420 wn_a: 5,
3421 c: 0,
3422 n: 10,
3423 m_type: 1,
3424 n_4: 2,
3425 };
3426
3427 assert!((block.tow_seconds() - 0.5).abs() < 1e-6);
3428 assert_eq!(block.slot(), 3);
3429 assert!(block.eccentricity().is_none());
3430 assert!(block.clock_bias_s().is_some());
3431 }
3432
3433 #[test]
3434 fn test_glo_alm_parse() {
3435 let mut data = vec![0u8; 56];
3436 data[12] = 38;
3437 data[13] = 250u8;
3438 data[14..18].copy_from_slice(&0.01_f32.to_le_bytes());
3439 data[18..22].copy_from_slice(&200_u32.to_le_bytes());
3440 data[22..26].copy_from_slice(&0.1_f32.to_le_bytes());
3441 data[26..30].copy_from_slice(&0.2_f32.to_le_bytes());
3442 data[30..34].copy_from_slice(&300.0_f32.to_le_bytes());
3443 data[34..38].copy_from_slice(&0.3_f32.to_le_bytes());
3444 data[38..42].copy_from_slice(&0.4_f32.to_le_bytes());
3445 data[42..46].copy_from_slice(&0.5_f32.to_le_bytes());
3446 data[46..50].copy_from_slice(&0.6_f32.to_le_bytes());
3447 data[50] = 3;
3448 data[51] = 0;
3449 data[52..54].copy_from_slice(&15_u16.to_le_bytes());
3450 data[54] = 1;
3451 data[55] = 2;
3452
3453 let header = header_for(block_ids::GLO_ALM, data.len(), 11000, 2301);
3454 let block = GloAlmBlock::parse(&header, &data).unwrap();
3455
3456 assert_eq!(block.svid, 38);
3457 assert_eq!(block.slot(), 1);
3458 assert_eq!(block.n, 15);
3459 assert!((block.clock_bias_s().unwrap() - 0.6).abs() < 1e-6);
3460 }
3461
3462 #[test]
3463 fn test_glo_time_accessors() {
3464 let block = GloTimeBlock {
3465 tow_ms: 750,
3466 wnc: 2400,
3467 svid: 41,
3468 freq_nr: 1,
3469 n_4: 3,
3470 kp: 2,
3471 n: 12,
3472 tau_gps: F32_DNU,
3473 tau_c: 0.2,
3474 b1: 0.01,
3475 b2: 0.02,
3476 };
3477
3478 assert!((block.tow_seconds() - 0.75).abs() < 1e-6);
3479 assert_eq!(block.slot(), 4);
3480 assert!(block.gps_glonass_offset_s().is_none());
3481 assert!((block.time_scale_correction_s().unwrap() - 0.2).abs() < 1e-9);
3482 }
3483
3484 #[test]
3485 fn test_glo_time_parse() {
3486 let mut data = vec![0u8; 38];
3487 data[12] = 39;
3488 data[13] = 1;
3489 data[14] = 5;
3490 data[15] = 1;
3491 data[16..18].copy_from_slice(&9_u16.to_le_bytes());
3492 data[18..22].copy_from_slice(&0.123_f32.to_le_bytes());
3493 data[22..30].copy_from_slice(&(-0.5_f64).to_le_bytes());
3494 data[30..34].copy_from_slice(&0.01_f32.to_le_bytes());
3495 data[34..38].copy_from_slice(&0.02_f32.to_le_bytes());
3496
3497 let header = header_for(block_ids::GLO_TIME, data.len(), 12000, 2401);
3498 let block = GloTimeBlock::parse(&header, &data).unwrap();
3499
3500 assert_eq!(block.svid, 39);
3501 assert_eq!(block.slot(), 2);
3502 assert_eq!(block.n, 9);
3503 assert!((block.time_scale_correction_s().unwrap() + 0.5).abs() < 1e-9);
3504 }
3505
3506 #[test]
3507 fn test_gal_alm_accessors() {
3508 let block = GalAlmBlock {
3509 tow_ms: 1500,
3510 wnc: 2500,
3511 svid: 71,
3512 source: 1,
3513 e: F32_DNU,
3514 t_oa: 100,
3515 delta_i: 0.1,
3516 omega_dot: 0.2,
3517 delta_sqrt_a: 0.3,
3518 omega_0: 1.0,
3519 omega: 1.1,
3520 m_0: 0.5,
3521 a_f1: 0.01,
3522 a_f0: 0.02,
3523 wn_a: 7,
3524 svid_a: 72,
3525 health: 0,
3526 ioda: 4,
3527 };
3528
3529 assert!((block.tow_seconds() - 1.5).abs() < 1e-6);
3530 assert_eq!(block.prn(), 1);
3531 assert!(block.eccentricity().is_none());
3532 assert!((block.delta_sqrt_a().unwrap() - 0.3).abs() < 1e-6);
3533 }
3534
3535 #[test]
3536 fn test_gal_alm_parse() {
3537 let mut data = vec![0u8; 59];
3538 data[12] = 72;
3539 data[13] = 2;
3540 data[14..18].copy_from_slice(&0.02_f32.to_le_bytes());
3541 data[18..22].copy_from_slice(&500_u32.to_le_bytes());
3542 data[22..26].copy_from_slice(&0.1_f32.to_le_bytes());
3543 data[26..30].copy_from_slice(&0.2_f32.to_le_bytes());
3544 data[30..34].copy_from_slice(&0.3_f32.to_le_bytes());
3545 data[34..38].copy_from_slice(&1.0_f32.to_le_bytes());
3546 data[38..42].copy_from_slice(&1.1_f32.to_le_bytes());
3547 data[42..46].copy_from_slice(&0.5_f32.to_le_bytes());
3548 data[46..50].copy_from_slice(&0.01_f32.to_le_bytes());
3549 data[50..54].copy_from_slice(&0.02_f32.to_le_bytes());
3550 data[54] = 9;
3551 data[55] = 73;
3552 data[56..58].copy_from_slice(&0x1234_u16.to_le_bytes());
3553 data[58] = 6;
3554
3555 let header = header_for(block_ids::GAL_ALM, data.len(), 13000, 2501);
3556 let block = GalAlmBlock::parse(&header, &data).unwrap();
3557
3558 assert_eq!(block.svid, 72);
3559 assert_eq!(block.prn(), 2);
3560 assert_eq!(block.wn_a, 9);
3561 assert_eq!(block.health, 0x1234);
3562 }
3563
3564 #[test]
3565 fn test_gal_ion_accessors() {
3566 let block = GalIonBlock {
3567 tow_ms: 1600,
3568 wnc: 2600,
3569 svid: 75,
3570 source: 16,
3571 a_i0: F32_DNU,
3572 a_i1: 0.1,
3573 a_i2: 0.2,
3574 storm_flags: 1,
3575 };
3576
3577 assert!((block.tow_seconds() - 1.6).abs() < 1e-6);
3578 assert!(block.is_fnav());
3579 assert!(!block.is_inav());
3580 assert!(block.a_i0().is_none());
3581 }
3582
3583 #[test]
3584 fn test_gal_ion_parse() {
3585 let mut data = vec![0u8; 27];
3586 data[12] = 80;
3587 data[13] = 1;
3588 data[14..18].copy_from_slice(&0.1_f32.to_le_bytes());
3589 data[18..22].copy_from_slice(&0.2_f32.to_le_bytes());
3590 data[22..26].copy_from_slice(&0.3_f32.to_le_bytes());
3591 data[26] = 2;
3592
3593 let header = header_for(block_ids::GAL_ION, data.len(), 14000, 2601);
3594 let block = GalIonBlock::parse(&header, &data).unwrap();
3595
3596 assert_eq!(block.svid, 80);
3597 assert!((block.a_i1 - 0.2).abs() < 1e-6);
3598 assert_eq!(block.storm_flags, 2);
3599 }
3600
3601 #[test]
3602 fn test_gal_utc_accessors() {
3603 let block = GalUtcBlock {
3604 tow_ms: 1700,
3605 wnc: 2700,
3606 svid: 76,
3607 source: 1,
3608 a_1: 0.001,
3609 a_0: F64_DNU,
3610 t_ot: 1000,
3611 wn_ot: 5,
3612 delta_t_ls: 18,
3613 wn_lsf: 6,
3614 dn: 3,
3615 delta_t_lsf: 19,
3616 };
3617
3618 assert!((block.tow_seconds() - 1.7).abs() < 1e-6);
3619 assert_eq!(block.prn(), 6);
3620 assert!(block.utc_bias_s().is_none());
3621 assert!((block.utc_drift_s_per_s().unwrap() - 0.001).abs() < 1e-6);
3622 }
3623
3624 #[test]
3625 fn test_gal_utc_parse() {
3626 let mut data = vec![0u8; 35];
3627 data[12] = 74;
3628 data[13] = 2;
3629 data[14..18].copy_from_slice(&0.002_f32.to_le_bytes());
3630 data[18..26].copy_from_slice(&1.25_f64.to_le_bytes());
3631 data[26..30].copy_from_slice(&800_u32.to_le_bytes());
3632 data[30] = 4;
3633 data[31] = 18u8;
3634 data[32] = 5;
3635 data[33] = 2;
3636 data[34] = 19u8;
3637
3638 let header = header_for(block_ids::GAL_UTC, data.len(), 15000, 2701);
3639 let block = GalUtcBlock::parse(&header, &data).unwrap();
3640
3641 assert_eq!(block.svid, 74);
3642 assert_eq!(block.wn_ot, 4);
3643 assert!((block.utc_bias_s().unwrap() - 1.25).abs() < 1e-9);
3644 }
3645
3646 #[test]
3647 fn test_gal_gst_gps_accessors() {
3648 let block = GalGstGpsBlock {
3649 tow_ms: 1800,
3650 wnc: 2800,
3651 svid: 71,
3652 source: 1,
3653 a_1g: F32_DNU,
3654 a_0g: 0.3,
3655 t_og: 7,
3656 wn_og: 8,
3657 };
3658
3659 assert!((block.tow_seconds() - 1.8).abs() < 1e-6);
3660 assert_eq!(block.prn(), 1);
3661 assert!(block.gst_gps_drift_s_per_s().is_none());
3662 assert!((block.gst_gps_offset_s().unwrap() - 0.3).abs() < 1e-6);
3663 }
3664
3665 #[test]
3666 fn test_gal_gst_gps_parse() {
3667 let mut data = vec![0u8; 27];
3668 data[12] = 72;
3669 data[13] = 0;
3670 data[14..18].copy_from_slice(&0.01_f32.to_le_bytes());
3671 data[18..22].copy_from_slice(&0.02_f32.to_le_bytes());
3672 data[22..26].copy_from_slice(&9_u32.to_le_bytes());
3673 data[26] = 10;
3674
3675 let header = header_for(block_ids::GAL_GST_GPS, data.len(), 16000, 2801);
3676 let block = GalGstGpsBlock::parse(&header, &data).unwrap();
3677
3678 assert_eq!(block.svid, 72);
3679 assert_eq!(block.t_og, 9);
3680 assert_eq!(block.wn_og, 10);
3681 }
3682
3683 #[test]
3684 fn test_gps_cnav_parse() {
3685 let mut data = vec![0u8; 170];
3686 data[12] = 12;
3687 data[13] = 0x80;
3688 data[14..16].copy_from_slice(&2045_u16.to_le_bytes());
3689 data[17] = (-2_i8) as u8;
3690 data[18..22].copy_from_slice(&1000_u32.to_le_bytes());
3691 data[22..26].copy_from_slice(&2000_u32.to_le_bytes());
3692 data[50..58].copy_from_slice(&0.5_f64.to_le_bytes());
3693 data[150..154].copy_from_slice(&(-1.25_f32).to_le_bytes());
3694 data[166..170].copy_from_slice(&0.0002_f32.to_le_bytes());
3695
3696 let header = header_for(block_ids::GPS_CNAV, data.len(), 17000, 2900);
3697 let block = GpsCNavBlock::parse(&header, &data).unwrap();
3698
3699 assert_eq!(block.prn, 12);
3700 assert_eq!(block.wn, 2045);
3701 assert_eq!(block.ura_ed, -2);
3702 assert_eq!(block.t_oe, 2000);
3703 assert!((block.m_0 - 0.5).abs() < 1e-12);
3704 assert!((block.t_gd + 1.25).abs() < 1e-6);
3705 assert!((block.isc_l5q5 - 0.0002).abs() < 1e-9);
3706 }
3707
3708 #[test]
3709 fn test_bds_ion_parse() {
3710 let mut data = vec![0u8; 46];
3711 data[12] = 7;
3712 data[14..18].copy_from_slice(&0.1_f32.to_le_bytes());
3713 data[30..34].copy_from_slice(&1.1_f32.to_le_bytes());
3714 data[42..46].copy_from_slice(&4.4_f32.to_le_bytes());
3715
3716 let header = header_for(block_ids::BDS_ION, data.len(), 18000, 2901);
3717 let block = BdsIonBlock::parse(&header, &data).unwrap();
3718
3719 assert_eq!(block.prn, 7);
3720 assert!((block.alpha_0 - 0.1).abs() < 1e-6);
3721 assert!((block.beta_0 - 1.1).abs() < 1e-6);
3722 assert!((block.beta_3 - 4.4).abs() < 1e-6);
3723 }
3724
3725 #[test]
3726 fn test_bds_cnav1_parse() {
3727 let mut data = vec![0u8; 158];
3728 data[12] = 3;
3729 data[13] = 0x02;
3730 data[14..18].copy_from_slice(&345600_u32.to_le_bytes());
3731 data[74..78].copy_from_slice(&1.25_f32.to_le_bytes());
3732 data[143] = 9;
3733 data[144..146].copy_from_slice(&512_u16.to_le_bytes());
3734 data[146..150].copy_from_slice(&0.001_f32.to_le_bytes());
3735 data[154..158].copy_from_slice(&(-0.002_f32).to_le_bytes());
3736
3737 let header = header_for(block_ids::BDS_CNAV1, data.len(), 19000, 2902);
3738 let block = BdsCNav1Block::parse(&header, &data).unwrap();
3739
3740 assert_eq!(block.prn_idx, 3);
3741 assert_eq!(block.flags & 0x03, 0x02);
3742 assert_eq!(block.t_oe, 345600);
3743 assert!((block.omega_dot - 1.25).abs() < 1e-6);
3744 assert_eq!(block.iode, 9);
3745 assert_eq!(block.iodc, 512);
3746 assert!((block.isc_b1cd - 0.001).abs() < 1e-6);
3747 assert!((block.t_gd_b2ap + 0.002).abs() < 1e-6);
3748 }
3749
3750 #[test]
3751 fn test_geo_iono_delay_accessors() {
3752 let block = GeoIonoDelayBlock {
3753 tow_ms: 2500,
3754 wnc: 2100,
3755 prn: 120,
3756 band_nbr: 3,
3757 iodi: 5,
3758 idc: vec![GeoIonoDelayIdc {
3759 igp_mask_no: 10,
3760 givei: 4,
3761 vertical_delay_m_raw: F32_DNU,
3762 }],
3763 };
3764
3765 assert!((block.tow_seconds() - 2.5).abs() < 1e-6);
3766 assert_eq!(block.num_idc(), 1);
3767 assert!(block.idc[0].vertical_delay_m().is_none());
3768 }
3769
3770 #[test]
3771 fn test_geo_iono_delay_parse() {
3772 let mut data = vec![0u8; 18 + (2 * 8)];
3773 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);
3791 let block = GeoIonoDelayBlock::parse(&header, &data).unwrap();
3792
3793 assert_eq!(block.prn, 120);
3794 assert_eq!(block.band_nbr, 3);
3795 assert_eq!(block.iodi, 5);
3796 assert_eq!(block.num_idc(), 2);
3797 assert_eq!(block.idc[0].igp_mask_no, 10);
3798 assert_eq!(block.idc[1].givei, 5);
3799 assert!((block.idc[0].vertical_delay_m().unwrap() - 12.5).abs() < 1e-6);
3800 assert!(block.idc[1].vertical_delay_m().is_none());
3801 }
3802
3803 #[test]
3804 fn test_geo_iono_delay_sbf_block_parse() {
3805 let total_len = 36usize; let mut data = vec![0u8; total_len];
3807 data[0..2].copy_from_slice(&SBF_SYNC);
3808 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());
3826
3827 data[28] = 11; data[29] = 6; data[32..36].copy_from_slice(&9.75_f32.to_le_bytes());
3831
3832 let (block, used) = SbfBlock::parse(&data).unwrap();
3833 assert_eq!(used, total_len);
3834 assert_eq!(block.block_id(), block_ids::GEO_IONO_DELAY);
3835 match block {
3836 SbfBlock::GeoIonoDelay(geo) => {
3837 assert_eq!(geo.tow_ms(), 9876);
3838 assert_eq!(geo.wnc(), 2025);
3839 assert_eq!(geo.num_idc(), 2);
3840 assert!((geo.idc[0].vertical_delay_m().unwrap() - 8.25).abs() < 1e-6);
3841 assert!((geo.idc[1].vertical_delay_m().unwrap() - 9.75).abs() < 1e-6);
3842 }
3843 _ => panic!("Expected GeoIonoDelay block"),
3844 }
3845 }
3846
3847 #[test]
3848 fn test_gps_raw_ca_parse() {
3849 let header = header_for(4017, 57, 5000, 2150);
3850 let mut data = vec![0u8; 57];
3851 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();
3859 assert_eq!(block.tow_seconds(), 5.0);
3860 assert_eq!(block.wnc(), 2150);
3861 assert_eq!(block.svid, 12);
3862 assert!(block.crc_ok());
3863 assert_eq!(block.viterbi_count, 0);
3864 assert_eq!(block.nav_bits_slice()[0], 0xAB);
3865 }
3866
3867 #[test]
3868 fn test_gps_raw_ca_too_short() {
3869 let header = header_for(4017, 57, 0, 0);
3870 let data = [0u8; 50];
3871 assert!(GpsRawCaBlock::parse(&header, &data).is_err());
3872 }
3873
3874 #[test]
3875 fn test_gps_raw_l2c_parse() {
3876 let header = header_for(4018, 57, 6000, 2200);
3877 let mut data = vec![0u8; 57];
3878 data[12] = 8;
3879 data[13] = 1;
3880 data[17..57].copy_from_slice(&[0xCDu8; 40]);
3881
3882 let block = GpsRawL2CBlock::parse(&header, &data).unwrap();
3883 assert_eq!(block.tow_seconds(), 6.0);
3884 assert_eq!(block.svid, 8);
3885 assert!(block.crc_ok());
3886 assert_eq!(block.nav_bits_slice()[0], 0xCD);
3887 }
3888
3889 #[test]
3890 fn test_gps_raw_l5_parse() {
3891 let header = header_for(4019, 57, 7000, 2250);
3892 let mut data = vec![0u8; 57];
3893 data[12] = 15;
3894 data[13] = 0;
3895
3896 let block = GpsRawL5Block::parse(&header, &data).unwrap();
3897 assert_eq!(block.tow_seconds(), 7.0);
3898 assert_eq!(block.svid, 15);
3899 assert!(!block.crc_ok());
3900 }
3901
3902 #[test]
3903 fn test_glo_raw_ca_parse() {
3904 let header = header_for(4026, 29, 8000, 2300);
3905 let mut data = vec![0u8; 29];
3906 data[12] = 45;
3907 data[13] = 1;
3908 data[16] = 3;
3909 data[17..29].copy_from_slice(&[0x12u8; 12]);
3910
3911 let block = GloRawCaBlock::parse(&header, &data).unwrap();
3912 assert_eq!(block.tow_seconds(), 8.0);
3913 assert_eq!(block.svid, 45);
3914 assert_eq!(block.freq_nr, 3);
3915 assert_eq!(block.nav_bits_slice()[0], 0x12);
3916 }
3917
3918 #[test]
3919 fn test_glo_raw_ca_too_short() {
3920 let header = header_for(4026, 29, 0, 0);
3921 let data = [0u8; 25];
3922 assert!(GloRawCaBlock::parse(&header, &data).is_err());
3923 }
3924
3925 #[test]
3926 fn test_gal_raw_fnav_parse() {
3927 let header = header_for(4022, 49, 1000, 2100);
3928 let mut data = vec![0u8; 49];
3929 data[12] = 85; data[13] = 1;
3931 data[17..49].copy_from_slice(&[0x11u8; 32]);
3932 let block = GalRawFnavBlock::parse(&header, &data).unwrap();
3933 assert_eq!(block.tow_seconds(), 1.0);
3934 assert_eq!(block.svid, 85);
3935 assert!(block.crc_ok());
3936 assert_eq!(block.nav_bits_slice()[0], 0x11);
3937 }
3938
3939 #[test]
3940 fn test_gal_raw_inav_parse() {
3941 let header = header_for(4023, 49, 2000, 2101);
3942 let mut data = vec![0u8; 49];
3943 data[12] = 72;
3944 data[13] = 0;
3945 let block = GalRawInavBlock::parse(&header, &data).unwrap();
3946 assert_eq!(block.tow_seconds(), 2.0);
3947 assert!(!block.crc_ok());
3948 }
3949
3950 #[test]
3951 fn test_gal_raw_cnav_parse() {
3952 let header = header_for(4024, 81, 3000, 2102);
3953 let mut data = vec![0u8; 81];
3954 data[12] = 90;
3955 data[17..81].copy_from_slice(&[0x22u8; 64]);
3956 let block = GalRawCnavBlock::parse(&header, &data).unwrap();
3957 assert_eq!(block.tow_seconds(), 3.0);
3958 assert_eq!(block.nav_bits_slice()[63], 0x22);
3959 }
3960
3961 #[test]
3962 fn test_geo_raw_l1_parse() {
3963 let header = header_for(4020, 49, 4000, 2103);
3964 let mut data = vec![0u8; 49];
3965 data[12] = 135; data[13] = 1;
3967 data[17..49].copy_from_slice(&[0x33u8; 32]);
3968 let block = GeoRawL1Block::parse(&header, &data).unwrap();
3969 assert_eq!(block.tow_seconds(), 4.0);
3970 assert_eq!(block.svid, 135);
3971 }
3972
3973 #[test]
3974 fn test_cmp_raw_parse() {
3975 let header = header_for(4047, 57, 5000, 2104);
3976 let mut data = vec![0u8; 57];
3977 data[12] = 155; data[17..57].copy_from_slice(&[0x44u8; 40]);
3979 let block = CmpRawBlock::parse(&header, &data).unwrap();
3980 assert_eq!(block.tow_seconds(), 5.0);
3981 assert_eq!(block.nav_bits_slice()[39], 0x44);
3982 }
3983
3984 #[test]
3985 fn test_qzs_raw_l1ca_parse() {
3986 let header = header_for(4066, 57, 6000, 2105);
3987 let mut data = vec![0u8; 57];
3988 data[12] = 183; data[13] = 1;
3990 let block = QzsRawL1CaBlock::parse(&header, &data).unwrap();
3991 assert_eq!(block.tow_seconds(), 6.0);
3992 assert_eq!(block.svid, 183);
3993 }
3994
3995 #[test]
3996 fn test_qzs_raw_l2c_parse() {
3997 let header = header_for(4067, 57, 7000, 2106);
3998 let mut data = vec![0u8; 57];
3999 data[12] = 186;
4000 let block = QzsRawL2CBlock::parse(&header, &data).unwrap();
4001 assert_eq!(block.tow_seconds(), 7.0);
4002 }
4003
4004 #[test]
4005 fn test_qzs_raw_l5_parse() {
4006 let header = header_for(4068, 57, 8000, 2107);
4007 let mut data = vec![0u8; 57];
4008 data[12] = 181;
4009 let block = QzsRawL5Block::parse(&header, &data).unwrap();
4010 assert_eq!(block.tow_seconds(), 8.0);
4011 }
4012
4013 #[test]
4014 fn test_gal_raw_fnav_too_short() {
4015 let header = header_for(4022, 49, 0, 0);
4016 assert!(GalRawFnavBlock::parse(&header, &[0u8; 40]).is_err());
4017 }
4018
4019 #[test]
4020 fn test_gal_raw_cnav_too_short() {
4021 let header = header_for(4024, 81, 0, 0);
4022 assert!(GalRawCnavBlock::parse(&header, &[0u8; 70]).is_err());
4023 }
4024
4025 #[test]
4026 fn test_gal_sar_rlm_accessors() {
4027 let block = GalSarRlmBlock {
4028 tow_ms: 4500,
4029 wnc: 2200,
4030 svid: 74,
4031 source: 2,
4032 rlm_length_bits: 80,
4033 rlm_bits_words: vec![0x8000_0000, 0, 0x0001_0000],
4034 };
4035
4036 assert!((block.tow_seconds() - 4.5).abs() < 1e-6);
4037 assert_eq!(block.prn(), 4);
4038 assert_eq!(block.rlm_length_bits(), 80);
4039 assert_eq!(block.rlm_bits_words().len(), 3);
4040 assert_eq!(block.bit(0), Some(true));
4041 assert_eq!(block.bit(1), Some(false));
4042 assert_eq!(block.bit(79), Some(true));
4043 assert_eq!(block.bit(80), None);
4044 }
4045
4046 #[test]
4047 fn test_gal_sar_rlm_parse() {
4048 let mut data = vec![0u8; 30];
4049 data[12] = 72; data[13] = 16; data[14] = 80; data[18..22].copy_from_slice(&0x1234_5678_u32.to_le_bytes());
4053 data[22..26].copy_from_slice(&0x9abc_def0_u32.to_le_bytes());
4054 data[26..30].copy_from_slice(&0x0001_0000_u32.to_le_bytes());
4055
4056 let header = header_for(block_ids::GAL_SAR_RLM, data.len(), 3210, 2048);
4057 let block = GalSarRlmBlock::parse(&header, &data).unwrap();
4058
4059 assert_eq!(block.svid, 72);
4060 assert_eq!(block.source, 16);
4061 assert_eq!(block.rlm_length_bits(), 80);
4062 assert_eq!(
4063 block.rlm_bits_words(),
4064 &[0x1234_5678, 0x9abc_def0, 0x0001_0000]
4065 );
4066 assert_eq!(block.bit(79), Some(true));
4067 }
4068
4069 #[test]
4070 fn test_gal_sar_rlm_too_short() {
4071 let header = header_for(block_ids::GAL_SAR_RLM, 30, 0, 0);
4072 assert!(GalSarRlmBlock::parse(&header, &[0u8; 20]).is_err());
4073 }
4074}