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