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