Skip to main content

dvb_si/descriptors/
scrambling.rs

1//! Scrambling Descriptor — ETSI EN 300 468 §6.2.32 (tag 0x65).
2//!
3//! A single byte identifying the scrambling mode in use (Table 86 syntax /
4//! Table 87 coding, PDF pp. 98-99): 0x01 = DVB-CSA1, 0x02 = DVB-CSA2,
5//! 0x03 = DVB-CSA3, 0x10 = DVB-CISSA v1, etc.
6
7use super::descriptor_body;
8use crate::error::{Error, Result};
9use dvb_common::{Parse, Serialize};
10
11/// Descriptor tag for scrambling_descriptor.
12pub const TAG: u8 = 0x65;
13const HEADER_LEN: usize = 2;
14/// Fixed payload length: a single scrambling_mode byte (EN 300 468 Table 86).
15const BODY_LEN: u8 = 1;
16
17/// Scrambling mode — ETSI EN 300 468 Table 87.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[non_exhaustive]
21pub enum ScramblingMode {
22    /// 0x01 — DVB-CSA1.
23    DvbCsa1,
24    /// 0x02 — DVB-CSA2.
25    DvbCsa2,
26    /// 0x03 — DVB-CSA3 standard.
27    DvbCsa3,
28    /// 0x10 — DVB-CISSA v1.
29    DvbCissaV1,
30    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
31    Reserved(u8),
32}
33
34impl ScramblingMode {
35    #[must_use]
36    /// Creates a value from a wire byte, preserving every possible
37    /// byte value for lossless round-trip.
38    pub fn from_u8(v: u8) -> Self {
39        match v {
40            0x01 => Self::DvbCsa1,
41            0x02 => Self::DvbCsa2,
42            0x03 => Self::DvbCsa3,
43            0x10 => Self::DvbCissaV1,
44            v => Self::Reserved(v),
45        }
46    }
47
48    #[must_use]
49    /// Returns the wire byte for this value.
50    pub fn to_u8(self) -> u8 {
51        match self {
52            Self::DvbCsa1 => 0x01,
53            Self::DvbCsa2 => 0x02,
54            Self::DvbCsa3 => 0x03,
55            Self::DvbCissaV1 => 0x10,
56            Self::Reserved(v) => v,
57        }
58    }
59
60    #[must_use]
61    /// Returns a human-readable spec name for this value.
62    pub fn name(self) -> &'static str {
63        match self {
64            Self::DvbCsa1 => "DVB-CSA1",
65            Self::DvbCsa2 => "DVB-CSA2",
66            Self::DvbCsa3 => "DVB-CSA3 (standard)",
67            Self::DvbCissaV1 => "DVB-CISSA v1",
68            Self::Reserved(_) => "reserved",
69        }
70    }
71}
72
73/// Scrambling Descriptor (tag 0x65).
74#[derive(Debug, Clone, PartialEq, Eq)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize))]
76pub struct ScramblingDescriptor {
77    /// 8-bit scrambling_mode (ETSI Table 87).
78    pub scrambling_mode: ScramblingMode,
79}
80
81impl<'a> Parse<'a> for ScramblingDescriptor {
82    type Error = crate::error::Error;
83    fn parse(bytes: &'a [u8]) -> Result<Self> {
84        let body = descriptor_body(
85            bytes,
86            TAG,
87            "ScramblingDescriptor",
88            "unexpected tag for scrambling_descriptor",
89        )?;
90        if body.len() != BODY_LEN as usize {
91            return Err(Error::InvalidDescriptor {
92                tag: TAG,
93                reason: "scrambling_descriptor length must equal 1",
94            });
95        }
96        Ok(Self {
97            scrambling_mode: ScramblingMode::from_u8(body[0]),
98        })
99    }
100}
101
102impl Serialize for ScramblingDescriptor {
103    type Error = crate::error::Error;
104    fn serialized_len(&self) -> usize {
105        HEADER_LEN + BODY_LEN as usize
106    }
107
108    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
109        let len = self.serialized_len();
110        if buf.len() < len {
111            return Err(Error::OutputBufferTooSmall {
112                need: len,
113                have: buf.len(),
114            });
115        }
116        buf[0] = TAG;
117        buf[1] = BODY_LEN;
118        buf[HEADER_LEN] = self.scrambling_mode.to_u8();
119        Ok(len)
120    }
121}
122impl<'a> crate::traits::DescriptorDef<'a> for ScramblingDescriptor {
123    const TAG: u8 = TAG;
124    const NAME: &'static str = "SCRAMBLING";
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn parse_extracts_scrambling_mode() {
133        let bytes = [TAG, 1, 0x02];
134        let d = ScramblingDescriptor::parse(&bytes).unwrap();
135        assert_eq!(d.scrambling_mode, ScramblingMode::DvbCsa2);
136    }
137
138    #[test]
139    fn parse_rejects_wrong_tag() {
140        let err = ScramblingDescriptor::parse(&[0x66, 1, 0x02]).unwrap_err();
141        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x66, .. }));
142    }
143
144    #[test]
145    fn parse_rejects_short_buffer() {
146        let err = ScramblingDescriptor::parse(&[TAG]).unwrap_err();
147        assert!(matches!(err, Error::BufferTooShort { .. }));
148    }
149
150    #[test]
151    fn parse_rejects_truncated_body() {
152        // length=1 but no payload byte present.
153        let err = ScramblingDescriptor::parse(&[TAG, 1]).unwrap_err();
154        assert!(matches!(err, Error::BufferTooShort { .. }));
155    }
156
157    #[test]
158    fn parse_rejects_wrong_length() {
159        let err = ScramblingDescriptor::parse(&[TAG, 2, 0x02, 0x03]).unwrap_err();
160        assert!(matches!(err, Error::InvalidDescriptor { .. }));
161    }
162
163    #[test]
164    fn serialize_round_trip() {
165        let d = ScramblingDescriptor {
166            scrambling_mode: ScramblingMode::DvbCissaV1,
167        };
168        let mut buf = vec![0u8; d.serialized_len()];
169        d.serialize_into(&mut buf).unwrap();
170        let re = ScramblingDescriptor::parse(&buf).unwrap();
171        assert_eq!(d, re);
172    }
173
174    #[test]
175    fn serialize_rejects_too_small_buffer() {
176        let d = ScramblingDescriptor {
177            scrambling_mode: ScramblingMode::DvbCsa3,
178        };
179        let mut tiny = [0u8; 1];
180        let err = d.serialize_into(&mut tiny).unwrap_err();
181        assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
182    }
183
184    #[test]
185    fn descriptor_length_matches_payload() {
186        let d = ScramblingDescriptor {
187            scrambling_mode: ScramblingMode::DvbCsa1,
188        };
189        assert_eq!(d.serialized_len() - 2, 1);
190    }
191
192    #[cfg(feature = "serde")]
193    #[test]
194    fn serde_round_trip() {
195        let d = ScramblingDescriptor {
196            scrambling_mode: ScramblingMode::DvbCsa2,
197        };
198        let json = serde_json::to_string(&d).unwrap();
199        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
200        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
201    }
202
203    #[test]
204    fn scrambling_mode_full_range_round_trip() {
205        for b in 0..=0xFF_u8 {
206            let sm = ScramblingMode::from_u8(b);
207            assert_eq!(sm.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
208        }
209    }
210
211    #[test]
212    fn scrambling_mode_name_for_known() {
213        assert_eq!(ScramblingMode::DvbCsa1.name(), "DVB-CSA1");
214        assert_eq!(ScramblingMode::DvbCsa3.name(), "DVB-CSA3 (standard)");
215        assert_eq!(ScramblingMode::DvbCissaV1.name(), "DVB-CISSA v1");
216        assert_eq!(ScramblingMode::Reserved(0x55).name(), "reserved");
217    }
218
219    #[test]
220    fn scrambling_mode_0x04_0x05_are_reserved() {
221        assert_eq!(
222            ScramblingMode::from_u8(0x04),
223            ScramblingMode::Reserved(0x04)
224        );
225        assert_eq!(
226            ScramblingMode::from_u8(0x05),
227            ScramblingMode::Reserved(0x05)
228        );
229    }
230}