Skip to main content

dvb_si/descriptors/
satellite_delivery_system.rs

1//! Satellite Delivery System Descriptor -- ETSI EN 300 468 \u00a76.2.13.2 (tag 0x43).
2//!
3//! Carried inside the NIT's `transport_stream_loop`'s second descriptor loop.
4//! Conveys carrier tuning parameters for a DVB-S / DVB-S2 transponder.
5
6use crate::error::{Error, Result};
7use crate::traits::Descriptor;
8use dvb_common::{Parse, Serialize};
9
10/// Descriptor tag for satellite_delivery_system_descriptor.
11pub const TAG: u8 = 0x43;
12const HEADER_LEN: usize = 2;
13const BODY_LEN: u8 = 11;
14
15/// Polarization (\u00a76.2.13.2 Table 38).
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18pub enum Polarization {
19    /// Linear horizontal.
20    LinearHorizontal,
21    /// Linear vertical.
22    LinearVertical,
23    /// Circular left.
24    CircularLeft,
25    /// Circular right.
26    CircularRight,
27}
28
29/// Modulation system (§6.2.13.2 Table 40: DVB-S or DVB-S2).
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub enum ModulationSystem {
33    /// DVB-S (first generation).
34    DvbS,
35    /// DVB-S2 (second generation).
36    DvbS2,
37}
38
39/// Modulation type (\u00a76.2.13.2 Table 41).
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub enum ModulationType {
43    /// Auto-detect.
44    Auto,
45    /// QPSK.
46    Qpsk,
47    /// 8PSK.
48    Psk8,
49    /// 16QAM.
50    Qam16,
51}
52
53/// Roll-off factor (§6.2.13.2 Table 39, DVB-S2 only).
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56pub enum RollOff {
57    /// 0.35 (DVB-S default).
58    Alpha035,
59    /// 0.25 (DVB-S2 common).
60    Alpha025,
61    /// 0.20 (DVB-S2 narrow).
62    Alpha020,
63    /// Reserved -- do not emit.
64    Reserved,
65}
66
67/// Satellite Delivery System Descriptor.
68#[derive(Debug, Clone, PartialEq, Eq)]
69#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
70pub struct SatelliteDeliverySystemDescriptor {
71    /// 32-bit BCD frequency in GHz (e.g. 11_725_000 kHz \u2192 0x11725000 = 11.72500 GHz).
72    pub frequency_bcd: u32,
73    /// 16-bit BCD orbital position tenths of a degree (e.g. 0x1920 = 192.0\u00b0).
74    pub orbital_position_bcd: u16,
75    /// False = west, true = east.
76    pub east: bool,
77    /// Polarization.
78    pub polarization: Polarization,
79    /// DVB-S2 roll-off factor. Meaningful only when `modulation_system` is
80    /// DVB-S2 (Table 37); for DVB-S the bits are reserved_zero_future_use and
81    /// serialize emits them as 0 regardless of this field.
82    pub roll_off: RollOff,
83    /// Modulation system.
84    pub modulation_system: ModulationSystem,
85    /// Modulation type.
86    pub modulation_type: ModulationType,
87    /// 28-bit BCD symbol rate in Msym/s (e.g. 0x0275_000 = 27.500 Msym/s).
88    pub symbol_rate_bcd: u32,
89    /// 4-bit FEC inner code.
90    pub fec_inner: u8,
91}
92
93impl<'a> Parse<'a> for SatelliteDeliverySystemDescriptor {
94    type Error = crate::error::Error;
95    fn parse(bytes: &'a [u8]) -> Result<Self> {
96        if bytes.len() < HEADER_LEN {
97            return Err(Error::BufferTooShort {
98                need: HEADER_LEN,
99                have: bytes.len(),
100                what: "satellite delivery system descriptor header",
101            });
102        }
103
104        let tag = bytes[0];
105        if tag != TAG {
106            return Err(Error::InvalidDescriptor {
107                tag,
108                reason: "expected tag 0x43",
109            });
110        }
111
112        let length = bytes[1] as usize;
113        let total = HEADER_LEN + length;
114
115        if bytes.len() < total {
116            return Err(Error::BufferTooShort {
117                need: total,
118                have: bytes.len(),
119                what: "satellite delivery system descriptor body",
120            });
121        }
122
123        if length != BODY_LEN as usize {
124            return Err(Error::InvalidDescriptor {
125                tag: TAG,
126                reason: "descriptor_length must equal 11",
127            });
128        }
129
130        let body = &bytes[HEADER_LEN..total];
131
132        // Frequency: 4 bytes BCD (GHz.MMMM)
133        let frequency_bcd = u32::from_be_bytes([body[0], body[1], body[2], body[3]]);
134
135        // Orbital position: 2 bytes BCD (tenths of a degree)
136        let orbital_position_bcd = u16::from_be_bytes([body[4], body[5]]);
137
138        // Flags byte 6: bit 7 = west_east_flag, bits 5-6 = polarization,
139        // bits 3-4 = roll_off, bit 2 = modulation_system, bits 1-0 = modulation_type
140        let flags = body[6];
141        let east = (flags & 0x80) != 0;
142
143        let pol_raw = (flags >> 5) & 0x03;
144        let polarization = match pol_raw {
145            0 => Polarization::LinearHorizontal,
146            1 => Polarization::LinearVertical,
147            2 => Polarization::CircularLeft,
148            _ => Polarization::CircularRight,
149        };
150
151        let roll_raw = (flags >> 3) & 0x03;
152        let roll_off = match roll_raw {
153            0 => RollOff::Alpha035,
154            1 => RollOff::Alpha025,
155            2 => RollOff::Alpha020,
156            _ => RollOff::Reserved,
157        };
158
159        let mod_sys_raw = (flags >> 2) & 0x01;
160        let modulation_system = match mod_sys_raw {
161            0 => ModulationSystem::DvbS,
162            _ => ModulationSystem::DvbS2,
163        };
164
165        let mod_type_raw = flags & 0x03;
166        let modulation_type = match mod_type_raw {
167            0 => ModulationType::Auto,
168            1 => ModulationType::Qpsk,
169            2 => ModulationType::Psk8,
170            _ => ModulationType::Qam16,
171        };
172
173        // Symbol rate: 28-bit BCD packed into 4 bytes (3.5 bytes + 4-bit FEC)
174        let symbol_rate_and_fec = u32::from_be_bytes([body[7], body[8], body[9], body[10]]);
175        let symbol_rate_bcd = symbol_rate_and_fec >> 4;
176        let fec_inner = (symbol_rate_and_fec & 0x0F) as u8;
177
178        Ok(SatelliteDeliverySystemDescriptor {
179            frequency_bcd,
180            orbital_position_bcd,
181            east,
182            polarization,
183            roll_off,
184            modulation_system,
185            modulation_type,
186            symbol_rate_bcd,
187            fec_inner,
188        })
189    }
190}
191
192impl Serialize for SatelliteDeliverySystemDescriptor {
193    type Error = crate::error::Error;
194    fn serialized_len(&self) -> usize {
195        HEADER_LEN + BODY_LEN as usize
196    }
197
198    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
199        let len = self.serialized_len();
200        if buf.len() < len {
201            return Err(Error::OutputBufferTooSmall {
202                need: len,
203                have: buf.len(),
204            });
205        }
206
207        buf[0] = TAG;
208        buf[1] = BODY_LEN;
209
210        // Frequency: 4 bytes BCD
211        let freq_bytes = self.frequency_bcd.to_be_bytes();
212        buf[2..6].copy_from_slice(&freq_bytes);
213
214        // Orbital position: 2 bytes BCD
215        let orb_bytes = self.orbital_position_bcd.to_be_bytes();
216        buf[6..8].copy_from_slice(&orb_bytes);
217
218        // Flags byte: combine west_east, polarization, roll_off, modulation_system, modulation_type
219        let mut flags: u8 = 0;
220        if self.east {
221            flags |= 0x80;
222        }
223        flags |= match self.polarization {
224            Polarization::LinearHorizontal => 0x00,
225            Polarization::LinearVertical => 0x20,
226            Polarization::CircularLeft => 0x40,
227            Polarization::CircularRight => 0x60,
228        };
229        // Table 37: roll_off exists only when modulation_system == DVB-S2;
230        // for DVB-S those 2 bits are reserved_zero_future_use and SHALL be 0.
231        if self.modulation_system == ModulationSystem::DvbS2 {
232            flags |= match self.roll_off {
233                RollOff::Alpha035 => 0x00,
234                RollOff::Alpha025 => 0x08,
235                RollOff::Alpha020 => 0x10,
236                RollOff::Reserved => 0x18,
237            };
238        }
239        flags |= match self.modulation_system {
240            ModulationSystem::DvbS => 0x00,
241            ModulationSystem::DvbS2 => 0x04,
242        };
243        flags |= match self.modulation_type {
244            ModulationType::Auto => 0x00,
245            ModulationType::Qpsk => 0x01,
246            ModulationType::Psk8 => 0x02,
247            ModulationType::Qam16 => 0x03,
248        };
249        buf[8] = flags;
250
251        // Symbol rate + FEC_inner: 28-bit BCD shifted left 4 bits, then OR with FEC.
252        // Mask to 28 bits so an over-range value can't spill past the field.
253        let sym_freq =
254            ((self.symbol_rate_bcd & 0x0FFF_FFFF) << 4) | (u32::from(self.fec_inner) & 0x0F);
255        let sym_bytes = sym_freq.to_be_bytes();
256        buf[9..13].copy_from_slice(&sym_bytes);
257
258        Ok(len)
259    }
260}
261
262impl<'a> Descriptor<'a> for SatelliteDeliverySystemDescriptor {
263    const TAG: u8 = TAG;
264
265    fn descriptor_length(&self) -> u8 {
266        BODY_LEN
267    }
268}
269
270impl<'a> crate::traits::DescriptorDef<'a> for SatelliteDeliverySystemDescriptor {
271    const TAG: u8 = TAG;
272    const NAME: &'static str = "SATELLITE_DELIVERY_SYSTEM";
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    /// Build a valid 13-byte descriptor (2 header + 11 body) and confirm
280    /// parse extracts frequency and orbital position correctly.
281    #[test]
282    fn parse_extracts_frequency_and_orbital_position() {
283        // frequency: 11.7250 GHz \u2192 BCD 0x11 0x72 0x50 0x00
284        // orbital: 192.0\u00b0 \u2192 BCD 0x19 0x20
285        let raw: Vec<u8> = vec![
286            TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, // frequency
287            0x19, 0x20, // orbital position
288            0x00, // flags (all defaults: west, linear-h, alpha-035, DVB-S, auto)
289            0x02, 0x75, 0x00, 0x00, // symbol rate 27.500 Msym/s, FEC 0
290        ];
291        let desc = SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
292        assert_eq!(desc.frequency_bcd, 0x11725000);
293        assert_eq!(desc.orbital_position_bcd, 0x1920);
294    }
295
296    /// Flags byte bit 7 encodes west/east direction.
297    #[test]
298    fn parse_extracts_west_east_flag() {
299        // East: bit 7 = 1
300        let raw_east: Vec<u8> = vec![
301            TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20,
302            0x80, // east flag set, everything else zero
303            0x02, 0x75, 0x00, 0x00,
304        ];
305        let desc_east = SatelliteDeliverySystemDescriptor::parse(&raw_east).unwrap();
306        assert!(desc_east.east, "east should be true when bit 7 is set");
307
308        // West: bit 7 = 0
309        let raw_west: Vec<u8> = vec![
310            TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20, 0x00, // east flag clear
311            0x02, 0x75, 0x00, 0x00,
312        ];
313        let desc_west = SatelliteDeliverySystemDescriptor::parse(&raw_west).unwrap();
314        assert!(!desc_west.east, "east should be false when bit 7 is clear");
315    }
316
317    /// All four polarization values are extracted correctly from bits 5-6.
318    #[test]
319    fn parse_extracts_polarization_variants() {
320        let pol_pairs: [(u8, Polarization); 4] = [
321            (0x00, Polarization::LinearHorizontal),
322            (0x20, Polarization::LinearVertical),
323            (0x40, Polarization::CircularLeft),
324            (0x60, Polarization::CircularRight),
325        ];
326
327        for (offset, expected_pol) in pol_pairs {
328            let raw: Vec<u8> = vec![
329                TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20,
330                offset, // polarization bits
331                0x02, 0x75, 0x00, 0x00,
332            ];
333            let desc = SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
334            assert_eq!(
335                desc.polarization, expected_pol,
336                "polarization mismatch for offset 0x{:02x}",
337                offset
338            );
339        }
340    }
341
342    /// Modulation system (bit 2) and modulation type (bits 1-0) are extracted.
343    #[test]
344    fn parse_extracts_modulation_system_and_type() {
345        // DVB-S (bit 2 = 0), QPSK (bits 1-0 = 01)
346        let raw: Vec<u8> = vec![
347            TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20, 0x01, // DVB-S, QPSK
348            0x02, 0x75, 0x00, 0x00,
349        ];
350        let desc = SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
351        assert_eq!(desc.modulation_system, ModulationSystem::DvbS);
352        assert_eq!(desc.modulation_type, ModulationType::Qpsk);
353
354        // DVB-S2 (bit 2 = 1), 8PSK (bits 1-0 = 10)
355        let raw2: Vec<u8> = vec![
356            TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20,
357            0x06, // DVB-S2 (0x04) + 8PSK (0x02)
358            0x02, 0x75, 0x00, 0x00,
359        ];
360        let desc2 = SatelliteDeliverySystemDescriptor::parse(&raw2).unwrap();
361        assert_eq!(desc2.modulation_system, ModulationSystem::DvbS2);
362        assert_eq!(desc2.modulation_type, ModulationType::Psk8);
363    }
364
365    /// Roll-off codes (bits 3-4) are extracted correctly.
366    #[test]
367    fn parse_extracts_roll_off() {
368        let roll_pairs: [(u8, RollOff); 4] = [
369            (0x00, RollOff::Alpha035),
370            (0x08, RollOff::Alpha025),
371            (0x10, RollOff::Alpha020),
372            (0x18, RollOff::Reserved),
373        ];
374
375        for (offset, expected_roll) in roll_pairs {
376            let raw: Vec<u8> = vec![
377                TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20, offset, // roll-off bits
378                0x02, 0x75, 0x00, 0x00,
379            ];
380            let desc = SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
381            assert_eq!(desc.roll_off, expected_roll);
382        }
383    }
384
385    /// Symbol rate (28-bit BCD) and FEC inner (4 bits) are extracted from
386    /// the last 4 bytes.
387    #[test]
388    fn parse_extracts_symbol_rate_and_fec() {
389        // symbol_rate: 27.500 Msym/s \u2192 BCD 0x027500, FEC: 5/6 \u2192 0x5
390        let raw: Vec<u8> = vec![
391            TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20, 0x00, 0x02, 0x75, 0x00,
392            0x05, // symbol_rate_bcd = 0x027500, fec_inner = 5
393        ];
394        let desc = SatelliteDeliverySystemDescriptor::parse(&raw).unwrap();
395        assert_eq!(desc.symbol_rate_bcd, 0x0275000);
396        assert_eq!(desc.fec_inner, 5);
397
398        // FEC full range test: 0x0 to 0xF
399        let raw2: Vec<u8> = vec![
400            TAG, BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20, 0x00, 0x02, 0x75, 0x00,
401            0x0F, // FEC = 0x0F
402        ];
403        let desc2 = SatelliteDeliverySystemDescriptor::parse(&raw2).unwrap();
404        assert_eq!(desc2.fec_inner, 0x0F);
405    }
406
407    /// Wrong tag byte should return InvalidDescriptor.
408    #[test]
409    fn parse_rejects_wrong_tag() {
410        let raw: Vec<u8> = vec![
411            0x44, // wrong tag (cable delivery system)
412            BODY_LEN, 0x11, 0x72, 0x50, 0x00, 0x19, 0x20, 0x00, 0x02, 0x75, 0x00, 0x00,
413        ];
414        let err = SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err();
415        assert!(
416            matches!(err, Error::InvalidDescriptor { tag: 0x44, .. }),
417            "expected InvalidDescriptor(tag=0x44), got {err:?}"
418        );
419    }
420
421    /// Body length must be exactly 11. Wrong length returns InvalidDescriptor.
422    #[test]
423    fn parse_rejects_wrong_length() {
424        let raw: Vec<u8> = vec![
425            TAG, 0x05, // wrong length (should be 11)
426            0x11, 0x72, 0x50, 0x00, 0x19, 0x20,
427        ];
428        let err = SatelliteDeliverySystemDescriptor::parse(&raw).unwrap_err();
429        assert!(
430            matches!(
431                err,
432                Error::InvalidDescriptor {
433                    reason: "descriptor_length must equal 11",
434                    ..
435                }
436            ),
437            "expected InvalidDescriptor about length, got {err:?}"
438        );
439    }
440
441    /// Parse \u2192 serialize \u2192 re-parse should yield an equal struct and
442    /// identical bytes.
443    #[test]
444    fn serialize_round_trip() {
445        let desc = SatelliteDeliverySystemDescriptor {
446            frequency_bcd: 0x11725000,
447            orbital_position_bcd: 0x1920,
448            east: true,
449            polarization: Polarization::CircularRight,
450            roll_off: RollOff::Alpha025,
451            modulation_system: ModulationSystem::DvbS2,
452            modulation_type: ModulationType::Psk8,
453            symbol_rate_bcd: 0x027500,
454            fec_inner: 5,
455        };
456
457        let mut buf = vec![0u8; desc.serialized_len()];
458        let written = desc.serialize_into(&mut buf).unwrap();
459        assert_eq!(written, desc.serialized_len());
460
461        let reparsed = SatelliteDeliverySystemDescriptor::parse(&buf).unwrap();
462        assert_eq!(desc, reparsed);
463    }
464}