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