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 DvbCissaV1,
30 Reserved(u8),
32}
33
34impl ScramblingMode {
35 #[must_use]
36 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 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 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}
72dvb_common::impl_spec_display!(ScramblingMode, Reserved);
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize))]
77pub struct ScramblingDescriptor {
78 pub scrambling_mode: ScramblingMode,
80}
81
82impl<'a> Parse<'a> for ScramblingDescriptor {
83 type Error = crate::error::Error;
84 fn parse(bytes: &'a [u8]) -> Result<Self> {
85 let body = descriptor_body(
86 bytes,
87 TAG,
88 "ScramblingDescriptor",
89 "unexpected tag for scrambling_descriptor",
90 )?;
91 if body.len() != BODY_LEN as usize {
92 return Err(Error::InvalidDescriptor {
93 tag: TAG,
94 reason: "scrambling_descriptor length must equal 1",
95 });
96 }
97 Ok(Self {
98 scrambling_mode: ScramblingMode::from_u8(body[0]),
99 })
100 }
101}
102
103impl Serialize for ScramblingDescriptor {
104 type Error = crate::error::Error;
105 fn serialized_len(&self) -> usize {
106 HEADER_LEN + BODY_LEN as usize
107 }
108
109 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
110 let len = self.serialized_len();
111 if buf.len() < len {
112 return Err(Error::OutputBufferTooSmall {
113 need: len,
114 have: buf.len(),
115 });
116 }
117 buf[0] = TAG;
118 buf[1] = BODY_LEN;
119 buf[HEADER_LEN] = self.scrambling_mode.to_u8();
120 Ok(len)
121 }
122}
123impl<'a> crate::traits::DescriptorDef<'a> for ScramblingDescriptor {
124 const TAG: u8 = TAG;
125 const NAME: &'static str = "SCRAMBLING";
126}
127
128#[cfg(test)]
129mod tests {
130 use super::*;
131
132 #[test]
133 fn parse_extracts_scrambling_mode() {
134 let bytes = [TAG, 1, 0x02];
135 let d = ScramblingDescriptor::parse(&bytes).unwrap();
136 assert_eq!(d.scrambling_mode, ScramblingMode::DvbCsa2);
137 }
138
139 #[test]
140 fn parse_rejects_wrong_tag() {
141 let err = ScramblingDescriptor::parse(&[0x66, 1, 0x02]).unwrap_err();
142 assert!(matches!(err, Error::InvalidDescriptor { tag: 0x66, .. }));
143 }
144
145 #[test]
146 fn parse_rejects_short_buffer() {
147 let err = ScramblingDescriptor::parse(&[TAG]).unwrap_err();
148 assert!(matches!(err, Error::BufferTooShort { .. }));
149 }
150
151 #[test]
152 fn parse_rejects_truncated_body() {
153 let err = ScramblingDescriptor::parse(&[TAG, 1]).unwrap_err();
155 assert!(matches!(err, Error::BufferTooShort { .. }));
156 }
157
158 #[test]
159 fn parse_rejects_wrong_length() {
160 let err = ScramblingDescriptor::parse(&[TAG, 2, 0x02, 0x03]).unwrap_err();
161 assert!(matches!(err, Error::InvalidDescriptor { .. }));
162 }
163
164 #[test]
165 fn serialize_round_trip() {
166 let d = ScramblingDescriptor {
167 scrambling_mode: ScramblingMode::DvbCissaV1,
168 };
169 let mut buf = vec![0u8; d.serialized_len()];
170 d.serialize_into(&mut buf).unwrap();
171 let re = ScramblingDescriptor::parse(&buf).unwrap();
172 assert_eq!(d, re);
173 }
174
175 #[test]
176 fn serialize_rejects_too_small_buffer() {
177 let d = ScramblingDescriptor {
178 scrambling_mode: ScramblingMode::DvbCsa3,
179 };
180 let mut tiny = [0u8; 1];
181 let err = d.serialize_into(&mut tiny).unwrap_err();
182 assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
183 }
184
185 #[test]
186 fn descriptor_length_matches_payload() {
187 let d = ScramblingDescriptor {
188 scrambling_mode: ScramblingMode::DvbCsa1,
189 };
190 assert_eq!(d.serialized_len() - 2, 1);
191 }
192
193 #[cfg(feature = "serde")]
194 #[test]
195 fn serde_round_trip() {
196 let d = ScramblingDescriptor {
197 scrambling_mode: ScramblingMode::DvbCsa2,
198 };
199 let json = serde_json::to_string(&d).unwrap();
200 let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
202 }
203
204 #[test]
205 fn scrambling_mode_full_range_round_trip() {
206 for b in 0..=0xFF_u8 {
207 let sm = ScramblingMode::from_u8(b);
208 assert_eq!(sm.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
209 }
210 }
211
212 #[test]
213 fn scrambling_mode_name_for_known() {
214 assert_eq!(ScramblingMode::DvbCsa1.name(), "DVB-CSA1");
215 assert_eq!(ScramblingMode::DvbCsa3.name(), "DVB-CSA3 (standard)");
216 assert_eq!(ScramblingMode::DvbCissaV1.name(), "DVB-CISSA v1");
217 assert_eq!(ScramblingMode::Reserved(0x55).name(), "reserved");
218 }
219
220 #[test]
221 fn scrambling_mode_0x04_0x05_are_reserved() {
222 assert_eq!(
223 ScramblingMode::from_u8(0x04),
224 ScramblingMode::Reserved(0x04)
225 );
226 assert_eq!(
227 ScramblingMode::from_u8(0x05),
228 ScramblingMode::Reserved(0x05)
229 );
230 }
231}