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