1use crate::error::{Error, Result};
15use alloc::vec;
16use alloc::vec::Vec;
17use broadcast_common::{Parse, Serialize};
18
19pub const TABLE_ID: u8 = 0x4D;
21pub const PID: u16 = 0x001B;
23
24const HEADER_LEN: usize = 9;
25const SECTION_LENGTH_PREFIX: usize = 3;
26const CRC_LEN: usize = 4;
27
28fn pad_to_byte(bits: usize) -> usize {
29 (8 - (bits % 8)) % 8
30}
31
32struct BitReader<'a> {
35 data: &'a [u8],
36 bit_pos: usize,
37}
38
39impl<'a> BitReader<'a> {
40 fn new(data: &'a [u8]) -> Self {
41 Self { data, bit_pos: 0 }
42 }
43 fn remaining_bits(&self) -> usize {
44 (self.data.len() * 8).saturating_sub(self.bit_pos)
45 }
46 fn bits_consumed(&self) -> usize {
47 self.bit_pos
48 }
49 fn read_u(&mut self, bits: u8) -> Result<u64> {
57 let bits = bits as usize;
58 if self.bit_pos + bits > self.data.len() * 8 {
59 return Err(Error::BufferTooShort {
60 need: self.bit_pos + bits,
61 have: self.data.len() * 8,
62 what: "SatSection bit reader overrun",
63 });
64 }
65 let mut br = broadcast_common::bits::BitReader::new(self.data);
66 br.skip_bits(self.bit_pos)
67 .expect("bounds already validated above");
68 let val = br
69 .read_bits(bits as u32)
70 .expect("bounds already validated above");
71 self.bit_pos += bits;
72 Ok(val)
73 }
74 fn read_i(&mut self, bits: u8) -> Result<i64> {
75 let raw = self.read_u(bits)?;
76 let bits = bits as usize;
77 if raw & (1u64 << (bits - 1)) != 0 {
78 Ok((raw as i64) | (!0i64 << bits))
79 } else {
80 Ok(raw as i64)
81 }
82 }
83 fn skip(&mut self, bits: u8) -> Result<()> {
84 if self.bit_pos + bits as usize > self.data.len() * 8 {
85 return Err(Error::BufferTooShort {
86 need: self.bit_pos + bits as usize,
87 have: self.data.len() * 8,
88 what: "SatSection bit reader overrun",
89 });
90 }
91 self.bit_pos += bits as usize;
92 Ok(())
93 }
94}
95
96struct BitWriter<'a> {
110 buf: &'a mut [u8],
111 bit_pos: usize,
112}
113
114impl<'a> BitWriter<'a> {
115 fn new(buf: &'a mut [u8]) -> Self {
116 Self { buf, bit_pos: 0 }
117 }
118 fn bits_written(&self) -> usize {
119 self.bit_pos
120 }
121 fn write_u(&mut self, bits: u8, val: u64) -> Result<()> {
122 let bits = bits as usize;
123 if self.bit_pos + bits > self.buf.len() * 8 {
124 return Err(Error::BufferTooShort {
125 need: self.bit_pos + bits,
126 have: self.buf.len() * 8,
127 what: "SatSection bit writer overrun",
128 });
129 }
130 for i in 0..bits {
131 let byte_idx = (self.bit_pos + i) / 8;
132 let bit_idx = 7 - ((self.bit_pos + i) % 8);
133 let bit_val = ((val >> (bits - 1 - i)) & 1) as u8;
134 self.buf[byte_idx] |= bit_val << bit_idx;
135 }
136 self.bit_pos += bits;
137 Ok(())
138 }
139 fn write_i(&mut self, bits: u8, val: i64) -> Result<()> {
140 self.write_u(bits, val as u64 & ((1u64 << bits) - 1))
141 }
142 fn write_zero(&mut self, bits: u8) -> Result<()> {
143 if self.bit_pos + bits as usize > self.buf.len() * 8 {
144 return Err(Error::BufferTooShort {
145 need: self.bit_pos + bits as usize,
146 have: self.buf.len() * 8,
147 what: "SatSection bit writer overrun",
148 });
149 }
150 self.bit_pos += bits as usize;
151 Ok(())
152 }
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, num_enum::TryFromPrimitive)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize))]
161#[repr(u8)]
162#[non_exhaustive]
163pub enum SatTableId {
164 PositionV2 = 0,
166 CellFragment = 1,
168 TimeAssociation = 2,
170 BeamhoppingTimePlan = 3,
172 PositionV3 = 4,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize))]
181#[non_exhaustive]
182pub enum PositionSystem {
183 Orbital {
185 orbital_position: u16,
187 west_east_flag: bool,
189 },
190 Sgp4 {
192 epoch_year: u8,
194 day_of_the_year: u16,
196 day_fraction: u32,
198 mean_motion_first_derivative: u32,
200 mean_motion_second_derivative: u32,
202 drag_term: u32,
204 inclination: u32,
206 right_ascension: u32,
208 eccentricity: u32,
210 argument_of_perigree: u32,
212 mean_anomaly: u32,
214 mean_motion: u32,
216 },
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
221#[cfg_attr(feature = "serde", derive(serde::Serialize))]
222pub struct PositionV2Satellite {
223 pub satellite_id: u32,
225 pub position: PositionSystem,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
231#[cfg_attr(feature = "serde", derive(serde::Serialize))]
232pub struct PositionV2Body {
233 pub satellites: Vec<PositionV2Satellite>,
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
241#[cfg_attr(feature = "serde", derive(serde::Serialize))]
242pub struct CellCenter {
243 pub center_latitude: i32,
245 pub center_longitude: i32,
247 pub max_distance: u32,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
253#[cfg_attr(feature = "serde", derive(serde::Serialize))]
254pub struct NewDeliverySystem {
255 pub new_delivery_system_id: u32,
257 pub time_of_application_base: u64,
259 pub time_of_application_ext: u16,
261}
262
263#[derive(Debug, Clone, PartialEq, Eq)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize))]
266pub struct ObsolescentDeliverySystem {
267 pub obsolescent_delivery_system_id: u32,
269 pub time_of_obsolescence_base: u64,
271 pub time_of_obsolescence_ext: u16,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
277#[cfg_attr(feature = "serde", derive(serde::Serialize))]
278pub struct CellFragment {
279 pub cell_fragment_id: u32,
281 pub first_occurrence: bool,
283 pub last_occurrence: bool,
285 pub center: Option<CellCenter>,
287 pub delivery_system_ids: Vec<u32>,
289 pub new_delivery_systems: Vec<NewDeliverySystem>,
291 pub obsolescent_delivery_systems: Vec<ObsolescentDeliverySystem>,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
297#[cfg_attr(feature = "serde", derive(serde::Serialize))]
298pub struct CellFragmentBody {
299 pub fragments: Vec<CellFragment>,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307#[cfg_attr(feature = "serde", derive(serde::Serialize))]
308#[non_exhaustive]
309pub enum AssociationType {
310 UtcWithoutLeap,
312 UtcWithLeap,
314 Reserved(u8),
316}
317
318impl AssociationType {
319 #[must_use]
320 pub fn from_u8(v: u8) -> Self {
322 match v & 0x0F {
323 0 => Self::UtcWithoutLeap,
324 1 => Self::UtcWithLeap,
325 v => Self::Reserved(v),
326 }
327 }
328
329 #[must_use]
330 pub const fn to_u8(self) -> u8 {
332 match self {
333 Self::UtcWithoutLeap => 0,
334 Self::UtcWithLeap => 1,
335 Self::Reserved(v) => v,
336 }
337 }
338
339 #[must_use]
340 pub fn name(self) -> &'static str {
342 match self {
343 Self::UtcWithoutLeap => "UTC without leap second",
344 Self::UtcWithLeap => "UTC with leap second",
345 Self::Reserved(_) => "Reserved",
346 }
347 }
348}
349broadcast_common::impl_spec_display!(AssociationType, Reserved);
350
351#[derive(Debug, Clone, PartialEq, Eq)]
353#[cfg_attr(feature = "serde", derive(serde::Serialize))]
354pub struct LeapInfo {
355 pub leap59: bool,
357 pub leap61: bool,
359 pub pastleap59: bool,
361 pub pastleap61: bool,
363}
364
365#[derive(Debug, Clone, PartialEq, Eq)]
367#[cfg_attr(feature = "serde", derive(serde::Serialize))]
368pub struct TimeAssociationBody {
369 pub association_type: AssociationType,
371 pub leap_info: Option<LeapInfo>,
373 pub ncr_base: u64,
375 pub ncr_ext: u16,
377 pub association_timestamp_seconds: u64,
379 pub association_timestamp_nanoseconds: u32,
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390#[cfg_attr(feature = "serde", derive(serde::Serialize))]
391#[non_exhaustive]
392pub enum TimePlanMode {
393 DwellOnTime,
395 Bitmap,
397 GridRevisitSleep,
399 Reserved(u8),
401}
402
403impl TimePlanMode {
404 #[must_use]
405 pub fn from_u8(v: u8) -> Self {
407 match v & 0x03 {
408 0 => Self::DwellOnTime,
409 1 => Self::Bitmap,
410 2 => Self::GridRevisitSleep,
411 v => Self::Reserved(v),
412 }
413 }
414
415 #[must_use]
416 pub const fn to_u8(self) -> u8 {
418 match self {
419 Self::DwellOnTime => 0,
420 Self::Bitmap => 1,
421 Self::GridRevisitSleep => 2,
422 Self::Reserved(v) => v,
423 }
424 }
425
426 #[must_use]
427 pub fn name(self) -> &'static str {
429 match self {
430 Self::DwellOnTime => "dwell/on-time",
431 Self::Bitmap => "bitmap",
432 Self::GridRevisitSleep => "grid/revisit/sleep",
433 Self::Reserved(_) => "reserved",
434 }
435 }
436}
437broadcast_common::impl_spec_display!(TimePlanMode, Reserved);
438
439#[derive(Debug, Clone, PartialEq, Eq)]
441#[cfg_attr(feature = "serde", derive(serde::Serialize))]
442#[non_exhaustive]
443pub enum BeamhoppingMode {
444 Mode0 {
446 dwell_duration_base: u64,
448 dwell_duration_ext: u16,
450 on_time_base: u64,
452 on_time_ext: u16,
454 },
455 Mode1 {
457 bit_map_size: u16,
459 current_slot: u16,
461 slot_transmission_on: Vec<bool>,
463 },
464 Mode2 {
466 grid_size_base: u64,
468 grid_size_ext: u16,
470 revisit_duration_base: u64,
472 revisit_duration_ext: u16,
474 sleep_time_base: u64,
476 sleep_time_ext: u16,
478 sleep_duration_base: u64,
480 sleep_duration_ext: u16,
482 },
483 Reserved(Vec<u8>),
486}
487
488#[derive(Debug, Clone, PartialEq, Eq)]
490#[cfg_attr(feature = "serde", derive(serde::Serialize))]
491pub struct BeamhoppingPlan {
492 pub beamhopping_time_plan_id: u32,
494 pub time_plan_mode: TimePlanMode,
496 pub time_of_application_base: u64,
498 pub time_of_application_ext: u16,
500 pub cycle_duration_base: u64,
502 pub cycle_duration_ext: u16,
504 pub mode: BeamhoppingMode,
506}
507
508#[derive(Debug, Clone, PartialEq, Eq)]
510#[cfg_attr(feature = "serde", derive(serde::Serialize))]
511pub struct BeamhoppingTimePlanBody {
512 pub plans: Vec<BeamhoppingPlan>,
514}
515
516#[derive(Debug, Clone, PartialEq, Eq)]
520#[cfg_attr(feature = "serde", derive(serde::Serialize))]
521pub struct UsableTime {
522 pub year: u8,
524 pub day: u16,
526 pub day_fraction: u32,
528}
529
530#[derive(Debug, Clone, PartialEq, Eq)]
532#[cfg_attr(feature = "serde", derive(serde::Serialize))]
533pub struct PositionV3Metadata {
534 pub total_start_time_year: u8,
536 pub total_start_time_day: u16,
538 pub total_start_time_day_fraction: u32,
540 pub total_stop_time_year: u8,
542 pub total_stop_time_day: u16,
544 pub total_stop_time_day_fraction: u32,
546 pub interpolation_flag: bool,
548 pub interpolation_type: InterpolationType,
550 pub interpolation_degree: u8,
552 pub usable_start_time: Option<UsableTime>,
554 pub usable_stop_time: Option<UsableTime>,
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq)]
560#[cfg_attr(feature = "serde", derive(serde::Serialize))]
561#[non_exhaustive]
562pub enum InterpolationType {
563 Reserved0,
565 Linear,
567 Lagrange,
569 Reserved3,
571 Hermite,
573 ReservedOther(u8),
575}
576
577impl InterpolationType {
578 #[must_use]
579 pub fn from_u8(v: u8) -> Self {
581 match v & 0x07 {
582 0 => Self::Reserved0,
583 1 => Self::Linear,
584 2 => Self::Lagrange,
585 3 => Self::Reserved3,
586 4 => Self::Hermite,
587 v => Self::ReservedOther(v),
588 }
589 }
590
591 #[must_use]
592 pub const fn to_u8(self) -> u8 {
594 match self {
595 Self::Reserved0 => 0,
596 Self::Linear => 1,
597 Self::Lagrange => 2,
598 Self::Reserved3 => 3,
599 Self::Hermite => 4,
600 Self::ReservedOther(v) => v,
601 }
602 }
603
604 #[must_use]
605 pub fn name(self) -> &'static str {
607 match self {
608 Self::Reserved0 => "Reserved",
609 Self::Linear => "Linear",
610 Self::Lagrange => "Lagrange",
611 Self::Reserved3 => "Reserved",
612 Self::Hermite => "Hermite",
613 Self::ReservedOther(_) => "Reserved",
614 }
615 }
616}
617broadcast_common::impl_spec_display!(InterpolationType, ReservedOther);
618
619#[derive(Debug, Clone, PartialEq, Eq)]
621#[cfg_attr(feature = "serde", derive(serde::Serialize))]
622pub struct EphemerisAccel {
623 pub ephemeris_x_ddot: u32,
625 pub ephemeris_y_ddot: u32,
627 pub ephemeris_z_ddot: u32,
629}
630
631#[derive(Debug, Clone, PartialEq, Eq)]
633#[cfg_attr(feature = "serde", derive(serde::Serialize))]
634pub struct EphemerisData {
635 pub epoch_year: u8,
637 pub epoch_day: u16,
639 pub epoch_day_fraction: u32,
641 pub ephemeris_x: u32,
643 pub ephemeris_y: u32,
645 pub ephemeris_z: u32,
647 pub ephemeris_x_dot: u32,
649 pub ephemeris_y_dot: u32,
651 pub ephemeris_z_dot: u32,
653 pub acceleration: Option<EphemerisAccel>,
655}
656
657#[derive(Debug, Clone, PartialEq, Eq)]
659#[cfg_attr(feature = "serde", derive(serde::Serialize))]
660pub struct CovarianceData {
661 pub covariance_epoch_year: u8,
663 pub covariance_epoch_day: u16,
665 pub covariance_epoch_day_fraction: u32,
667 pub covariance_elements: [u32; 21],
669}
670
671#[derive(Debug, Clone, PartialEq, Eq)]
673#[cfg_attr(feature = "serde", derive(serde::Serialize))]
674pub struct PositionV3Satellite {
675 pub satellite_id: u32,
677 pub usable_start_time_flag: bool,
679 pub usable_stop_time_flag: bool,
681 pub ephemeris_accel_flag: bool,
683 pub covariance_flag: bool,
685 pub metadata: Option<PositionV3Metadata>,
688 pub ephemeris_data: Vec<EphemerisData>,
690 pub covariance: Option<CovarianceData>,
692}
693
694#[derive(Debug, Clone, PartialEq, Eq)]
696#[cfg_attr(feature = "serde", derive(serde::Serialize))]
697pub struct PositionV3Body {
698 pub oem_version_major: u8,
700 pub oem_version_minor: u8,
702 pub creation_date_year: u8,
704 pub creation_date_day: u16,
706 pub creation_date_day_fraction: u32,
708 pub satellites: Vec<PositionV3Satellite>,
710}
711
712#[derive(Debug, Clone, PartialEq, Eq)]
717#[cfg_attr(feature = "serde", derive(serde::Serialize))]
718#[non_exhaustive]
719pub enum SatBody {
720 PositionV2(PositionV2Body),
722 CellFragment(CellFragmentBody),
724 TimeAssociation(TimeAssociationBody),
726 BeamhoppingTimePlan(BeamhoppingTimePlanBody),
728 PositionV3(PositionV3Body),
730 Raw(Vec<u8>),
732}
733
734fn sat_body_serialized_len(body: &SatBody) -> usize {
735 match body {
736 SatBody::Raw(v) => v.len(),
737 _ => {
738 let mut cap = 4096usize;
745 loop {
746 let mut tmp = vec![0u8; cap];
747 let mut writer = BitWriter::new(&mut tmp);
748 if sat_body_write(body, &mut writer).is_ok() {
749 break writer.bits_written().div_ceil(8);
750 }
751 if cap >= 1 << 20 {
752 break cap; }
754 cap *= 2;
755 }
756 }
757 }
758}
759
760fn sat_body_write(body: &SatBody, w: &mut BitWriter) -> Result<()> {
761 match body {
762 SatBody::PositionV2(b) => {
763 for sat in &b.satellites {
764 w.write_u(24, sat.satellite_id as u64)?;
765 w.write_zero(7)?;
766 match &sat.position {
767 PositionSystem::Orbital {
768 orbital_position,
769 west_east_flag,
770 } => {
771 w.write_u(1, 0)?;
772 w.write_u(16, *orbital_position as u64)?;
773 w.write_u(1, *west_east_flag as u64)?;
774 w.write_zero(7)?;
775 }
776 PositionSystem::Sgp4 {
777 epoch_year,
778 day_of_the_year,
779 day_fraction,
780 mean_motion_first_derivative,
781 mean_motion_second_derivative,
782 drag_term,
783 inclination,
784 right_ascension,
785 eccentricity,
786 argument_of_perigree,
787 mean_anomaly,
788 mean_motion,
789 } => {
790 w.write_u(1, 1)?;
791 w.write_u(8, *epoch_year as u64)?;
792 w.write_u(16, *day_of_the_year as u64)?;
793 w.write_u(32, *day_fraction as u64)?;
794 w.write_u(32, *mean_motion_first_derivative as u64)?;
795 w.write_u(32, *mean_motion_second_derivative as u64)?;
796 w.write_u(32, *drag_term as u64)?;
797 w.write_u(32, *inclination as u64)?;
798 w.write_u(32, *right_ascension as u64)?;
799 w.write_u(32, *eccentricity as u64)?;
800 w.write_u(32, *argument_of_perigree as u64)?;
801 w.write_u(32, *mean_anomaly as u64)?;
802 w.write_u(32, *mean_motion as u64)?;
803 }
804 }
805 }
806 }
807 SatBody::CellFragment(b) => {
808 for frag in &b.fragments {
809 w.write_u(32, frag.cell_fragment_id as u64)?;
810 w.write_u(1, frag.first_occurrence as u64)?;
811 w.write_u(1, frag.last_occurrence as u64)?;
812 if frag.first_occurrence {
813 if let Some(ref c) = frag.center {
814 w.write_zero(4)?;
815 w.write_i(18, c.center_latitude as i64)?;
816 w.write_zero(5)?;
817 w.write_i(19, c.center_longitude as i64)?;
818 w.write_u(24, c.max_distance as u64)?;
819 w.write_zero(6)?;
820 }
821 } else {
822 w.write_zero(4)?;
823 }
824 w.write_u(10, frag.delivery_system_ids.len() as u64)?;
825 for id in &frag.delivery_system_ids {
826 w.write_u(32, *id as u64)?;
827 }
828 w.write_zero(6)?;
829 w.write_u(10, frag.new_delivery_systems.len() as u64)?;
830 for nds in &frag.new_delivery_systems {
831 w.write_u(32, nds.new_delivery_system_id as u64)?;
832 w.write_u(33, nds.time_of_application_base)?;
833 w.write_zero(6)?;
834 w.write_u(9, nds.time_of_application_ext as u64)?;
835 }
836 w.write_zero(6)?;
837 w.write_u(10, frag.obsolescent_delivery_systems.len() as u64)?;
838 for ods in &frag.obsolescent_delivery_systems {
839 w.write_u(32, ods.obsolescent_delivery_system_id as u64)?;
840 w.write_u(33, ods.time_of_obsolescence_base)?;
841 w.write_zero(6)?;
842 w.write_u(9, ods.time_of_obsolescence_ext as u64)?;
843 }
844 }
845 }
846 SatBody::TimeAssociation(b) => {
847 w.write_u(4, b.association_type.to_u8() as u64)?;
848 if b.association_type.to_u8() == 1 {
849 if let Some(ref li) = b.leap_info {
850 w.write_u(1, li.leap59 as u64)?;
851 w.write_u(1, li.leap61 as u64)?;
852 w.write_u(1, li.pastleap59 as u64)?;
853 w.write_u(1, li.pastleap61 as u64)?;
854 } else {
855 w.write_zero(4)?;
856 }
857 } else {
858 w.write_zero(4)?;
859 }
860 w.write_u(33, b.ncr_base)?;
861 w.write_zero(6)?;
862 w.write_u(9, b.ncr_ext as u64)?;
863 w.write_u(64, b.association_timestamp_seconds)?;
864 w.write_u(32, b.association_timestamp_nanoseconds as u64)?;
865 }
866 SatBody::BeamhoppingTimePlan(b) => {
867 for plan in &b.plans {
868 w.write_u(32, plan.beamhopping_time_plan_id as u64)?;
869 w.write_zero(4)?;
870 let mode_bits = match &plan.mode {
871 BeamhoppingMode::Mode0 { .. } => 33 + 6 + 9 + 33 + 6 + 9,
872 BeamhoppingMode::Mode1 { bit_map_size, .. } => {
873 let bm = *bit_map_size as usize;
874 let raw = 1 + 15 + 1 + 15 + bm;
875 raw + pad_to_byte(raw)
876 }
877 BeamhoppingMode::Mode2 { .. } => {
878 33 + 6 + 9 + 33 + 6 + 9 + 33 + 6 + 9 + 33 + 6 + 9
879 }
880 BeamhoppingMode::Reserved(v) => v.len() * 8,
881 };
882 let total_bits_after_length = 8 + 48 + 48 + mode_bits;
883 let plan_length_bytes = total_bits_after_length / 8;
884 w.write_u(12, plan_length_bytes as u64)?;
885 w.write_zero(6)?;
886 w.write_u(2, plan.time_plan_mode.to_u8() as u64)?;
887 w.write_u(33, plan.time_of_application_base)?;
888 w.write_zero(6)?;
889 w.write_u(9, plan.time_of_application_ext as u64)?;
890 w.write_u(33, plan.cycle_duration_base)?;
891 w.write_zero(6)?;
892 w.write_u(9, plan.cycle_duration_ext as u64)?;
893 match &plan.mode {
894 BeamhoppingMode::Mode0 {
895 dwell_duration_base,
896 dwell_duration_ext,
897 on_time_base,
898 on_time_ext,
899 } => {
900 w.write_u(33, *dwell_duration_base)?;
901 w.write_zero(6)?;
902 w.write_u(9, *dwell_duration_ext as u64)?;
903 w.write_u(33, *on_time_base)?;
904 w.write_zero(6)?;
905 w.write_u(9, *on_time_ext as u64)?;
906 }
907 BeamhoppingMode::Mode1 {
908 bit_map_size,
909 current_slot,
910 slot_transmission_on,
911 } => {
912 w.write_zero(1)?;
913 w.write_u(15, *bit_map_size as u64)?;
914 w.write_zero(1)?;
915 w.write_u(15, *current_slot as u64)?;
916 for &on in slot_transmission_on {
917 w.write_u(1, on as u64)?;
918 }
919 let total = 1 + 15 + 1 + 15 + *bit_map_size as usize;
920 for _ in 0..pad_to_byte(total) {
921 w.write_zero(1)?;
922 }
923 }
924 BeamhoppingMode::Mode2 {
925 grid_size_base,
926 grid_size_ext,
927 revisit_duration_base,
928 revisit_duration_ext,
929 sleep_time_base,
930 sleep_time_ext,
931 sleep_duration_base,
932 sleep_duration_ext,
933 } => {
934 w.write_u(33, *grid_size_base)?;
935 w.write_zero(6)?;
936 w.write_u(9, *grid_size_ext as u64)?;
937 w.write_u(33, *revisit_duration_base)?;
938 w.write_zero(6)?;
939 w.write_u(9, *revisit_duration_ext as u64)?;
940 w.write_u(33, *sleep_time_base)?;
941 w.write_zero(6)?;
942 w.write_u(9, *sleep_time_ext as u64)?;
943 w.write_u(33, *sleep_duration_base)?;
944 w.write_zero(6)?;
945 w.write_u(9, *sleep_duration_ext as u64)?;
946 }
947 BeamhoppingMode::Reserved(v) => {
948 for &b in v {
949 w.write_u(8, b as u64)?;
950 }
951 }
952 }
953 }
954 }
955 SatBody::PositionV3(b) => {
956 w.write_u(4, b.oem_version_major as u64)?;
957 w.write_u(4, b.oem_version_minor as u64)?;
958 w.write_u(8, b.creation_date_year as u64)?;
959 w.write_zero(7)?;
960 w.write_u(9, b.creation_date_day as u64)?;
961 w.write_u(32, b.creation_date_day_fraction as u64)?;
962 for sat in &b.satellites {
963 w.write_u(24, sat.satellite_id as u64)?;
964 w.write_zero(3)?;
965 w.write_u(1, u8::from(sat.metadata.is_some()) as u64)?;
966 w.write_u(1, sat.usable_start_time_flag as u64)?;
967 w.write_u(1, sat.usable_stop_time_flag as u64)?;
968 w.write_u(1, sat.ephemeris_accel_flag as u64)?;
969 w.write_u(1, sat.covariance_flag as u64)?;
970 if let Some(ref md) = sat.metadata {
971 w.write_u(8, md.total_start_time_year as u64)?;
972 w.write_zero(7)?;
973 w.write_u(9, md.total_start_time_day as u64)?;
974 w.write_u(32, md.total_start_time_day_fraction as u64)?;
975 w.write_u(8, md.total_stop_time_year as u64)?;
976 w.write_zero(7)?;
977 w.write_u(9, md.total_stop_time_day as u64)?;
978 w.write_u(32, md.total_stop_time_day_fraction as u64)?;
979 w.write_zero(1)?;
980 w.write_u(1, md.interpolation_flag as u64)?;
981 w.write_u(3, md.interpolation_type.to_u8() as u64)?;
982 w.write_u(3, md.interpolation_degree as u64)?;
983 if sat.usable_start_time_flag {
984 if let Some(ref ut) = md.usable_start_time {
985 w.write_u(8, ut.year as u64)?;
986 w.write_zero(7)?;
987 w.write_u(9, ut.day as u64)?;
988 w.write_u(32, ut.day_fraction as u64)?;
989 } else {
990 w.write_zero(8)?;
991 w.write_zero(7)?;
992 w.write_zero(9)?;
993 w.write_zero(32)?;
994 }
995 }
996 if sat.usable_stop_time_flag {
997 if let Some(ref ut) = md.usable_stop_time {
998 w.write_u(8, ut.year as u64)?;
999 w.write_zero(7)?;
1000 w.write_u(9, ut.day as u64)?;
1001 w.write_u(32, ut.day_fraction as u64)?;
1002 } else {
1003 w.write_zero(8)?;
1004 w.write_zero(7)?;
1005 w.write_zero(9)?;
1006 w.write_zero(32)?;
1007 }
1008 }
1009 }
1010 w.write_u(16, sat.ephemeris_data.len() as u64)?;
1011 for ed in &sat.ephemeris_data {
1012 w.write_u(8, ed.epoch_year as u64)?;
1013 w.write_zero(7)?;
1014 w.write_u(9, ed.epoch_day as u64)?;
1015 w.write_u(32, ed.epoch_day_fraction as u64)?;
1016 w.write_u(32, ed.ephemeris_x as u64)?;
1017 w.write_u(32, ed.ephemeris_y as u64)?;
1018 w.write_u(32, ed.ephemeris_z as u64)?;
1019 w.write_u(32, ed.ephemeris_x_dot as u64)?;
1020 w.write_u(32, ed.ephemeris_y_dot as u64)?;
1021 w.write_u(32, ed.ephemeris_z_dot as u64)?;
1022 if sat.ephemeris_accel_flag {
1023 if let Some(ref acc) = ed.acceleration {
1024 w.write_u(32, acc.ephemeris_x_ddot as u64)?;
1025 w.write_u(32, acc.ephemeris_y_ddot as u64)?;
1026 w.write_u(32, acc.ephemeris_z_ddot as u64)?;
1027 } else {
1028 w.write_zero(32)?;
1029 w.write_zero(32)?;
1030 w.write_zero(32)?;
1031 }
1032 }
1033 }
1034 if sat.covariance_flag {
1035 if let Some(ref cov) = sat.covariance {
1036 w.write_u(8, cov.covariance_epoch_year as u64)?;
1037 w.write_zero(7)?;
1038 w.write_u(9, cov.covariance_epoch_day as u64)?;
1039 w.write_u(32, cov.covariance_epoch_day_fraction as u64)?;
1040 for elem in &cov.covariance_elements {
1041 w.write_u(32, *elem as u64)?;
1042 }
1043 } else {
1044 w.write_zero(8)?;
1045 w.write_zero(7)?;
1046 w.write_zero(9)?;
1047 w.write_zero(32)?;
1048 for _ in 0..21 {
1049 w.write_zero(32)?;
1050 }
1051 }
1052 }
1053 }
1054 }
1055 SatBody::Raw(_) => {}
1056 }
1057 Ok(())
1058}
1059
1060fn sat_body_parse(sat_table_id: u8, data: &[u8]) -> Result<SatBody> {
1061 if data.is_empty() && sat_table_id <= 4 {
1062 return Ok(match sat_table_id {
1063 0 => SatBody::PositionV2(PositionV2Body {
1064 satellites: Vec::new(),
1065 }),
1066 1 => SatBody::CellFragment(CellFragmentBody {
1067 fragments: Vec::new(),
1068 }),
1069 3 => SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody { plans: Vec::new() }),
1070 _ => {
1071 return Err(Error::BufferTooShort {
1072 need: 1,
1073 have: 0,
1074 what: "SatSection body (non-loop type requires data)",
1075 });
1076 }
1077 });
1078 }
1079 let mut r = BitReader::new(data);
1080 match sat_table_id {
1081 0 => {
1082 let mut satellites = Vec::new();
1083 while r.remaining_bits() > 24 + 7 {
1084 let satellite_id = r.read_u(24)? as u32;
1085 r.skip(7)?;
1086 let position_system = r.read_u(1)?;
1087 let position = if position_system == 0 {
1088 const ORBITAL_BITS: usize = 16 + 1 + 7;
1089 if r.remaining_bits() < ORBITAL_BITS {
1090 return Err(Error::BufferTooShort {
1091 need: ORBITAL_BITS,
1092 have: r.remaining_bits(),
1093 what: "SatSection PositionV2 Orbital fields",
1094 });
1095 }
1096 let orbital_position = r.read_u(16)? as u16;
1097 let west_east_flag = r.read_u(1)? != 0;
1098 r.skip(7)?;
1099 PositionSystem::Orbital {
1100 orbital_position,
1101 west_east_flag,
1102 }
1103 } else {
1104 const SGP4_BITS: usize = 8 + 16 + 32 * 10;
1105 if r.remaining_bits() < SGP4_BITS {
1106 return Err(Error::BufferTooShort {
1107 need: SGP4_BITS,
1108 have: r.remaining_bits(),
1109 what: "SatSection PositionV2 SGP4 fields",
1110 });
1111 }
1112 let epoch_year = r.read_u(8)? as u8;
1113 let day_of_the_year = r.read_u(16)? as u16;
1114 let day_fraction = r.read_u(32)? as u32;
1115 let mean_motion_first_derivative = r.read_u(32)? as u32;
1116 let mean_motion_second_derivative = r.read_u(32)? as u32;
1117 let drag_term = r.read_u(32)? as u32;
1118 let inclination = r.read_u(32)? as u32;
1119 let right_ascension = r.read_u(32)? as u32;
1120 let eccentricity = r.read_u(32)? as u32;
1121 let argument_of_perigree = r.read_u(32)? as u32;
1122 let mean_anomaly = r.read_u(32)? as u32;
1123 let mean_motion = r.read_u(32)? as u32;
1124 PositionSystem::Sgp4 {
1125 epoch_year,
1126 day_of_the_year,
1127 day_fraction,
1128 mean_motion_first_derivative,
1129 mean_motion_second_derivative,
1130 drag_term,
1131 inclination,
1132 right_ascension,
1133 eccentricity,
1134 argument_of_perigree,
1135 mean_anomaly,
1136 mean_motion,
1137 }
1138 };
1139 satellites.push(PositionV2Satellite {
1140 satellite_id,
1141 position,
1142 });
1143 }
1144 Ok(SatBody::PositionV2(PositionV2Body { satellites }))
1145 }
1146 1 => {
1147 let mut fragments = Vec::new();
1148 while r.remaining_bits() >= 32 + 2 {
1149 let cell_fragment_id = r.read_u(32)? as u32;
1150 let first_occurrence = r.read_u(1)? != 0;
1151 let last_occurrence = r.read_u(1)? != 0;
1152 let center = if first_occurrence {
1153 const CENTER_BITS: usize = 4 + 18 + 5 + 19 + 24 + 6;
1154 if r.remaining_bits() < CENTER_BITS {
1155 return Err(Error::BufferTooShort {
1156 need: CENTER_BITS,
1157 have: r.remaining_bits(),
1158 what: "SatSection CellFragment center",
1159 });
1160 }
1161 r.skip(4)?;
1162 let center_latitude = r.read_i(18)? as i32;
1163 r.skip(5)?;
1164 let center_longitude = r.read_i(19)? as i32;
1165 let max_distance = r.read_u(24)? as u32;
1166 r.skip(6)?;
1167 Some(CellCenter {
1168 center_latitude,
1169 center_longitude,
1170 max_distance,
1171 })
1172 } else {
1173 r.skip(4)?;
1174 None
1175 };
1176 let dsid_count = r.read_u(10)? as usize;
1177 if r.remaining_bits() < dsid_count * 32 {
1178 return Err(Error::BufferTooShort {
1179 need: dsid_count * 32,
1180 have: r.remaining_bits(),
1181 what: "SatSection CellFragment delivery_system_ids",
1182 });
1183 }
1184 let mut delivery_system_ids =
1185 Vec::with_capacity(dsid_count.min(r.remaining_bits() / 32));
1186 for _ in 0..dsid_count {
1187 delivery_system_ids.push(r.read_u(32)? as u32);
1188 }
1189 r.skip(6)?;
1190 let nds_count = r.read_u(10)? as usize;
1191 const NDS_ENTRY_BITS: usize = 32 + 33 + 6 + 9;
1192 if r.remaining_bits() < nds_count * NDS_ENTRY_BITS {
1193 return Err(Error::BufferTooShort {
1194 need: nds_count * NDS_ENTRY_BITS,
1195 have: r.remaining_bits(),
1196 what: "SatSection CellFragment new_delivery_systems",
1197 });
1198 }
1199 let mut new_delivery_systems =
1200 Vec::with_capacity(nds_count.min(r.remaining_bits() / NDS_ENTRY_BITS));
1201 for _ in 0..nds_count {
1202 let new_delivery_system_id = r.read_u(32)? as u32;
1203 let time_of_application_base = r.read_u(33)?;
1204 r.skip(6)?;
1205 let time_of_application_ext = r.read_u(9)? as u16;
1206 new_delivery_systems.push(NewDeliverySystem {
1207 new_delivery_system_id,
1208 time_of_application_base,
1209 time_of_application_ext,
1210 });
1211 }
1212 r.skip(6)?;
1213 let ods_count = r.read_u(10)? as usize;
1214 if r.remaining_bits() < ods_count * NDS_ENTRY_BITS {
1215 return Err(Error::BufferTooShort {
1216 need: ods_count * NDS_ENTRY_BITS,
1217 have: r.remaining_bits(),
1218 what: "SatSection CellFragment obsolescent_delivery_systems",
1219 });
1220 }
1221 let mut obsolescent_delivery_systems =
1222 Vec::with_capacity(ods_count.min(r.remaining_bits() / NDS_ENTRY_BITS));
1223 for _ in 0..ods_count {
1224 let obsolescent_delivery_system_id = r.read_u(32)? as u32;
1225 let time_of_obsolescence_base = r.read_u(33)?;
1226 r.skip(6)?;
1227 let time_of_obsolescence_ext = r.read_u(9)? as u16;
1228 obsolescent_delivery_systems.push(ObsolescentDeliverySystem {
1229 obsolescent_delivery_system_id,
1230 time_of_obsolescence_base,
1231 time_of_obsolescence_ext,
1232 });
1233 }
1234 fragments.push(CellFragment {
1235 cell_fragment_id,
1236 first_occurrence,
1237 last_occurrence,
1238 center,
1239 delivery_system_ids,
1240 new_delivery_systems,
1241 obsolescent_delivery_systems,
1242 });
1243 }
1244 Ok(SatBody::CellFragment(CellFragmentBody { fragments }))
1245 }
1246 2 => {
1247 const TIME_ASSOC_MIN_BITS: usize = 4 + 4 + 33 + 6 + 9 + 64 + 32;
1248 if r.remaining_bits() < TIME_ASSOC_MIN_BITS {
1249 return Err(Error::BufferTooShort {
1250 need: TIME_ASSOC_MIN_BITS,
1251 have: r.remaining_bits(),
1252 what: "SatSection TimeAssociation body",
1253 });
1254 }
1255 let association_type = AssociationType::from_u8(r.read_u(4)? as u8);
1256 let leap_info = if association_type.to_u8() == 1 {
1257 Some(LeapInfo {
1258 leap59: r.read_u(1)? != 0,
1259 leap61: r.read_u(1)? != 0,
1260 pastleap59: r.read_u(1)? != 0,
1261 pastleap61: r.read_u(1)? != 0,
1262 })
1263 } else {
1264 r.skip(4)?;
1265 None
1266 };
1267 let ncr_base = r.read_u(33)?;
1268 r.skip(6)?;
1269 let ncr_ext = r.read_u(9)? as u16;
1270 let association_timestamp_seconds = r.read_u(64)?;
1271 let association_timestamp_nanoseconds = r.read_u(32)? as u32;
1272 Ok(SatBody::TimeAssociation(TimeAssociationBody {
1273 association_type,
1274 leap_info,
1275 ncr_base,
1276 ncr_ext,
1277 association_timestamp_seconds,
1278 association_timestamp_nanoseconds,
1279 }))
1280 }
1281 3 => {
1282 let mut plans = Vec::new();
1283 while r.remaining_bits() >= 32 + 4 + 12 {
1284 let beamhopping_time_plan_id = r.read_u(32)? as u32;
1285 r.skip(4)?;
1286 let plan_length = r.read_u(12)? as usize;
1287 let plan_end_bits = r.bits_consumed() + plan_length * 8;
1288 r.skip(6)?;
1289 let time_plan_mode = TimePlanMode::from_u8(r.read_u(2)? as u8);
1290 let time_of_application_base = r.read_u(33)?;
1291 r.skip(6)?;
1292 let time_of_application_ext = r.read_u(9)? as u16;
1293 let cycle_duration_base = r.read_u(33)?;
1294 r.skip(6)?;
1295 let cycle_duration_ext = r.read_u(9)? as u16;
1296 let mode = match time_plan_mode {
1297 TimePlanMode::DwellOnTime => {
1298 const MODE0_BITS: usize = 33 + 6 + 9 + 33 + 6 + 9;
1299 if r.remaining_bits() < MODE0_BITS {
1300 return Err(Error::BufferTooShort {
1301 need: MODE0_BITS,
1302 have: r.remaining_bits(),
1303 what: "SatSection Beamhopping Mode0",
1304 });
1305 }
1306 let dwell_duration_base = r.read_u(33)?;
1307 r.skip(6)?;
1308 let dwell_duration_ext = r.read_u(9)? as u16;
1309 let on_time_base = r.read_u(33)?;
1310 r.skip(6)?;
1311 let on_time_ext = r.read_u(9)? as u16;
1312 BeamhoppingMode::Mode0 {
1313 dwell_duration_base,
1314 dwell_duration_ext,
1315 on_time_base,
1316 on_time_ext,
1317 }
1318 }
1319 TimePlanMode::Bitmap => {
1320 const MODE1_HEADER_BITS: usize = 1 + 15 + 1 + 15;
1321 if r.remaining_bits() < MODE1_HEADER_BITS {
1322 return Err(Error::BufferTooShort {
1323 need: MODE1_HEADER_BITS,
1324 have: r.remaining_bits(),
1325 what: "SatSection Beamhopping Mode1 header",
1326 });
1327 }
1328 r.skip(1)?;
1329 let bit_map_size = r.read_u(15)? as u16;
1330 r.skip(1)?;
1331 let current_slot = r.read_u(15)? as u16;
1332 if r.remaining_bits() < bit_map_size as usize {
1333 return Err(Error::BufferTooShort {
1334 need: bit_map_size as usize,
1335 have: r.remaining_bits(),
1336 what: "SatSection Beamhopping Mode1 bitmap",
1337 });
1338 }
1339 let mut slot_transmission_on =
1340 Vec::with_capacity((bit_map_size as usize).min(r.remaining_bits()));
1341 for _ in 0..bit_map_size {
1342 slot_transmission_on.push(r.read_u(1)? != 0);
1343 }
1344 let total = 1 + 15 + 1 + 15 + bit_map_size as usize;
1345 r.skip(pad_to_byte(total) as u8)?;
1346 BeamhoppingMode::Mode1 {
1347 bit_map_size,
1348 current_slot,
1349 slot_transmission_on,
1350 }
1351 }
1352 TimePlanMode::GridRevisitSleep => {
1353 const MODE2_BITS: usize = 33 + 6 + 9 + 33 + 6 + 9 + 33 + 6 + 9 + 33 + 6 + 9;
1354 if r.remaining_bits() < MODE2_BITS {
1355 return Err(Error::BufferTooShort {
1356 need: MODE2_BITS,
1357 have: r.remaining_bits(),
1358 what: "SatSection Beamhopping Mode2",
1359 });
1360 }
1361 let grid_size_base = r.read_u(33)?;
1362 r.skip(6)?;
1363 let grid_size_ext = r.read_u(9)? as u16;
1364 let revisit_duration_base = r.read_u(33)?;
1365 r.skip(6)?;
1366 let revisit_duration_ext = r.read_u(9)? as u16;
1367 let sleep_time_base = r.read_u(33)?;
1368 r.skip(6)?;
1369 let sleep_time_ext = r.read_u(9)? as u16;
1370 let sleep_duration_base = r.read_u(33)?;
1371 r.skip(6)?;
1372 let sleep_duration_ext = r.read_u(9)? as u16;
1373 BeamhoppingMode::Mode2 {
1374 grid_size_base,
1375 grid_size_ext,
1376 revisit_duration_base,
1377 revisit_duration_ext,
1378 sleep_time_base,
1379 sleep_time_ext,
1380 sleep_duration_base,
1381 sleep_duration_ext,
1382 }
1383 }
1384 _ => {
1385 let start_byte = r.bits_consumed().div_ceil(8);
1386 let end_byte = plan_end_bits / 8;
1387 let raw = if start_byte < end_byte && end_byte <= data.len() {
1388 data[start_byte..end_byte].to_vec()
1389 } else {
1390 Vec::new()
1391 };
1392 BeamhoppingMode::Reserved(raw)
1393 }
1394 };
1395 r.bit_pos = plan_end_bits;
1396 plans.push(BeamhoppingPlan {
1397 beamhopping_time_plan_id,
1398 time_plan_mode,
1399 time_of_application_base,
1400 time_of_application_ext,
1401 cycle_duration_base,
1402 cycle_duration_ext,
1403 mode,
1404 });
1405 }
1406 Ok(SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
1407 plans,
1408 }))
1409 }
1410 4 => {
1411 const POS_V3_HEADER_BITS: usize = 4 + 4 + 8 + 7 + 9 + 32;
1412 if r.remaining_bits() < POS_V3_HEADER_BITS {
1413 return Err(Error::BufferTooShort {
1414 need: POS_V3_HEADER_BITS,
1415 have: r.remaining_bits(),
1416 what: "SatSection PositionV3 body header",
1417 });
1418 }
1419 let oem_version_major = r.read_u(4)? as u8;
1420 let oem_version_minor = r.read_u(4)? as u8;
1421 let creation_date_year = r.read_u(8)? as u8;
1422 r.skip(7)?;
1423 let creation_date_day = r.read_u(9)? as u16;
1424 let creation_date_day_fraction = r.read_u(32)? as u32;
1425 let mut satellites = Vec::new();
1426 while r.remaining_bits() >= 24 + 3 + 5 {
1427 let satellite_id = r.read_u(24)? as u32;
1428 r.skip(3)?;
1429 let metadata_flag = r.read_u(1)? != 0;
1430 let usable_start_time_flag = r.read_u(1)? != 0;
1431 let usable_stop_time_flag = r.read_u(1)? != 0;
1432 let ephemeris_accel_flag = r.read_u(1)? != 0;
1433 let covariance_flag = r.read_u(1)? != 0;
1434 let metadata = if metadata_flag {
1435 const METADATA_FIXED_BITS: usize =
1436 8 + 7 + 9 + 32 + 8 + 7 + 9 + 32 + 1 + 1 + 3 + 3;
1437 if r.remaining_bits() < METADATA_FIXED_BITS {
1438 return Err(Error::BufferTooShort {
1439 need: METADATA_FIXED_BITS,
1440 have: r.remaining_bits(),
1441 what: "SatSection PositionV3 metadata",
1442 });
1443 }
1444 let total_start_time_year = r.read_u(8)? as u8;
1445 r.skip(7)?;
1446 let total_start_time_day = r.read_u(9)? as u16;
1447 let total_start_time_day_fraction = r.read_u(32)? as u32;
1448 let total_stop_time_year = r.read_u(8)? as u8;
1449 r.skip(7)?;
1450 let total_stop_time_day = r.read_u(9)? as u16;
1451 let total_stop_time_day_fraction = r.read_u(32)? as u32;
1452 r.skip(1)?;
1453 let interpolation_flag = r.read_u(1)? != 0;
1454 let interpolation_type = InterpolationType::from_u8(r.read_u(3)? as u8);
1455 let interpolation_degree = r.read_u(3)? as u8;
1456 let usable_start_time = if usable_start_time_flag {
1457 const USABLE_TIME_BITS: usize = 8 + 7 + 9 + 32;
1458 if r.remaining_bits() < USABLE_TIME_BITS {
1459 return Err(Error::BufferTooShort {
1460 need: USABLE_TIME_BITS,
1461 have: r.remaining_bits(),
1462 what: "SatSection PositionV3 usable_start_time",
1463 });
1464 }
1465 let year = r.read_u(8)? as u8;
1466 r.skip(7)?;
1467 let day = r.read_u(9)? as u16;
1468 let day_fraction = r.read_u(32)? as u32;
1469 Some(UsableTime {
1470 year,
1471 day,
1472 day_fraction,
1473 })
1474 } else {
1475 None
1476 };
1477 let usable_stop_time = if usable_stop_time_flag {
1478 const USABLE_TIME_BITS: usize = 8 + 7 + 9 + 32;
1479 if r.remaining_bits() < USABLE_TIME_BITS {
1480 return Err(Error::BufferTooShort {
1481 need: USABLE_TIME_BITS,
1482 have: r.remaining_bits(),
1483 what: "SatSection PositionV3 usable_stop_time",
1484 });
1485 }
1486 let year = r.read_u(8)? as u8;
1487 r.skip(7)?;
1488 let day = r.read_u(9)? as u16;
1489 let day_fraction = r.read_u(32)? as u32;
1490 Some(UsableTime {
1491 year,
1492 day,
1493 day_fraction,
1494 })
1495 } else {
1496 None
1497 };
1498 Some(PositionV3Metadata {
1499 total_start_time_year,
1500 total_start_time_day,
1501 total_start_time_day_fraction,
1502 total_stop_time_year,
1503 total_stop_time_day,
1504 total_stop_time_day_fraction,
1505 interpolation_flag,
1506 interpolation_type,
1507 interpolation_degree,
1508 usable_start_time,
1509 usable_stop_time,
1510 })
1511 } else {
1512 None
1513 };
1514 let ephemeris_data_count = r.read_u(16)? as u16;
1515 let entry_bits: usize =
1516 8 + 7 + 9 + 32 + 32 * 6 + if ephemeris_accel_flag { 32 * 3 } else { 0 };
1517 let mut ephemeris_data = Vec::with_capacity(
1518 (ephemeris_data_count as usize)
1519 .min(r.remaining_bits().saturating_sub(entry_bits) / entry_bits + 1),
1520 );
1521 for _ in 0..ephemeris_data_count {
1522 if r.remaining_bits() < entry_bits {
1523 return Err(Error::BufferTooShort {
1524 need: entry_bits,
1525 have: r.remaining_bits(),
1526 what: "SatSection PositionV3 ephemeris_data entry",
1527 });
1528 }
1529 let epoch_year = r.read_u(8)? as u8;
1530 r.skip(7)?;
1531 let epoch_day = r.read_u(9)? as u16;
1532 let epoch_day_fraction = r.read_u(32)? as u32;
1533 let ephemeris_x = r.read_u(32)? as u32;
1534 let ephemeris_y = r.read_u(32)? as u32;
1535 let ephemeris_z = r.read_u(32)? as u32;
1536 let ephemeris_x_dot = r.read_u(32)? as u32;
1537 let ephemeris_y_dot = r.read_u(32)? as u32;
1538 let ephemeris_z_dot = r.read_u(32)? as u32;
1539 let acceleration = if ephemeris_accel_flag {
1540 Some(EphemerisAccel {
1541 ephemeris_x_ddot: r.read_u(32)? as u32,
1542 ephemeris_y_ddot: r.read_u(32)? as u32,
1543 ephemeris_z_ddot: r.read_u(32)? as u32,
1544 })
1545 } else {
1546 None
1547 };
1548 ephemeris_data.push(EphemerisData {
1549 epoch_year,
1550 epoch_day,
1551 epoch_day_fraction,
1552 ephemeris_x,
1553 ephemeris_y,
1554 ephemeris_z,
1555 ephemeris_x_dot,
1556 ephemeris_y_dot,
1557 ephemeris_z_dot,
1558 acceleration,
1559 });
1560 }
1561 let covariance = if covariance_flag {
1562 const COV_HEADER_BITS: usize = 8 + 7 + 9 + 32;
1563 const COV_ELEMENTS_BITS: usize = 21 * 32;
1564 const COV_BITS: usize = COV_HEADER_BITS + COV_ELEMENTS_BITS;
1565 if r.remaining_bits() < COV_BITS {
1566 return Err(Error::BufferTooShort {
1567 need: COV_BITS,
1568 have: r.remaining_bits(),
1569 what: "SatSection PositionV3 covariance",
1570 });
1571 }
1572 let covariance_epoch_year = r.read_u(8)? as u8;
1573 r.skip(7)?;
1574 let covariance_epoch_day = r.read_u(9)? as u16;
1575 let covariance_epoch_day_fraction = r.read_u(32)? as u32;
1576 let mut covariance_elements = [0u32; 21];
1577 for elem in &mut covariance_elements {
1578 *elem = r.read_u(32)? as u32;
1579 }
1580 Some(CovarianceData {
1581 covariance_epoch_year,
1582 covariance_epoch_day,
1583 covariance_epoch_day_fraction,
1584 covariance_elements,
1585 })
1586 } else {
1587 None
1588 };
1589 satellites.push(PositionV3Satellite {
1590 satellite_id,
1591 usable_start_time_flag,
1592 usable_stop_time_flag,
1593 ephemeris_accel_flag,
1594 covariance_flag,
1595 metadata,
1596 ephemeris_data,
1597 covariance,
1598 });
1599 }
1600 Ok(SatBody::PositionV3(PositionV3Body {
1601 oem_version_major,
1602 oem_version_minor,
1603 creation_date_year,
1604 creation_date_day,
1605 creation_date_day_fraction,
1606 satellites,
1607 }))
1608 }
1609 _ => Ok(SatBody::Raw(data.to_vec())),
1610 }
1611}
1612
1613#[derive(Debug, Clone, PartialEq, Eq)]
1621#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1622pub struct SatSection {
1623 pub satellite_table_id: u8,
1625 pub private_indicator: bool,
1627 pub table_count: u16,
1629 pub version_number: u8,
1631 pub current_next_indicator: bool,
1633 pub section_number: u8,
1635 pub last_section_number: u8,
1637 pub body: SatBody,
1639}
1640
1641impl SatSection {
1642 #[must_use]
1644 pub fn kind(&self) -> Option<SatTableId> {
1645 SatTableId::try_from(self.satellite_table_id).ok()
1646 }
1647}
1648
1649impl<'a> Parse<'a> for SatSection {
1650 type Error = crate::error::Error;
1651 fn parse(bytes: &'a [u8]) -> Result<Self> {
1652 let min_len = HEADER_LEN + CRC_LEN;
1653 if bytes.len() < min_len {
1654 return Err(Error::BufferTooShort {
1655 need: min_len,
1656 have: bytes.len(),
1657 what: "SatSection",
1658 });
1659 }
1660 if bytes[0] != TABLE_ID {
1661 return Err(Error::UnexpectedTableId {
1662 table_id: bytes[0],
1663 what: "SatSection",
1664 expected: &[TABLE_ID],
1665 });
1666 }
1667 let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
1668 let total = super::check_section_length(
1669 bytes.len(),
1670 SECTION_LENGTH_PREFIX,
1671 section_length,
1672 HEADER_LEN + CRC_LEN,
1673 )?;
1674 let satellite_table_id = bytes[3] >> 2;
1675 let private_indicator = (bytes[1] & 0x40) != 0;
1676 let table_count = (((bytes[3] & 0x03) as u16) << 8) | bytes[4] as u16;
1677 let version_number = (bytes[5] >> 1) & 0x1F;
1678 let current_next_indicator = bytes[5] & 0x01 != 0;
1679 let section_number = bytes[6];
1680 let last_section_number = bytes[7];
1681 let body_data = &bytes[HEADER_LEN..total - CRC_LEN];
1682 let body = sat_body_parse(satellite_table_id, body_data)?;
1683 Ok(SatSection {
1684 satellite_table_id,
1685 private_indicator,
1686 table_count,
1687 version_number,
1688 current_next_indicator,
1689 section_number,
1690 last_section_number,
1691 body,
1692 })
1693 }
1694}
1695
1696impl Serialize for SatSection {
1697 type Error = crate::error::Error;
1698 fn serialized_len(&self) -> usize {
1699 HEADER_LEN + sat_body_serialized_len(&self.body) + CRC_LEN
1700 }
1701 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
1702 let len = self.serialized_len();
1703 if buf.len() < len {
1704 return Err(Error::OutputBufferTooSmall {
1705 need: len,
1706 have: buf.len(),
1707 });
1708 }
1709 let section_length_usize = len - SECTION_LENGTH_PREFIX;
1710 if section_length_usize > 0x0FFF {
1711 return Err(Error::SectionLengthOverflow {
1712 declared: section_length_usize,
1713 available: 0x0FFF,
1714 });
1715 }
1716 let section_length = section_length_usize as u16;
1717 if let SatBody::PositionV3(ref v3) = self.body {
1718 for sat in &v3.satellites {
1719 if sat.ephemeris_data.len() > u16::MAX as usize {
1720 return Err(Error::SectionLengthOverflow {
1721 declared: sat.ephemeris_data.len(),
1722 available: u16::MAX as usize,
1723 });
1724 }
1725 }
1726 }
1727 buf[0] = TABLE_ID;
1728 buf[1] = super::SECTION_B1_SSI
1729 | (u8::from(self.private_indicator) << 6)
1730 | super::SECTION_B1_RESERVED_HI
1731 | ((section_length >> 8) as u8 & 0x0F);
1732 buf[2] = (section_length & 0xFF) as u8;
1733 buf[3] = (self.satellite_table_id << 2) | ((self.table_count >> 8) as u8 & 0x03);
1734 buf[4] = (self.table_count & 0xFF) as u8;
1735 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
1736 buf[6] = self.section_number;
1737 buf[7] = self.last_section_number;
1738 buf[8] = 0x00;
1739 let body_start = HEADER_LEN;
1740 match &self.body {
1741 SatBody::Raw(v) => {
1742 buf[body_start..body_start + v.len()].copy_from_slice(v);
1743 }
1744 _ => {
1745 let body_byte_len = sat_body_serialized_len(&self.body);
1746 for b in &mut buf[body_start..body_start + body_byte_len] {
1747 *b = 0;
1748 }
1749 let mut writer = BitWriter::new(&mut buf[body_start..body_start + body_byte_len]);
1750 sat_body_write(&self.body, &mut writer)?;
1751 }
1752 }
1753 let body_end = HEADER_LEN + sat_body_serialized_len(&self.body);
1754 let crc = broadcast_common::crc32_mpeg2::compute(&buf[..body_end]);
1755 buf[body_end..len].copy_from_slice(&crc.to_be_bytes());
1756 Ok(len)
1757 }
1758}
1759impl crate::traits::TableDef<'_> for SatSection {
1760 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
1761 const NAME: &'static str = "SATELLITE_ACCESS";
1762}
1763
1764#[cfg(test)]
1765mod tests {
1766 use super::*;
1767
1768 fn build_sat(stid: u8, table_count: u16, body: &SatBody) -> Vec<u8> {
1769 let sat = SatSection {
1770 satellite_table_id: stid,
1771 private_indicator: true,
1772 table_count,
1773 version_number: 5,
1774 current_next_indicator: true,
1775 section_number: 0,
1776 last_section_number: 0,
1777 body: body.clone(),
1778 };
1779 let mut buf = vec![0u8; sat.serialized_len()];
1780 sat.serialize_into(&mut buf).unwrap();
1781 buf
1782 }
1783
1784 fn build_sat_private_indicator_false(stid: u8, body: &SatBody) -> Vec<u8> {
1785 let sat = SatSection {
1786 satellite_table_id: stid,
1787 private_indicator: false,
1788 table_count: 0,
1789 version_number: 5,
1790 current_next_indicator: true,
1791 section_number: 0,
1792 last_section_number: 0,
1793 body: body.clone(),
1794 };
1795 let mut buf = vec![0u8; sat.serialized_len()];
1796 sat.serialize_into(&mut buf).unwrap();
1797 buf
1798 }
1799
1800 #[test]
1801 fn parse_raw_body() {
1802 let body_data = [0xAA, 0xBB, 0xCC, 0xDD];
1803 let bytes = build_sat(7, 0, &SatBody::Raw(body_data.to_vec()));
1804 let sat = SatSection::parse(&bytes).unwrap();
1805 assert_eq!(sat.satellite_table_id, 7);
1806 assert_eq!(sat.kind(), None);
1807 assert_eq!(sat.body, SatBody::Raw(body_data.to_vec()));
1808 }
1809
1810 #[test]
1811 fn private_indicator_false_round_trip() {
1812 let body = SatBody::TimeAssociation(TimeAssociationBody {
1813 association_type: AssociationType::UtcWithoutLeap,
1814 leap_info: None,
1815 ncr_base: 0,
1816 ncr_ext: 0,
1817 association_timestamp_seconds: 0,
1818 association_timestamp_nanoseconds: 0,
1819 });
1820 let bytes = build_sat_private_indicator_false(2, &body);
1821 let sat = SatSection::parse(&bytes).unwrap();
1822 assert!(!sat.private_indicator);
1823 let mut buf2 = vec![0u8; sat.serialized_len()];
1824 sat.serialize_into(&mut buf2).unwrap();
1825 assert_eq!(
1826 bytes, buf2,
1827 "byte-exact round-trip with private_indicator=false"
1828 );
1829 }
1830
1831 #[test]
1832 fn parse_position_v3_discriminant() {
1833 let body = SatBody::PositionV3(PositionV3Body {
1834 oem_version_major: 1,
1835 oem_version_minor: 0,
1836 creation_date_year: 25,
1837 creation_date_day: 100,
1838 creation_date_day_fraction: 0,
1839 satellites: Vec::new(),
1840 });
1841 let bytes = build_sat(4, 0x1A3, &body);
1842 let sat = SatSection::parse(&bytes).unwrap();
1843 assert_eq!(sat.satellite_table_id, 4);
1844 assert_eq!(sat.kind(), Some(SatTableId::PositionV3));
1845 assert_eq!(sat.table_count, 0x1A3);
1846 }
1847
1848 #[test]
1849 fn time_association_round_trip() {
1850 let body = SatBody::TimeAssociation(TimeAssociationBody {
1851 association_type: AssociationType::UtcWithLeap,
1852 leap_info: Some(LeapInfo {
1853 leap59: true,
1854 leap61: false,
1855 pastleap59: false,
1856 pastleap61: true,
1857 }),
1858 ncr_base: 0x0000_AAAA_AAAA_u64,
1859 ncr_ext: 0x1AA,
1860 association_timestamp_seconds: 0x12345678_9ABCDEF0,
1861 association_timestamp_nanoseconds: 0xDEADBEEF,
1862 });
1863 let bytes = build_sat(2, 0, &body);
1864 let sat = SatSection::parse(&bytes).unwrap();
1865 match &sat.body {
1866 SatBody::TimeAssociation(ta) => {
1867 assert_eq!(ta.association_type, AssociationType::UtcWithLeap);
1868 let li = ta.leap_info.as_ref().unwrap();
1869 assert!(li.leap59);
1870 assert!(!li.leap61);
1871 assert!(!li.pastleap59);
1872 assert!(li.pastleap61);
1873 assert_eq!(ta.ncr_base, 0x0000_AAAA_AAAA);
1874 assert_eq!(ta.ncr_ext, 0x1AA);
1875 assert_eq!(ta.association_timestamp_seconds, 0x12345678_9ABCDEF0);
1876 assert_eq!(ta.association_timestamp_nanoseconds, 0xDEADBEEF);
1877 }
1878 other => panic!("expected TimeAssociation, got {other:?}"),
1879 }
1880 let mut buf2 = vec![0u8; sat.serialized_len()];
1881 sat.serialize_into(&mut buf2).unwrap();
1882 assert_eq!(bytes, buf2, "byte-exact re-serialize");
1883 }
1884
1885 #[test]
1886 fn position_v2_orbital_round_trip() {
1887 let body = SatBody::PositionV2(PositionV2Body {
1888 satellites: vec![PositionV2Satellite {
1889 satellite_id: 0x123456,
1890 position: PositionSystem::Orbital {
1891 orbital_position: 0x1234,
1892 west_east_flag: true,
1893 },
1894 }],
1895 });
1896 let bytes = build_sat(0, 0, &body);
1897 let sat = SatSection::parse(&bytes).unwrap();
1898 match &sat.body {
1899 SatBody::PositionV2(pv2) => {
1900 assert_eq!(pv2.satellites.len(), 1);
1901 assert_eq!(pv2.satellites[0].satellite_id, 0x123456);
1902 match &pv2.satellites[0].position {
1903 PositionSystem::Orbital {
1904 orbital_position,
1905 west_east_flag,
1906 } => {
1907 assert_eq!(*orbital_position, 0x1234);
1908 assert!(*west_east_flag);
1909 }
1910 other => panic!("expected Orbital, got {other:?}"),
1911 }
1912 }
1913 other => panic!("expected PositionV2, got {other:?}"),
1914 }
1915 let mut buf2 = vec![0u8; sat.serialized_len()];
1916 sat.serialize_into(&mut buf2).unwrap();
1917 assert_eq!(bytes, buf2, "byte-exact re-serialize");
1918 }
1919
1920 #[test]
1921 fn beamhopping_mode0_round_trip() {
1922 let body = SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
1923 plans: vec![BeamhoppingPlan {
1924 beamhopping_time_plan_id: 0xDEADBEEF,
1925 time_plan_mode: TimePlanMode::DwellOnTime,
1926 time_of_application_base: 0x0000_AAAA_AAAA,
1927 time_of_application_ext: 0x100,
1928 cycle_duration_base: 0x0000_5555_5555,
1929 cycle_duration_ext: 0x080,
1930 mode: BeamhoppingMode::Mode0 {
1931 dwell_duration_base: 0x0000_1111_1111,
1932 dwell_duration_ext: 0x111,
1933 on_time_base: 0x0000_2222_2222,
1934 on_time_ext: 0x222,
1935 },
1936 }],
1937 });
1938 let bytes = build_sat(3, 0, &body);
1939 let sat = SatSection::parse(&bytes).unwrap();
1940 match &sat.body {
1941 SatBody::BeamhoppingTimePlan(bhp) => {
1942 assert_eq!(bhp.plans.len(), 1);
1943 assert_eq!(bhp.plans[0].beamhopping_time_plan_id, 0xDEADBEEF);
1944 assert_eq!(bhp.plans[0].time_plan_mode, TimePlanMode::DwellOnTime);
1945 match &bhp.plans[0].mode {
1946 BeamhoppingMode::Mode0 {
1947 dwell_duration_base,
1948 ..
1949 } => {
1950 assert_eq!(*dwell_duration_base, 0x0000_1111_1111);
1951 }
1952 other => panic!("expected Mode0, got {other:?}"),
1953 }
1954 }
1955 other => panic!("expected BeamhoppingTimePlan, got {other:?}"),
1956 }
1957 let mut buf2 = vec![0u8; sat.serialized_len()];
1958 sat.serialize_into(&mut buf2).unwrap();
1959 assert_eq!(bytes, buf2, "byte-exact re-serialize");
1960 }
1961
1962 #[test]
1963 fn reserved_discriminant_has_no_kind() {
1964 let bytes = build_sat(7, 0, &SatBody::Raw(Vec::new()));
1965 let sat = SatSection::parse(&bytes).unwrap();
1966 assert_eq!(sat.satellite_table_id, 7);
1967 assert_eq!(sat.kind(), None);
1968 }
1969
1970 #[test]
1971 fn parse_rejects_wrong_tag() {
1972 let mut bytes = build_sat(0, 0, &SatBody::Raw(vec![1, 2, 3]));
1973 bytes[0] = 0x40;
1974 assert!(matches!(
1975 SatSection::parse(&bytes).unwrap_err(),
1976 Error::UnexpectedTableId { table_id: 0x40, .. }
1977 ));
1978 }
1979
1980 #[test]
1981 fn rejects_short_buffer() {
1982 assert!(matches!(
1983 SatSection::parse(&[0x4D, 0xF0]).unwrap_err(),
1984 Error::BufferTooShort {
1985 what: "SatSection",
1986 ..
1987 }
1988 ));
1989 }
1990
1991 #[test]
1992 fn serialize_round_trip_raw() {
1993 let body_data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
1994 let sat = SatSection {
1995 satellite_table_id: 10,
1996 private_indicator: true,
1997 table_count: 0x2FF,
1998 version_number: 5,
1999 current_next_indicator: true,
2000 section_number: 0,
2001 last_section_number: 0,
2002 body: SatBody::Raw(body_data.clone()),
2003 };
2004 let mut buf = vec![0u8; sat.serialized_len()];
2005 sat.serialize_into(&mut buf).unwrap();
2006 let re = SatSection::parse(&buf).unwrap();
2007 assert_eq!(re.body, SatBody::Raw(body_data));
2008 assert_eq!(re.table_count, 0x2FF);
2009 }
2010
2011 #[test]
2012 fn parse_handwritten_sat_raw() {
2013 let mut bytes: Vec<u8> = vec![
2014 0x4D, 0xF0, 0x0E, 0x1C, 0x00, 0xCB, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD,
2015 ];
2016 let crc = broadcast_common::crc32_mpeg2::compute(&bytes);
2017 bytes.extend_from_slice(&crc.to_be_bytes());
2018 let sat = SatSection::parse(&bytes).unwrap();
2019 assert_eq!(sat.satellite_table_id, 7);
2020 assert_eq!(sat.table_count, 0);
2021 assert_eq!(sat.version_number, 5);
2022 assert!(sat.current_next_indicator);
2023 match sat.body {
2024 SatBody::Raw(v) => assert_eq!(v, &[0xAA, 0xBB, 0xCC, 0xDD]),
2025 other => panic!("expected Raw, got {other:?}"),
2026 }
2027 }
2028
2029 #[test]
2030 fn beamhopping_multi_plan_round_trip() {
2031 let body = SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
2032 plans: vec![
2033 BeamhoppingPlan {
2034 beamhopping_time_plan_id: 0x11111111,
2035 time_plan_mode: TimePlanMode::DwellOnTime,
2036 time_of_application_base: 0x0000_AAAA_AAAA,
2037 time_of_application_ext: 0x100,
2038 cycle_duration_base: 0x0000_5555_5555,
2039 cycle_duration_ext: 0x080,
2040 mode: BeamhoppingMode::Mode0 {
2041 dwell_duration_base: 0x0000_1111_1111,
2042 dwell_duration_ext: 0x111,
2043 on_time_base: 0x0000_2222_2222,
2044 on_time_ext: 0x222,
2045 },
2046 },
2047 BeamhoppingPlan {
2048 beamhopping_time_plan_id: 0x22222222,
2049 time_plan_mode: TimePlanMode::DwellOnTime,
2050 time_of_application_base: 0x0000_BBBB_BBBB,
2051 time_of_application_ext: 0x200,
2052 cycle_duration_base: 0x0000_6666_6666,
2053 cycle_duration_ext: 0x090,
2054 mode: BeamhoppingMode::Mode0 {
2055 dwell_duration_base: 0x0000_3333_3333,
2056 dwell_duration_ext: 0x333,
2057 on_time_base: 0x0000_4444_4444,
2058 on_time_ext: 0x444,
2059 },
2060 },
2061 ],
2062 });
2063 let bytes = build_sat(3, 0, &body);
2064 let sat = SatSection::parse(&bytes).unwrap();
2065 match &sat.body {
2066 SatBody::BeamhoppingTimePlan(bhp) => {
2067 assert_eq!(bhp.plans.len(), 2);
2068 assert_eq!(bhp.plans[0].beamhopping_time_plan_id, 0x11111111);
2069 assert_eq!(bhp.plans[1].beamhopping_time_plan_id, 0x22222222);
2070 }
2071 other => panic!("expected BeamhoppingTimePlan, got {other:?}"),
2072 }
2073 let mut buf2 = vec![0u8; sat.serialized_len()];
2074 sat.serialize_into(&mut buf2).unwrap();
2075 assert_eq!(bytes, buf2, "byte-exact multi-plan round-trip");
2076 }
2077
2078 #[test]
2079 fn position_v3_one_sat_with_metadata_round_trip() {
2080 let body = SatBody::PositionV3(PositionV3Body {
2081 oem_version_major: 2,
2082 oem_version_minor: 1,
2083 creation_date_year: 26,
2084 creation_date_day: 42,
2085 creation_date_day_fraction: 0,
2086 satellites: vec![PositionV3Satellite {
2087 satellite_id: 0xABCDEF,
2088 usable_start_time_flag: true,
2089 usable_stop_time_flag: false,
2090 ephemeris_accel_flag: false,
2091 covariance_flag: false,
2092 metadata: Some(PositionV3Metadata {
2093 total_start_time_year: 26,
2094 total_start_time_day: 1,
2095 total_start_time_day_fraction: 0,
2096 total_stop_time_year: 27,
2097 total_stop_time_day: 100,
2098 total_stop_time_day_fraction: 0,
2099 interpolation_flag: true,
2100 interpolation_type: InterpolationType::Linear,
2101 interpolation_degree: 2,
2102 usable_start_time: Some(UsableTime {
2103 year: 26,
2104 day: 10,
2105 day_fraction: 0,
2106 }),
2107 usable_stop_time: None,
2108 }),
2109 ephemeris_data: Vec::new(),
2110 covariance: None,
2111 }],
2112 });
2113 let bytes = build_sat(4, 0, &body);
2114 let sat = SatSection::parse(&bytes).unwrap();
2115 match &sat.body {
2116 SatBody::PositionV3(v3) => {
2117 assert_eq!(v3.satellites.len(), 1);
2118 assert_eq!(v3.satellites[0].satellite_id, 0xABCDEF);
2119 let md = v3.satellites[0].metadata.as_ref().unwrap();
2120 assert!(md.interpolation_flag);
2121 assert_eq!(md.interpolation_type, InterpolationType::Linear);
2122 assert_eq!(md.interpolation_degree, 2);
2123 assert!(md.usable_start_time.is_some());
2124 }
2125 other => panic!("expected PositionV3, got {other:?}"),
2126 }
2127 let mut buf2 = vec![0u8; sat.serialized_len()];
2128 sat.serialize_into(&mut buf2).unwrap();
2129 assert_eq!(bytes, buf2, "byte-exact PositionV3 round-trip");
2130 }
2131
2132 #[test]
2133 fn cell_fragment_round_trip() {
2134 let body = SatBody::CellFragment(CellFragmentBody {
2135 fragments: vec![CellFragment {
2136 cell_fragment_id: 0x11223344,
2137 first_occurrence: true,
2138 last_occurrence: false,
2139 center: Some(CellCenter {
2140 center_latitude: 1000,
2141 center_longitude: -2000,
2142 max_distance: 500000,
2143 }),
2144 delivery_system_ids: vec![0x55667788],
2145 new_delivery_systems: vec![NewDeliverySystem {
2146 new_delivery_system_id: 0xAABBCCDD,
2147 time_of_application_base: 0x0000_1234_5678,
2148 time_of_application_ext: 0x100,
2149 }],
2150 obsolescent_delivery_systems: vec![ObsolescentDeliverySystem {
2151 obsolescent_delivery_system_id: 0xEEFF0011,
2152 time_of_obsolescence_base: 0x0000_9ABC_DEF0,
2153 time_of_obsolescence_ext: 0x1FF,
2154 }],
2155 }],
2156 });
2157 let bytes = build_sat(1, 0, &body);
2158 let sat = SatSection::parse(&bytes).unwrap();
2159 match &sat.body {
2160 SatBody::CellFragment(cf) => {
2161 assert_eq!(cf.fragments.len(), 1);
2162 assert_eq!(cf.fragments[0].cell_fragment_id, 0x11223344);
2163 assert!(cf.fragments[0].first_occurrence);
2164 assert!(cf.fragments[0].center.is_some());
2165 assert_eq!(cf.fragments[0].delivery_system_ids.len(), 1);
2166 assert_eq!(cf.fragments[0].new_delivery_systems.len(), 1);
2167 assert_eq!(cf.fragments[0].obsolescent_delivery_systems.len(), 1);
2168 }
2169 other => panic!("expected CellFragment, got {other:?}"),
2170 }
2171 let mut buf2 = vec![0u8; sat.serialized_len()];
2172 sat.serialize_into(&mut buf2).unwrap();
2173 assert_eq!(bytes, buf2, "byte-exact CellFragment round-trip");
2174 }
2175
2176 #[test]
2177 fn beamhopping_mode1_round_trip() {
2178 let body = SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
2179 plans: vec![BeamhoppingPlan {
2180 beamhopping_time_plan_id: 0x12345678,
2181 time_plan_mode: TimePlanMode::Bitmap,
2182 time_of_application_base: 0x0000_AAAA_AAAA,
2183 time_of_application_ext: 0x100,
2184 cycle_duration_base: 0x0000_5555_5555,
2185 cycle_duration_ext: 0x080,
2186 mode: BeamhoppingMode::Mode1 {
2187 bit_map_size: 8,
2188 current_slot: 3,
2189 slot_transmission_on: vec![true, false, true, true, false, false, true, false],
2190 },
2191 }],
2192 });
2193 let bytes = build_sat(3, 0, &body);
2194 let sat = SatSection::parse(&bytes).unwrap();
2195 match &sat.body {
2196 SatBody::BeamhoppingTimePlan(bhp) => {
2197 assert_eq!(bhp.plans.len(), 1);
2198 assert_eq!(bhp.plans[0].time_plan_mode, TimePlanMode::Bitmap);
2199 match &bhp.plans[0].mode {
2200 BeamhoppingMode::Mode1 {
2201 bit_map_size,
2202 current_slot,
2203 slot_transmission_on,
2204 } => {
2205 assert_eq!(*bit_map_size, 8);
2206 assert_eq!(*current_slot, 3);
2207 assert_eq!(
2208 slot_transmission_on,
2209 &[true, false, true, true, false, false, true, false]
2210 );
2211 }
2212 other => panic!("expected Mode1, got {other:?}"),
2213 }
2214 }
2215 other => panic!("expected BeamhoppingTimePlan, got {other:?}"),
2216 }
2217 let mut buf2 = vec![0u8; sat.serialized_len()];
2218 sat.serialize_into(&mut buf2).unwrap();
2219 assert_eq!(bytes, buf2, "byte-exact Mode1 round-trip");
2220 }
2221
2222 #[test]
2223 fn beamhopping_mode2_round_trip() {
2224 let body = SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
2225 plans: vec![BeamhoppingPlan {
2226 beamhopping_time_plan_id: 0x87654321,
2227 time_plan_mode: TimePlanMode::GridRevisitSleep,
2228 time_of_application_base: 0x0000_BBBB_BBBB,
2229 time_of_application_ext: 0x200,
2230 cycle_duration_base: 0x0000_6666_6666,
2231 cycle_duration_ext: 0x090,
2232 mode: BeamhoppingMode::Mode2 {
2233 grid_size_base: 0x0000_1111_1111,
2234 grid_size_ext: 0x111,
2235 revisit_duration_base: 0x0000_2222_2222,
2236 revisit_duration_ext: 0x222,
2237 sleep_time_base: 0x0000_3333_3333,
2238 sleep_time_ext: 0x333,
2239 sleep_duration_base: 0x0000_4444_4444,
2240 sleep_duration_ext: 0x444,
2241 },
2242 }],
2243 });
2244 let bytes = build_sat(3, 0, &body);
2245 let sat = SatSection::parse(&bytes).unwrap();
2246 match &sat.body {
2247 SatBody::BeamhoppingTimePlan(bhp) => {
2248 assert_eq!(bhp.plans.len(), 1);
2249 assert_eq!(bhp.plans[0].time_plan_mode, TimePlanMode::GridRevisitSleep);
2250 match &bhp.plans[0].mode {
2251 BeamhoppingMode::Mode2 { grid_size_base, .. } => {
2252 assert_eq!(*grid_size_base, 0x0000_1111_1111);
2253 }
2254 other => panic!("expected Mode2, got {other:?}"),
2255 }
2256 }
2257 other => panic!("expected BeamhoppingTimePlan, got {other:?}"),
2258 }
2259 let mut buf2 = vec![0u8; sat.serialized_len()];
2260 sat.serialize_into(&mut buf2).unwrap();
2261 assert_eq!(bytes, buf2, "byte-exact Mode2 round-trip");
2262 }
2263
2264 #[test]
2265 fn beamhopping_reserved_mode_round_trip() {
2266 let body = SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
2267 plans: vec![
2268 BeamhoppingPlan {
2269 beamhopping_time_plan_id: 0x11111111,
2270 time_plan_mode: TimePlanMode::DwellOnTime,
2271 time_of_application_base: 0x0000_AAAA_AAAA,
2272 time_of_application_ext: 0x100,
2273 cycle_duration_base: 0x0000_5555_5555,
2274 cycle_duration_ext: 0x080,
2275 mode: BeamhoppingMode::Mode0 {
2276 dwell_duration_base: 0x0000_1111_1111,
2277 dwell_duration_ext: 0x111,
2278 on_time_base: 0x0000_2222_2222,
2279 on_time_ext: 0x222,
2280 },
2281 },
2282 BeamhoppingPlan {
2283 beamhopping_time_plan_id: 0x22222222,
2284 time_plan_mode: TimePlanMode::Reserved(3),
2285 time_of_application_base: 0x0000_CCCC_CCCC,
2286 time_of_application_ext: 0x300,
2287 cycle_duration_base: 0x0000_DDDD_DDDD,
2288 cycle_duration_ext: 0x400,
2289 mode: BeamhoppingMode::Reserved(vec![0xAA, 0xBB, 0xCC]),
2290 },
2291 ],
2292 });
2293 let bytes = build_sat(3, 0, &body);
2294 let sat = SatSection::parse(&bytes).unwrap();
2295 match &sat.body {
2296 SatBody::BeamhoppingTimePlan(bhp) => {
2297 assert_eq!(bhp.plans.len(), 2);
2298 assert_eq!(bhp.plans[0].time_plan_mode, TimePlanMode::DwellOnTime);
2299 assert_eq!(bhp.plans[1].time_plan_mode, TimePlanMode::Reserved(3));
2300 match &bhp.plans[1].mode {
2301 BeamhoppingMode::Reserved(v) => {
2302 assert_eq!(v, &[0xAA, 0xBB, 0xCC]);
2303 }
2304 other => panic!("expected Reserved, got {other:?}"),
2305 }
2306 }
2307 other => panic!("expected BeamhoppingTimePlan, got {other:?}"),
2308 }
2309 let mut buf2 = vec![0u8; sat.serialized_len()];
2310 sat.serialize_into(&mut buf2).unwrap();
2311 assert_eq!(bytes, buf2, "byte-exact Reserved mode round-trip");
2312 }
2313
2314 #[test]
2315 fn cell_fragment_truncated_dsid_count() {
2316 let body = SatBody::CellFragment(CellFragmentBody {
2317 fragments: vec![CellFragment {
2318 cell_fragment_id: 1,
2319 first_occurrence: false,
2320 last_occurrence: false,
2321 center: None,
2322 delivery_system_ids: vec![0x11111111],
2323 new_delivery_systems: Vec::new(),
2324 obsolescent_delivery_systems: Vec::new(),
2325 }],
2326 });
2327 let bytes = build_sat(1, 0, &body);
2328 let sat = SatSection::parse(&bytes).unwrap();
2329 let mut buf2 = vec![0u8; sat.serialized_len()];
2330 sat.serialize_into(&mut buf2).unwrap();
2331 assert_eq!(bytes, buf2);
2332
2333 let corrupt_sat = SatSection {
2334 satellite_table_id: 1,
2335 private_indicator: true,
2336 table_count: 0,
2337 version_number: 5,
2338 current_next_indicator: true,
2339 section_number: 0,
2340 last_section_number: 0,
2341 body: SatBody::CellFragment(CellFragmentBody {
2342 fragments: vec![CellFragment {
2343 cell_fragment_id: 1,
2344 first_occurrence: false,
2345 last_occurrence: false,
2346 center: None,
2347 delivery_system_ids: vec![0x11111111; 50],
2348 new_delivery_systems: Vec::new(),
2349 obsolescent_delivery_systems: Vec::new(),
2350 }],
2351 }),
2352 };
2353 let mut corrupt_buf = vec![0u8; corrupt_sat.serialized_len()];
2354 corrupt_sat.serialize_into(&mut corrupt_buf).unwrap();
2355 let section_length = (corrupt_buf.len() - SECTION_LENGTH_PREFIX) as u16;
2356 corrupt_buf[1] = 0x80 | 0x40 | 0x30 | ((section_length >> 8) as u8 & 0x0F);
2357 corrupt_buf[2] = (section_length & 0xFF) as u8;
2358 let crc_end = corrupt_buf.len();
2359 let crc = broadcast_common::crc32_mpeg2::compute(&corrupt_buf[..crc_end - CRC_LEN]);
2360 corrupt_buf[crc_end - CRC_LEN..crc_end].copy_from_slice(&crc.to_be_bytes());
2361 let original_len = corrupt_buf.len();
2362 corrupt_buf.truncate(original_len - 100);
2363 let sl = (corrupt_buf.len() - SECTION_LENGTH_PREFIX) as u16;
2364 corrupt_buf[1] = (corrupt_buf[1] & 0xF0) | ((sl >> 8) as u8 & 0x0F);
2365 corrupt_buf[2] = (sl & 0xFF) as u8;
2366 let crc_end = corrupt_buf.len();
2367 let crc2 = broadcast_common::crc32_mpeg2::compute(&corrupt_buf[..crc_end - CRC_LEN]);
2368 corrupt_buf[crc_end - CRC_LEN..crc_end].copy_from_slice(&crc2.to_be_bytes());
2369 assert!(SatSection::parse(&corrupt_buf).is_err());
2370 }
2371
2372 #[test]
2373 fn beamhopping_mode1_truncated_bit_map_size() {
2374 let corrupt_sat = SatSection {
2375 satellite_table_id: 3,
2376 private_indicator: true,
2377 table_count: 0,
2378 version_number: 5,
2379 current_next_indicator: true,
2380 section_number: 0,
2381 last_section_number: 0,
2382 body: SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
2383 plans: vec![BeamhoppingPlan {
2384 beamhopping_time_plan_id: 1,
2385 time_plan_mode: TimePlanMode::Bitmap,
2386 time_of_application_base: 0,
2387 time_of_application_ext: 0,
2388 cycle_duration_base: 0,
2389 cycle_duration_ext: 0,
2390 mode: BeamhoppingMode::Mode1 {
2391 bit_map_size: 200,
2392 current_slot: 0,
2393 slot_transmission_on: vec![true; 200],
2394 },
2395 }],
2396 }),
2397 };
2398 let mut corrupt_buf = vec![0u8; corrupt_sat.serialized_len()];
2399 corrupt_sat.serialize_into(&mut corrupt_buf).unwrap();
2400 let original_len = corrupt_buf.len();
2401 let truncate_at = HEADER_LEN + 20;
2402 assert!(
2403 truncate_at + CRC_LEN < original_len,
2404 "fixture must be large enough to truncate meaningfully"
2405 );
2406 {
2407 corrupt_buf.truncate(truncate_at + CRC_LEN);
2408 let sl = (corrupt_buf.len() - SECTION_LENGTH_PREFIX) as u16;
2409 corrupt_buf[1] = (corrupt_buf[1] & 0xF0) | ((sl >> 8) as u8 & 0x0F);
2410 corrupt_buf[2] = (sl & 0xFF) as u8;
2411 let crc_end = corrupt_buf.len();
2412 let crc = broadcast_common::crc32_mpeg2::compute(&corrupt_buf[..crc_end - CRC_LEN]);
2413 corrupt_buf[crc_end - CRC_LEN..crc_end].copy_from_slice(&crc.to_be_bytes());
2414 assert!(SatSection::parse(&corrupt_buf).is_err());
2415 }
2416 }
2417
2418 #[test]
2419 fn position_v3_truncated_ephemeris_data_count() {
2420 let corrupt_sat = SatSection {
2421 satellite_table_id: 4,
2422 private_indicator: true,
2423 table_count: 0,
2424 version_number: 5,
2425 current_next_indicator: true,
2426 section_number: 0,
2427 last_section_number: 0,
2428 body: SatBody::PositionV3(PositionV3Body {
2429 oem_version_major: 1,
2430 oem_version_minor: 0,
2431 creation_date_year: 25,
2432 creation_date_day: 1,
2433 creation_date_day_fraction: 0,
2434 satellites: vec![PositionV3Satellite {
2435 satellite_id: 1,
2436 usable_start_time_flag: false,
2437 usable_stop_time_flag: false,
2438 ephemeris_accel_flag: false,
2439 covariance_flag: false,
2440 metadata: None,
2441 ephemeris_data: vec![
2442 EphemerisData {
2443 epoch_year: 25,
2444 epoch_day: 1,
2445 epoch_day_fraction: 0,
2446 ephemeris_x: 0,
2447 ephemeris_y: 0,
2448 ephemeris_z: 0,
2449 ephemeris_x_dot: 0,
2450 ephemeris_y_dot: 0,
2451 ephemeris_z_dot: 0,
2452 acceleration: None,
2453 };
2454 5
2455 ],
2456 covariance: None,
2457 }],
2458 }),
2459 };
2460 let mut corrupt_buf = vec![0u8; corrupt_sat.serialized_len()];
2461 corrupt_sat.serialize_into(&mut corrupt_buf).unwrap();
2462 let original_len = corrupt_buf.len();
2463 let truncate_at = HEADER_LEN + 30;
2464 assert!(
2465 truncate_at + CRC_LEN < original_len,
2466 "fixture must be large enough to truncate meaningfully"
2467 );
2468 {
2469 corrupt_buf.truncate(truncate_at + CRC_LEN);
2470 let sl = (corrupt_buf.len() - SECTION_LENGTH_PREFIX) as u16;
2471 corrupt_buf[1] = (corrupt_buf[1] & 0xF0) | ((sl >> 8) as u8 & 0x0F);
2472 corrupt_buf[2] = (sl & 0xFF) as u8;
2473 let crc_end = corrupt_buf.len();
2474 let crc = broadcast_common::crc32_mpeg2::compute(&corrupt_buf[..crc_end - CRC_LEN]);
2475 corrupt_buf[crc_end - CRC_LEN..crc_end].copy_from_slice(&crc.to_be_bytes());
2476 assert!(SatSection::parse(&corrupt_buf).is_err());
2477 }
2478 }
2479
2480 #[test]
2481 fn hand_byte_time_association() {
2482 let body = SatBody::TimeAssociation(TimeAssociationBody {
2483 association_type: AssociationType::UtcWithoutLeap,
2484 leap_info: None,
2485 ncr_base: 0x0000_AAAA_AAAA_u64,
2486 ncr_ext: 0x1AA,
2487 association_timestamp_seconds: 0,
2488 association_timestamp_nanoseconds: 0,
2489 });
2490 let bytes = build_sat(2, 0, &body);
2491 let sat = SatSection::parse(&bytes).unwrap();
2492 assert_eq!(sat.satellite_table_id, 2);
2493 match &sat.body {
2494 SatBody::TimeAssociation(ta) => {
2495 assert_eq!(ta.association_type, AssociationType::UtcWithoutLeap);
2496 assert_eq!(ta.ncr_base, 0x0000_AAAA_AAAA);
2497 assert_eq!(ta.ncr_ext, 0x1AA);
2498 }
2499 other => panic!("expected TimeAssociation, got {other:?}"),
2500 }
2501 assert_eq!((bytes[1] >> 6) & 1, 1);
2502 assert_eq!((bytes[3] >> 2) & 0x3F, 2);
2503 }
2504
2505 #[test]
2506 fn hand_byte_position_v2_orbital() {
2507 let body = SatBody::PositionV2(PositionV2Body {
2508 satellites: vec![PositionV2Satellite {
2509 satellite_id: 0x010203,
2510 position: PositionSystem::Orbital {
2511 orbital_position: 0x1920,
2512 west_east_flag: true,
2513 },
2514 }],
2515 });
2516 let bytes = build_sat(0, 0, &body);
2517 let sat = SatSection::parse(&bytes).unwrap();
2518 match &sat.body {
2519 SatBody::PositionV2(pv2) => {
2520 assert_eq!(pv2.satellites[0].satellite_id, 0x010203);
2521 match &pv2.satellites[0].position {
2522 PositionSystem::Orbital {
2523 orbital_position, ..
2524 } => {
2525 assert_eq!(*orbital_position, 0x1920);
2526 }
2527 other => panic!("expected Orbital, got {other:?}"),
2528 }
2529 }
2530 other => panic!("expected PositionV2, got {other:?}"),
2531 }
2532 assert_eq!((bytes[3] >> 2) & 0x3F, 0);
2533 }
2534
2535 #[test]
2536 fn hand_byte_cell_fragment() {
2537 let body = SatBody::CellFragment(CellFragmentBody {
2538 fragments: vec![CellFragment {
2539 cell_fragment_id: 0xAABBCCDD,
2540 first_occurrence: false,
2541 last_occurrence: true,
2542 center: None,
2543 delivery_system_ids: Vec::new(),
2544 new_delivery_systems: Vec::new(),
2545 obsolescent_delivery_systems: Vec::new(),
2546 }],
2547 });
2548 let bytes = build_sat(1, 0, &body);
2549 let sat = SatSection::parse(&bytes).unwrap();
2550 match &sat.body {
2551 SatBody::CellFragment(cf) => {
2552 assert_eq!(cf.fragments[0].cell_fragment_id, 0xAABBCCDD);
2553 assert!(cf.fragments[0].last_occurrence);
2554 }
2555 other => panic!("expected CellFragment, got {other:?}"),
2556 }
2557 assert_eq!((bytes[3] >> 2) & 0x3F, 1);
2558 }
2559
2560 #[test]
2561 fn hand_byte_beamhopping_mode0() {
2562 let body = SatBody::BeamhoppingTimePlan(BeamhoppingTimePlanBody {
2563 plans: vec![BeamhoppingPlan {
2564 beamhopping_time_plan_id: 0xDEADBEEF,
2565 time_plan_mode: TimePlanMode::DwellOnTime,
2566 time_of_application_base: 0,
2567 time_of_application_ext: 0,
2568 cycle_duration_base: 0,
2569 cycle_duration_ext: 0,
2570 mode: BeamhoppingMode::Mode0 {
2571 dwell_duration_base: 0,
2572 dwell_duration_ext: 0,
2573 on_time_base: 0,
2574 on_time_ext: 0,
2575 },
2576 }],
2577 });
2578 let bytes = build_sat(3, 0, &body);
2579 let sat = SatSection::parse(&bytes).unwrap();
2580 match &sat.body {
2581 SatBody::BeamhoppingTimePlan(bhp) => {
2582 assert_eq!(bhp.plans[0].beamhopping_time_plan_id, 0xDEADBEEF);
2583 assert_eq!(bhp.plans[0].time_plan_mode, TimePlanMode::DwellOnTime);
2584 }
2585 other => panic!("expected BeamhoppingTimePlan, got {other:?}"),
2586 }
2587 assert_eq!((bytes[3] >> 2) & 0x3F, 3);
2588 }
2589
2590 #[test]
2591 fn hand_byte_position_v3() {
2592 let body = SatBody::PositionV3(PositionV3Body {
2593 oem_version_major: 1,
2594 oem_version_minor: 2,
2595 creation_date_year: 26,
2596 creation_date_day: 42,
2597 creation_date_day_fraction: 0,
2598 satellites: Vec::new(),
2599 });
2600 let bytes = build_sat(4, 0, &body);
2601 let sat = SatSection::parse(&bytes).unwrap();
2602 match &sat.body {
2603 SatBody::PositionV3(v3) => {
2604 assert_eq!(v3.oem_version_major, 1);
2605 assert_eq!(v3.oem_version_minor, 2);
2606 }
2607 other => panic!("expected PositionV3, got {other:?}"),
2608 }
2609 assert_eq!((bytes[3] >> 2) & 0x3F, 4);
2610 }
2611
2612 fn crc_section(bytes: &[u8]) -> Vec<u8> {
2618 let mut v = bytes.to_vec();
2619 let crc = broadcast_common::crc32_mpeg2::compute(&v);
2620 v.extend_from_slice(&crc.to_be_bytes());
2621 v
2622 }
2623
2624 #[test]
2625 fn hand_built_time_association_anchor() {
2626 let bytes = crc_section(&[
2636 0x4D, 0xF0, 0x1D, 0x08, 0x00, 0xCB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2637 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2638 ]);
2639 let sat = SatSection::parse(&bytes).unwrap();
2640 assert_eq!(sat.satellite_table_id, 2);
2641 match &sat.body {
2642 SatBody::TimeAssociation(ta) => {
2643 assert_eq!(ta.association_type, AssociationType::UtcWithoutLeap);
2644 assert_eq!(ta.ncr_base, 1);
2645 assert_eq!(ta.ncr_ext, 0);
2646 assert_eq!(ta.association_timestamp_seconds, 0);
2647 assert_eq!(ta.association_timestamp_nanoseconds, 0);
2648 }
2649 other => panic!("expected TimeAssociation, got {other:?}"),
2650 }
2651 let mut buf = vec![0u8; sat.serialized_len()];
2652 sat.serialize_into(&mut buf).unwrap();
2653 assert_eq!(buf, bytes, "byte-identical re-serialize");
2654 }
2655
2656 #[test]
2657 fn hand_built_position_v2_orbital_anchor() {
2658 let bytes = crc_section(&[
2667 0x4D, 0xF0, 0x11, 0x00, 0x00, 0xCB, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x00, 0x12,
2668 0x34, 0x80,
2669 ]);
2670 let sat = SatSection::parse(&bytes).unwrap();
2671 assert_eq!(sat.satellite_table_id, 0);
2672 match &sat.body {
2673 SatBody::PositionV2(pv2) => {
2674 assert_eq!(pv2.satellites.len(), 1);
2675 let s = &pv2.satellites[0];
2676 assert_eq!(s.satellite_id, 0x010203);
2677 match &s.position {
2678 PositionSystem::Orbital {
2679 orbital_position,
2680 west_east_flag,
2681 } => {
2682 assert_eq!(*orbital_position, 0x1234);
2683 assert!(*west_east_flag);
2684 }
2685 other => panic!("expected Orbital, got {other:?}"),
2686 }
2687 }
2688 other => panic!("expected PositionV2, got {other:?}"),
2689 }
2690 let mut buf = vec![0u8; sat.serialized_len()];
2691 sat.serialize_into(&mut buf).unwrap();
2692 assert_eq!(buf, bytes, "byte-identical re-serialize");
2693 }
2694
2695 #[test]
2696 fn hand_built_cell_fragment_anchor() {
2697 let bytes = crc_section(&[
2709 0x4D, 0xF0, 0x14, 0x04, 0x00, 0xCB, 0x00, 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, 0x40,
2710 0x00, 0x00, 0x00, 0x00, 0x00,
2711 ]);
2712 let sat = SatSection::parse(&bytes).unwrap();
2713 assert_eq!(sat.satellite_table_id, 1);
2714 match &sat.body {
2715 SatBody::CellFragment(cf) => {
2716 assert_eq!(cf.fragments.len(), 1);
2717 let f = &cf.fragments[0];
2718 assert_eq!(f.cell_fragment_id, 0xAABBCCDD);
2719 assert!(!f.first_occurrence);
2720 assert!(f.last_occurrence);
2721 assert!(f.center.is_none());
2722 assert!(f.delivery_system_ids.is_empty());
2723 assert!(f.new_delivery_systems.is_empty());
2724 assert!(f.obsolescent_delivery_systems.is_empty());
2725 }
2726 other => panic!("expected CellFragment, got {other:?}"),
2727 }
2728 let mut buf = vec![0u8; sat.serialized_len()];
2729 sat.serialize_into(&mut buf).unwrap();
2730 assert_eq!(buf, bytes, "byte-identical re-serialize");
2731 }
2732
2733 #[test]
2734 fn hand_built_beamhopping_mode0_anchor() {
2735 let bytes = crc_section(&[
2744 0x4D, 0xF0, 0x29, 0x0C, 0x00, 0xCB, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF, 0x00,
2745 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2746 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2747 ]);
2748 let sat = SatSection::parse(&bytes).unwrap();
2749 assert_eq!(sat.satellite_table_id, 3);
2750 match &sat.body {
2751 SatBody::BeamhoppingTimePlan(bhp) => {
2752 assert_eq!(bhp.plans.len(), 1);
2753 let p = &bhp.plans[0];
2754 assert_eq!(p.beamhopping_time_plan_id, 0xDEADBEEF);
2755 assert_eq!(p.time_plan_mode, TimePlanMode::DwellOnTime);
2756 assert_eq!(p.time_of_application_base, 0);
2757 assert_eq!(p.cycle_duration_base, 0);
2758 match &p.mode {
2759 BeamhoppingMode::Mode0 {
2760 dwell_duration_base,
2761 dwell_duration_ext,
2762 on_time_base,
2763 on_time_ext,
2764 } => {
2765 assert_eq!(*dwell_duration_base, 0);
2766 assert_eq!(*dwell_duration_ext, 0);
2767 assert_eq!(*on_time_base, 0);
2768 assert_eq!(*on_time_ext, 0);
2769 }
2770 other => panic!("expected Mode0, got {other:?}"),
2771 }
2772 }
2773 other => panic!("expected BeamhoppingTimePlan, got {other:?}"),
2774 }
2775 let mut buf = vec![0u8; sat.serialized_len()];
2776 sat.serialize_into(&mut buf).unwrap();
2777 assert_eq!(buf, bytes, "byte-identical re-serialize");
2778 }
2779
2780 #[test]
2781 fn hand_built_position_v3_anchor() {
2782 let bytes = crc_section(&[
2791 0x4D, 0xF0, 0x12, 0x10, 0x00, 0xCB, 0x00, 0x00, 0x00, 0x12, 0x1A, 0x00, 0x2A, 0x00,
2792 0x00, 0x00, 0x00,
2793 ]);
2794 let sat = SatSection::parse(&bytes).unwrap();
2795 assert_eq!(sat.satellite_table_id, 4);
2796 match &sat.body {
2797 SatBody::PositionV3(v3) => {
2798 assert_eq!(v3.oem_version_major, 1);
2799 assert_eq!(v3.oem_version_minor, 2);
2800 assert_eq!(v3.creation_date_year, 26);
2801 assert_eq!(v3.creation_date_day, 42);
2802 assert_eq!(v3.creation_date_day_fraction, 0);
2803 assert!(v3.satellites.is_empty());
2804 }
2805 other => panic!("expected PositionV3, got {other:?}"),
2806 }
2807 let mut buf = vec![0u8; sat.serialized_len()];
2808 sat.serialize_into(&mut buf).unwrap();
2809 assert_eq!(buf, bytes, "byte-identical re-serialize");
2810 }
2811
2812 #[test]
2813 fn parse_rejects_truncated_time_association_body() {
2814 let body = SatBody::TimeAssociation(TimeAssociationBody {
2815 association_type: AssociationType::UtcWithoutLeap,
2816 leap_info: None,
2817 ncr_base: 0,
2818 ncr_ext: 0,
2819 association_timestamp_seconds: 0,
2820 association_timestamp_nanoseconds: 0,
2821 });
2822 let bytes = build_sat(2, 0, &body);
2823 let sat = SatSection::parse(&bytes).unwrap();
2824 let mut buf = vec![0u8; sat.serialized_len()];
2825 sat.serialize_into(&mut buf).unwrap();
2826 buf.truncate(HEADER_LEN + 4 + CRC_LEN);
2827 let sl = (buf.len() - SECTION_LENGTH_PREFIX) as u16;
2828 buf[1] = (buf[1] & 0xF0) | ((sl >> 8) as u8 & 0x0F);
2829 buf[2] = (sl & 0xFF) as u8;
2830 let crc_end = buf.len();
2831 let crc = broadcast_common::crc32_mpeg2::compute(&buf[..crc_end - CRC_LEN]);
2832 buf[crc_end - CRC_LEN..crc_end].copy_from_slice(&crc.to_be_bytes());
2833 assert!(SatSection::parse(&buf).is_err());
2834 }
2835
2836 #[test]
2837 fn sat_table_id_wire_to_name() {
2838 let stid = SatTableId::try_from(0u8).unwrap();
2839 assert_eq!(stid, SatTableId::PositionV2);
2840 let stid = SatTableId::try_from(2u8).unwrap();
2841 assert_eq!(stid, SatTableId::TimeAssociation);
2842 let stid = SatTableId::try_from(4u8).unwrap();
2843 assert_eq!(stid, SatTableId::PositionV3);
2844 }
2845
2846 #[test]
2847 fn association_type_wire_to_name() {
2848 assert_eq!(
2849 AssociationType::from_u8(0).name(),
2850 "UTC without leap second"
2851 );
2852 assert_eq!(AssociationType::from_u8(1).name(), "UTC with leap second");
2853 }
2854
2855 #[test]
2856 fn interpolation_type_wire_to_name() {
2857 assert_eq!(InterpolationType::from_u8(1).name(), "Linear");
2858 assert_eq!(InterpolationType::from_u8(2).name(), "Lagrange");
2859 assert_eq!(InterpolationType::from_u8(4).name(), "Hermite");
2860 assert_eq!(InterpolationType::from_u8(0).name(), "Reserved");
2861 }
2862
2863 #[test]
2864 fn time_plan_mode_full_range_round_trip() {
2865 for v in 0u8..=0x03 {
2866 let tpm = TimePlanMode::from_u8(v);
2867 assert_eq!(tpm.to_u8(), v, "TimePlanMode round-trip failed for {v}");
2868 }
2869 }
2870
2871 #[test]
2872 fn time_plan_mode_known_values() {
2873 assert_eq!(TimePlanMode::from_u8(0), TimePlanMode::DwellOnTime);
2874 assert_eq!(TimePlanMode::from_u8(1), TimePlanMode::Bitmap);
2875 assert_eq!(TimePlanMode::from_u8(2), TimePlanMode::GridRevisitSleep);
2876 assert_eq!(TimePlanMode::from_u8(3), TimePlanMode::Reserved(3));
2877 assert_eq!(TimePlanMode::DwellOnTime.name(), "dwell/on-time");
2878 assert_eq!(TimePlanMode::Bitmap.name(), "bitmap");
2879 assert_eq!(TimePlanMode::GridRevisitSleep.name(), "grid/revisit/sleep");
2880 assert_eq!(TimePlanMode::Reserved(3).name(), "reserved");
2881 }
2882}