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