1use super::*;
6use crate::descriptors::satellite_delivery_system::{Polarization, RollOff};
7use alloc::vec::Vec;
8
9impl<'a> ExtensionBodyDef<'a> for S2XSatelliteDeliverySystem<'a> {
10 const TAG_EXTENSION: u8 = 0x17;
11 const NAME: &'static str = "S2X_SATELLITE_DELIVERY_SYSTEM";
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21#[non_exhaustive]
22pub enum S2XMode {
23 Reserved0,
25 S2X,
27 S2XTimeSlicing,
29 S2XChannelBonding,
31 Reserved(u8),
33}
34
35impl S2XMode {
36 #[must_use]
37 pub fn from_u8(v: u8) -> Self {
39 match v {
40 0 => S2XMode::Reserved0,
41 1 => S2XMode::S2X,
42 2 => S2XMode::S2XTimeSlicing,
43 3 => S2XMode::S2XChannelBonding,
44 other => S2XMode::Reserved(other),
45 }
46 }
47
48 #[must_use]
49 pub const fn to_u8(self) -> u8 {
51 match self {
52 S2XMode::Reserved0 => 0,
53 S2XMode::S2X => 1,
54 S2XMode::S2XTimeSlicing => 2,
55 S2XMode::S2XChannelBonding => 3,
56 S2XMode::Reserved(v) => v,
57 }
58 }
59
60 #[must_use]
61 pub fn name(self) -> &'static str {
63 match self {
64 S2XMode::Reserved0 => "reserved for future use",
65 S2XMode::S2X => "S2X",
66 S2XMode::S2XTimeSlicing => "S2X + time slicing",
67 S2XMode::S2XChannelBonding => "S2X + channel bonding",
68 S2XMode::Reserved(_) => "reserved",
69 }
70 }
71}
72dvb_common::impl_spec_display!(S2XMode, Reserved);
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77#[non_exhaustive]
78pub enum TsGsS2XMode {
79 GenericPacketized,
81 Gse,
83 GseHighEfficiency,
85 DvbTransportStream,
87 Reserved(u8),
89}
90
91impl TsGsS2XMode {
92 #[must_use]
93 pub fn from_u8(v: u8) -> Self {
95 match v {
96 0 => TsGsS2XMode::GenericPacketized,
97 1 => TsGsS2XMode::Gse,
98 2 => TsGsS2XMode::GseHighEfficiency,
99 3 => TsGsS2XMode::DvbTransportStream,
100 other => TsGsS2XMode::Reserved(other),
101 }
102 }
103
104 #[must_use]
105 pub const fn to_u8(self) -> u8 {
107 match self {
108 TsGsS2XMode::GenericPacketized => 0,
109 TsGsS2XMode::Gse => 1,
110 TsGsS2XMode::GseHighEfficiency => 2,
111 TsGsS2XMode::DvbTransportStream => 3,
112 TsGsS2XMode::Reserved(v) => v,
113 }
114 }
115
116 #[must_use]
117 pub fn name(self) -> &'static str {
119 match self {
120 TsGsS2XMode::GenericPacketized => "generic packetized",
121 TsGsS2XMode::Gse => "GSE",
122 TsGsS2XMode::GseHighEfficiency => "GSE high efficiency mode",
123 TsGsS2XMode::DvbTransportStream => "DVB transport stream",
124 TsGsS2XMode::Reserved(_) => "reserved",
125 }
126 }
127}
128dvb_common::impl_spec_display!(TsGsS2XMode, Reserved);
129
130const RP_BROADCAST: u8 = 0x01;
132const RP_INTERACTIVE: u8 = 0x02;
133const RP_DSNG: u8 = 0x04;
134const RP_PROFESSIONAL: u8 = 0x08;
135const RP_VL_SNR: u8 = 0x10;
136
137#[derive(Debug, Clone, PartialEq, Eq)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize))]
143pub struct S2XChannelBond {
144 pub frequency: u32,
146 pub orbital_position: u16,
148 pub west_east_flag: bool,
150 pub polarization: Polarization,
152 pub multiple_input_stream_flag: bool,
154 pub roll_off: RollOff,
156 pub symbol_rate: u32,
158 pub input_stream_identifier: Option<u8>,
160}
161
162impl S2XChannelBond {
163 #[must_use]
166 pub fn frequency_hz(&self) -> Option<u64> {
167 dvb_common::bcd::bcd_to_decimal(u64::from(self.frequency), 8).map(|v| v * 10_000)
168 }
169
170 #[must_use]
172 pub fn orbital_position_deg(&self) -> Option<f64> {
173 dvb_common::bcd::bcd_to_decimal(u64::from(self.orbital_position), 4)
174 .map(|tenths| tenths as f64 / 10.0)
175 }
176
177 #[must_use]
179 pub fn symbol_rate_sps(&self) -> Option<u64> {
180 dvb_common::bcd::bcd_to_decimal(u64::from(self.symbol_rate), 7).map(|v| v * 100)
181 }
182}
183
184#[derive(Debug, Clone, PartialEq, Eq)]
186#[cfg_attr(feature = "serde", derive(serde::Serialize))]
187#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
188pub struct S2XSatelliteDeliverySystem<'a> {
189 pub receiver_profiles: u8,
191 pub s2x_mode: S2XMode,
193 pub scrambling_sequence_selector: bool,
195 pub ts_gs_s2x_mode: TsGsS2XMode,
197 pub scrambling_sequence_index: Option<u32>,
199 pub frequency: u32,
201 pub orbital_position: u16,
203 pub west_east_flag: bool,
205 pub polarization: Polarization,
207 pub multiple_input_stream_flag: bool,
209 pub roll_off: RollOff,
211 pub symbol_rate: u32,
213 pub input_stream_identifier: Option<u8>,
215 pub timeslice_number: Option<u8>,
217 pub channel_bonds: Vec<S2XChannelBond>,
219 pub reserved_tail: &'a [u8],
221}
222
223impl S2XSatelliteDeliverySystem<'_> {
224 #[must_use]
227 pub fn frequency_hz(&self) -> Option<u64> {
228 dvb_common::bcd::bcd_to_decimal(u64::from(self.frequency), 8).map(|v| v * 10_000)
229 }
230
231 #[must_use]
233 pub fn orbital_position_deg(&self) -> Option<f64> {
234 dvb_common::bcd::bcd_to_decimal(u64::from(self.orbital_position), 4)
235 .map(|tenths| tenths as f64 / 10.0)
236 }
237
238 #[must_use]
240 pub fn symbol_rate_sps(&self) -> Option<u64> {
241 dvb_common::bcd::bcd_to_decimal(u64::from(self.symbol_rate), 7).map(|v| v * 100)
242 }
243
244 #[must_use]
248 pub fn receiver_broadcast(&self) -> bool {
249 (self.receiver_profiles & RP_BROADCAST) != 0
250 }
251
252 #[must_use]
254 pub fn receiver_interactive(&self) -> bool {
255 (self.receiver_profiles & RP_INTERACTIVE) != 0
256 }
257
258 #[must_use]
260 pub fn receiver_dsng(&self) -> bool {
261 (self.receiver_profiles & RP_DSNG) != 0
262 }
263
264 #[must_use]
266 pub fn receiver_professional(&self) -> bool {
267 (self.receiver_profiles & RP_PROFESSIONAL) != 0
268 }
269
270 #[must_use]
272 pub fn receiver_vl_snr(&self) -> bool {
273 (self.receiver_profiles & RP_VL_SNR) != 0
274 }
275}
276
277const BOND_BASE_LEN: usize = S2X_PRIMARY_LEN;
278
279fn parse_channel_common(
280 sel: &[u8],
281 pos: &mut usize,
282) -> Result<(u32, u16, bool, Polarization, bool, RollOff, u32)> {
283 if sel.len() < *pos + BOND_BASE_LEN {
284 return Err(Error::BufferTooShort {
285 need: *pos + BOND_BASE_LEN,
286 have: sel.len(),
287 what: "S2X body",
288 });
289 }
290 let (freq_bytes, _) = sel[*pos..]
291 .split_first_chunk::<4>()
292 .ok_or(Error::BufferTooShort {
293 need: *pos + BOND_BASE_LEN,
294 have: sel.len(),
295 what: "S2X body",
296 })?;
297 let frequency = u32::from_be_bytes(*freq_bytes);
298 let (orb_bytes, _) = sel[*pos + 4..]
299 .split_first_chunk::<2>()
300 .ok_or(Error::BufferTooShort {
301 need: *pos + BOND_BASE_LEN,
302 have: sel.len(),
303 what: "S2X body",
304 })?;
305 let orbital_position = u16::from_be_bytes(*orb_bytes);
306 let pb = sel[*pos + 6];
307 let west_east_flag = (pb & 0x80) != 0;
308 let polarization = Polarization::from_u8((pb >> 5) & 0x03);
309 let multiple_input_stream_flag = (pb & 0x10) != 0;
310 let roll_off = RollOff::from_u8(pb & 0x07);
311 let symbol_rate = (u32::from(sel[*pos + 7] & 0x0F) << 24)
312 | (u32::from(sel[*pos + 8]) << 16)
313 | (u32::from(sel[*pos + 9]) << 8)
314 | u32::from(sel[*pos + 10]);
315 *pos += BOND_BASE_LEN;
316 Ok((
317 frequency,
318 orbital_position,
319 west_east_flag,
320 polarization,
321 multiple_input_stream_flag,
322 roll_off,
323 symbol_rate,
324 ))
325}
326
327fn write_channel_common(
328 buf: &mut [u8],
329 p: &mut usize,
330 frequency: u32,
331 orbital_position: u16,
332 packed: u8,
333 symbol_rate: u32,
334) {
335 buf[*p..*p + 4].copy_from_slice(&frequency.to_be_bytes());
336 buf[*p + 4..*p + 6].copy_from_slice(&orbital_position.to_be_bytes());
337 buf[*p + 6] = packed;
338 let sr = symbol_rate & 0x0FFF_FFFF;
339 buf[*p + 7] = (sr >> 24) as u8 & 0x0F;
340 buf[*p + 8] = (sr >> 16) as u8;
341 buf[*p + 9] = (sr >> 8) as u8;
342 buf[*p + 10] = sr as u8;
343 *p += BOND_BASE_LEN;
344}
345
346fn pack_we_pol_mis_ro(we: bool, pol: Polarization, mis: bool, ro: RollOff) -> u8 {
347 (u8::from(we) << 7) | ((pol.to_u8() & 0x03) << 5) | (u8::from(mis) << 4) | (ro.to_u8() & 0x07)
348}
349
350impl<'a> Parse<'a> for S2XSatelliteDeliverySystem<'a> {
351 type Error = crate::error::Error;
352 fn parse(sel: &'a [u8]) -> Result<Self> {
353 if sel.len() < 2 {
355 return Err(Error::BufferTooShort {
356 need: 2,
357 have: sel.len(),
358 what: "S2X body",
359 });
360 }
361 let receiver_profiles = sel[0] >> 3;
362 let b1 = sel[1];
363 let s2x_mode = S2XMode::from_u8((b1 >> 6) & 0x03);
366 let scrambling_sequence_selector = (b1 & 0x20) != 0;
367 let ts_gs_s2x_mode = TsGsS2XMode::from_u8(b1 & 0x03);
368 let mut pos = 2;
369 let scrambling_sequence_index = if scrambling_sequence_selector {
370 if sel.len() < pos + S2X_SCRAMBLING_LEN {
371 return Err(Error::BufferTooShort {
372 need: pos + S2X_SCRAMBLING_LEN,
373 have: sel.len(),
374 what: "S2X body",
375 });
376 }
377 let idx = (u32::from(sel[pos] & 0x03) << 16)
378 | (u32::from(sel[pos + 1]) << 8)
379 | u32::from(sel[pos + 2]);
380 pos += S2X_SCRAMBLING_LEN;
381 Some(idx)
382 } else {
383 None
384 };
385 let (
389 frequency,
390 orbital_position,
391 west_east_flag,
392 polarization,
393 multiple_input_stream_flag,
394 roll_off,
395 symbol_rate,
396 ) = parse_channel_common(sel, &mut pos)?;
397 let input_stream_identifier = if multiple_input_stream_flag {
398 if sel.len() < pos + 1 {
399 return Err(Error::BufferTooShort {
400 need: pos + 1,
401 have: sel.len(),
402 what: "S2X body",
403 });
404 }
405 let isi = sel[pos];
406 pos += 1;
407 Some(isi)
408 } else {
409 None
410 };
411 let timeslice_number = if s2x_mode == S2XMode::S2XTimeSlicing {
412 if sel.len() < pos + 1 {
413 return Err(Error::BufferTooShort {
414 need: pos + 1,
415 have: sel.len(),
416 what: "S2X body",
417 });
418 }
419 let ts = sel[pos];
420 pos += 1;
421 Some(ts)
422 } else {
423 None
424 };
425 let (channel_bonds, reserved_tail) = if s2x_mode == S2XMode::S2XChannelBonding {
426 if sel.len() < pos + 1 {
428 return Err(Error::BufferTooShort {
429 need: pos + 1,
430 have: sel.len(),
431 what: "S2X body",
432 });
433 }
434 let bond_byte = sel[pos];
435 pos += 1;
436 let num_channel_bonds = (bond_byte & 0x01) as usize + 1;
438 let mut bonds = Vec::with_capacity(num_channel_bonds);
439 for _ in 0..num_channel_bonds {
440 let (freq, orb, we, pol, mis, ro, sr) = parse_channel_common(sel, &mut pos)?;
441 let isi = if mis {
442 if sel.len() < pos + 1 {
443 return Err(Error::BufferTooShort {
444 need: pos + 1,
445 have: sel.len(),
446 what: "S2X body",
447 });
448 }
449 let v = sel[pos];
450 pos += 1;
451 Some(v)
452 } else {
453 None
454 };
455 bonds.push(S2XChannelBond {
456 frequency: freq,
457 orbital_position: orb,
458 west_east_flag: we,
459 polarization: pol,
460 multiple_input_stream_flag: mis,
461 roll_off: ro,
462 symbol_rate: sr,
463 input_stream_identifier: isi,
464 });
465 }
466 (bonds, &sel[pos..])
467 } else {
468 (Vec::new(), &sel[pos..])
469 };
470 Ok(S2XSatelliteDeliverySystem {
471 receiver_profiles,
472 s2x_mode,
473 scrambling_sequence_selector,
474 ts_gs_s2x_mode,
475 scrambling_sequence_index,
476 frequency,
477 orbital_position,
478 west_east_flag,
479 polarization,
480 multiple_input_stream_flag,
481 roll_off,
482 symbol_rate,
483 input_stream_identifier,
484 timeslice_number,
485 channel_bonds,
486 reserved_tail,
487 })
488 }
489}
490
491impl Serialize for S2XSatelliteDeliverySystem<'_> {
492 type Error = crate::error::Error;
493 fn serialized_len(&self) -> usize {
494 let bond_len: usize = if self.s2x_mode == S2XMode::S2XChannelBonding {
495 1 + self
496 .channel_bonds
497 .iter()
498 .map(|b| BOND_BASE_LEN + usize::from(b.input_stream_identifier.is_some()))
499 .sum::<usize>()
500 } else {
501 0
502 };
503 2 + if self.scrambling_sequence_selector {
504 S2X_SCRAMBLING_LEN
505 } else {
506 0
507 } + S2X_PRIMARY_LEN
508 + usize::from(self.input_stream_identifier.is_some())
509 + usize::from(self.timeslice_number.is_some())
510 + bond_len
511 + self.reserved_tail.len()
512 }
513 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
514 let len = self.serialized_len();
515 if buf.len() < len {
516 return Err(Error::OutputBufferTooSmall {
517 need: len,
518 have: buf.len(),
519 });
520 }
521 buf[0] = self.receiver_profiles << 3;
522 buf[1] = ((self.s2x_mode.to_u8() & 0x03) << 6)
523 | (u8::from(self.scrambling_sequence_selector) << 5)
524 | (self.ts_gs_s2x_mode.to_u8() & 0x03);
525 let mut p = 2;
526 if self.scrambling_sequence_selector {
527 let idx = self.scrambling_sequence_index.unwrap_or(0) & 0x3FFFF;
528 buf[p] = (idx >> 16) as u8 & 0x03;
529 buf[p + 1] = (idx >> 8) as u8;
530 buf[p + 2] = idx as u8;
531 p += S2X_SCRAMBLING_LEN;
532 }
533 write_channel_common(
534 buf,
535 &mut p,
536 self.frequency,
537 self.orbital_position,
538 pack_we_pol_mis_ro(
539 self.west_east_flag,
540 self.polarization,
541 self.multiple_input_stream_flag,
542 self.roll_off,
543 ),
544 self.symbol_rate,
545 );
546 if let Some(isi) = self.input_stream_identifier {
547 buf[p] = isi;
548 p += 1;
549 }
550 if let Some(ts) = self.timeslice_number {
551 buf[p] = ts;
552 p += 1;
553 }
554 if self.s2x_mode == S2XMode::S2XChannelBonding {
555 buf[p] = (self.channel_bonds.len() as u8).saturating_sub(1) & 0x01;
557 p += 1;
558 for bond in &self.channel_bonds {
559 write_channel_common(
560 buf,
561 &mut p,
562 bond.frequency,
563 bond.orbital_position,
564 pack_we_pol_mis_ro(
565 bond.west_east_flag,
566 bond.polarization,
567 bond.multiple_input_stream_flag,
568 bond.roll_off,
569 ),
570 bond.symbol_rate,
571 );
572 if let Some(isi) = bond.input_stream_identifier {
573 buf[p] = isi;
574 p += 1;
575 }
576 }
577 }
578 buf[p..p + self.reserved_tail.len()].copy_from_slice(self.reserved_tail);
579 Ok(len)
580 }
581}
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586 use crate::descriptors::extension::test_support::*;
587 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
588
589 #[test]
590 fn s2x_mode_roundtrip() {
591 for b in 0..=0xFFu8 {
592 assert_eq!(S2XMode::from_u8(b).to_u8(), b);
593 }
594 }
595
596 #[test]
597 fn ts_gs_s2x_mode_roundtrip() {
598 for b in 0..=0xFFu8 {
599 assert_eq!(TsGsS2XMode::from_u8(b).to_u8(), b);
600 }
601 }
602
603 #[test]
604 fn parse_s2x_primary_with_isi_and_timeslice() {
605 let b0 = 0x05 << 3;
607 let b1 = (0x02 << 6) | 0x01; let mut sel = vec![b0, b1];
609 sel.extend_from_slice(&0x0102_0304u32.to_be_bytes()); sel.extend_from_slice(&0x00C8u16.to_be_bytes()); sel.push((1 << 7) | (0x02 << 5) | (1 << 4) | 0x03); let sr: u32 = 0x0AB_CDEF; sel.push((sr >> 24) as u8 & 0x0F);
614 sel.push((sr >> 16) as u8);
615 sel.push((sr >> 8) as u8);
616 sel.push(sr as u8);
617 sel.push(0x42); sel.push(0x09); let bytes = wrap(0x17, &sel);
620 let d = ExtensionDescriptor::parse(&bytes).unwrap();
621 match &d.body {
622 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
623 assert_eq!(b.receiver_profiles, 0x05);
624 assert_eq!(b.s2x_mode, S2XMode::S2XTimeSlicing);
625 assert!(!b.scrambling_sequence_selector);
626 assert_eq!(b.ts_gs_s2x_mode, TsGsS2XMode::Gse);
627 assert_eq!(b.frequency, 0x0102_0304);
628 assert_eq!(b.orbital_position, 0x00C8);
629 assert!(b.west_east_flag);
630 assert_eq!(b.polarization, Polarization::CircularLeft);
631 assert!(b.multiple_input_stream_flag);
632 assert_eq!(b.roll_off, RollOff::Reserved(3));
633 assert_eq!(b.symbol_rate, 0x0AB_CDEF);
634 assert_eq!(b.input_stream_identifier, Some(0x42));
635 assert_eq!(b.timeslice_number, Some(0x09));
636 assert!(b.channel_bonds.is_empty());
637 assert!(b.reserved_tail.is_empty());
638 }
639 other => panic!("expected S2X, got {other:?}"),
640 }
641 round_trip(&d);
642 }
643
644 #[test]
645 fn parse_s2x_with_scrambling_index() {
646 let b0 = 0x01 << 3;
647 let b1 = (0x01 << 6) | 0x20; let mut sel = vec![b0, b1];
649 sel.push(0x02);
651 sel.push(0xAB);
652 sel.push(0xCD);
653 sel.extend_from_slice(&0u32.to_be_bytes()); sel.extend_from_slice(&0u16.to_be_bytes()); sel.push(0x00); sel.extend_from_slice(&[0, 0, 0, 0]); let bytes = wrap(0x17, &sel);
658 let d = ExtensionDescriptor::parse(&bytes).unwrap();
659 match &d.body {
660 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
661 assert!(b.scrambling_sequence_selector);
662 assert_eq!(b.scrambling_sequence_index, Some(0x2ABCD));
663 assert_eq!(b.input_stream_identifier, None);
664 assert_eq!(b.timeslice_number, None);
665 assert!(b.channel_bonds.is_empty());
666 assert!(b.reserved_tail.is_empty());
667 }
668 other => panic!("expected S2X, got {other:?}"),
669 }
670 round_trip(&d);
671 }
672
673 #[test]
674 fn parse_s2x_mode1_tail_preserved() {
675 let b0 = 0x01 << 3;
677 let b1 = 0x01 << 6; let mut sel = vec![b0, b1];
679 sel.extend_from_slice(&0u32.to_be_bytes());
680 sel.extend_from_slice(&0u16.to_be_bytes());
681 sel.push(0x00); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0xAA, 0xBB, 0xCC]); let bytes = wrap(0x17, &sel);
685 let d = ExtensionDescriptor::parse(&bytes).unwrap();
686 match &d.body {
687 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
688 assert_eq!(b.s2x_mode, S2XMode::S2X);
689 assert_eq!(b.timeslice_number, None);
690 assert!(b.channel_bonds.is_empty());
691 assert_eq!(b.reserved_tail, &[0xAA, 0xBB, 0xCC]);
692 }
693 other => panic!("expected S2X, got {other:?}"),
694 }
695 round_trip(&d);
696 }
697
698 #[test]
699 fn parse_s2x_mode3_channel_bonds() {
700 let b0 = 0x01 << 3;
702 let b1 = 0x03 << 6; let mut sel = vec![b0, b1];
704 sel.extend_from_slice(&0x1111_1111u32.to_be_bytes()); sel.extend_from_slice(&0x0001u16.to_be_bytes()); sel.push(0x00); sel.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); sel.push(0x01);
712
713 sel.extend_from_slice(&0x2222_2222u32.to_be_bytes());
716 sel.extend_from_slice(&0x0002u16.to_be_bytes());
717 sel.push((1 << 7) | (0x02 << 5) | (1 << 4) | 0x03); let sr: u32 = 0x0AB_CDEF;
719 sel.push((sr >> 24) as u8 & 0x0F);
720 sel.push((sr >> 16) as u8);
721 sel.push((sr >> 8) as u8);
722 sel.push(sr as u8);
723 sel.push(0x77); sel.extend_from_slice(&0x3333_3333u32.to_be_bytes());
728 sel.extend_from_slice(&0x0003u16.to_be_bytes());
729 sel.push((0x01 << 5) | 0x04); let sr2: u32 = 0x005_4321;
731 sel.push((sr2 >> 24) as u8 & 0x0F);
732 sel.push((sr2 >> 16) as u8);
733 sel.push((sr2 >> 8) as u8);
734 sel.push(sr2 as u8);
735
736 let bytes = wrap(0x17, &sel);
737 let d = ExtensionDescriptor::parse(&bytes).unwrap();
738 match &d.body {
739 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
740 assert_eq!(b.s2x_mode, S2XMode::S2XChannelBonding);
741 assert_eq!(b.channel_bonds.len(), 2);
742
743 let b0 = &b.channel_bonds[0];
744 assert_eq!(b0.frequency, 0x2222_2222);
745 assert_eq!(b0.orbital_position, 0x0002);
746 assert!(b0.west_east_flag);
747 assert_eq!(b0.polarization, Polarization::CircularLeft);
748 assert!(b0.multiple_input_stream_flag);
749 assert_eq!(b0.roll_off, RollOff::Reserved(3));
750 assert_eq!(b0.symbol_rate, 0x0AB_CDEF);
751 assert_eq!(b0.input_stream_identifier, Some(0x77));
752
753 let b1 = &b.channel_bonds[1];
754 assert_eq!(b1.frequency, 0x3333_3333);
755 assert_eq!(b1.orbital_position, 0x0003);
756 assert!(!b1.west_east_flag);
757 assert_eq!(b1.polarization, Polarization::LinearVertical);
758 assert!(!b1.multiple_input_stream_flag);
759 assert_eq!(b1.roll_off, RollOff::Reserved(4));
760 assert_eq!(b1.symbol_rate, 0x005_4321);
761 assert_eq!(b1.input_stream_identifier, None);
762
763 assert!(b.reserved_tail.is_empty());
764 }
765 other => panic!("expected S2X, got {other:?}"),
766 }
767 round_trip(&d);
768 }
769
770 #[test]
771 fn tsduck_s2x_mode3_byte_exact() {
772 let hex = "7f2a1750e3023456876543210037250456789601065432180340f600246754bd00654367123451000087642e";
775 let bytes = from_hex(hex);
776 let d = ExtensionDescriptor::parse(&bytes)
777 .unwrap_or_else(|e| panic!("parse tsduck s2x: {e:?}"));
778
779 assert_eq!(d.kind(), Some(ExtensionTag::S2XSatelliteDeliverySystem));
780 match &d.body {
781 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
782 assert_eq!(b.s2x_mode, S2XMode::S2XChannelBonding);
783 assert!(b.scrambling_sequence_selector);
784 assert_eq!(b.scrambling_sequence_index, Some(0x023456));
785 assert!(!b.channel_bonds.is_empty());
786 assert_eq!(b.channel_bonds.len(), 2);
787
788 let b0 = &b.channel_bonds[0];
789 assert_eq!(b0.frequency, 0x0654_3218);
790 assert_eq!(b0.orbital_position, 0x0340);
791 assert!(b0.west_east_flag);
792 assert_eq!(b0.polarization, Polarization::CircularRight);
793 assert!(b0.multiple_input_stream_flag);
794 assert_eq!(b0.roll_off, RollOff::Reserved(6));
795 assert_eq!(b0.symbol_rate, 0x0024_6754);
796 assert_eq!(b0.input_stream_identifier, Some(0xBD));
797
798 let b1 = &b.channel_bonds[1];
799 assert_eq!(b1.frequency, 0x0065_4367);
800 assert_eq!(b1.orbital_position, 0x1234);
801 assert!(!b1.west_east_flag);
802 assert_eq!(b1.polarization, Polarization::CircularLeft);
803 assert!(b1.multiple_input_stream_flag);
804 assert_eq!(b1.roll_off, RollOff::Alpha025);
805 assert_eq!(b1.symbol_rate, 0x0000_8764);
806 assert_eq!(b1.input_stream_identifier, Some(0x2E));
807
808 assert!(b.reserved_tail.is_empty());
809 }
810 other => panic!("expected S2X, got {other:?}"),
811 }
812
813 let mut out = vec![0u8; d.serialized_len()];
815 let n = d.serialize_into(&mut out).unwrap();
816 assert_eq!(
817 &out[..n],
818 &bytes[..],
819 "S2X mode 3 byte-exact re-serialize failed"
820 );
821 }
822}