1use super::*;
13use crate::descriptors::satellite_delivery_system::{Polarization, RollOff};
14use alloc::vec::Vec;
15
16impl<'a> ExtensionBodyDef<'a> for S2Xv2SatelliteDeliverySystem<'a> {
17 const TAG_EXTENSION: u8 = 0x24;
18 const NAME: &'static str = "S2XV2_SATELLITE_DELIVERY_SYSTEM";
19}
20
21const S2XV2_CORE_LEN: usize = 18;
25const S2XV2_SCRAMBLING_LEN: usize = 3;
27const S2XV2_SUPERFRAME_MIN_LEN: usize = 8;
29const S2XV2_SUPERFRAME_BH_LEN: usize = 12;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35#[non_exhaustive]
36pub enum S2Xv2Mode {
37 Reserved0,
39 S2X,
41 S2XTimeSlicing,
43 Reserved3,
45 S2XSuperframe,
47 S2XSuperframeTimeSlicing,
49 Reserved(u8),
51}
52
53impl S2Xv2Mode {
54 #[must_use]
56 pub fn from_u8(v: u8) -> Self {
57 match v {
58 0 => Self::Reserved0,
59 1 => Self::S2X,
60 2 => Self::S2XTimeSlicing,
61 3 => Self::Reserved3,
62 4 => Self::S2XSuperframe,
63 5 => Self::S2XSuperframeTimeSlicing,
64 other => Self::Reserved(other),
65 }
66 }
67
68 #[must_use]
70 pub fn to_u8(self) -> u8 {
71 match self {
72 Self::Reserved0 => 0,
73 Self::S2X => 1,
74 Self::S2XTimeSlicing => 2,
75 Self::Reserved3 => 3,
76 Self::S2XSuperframe => 4,
77 Self::S2XSuperframeTimeSlicing => 5,
78 Self::Reserved(v) => v,
79 }
80 }
81
82 #[must_use]
84 pub fn name(self) -> &'static str {
85 match self {
86 Self::Reserved0 => "reserved for future use",
87 Self::S2X => "S2X",
88 Self::S2XTimeSlicing => "S2X + time slicing",
89 Self::Reserved3 => "reserved for future use",
90 Self::S2XSuperframe => "S2X superframe",
91 Self::S2XSuperframeTimeSlicing => "S2X superframe + timeslicing",
92 Self::Reserved(_) => "reserved",
93 }
94 }
95
96 #[must_use]
99 pub fn has_scrambling_selector(self) -> bool {
100 matches!(self, Self::S2X | Self::S2XTimeSlicing)
101 }
102
103 #[must_use]
106 pub fn has_timeslice(self) -> bool {
107 matches!(self, Self::S2XTimeSlicing | Self::S2XSuperframeTimeSlicing)
108 }
109
110 #[must_use]
113 pub fn has_superframe(self) -> bool {
114 matches!(self, Self::S2XSuperframe | Self::S2XSuperframeTimeSlicing)
115 }
116}
117dvb_common::impl_spec_display!(S2Xv2Mode, Reserved);
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize))]
122pub struct S2Xv2Superframe {
123 pub sosf_wh_sequence_number: u8,
125 pub sffi_selector: bool,
127 pub beam_hopping_time_plan_selector: bool,
129 pub reference_scrambling_index: u32,
131 pub sffi: Option<u8>,
133 pub payload_scrambling_index: u32,
135 pub beamhopping_time_plan_id: Option<u32>,
138 pub superframe_pilots_wh_sequence_number: u8,
140 pub postamble_pli: u8,
142}
143
144impl S2Xv2Superframe {
145 #[must_use]
147 pub fn serialized_len(&self) -> usize {
148 if self.beam_hopping_time_plan_selector {
149 S2XV2_SUPERFRAME_BH_LEN
150 } else {
151 S2XV2_SUPERFRAME_MIN_LEN
152 }
153 }
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
158#[cfg_attr(feature = "serde", derive(serde::Serialize))]
159#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
160pub struct S2Xv2SatelliteDeliverySystem<'a> {
161 pub delivery_system_id: u32,
163 pub s2xv2_mode: S2Xv2Mode,
165 pub multiple_input_stream_flag: bool,
167 pub roll_off: RollOff,
169 pub ncr_reference: bool,
171 pub ncr_version: bool,
173 pub channel_bond: u8,
175 pub polarization: Polarization,
177 pub scrambling_sequence_selector: Option<bool>,
180 pub ts_gs_s2x_mode: u8,
182 pub receiver_profiles: u8,
184 pub satellite_id: u32,
186 pub frequency: u32,
188 pub symbol_rate: u32,
190 pub input_stream_identifier: Option<u8>,
192 pub scrambling_sequence_index: Option<u32>,
195 pub timeslice_number: Option<u8>,
197 pub secondary_delivery_system_ids: Vec<u32>,
200 pub superframe: Option<S2Xv2Superframe>,
202 #[cfg_attr(feature = "serde", serde(borrow))]
204 pub reserved_tail: &'a [u8],
205}
206
207impl<'a> Parse<'a> for S2Xv2SatelliteDeliverySystem<'a> {
208 type Error = crate::error::Error;
209 fn parse(sel: &'a [u8]) -> Result<Self> {
210 if sel.len() < S2XV2_CORE_LEN {
211 return Err(Error::BufferTooShort {
212 need: S2XV2_CORE_LEN,
213 have: sel.len(),
214 what: "S2Xv2 body",
215 });
216 }
217 let (dsid_bytes, _) = sel.split_first_chunk::<4>().ok_or(Error::BufferTooShort {
219 need: S2XV2_CORE_LEN,
220 have: sel.len(),
221 what: "S2Xv2 body",
222 })?;
223 let delivery_system_id = u32::from_be_bytes(*dsid_bytes);
224 let b4 = sel[4];
226 let s2xv2_mode = S2Xv2Mode::from_u8(b4 >> 4);
227 let multiple_input_stream_flag = (b4 & 0x08) != 0;
228 let roll_off = RollOff::from_u8(b4 & 0x07);
229 let b5 = sel[5];
231 let ncr_reference = (b5 & 0x20) != 0;
232 let ncr_version = (b5 & 0x10) != 0;
233 let channel_bond = (b5 >> 2) & 0x03;
234 let polarization = Polarization::from_u8(b5 & 0x03);
235 let b6 = sel[6];
237 let raw_scrambling_bit = (b6 >> 7) & 0x01;
238 let ts_gs_s2x_mode = (b6 >> 5) & 0x03;
239 let receiver_profiles = b6 & 0x1F;
240 let scrambling_sequence_selector = if s2xv2_mode.has_scrambling_selector() {
241 Some(raw_scrambling_bit != 0)
242 } else {
243 None
244 };
245 let satellite_id = (u32::from(sel[7]) << 16) | (u32::from(sel[8]) << 8) | u32::from(sel[9]);
247 let (freq_bytes, _) = sel[10..]
249 .split_first_chunk::<4>()
250 .ok_or(Error::BufferTooShort {
251 need: S2XV2_CORE_LEN,
252 have: sel.len(),
253 what: "S2Xv2 body",
254 })?;
255 let frequency = u32::from_be_bytes(*freq_bytes);
256 let (sr_bytes, _) = sel[14..]
258 .split_first_chunk::<4>()
259 .ok_or(Error::BufferTooShort {
260 need: S2XV2_CORE_LEN,
261 have: sel.len(),
262 what: "S2Xv2 body",
263 })?;
264 let symbol_rate = u32::from_be_bytes(*sr_bytes);
265 let mut pos = S2XV2_CORE_LEN;
266
267 let input_stream_identifier = if multiple_input_stream_flag {
269 if sel.len() < pos + 1 {
270 return Err(Error::BufferTooShort {
271 need: pos + 1,
272 have: sel.len(),
273 what: "S2Xv2 body (input_stream_identifier)",
274 });
275 }
276 let isi = sel[pos];
277 pos += 1;
278 Some(isi)
279 } else {
280 None
281 };
282
283 let scrambling_sequence_index = if scrambling_sequence_selector == Some(true) {
285 if sel.len() < pos + S2XV2_SCRAMBLING_LEN {
286 return Err(Error::BufferTooShort {
287 need: pos + S2XV2_SCRAMBLING_LEN,
288 have: sel.len(),
289 what: "S2Xv2 body (scrambling_sequence_index)",
290 });
291 }
292 let idx = (u32::from(sel[pos] & 0x03) << 16)
294 | (u32::from(sel[pos + 1]) << 8)
295 | u32::from(sel[pos + 2]);
296 pos += S2XV2_SCRAMBLING_LEN;
297 Some(idx)
298 } else {
299 None
300 };
301
302 let timeslice_number = if s2xv2_mode.has_timeslice() {
304 if sel.len() < pos + 1 {
305 return Err(Error::BufferTooShort {
306 need: pos + 1,
307 have: sel.len(),
308 what: "S2Xv2 body (timeslice_number)",
309 });
310 }
311 let ts = sel[pos];
312 pos += 1;
313 Some(ts)
314 } else {
315 None
316 };
317
318 let secondary_delivery_system_ids = if channel_bond == 1 {
320 if sel.len() < pos + 1 {
321 return Err(Error::BufferTooShort {
322 need: pos + 1,
323 have: sel.len(),
324 what: "S2Xv2 body (channel_bond header)",
325 });
326 }
327 let bond_byte = sel[pos];
328 pos += 1;
329 let n = (bond_byte & 0x01) as usize + 1;
331 let mut ids = Vec::with_capacity(n);
332 for _ in 0..n {
333 if sel.len() < pos + 4 {
334 return Err(Error::BufferTooShort {
335 need: pos + 4,
336 have: sel.len(),
337 what: "S2Xv2 body (secondary_delivery_system_id)",
338 });
339 }
340 let (id_bytes, _) = sel
341 .get(pos..)
342 .and_then(|s| s.split_first_chunk::<4>())
343 .ok_or(Error::BufferTooShort {
344 need: pos + 4,
345 have: sel.len(),
346 what: "S2Xv2 body (secondary_delivery_system_id)",
347 })?;
348 let id = u32::from_be_bytes(*id_bytes);
349 ids.push(id);
350 pos += 4;
351 }
352 ids
353 } else {
354 Vec::new()
355 };
356
357 let superframe = if s2xv2_mode.has_superframe() {
359 if sel.len() < pos + S2XV2_SUPERFRAME_MIN_LEN {
360 return Err(Error::BufferTooShort {
361 need: pos + S2XV2_SUPERFRAME_MIN_LEN,
362 have: sel.len(),
363 what: "S2Xv2 body (superframe)",
364 });
365 }
366 let sosf_wh_sequence_number = sel[pos];
367 let b1 = sel[pos + 1];
369 let sffi_selector = (b1 & 0x80) != 0;
370 let beam_hopping_time_plan_selector = (b1 & 0x40) != 0;
371 let ref_scram_hi = u32::from(b1 & 0x0F);
372 let reference_scrambling_index =
373 (ref_scram_hi << 16) | (u32::from(sel[pos + 2]) << 8) | u32::from(sel[pos + 3]);
374 let b4s = sel[pos + 4];
376 let sffi = if sffi_selector { Some(b4s >> 4) } else { None };
377 let psi_hi = u32::from(b4s & 0x0F);
378 let payload_scrambling_index =
379 (psi_hi << 16) | (u32::from(sel[pos + 5]) << 8) | u32::from(sel[pos + 6]);
380 pos += 7;
381 let beamhopping_time_plan_id = if beam_hopping_time_plan_selector {
383 if sel.len() < pos + 4 {
384 return Err(Error::BufferTooShort {
385 need: pos + 4,
386 have: sel.len(),
387 what: "S2Xv2 body (beamhopping_time_plan_id)",
388 });
389 }
390 let (bh_bytes, _) = sel
391 .get(pos..)
392 .and_then(|s| s.split_first_chunk::<4>())
393 .ok_or(Error::BufferTooShort {
394 need: pos + 4,
395 have: sel.len(),
396 what: "S2Xv2 body (beamhopping_time_plan_id)",
397 })?;
398 let id = u32::from_be_bytes(*bh_bytes);
399 pos += 4;
400 Some(id)
401 } else {
402 None
403 };
404 if sel.len() < pos + 1 {
406 return Err(Error::BufferTooShort {
407 need: pos + 1,
408 have: sel.len(),
409 what: "S2Xv2 body (superframe final byte)",
410 });
411 }
412 let last = sel[pos];
413 let superframe_pilots_wh_sequence_number = (last >> 3) & 0x1F;
414 let postamble_pli = last & 0x07;
415 pos += 1;
416 Some(S2Xv2Superframe {
417 sosf_wh_sequence_number,
418 sffi_selector,
419 beam_hopping_time_plan_selector,
420 reference_scrambling_index,
421 sffi,
422 payload_scrambling_index,
423 beamhopping_time_plan_id,
424 superframe_pilots_wh_sequence_number,
425 postamble_pli,
426 })
427 } else {
428 None
429 };
430
431 Ok(S2Xv2SatelliteDeliverySystem {
432 delivery_system_id,
433 s2xv2_mode,
434 multiple_input_stream_flag,
435 roll_off,
436 ncr_reference,
437 ncr_version,
438 channel_bond,
439 polarization,
440 scrambling_sequence_selector,
441 ts_gs_s2x_mode,
442 receiver_profiles,
443 satellite_id,
444 frequency,
445 symbol_rate,
446 input_stream_identifier,
447 scrambling_sequence_index,
448 timeslice_number,
449 secondary_delivery_system_ids,
450 superframe,
451 reserved_tail: &sel[pos..],
452 })
453 }
454}
455
456impl Serialize for S2Xv2SatelliteDeliverySystem<'_> {
457 type Error = crate::error::Error;
458 fn serialized_len(&self) -> usize {
459 S2XV2_CORE_LEN
460 + usize::from(self.input_stream_identifier.is_some())
461 + if self.scrambling_sequence_selector == Some(true) {
462 S2XV2_SCRAMBLING_LEN
463 } else {
464 0
465 }
466 + usize::from(self.timeslice_number.is_some())
467 + if self.channel_bond == 1 {
468 1 + self.secondary_delivery_system_ids.len() * 4
469 } else {
470 0
471 }
472 + self.superframe.as_ref().map_or(0, |sf| sf.serialized_len())
473 + self.reserved_tail.len()
474 }
475
476 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
477 let len = self.serialized_len();
478 if buf.len() < len {
479 return Err(Error::OutputBufferTooSmall {
480 need: len,
481 have: buf.len(),
482 });
483 }
484 buf[0..4].copy_from_slice(&self.delivery_system_id.to_be_bytes());
486 buf[4] = ((self.s2xv2_mode.to_u8() & 0x0F) << 4)
488 | (u8::from(self.multiple_input_stream_flag) << 3)
489 | (self.roll_off.to_u8() & 0x07);
490 buf[5] = (u8::from(self.ncr_reference) << 5)
492 | (u8::from(self.ncr_version) << 4)
493 | ((self.channel_bond & 0x03) << 2)
494 | (self.polarization.to_u8() & 0x03);
495 let scram_bit: u8 = match self.scrambling_sequence_selector {
497 Some(true) => 1,
498 _ => 0,
499 };
500 buf[6] = (scram_bit << 7)
501 | ((self.ts_gs_s2x_mode & 0x03) << 5)
502 | (self.receiver_profiles & 0x1F);
503 buf[7] = (self.satellite_id >> 16) as u8;
505 buf[8] = (self.satellite_id >> 8) as u8;
506 buf[9] = self.satellite_id as u8;
507 buf[10..14].copy_from_slice(&self.frequency.to_be_bytes());
509 buf[14..18].copy_from_slice(&self.symbol_rate.to_be_bytes());
511 let mut p = S2XV2_CORE_LEN;
512 if let Some(isi) = self.input_stream_identifier {
514 buf[p] = isi;
515 p += 1;
516 }
517 if self.scrambling_sequence_selector == Some(true) {
519 let idx = self.scrambling_sequence_index.unwrap_or(0) & 0x0003_FFFF;
520 buf[p] = (idx >> 16) as u8 & 0x03;
521 buf[p + 1] = (idx >> 8) as u8;
522 buf[p + 2] = idx as u8;
523 p += S2XV2_SCRAMBLING_LEN;
524 }
525 if let Some(ts) = self.timeslice_number {
527 buf[p] = ts;
528 p += 1;
529 }
530 if self.channel_bond == 1 {
532 let n = self.secondary_delivery_system_ids.len().saturating_sub(1) as u8 & 0x01;
534 buf[p] = n;
535 p += 1;
536 for &id in &self.secondary_delivery_system_ids {
537 buf[p..p + 4].copy_from_slice(&id.to_be_bytes());
538 p += 4;
539 }
540 }
541 if let Some(sf) = &self.superframe {
543 buf[p] = sf.sosf_wh_sequence_number;
544 let ref_hi = (sf.reference_scrambling_index >> 16) as u8 & 0x0F;
545 buf[p + 1] = (u8::from(sf.sffi_selector) << 7)
546 | (u8::from(sf.beam_hopping_time_plan_selector) << 6)
547 | ref_hi;
548 buf[p + 2] = (sf.reference_scrambling_index >> 8) as u8;
549 buf[p + 3] = sf.reference_scrambling_index as u8;
550 let sffi_nibble: u8 = sf.sffi.unwrap_or(0) & 0x0F;
551 let psi_hi = (sf.payload_scrambling_index >> 16) as u8 & 0x0F;
552 buf[p + 4] = (sffi_nibble << 4) | psi_hi;
553 buf[p + 5] = (sf.payload_scrambling_index >> 8) as u8;
554 buf[p + 6] = sf.payload_scrambling_index as u8;
555 p += 7;
556 if let Some(bh_id) = sf.beamhopping_time_plan_id {
557 buf[p..p + 4].copy_from_slice(&bh_id.to_be_bytes());
558 p += 4;
559 }
560 buf[p] =
561 ((sf.superframe_pilots_wh_sequence_number & 0x1F) << 3) | (sf.postamble_pli & 0x07);
562 p += 1;
563 }
564 buf[p..p + self.reserved_tail.len()].copy_from_slice(self.reserved_tail);
566 Ok(len)
567 }
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573 use crate::descriptors::extension::test_support::*;
574 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
575
576 fn minimal_mode1_sel() -> Vec<u8> {
578 let mut sel = Vec::new();
579 sel.extend_from_slice(&0x0102_0304u32.to_be_bytes());
581 sel.push(0x12);
583 sel.push(0x21);
586 sel.push(0x67);
588 sel.extend_from_slice(&[0x01, 0x02, 0x03]);
590 sel.extend_from_slice(&0x1234_5678u32.to_be_bytes());
592 sel.extend_from_slice(&0xABCD_EF01u32.to_be_bytes());
594 sel
595 }
596
597 #[test]
598 fn parse_s2xv2_mode1_no_scrambling_round_trip() {
599 let sel = minimal_mode1_sel();
601 let bytes = wrap(0x24, &sel);
602 let d = ExtensionDescriptor::parse(&bytes).unwrap();
603 assert_eq!(d.kind(), Some(ExtensionTag::S2Xv2SatelliteDeliverySystem));
604 match &d.body {
605 ExtensionBody::S2Xv2SatelliteDeliverySystem(b) => {
606 assert_eq!(b.delivery_system_id, 0x0102_0304);
607 assert_eq!(b.s2xv2_mode, S2Xv2Mode::S2X);
608 assert!(!b.multiple_input_stream_flag);
609 assert_eq!(b.roll_off, RollOff::Alpha020);
610 assert!(b.ncr_reference);
611 assert!(!b.ncr_version);
612 assert_eq!(b.channel_bond, 0);
613 assert_eq!(b.polarization, Polarization::LinearVertical);
614 assert_eq!(b.scrambling_sequence_selector, Some(false));
615 assert_eq!(b.ts_gs_s2x_mode, 3);
616 assert_eq!(b.receiver_profiles, 0x07);
617 assert_eq!(b.satellite_id, 0x010203);
618 assert_eq!(b.frequency, 0x1234_5678);
619 assert_eq!(b.symbol_rate, 0xABCD_EF01);
620 assert!(b.input_stream_identifier.is_none());
621 assert!(b.scrambling_sequence_index.is_none());
622 assert!(b.timeslice_number.is_none());
623 assert!(b.secondary_delivery_system_ids.is_empty());
624 assert!(b.superframe.is_none());
625 assert!(b.reserved_tail.is_empty());
626 }
627 other => panic!("expected S2Xv2, got {other:?}"),
628 }
629 round_trip(&d);
630 }
631
632 #[test]
633 fn parse_s2xv2_mode1_scrambling_and_mis_round_trip() {
634 let mut sel = Vec::new();
637 sel.extend_from_slice(&0xDEAD_BEEFu32.to_be_bytes()); sel.push(0x19);
640 sel.push(0x13);
642 sel.push(0x9F);
644 sel.extend_from_slice(&[0xAA, 0xBB, 0xCC]); sel.extend_from_slice(&0x0000_0001u32.to_be_bytes()); sel.extend_from_slice(&0x0000_0002u32.to_be_bytes()); sel.push(0x42); sel.extend_from_slice(&[0x01, 0xAB, 0xCD]);
651 let bytes = wrap(0x24, &sel);
652 let d = ExtensionDescriptor::parse(&bytes).unwrap();
653 match &d.body {
654 ExtensionBody::S2Xv2SatelliteDeliverySystem(b) => {
655 assert_eq!(b.delivery_system_id, 0xDEAD_BEEF);
656 assert_eq!(b.s2xv2_mode, S2Xv2Mode::S2X);
657 assert!(b.multiple_input_stream_flag);
658 assert_eq!(b.roll_off, RollOff::Alpha025);
660 assert!(!b.ncr_reference);
661 assert!(b.ncr_version);
662 assert_eq!(b.channel_bond, 0);
663 assert_eq!(b.polarization, Polarization::CircularRight);
664 assert_eq!(b.scrambling_sequence_selector, Some(true));
665 assert_eq!(b.ts_gs_s2x_mode, 0);
666 assert_eq!(b.receiver_profiles, 0x1F);
667 assert_eq!(b.input_stream_identifier, Some(0x42));
668 assert_eq!(b.scrambling_sequence_index, Some(0x1ABCD));
669 assert!(b.timeslice_number.is_none());
670 assert!(b.secondary_delivery_system_ids.is_empty());
671 assert!(b.superframe.is_none());
672 }
673 other => panic!("expected S2Xv2, got {other:?}"),
674 }
675 round_trip(&d);
676 }
677
678 #[test]
679 fn parse_s2xv2_mode2_timeslice_round_trip() {
680 let mut sel = Vec::new();
682 sel.extend_from_slice(&0x0000_0001u32.to_be_bytes());
683 sel.push(0x20);
685 sel.push(0x00); sel.push(0x01);
688 sel.extend_from_slice(&[0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.push(0x07); let bytes = wrap(0x24, &sel);
693 let d = ExtensionDescriptor::parse(&bytes).unwrap();
694 match &d.body {
695 ExtensionBody::S2Xv2SatelliteDeliverySystem(b) => {
696 assert_eq!(b.s2xv2_mode, S2Xv2Mode::S2XTimeSlicing);
697 assert_eq!(b.timeslice_number, Some(0x07));
698 assert!(b.scrambling_sequence_index.is_none());
699 assert!(b.secondary_delivery_system_ids.is_empty());
700 assert!(b.superframe.is_none());
701 }
702 other => panic!("expected S2Xv2, got {other:?}"),
703 }
704 round_trip(&d);
705 }
706
707 #[test]
708 fn parse_s2xv2_channel_bond_round_trip() {
709 let mut sel = Vec::new();
711 sel.extend_from_slice(&0xFFFF_FFFFu32.to_be_bytes()); sel.push(0x00);
714 sel.push(0x04);
716 sel.push(0x00);
718 sel.extend_from_slice(&[0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.push(0x01);
723 sel.extend_from_slice(&0x1111_1111u32.to_be_bytes()); sel.extend_from_slice(&0x2222_2222u32.to_be_bytes()); let bytes = wrap(0x24, &sel);
726 let d = ExtensionDescriptor::parse(&bytes).unwrap();
727 match &d.body {
728 ExtensionBody::S2Xv2SatelliteDeliverySystem(b) => {
729 assert_eq!(b.channel_bond, 1);
730 assert_eq!(
731 b.secondary_delivery_system_ids,
732 vec![0x1111_1111, 0x2222_2222]
733 );
734 assert!(b.superframe.is_none());
735 }
736 other => panic!("expected S2Xv2, got {other:?}"),
737 }
738 round_trip(&d);
739 }
740
741 #[test]
742 fn parse_s2xv2_superframe_no_beamhopping_round_trip() {
743 let mut sel = Vec::new();
745 sel.extend_from_slice(&0x0000_0002u32.to_be_bytes()); sel.push(0x40);
748 sel.push(0x00); sel.push(0x23);
751 sel.extend_from_slice(&[0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.push(0xAB); sel.push(0x85);
758 sel.push(0x12); sel.push(0x34); sel.push(0x96);
762 sel.push(0x78); sel.push(0x90); sel.push(0xD3);
767 let bytes = wrap(0x24, &sel);
768 let d = ExtensionDescriptor::parse(&bytes).unwrap();
769 match &d.body {
770 ExtensionBody::S2Xv2SatelliteDeliverySystem(b) => {
771 assert_eq!(b.s2xv2_mode, S2Xv2Mode::S2XSuperframe);
772 assert!(b.superframe.is_some());
773 let sf = b.superframe.as_ref().unwrap();
774 assert_eq!(sf.sosf_wh_sequence_number, 0xAB);
775 assert!(sf.sffi_selector);
776 assert!(!sf.beam_hopping_time_plan_selector);
777 assert_eq!(sf.reference_scrambling_index, 0x05_1234);
778 assert_eq!(sf.sffi, Some(0x9));
779 assert_eq!(sf.payload_scrambling_index, 0x06_7890);
780 assert!(sf.beamhopping_time_plan_id.is_none());
781 assert_eq!(sf.superframe_pilots_wh_sequence_number, 0x1A);
782 assert_eq!(sf.postamble_pli, 0x03);
783 }
784 other => panic!("expected S2Xv2, got {other:?}"),
785 }
786 round_trip(&d);
787 }
788
789 #[test]
790 fn parse_s2xv2_superframe_with_beamhopping_round_trip() {
791 let mut sel = Vec::new();
793 sel.extend_from_slice(&0x0000_0003u32.to_be_bytes()); sel.push(0x50);
796 sel.push(0x00); sel.push(0x00);
799 sel.extend_from_slice(&[0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.extend_from_slice(&[0, 0, 0, 0]); sel.push(0x0F);
804 sel.push(0x01); sel.push(0x4A);
808 sel.push(0xBC); sel.push(0xDE); sel.push(0x00);
812 sel.push(0x00); sel.push(0xFF); sel.extend_from_slice(&0x1234_5678u32.to_be_bytes());
816 sel.push(0x2F);
818 let bytes = wrap(0x24, &sel);
819 let d = ExtensionDescriptor::parse(&bytes).unwrap();
820 match &d.body {
821 ExtensionBody::S2Xv2SatelliteDeliverySystem(b) => {
822 assert_eq!(b.s2xv2_mode, S2Xv2Mode::S2XSuperframeTimeSlicing);
823 assert_eq!(b.timeslice_number, Some(0x0F));
824 let sf = b.superframe.as_ref().unwrap();
825 assert!(!sf.sffi_selector);
826 assert!(sf.beam_hopping_time_plan_selector);
827 assert_eq!(sf.reference_scrambling_index, 0x0A_BCDE);
828 assert!(sf.sffi.is_none());
829 assert_eq!(sf.payload_scrambling_index, 0x0000FF);
830 assert_eq!(sf.beamhopping_time_plan_id, Some(0x1234_5678));
831 assert_eq!(sf.superframe_pilots_wh_sequence_number, 0x05);
832 assert_eq!(sf.postamble_pli, 0x07);
833 }
834 other => panic!("expected S2Xv2, got {other:?}"),
835 }
836 round_trip(&d);
837 }
838
839 #[test]
840 fn parse_s2xv2_reserved_tail_preserved() {
841 let mut sel = Vec::new();
843 sel.extend_from_slice(&0x0000_0000u32.to_be_bytes());
844 sel.push(0x02);
846 sel.push(0x00); sel.push(0x00); sel.extend_from_slice(&[0, 0, 0]);
849 sel.extend_from_slice(&[0, 0, 0, 0]);
850 sel.extend_from_slice(&[0, 0, 0, 0]);
851 sel.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]); let bytes = wrap(0x24, &sel);
853 let d = ExtensionDescriptor::parse(&bytes).unwrap();
854 match &d.body {
855 ExtensionBody::S2Xv2SatelliteDeliverySystem(b) => {
856 assert_eq!(b.s2xv2_mode, S2Xv2Mode::Reserved0);
857 assert_eq!(b.reserved_tail, &[0xDE, 0xAD, 0xBE, 0xEF]);
858 }
859 other => panic!("expected S2Xv2, got {other:?}"),
860 }
861 round_trip(&d);
862 }
863
864 #[test]
865 fn parse_s2xv2_rejects_truncated() {
866 let sel = vec![0u8; 10];
868 let bytes = wrap(0x24, &sel);
869 assert!(matches!(
870 ExtensionDescriptor::parse(&bytes).unwrap_err(),
871 crate::error::Error::BufferTooShort { .. }
872 ));
873 }
874
875 #[test]
876 fn s2xv2_mode_roundtrip_all_values() {
877 for b in 0..=0x0Fu8 {
878 assert_eq!(S2Xv2Mode::from_u8(b).to_u8(), b);
879 }
880 }
881
882 #[cfg(feature = "serde")]
883 #[test]
884 fn serde_serialize_s2xv2() {
885 let sel = minimal_mode1_sel();
886 let bytes = wrap(0x24, &sel);
887 let d = ExtensionDescriptor::parse(&bytes).unwrap();
888 let json = serde_json::to_string(&d).unwrap();
889 assert!(json.contains("\"tag_extension\":36"));
891 assert!(json.contains("\"s2Xv2SatelliteDeliverySystem\""));
892 }
893}