Skip to main content

dvb_si/descriptors/
s2_satellite_delivery_system.rs

1//! S2 Satellite Delivery System Descriptor — ETSI EN 300 468 §6.2.13.3 (tag 0x79).
2//!
3//! Companion to the satellite_delivery_system_descriptor (tag 0x43) when the
4//! carrier is DVB-S2. Carries a PLS Gold scrambling sequence index, an
5//! input_stream_identifier (ISI) for multi-stream carriers, the TS/GS mode, and
6//! an optional timeslice number.
7//!
8//! Wire layout (§6.2.13.3 Table 42):
9//!
10//! ```text
11//!  byte 0 (flags):
12//!    bit 7      scrambling_sequence_selector
13//!    bit 6      multiple_input_stream_flag
14//!    bit 5      reserved_zero_future_use
15//!    bit 4      not_timeslice_flag
16//!    bits 3..2  reserved_future_use
17//!    bits 1..0  TS_GS_mode
18//!  if scrambling_sequence_selector == 1 (3 bytes):
19//!    reserved(6) + scrambling_sequence_index(18)
20//!  if multiple_input_stream_flag == 1 (1 byte):
21//!    input_stream_identifier(8)
22//!  if not_timeslice_flag == 0 (1 byte):
23//!    timeslice_number(8)
24//! ```
25
26use super::descriptor_body;
27use crate::error::{Error, Result};
28use dvb_common::{Parse, Serialize};
29
30/// Descriptor tag for s2_satellite_delivery_system_descriptor.
31pub const TAG: u8 = 0x79;
32const HEADER_LEN: usize = 2;
33const FLAGS_LEN: usize = 1;
34const SCRAMBLING_FIELD_LEN: usize = 3;
35const ISI_FIELD_LEN: usize = 1;
36const TIMESLICE_FIELD_LEN: usize = 1;
37
38const FLAG_SCRAMBLING_SELECTOR: u8 = 0x80;
39const FLAG_MULTIPLE_INPUT_STREAM: u8 = 0x40;
40const FLAG_NOT_TIMESLICE: u8 = 0x10;
41/// TS_GS_mode occupies the bottom 2 bits of the flags byte.
42const TS_GS_MODE_MASK: u8 = 0x03;
43/// Reserved flag bits set to 1 on serialize. Per §6.2.13.3 Table 42, bit 5 is
44/// `reserved_zero_future_use` (MUST be 0); only bits 3..2 (`reserved_future_use`)
45/// are set, giving 0x0C. All reserved bits are ignored on parse (§5.1).
46const FLAG_RESERVED_BITS: u8 = 0x0C;
47
48const SCRAMBLING_RESERVED_MASK: u8 = 0xFC;
49const SCRAMBLING_INDEX_HI_MASK: u8 = 0x03;
50const SCRAMBLING_INDEX_MAX: u32 = 0x3FFFF;
51
52/// TS/GS mode — ETSI EN 300 468 Table 43.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize))]
55#[non_exhaustive]
56pub enum TsGsMode {
57    /// Generic Packetized.
58    GenericPacketized,
59    /// Generic Stream Encapsulation (GSE).
60    Gse,
61    /// DVB transport stream.
62    DvbTransportStream,
63    /// Reserved / future use.
64    Reserved(u8),
65}
66
67impl TsGsMode {
68    #[must_use]
69    /// Construct from a raw `u8`; every value maps to a variant (total, lossless).
70    pub fn from_u8(v: u8) -> Self {
71        match v {
72            0 => TsGsMode::GenericPacketized,
73            1 => TsGsMode::Gse,
74            2 => TsGsMode::DvbTransportStream,
75            other => TsGsMode::Reserved(other),
76        }
77    }
78
79    #[must_use]
80    /// Inverse of `from_u8`; `Self::Reserved` emits its stored value.
81    pub fn to_u8(self) -> u8 {
82        match self {
83            TsGsMode::GenericPacketized => 0,
84            TsGsMode::Gse => 1,
85            TsGsMode::DvbTransportStream => 2,
86            TsGsMode::Reserved(v) => v,
87        }
88    }
89
90    #[must_use]
91    /// Human-readable spec name per the governing Table.
92    pub fn name(self) -> &'static str {
93        match self {
94            TsGsMode::GenericPacketized => "Generic Packetized",
95            TsGsMode::Gse => "Generic Stream Encapsulation (GSE)",
96            TsGsMode::DvbTransportStream => "DVB transport stream",
97            TsGsMode::Reserved(_) => "reserved",
98        }
99    }
100}
101
102/// S2 Satellite Delivery System Descriptor (§6.2.13.3, Table 42).
103#[derive(Debug, Clone, PartialEq, Eq)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize))]
105pub struct S2SatelliteDeliverySystemDescriptor {
106    /// When set, `scrambling_sequence_index` is present.
107    pub scrambling_sequence_selector: bool,
108    /// When set, `input_stream_identifier` is present.
109    pub multiple_input_stream_flag: bool,
110    /// When set, timeslicing is NOT used and `timeslice_number` is absent.
111    pub not_timeslice_flag: bool,
112    /// 2-bit TS/GS mode (Table 43).
113    pub ts_gs_mode: TsGsMode,
114    /// 18-bit PLS Gold scrambling sequence index.
115    pub scrambling_sequence_index: Option<u32>,
116    /// 8-bit input_stream_identifier (ISI).
117    pub input_stream_identifier: Option<u8>,
118    /// 8-bit timeslice_number, present when `not_timeslice_flag` is false.
119    pub timeslice_number: Option<u8>,
120}
121
122impl<'a> Parse<'a> for S2SatelliteDeliverySystemDescriptor {
123    type Error = crate::error::Error;
124    fn parse(bytes: &'a [u8]) -> Result<Self> {
125        if bytes.len() < HEADER_LEN + FLAGS_LEN {
126            return Err(Error::BufferTooShort {
127                need: HEADER_LEN + FLAGS_LEN,
128                have: bytes.len(),
129                what: "S2SatelliteDeliverySystemDescriptor header",
130            });
131        }
132        let body = descriptor_body(
133            bytes,
134            TAG,
135            "S2SatelliteDeliverySystemDescriptor",
136            "unexpected tag for s2_satellite_delivery_system_descriptor",
137        )?;
138        if body.len() < FLAGS_LEN {
139            return Err(Error::InvalidDescriptor {
140                tag: TAG,
141                reason: "body must contain at least the flags byte",
142            });
143        }
144
145        // Reserved bits are ignored on parse (EN 300 468 §5.1).
146        let flags = body[0];
147        let scrambling_sequence_selector = (flags & FLAG_SCRAMBLING_SELECTOR) != 0;
148        let multiple_input_stream_flag = (flags & FLAG_MULTIPLE_INPUT_STREAM) != 0;
149        let not_timeslice_flag = (flags & FLAG_NOT_TIMESLICE) != 0;
150        let ts_gs_mode = TsGsMode::from_u8(flags & TS_GS_MODE_MASK);
151
152        let mut pos = FLAGS_LEN;
153
154        let scrambling_sequence_index = if scrambling_sequence_selector {
155            if pos + SCRAMBLING_FIELD_LEN > body.len() {
156                return Err(Error::BufferTooShort {
157                    need: pos + SCRAMBLING_FIELD_LEN,
158                    have: body.len(),
159                    what: "S2SatelliteDeliverySystemDescriptor scrambling_sequence_index",
160                });
161            }
162            let b0 = body[pos];
163            let index = (u32::from(b0 & SCRAMBLING_INDEX_HI_MASK) << 16)
164                | (u32::from(body[pos + 1]) << 8)
165                | u32::from(body[pos + 2]);
166            pos += SCRAMBLING_FIELD_LEN;
167            Some(index)
168        } else {
169            None
170        };
171
172        let input_stream_identifier = if multiple_input_stream_flag {
173            if pos + ISI_FIELD_LEN > body.len() {
174                return Err(Error::BufferTooShort {
175                    need: pos + ISI_FIELD_LEN,
176                    have: body.len(),
177                    what: "S2SatelliteDeliverySystemDescriptor input_stream_identifier",
178                });
179            }
180            let isi = body[pos];
181            pos += ISI_FIELD_LEN;
182            Some(isi)
183        } else {
184            None
185        };
186
187        // timeslice_number is present when timeslicing IS used (not_timeslice_flag == 0).
188        let timeslice_number = if !not_timeslice_flag {
189            if pos + TIMESLICE_FIELD_LEN > body.len() {
190                return Err(Error::BufferTooShort {
191                    need: pos + TIMESLICE_FIELD_LEN,
192                    have: body.len(),
193                    what: "S2SatelliteDeliverySystemDescriptor timeslice_number",
194                });
195            }
196            Some(body[pos])
197        } else {
198            None
199        };
200
201        Ok(Self {
202            scrambling_sequence_selector,
203            multiple_input_stream_flag,
204            not_timeslice_flag,
205            ts_gs_mode,
206            scrambling_sequence_index,
207            input_stream_identifier,
208            timeslice_number,
209        })
210    }
211}
212
213impl Serialize for S2SatelliteDeliverySystemDescriptor {
214    type Error = crate::error::Error;
215    fn serialized_len(&self) -> usize {
216        HEADER_LEN
217            + FLAGS_LEN
218            + if self.scrambling_sequence_selector {
219                SCRAMBLING_FIELD_LEN
220            } else {
221                0
222            }
223            + if self.multiple_input_stream_flag {
224                ISI_FIELD_LEN
225            } else {
226                0
227            }
228            + if self.not_timeslice_flag {
229                0
230            } else {
231                TIMESLICE_FIELD_LEN
232            }
233    }
234
235    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
236        let len = self.serialized_len();
237        if buf.len() < len {
238            return Err(Error::OutputBufferTooSmall {
239                need: len,
240                have: buf.len(),
241            });
242        }
243        buf[0] = TAG;
244        buf[1] = (len - HEADER_LEN) as u8;
245
246        let mut flags = FLAG_RESERVED_BITS | (self.ts_gs_mode.to_u8() & TS_GS_MODE_MASK);
247        if self.scrambling_sequence_selector {
248            flags |= FLAG_SCRAMBLING_SELECTOR;
249        }
250        if self.multiple_input_stream_flag {
251            flags |= FLAG_MULTIPLE_INPUT_STREAM;
252        }
253        if self.not_timeslice_flag {
254            flags |= FLAG_NOT_TIMESLICE;
255        }
256        buf[HEADER_LEN] = flags;
257
258        let mut pos = HEADER_LEN + FLAGS_LEN;
259        if self.scrambling_sequence_selector {
260            let index = self.scrambling_sequence_index.unwrap_or(0) & SCRAMBLING_INDEX_MAX;
261            buf[pos] = SCRAMBLING_RESERVED_MASK | ((index >> 16) as u8 & SCRAMBLING_INDEX_HI_MASK);
262            buf[pos + 1] = (index >> 8) as u8;
263            buf[pos + 2] = index as u8;
264            pos += SCRAMBLING_FIELD_LEN;
265        }
266        if self.multiple_input_stream_flag {
267            buf[pos] = self.input_stream_identifier.unwrap_or(0);
268            pos += ISI_FIELD_LEN;
269        }
270        if !self.not_timeslice_flag {
271            buf[pos] = self.timeslice_number.unwrap_or(0);
272        }
273        Ok(len)
274    }
275}
276impl<'a> crate::traits::DescriptorDef<'a> for S2SatelliteDeliverySystemDescriptor {
277    const TAG: u8 = TAG;
278    const NAME: &'static str = "S2_SATELLITE_DELIVERY_SYSTEM";
279}
280
281#[cfg(test)]
282mod tests {
283    use super::*;
284
285    #[test]
286    fn ts_gs_mode_from_u8_to_u8_roundtrip() {
287        for b in 0..=0xFFu8 {
288            assert_eq!(
289                TsGsMode::from_u8(b).to_u8(),
290                b,
291                "round-trip fail for {b:#04x}"
292            );
293        }
294    }
295
296    /// flags 0x2C = reserved bits set, all feature flags clear, ts_gs_mode 0,
297    /// not_timeslice_flag clear → timeslice_number present.
298    #[test]
299    fn parse_minimal_with_timeslice() {
300        let raw = [TAG, 2, 0x2C, 0x07];
301        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
302        assert!(!d.scrambling_sequence_selector);
303        assert!(!d.multiple_input_stream_flag);
304        assert!(!d.not_timeslice_flag);
305        assert_eq!(d.ts_gs_mode, TsGsMode::GenericPacketized);
306        assert_eq!(d.timeslice_number, Some(0x07));
307        assert_eq!(d.scrambling_sequence_index, None);
308        assert_eq!(d.input_stream_identifier, None);
309    }
310
311    #[test]
312    fn parse_not_timeslice_omits_timeslice_number() {
313        // not_timeslice_flag (0x10) set + reserved (0x2C) + ts_gs_mode 2 = 0x3E
314        let raw = [TAG, 1, 0x3E];
315        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
316        assert!(d.not_timeslice_flag);
317        assert_eq!(d.ts_gs_mode, TsGsMode::DvbTransportStream);
318        assert_eq!(d.timeslice_number, None);
319    }
320
321    #[test]
322    fn parse_extracts_isi_and_timeslice() {
323        // flags = MIS(0x40) | reserved(0x2C) = 0x6C; ISI byte, then timeslice byte
324        let raw = [TAG, 3, 0x6C, 0x05, 0x09];
325        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
326        assert!(d.multiple_input_stream_flag);
327        assert_eq!(d.input_stream_identifier, Some(5));
328        assert_eq!(d.timeslice_number, Some(9));
329    }
330
331    #[test]
332    fn parse_extracts_scrambling_index() {
333        // flags = scrambling(0x80) | not_timeslice(0x10) | reserved(0x2C) = 0xBC
334        let raw = [TAG, 4, 0xBC, 0xFD, 0x23, 0x45];
335        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
336        assert!(d.scrambling_sequence_selector);
337        assert_eq!(d.scrambling_sequence_index, Some(0x12345));
338        assert!(d.not_timeslice_flag);
339        assert_eq!(d.timeslice_number, None);
340    }
341
342    #[test]
343    fn parse_full_18_bit_index() {
344        let raw = [TAG, 4, 0xBC, 0xFF, 0xFF, 0xFF];
345        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
346        assert_eq!(d.scrambling_sequence_index, Some(0x3FFFF));
347    }
348
349    #[test]
350    fn parse_rejects_wrong_tag() {
351        let raw = [0x44, 1, 0x3E];
352        assert!(matches!(
353            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
354            Error::InvalidDescriptor { tag: 0x44, .. }
355        ));
356    }
357
358    #[test]
359    fn parse_rejects_truncated_scrambling() {
360        let raw = [TAG, 1, 0x9C]; // scrambling flag set, but no scrambling bytes
361        assert!(matches!(
362            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
363            Error::BufferTooShort { .. }
364        ));
365    }
366
367    #[test]
368    fn serialize_round_trip_all_fields() {
369        let d = S2SatelliteDeliverySystemDescriptor {
370            scrambling_sequence_selector: true,
371            multiple_input_stream_flag: true,
372            not_timeslice_flag: false,
373            ts_gs_mode: TsGsMode::DvbTransportStream,
374            scrambling_sequence_index: Some(0x2BCDE),
375            input_stream_identifier: Some(0x42),
376            timeslice_number: Some(0x11),
377        };
378        let mut buf = vec![0u8; d.serialized_len()];
379        d.serialize_into(&mut buf).unwrap();
380        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
381    }
382
383    #[test]
384    fn serialize_round_trip_not_timeslice() {
385        let d = S2SatelliteDeliverySystemDescriptor {
386            scrambling_sequence_selector: false,
387            multiple_input_stream_flag: false,
388            not_timeslice_flag: true,
389            ts_gs_mode: TsGsMode::Gse,
390            scrambling_sequence_index: None,
391            input_stream_identifier: None,
392            timeslice_number: None,
393        };
394        let mut buf = vec![0u8; d.serialized_len()];
395        d.serialize_into(&mut buf).unwrap();
396        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
397    }
398}