1use super::*;
6use crate::descriptors::satellite_delivery_system::{Polarization, RollOff};
7
8impl<'a> ExtensionBodyDef<'a> for S2XSatelliteDeliverySystem<'a> {
9 const TAG_EXTENSION: u8 = 0x17;
10 const NAME: &'static str = "S2X_SATELLITE_DELIVERY_SYSTEM";
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[non_exhaustive]
21pub enum S2XMode {
22 Reserved0,
24 S2X,
26 S2XTimeSlicing,
28 S2XChannelBonding,
30 Reserved(u8),
32}
33
34impl S2XMode {
35 #[must_use]
36 pub fn from_u8(v: u8) -> Self {
38 match v {
39 0 => S2XMode::Reserved0,
40 1 => S2XMode::S2X,
41 2 => S2XMode::S2XTimeSlicing,
42 3 => S2XMode::S2XChannelBonding,
43 other => S2XMode::Reserved(other),
44 }
45 }
46
47 #[must_use]
48 pub fn to_u8(self) -> u8 {
50 match self {
51 S2XMode::Reserved0 => 0,
52 S2XMode::S2X => 1,
53 S2XMode::S2XTimeSlicing => 2,
54 S2XMode::S2XChannelBonding => 3,
55 S2XMode::Reserved(v) => v,
56 }
57 }
58
59 #[must_use]
60 pub fn name(self) -> &'static str {
62 match self {
63 S2XMode::Reserved0 => "reserved for future use",
64 S2XMode::S2X => "S2X",
65 S2XMode::S2XTimeSlicing => "S2X + time slicing",
66 S2XMode::S2XChannelBonding => "S2X + channel bonding",
67 S2XMode::Reserved(_) => "reserved",
68 }
69 }
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize))]
75#[non_exhaustive]
76pub enum TsGsS2XMode {
77 GenericPacketized,
79 Gse,
81 GseHighEfficiency,
83 DvbTransportStream,
85 Reserved(u8),
87}
88
89impl TsGsS2XMode {
90 #[must_use]
91 pub fn from_u8(v: u8) -> Self {
93 match v {
94 0 => TsGsS2XMode::GenericPacketized,
95 1 => TsGsS2XMode::Gse,
96 2 => TsGsS2XMode::GseHighEfficiency,
97 3 => TsGsS2XMode::DvbTransportStream,
98 other => TsGsS2XMode::Reserved(other),
99 }
100 }
101
102 #[must_use]
103 pub fn to_u8(self) -> u8 {
105 match self {
106 TsGsS2XMode::GenericPacketized => 0,
107 TsGsS2XMode::Gse => 1,
108 TsGsS2XMode::GseHighEfficiency => 2,
109 TsGsS2XMode::DvbTransportStream => 3,
110 TsGsS2XMode::Reserved(v) => v,
111 }
112 }
113
114 #[must_use]
115 pub fn name(self) -> &'static str {
117 match self {
118 TsGsS2XMode::GenericPacketized => "generic packetized",
119 TsGsS2XMode::Gse => "GSE",
120 TsGsS2XMode::GseHighEfficiency => "GSE high efficiency mode",
121 TsGsS2XMode::DvbTransportStream => "DVB transport stream",
122 TsGsS2XMode::Reserved(_) => "reserved",
123 }
124 }
125}
126
127const RP_BROADCAST: u8 = 0x01;
129const RP_INTERACTIVE: u8 = 0x02;
130const RP_DSNG: u8 = 0x04;
131const RP_PROFESSIONAL: u8 = 0x08;
132const RP_VL_SNR: u8 = 0x10;
133
134#[derive(Debug, Clone, PartialEq, Eq)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize))]
140pub struct S2XChannelBond {
141 pub frequency: u32,
143 pub orbital_position: u16,
145 pub west_east_flag: bool,
147 pub polarization: Polarization,
149 pub multiple_input_stream_flag: bool,
151 pub roll_off: RollOff,
153 pub symbol_rate: u32,
155 pub input_stream_identifier: Option<u8>,
157}
158
159impl S2XChannelBond {
160 #[must_use]
163 pub fn frequency_hz(&self) -> Option<u64> {
164 dvb_common::bcd::bcd_to_decimal(u64::from(self.frequency), 8).map(|v| v * 10_000)
165 }
166
167 #[must_use]
169 pub fn orbital_position_deg(&self) -> Option<f64> {
170 dvb_common::bcd::bcd_to_decimal(u64::from(self.orbital_position), 4)
171 .map(|tenths| tenths as f64 / 10.0)
172 }
173
174 #[must_use]
176 pub fn symbol_rate_sps(&self) -> Option<u64> {
177 dvb_common::bcd::bcd_to_decimal(u64::from(self.symbol_rate), 7).map(|v| v * 100)
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize))]
184#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
185pub struct S2XSatelliteDeliverySystem<'a> {
186 pub receiver_profiles: u8,
188 pub s2x_mode: S2XMode,
190 pub scrambling_sequence_selector: bool,
192 pub ts_gs_s2x_mode: TsGsS2XMode,
194 pub scrambling_sequence_index: Option<u32>,
196 pub frequency: u32,
198 pub orbital_position: u16,
200 pub west_east_flag: bool,
202 pub polarization: Polarization,
204 pub multiple_input_stream_flag: bool,
206 pub roll_off: RollOff,
208 pub symbol_rate: u32,
210 pub input_stream_identifier: Option<u8>,
212 pub timeslice_number: Option<u8>,
214 pub channel_bonds: Vec<S2XChannelBond>,
216 pub reserved_tail: &'a [u8],
218}
219
220impl S2XSatelliteDeliverySystem<'_> {
221 #[must_use]
224 pub fn frequency_hz(&self) -> Option<u64> {
225 dvb_common::bcd::bcd_to_decimal(u64::from(self.frequency), 8).map(|v| v * 10_000)
226 }
227
228 #[must_use]
230 pub fn orbital_position_deg(&self) -> Option<f64> {
231 dvb_common::bcd::bcd_to_decimal(u64::from(self.orbital_position), 4)
232 .map(|tenths| tenths as f64 / 10.0)
233 }
234
235 #[must_use]
237 pub fn symbol_rate_sps(&self) -> Option<u64> {
238 dvb_common::bcd::bcd_to_decimal(u64::from(self.symbol_rate), 7).map(|v| v * 100)
239 }
240
241 #[must_use]
245 pub fn receiver_broadcast(&self) -> bool {
246 (self.receiver_profiles & RP_BROADCAST) != 0
247 }
248
249 #[must_use]
251 pub fn receiver_interactive(&self) -> bool {
252 (self.receiver_profiles & RP_INTERACTIVE) != 0
253 }
254
255 #[must_use]
257 pub fn receiver_dsng(&self) -> bool {
258 (self.receiver_profiles & RP_DSNG) != 0
259 }
260
261 #[must_use]
263 pub fn receiver_professional(&self) -> bool {
264 (self.receiver_profiles & RP_PROFESSIONAL) != 0
265 }
266
267 #[must_use]
269 pub fn receiver_vl_snr(&self) -> bool {
270 (self.receiver_profiles & RP_VL_SNR) != 0
271 }
272}
273
274const BOND_BASE_LEN: usize = S2X_PRIMARY_LEN;
275
276fn parse_channel_common(
277 sel: &[u8],
278 pos: &mut usize,
279) -> Result<(u32, u16, bool, Polarization, bool, RollOff, u32)> {
280 if sel.len() < *pos + BOND_BASE_LEN {
281 return Err(Error::BufferTooShort {
282 need: *pos + BOND_BASE_LEN,
283 have: sel.len(),
284 what: "S2X body",
285 });
286 }
287 let frequency = u32::from_be_bytes([sel[*pos], sel[*pos + 1], sel[*pos + 2], sel[*pos + 3]]);
288 let orbital_position = u16::from_be_bytes([sel[*pos + 4], sel[*pos + 5]]);
289 let pb = sel[*pos + 6];
290 let west_east_flag = (pb & 0x80) != 0;
291 let polarization = Polarization::from_u8((pb >> 5) & 0x03);
292 let multiple_input_stream_flag = (pb & 0x10) != 0;
293 let roll_off = RollOff::from_u8(pb & 0x07);
294 let symbol_rate = (u32::from(sel[*pos + 7] & 0x0F) << 24)
295 | (u32::from(sel[*pos + 8]) << 16)
296 | (u32::from(sel[*pos + 9]) << 8)
297 | u32::from(sel[*pos + 10]);
298 *pos += BOND_BASE_LEN;
299 Ok((
300 frequency,
301 orbital_position,
302 west_east_flag,
303 polarization,
304 multiple_input_stream_flag,
305 roll_off,
306 symbol_rate,
307 ))
308}
309
310fn write_channel_common(
311 buf: &mut [u8],
312 p: &mut usize,
313 frequency: u32,
314 orbital_position: u16,
315 packed: u8,
316 symbol_rate: u32,
317) {
318 buf[*p..*p + 4].copy_from_slice(&frequency.to_be_bytes());
319 buf[*p + 4..*p + 6].copy_from_slice(&orbital_position.to_be_bytes());
320 buf[*p + 6] = packed;
321 let sr = symbol_rate & 0x0FFF_FFFF;
322 buf[*p + 7] = (sr >> 24) as u8 & 0x0F;
323 buf[*p + 8] = (sr >> 16) as u8;
324 buf[*p + 9] = (sr >> 8) as u8;
325 buf[*p + 10] = sr as u8;
326 *p += BOND_BASE_LEN;
327}
328
329fn pack_we_pol_mis_ro(we: bool, pol: Polarization, mis: bool, ro: RollOff) -> u8 {
330 (u8::from(we) << 7) | ((pol.to_u8() & 0x03) << 5) | (u8::from(mis) << 4) | (ro.to_u8() & 0x07)
331}
332
333impl<'a> Parse<'a> for S2XSatelliteDeliverySystem<'a> {
334 type Error = crate::error::Error;
335 fn parse(sel: &'a [u8]) -> Result<Self> {
336 if sel.len() < 2 {
338 return Err(Error::BufferTooShort {
339 need: 2,
340 have: sel.len(),
341 what: "S2X body",
342 });
343 }
344 let receiver_profiles = sel[0] >> 3;
345 let b1 = sel[1];
346 let s2x_mode = S2XMode::from_u8((b1 >> 6) & 0x03);
349 let scrambling_sequence_selector = (b1 & 0x20) != 0;
350 let ts_gs_s2x_mode = TsGsS2XMode::from_u8(b1 & 0x03);
351 let mut pos = 2;
352 let scrambling_sequence_index = if scrambling_sequence_selector {
353 if sel.len() < pos + S2X_SCRAMBLING_LEN {
354 return Err(Error::BufferTooShort {
355 need: pos + S2X_SCRAMBLING_LEN,
356 have: sel.len(),
357 what: "S2X body",
358 });
359 }
360 let idx = (u32::from(sel[pos] & 0x03) << 16)
361 | (u32::from(sel[pos + 1]) << 8)
362 | u32::from(sel[pos + 2]);
363 pos += S2X_SCRAMBLING_LEN;
364 Some(idx)
365 } else {
366 None
367 };
368 let (
372 frequency,
373 orbital_position,
374 west_east_flag,
375 polarization,
376 multiple_input_stream_flag,
377 roll_off,
378 symbol_rate,
379 ) = parse_channel_common(sel, &mut pos)?;
380 let input_stream_identifier = if multiple_input_stream_flag {
381 if sel.len() < pos + 1 {
382 return Err(Error::BufferTooShort {
383 need: pos + 1,
384 have: sel.len(),
385 what: "S2X body",
386 });
387 }
388 let isi = sel[pos];
389 pos += 1;
390 Some(isi)
391 } else {
392 None
393 };
394 let timeslice_number = if s2x_mode == S2XMode::S2XTimeSlicing {
395 if sel.len() < pos + 1 {
396 return Err(Error::BufferTooShort {
397 need: pos + 1,
398 have: sel.len(),
399 what: "S2X body",
400 });
401 }
402 let ts = sel[pos];
403 pos += 1;
404 Some(ts)
405 } else {
406 None
407 };
408 let (channel_bonds, reserved_tail) = if s2x_mode == S2XMode::S2XChannelBonding {
409 if sel.len() < pos + 1 {
411 return Err(Error::BufferTooShort {
412 need: pos + 1,
413 have: sel.len(),
414 what: "S2X body",
415 });
416 }
417 let bond_byte = sel[pos];
418 pos += 1;
419 let num_channel_bonds = (bond_byte & 0x01) as usize + 1;
421 let mut bonds = Vec::with_capacity(num_channel_bonds);
422 for _ in 0..num_channel_bonds {
423 let (freq, orb, we, pol, mis, ro, sr) = parse_channel_common(sel, &mut pos)?;
424 let isi = if mis {
425 if sel.len() < pos + 1 {
426 return Err(Error::BufferTooShort {
427 need: pos + 1,
428 have: sel.len(),
429 what: "S2X body",
430 });
431 }
432 let v = sel[pos];
433 pos += 1;
434 Some(v)
435 } else {
436 None
437 };
438 bonds.push(S2XChannelBond {
439 frequency: freq,
440 orbital_position: orb,
441 west_east_flag: we,
442 polarization: pol,
443 multiple_input_stream_flag: mis,
444 roll_off: ro,
445 symbol_rate: sr,
446 input_stream_identifier: isi,
447 });
448 }
449 (bonds, &sel[pos..])
450 } else {
451 (Vec::new(), &sel[pos..])
452 };
453 Ok(S2XSatelliteDeliverySystem {
454 receiver_profiles,
455 s2x_mode,
456 scrambling_sequence_selector,
457 ts_gs_s2x_mode,
458 scrambling_sequence_index,
459 frequency,
460 orbital_position,
461 west_east_flag,
462 polarization,
463 multiple_input_stream_flag,
464 roll_off,
465 symbol_rate,
466 input_stream_identifier,
467 timeslice_number,
468 channel_bonds,
469 reserved_tail,
470 })
471 }
472}
473
474impl Serialize for S2XSatelliteDeliverySystem<'_> {
475 type Error = crate::error::Error;
476 fn serialized_len(&self) -> usize {
477 let bond_len: usize = if self.s2x_mode == S2XMode::S2XChannelBonding {
478 1 + self
479 .channel_bonds
480 .iter()
481 .map(|b| BOND_BASE_LEN + usize::from(b.input_stream_identifier.is_some()))
482 .sum::<usize>()
483 } else {
484 0
485 };
486 2 + if self.scrambling_sequence_selector {
487 S2X_SCRAMBLING_LEN
488 } else {
489 0
490 } + S2X_PRIMARY_LEN
491 + usize::from(self.input_stream_identifier.is_some())
492 + usize::from(self.timeslice_number.is_some())
493 + bond_len
494 + self.reserved_tail.len()
495 }
496 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
497 let len = self.serialized_len();
498 if buf.len() < len {
499 return Err(Error::OutputBufferTooSmall {
500 need: len,
501 have: buf.len(),
502 });
503 }
504 buf[0] = self.receiver_profiles << 3;
505 buf[1] = ((self.s2x_mode.to_u8() & 0x03) << 6)
506 | (u8::from(self.scrambling_sequence_selector) << 5)
507 | (self.ts_gs_s2x_mode.to_u8() & 0x03);
508 let mut p = 2;
509 if self.scrambling_sequence_selector {
510 let idx = self.scrambling_sequence_index.unwrap_or(0) & 0x3FFFF;
511 buf[p] = (idx >> 16) as u8 & 0x03;
512 buf[p + 1] = (idx >> 8) as u8;
513 buf[p + 2] = idx as u8;
514 p += S2X_SCRAMBLING_LEN;
515 }
516 write_channel_common(
517 buf,
518 &mut p,
519 self.frequency,
520 self.orbital_position,
521 pack_we_pol_mis_ro(
522 self.west_east_flag,
523 self.polarization,
524 self.multiple_input_stream_flag,
525 self.roll_off,
526 ),
527 self.symbol_rate,
528 );
529 if let Some(isi) = self.input_stream_identifier {
530 buf[p] = isi;
531 p += 1;
532 }
533 if let Some(ts) = self.timeslice_number {
534 buf[p] = ts;
535 p += 1;
536 }
537 if self.s2x_mode == S2XMode::S2XChannelBonding {
538 buf[p] = (self.channel_bonds.len() as u8).saturating_sub(1) & 0x01;
540 p += 1;
541 for bond in &self.channel_bonds {
542 write_channel_common(
543 buf,
544 &mut p,
545 bond.frequency,
546 bond.orbital_position,
547 pack_we_pol_mis_ro(
548 bond.west_east_flag,
549 bond.polarization,
550 bond.multiple_input_stream_flag,
551 bond.roll_off,
552 ),
553 bond.symbol_rate,
554 );
555 if let Some(isi) = bond.input_stream_identifier {
556 buf[p] = isi;
557 p += 1;
558 }
559 }
560 }
561 buf[p..p + self.reserved_tail.len()].copy_from_slice(self.reserved_tail);
562 Ok(len)
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use super::*;
569 use crate::descriptors::extension::test_support::*;
570 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
571
572 #[test]
573 fn s2x_mode_roundtrip() {
574 for b in 0..=0xFFu8 {
575 assert_eq!(S2XMode::from_u8(b).to_u8(), b);
576 }
577 }
578
579 #[test]
580 fn ts_gs_s2x_mode_roundtrip() {
581 for b in 0..=0xFFu8 {
582 assert_eq!(TsGsS2XMode::from_u8(b).to_u8(), b);
583 }
584 }
585
586 #[test]
587 fn parse_s2x_primary_with_isi_and_timeslice() {
588 let b0 = 0x05 << 3;
590 let b1 = (0x02 << 6) | 0x01; let mut sel = vec![b0, b1];
592 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);
597 sel.push((sr >> 16) as u8);
598 sel.push((sr >> 8) as u8);
599 sel.push(sr as u8);
600 sel.push(0x42); sel.push(0x09); let bytes = wrap(0x17, &sel);
603 let d = ExtensionDescriptor::parse(&bytes).unwrap();
604 match &d.body {
605 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
606 assert_eq!(b.receiver_profiles, 0x05);
607 assert_eq!(b.s2x_mode, S2XMode::S2XTimeSlicing);
608 assert!(!b.scrambling_sequence_selector);
609 assert_eq!(b.ts_gs_s2x_mode, TsGsS2XMode::Gse);
610 assert_eq!(b.frequency, 0x0102_0304);
611 assert_eq!(b.orbital_position, 0x00C8);
612 assert!(b.west_east_flag);
613 assert_eq!(b.polarization, Polarization::CircularLeft);
614 assert!(b.multiple_input_stream_flag);
615 assert_eq!(b.roll_off, RollOff::Reserved(3));
616 assert_eq!(b.symbol_rate, 0x0AB_CDEF);
617 assert_eq!(b.input_stream_identifier, Some(0x42));
618 assert_eq!(b.timeslice_number, Some(0x09));
619 assert!(b.channel_bonds.is_empty());
620 assert!(b.reserved_tail.is_empty());
621 }
622 other => panic!("expected S2X, got {other:?}"),
623 }
624 round_trip(&d);
625 }
626
627 #[test]
628 fn parse_s2x_with_scrambling_index() {
629 let b0 = 0x01 << 3;
630 let b1 = (0x01 << 6) | 0x20; let mut sel = vec![b0, b1];
632 sel.push(0x02);
634 sel.push(0xAB);
635 sel.push(0xCD);
636 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);
641 let d = ExtensionDescriptor::parse(&bytes).unwrap();
642 match &d.body {
643 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
644 assert!(b.scrambling_sequence_selector);
645 assert_eq!(b.scrambling_sequence_index, Some(0x2ABCD));
646 assert_eq!(b.input_stream_identifier, None);
647 assert_eq!(b.timeslice_number, None);
648 assert!(b.channel_bonds.is_empty());
649 assert!(b.reserved_tail.is_empty());
650 }
651 other => panic!("expected S2X, got {other:?}"),
652 }
653 round_trip(&d);
654 }
655
656 #[test]
657 fn parse_s2x_mode1_tail_preserved() {
658 let b0 = 0x01 << 3;
660 let b1 = 0x01 << 6; let mut sel = vec![b0, b1];
662 sel.extend_from_slice(&0u32.to_be_bytes());
663 sel.extend_from_slice(&0u16.to_be_bytes());
664 sel.push(0x00); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0xAA, 0xBB, 0xCC]); let bytes = wrap(0x17, &sel);
668 let d = ExtensionDescriptor::parse(&bytes).unwrap();
669 match &d.body {
670 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
671 assert_eq!(b.s2x_mode, S2XMode::S2X);
672 assert_eq!(b.timeslice_number, None);
673 assert!(b.channel_bonds.is_empty());
674 assert_eq!(b.reserved_tail, &[0xAA, 0xBB, 0xCC]);
675 }
676 other => panic!("expected S2X, got {other:?}"),
677 }
678 round_trip(&d);
679 }
680
681 #[test]
682 fn parse_s2x_mode3_channel_bonds() {
683 let b0 = 0x01 << 3;
685 let b1 = 0x03 << 6; let mut sel = vec![b0, b1];
687 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);
695
696 sel.extend_from_slice(&0x2222_2222u32.to_be_bytes());
699 sel.extend_from_slice(&0x0002u16.to_be_bytes());
700 sel.push((1 << 7) | (0x02 << 5) | (1 << 4) | 0x03); let sr: u32 = 0x0AB_CDEF;
702 sel.push((sr >> 24) as u8 & 0x0F);
703 sel.push((sr >> 16) as u8);
704 sel.push((sr >> 8) as u8);
705 sel.push(sr as u8);
706 sel.push(0x77); sel.extend_from_slice(&0x3333_3333u32.to_be_bytes());
711 sel.extend_from_slice(&0x0003u16.to_be_bytes());
712 sel.push((0x01 << 5) | 0x04); let sr2: u32 = 0x005_4321;
714 sel.push((sr2 >> 24) as u8 & 0x0F);
715 sel.push((sr2 >> 16) as u8);
716 sel.push((sr2 >> 8) as u8);
717 sel.push(sr2 as u8);
718
719 let bytes = wrap(0x17, &sel);
720 let d = ExtensionDescriptor::parse(&bytes).unwrap();
721 match &d.body {
722 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
723 assert_eq!(b.s2x_mode, S2XMode::S2XChannelBonding);
724 assert_eq!(b.channel_bonds.len(), 2);
725
726 let b0 = &b.channel_bonds[0];
727 assert_eq!(b0.frequency, 0x2222_2222);
728 assert_eq!(b0.orbital_position, 0x0002);
729 assert!(b0.west_east_flag);
730 assert_eq!(b0.polarization, Polarization::CircularLeft);
731 assert!(b0.multiple_input_stream_flag);
732 assert_eq!(b0.roll_off, RollOff::Reserved(3));
733 assert_eq!(b0.symbol_rate, 0x0AB_CDEF);
734 assert_eq!(b0.input_stream_identifier, Some(0x77));
735
736 let b1 = &b.channel_bonds[1];
737 assert_eq!(b1.frequency, 0x3333_3333);
738 assert_eq!(b1.orbital_position, 0x0003);
739 assert!(!b1.west_east_flag);
740 assert_eq!(b1.polarization, Polarization::LinearVertical);
741 assert!(!b1.multiple_input_stream_flag);
742 assert_eq!(b1.roll_off, RollOff::Reserved(4));
743 assert_eq!(b1.symbol_rate, 0x005_4321);
744 assert_eq!(b1.input_stream_identifier, None);
745
746 assert!(b.reserved_tail.is_empty());
747 }
748 other => panic!("expected S2X, got {other:?}"),
749 }
750 round_trip(&d);
751 }
752
753 #[test]
754 fn tsduck_s2x_mode3_byte_exact() {
755 let hex = "7f2a1750e3023456876543210037250456789601065432180340f600246754bd00654367123451000087642e";
758 let bytes = from_hex(hex);
759 let d = ExtensionDescriptor::parse(&bytes)
760 .unwrap_or_else(|e| panic!("parse tsduck s2x: {e:?}"));
761
762 assert_eq!(d.kind(), Some(ExtensionTag::S2XSatelliteDeliverySystem));
763 match &d.body {
764 ExtensionBody::S2XSatelliteDeliverySystem(b) => {
765 assert_eq!(b.s2x_mode, S2XMode::S2XChannelBonding);
766 assert!(b.scrambling_sequence_selector);
767 assert_eq!(b.scrambling_sequence_index, Some(0x023456));
768 assert!(!b.channel_bonds.is_empty());
769 assert_eq!(b.channel_bonds.len(), 2);
770
771 let b0 = &b.channel_bonds[0];
772 assert_eq!(b0.frequency, 0x0654_3218);
773 assert_eq!(b0.orbital_position, 0x0340);
774 assert!(b0.west_east_flag);
775 assert_eq!(b0.polarization, Polarization::CircularRight);
776 assert!(b0.multiple_input_stream_flag);
777 assert_eq!(b0.roll_off, RollOff::Reserved(6));
778 assert_eq!(b0.symbol_rate, 0x0024_6754);
779 assert_eq!(b0.input_stream_identifier, Some(0xBD));
780
781 let b1 = &b.channel_bonds[1];
782 assert_eq!(b1.frequency, 0x0065_4367);
783 assert_eq!(b1.orbital_position, 0x1234);
784 assert!(!b1.west_east_flag);
785 assert_eq!(b1.polarization, Polarization::CircularLeft);
786 assert!(b1.multiple_input_stream_flag);
787 assert_eq!(b1.roll_off, RollOff::Alpha025);
788 assert_eq!(b1.symbol_rate, 0x0000_8764);
789 assert_eq!(b1.input_stream_identifier, Some(0x2E));
790
791 assert!(b.reserved_tail.is_empty());
792 }
793 other => panic!("expected S2X, got {other:?}"),
794 }
795
796 let mut out = vec![0u8; d.serialized_len()];
798 let n = d.serialize_into(&mut out).unwrap();
799 assert_eq!(
800 &out[..n],
801 &bytes[..],
802 "S2X mode 3 byte-exact re-serialize failed"
803 );
804 }
805}