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}
101dvb_common::impl_spec_display!(TsGsMode, Reserved);
102
103/// S2 Satellite Delivery System Descriptor (§6.2.13.3, Table 42).
104#[derive(Debug, Clone, PartialEq, Eq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize))]
106pub struct S2SatelliteDeliverySystemDescriptor {
107    /// When set, `scrambling_sequence_index` is present.
108    pub scrambling_sequence_selector: bool,
109    /// When set, `input_stream_identifier` is present.
110    pub multiple_input_stream_flag: bool,
111    /// When set, timeslicing is NOT used and `timeslice_number` is absent.
112    pub not_timeslice_flag: bool,
113    /// 2-bit TS/GS mode (Table 43).
114    pub ts_gs_mode: TsGsMode,
115    /// 18-bit PLS Gold scrambling sequence index.
116    pub scrambling_sequence_index: Option<u32>,
117    /// 8-bit input_stream_identifier (ISI).
118    pub input_stream_identifier: Option<u8>,
119    /// 8-bit timeslice_number, present when `not_timeslice_flag` is false.
120    pub timeslice_number: Option<u8>,
121}
122
123impl<'a> Parse<'a> for S2SatelliteDeliverySystemDescriptor {
124    type Error = crate::error::Error;
125    fn parse(bytes: &'a [u8]) -> Result<Self> {
126        if bytes.len() < HEADER_LEN + FLAGS_LEN {
127            return Err(Error::BufferTooShort {
128                need: HEADER_LEN + FLAGS_LEN,
129                have: bytes.len(),
130                what: "S2SatelliteDeliverySystemDescriptor header",
131            });
132        }
133        let body = descriptor_body(
134            bytes,
135            TAG,
136            "S2SatelliteDeliverySystemDescriptor",
137            "unexpected tag for s2_satellite_delivery_system_descriptor",
138        )?;
139        if body.len() < FLAGS_LEN {
140            return Err(Error::InvalidDescriptor {
141                tag: TAG,
142                reason: "body must contain at least the flags byte",
143            });
144        }
145
146        // Reserved bits are ignored on parse (EN 300 468 §5.1).
147        let flags = body[0];
148        let scrambling_sequence_selector = (flags & FLAG_SCRAMBLING_SELECTOR) != 0;
149        let multiple_input_stream_flag = (flags & FLAG_MULTIPLE_INPUT_STREAM) != 0;
150        let not_timeslice_flag = (flags & FLAG_NOT_TIMESLICE) != 0;
151        let ts_gs_mode = TsGsMode::from_u8(flags & TS_GS_MODE_MASK);
152
153        let mut pos = FLAGS_LEN;
154
155        let scrambling_sequence_index = if scrambling_sequence_selector {
156            if pos + SCRAMBLING_FIELD_LEN > body.len() {
157                return Err(Error::BufferTooShort {
158                    need: pos + SCRAMBLING_FIELD_LEN,
159                    have: body.len(),
160                    what: "S2SatelliteDeliverySystemDescriptor scrambling_sequence_index",
161                });
162            }
163            let b0 = body[pos];
164            let index = (u32::from(b0 & SCRAMBLING_INDEX_HI_MASK) << 16)
165                | (u32::from(body[pos + 1]) << 8)
166                | u32::from(body[pos + 2]);
167            pos += SCRAMBLING_FIELD_LEN;
168            Some(index)
169        } else {
170            None
171        };
172
173        let input_stream_identifier = if multiple_input_stream_flag {
174            if pos + ISI_FIELD_LEN > body.len() {
175                return Err(Error::BufferTooShort {
176                    need: pos + ISI_FIELD_LEN,
177                    have: body.len(),
178                    what: "S2SatelliteDeliverySystemDescriptor input_stream_identifier",
179                });
180            }
181            let isi = body[pos];
182            pos += ISI_FIELD_LEN;
183            Some(isi)
184        } else {
185            None
186        };
187
188        // timeslice_number is present when timeslicing IS used (not_timeslice_flag == 0).
189        let timeslice_number = if !not_timeslice_flag {
190            if pos + TIMESLICE_FIELD_LEN > body.len() {
191                return Err(Error::BufferTooShort {
192                    need: pos + TIMESLICE_FIELD_LEN,
193                    have: body.len(),
194                    what: "S2SatelliteDeliverySystemDescriptor timeslice_number",
195                });
196            }
197            Some(body[pos])
198        } else {
199            None
200        };
201
202        Ok(Self {
203            scrambling_sequence_selector,
204            multiple_input_stream_flag,
205            not_timeslice_flag,
206            ts_gs_mode,
207            scrambling_sequence_index,
208            input_stream_identifier,
209            timeslice_number,
210        })
211    }
212}
213
214impl Serialize for S2SatelliteDeliverySystemDescriptor {
215    type Error = crate::error::Error;
216    fn serialized_len(&self) -> usize {
217        HEADER_LEN
218            + FLAGS_LEN
219            + if self.scrambling_sequence_selector {
220                SCRAMBLING_FIELD_LEN
221            } else {
222                0
223            }
224            + if self.multiple_input_stream_flag {
225                ISI_FIELD_LEN
226            } else {
227                0
228            }
229            + if self.not_timeslice_flag {
230                0
231            } else {
232                TIMESLICE_FIELD_LEN
233            }
234    }
235
236    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
237        let len = self.serialized_len();
238        if buf.len() < len {
239            return Err(Error::OutputBufferTooSmall {
240                need: len,
241                have: buf.len(),
242            });
243        }
244        buf[0] = TAG;
245        buf[1] = (len - HEADER_LEN) as u8;
246
247        let mut flags = FLAG_RESERVED_BITS | (self.ts_gs_mode.to_u8() & TS_GS_MODE_MASK);
248        if self.scrambling_sequence_selector {
249            flags |= FLAG_SCRAMBLING_SELECTOR;
250        }
251        if self.multiple_input_stream_flag {
252            flags |= FLAG_MULTIPLE_INPUT_STREAM;
253        }
254        if self.not_timeslice_flag {
255            flags |= FLAG_NOT_TIMESLICE;
256        }
257        buf[HEADER_LEN] = flags;
258
259        let mut pos = HEADER_LEN + FLAGS_LEN;
260        if self.scrambling_sequence_selector {
261            let index = self.scrambling_sequence_index.unwrap_or(0) & SCRAMBLING_INDEX_MAX;
262            buf[pos] = SCRAMBLING_RESERVED_MASK | ((index >> 16) as u8 & SCRAMBLING_INDEX_HI_MASK);
263            buf[pos + 1] = (index >> 8) as u8;
264            buf[pos + 2] = index as u8;
265            pos += SCRAMBLING_FIELD_LEN;
266        }
267        if self.multiple_input_stream_flag {
268            buf[pos] = self.input_stream_identifier.unwrap_or(0);
269            pos += ISI_FIELD_LEN;
270        }
271        if !self.not_timeslice_flag {
272            buf[pos] = self.timeslice_number.unwrap_or(0);
273        }
274        Ok(len)
275    }
276}
277impl<'a> crate::traits::DescriptorDef<'a> for S2SatelliteDeliverySystemDescriptor {
278    const TAG: u8 = TAG;
279    const NAME: &'static str = "S2_SATELLITE_DELIVERY_SYSTEM";
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn ts_gs_mode_from_u8_to_u8_roundtrip() {
288        for b in 0..=0xFFu8 {
289            assert_eq!(
290                TsGsMode::from_u8(b).to_u8(),
291                b,
292                "round-trip fail for {b:#04x}"
293            );
294        }
295    }
296
297    /// flags 0x2C = reserved bits set, all feature flags clear, ts_gs_mode 0,
298    /// not_timeslice_flag clear → timeslice_number present.
299    #[test]
300    fn parse_minimal_with_timeslice() {
301        let raw = [TAG, 2, 0x2C, 0x07];
302        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
303        assert!(!d.scrambling_sequence_selector);
304        assert!(!d.multiple_input_stream_flag);
305        assert!(!d.not_timeslice_flag);
306        assert_eq!(d.ts_gs_mode, TsGsMode::GenericPacketized);
307        assert_eq!(d.timeslice_number, Some(0x07));
308        assert_eq!(d.scrambling_sequence_index, None);
309        assert_eq!(d.input_stream_identifier, None);
310    }
311
312    #[test]
313    fn parse_not_timeslice_omits_timeslice_number() {
314        // not_timeslice_flag (0x10) set + reserved (0x2C) + ts_gs_mode 2 = 0x3E
315        let raw = [TAG, 1, 0x3E];
316        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
317        assert!(d.not_timeslice_flag);
318        assert_eq!(d.ts_gs_mode, TsGsMode::DvbTransportStream);
319        assert_eq!(d.timeslice_number, None);
320    }
321
322    #[test]
323    fn parse_extracts_isi_and_timeslice() {
324        // flags = MIS(0x40) | reserved(0x2C) = 0x6C; ISI byte, then timeslice byte
325        let raw = [TAG, 3, 0x6C, 0x05, 0x09];
326        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
327        assert!(d.multiple_input_stream_flag);
328        assert_eq!(d.input_stream_identifier, Some(5));
329        assert_eq!(d.timeslice_number, Some(9));
330    }
331
332    #[test]
333    fn parse_extracts_scrambling_index() {
334        // flags = scrambling(0x80) | not_timeslice(0x10) | reserved(0x2C) = 0xBC
335        let raw = [TAG, 4, 0xBC, 0xFD, 0x23, 0x45];
336        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
337        assert!(d.scrambling_sequence_selector);
338        assert_eq!(d.scrambling_sequence_index, Some(0x12345));
339        assert!(d.not_timeslice_flag);
340        assert_eq!(d.timeslice_number, None);
341    }
342
343    #[test]
344    fn parse_full_18_bit_index() {
345        let raw = [TAG, 4, 0xBC, 0xFF, 0xFF, 0xFF];
346        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
347        assert_eq!(d.scrambling_sequence_index, Some(0x3FFFF));
348    }
349
350    #[test]
351    fn parse_rejects_wrong_tag() {
352        let raw = [0x44, 1, 0x3E];
353        assert!(matches!(
354            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
355            Error::InvalidDescriptor { tag: 0x44, .. }
356        ));
357    }
358
359    #[test]
360    fn parse_rejects_truncated_scrambling() {
361        let raw = [TAG, 1, 0x9C]; // scrambling flag set, but no scrambling bytes
362        assert!(matches!(
363            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
364            Error::BufferTooShort { .. }
365        ));
366    }
367
368    #[test]
369    fn serialize_round_trip_all_fields() {
370        let d = S2SatelliteDeliverySystemDescriptor {
371            scrambling_sequence_selector: true,
372            multiple_input_stream_flag: true,
373            not_timeslice_flag: false,
374            ts_gs_mode: TsGsMode::DvbTransportStream,
375            scrambling_sequence_index: Some(0x2BCDE),
376            input_stream_identifier: Some(0x42),
377            timeslice_number: Some(0x11),
378        };
379        let mut buf = vec![0u8; d.serialized_len()];
380        d.serialize_into(&mut buf).unwrap();
381        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
382    }
383
384    #[test]
385    fn serialize_round_trip_not_timeslice() {
386        let d = S2SatelliteDeliverySystemDescriptor {
387            scrambling_sequence_selector: false,
388            multiple_input_stream_flag: false,
389            not_timeslice_flag: true,
390            ts_gs_mode: TsGsMode::Gse,
391            scrambling_sequence_index: None,
392            input_stream_identifier: None,
393            timeslice_number: None,
394        };
395        let mut buf = vec![0u8; d.serialized_len()];
396        d.serialize_into(&mut buf).unwrap();
397        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
398    }
399}