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; its value coding is
42/// Table 43 ("Coding of the TS GS mode"). The descriptor layout itself is Table 42.
43const TS_GS_MODE_MASK: u8 = 0x03;
44/// Reserved flag bits set to 1 on serialize. Per §6.2.13.3 Table 42, bit 5 is
45/// `reserved_zero_future_use` (MUST be 0); only bits 3..2 (`reserved_future_use`)
46/// are set, giving 0x0C. All reserved bits are ignored on parse (§5.1).
47const FLAG_RESERVED_BITS: u8 = 0x0C;
48
49const SCRAMBLING_RESERVED_MASK: u8 = 0xFC;
50const SCRAMBLING_INDEX_HI_MASK: u8 = 0x03;
51const SCRAMBLING_INDEX_MAX: u32 = 0x3FFFF;
52
53/// S2 Satellite Delivery System Descriptor (§6.2.13.3, Table 42).
54#[derive(Debug, Clone, PartialEq, Eq)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize))]
56pub struct S2SatelliteDeliverySystemDescriptor {
57    /// When set, `scrambling_sequence_index` is present.
58    pub scrambling_sequence_selector: bool,
59    /// When set, `input_stream_identifier` is present.
60    pub multiple_input_stream_flag: bool,
61    /// When set, timeslicing is NOT used and `timeslice_number` is absent.
62    pub not_timeslice_flag: bool,
63    /// 2-bit TS/GS mode (Table 43: 0 generic packetized, 1 GSE, 2 DVB TS, 3 reserved).
64    pub ts_gs_mode: u8,
65    /// 18-bit PLS Gold scrambling sequence index.
66    pub scrambling_sequence_index: Option<u32>,
67    /// 8-bit input_stream_identifier (ISI).
68    pub input_stream_identifier: Option<u8>,
69    /// 8-bit timeslice_number, present when `not_timeslice_flag` is false.
70    pub timeslice_number: Option<u8>,
71}
72
73impl<'a> Parse<'a> for S2SatelliteDeliverySystemDescriptor {
74    type Error = crate::error::Error;
75    fn parse(bytes: &'a [u8]) -> Result<Self> {
76        if bytes.len() < HEADER_LEN + FLAGS_LEN {
77            return Err(Error::BufferTooShort {
78                need: HEADER_LEN + FLAGS_LEN,
79                have: bytes.len(),
80                what: "S2SatelliteDeliverySystemDescriptor header",
81            });
82        }
83        let body = descriptor_body(
84            bytes,
85            TAG,
86            "S2SatelliteDeliverySystemDescriptor",
87            "unexpected tag for s2_satellite_delivery_system_descriptor",
88        )?;
89        if body.len() < FLAGS_LEN {
90            return Err(Error::InvalidDescriptor {
91                tag: TAG,
92                reason: "body must contain at least the flags byte",
93            });
94        }
95
96        // Reserved bits are ignored on parse (EN 300 468 §5.1).
97        let flags = body[0];
98        let scrambling_sequence_selector = (flags & FLAG_SCRAMBLING_SELECTOR) != 0;
99        let multiple_input_stream_flag = (flags & FLAG_MULTIPLE_INPUT_STREAM) != 0;
100        let not_timeslice_flag = (flags & FLAG_NOT_TIMESLICE) != 0;
101        let ts_gs_mode = flags & TS_GS_MODE_MASK;
102
103        let mut pos = FLAGS_LEN;
104
105        let scrambling_sequence_index = if scrambling_sequence_selector {
106            if pos + SCRAMBLING_FIELD_LEN > body.len() {
107                return Err(Error::BufferTooShort {
108                    need: pos + SCRAMBLING_FIELD_LEN,
109                    have: body.len(),
110                    what: "S2SatelliteDeliverySystemDescriptor scrambling_sequence_index",
111                });
112            }
113            let b0 = body[pos];
114            let index = (u32::from(b0 & SCRAMBLING_INDEX_HI_MASK) << 16)
115                | (u32::from(body[pos + 1]) << 8)
116                | u32::from(body[pos + 2]);
117            pos += SCRAMBLING_FIELD_LEN;
118            Some(index)
119        } else {
120            None
121        };
122
123        let input_stream_identifier = if multiple_input_stream_flag {
124            if pos + ISI_FIELD_LEN > body.len() {
125                return Err(Error::BufferTooShort {
126                    need: pos + ISI_FIELD_LEN,
127                    have: body.len(),
128                    what: "S2SatelliteDeliverySystemDescriptor input_stream_identifier",
129                });
130            }
131            let isi = body[pos];
132            pos += ISI_FIELD_LEN;
133            Some(isi)
134        } else {
135            None
136        };
137
138        // timeslice_number is present when timeslicing IS used (not_timeslice_flag == 0).
139        let timeslice_number = if !not_timeslice_flag {
140            if pos + TIMESLICE_FIELD_LEN > body.len() {
141                return Err(Error::BufferTooShort {
142                    need: pos + TIMESLICE_FIELD_LEN,
143                    have: body.len(),
144                    what: "S2SatelliteDeliverySystemDescriptor timeslice_number",
145                });
146            }
147            Some(body[pos])
148        } else {
149            None
150        };
151
152        Ok(Self {
153            scrambling_sequence_selector,
154            multiple_input_stream_flag,
155            not_timeslice_flag,
156            ts_gs_mode,
157            scrambling_sequence_index,
158            input_stream_identifier,
159            timeslice_number,
160        })
161    }
162}
163
164impl Serialize for S2SatelliteDeliverySystemDescriptor {
165    type Error = crate::error::Error;
166    fn serialized_len(&self) -> usize {
167        HEADER_LEN
168            + FLAGS_LEN
169            + if self.scrambling_sequence_selector {
170                SCRAMBLING_FIELD_LEN
171            } else {
172                0
173            }
174            + if self.multiple_input_stream_flag {
175                ISI_FIELD_LEN
176            } else {
177                0
178            }
179            + if self.not_timeslice_flag {
180                0
181            } else {
182                TIMESLICE_FIELD_LEN
183            }
184    }
185
186    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
187        let len = self.serialized_len();
188        if buf.len() < len {
189            return Err(Error::OutputBufferTooSmall {
190                need: len,
191                have: buf.len(),
192            });
193        }
194        buf[0] = TAG;
195        buf[1] = (len - HEADER_LEN) as u8;
196
197        let mut flags = FLAG_RESERVED_BITS | (self.ts_gs_mode & TS_GS_MODE_MASK);
198        if self.scrambling_sequence_selector {
199            flags |= FLAG_SCRAMBLING_SELECTOR;
200        }
201        if self.multiple_input_stream_flag {
202            flags |= FLAG_MULTIPLE_INPUT_STREAM;
203        }
204        if self.not_timeslice_flag {
205            flags |= FLAG_NOT_TIMESLICE;
206        }
207        buf[HEADER_LEN] = flags;
208
209        let mut pos = HEADER_LEN + FLAGS_LEN;
210        if self.scrambling_sequence_selector {
211            let index = self.scrambling_sequence_index.unwrap_or(0) & SCRAMBLING_INDEX_MAX;
212            buf[pos] = SCRAMBLING_RESERVED_MASK | ((index >> 16) as u8 & SCRAMBLING_INDEX_HI_MASK);
213            buf[pos + 1] = (index >> 8) as u8;
214            buf[pos + 2] = index as u8;
215            pos += SCRAMBLING_FIELD_LEN;
216        }
217        if self.multiple_input_stream_flag {
218            buf[pos] = self.input_stream_identifier.unwrap_or(0);
219            pos += ISI_FIELD_LEN;
220        }
221        if !self.not_timeslice_flag {
222            buf[pos] = self.timeslice_number.unwrap_or(0);
223        }
224        Ok(len)
225    }
226}
227impl<'a> crate::traits::DescriptorDef<'a> for S2SatelliteDeliverySystemDescriptor {
228    const TAG: u8 = TAG;
229    const NAME: &'static str = "S2_SATELLITE_DELIVERY_SYSTEM";
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    /// flags 0x2C = reserved bits set, all feature flags clear, ts_gs_mode 0,
237    /// not_timeslice_flag clear → timeslice_number present.
238    #[test]
239    fn parse_minimal_with_timeslice() {
240        let raw = [TAG, 2, 0x2C, 0x07];
241        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
242        assert!(!d.scrambling_sequence_selector);
243        assert!(!d.multiple_input_stream_flag);
244        assert!(!d.not_timeslice_flag);
245        assert_eq!(d.ts_gs_mode, 0);
246        assert_eq!(d.timeslice_number, Some(0x07));
247        assert_eq!(d.scrambling_sequence_index, None);
248        assert_eq!(d.input_stream_identifier, None);
249    }
250
251    #[test]
252    fn parse_not_timeslice_omits_timeslice_number() {
253        // not_timeslice_flag (0x10) set + reserved (0x2C) + ts_gs_mode 2 = 0x3E
254        let raw = [TAG, 1, 0x3E];
255        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
256        assert!(d.not_timeslice_flag);
257        assert_eq!(d.ts_gs_mode, 2);
258        assert_eq!(d.timeslice_number, None);
259    }
260
261    #[test]
262    fn parse_extracts_isi_and_timeslice() {
263        // flags = MIS(0x40) | reserved(0x2C) = 0x6C; ISI byte, then timeslice byte
264        let raw = [TAG, 3, 0x6C, 0x05, 0x09];
265        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
266        assert!(d.multiple_input_stream_flag);
267        assert_eq!(d.input_stream_identifier, Some(5));
268        assert_eq!(d.timeslice_number, Some(9));
269    }
270
271    #[test]
272    fn parse_extracts_scrambling_index() {
273        // flags = scrambling(0x80) | not_timeslice(0x10) | reserved(0x2C) = 0xBC
274        let raw = [TAG, 4, 0xBC, 0xFD, 0x23, 0x45];
275        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
276        assert!(d.scrambling_sequence_selector);
277        assert_eq!(d.scrambling_sequence_index, Some(0x12345));
278        assert!(d.not_timeslice_flag);
279        assert_eq!(d.timeslice_number, None);
280    }
281
282    #[test]
283    fn parse_full_18_bit_index() {
284        let raw = [TAG, 4, 0xBC, 0xFF, 0xFF, 0xFF];
285        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
286        assert_eq!(d.scrambling_sequence_index, Some(0x3FFFF));
287    }
288
289    #[test]
290    fn parse_rejects_wrong_tag() {
291        let raw = [0x44, 1, 0x3E];
292        assert!(matches!(
293            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
294            Error::InvalidDescriptor { tag: 0x44, .. }
295        ));
296    }
297
298    #[test]
299    fn parse_rejects_truncated_scrambling() {
300        let raw = [TAG, 1, 0x9C]; // scrambling flag set, but no scrambling bytes
301        assert!(matches!(
302            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
303            Error::BufferTooShort { .. }
304        ));
305    }
306
307    #[test]
308    fn serialize_round_trip_all_fields() {
309        let d = S2SatelliteDeliverySystemDescriptor {
310            scrambling_sequence_selector: true,
311            multiple_input_stream_flag: true,
312            not_timeslice_flag: false,
313            ts_gs_mode: 2,
314            scrambling_sequence_index: Some(0x2BCDE),
315            input_stream_identifier: Some(0x42),
316            timeslice_number: Some(0x11),
317        };
318        let mut buf = vec![0u8; d.serialized_len()];
319        d.serialize_into(&mut buf).unwrap();
320        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
321    }
322
323    #[test]
324    fn serialize_round_trip_not_timeslice() {
325        let d = S2SatelliteDeliverySystemDescriptor {
326            scrambling_sequence_selector: false,
327            multiple_input_stream_flag: false,
328            not_timeslice_flag: true,
329            ts_gs_mode: 1,
330            scrambling_sequence_index: None,
331            input_stream_identifier: None,
332            timeslice_number: None,
333        };
334        let mut buf = vec![0u8; d.serialized_len()];
335        d.serialize_into(&mut buf).unwrap();
336        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
337    }
338}