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