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 crate::error::{Error, Result};
27use crate::traits::Descriptor;
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, serde::Deserialize))]
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        if bytes[0] != TAG {
84            return Err(Error::InvalidDescriptor {
85                tag: bytes[0],
86                reason: "unexpected tag for s2_satellite_delivery_system_descriptor",
87            });
88        }
89        let length = bytes[1] as usize;
90        let end = HEADER_LEN + length;
91        if bytes.len() < end {
92            return Err(Error::BufferTooShort {
93                need: end,
94                have: bytes.len(),
95                what: "S2SatelliteDeliverySystemDescriptor body",
96            });
97        }
98        if length < FLAGS_LEN {
99            return Err(Error::InvalidDescriptor {
100                tag: TAG,
101                reason: "body must contain at least the flags byte",
102            });
103        }
104
105        // Reserved bits are ignored on parse (EN 300 468 §5.1).
106        let flags = bytes[HEADER_LEN];
107        let scrambling_sequence_selector = (flags & FLAG_SCRAMBLING_SELECTOR) != 0;
108        let multiple_input_stream_flag = (flags & FLAG_MULTIPLE_INPUT_STREAM) != 0;
109        let not_timeslice_flag = (flags & FLAG_NOT_TIMESLICE) != 0;
110        let ts_gs_mode = flags & TS_GS_MODE_MASK;
111
112        let mut pos = HEADER_LEN + FLAGS_LEN;
113
114        let scrambling_sequence_index = if scrambling_sequence_selector {
115            if pos + SCRAMBLING_FIELD_LEN > end {
116                return Err(Error::BufferTooShort {
117                    need: pos + SCRAMBLING_FIELD_LEN - HEADER_LEN,
118                    have: length,
119                    what: "S2SatelliteDeliverySystemDescriptor scrambling_sequence_index",
120                });
121            }
122            let b0 = bytes[pos];
123            let index = (u32::from(b0 & SCRAMBLING_INDEX_HI_MASK) << 16)
124                | (u32::from(bytes[pos + 1]) << 8)
125                | u32::from(bytes[pos + 2]);
126            pos += SCRAMBLING_FIELD_LEN;
127            Some(index)
128        } else {
129            None
130        };
131
132        let input_stream_identifier = if multiple_input_stream_flag {
133            if pos + ISI_FIELD_LEN > end {
134                return Err(Error::BufferTooShort {
135                    need: pos + ISI_FIELD_LEN - HEADER_LEN,
136                    have: length,
137                    what: "S2SatelliteDeliverySystemDescriptor input_stream_identifier",
138                });
139            }
140            let isi = bytes[pos];
141            pos += ISI_FIELD_LEN;
142            Some(isi)
143        } else {
144            None
145        };
146
147        // timeslice_number is present when timeslicing IS used (not_timeslice_flag == 0).
148        let timeslice_number = if !not_timeslice_flag {
149            if pos + TIMESLICE_FIELD_LEN > end {
150                return Err(Error::BufferTooShort {
151                    need: pos + TIMESLICE_FIELD_LEN - HEADER_LEN,
152                    have: length,
153                    what: "S2SatelliteDeliverySystemDescriptor timeslice_number",
154                });
155            }
156            Some(bytes[pos])
157        } else {
158            None
159        };
160
161        Ok(Self {
162            scrambling_sequence_selector,
163            multiple_input_stream_flag,
164            not_timeslice_flag,
165            ts_gs_mode,
166            scrambling_sequence_index,
167            input_stream_identifier,
168            timeslice_number,
169        })
170    }
171}
172
173impl Serialize for S2SatelliteDeliverySystemDescriptor {
174    type Error = crate::error::Error;
175    fn serialized_len(&self) -> usize {
176        HEADER_LEN
177            + FLAGS_LEN
178            + if self.scrambling_sequence_selector {
179                SCRAMBLING_FIELD_LEN
180            } else {
181                0
182            }
183            + if self.multiple_input_stream_flag {
184                ISI_FIELD_LEN
185            } else {
186                0
187            }
188            + if self.not_timeslice_flag {
189                0
190            } else {
191                TIMESLICE_FIELD_LEN
192            }
193    }
194
195    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
196        let len = self.serialized_len();
197        if buf.len() < len {
198            return Err(Error::OutputBufferTooSmall {
199                need: len,
200                have: buf.len(),
201            });
202        }
203        buf[0] = TAG;
204        buf[1] = (len - HEADER_LEN) as u8;
205
206        let mut flags = FLAG_RESERVED_BITS | (self.ts_gs_mode & TS_GS_MODE_MASK);
207        if self.scrambling_sequence_selector {
208            flags |= FLAG_SCRAMBLING_SELECTOR;
209        }
210        if self.multiple_input_stream_flag {
211            flags |= FLAG_MULTIPLE_INPUT_STREAM;
212        }
213        if self.not_timeslice_flag {
214            flags |= FLAG_NOT_TIMESLICE;
215        }
216        buf[HEADER_LEN] = flags;
217
218        let mut pos = HEADER_LEN + FLAGS_LEN;
219        if self.scrambling_sequence_selector {
220            let index = self.scrambling_sequence_index.unwrap_or(0) & SCRAMBLING_INDEX_MAX;
221            buf[pos] = SCRAMBLING_RESERVED_MASK | ((index >> 16) as u8 & SCRAMBLING_INDEX_HI_MASK);
222            buf[pos + 1] = (index >> 8) as u8;
223            buf[pos + 2] = index as u8;
224            pos += SCRAMBLING_FIELD_LEN;
225        }
226        if self.multiple_input_stream_flag {
227            buf[pos] = self.input_stream_identifier.unwrap_or(0);
228            pos += ISI_FIELD_LEN;
229        }
230        if !self.not_timeslice_flag {
231            buf[pos] = self.timeslice_number.unwrap_or(0);
232        }
233        Ok(len)
234    }
235}
236
237impl<'a> Descriptor<'a> for S2SatelliteDeliverySystemDescriptor {
238    const TAG: u8 = TAG;
239    fn descriptor_length(&self) -> u8 {
240        (self.serialized_len() - HEADER_LEN) as u8
241    }
242}
243
244impl<'a> crate::traits::DescriptorDef<'a> for S2SatelliteDeliverySystemDescriptor {
245    const TAG: u8 = TAG;
246    const NAME: &'static str = "S2_SATELLITE_DELIVERY_SYSTEM";
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    /// flags 0x2C = reserved bits set, all feature flags clear, ts_gs_mode 0,
254    /// not_timeslice_flag clear → timeslice_number present.
255    #[test]
256    fn parse_minimal_with_timeslice() {
257        let raw = [TAG, 2, 0x2C, 0x07];
258        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
259        assert!(!d.scrambling_sequence_selector);
260        assert!(!d.multiple_input_stream_flag);
261        assert!(!d.not_timeslice_flag);
262        assert_eq!(d.ts_gs_mode, 0);
263        assert_eq!(d.timeslice_number, Some(0x07));
264        assert_eq!(d.scrambling_sequence_index, None);
265        assert_eq!(d.input_stream_identifier, None);
266    }
267
268    #[test]
269    fn parse_not_timeslice_omits_timeslice_number() {
270        // not_timeslice_flag (0x10) set + reserved (0x2C) + ts_gs_mode 2 = 0x3E
271        let raw = [TAG, 1, 0x3E];
272        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
273        assert!(d.not_timeslice_flag);
274        assert_eq!(d.ts_gs_mode, 2);
275        assert_eq!(d.timeslice_number, None);
276    }
277
278    #[test]
279    fn parse_extracts_isi_and_timeslice() {
280        // flags = MIS(0x40) | reserved(0x2C) = 0x6C; ISI byte, then timeslice byte
281        let raw = [TAG, 3, 0x6C, 0x05, 0x09];
282        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
283        assert!(d.multiple_input_stream_flag);
284        assert_eq!(d.input_stream_identifier, Some(5));
285        assert_eq!(d.timeslice_number, Some(9));
286    }
287
288    #[test]
289    fn parse_extracts_scrambling_index() {
290        // flags = scrambling(0x80) | not_timeslice(0x10) | reserved(0x2C) = 0xBC
291        let raw = [TAG, 4, 0xBC, 0xFD, 0x23, 0x45];
292        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
293        assert!(d.scrambling_sequence_selector);
294        assert_eq!(d.scrambling_sequence_index, Some(0x12345));
295        assert!(d.not_timeslice_flag);
296        assert_eq!(d.timeslice_number, None);
297    }
298
299    #[test]
300    fn parse_full_18_bit_index() {
301        let raw = [TAG, 4, 0xBC, 0xFF, 0xFF, 0xFF];
302        let d = S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
303        assert_eq!(d.scrambling_sequence_index, Some(0x3FFFF));
304    }
305
306    #[test]
307    fn parse_rejects_wrong_tag() {
308        let raw = [0x44, 1, 0x3E];
309        assert!(matches!(
310            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
311            Error::InvalidDescriptor { tag: 0x44, .. }
312        ));
313    }
314
315    #[test]
316    fn parse_rejects_truncated_scrambling() {
317        let raw = [TAG, 1, 0x9C]; // scrambling flag set, but no scrambling bytes
318        assert!(matches!(
319            S2SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err(),
320            Error::BufferTooShort { .. }
321        ));
322    }
323
324    #[test]
325    fn serialize_round_trip_all_fields() {
326        let d = S2SatelliteDeliverySystemDescriptor {
327            scrambling_sequence_selector: true,
328            multiple_input_stream_flag: true,
329            not_timeslice_flag: false,
330            ts_gs_mode: 2,
331            scrambling_sequence_index: Some(0x2BCDE),
332            input_stream_identifier: Some(0x42),
333            timeslice_number: Some(0x11),
334        };
335        let mut buf = vec![0u8; d.serialized_len()];
336        d.serialize_into(&mut buf).unwrap();
337        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
338    }
339
340    #[test]
341    fn serialize_round_trip_not_timeslice() {
342        let d = S2SatelliteDeliverySystemDescriptor {
343            scrambling_sequence_selector: false,
344            multiple_input_stream_flag: false,
345            not_timeslice_flag: true,
346            ts_gs_mode: 1,
347            scrambling_sequence_index: None,
348            input_stream_identifier: None,
349            timeslice_number: None,
350        };
351        let mut buf = vec![0u8; d.serialized_len()];
352        d.serialize_into(&mut buf).unwrap();
353        assert_eq!(S2SatelliteDeliverySystemDescriptor::parse(&buf).unwrap(), d);
354    }
355}