1use super::descriptor_body;
10use crate::error::{Error, Result};
11use dvb_common::{Parse, Serialize};
12
13pub const TAG: u8 = 0x7E;
15pub const HEADER_LEN: usize = 2;
17pub const BODY_LEN: usize = 1;
19
20const USER_DEFINED_MASK: u8 = 0b1000_0000;
21const RESERVED_MASK: u8 = 0b0111_0000;
22const DO_NOT_SCRAMBLE_MASK: u8 = 0b0000_1000;
23const CONTROL_REMOTE_ACCESS_MASK: u8 = 0b0000_0110;
24const CONTROL_REMOTE_ACCESS_SHIFT: u8 = 1;
25const DO_NOT_APPLY_REVOCATION_MASK: u8 = 0b0000_0001;
26pub const CONTROL_REMOTE_ACCESS_MAX: u8 = 0b11;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32#[non_exhaustive]
33pub enum ControlRemoteAccess {
34 Enabled,
36 EnabledManagedDomain,
39 EnabledManagedDomainTimeLimited,
42 NotAllowed,
44 Reserved(u8),
46}
47
48impl ControlRemoteAccess {
49 #[must_use]
50 pub fn from_u8(v: u8) -> Self {
53 match v {
54 0b00 => Self::Enabled,
55 0b01 => Self::EnabledManagedDomain,
56 0b10 => Self::EnabledManagedDomainTimeLimited,
57 0b11 => Self::NotAllowed,
58 v => Self::Reserved(v),
59 }
60 }
61
62 #[must_use]
63 pub fn to_u8(self) -> u8 {
65 match self {
66 Self::Enabled => 0b00,
67 Self::EnabledManagedDomain => 0b01,
68 Self::EnabledManagedDomainTimeLimited => 0b10,
69 Self::NotAllowed => 0b11,
70 Self::Reserved(v) => v,
71 }
72 }
73
74 #[must_use]
75 pub fn name(self) -> &'static str {
77 match self {
78 Self::Enabled => "redistribution enabled",
79 Self::EnabledManagedDomain => "redistribution enabled (managed domain only)",
80 Self::EnabledManagedDomainTimeLimited => {
81 "redistribution enabled (managed domain, time-limited)"
82 }
83 Self::NotAllowed => "redistribution not allowed",
84 Self::Reserved(_) => "reserved",
85 }
86 }
87}
88dvb_common::impl_spec_display!(ControlRemoteAccess, Reserved);
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct FtaContentManagementDescriptor {
94 pub user_defined: bool,
96 pub do_not_scramble: bool,
98 pub control_remote_access_over_internet: ControlRemoteAccess,
100 pub do_not_apply_revocation: bool,
102}
103
104impl<'a> Parse<'a> for FtaContentManagementDescriptor {
105 type Error = crate::error::Error;
106 fn parse(bytes: &'a [u8]) -> Result<Self> {
107 let body = descriptor_body(
108 bytes,
109 TAG,
110 "FtaContentManagementDescriptor",
111 "unexpected tag for FTA_content_management_descriptor",
112 )?;
113 if body.len() != BODY_LEN {
114 return Err(Error::InvalidDescriptor {
115 tag: TAG,
116 reason: "FTA_content_management_descriptor length must be exactly 1",
117 });
118 }
119 let flags = body[0];
120 Ok(Self {
122 user_defined: flags & USER_DEFINED_MASK != 0,
123 do_not_scramble: flags & DO_NOT_SCRAMBLE_MASK != 0,
124 control_remote_access_over_internet: ControlRemoteAccess::from_u8(
125 (flags & CONTROL_REMOTE_ACCESS_MASK) >> CONTROL_REMOTE_ACCESS_SHIFT,
126 ),
127 do_not_apply_revocation: flags & DO_NOT_APPLY_REVOCATION_MASK != 0,
128 })
129 }
130}
131
132impl Serialize for FtaContentManagementDescriptor {
133 type Error = crate::error::Error;
134 fn serialized_len(&self) -> usize {
135 HEADER_LEN + BODY_LEN
136 }
137
138 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
139 if self.control_remote_access_over_internet.to_u8() > CONTROL_REMOTE_ACCESS_MAX {
140 return Err(Error::InvalidDescriptor {
141 tag: TAG,
142 reason: "control_remote_access_over_internet exceeds 2 bits",
143 });
144 }
145 let len = self.serialized_len();
146 if buf.len() < len {
147 return Err(Error::OutputBufferTooSmall {
148 need: len,
149 have: buf.len(),
150 });
151 }
152 let mut flags = RESERVED_MASK;
154 if self.user_defined {
155 flags |= USER_DEFINED_MASK;
156 }
157 if self.do_not_scramble {
158 flags |= DO_NOT_SCRAMBLE_MASK;
159 }
160 flags |= (self.control_remote_access_over_internet.to_u8() << CONTROL_REMOTE_ACCESS_SHIFT)
161 & CONTROL_REMOTE_ACCESS_MASK;
162 if self.do_not_apply_revocation {
163 flags |= DO_NOT_APPLY_REVOCATION_MASK;
164 }
165 buf[0] = TAG;
166 buf[1] = BODY_LEN as u8;
167 buf[HEADER_LEN] = flags;
168 Ok(len)
169 }
170}
171impl<'a> crate::traits::DescriptorDef<'a> for FtaContentManagementDescriptor {
172 const TAG: u8 = TAG;
173 const NAME: &'static str = "FTA_CONTENT_MANAGEMENT";
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn parse_extracts_all_fields() {
182 let bytes = [TAG, 1, 0x8D];
185 let d = FtaContentManagementDescriptor::parse(&bytes).unwrap();
186 assert!(d.user_defined);
187 assert!(d.do_not_scramble);
188 assert_eq!(
189 d.control_remote_access_over_internet,
190 ControlRemoteAccess::EnabledManagedDomainTimeLimited
191 );
192 assert!(d.do_not_apply_revocation);
193 }
194
195 #[test]
196 fn parse_ignores_reserved_bits() {
197 let bytes = [TAG, 1, 0x70];
199 let d = FtaContentManagementDescriptor::parse(&bytes).unwrap();
200 assert!(!d.user_defined);
201 assert!(!d.do_not_scramble);
202 assert_eq!(
203 d.control_remote_access_over_internet,
204 ControlRemoteAccess::Enabled
205 );
206 assert!(!d.do_not_apply_revocation);
207 }
208
209 #[test]
210 fn parse_rejects_wrong_tag() {
211 assert!(matches!(
212 FtaContentManagementDescriptor::parse(&[0x7F, 1, 0]).unwrap_err(),
213 Error::InvalidDescriptor { tag: 0x7F, .. }
214 ));
215 }
216
217 #[test]
218 fn parse_rejects_wrong_length() {
219 assert!(matches!(
220 FtaContentManagementDescriptor::parse(&[TAG, 2, 0, 0]).unwrap_err(),
221 Error::InvalidDescriptor { tag: TAG, .. }
222 ));
223 }
224
225 #[test]
226 fn parse_rejects_short_body() {
227 assert!(matches!(
228 FtaContentManagementDescriptor::parse(&[TAG, 1]).unwrap_err(),
229 Error::BufferTooShort { .. }
230 ));
231 }
232
233 #[test]
234 fn serialize_round_trip() {
235 let d = FtaContentManagementDescriptor {
236 user_defined: true,
237 do_not_scramble: true,
238 control_remote_access_over_internet:
239 ControlRemoteAccess::EnabledManagedDomainTimeLimited,
240 do_not_apply_revocation: true,
241 };
242 let mut buf = vec![0u8; d.serialized_len()];
243 d.serialize_into(&mut buf).unwrap();
244 assert_eq!(buf, [TAG, 1, 0xFD]);
246 assert_eq!(FtaContentManagementDescriptor::parse(&buf).unwrap(), d);
247 }
248
249 #[test]
250 fn serialize_rejects_too_small_buffer() {
251 let d = FtaContentManagementDescriptor {
252 user_defined: false,
253 do_not_scramble: false,
254 control_remote_access_over_internet: ControlRemoteAccess::Enabled,
255 do_not_apply_revocation: false,
256 };
257 let mut buf = vec![0u8; 2];
258 assert!(matches!(
259 d.serialize_into(&mut buf).unwrap_err(),
260 Error::OutputBufferTooSmall { .. }
261 ));
262 }
263
264 #[test]
265 fn serialize_rejects_over_range_cra() {
266 let d = FtaContentManagementDescriptor {
267 user_defined: false,
268 do_not_scramble: false,
269 control_remote_access_over_internet: ControlRemoteAccess::Reserved(0b100), do_not_apply_revocation: false,
271 };
272 let mut buf = vec![0u8; d.serialized_len()];
273 assert!(matches!(
274 d.serialize_into(&mut buf).unwrap_err(),
275 Error::InvalidDescriptor { tag: TAG, .. }
276 ));
277 }
278
279 #[cfg(feature = "serde")]
280 #[test]
281 fn serde_round_trip() {
282 let d = FtaContentManagementDescriptor {
283 user_defined: true,
284 do_not_scramble: false,
285 control_remote_access_over_internet: ControlRemoteAccess::EnabledManagedDomain,
286 do_not_apply_revocation: true,
287 };
288 let json = serde_json::to_string(&d).unwrap();
289 let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
291 }
292
293 #[test]
294 fn control_remote_access_full_range_round_trip() {
295 for b in 0..=0xFF_u8 {
296 let cra = ControlRemoteAccess::from_u8(b);
297 assert_eq!(cra.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
298 }
299 }
300}