1use crate::error::{SbfError, SbfResult};
6use crate::header::SbfHeader;
7
8use super::block_ids;
9use super::dnu::{F32_DNU, F64_DNU};
10use super::SbfBlockParse;
11
12#[derive(Debug, Clone)]
20pub struct GeoMt00Block {
21 tow_ms: u32,
22 wnc: u16,
23 pub prn: u8,
25}
26
27impl GeoMt00Block {
28 pub fn tow_seconds(&self) -> f64 {
29 self.tow_ms as f64 * 0.001
30 }
31 pub fn tow_ms(&self) -> u32 {
32 self.tow_ms
33 }
34 pub fn wnc(&self) -> u16 {
35 self.wnc
36 }
37}
38
39impl SbfBlockParse for GeoMt00Block {
40 const BLOCK_ID: u16 = block_ids::GEO_MT00;
41
42 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
43 if data.len() < 13 {
44 return Err(SbfError::ParseError("GEOMT00 too short".into()));
45 }
46
47 Ok(Self {
48 tow_ms: header.tow_ms,
49 wnc: header.wnc,
50 prn: data[12],
51 })
52 }
53}
54
55#[derive(Debug, Clone)]
63pub struct GeoPrnMaskBlock {
64 tow_ms: u32,
65 wnc: u16,
66 pub prn: u8,
68 pub iodp: u8,
70 pub nbr_prns: u8,
72 pub prn_mask: Vec<u8>,
74}
75
76impl GeoPrnMaskBlock {
77 pub fn tow_seconds(&self) -> f64 {
78 self.tow_ms as f64 * 0.001
79 }
80 pub fn tow_ms(&self) -> u32 {
81 self.tow_ms
82 }
83 pub fn wnc(&self) -> u16 {
84 self.wnc
85 }
86}
87
88impl SbfBlockParse for GeoPrnMaskBlock {
89 const BLOCK_ID: u16 = block_ids::GEO_PRN_MASK;
90
91 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
92 if data.len() < 16 {
93 return Err(SbfError::ParseError("GEOPRNMask too short".into()));
94 }
95
96 let prn = data[12];
97 let iodp = data[13];
98 let nbr_prns = data[14] as usize;
99
100 let prn_mask_len = nbr_prns.min(data.len().saturating_sub(15));
101 let prn_mask: Vec<u8> = data[15..15 + prn_mask_len].to_vec();
102
103 Ok(Self {
104 tow_ms: header.tow_ms,
105 wnc: header.wnc,
106 prn,
107 iodp,
108 nbr_prns: nbr_prns as u8,
109 prn_mask,
110 })
111 }
112}
113
114#[derive(Debug, Clone)]
120pub struct GeoFastCorrEntry {
121 pub prn_mask_no: u8,
123 pub udrei: u8,
125 prc_m: f32,
126}
127
128impl GeoFastCorrEntry {
129 pub fn prc_m(&self) -> Option<f32> {
132 if self.prc_m == F32_DNU {
133 None
134 } else {
135 Some(self.prc_m)
136 }
137 }
138 pub fn prc_m_raw(&self) -> f32 {
139 self.prc_m
140 }
141}
142
143#[derive(Debug, Clone)]
147pub struct GeoFastCorrBlock {
148 tow_ms: u32,
149 wnc: u16,
150 pub prn: u8,
152 pub mt: u8,
154 pub iodp: u8,
156 pub iodf: u8,
158 pub n: u8,
160 pub sb_length: u8,
162 pub corrections: Vec<GeoFastCorrEntry>,
164}
165
166impl GeoFastCorrBlock {
167 pub fn tow_seconds(&self) -> f64 {
168 self.tow_ms as f64 * 0.001
169 }
170 pub fn tow_ms(&self) -> u32 {
171 self.tow_ms
172 }
173 pub fn wnc(&self) -> u16 {
174 self.wnc
175 }
176 pub fn num_corrections(&self) -> usize {
177 self.corrections.len()
178 }
179}
180
181impl SbfBlockParse for GeoFastCorrBlock {
182 const BLOCK_ID: u16 = block_ids::GEO_FAST_CORR;
183
184 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
185 if data.len() < 20 {
186 return Err(SbfError::ParseError("GEOFastCorr too short".into()));
187 }
188
189 let prn = data[12];
190 let mt = data[13];
191 let iodp = data[14];
192 let iodf = data[15];
193 let n = data[16] as usize;
194 let sb_length = data[17] as usize;
195
196 if sb_length < 6 {
197 return Err(SbfError::ParseError(
198 "GEOFastCorr SBLength too small".into(),
199 ));
200 }
201
202 let mut corrections = Vec::with_capacity(n);
203 let mut offset = 18;
204
205 for _ in 0..n {
206 if offset + sb_length > data.len() {
207 break;
208 }
209
210 let prn_mask_no = data[offset];
211 let udrei = data[offset + 1];
212 let prc_m = f32::from_le_bytes(data[offset + 2..offset + 6].try_into().unwrap());
213
214 corrections.push(GeoFastCorrEntry {
215 prn_mask_no,
216 udrei,
217 prc_m,
218 });
219
220 offset += sb_length;
221 }
222
223 Ok(Self {
224 tow_ms: header.tow_ms,
225 wnc: header.wnc,
226 prn,
227 mt,
228 iodp,
229 iodf,
230 n: n as u8,
231 sb_length: sb_length as u8,
232 corrections,
233 })
234 }
235}
236
237#[derive(Debug, Clone)]
245pub struct GeoNavBlock {
246 tow_ms: u32,
247 wnc: u16,
248 pub prn: u8,
250 pub iodn_spare: u16,
252 pub ura: u16,
254 pub t0: u32,
256 xg_m: f64,
257 yg_m: f64,
258 zg_m: f64,
259 xgd_mps: f64,
260 ygd_mps: f64,
261 zgd_mps: f64,
262 xgdd_mps2: f64,
263 ygdd_mps2: f64,
264 zgdd_mps2: f64,
265 ag_f0_s: f32,
266 ag_f1_sps: f32,
267}
268
269impl GeoNavBlock {
270 pub fn tow_seconds(&self) -> f64 {
271 self.tow_ms as f64 * 0.001
272 }
273 pub fn tow_ms(&self) -> u32 {
274 self.tow_ms
275 }
276 pub fn wnc(&self) -> u16 {
277 self.wnc
278 }
279 pub fn position_x_m(&self) -> Option<f64> {
280 if self.xg_m == F64_DNU {
281 None
282 } else {
283 Some(self.xg_m)
284 }
285 }
286 pub fn position_y_m(&self) -> Option<f64> {
287 if self.yg_m == F64_DNU {
288 None
289 } else {
290 Some(self.yg_m)
291 }
292 }
293 pub fn position_z_m(&self) -> Option<f64> {
294 if self.zg_m == F64_DNU {
295 None
296 } else {
297 Some(self.zg_m)
298 }
299 }
300 pub fn velocity_x_mps(&self) -> Option<f64> {
301 if self.xgd_mps == F64_DNU {
302 None
303 } else {
304 Some(self.xgd_mps)
305 }
306 }
307 pub fn velocity_y_mps(&self) -> Option<f64> {
308 if self.ygd_mps == F64_DNU {
309 None
310 } else {
311 Some(self.ygd_mps)
312 }
313 }
314 pub fn velocity_z_mps(&self) -> Option<f64> {
315 if self.zgd_mps == F64_DNU {
316 None
317 } else {
318 Some(self.zgd_mps)
319 }
320 }
321 pub fn acceleration_x_mps2(&self) -> Option<f64> {
322 if self.xgdd_mps2 == F64_DNU {
323 None
324 } else {
325 Some(self.xgdd_mps2)
326 }
327 }
328 pub fn acceleration_y_mps2(&self) -> Option<f64> {
329 if self.ygdd_mps2 == F64_DNU {
330 None
331 } else {
332 Some(self.ygdd_mps2)
333 }
334 }
335 pub fn acceleration_z_mps2(&self) -> Option<f64> {
336 if self.zgdd_mps2 == F64_DNU {
337 None
338 } else {
339 Some(self.zgdd_mps2)
340 }
341 }
342 pub fn clock_bias_s(&self) -> Option<f32> {
343 if self.ag_f0_s == F32_DNU {
344 None
345 } else {
346 Some(self.ag_f0_s)
347 }
348 }
349 pub fn clock_drift_sps(&self) -> Option<f32> {
350 if self.ag_f1_sps == F32_DNU {
351 None
352 } else {
353 Some(self.ag_f1_sps)
354 }
355 }
356}
357
358impl SbfBlockParse for GeoNavBlock {
359 const BLOCK_ID: u16 = block_ids::GEO_NAV;
360
361 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
362 const MIN_LEN: usize = 101;
363 if data.len() < MIN_LEN {
364 return Err(SbfError::ParseError("GEONav too short".into()));
365 }
366
367 let prn = data[12];
368 let iodn_spare = u16::from_le_bytes([data[13], data[14]]);
369 let ura = u16::from_le_bytes([data[15], data[16]]);
370 let t0 = u32::from_le_bytes([data[17], data[18], data[19], data[20]]);
371 let xg_m = f64::from_le_bytes(data[21..29].try_into().unwrap());
372 let yg_m = f64::from_le_bytes(data[29..37].try_into().unwrap());
373 let zg_m = f64::from_le_bytes(data[37..45].try_into().unwrap());
374 let xgd_mps = f64::from_le_bytes(data[45..53].try_into().unwrap());
375 let ygd_mps = f64::from_le_bytes(data[53..61].try_into().unwrap());
376 let zgd_mps = f64::from_le_bytes(data[61..69].try_into().unwrap());
377 let xgdd_mps2 = f64::from_le_bytes(data[69..77].try_into().unwrap());
378 let ygdd_mps2 = f64::from_le_bytes(data[77..85].try_into().unwrap());
379 let zgdd_mps2 = f64::from_le_bytes(data[85..93].try_into().unwrap());
380 let ag_f0_s = f32::from_le_bytes(data[93..97].try_into().unwrap());
381 let ag_f1_sps = f32::from_le_bytes(data[97..101].try_into().unwrap());
382
383 Ok(Self {
384 tow_ms: header.tow_ms,
385 wnc: header.wnc,
386 prn,
387 iodn_spare,
388 ura,
389 t0,
390 xg_m,
391 yg_m,
392 zg_m,
393 xgd_mps,
394 ygd_mps,
395 zgd_mps,
396 xgdd_mps2,
397 ygdd_mps2,
398 zgdd_mps2,
399 ag_f0_s,
400 ag_f1_sps,
401 })
402 }
403}
404
405#[derive(Debug, Clone)]
413pub struct GeoIntegrityBlock {
414 tow_ms: u32,
415 wnc: u16,
416 pub prn: u8,
418 pub iodf: [u8; 4],
420 pub udrei: [u8; 51],
422}
423
424impl GeoIntegrityBlock {
425 pub fn tow_seconds(&self) -> f64 {
426 self.tow_ms as f64 * 0.001
427 }
428 pub fn tow_ms(&self) -> u32 {
429 self.tow_ms
430 }
431 pub fn wnc(&self) -> u16 {
432 self.wnc
433 }
434}
435
436impl SbfBlockParse for GeoIntegrityBlock {
437 const BLOCK_ID: u16 = block_ids::GEO_INTEGRITY;
438
439 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
440 const MIN_LEN: usize = 68;
441 if data.len() < MIN_LEN {
442 return Err(SbfError::ParseError("GEOIntegrity too short".into()));
443 }
444
445 let prn = data[12];
446 let mut iodf = [0u8; 4];
447 iodf.copy_from_slice(&data[13..17]);
448 let mut udrei = [0u8; 51];
449 udrei.copy_from_slice(&data[17..68]);
450
451 Ok(Self {
452 tow_ms: header.tow_ms,
453 wnc: header.wnc,
454 prn,
455 iodf,
456 udrei,
457 })
458 }
459}
460
461#[derive(Debug, Clone)]
469pub struct GeoAlmBlock {
470 tow_ms: u32,
471 wnc: u16,
472 pub prn: u8,
474 pub data_id: u8,
476 pub health: u16,
478 pub t0: u32,
480 xg_m: f64,
481 yg_m: f64,
482 zg_m: f64,
483 xgd_mps: f64,
484 ygd_mps: f64,
485 zgd_mps: f64,
486}
487
488impl GeoAlmBlock {
489 pub fn tow_seconds(&self) -> f64 {
490 self.tow_ms as f64 * 0.001
491 }
492 pub fn tow_ms(&self) -> u32 {
493 self.tow_ms
494 }
495 pub fn wnc(&self) -> u16 {
496 self.wnc
497 }
498 pub fn position_x_m(&self) -> Option<f64> {
499 if self.xg_m == F64_DNU {
500 None
501 } else {
502 Some(self.xg_m)
503 }
504 }
505 pub fn position_y_m(&self) -> Option<f64> {
506 if self.yg_m == F64_DNU {
507 None
508 } else {
509 Some(self.yg_m)
510 }
511 }
512 pub fn position_z_m(&self) -> Option<f64> {
513 if self.zg_m == F64_DNU {
514 None
515 } else {
516 Some(self.zg_m)
517 }
518 }
519 pub fn velocity_x_mps(&self) -> Option<f64> {
520 if self.xgd_mps == F64_DNU {
521 None
522 } else {
523 Some(self.xgd_mps)
524 }
525 }
526 pub fn velocity_y_mps(&self) -> Option<f64> {
527 if self.ygd_mps == F64_DNU {
528 None
529 } else {
530 Some(self.ygd_mps)
531 }
532 }
533 pub fn velocity_z_mps(&self) -> Option<f64> {
534 if self.zgd_mps == F64_DNU {
535 None
536 } else {
537 Some(self.zgd_mps)
538 }
539 }
540}
541
542impl SbfBlockParse for GeoAlmBlock {
543 const BLOCK_ID: u16 = block_ids::GEO_ALM;
544
545 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
546 const MIN_LEN: usize = 68;
547 if data.len() < MIN_LEN {
548 return Err(SbfError::ParseError("GEOAlm too short".into()));
549 }
550
551 let prn = data[12];
552 let data_id = data[13];
553 let health = u16::from_le_bytes([data[14], data[15]]);
554 let t0 = u32::from_le_bytes([data[16], data[17], data[18], data[19]]);
555 let xg_m = f64::from_le_bytes(data[20..28].try_into().unwrap());
556 let yg_m = f64::from_le_bytes(data[28..36].try_into().unwrap());
557 let zg_m = f64::from_le_bytes(data[36..44].try_into().unwrap());
558 let xgd_mps = f64::from_le_bytes(data[44..52].try_into().unwrap());
559 let ygd_mps = f64::from_le_bytes(data[52..60].try_into().unwrap());
560 let zgd_mps = f64::from_le_bytes(data[60..68].try_into().unwrap());
561
562 Ok(Self {
563 tow_ms: header.tow_ms,
564 wnc: header.wnc,
565 prn,
566 data_id,
567 health,
568 t0,
569 xg_m,
570 yg_m,
571 zg_m,
572 xgd_mps,
573 ygd_mps,
574 zgd_mps,
575 })
576 }
577}
578
579#[derive(Debug, Clone)]
587pub struct GeoFastCorrDegrBlock {
588 tow_ms: u32,
589 wnc: u16,
590 pub prn: u8,
592 pub iodp: u8,
594 pub t_lat: u8,
596 pub ai: [u8; 51],
598}
599
600impl GeoFastCorrDegrBlock {
601 pub fn tow_seconds(&self) -> f64 {
602 self.tow_ms as f64 * 0.001
603 }
604 pub fn tow_ms(&self) -> u32 {
605 self.tow_ms
606 }
607 pub fn wnc(&self) -> u16 {
608 self.wnc
609 }
610}
611
612impl SbfBlockParse for GeoFastCorrDegrBlock {
613 const BLOCK_ID: u16 = block_ids::GEO_FAST_CORR_DEGR;
614
615 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
616 const MIN_LEN: usize = 66;
617 if data.len() < MIN_LEN {
618 return Err(SbfError::ParseError("GEOFastCorrDegr too short".into()));
619 }
620
621 let prn = data[12];
622 let iodp = data[13];
623 let t_lat = data[14];
624 let mut ai = [0u8; 51];
625 ai.copy_from_slice(&data[15..66]);
626
627 Ok(Self {
628 tow_ms: header.tow_ms,
629 wnc: header.wnc,
630 prn,
631 iodp,
632 t_lat,
633 ai,
634 })
635 }
636}
637
638#[derive(Debug, Clone)]
646pub struct GeoDegrFactorsBlock {
647 tow_ms: u32,
648 wnc: u16,
649 pub prn: u8,
651 brrc: f64,
652 cltc_lsb: f64,
653 cltc_v1: f64,
654 pub iltc_v1: u32,
655 cltc_v0: f64,
656 pub iltc_v0: u32,
657 cgeo_lsb: f64,
658 cgeo_v: f64,
659 pub igeo: u32,
660 cer: f32,
661 ciono_step: f64,
662 pub iiono: u32,
663 ciono_ramp: f64,
664 pub rss_udre: u8,
665 pub rss_iono: u8,
666 ccovariance: f64,
667}
668
669impl GeoDegrFactorsBlock {
670 pub fn tow_seconds(&self) -> f64 {
671 self.tow_ms as f64 * 0.001
672 }
673 pub fn tow_ms(&self) -> u32 {
674 self.tow_ms
675 }
676 pub fn wnc(&self) -> u16 {
677 self.wnc
678 }
679 pub fn brrc(&self) -> Option<f64> {
680 if self.brrc == F64_DNU {
681 None
682 } else {
683 Some(self.brrc)
684 }
685 }
686 pub fn cltc_lsb(&self) -> Option<f64> {
687 if self.cltc_lsb == F64_DNU {
688 None
689 } else {
690 Some(self.cltc_lsb)
691 }
692 }
693 pub fn cltc_v1(&self) -> Option<f64> {
694 if self.cltc_v1 == F64_DNU {
695 None
696 } else {
697 Some(self.cltc_v1)
698 }
699 }
700 pub fn cltc_v0(&self) -> Option<f64> {
701 if self.cltc_v0 == F64_DNU {
702 None
703 } else {
704 Some(self.cltc_v0)
705 }
706 }
707 pub fn cgeo_lsb(&self) -> Option<f64> {
708 if self.cgeo_lsb == F64_DNU {
709 None
710 } else {
711 Some(self.cgeo_lsb)
712 }
713 }
714 pub fn cgeo_v(&self) -> Option<f64> {
715 if self.cgeo_v == F64_DNU {
716 None
717 } else {
718 Some(self.cgeo_v)
719 }
720 }
721 pub fn cer(&self) -> Option<f32> {
722 if self.cer == F32_DNU {
723 None
724 } else {
725 Some(self.cer)
726 }
727 }
728 pub fn ciono_step(&self) -> Option<f64> {
729 if self.ciono_step == F64_DNU {
730 None
731 } else {
732 Some(self.ciono_step)
733 }
734 }
735 pub fn ciono_ramp(&self) -> Option<f64> {
736 if self.ciono_ramp == F64_DNU {
737 None
738 } else {
739 Some(self.ciono_ramp)
740 }
741 }
742 pub fn ccovariance(&self) -> Option<f64> {
743 if self.ccovariance == F64_DNU {
744 None
745 } else {
746 Some(self.ccovariance)
747 }
748 }
749}
750
751impl SbfBlockParse for GeoDegrFactorsBlock {
752 const BLOCK_ID: u16 = block_ids::GEO_DEGR_FACTORS;
753
754 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
755 const MIN_LEN: usize = 107;
756 if data.len() < MIN_LEN {
757 return Err(SbfError::ParseError("GEODegrFactors too short".into()));
758 }
759
760 let prn = data[12];
761 let brrc = f64::from_le_bytes(data[13..21].try_into().unwrap());
762 let cltc_lsb = f64::from_le_bytes(data[21..29].try_into().unwrap());
763 let cltc_v1 = f64::from_le_bytes(data[29..37].try_into().unwrap());
764 let iltc_v1 = u32::from_le_bytes([data[37], data[38], data[39], data[40]]);
765 let cltc_v0 = f64::from_le_bytes(data[41..49].try_into().unwrap());
766 let iltc_v0 = u32::from_le_bytes([data[49], data[50], data[51], data[52]]);
767 let cgeo_lsb = f64::from_le_bytes(data[53..61].try_into().unwrap());
768 let cgeo_v = f64::from_le_bytes(data[61..69].try_into().unwrap());
769 let igeo = u32::from_le_bytes([data[69], data[70], data[71], data[72]]);
770 let cer = f32::from_le_bytes(data[73..77].try_into().unwrap());
771 let ciono_step = f64::from_le_bytes(data[77..85].try_into().unwrap());
772 let iiono = u32::from_le_bytes([data[85], data[86], data[87], data[88]]);
773 let ciono_ramp = f64::from_le_bytes(data[89..97].try_into().unwrap());
774 let rss_udre = data[97];
775 let rss_iono = data[98];
776 let ccovariance = f64::from_le_bytes(data[99..107].try_into().unwrap());
777
778 Ok(Self {
779 tow_ms: header.tow_ms,
780 wnc: header.wnc,
781 prn,
782 brrc,
783 cltc_lsb,
784 cltc_v1,
785 iltc_v1,
786 cltc_v0,
787 iltc_v0,
788 cgeo_lsb,
789 cgeo_v,
790 igeo,
791 cer,
792 ciono_step,
793 iiono,
794 ciono_ramp,
795 rss_udre,
796 rss_iono,
797 ccovariance,
798 })
799 }
800}
801
802#[derive(Debug, Clone)]
808pub struct GeoServiceRegion {
809 pub latitude1: i8,
811 pub latitude2: i8,
813 pub longitude1: i16,
815 pub longitude2: i16,
817 pub region_shape: u8,
819}
820
821#[derive(Debug, Clone)]
825pub struct GeoServiceLevelBlock {
826 tow_ms: u32,
827 wnc: u16,
828 pub prn: u8,
830 pub iods: u8,
832 pub nr_messages: u8,
834 pub message_nr: u8,
836 pub priority_code: u8,
838 pub d_udrei_in: u8,
840 pub d_udrei_out: u8,
842 pub n: u8,
844 pub sb_length: u8,
846 pub regions: Vec<GeoServiceRegion>,
848}
849
850impl GeoServiceLevelBlock {
851 pub fn tow_seconds(&self) -> f64 {
852 self.tow_ms as f64 * 0.001
853 }
854 pub fn tow_ms(&self) -> u32 {
855 self.tow_ms
856 }
857 pub fn wnc(&self) -> u16 {
858 self.wnc
859 }
860 pub fn num_regions(&self) -> usize {
861 self.regions.len()
862 }
863}
864
865impl SbfBlockParse for GeoServiceLevelBlock {
866 const BLOCK_ID: u16 = block_ids::GEO_SERVICE_LEVEL;
867
868 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
869 const MIN_LEN: usize = 21;
870 if data.len() < MIN_LEN {
871 return Err(SbfError::ParseError("GEOServiceLevel too short".into()));
872 }
873
874 let prn = data[12];
875 let iods = data[13];
876 let nr_messages = data[14];
877 let message_nr = data[15];
878 let priority_code = data[16];
879 let d_udrei_in = data[17];
880 let d_udrei_out = data[18];
881 let n = data[19] as usize;
882 let sb_length = data[20] as usize;
883
884 if sb_length < 7 {
885 return Err(SbfError::ParseError(
886 "GEOServiceLevel SBLength too small".into(),
887 ));
888 }
889
890 let mut regions = Vec::with_capacity(n);
891 let mut offset = 21;
892
893 for _ in 0..n {
894 if offset + sb_length > data.len() {
895 return Err(SbfError::ParseError(
896 "GEOServiceLevel data truncated: fewer regions than N".into(),
897 ));
898 }
899
900 let latitude1 = data[offset] as i8;
901 let latitude2 = data[offset + 1] as i8;
902 let longitude1 = i16::from_le_bytes([data[offset + 2], data[offset + 3]]);
903 let longitude2 = i16::from_le_bytes([data[offset + 4], data[offset + 5]]);
904 let region_shape = data[offset + 6];
905
906 regions.push(GeoServiceRegion {
907 latitude1,
908 latitude2,
909 longitude1,
910 longitude2,
911 region_shape,
912 });
913
914 offset += sb_length;
915 }
916
917 Ok(Self {
918 tow_ms: header.tow_ms,
919 wnc: header.wnc,
920 prn,
921 iods,
922 nr_messages,
923 message_nr,
924 priority_code,
925 d_udrei_in,
926 d_udrei_out,
927 n: n as u8,
928 sb_length: sb_length as u8,
929 regions,
930 })
931 }
932}
933
934#[derive(Debug, Clone)]
942pub struct GeoNetworkTimeBlock {
943 tow_ms: u32,
944 wnc: u16,
945 pub prn: u8,
947 pub a1: f32,
949 pub a0: f64,
951 pub t_ot: u32,
953 pub wn_t: u8,
955 pub del_t_1s: i8,
957 pub wn_lsf: u8,
959 pub dn: u8,
961 pub del_t_lsf: i8,
963 pub utc_std_id: u8,
965 pub gps_wn: u16,
967 pub gps_tow: u32,
969 pub glonass_ind: u8,
971}
972
973impl GeoNetworkTimeBlock {
974 pub fn tow_seconds(&self) -> f64 {
975 self.tow_ms as f64 * 0.001
976 }
977 pub fn tow_ms(&self) -> u32 {
978 self.tow_ms
979 }
980 pub fn wnc(&self) -> u16 {
981 self.wnc
982 }
983}
984
985impl SbfBlockParse for GeoNetworkTimeBlock {
986 const BLOCK_ID: u16 = block_ids::GEO_NETWORK_TIME;
987
988 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
989 const MIN_LEN: usize = 42;
990 if data.len() < MIN_LEN {
991 return Err(SbfError::ParseError("GEONetworkTime too short".into()));
992 }
993
994 let prn = data[12];
995 let a1 = f32::from_le_bytes(data[13..17].try_into().unwrap());
996 let a0 = f64::from_le_bytes(data[17..25].try_into().unwrap());
997 let t_ot = u32::from_le_bytes([data[25], data[26], data[27], data[28]]);
998 let wn_t = data[29];
999 let del_t_1s = data[30] as i8;
1000 let wn_lsf = data[31];
1001 let dn = data[32];
1002 let del_t_lsf = data[33] as i8;
1003 let utc_std_id = data[34];
1004 let gps_wn = u16::from_le_bytes([data[35], data[36]]);
1005 let gps_tow = u32::from_le_bytes([data[37], data[38], data[39], data[40]]);
1006 let glonass_ind = data[41];
1007
1008 Ok(Self {
1009 tow_ms: header.tow_ms,
1010 wnc: header.wnc,
1011 prn,
1012 a1,
1013 a0,
1014 t_ot,
1015 wn_t,
1016 del_t_1s,
1017 wn_lsf,
1018 dn,
1019 del_t_lsf,
1020 utc_std_id,
1021 gps_wn,
1022 gps_tow,
1023 glonass_ind,
1024 })
1025 }
1026}
1027
1028#[derive(Debug, Clone)]
1036pub struct GeoIgpMaskBlock {
1037 tow_ms: u32,
1038 wnc: u16,
1039 pub prn: u8,
1041 pub nbr_bands: u8,
1043 pub band_nbr: u8,
1045 pub iodi: u8,
1047 pub nbr_igps: u8,
1049 pub igp_mask: Vec<u8>,
1051}
1052
1053impl GeoIgpMaskBlock {
1054 pub fn tow_seconds(&self) -> f64 {
1055 self.tow_ms as f64 * 0.001
1056 }
1057 pub fn tow_ms(&self) -> u32 {
1058 self.tow_ms
1059 }
1060 pub fn wnc(&self) -> u16 {
1061 self.wnc
1062 }
1063 pub fn num_igps(&self) -> usize {
1064 self.igp_mask.len()
1065 }
1066}
1067
1068impl SbfBlockParse for GeoIgpMaskBlock {
1069 const BLOCK_ID: u16 = block_ids::GEO_IGP_MASK;
1070
1071 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1072 const MIN_LEN: usize = 18;
1073 if data.len() < MIN_LEN {
1074 return Err(SbfError::ParseError("GEOIGPMask too short".into()));
1075 }
1076
1077 let prn = data[12];
1078 let nbr_bands = data[13];
1079 let band_nbr = data[14];
1080 let iodi = data[15];
1081 let nbr_igps = data[16] as usize;
1082
1083 let igp_mask_len = nbr_igps.min(data.len().saturating_sub(17));
1084 let igp_mask: Vec<u8> = data[17..17 + igp_mask_len].to_vec();
1085
1086 Ok(Self {
1087 tow_ms: header.tow_ms,
1088 wnc: header.wnc,
1089 prn,
1090 nbr_bands,
1091 band_nbr,
1092 iodi,
1093 nbr_igps: nbr_igps as u8,
1094 igp_mask,
1095 })
1096 }
1097}
1098
1099#[derive(Debug, Clone)]
1105pub struct GeoLongTermCorrEntry {
1106 pub velocity_code: u8,
1108 pub prn_mask_no: u8,
1110 pub iodp: u8,
1112 pub iode: u8,
1114 dx_m: f32,
1115 dy_m: f32,
1116 dz_m: f32,
1117 dx_rate_mps: f32,
1118 dy_rate_mps: f32,
1119 dz_rate_mps: f32,
1120 da_f0_s: f32,
1121 da_f1_sps: f32,
1122 pub t_oe: u32,
1124}
1125
1126impl GeoLongTermCorrEntry {
1127 pub fn dx_m(&self) -> Option<f32> {
1128 if self.dx_m == F32_DNU {
1129 None
1130 } else {
1131 Some(self.dx_m)
1132 }
1133 }
1134 pub fn dy_m(&self) -> Option<f32> {
1135 if self.dy_m == F32_DNU {
1136 None
1137 } else {
1138 Some(self.dy_m)
1139 }
1140 }
1141 pub fn dz_m(&self) -> Option<f32> {
1142 if self.dz_m == F32_DNU {
1143 None
1144 } else {
1145 Some(self.dz_m)
1146 }
1147 }
1148 pub fn dx_rate_mps(&self) -> Option<f32> {
1149 if self.dx_rate_mps == F32_DNU {
1150 None
1151 } else {
1152 Some(self.dx_rate_mps)
1153 }
1154 }
1155 pub fn dy_rate_mps(&self) -> Option<f32> {
1156 if self.dy_rate_mps == F32_DNU {
1157 None
1158 } else {
1159 Some(self.dy_rate_mps)
1160 }
1161 }
1162 pub fn dz_rate_mps(&self) -> Option<f32> {
1163 if self.dz_rate_mps == F32_DNU {
1164 None
1165 } else {
1166 Some(self.dz_rate_mps)
1167 }
1168 }
1169 pub fn da_f0_s(&self) -> Option<f32> {
1170 if self.da_f0_s == F32_DNU {
1171 None
1172 } else {
1173 Some(self.da_f0_s)
1174 }
1175 }
1176 pub fn da_f1_sps(&self) -> Option<f32> {
1177 if self.da_f1_sps == F32_DNU {
1178 None
1179 } else {
1180 Some(self.da_f1_sps)
1181 }
1182 }
1183}
1184
1185#[derive(Debug, Clone)]
1189pub struct GeoLongTermCorrBlock {
1190 tow_ms: u32,
1191 wnc: u16,
1192 pub prn: u8,
1194 pub n: u8,
1196 pub sb_length: u8,
1198 pub corrections: Vec<GeoLongTermCorrEntry>,
1200}
1201
1202impl GeoLongTermCorrBlock {
1203 pub fn tow_seconds(&self) -> f64 {
1204 self.tow_ms as f64 * 0.001
1205 }
1206 pub fn tow_ms(&self) -> u32 {
1207 self.tow_ms
1208 }
1209 pub fn wnc(&self) -> u16 {
1210 self.wnc
1211 }
1212 pub fn num_corrections(&self) -> usize {
1213 self.corrections.len()
1214 }
1215}
1216
1217impl SbfBlockParse for GeoLongTermCorrBlock {
1218 const BLOCK_ID: u16 = block_ids::GEO_LONG_TERM_CORR;
1219
1220 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1221 const MIN_LEN: usize = 16;
1222 if data.len() < MIN_LEN {
1223 return Err(SbfError::ParseError("GEOLongTermCorr too short".into()));
1224 }
1225
1226 let prn = data[12];
1227 let n = data[13] as usize;
1228 let sb_length = data[14] as usize;
1229
1230 if sb_length < 28 {
1231 return Err(SbfError::ParseError(
1232 "GEOLongTermCorr SBLength too small".into(),
1233 ));
1234 }
1235
1236 let mut corrections = Vec::with_capacity(n);
1237 let mut offset = 15;
1238
1239 for _ in 0..n {
1240 if offset + sb_length > data.len() {
1241 break;
1242 }
1243
1244 let velocity_code = data[offset];
1245 let prn_mask_no = data[offset + 1];
1246 let iodp = data[offset + 2];
1247 let iode = data[offset + 3];
1248 let dx_m = f32::from_le_bytes(data[offset + 4..offset + 8].try_into().unwrap());
1249 let dy_m = f32::from_le_bytes(data[offset + 8..offset + 12].try_into().unwrap());
1250 let dz_m = f32::from_le_bytes(data[offset + 12..offset + 16].try_into().unwrap());
1251
1252 let (dx_rate_mps, dy_rate_mps, dz_rate_mps, da_f0_s, da_f1_sps, t_oe) =
1253 if sb_length >= 40 {
1254 (
1255 f32::from_le_bytes(data[offset + 16..offset + 20].try_into().unwrap()),
1256 f32::from_le_bytes(data[offset + 20..offset + 24].try_into().unwrap()),
1257 f32::from_le_bytes(data[offset + 24..offset + 28].try_into().unwrap()),
1258 f32::from_le_bytes(data[offset + 28..offset + 32].try_into().unwrap()),
1259 f32::from_le_bytes(data[offset + 32..offset + 36].try_into().unwrap()),
1260 u32::from_le_bytes([
1261 data[offset + 36],
1262 data[offset + 37],
1263 data[offset + 38],
1264 data[offset + 39],
1265 ]),
1266 )
1267 } else {
1268 let t_oe = u32::from_le_bytes([
1269 data[offset + 24],
1270 data[offset + 25],
1271 data[offset + 26],
1272 data[offset + 27],
1273 ]);
1274 (
1275 F32_DNU,
1276 F32_DNU,
1277 F32_DNU,
1278 f32::from_le_bytes(data[offset + 16..offset + 20].try_into().unwrap()),
1279 f32::from_le_bytes(data[offset + 20..offset + 24].try_into().unwrap()),
1280 t_oe,
1281 )
1282 };
1283
1284 corrections.push(GeoLongTermCorrEntry {
1285 velocity_code,
1286 prn_mask_no,
1287 iodp,
1288 iode,
1289 dx_m,
1290 dy_m,
1291 dz_m,
1292 dx_rate_mps,
1293 dy_rate_mps,
1294 dz_rate_mps,
1295 da_f0_s,
1296 da_f1_sps,
1297 t_oe,
1298 });
1299
1300 offset += sb_length;
1301 }
1302
1303 Ok(Self {
1304 tow_ms: header.tow_ms,
1305 wnc: header.wnc,
1306 prn,
1307 n: n as u8,
1308 sb_length: sb_length as u8,
1309 corrections,
1310 })
1311 }
1312}
1313
1314#[derive(Debug, Clone)]
1320pub struct GeoClockEphCovMatrixEntry {
1321 pub prn_mask_no: u8,
1323 pub scale_exp: u8,
1325 pub e11: u16,
1327 pub e22: u16,
1328 pub e33: u16,
1329 pub e44: u16,
1330 pub e12: i16,
1332 pub e13: i16,
1333 pub e14: i16,
1334 pub e23: i16,
1335 pub e24: i16,
1336 pub e34: i16,
1337}
1338
1339impl GeoClockEphCovMatrixEntry {
1340 pub fn scale_factor(&self) -> f64 {
1342 2f64.powi(self.scale_exp as i32)
1343 }
1344}
1345
1346#[derive(Debug, Clone)]
1350pub struct GeoClockEphCovMatrixBlock {
1351 tow_ms: u32,
1352 wnc: u16,
1353 pub prn: u8,
1355 pub iodp: u8,
1357 pub n: u8,
1359 pub sb_length: u8,
1361 pub entries: Vec<GeoClockEphCovMatrixEntry>,
1363}
1364
1365impl GeoClockEphCovMatrixBlock {
1366 pub fn tow_seconds(&self) -> f64 {
1367 self.tow_ms as f64 * 0.001
1368 }
1369 pub fn tow_ms(&self) -> u32 {
1370 self.tow_ms
1371 }
1372 pub fn wnc(&self) -> u16 {
1373 self.wnc
1374 }
1375 pub fn num_entries(&self) -> usize {
1376 self.entries.len()
1377 }
1378}
1379
1380impl SbfBlockParse for GeoClockEphCovMatrixBlock {
1381 const BLOCK_ID: u16 = block_ids::GEO_CLOCK_EPH_COV_MATRIX;
1382
1383 fn parse(header: &SbfHeader, data: &[u8]) -> SbfResult<Self> {
1384 const MIN_LEN: usize = 17;
1385 if data.len() < MIN_LEN {
1386 return Err(SbfError::ParseError(
1387 "GEOClockEphCovMatrix too short".into(),
1388 ));
1389 }
1390
1391 let prn = data[12];
1392 let iodp = data[13];
1393 let n = data[14] as usize;
1394 let sb_length = data[15] as usize;
1395
1396 if sb_length < 22 {
1397 return Err(SbfError::ParseError(
1398 "GEOClockEphCovMatrix SBLength too small".into(),
1399 ));
1400 }
1401
1402 let mut entries = Vec::with_capacity(n);
1403 let mut offset = 16;
1404
1405 for _ in 0..n {
1406 if offset + sb_length > data.len() {
1407 break;
1408 }
1409
1410 let prn_mask_no = data[offset];
1411 let scale_exp = data[offset + 1];
1412 let e11 = u16::from_le_bytes([data[offset + 2], data[offset + 3]]);
1413 let e22 = u16::from_le_bytes([data[offset + 4], data[offset + 5]]);
1414 let e33 = u16::from_le_bytes([data[offset + 6], data[offset + 7]]);
1415 let e44 = u16::from_le_bytes([data[offset + 8], data[offset + 9]]);
1416 let e12 = i16::from_le_bytes([data[offset + 10], data[offset + 11]]);
1417 let e13 = i16::from_le_bytes([data[offset + 12], data[offset + 13]]);
1418 let e14 = i16::from_le_bytes([data[offset + 14], data[offset + 15]]);
1419 let e23 = i16::from_le_bytes([data[offset + 16], data[offset + 17]]);
1420 let e24 = i16::from_le_bytes([data[offset + 18], data[offset + 19]]);
1421 let e34 = i16::from_le_bytes([data[offset + 20], data[offset + 21]]);
1422
1423 entries.push(GeoClockEphCovMatrixEntry {
1424 prn_mask_no,
1425 scale_exp,
1426 e11,
1427 e22,
1428 e33,
1429 e44,
1430 e12,
1431 e13,
1432 e14,
1433 e23,
1434 e24,
1435 e34,
1436 });
1437
1438 offset += sb_length;
1439 }
1440
1441 Ok(Self {
1442 tow_ms: header.tow_ms,
1443 wnc: header.wnc,
1444 prn,
1445 iodp,
1446 n: n as u8,
1447 sb_length: sb_length as u8,
1448 entries,
1449 })
1450 }
1451}
1452
1453#[cfg(test)]
1454mod tests {
1455 use super::*;
1456 use crate::header::SbfHeader;
1457
1458 fn header_for(block_id: u16, tow_ms: u32, wnc: u16) -> SbfHeader {
1459 SbfHeader {
1460 crc: 0,
1461 block_id,
1462 block_rev: 0,
1463 length: 32,
1464 tow_ms,
1465 wnc,
1466 }
1467 }
1468
1469 #[test]
1470 fn test_geo_mt00_parse() {
1471 let header = header_for(5925, 1000, 2200);
1472 let mut data = vec![0u8; 13];
1473 data[12] = 131; let block = GeoMt00Block::parse(&header, &data).unwrap();
1476 assert_eq!(block.tow_seconds(), 1.0);
1477 assert_eq!(block.wnc(), 2200);
1478 assert_eq!(block.prn, 131);
1479 }
1480
1481 #[test]
1482 fn test_geo_mt00_too_short() {
1483 let header = header_for(5925, 0, 0);
1484 let data = [0u8; 10];
1485 assert!(GeoMt00Block::parse(&header, &data).is_err());
1486 }
1487
1488 #[test]
1489 fn test_geo_prn_mask_parse() {
1490 let header = header_for(5926, 2000, 2100);
1491 let mut data = vec![0u8; 20];
1492 data[12] = 120;
1493 data[13] = 5;
1494 data[14] = 3; data[15] = 1;
1496 data[16] = 2;
1497 data[17] = 3;
1498
1499 let block = GeoPrnMaskBlock::parse(&header, &data).unwrap();
1500 assert_eq!(block.prn, 120);
1501 assert_eq!(block.iodp, 5);
1502 assert_eq!(block.nbr_prns, 3);
1503 assert_eq!(block.prn_mask, vec![1, 2, 3]);
1504 }
1505
1506 #[test]
1507 fn test_geo_fast_corr_parse() {
1508 let header = header_for(5927, 3000, 2000);
1509 let mut data = vec![0u8; 30];
1510 data[12] = 124;
1511 data[13] = 2; data[14] = 1;
1513 data[15] = 0;
1514 data[16] = 1; data[17] = 6; data[18] = 10; data[19] = 2; data[20..24].copy_from_slice(&2.5_f32.to_le_bytes()); let block = GeoFastCorrBlock::parse(&header, &data).unwrap();
1521 assert_eq!(block.prn, 124);
1522 assert_eq!(block.mt, 2);
1523 assert_eq!(block.n, 1);
1524 assert_eq!(block.corrections.len(), 1);
1525 assert_eq!(block.corrections[0].prn_mask_no, 10);
1526 assert_eq!(block.corrections[0].udrei, 2);
1527 assert!((block.corrections[0].prc_m().unwrap() - 2.5).abs() < 1e-6);
1528 }
1529
1530 #[test]
1531 fn test_geo_fast_corr_dnu() {
1532 let header = header_for(5927, 0, 0);
1533 let mut data = vec![0u8; 30];
1534 data[16] = 1;
1535 data[17] = 6;
1536 data[20..24].copy_from_slice(&F32_DNU.to_le_bytes());
1537
1538 let block = GeoFastCorrBlock::parse(&header, &data).unwrap();
1539 assert!(block.corrections[0].prc_m().is_none());
1540 }
1541
1542 #[test]
1543 fn test_geo_nav_parse() {
1544 let header = header_for(5896, 5000, 2200);
1545 let mut data = vec![0u8; 101];
1546 data[12] = 124;
1547 data[13..15].copy_from_slice(&1u16.to_le_bytes());
1548 data[15..17].copy_from_slice(&2u16.to_le_bytes());
1549 data[17..21].copy_from_slice(&100u32.to_le_bytes());
1550 data[21..29].copy_from_slice(&12345678.0_f64.to_le_bytes());
1551 data[29..37].copy_from_slice(&23456789.0_f64.to_le_bytes());
1552 data[37..45].copy_from_slice(&34567890.0_f64.to_le_bytes());
1553 data[93..97].copy_from_slice(&0.0001_f32.to_le_bytes());
1554 data[97..101].copy_from_slice(&0.00001_f32.to_le_bytes());
1555
1556 let block = GeoNavBlock::parse(&header, &data).unwrap();
1557 assert_eq!(block.tow_seconds(), 5.0);
1558 assert_eq!(block.wnc(), 2200);
1559 assert_eq!(block.prn, 124);
1560 assert_eq!(block.t0, 100);
1561 assert!((block.position_x_m().unwrap() - 12345678.0).abs() < 1e-6);
1562 assert!((block.clock_bias_s().unwrap() - 0.0001).abs() < 1e-9);
1563 }
1564
1565 #[test]
1566 fn test_geo_nav_dnu() {
1567 let header = header_for(5896, 0, 0);
1568 let mut data = vec![0u8; 101];
1569 data[21..29].copy_from_slice(&F64_DNU.to_le_bytes());
1570 data[93..97].copy_from_slice(&F32_DNU.to_le_bytes());
1571
1572 let block = GeoNavBlock::parse(&header, &data).unwrap();
1573 assert!(block.position_x_m().is_none());
1574 assert!(block.clock_bias_s().is_none());
1575 }
1576
1577 #[test]
1578 fn test_geo_integrity_parse() {
1579 let header = header_for(5928, 1000, 2100);
1580 let mut data = vec![0u8; 68];
1581 data[12] = 120;
1582 data[13..17].copy_from_slice(&[1, 2, 3, 4]);
1583 data[17..68].fill(5);
1584
1585 let block = GeoIntegrityBlock::parse(&header, &data).unwrap();
1586 assert_eq!(block.tow_seconds(), 1.0);
1587 assert_eq!(block.prn, 120);
1588 assert_eq!(block.iodf, [1, 2, 3, 4]);
1589 assert_eq!(block.udrei[0], 5);
1590 }
1591
1592 #[test]
1593 fn test_geo_alm_parse() {
1594 let header = header_for(5897, 2000, 2050);
1595 let mut data = vec![0u8; 68];
1596 data[12] = 131;
1597 data[13] = 1;
1598 data[14..16].copy_from_slice(&0u16.to_le_bytes());
1599 data[16..20].copy_from_slice(&500u32.to_le_bytes());
1600 data[20..28].copy_from_slice(&1e7_f64.to_le_bytes());
1601 data[28..36].copy_from_slice(&2e7_f64.to_le_bytes());
1602 data[36..44].copy_from_slice(&3e7_f64.to_le_bytes());
1603
1604 let block = GeoAlmBlock::parse(&header, &data).unwrap();
1605 assert_eq!(block.prn, 131);
1606 assert_eq!(block.data_id, 1);
1607 assert_eq!(block.t0, 500);
1608 assert!((block.position_x_m().unwrap() - 1e7).abs() < 1.0);
1609 }
1610
1611 #[test]
1612 fn test_geo_network_time_parse() {
1613 let header = header_for(5918, 3000, 2000);
1614 let mut data = vec![0u8; 42];
1615 data[12] = 124;
1616 data[13..17].copy_from_slice(&0.0001_f32.to_le_bytes());
1617 data[17..25].copy_from_slice(&0.5_f64.to_le_bytes());
1618 data[25..29].copy_from_slice(&1000u32.to_le_bytes());
1619 data[29] = 100;
1620 data[30] = 18i8 as u8;
1621 data[35..37].copy_from_slice(&2300u16.to_le_bytes());
1622 data[37..41].copy_from_slice(&43200000u32.to_le_bytes());
1623
1624 let block = GeoNetworkTimeBlock::parse(&header, &data).unwrap();
1625 assert_eq!(block.prn, 124);
1626 assert_eq!(block.gps_wn, 2300);
1627 assert_eq!(block.gps_tow, 43200000);
1628 }
1629
1630 #[test]
1631 fn test_geo_fast_corr_degr_parse() {
1632 let header = header_for(5929, 1000, 2200);
1633 let mut data = vec![0u8; 66];
1634 data[12] = 124;
1635 data[13] = 3;
1636 data[14] = 5;
1637 data[15..20].copy_from_slice(&[1, 2, 3, 4, 5]);
1638
1639 let block = GeoFastCorrDegrBlock::parse(&header, &data).unwrap();
1640 assert_eq!(block.tow_seconds(), 1.0);
1641 assert_eq!(block.prn, 124);
1642 assert_eq!(block.iodp, 3);
1643 assert_eq!(block.t_lat, 5);
1644 assert_eq!(block.ai[0], 1);
1645 assert_eq!(block.ai[4], 5);
1646 }
1647
1648 #[test]
1649 fn test_geo_fast_corr_degr_too_short() {
1650 let header = header_for(5929, 0, 0);
1651 let data = vec![0u8; 50];
1652 assert!(GeoFastCorrDegrBlock::parse(&header, &data).is_err());
1653 }
1654
1655 #[test]
1656 fn test_geo_degr_factors_parse() {
1657 let header = header_for(5930, 2000, 2100);
1658 let mut data = vec![0u8; 107];
1659 data[12] = 120;
1660 data[13..21].copy_from_slice(&1.5_f64.to_le_bytes());
1661 data[21..29].copy_from_slice(&0.125_f64.to_le_bytes());
1662 data[37..41].copy_from_slice(&60u32.to_le_bytes());
1663 data[97] = 1;
1664 data[98] = 0;
1665
1666 let block = GeoDegrFactorsBlock::parse(&header, &data).unwrap();
1667 assert_eq!(block.tow_seconds(), 2.0);
1668 assert_eq!(block.prn, 120);
1669 assert!((block.brrc().unwrap() - 1.5).abs() < 1e-10);
1670 assert_eq!(block.iltc_v1, 60);
1671 assert_eq!(block.rss_udre, 1);
1672 }
1673
1674 #[test]
1675 fn test_geo_degr_factors_dnu() {
1676 let header = header_for(5930, 0, 0);
1677 let mut data = vec![0u8; 107];
1678 data[13..21].copy_from_slice(&F64_DNU.to_le_bytes());
1679 data[73..77].copy_from_slice(&F32_DNU.to_le_bytes());
1680
1681 let block = GeoDegrFactorsBlock::parse(&header, &data).unwrap();
1682 assert!(block.brrc().is_none());
1683 assert!(block.cer().is_none());
1684 }
1685
1686 #[test]
1687 fn test_geo_service_level_parse() {
1688 let header = header_for(5917, 3000, 2000);
1689 let mut data = vec![0u8; 28];
1690 data[12] = 124;
1691 data[13] = 1;
1692 data[14] = 2;
1693 data[15] = 0;
1694 data[16] = 0;
1695 data[17] = 1;
1696 data[18] = 2;
1697 data[19] = 1;
1698 data[20] = 7;
1699 data[21] = 45i8 as u8;
1700 data[22] = 50i8 as u8;
1701 data[23..25].copy_from_slice(&(-120i16).to_le_bytes());
1702 data[25..27].copy_from_slice(&(-115i16).to_le_bytes());
1703 data[27] = 0;
1704
1705 let block = GeoServiceLevelBlock::parse(&header, &data).unwrap();
1706 assert_eq!(block.tow_seconds(), 3.0);
1707 assert_eq!(block.prn, 124);
1708 assert_eq!(block.iods, 1);
1709 assert_eq!(block.n, 1);
1710 assert_eq!(block.regions.len(), 1);
1711 assert_eq!(block.regions[0].latitude1, 45);
1712 assert_eq!(block.regions[0].latitude2, 50);
1713 assert_eq!(block.regions[0].longitude1, -120);
1714 }
1715
1716 #[test]
1717 fn test_geo_service_level_too_short() {
1718 let header = header_for(5917, 0, 0);
1719 let data = vec![0u8; 15];
1720 assert!(GeoServiceLevelBlock::parse(&header, &data).is_err());
1721 }
1722
1723 #[test]
1724 fn test_geo_service_level_truncated_regions() {
1725 let header = header_for(5917, 0, 0);
1726 let mut data = vec![0u8; 28];
1727 data[19] = 2;
1728 data[20] = 7;
1729 assert!(GeoServiceLevelBlock::parse(&header, &data).is_err());
1730 }
1731
1732 #[test]
1733 fn test_geo_igp_mask_parse() {
1734 let header = header_for(5931, 5000, 2100);
1735 let mut data = vec![0u8; 22];
1736 data[12] = 124;
1737 data[13] = 1;
1738 data[14] = 0;
1739 data[15] = 5;
1740 data[16] = 4;
1741 data[17] = 0xAB;
1742 data[18] = 0xCD;
1743 data[19] = 0xEF;
1744 data[20] = 0x12;
1745
1746 let block = GeoIgpMaskBlock::parse(&header, &data).unwrap();
1747 assert_eq!(block.tow_seconds(), 5.0);
1748 assert_eq!(block.wnc(), 2100);
1749 assert_eq!(block.prn, 124);
1750 assert_eq!(block.nbr_bands, 1);
1751 assert_eq!(block.band_nbr, 0);
1752 assert_eq!(block.iodi, 5);
1753 assert_eq!(block.nbr_igps, 4);
1754 assert_eq!(block.igp_mask.len(), 4);
1755 assert_eq!(block.igp_mask[0], 0xAB);
1756 assert_eq!(block.igp_mask[3], 0x12);
1757 }
1758
1759 #[test]
1760 fn test_geo_igp_mask_too_short() {
1761 let header = header_for(5931, 0, 0);
1762 let data = vec![0u8; 15];
1763 assert!(GeoIgpMaskBlock::parse(&header, &data).is_err());
1764 }
1765
1766 #[test]
1767 fn test_geo_long_term_corr_parse() {
1768 let header = header_for(5932, 1000, 2200);
1769 let mut data = vec![0u8; 56];
1770 data[12] = 131;
1771 data[13] = 1;
1772 data[14] = 40;
1773 data[15] = 1;
1774 data[16] = 2;
1775 data[17] = 3;
1776 data[18] = 4;
1777 data[19..23].copy_from_slice(&(1.5f32).to_le_bytes());
1778 data[23..27].copy_from_slice(&(2.5f32).to_le_bytes());
1779 data[27..31].copy_from_slice(&(3.5f32).to_le_bytes());
1780 data[31..35].copy_from_slice(&(0.1f32).to_le_bytes());
1781 data[35..39].copy_from_slice(&(0.2f32).to_le_bytes());
1782 data[39..43].copy_from_slice(&(0.3f32).to_le_bytes());
1783 data[43..47].copy_from_slice(&(1e-6f32).to_le_bytes());
1784 data[47..51].copy_from_slice(&(1e-10f32).to_le_bytes());
1785 data[51..55].copy_from_slice(&86400u32.to_le_bytes());
1786
1787 let block = GeoLongTermCorrBlock::parse(&header, &data).unwrap();
1788 assert_eq!(block.tow_seconds(), 1.0);
1789 assert_eq!(block.prn, 131);
1790 assert_eq!(block.n, 1);
1791 assert_eq!(block.corrections.len(), 1);
1792 let c = &block.corrections[0];
1793 assert_eq!(c.prn_mask_no, 2);
1794 assert_eq!(c.iodp, 3);
1795 assert_eq!(c.iode, 4);
1796 assert_eq!(c.dx_m(), Some(1.5));
1797 assert_eq!(c.t_oe, 86400);
1798 }
1799
1800 #[test]
1801 fn test_geo_long_term_corr_dnu() {
1802 let header = header_for(5932, 0, 0);
1803 let mut data = vec![0u8; 56];
1804 data[13] = 1;
1805 data[14] = 40;
1806 data[19..23].copy_from_slice(&F32_DNU.to_le_bytes());
1807 data[23..27].copy_from_slice(&F32_DNU.to_le_bytes());
1808 data[27..31].copy_from_slice(&F32_DNU.to_le_bytes());
1809
1810 let block = GeoLongTermCorrBlock::parse(&header, &data).unwrap();
1811 assert!(block.corrections[0].dx_m().is_none());
1812 assert!(block.corrections[0].dy_m().is_none());
1813 assert!(block.corrections[0].dz_m().is_none());
1814 }
1815
1816 #[test]
1817 fn test_geo_clock_eph_cov_matrix_parse() {
1818 let header = header_for(5934, 2000, 100);
1819 let mut data = vec![0u8; 40];
1820 data[12] = 120;
1821 data[13] = 7;
1822 data[14] = 1;
1823 data[15] = 22;
1824 data[16] = 1;
1825 data[17] = 3;
1826 data[18..20].copy_from_slice(&100u16.to_le_bytes());
1827 data[20..22].copy_from_slice(&200u16.to_le_bytes());
1828 data[22..24].copy_from_slice(&300u16.to_le_bytes());
1829 data[24..26].copy_from_slice(&400u16.to_le_bytes());
1830 data[26..28].copy_from_slice(&(-10i16).to_le_bytes());
1831 data[28..30].copy_from_slice(&(-20i16).to_le_bytes());
1832 data[30..32].copy_from_slice(&(-30i16).to_le_bytes());
1833 data[32..34].copy_from_slice(&(-40i16).to_le_bytes());
1834 data[34..36].copy_from_slice(&(-50i16).to_le_bytes());
1835 data[36..38].copy_from_slice(&(-60i16).to_le_bytes());
1836
1837 let block = GeoClockEphCovMatrixBlock::parse(&header, &data).unwrap();
1838 assert_eq!(block.tow_seconds(), 2.0);
1839 assert_eq!(block.prn, 120);
1840 assert_eq!(block.iodp, 7);
1841 assert_eq!(block.n, 1);
1842 assert_eq!(block.entries.len(), 1);
1843 let e = &block.entries[0];
1844 assert_eq!(e.prn_mask_no, 1);
1845 assert_eq!(e.scale_exp, 3);
1846 assert_eq!(e.e11, 100);
1847 assert_eq!(e.e22, 200);
1848 assert_eq!(e.e34, -60);
1849 assert_eq!(e.scale_factor(), 8.0);
1850 }
1851
1852 #[test]
1853 fn test_geo_clock_eph_cov_matrix_too_short() {
1854 let header = header_for(5934, 0, 0);
1855 let data = vec![0u8; 14];
1856 assert!(GeoClockEphCovMatrixBlock::parse(&header, &data).is_err());
1857 }
1858}