Skip to main content

dvb_si/descriptors/
fta_content_management.rs

1//! FTA Content Management Descriptor — ETSI EN 300 468 §6.2.18.1 (tag 0x7E, Table 57, PDF p. 82).
2//!
3//! Carried in NIT/BAT/SDT/EIT. Fixed 1-byte body packing five fields
4//! (Table 57, MSB→LSB): `user_defined` (1), `reserved_future_use` (3),
5//! `do_not_scramble` (1), `control_remote_access_over_internet` (2),
6//! `do_not_apply_revocation` (1).
7//! `control_remote_access_over_internet` coding is Table 58.
8
9use super::descriptor_body;
10use crate::error::{Error, Result};
11use dvb_common::{Parse, Serialize};
12
13/// Descriptor tag for FTA_content_management_descriptor.
14pub const TAG: u8 = 0x7E;
15/// Length of the header (tag byte + length byte).
16pub const HEADER_LEN: usize = 2;
17/// Fixed body length: one packed flag byte.
18pub 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;
26/// Max value of the 2-bit control_remote_access_over_internet field.
27pub const CONTROL_REMOTE_ACCESS_MAX: u8 = 0b11;
28
29/// Control remote access over internet — ETSI EN 300 468 Table 58.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))]
32#[non_exhaustive]
33pub enum ControlRemoteAccess {
34    /// 0b00 — redistribution over the Internet is enabled.
35    Enabled,
36    /// 0b01 — redistribution over the Internet is enabled but only within a
37    /// managed domain.
38    EnabledManagedDomain,
39    /// 0b10 — redistribution over the Internet is enabled but only within a
40    /// managed domain and after a certain short period of time.
41    EnabledManagedDomainTimeLimited,
42    /// 0b11 — redistribution over the Internet is not allowed.
43    NotAllowed,
44    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
45    Reserved(u8),
46}
47
48impl ControlRemoteAccess {
49    #[must_use]
50    /// Creates a value from a wire byte, preserving every possible
51    /// byte value for lossless round-trip.
52    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    /// Returns the wire byte for this value.
64    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    /// Returns a human-readable spec name for this value.
76    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}
88
89/// FTA Content Management Descriptor.
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91#[cfg_attr(feature = "serde", derive(serde::Serialize))]
92pub struct FtaContentManagementDescriptor {
93    /// 1-bit user_defined flag.
94    pub user_defined: bool,
95    /// 1-bit do_not_scramble flag.
96    pub do_not_scramble: bool,
97    /// 2-bit control_remote_access_over_internet field (Table 58).
98    pub control_remote_access_over_internet: ControlRemoteAccess,
99    /// 1-bit do_not_apply_revocation flag.
100    pub do_not_apply_revocation: bool,
101}
102
103impl<'a> Parse<'a> for FtaContentManagementDescriptor {
104    type Error = crate::error::Error;
105    fn parse(bytes: &'a [u8]) -> Result<Self> {
106        let body = descriptor_body(
107            bytes,
108            TAG,
109            "FtaContentManagementDescriptor",
110            "unexpected tag for FTA_content_management_descriptor",
111        )?;
112        if body.len() != BODY_LEN {
113            return Err(Error::InvalidDescriptor {
114                tag: TAG,
115                reason: "FTA_content_management_descriptor length must be exactly 1",
116            });
117        }
118        let flags = body[0];
119        // reserved_future_use (3 bits) ignored on parse (§5.1).
120        Ok(Self {
121            user_defined: flags & USER_DEFINED_MASK != 0,
122            do_not_scramble: flags & DO_NOT_SCRAMBLE_MASK != 0,
123            control_remote_access_over_internet: ControlRemoteAccess::from_u8(
124                (flags & CONTROL_REMOTE_ACCESS_MASK) >> CONTROL_REMOTE_ACCESS_SHIFT,
125            ),
126            do_not_apply_revocation: flags & DO_NOT_APPLY_REVOCATION_MASK != 0,
127        })
128    }
129}
130
131impl Serialize for FtaContentManagementDescriptor {
132    type Error = crate::error::Error;
133    fn serialized_len(&self) -> usize {
134        HEADER_LEN + BODY_LEN
135    }
136
137    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
138        if self.control_remote_access_over_internet.to_u8() > CONTROL_REMOTE_ACCESS_MAX {
139            return Err(Error::InvalidDescriptor {
140                tag: TAG,
141                reason: "control_remote_access_over_internet exceeds 2 bits",
142            });
143        }
144        let len = self.serialized_len();
145        if buf.len() < len {
146            return Err(Error::OutputBufferTooSmall {
147                need: len,
148                have: buf.len(),
149            });
150        }
151        // reserved_future_use 3 bits emitted as 1s (§5.1).
152        let mut flags = RESERVED_MASK;
153        if self.user_defined {
154            flags |= USER_DEFINED_MASK;
155        }
156        if self.do_not_scramble {
157            flags |= DO_NOT_SCRAMBLE_MASK;
158        }
159        flags |= (self.control_remote_access_over_internet.to_u8() << CONTROL_REMOTE_ACCESS_SHIFT)
160            & CONTROL_REMOTE_ACCESS_MASK;
161        if self.do_not_apply_revocation {
162            flags |= DO_NOT_APPLY_REVOCATION_MASK;
163        }
164        buf[0] = TAG;
165        buf[1] = BODY_LEN as u8;
166        buf[HEADER_LEN] = flags;
167        Ok(len)
168    }
169}
170impl<'a> crate::traits::DescriptorDef<'a> for FtaContentManagementDescriptor {
171    const TAG: u8 = TAG;
172    const NAME: &'static str = "FTA_CONTENT_MANAGEMENT";
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn parse_extracts_all_fields() {
181        // user_defined=1, reserved=000, do_not_scramble=1, cra=10, revocation=1
182        // = 1 000 1 10 1 = 0b1000_1101 = 0x8D
183        let bytes = [TAG, 1, 0x8D];
184        let d = FtaContentManagementDescriptor::parse(&bytes).unwrap();
185        assert!(d.user_defined);
186        assert!(d.do_not_scramble);
187        assert_eq!(
188            d.control_remote_access_over_internet,
189            ControlRemoteAccess::EnabledManagedDomainTimeLimited
190        );
191        assert!(d.do_not_apply_revocation);
192    }
193
194    #[test]
195    fn parse_ignores_reserved_bits() {
196        // reserved bits all set, everything else zero: 0 111 0 00 0 = 0x70.
197        let bytes = [TAG, 1, 0x70];
198        let d = FtaContentManagementDescriptor::parse(&bytes).unwrap();
199        assert!(!d.user_defined);
200        assert!(!d.do_not_scramble);
201        assert_eq!(
202            d.control_remote_access_over_internet,
203            ControlRemoteAccess::Enabled
204        );
205        assert!(!d.do_not_apply_revocation);
206    }
207
208    #[test]
209    fn parse_rejects_wrong_tag() {
210        assert!(matches!(
211            FtaContentManagementDescriptor::parse(&[0x7F, 1, 0]).unwrap_err(),
212            Error::InvalidDescriptor { tag: 0x7F, .. }
213        ));
214    }
215
216    #[test]
217    fn parse_rejects_wrong_length() {
218        assert!(matches!(
219            FtaContentManagementDescriptor::parse(&[TAG, 2, 0, 0]).unwrap_err(),
220            Error::InvalidDescriptor { tag: TAG, .. }
221        ));
222    }
223
224    #[test]
225    fn parse_rejects_short_body() {
226        assert!(matches!(
227            FtaContentManagementDescriptor::parse(&[TAG, 1]).unwrap_err(),
228            Error::BufferTooShort { .. }
229        ));
230    }
231
232    #[test]
233    fn serialize_round_trip() {
234        let d = FtaContentManagementDescriptor {
235            user_defined: true,
236            do_not_scramble: true,
237            control_remote_access_over_internet:
238                ControlRemoteAccess::EnabledManagedDomainTimeLimited,
239            do_not_apply_revocation: true,
240        };
241        let mut buf = vec![0u8; d.serialized_len()];
242        d.serialize_into(&mut buf).unwrap();
243        // Reserved 3 bits emitted as 1s: 1 111 1 10 1 = 0xFD.
244        assert_eq!(buf, [TAG, 1, 0xFD]);
245        assert_eq!(FtaContentManagementDescriptor::parse(&buf).unwrap(), d);
246    }
247
248    #[test]
249    fn serialize_rejects_too_small_buffer() {
250        let d = FtaContentManagementDescriptor {
251            user_defined: false,
252            do_not_scramble: false,
253            control_remote_access_over_internet: ControlRemoteAccess::Enabled,
254            do_not_apply_revocation: false,
255        };
256        let mut buf = vec![0u8; 2];
257        assert!(matches!(
258            d.serialize_into(&mut buf).unwrap_err(),
259            Error::OutputBufferTooSmall { .. }
260        ));
261    }
262
263    #[test]
264    fn serialize_rejects_over_range_cra() {
265        let d = FtaContentManagementDescriptor {
266            user_defined: false,
267            do_not_scramble: false,
268            control_remote_access_over_internet: ControlRemoteAccess::Reserved(0b100), // 3 bits
269            do_not_apply_revocation: false,
270        };
271        let mut buf = vec![0u8; d.serialized_len()];
272        assert!(matches!(
273            d.serialize_into(&mut buf).unwrap_err(),
274            Error::InvalidDescriptor { tag: TAG, .. }
275        ));
276    }
277
278    #[cfg(feature = "serde")]
279    #[test]
280    fn serde_round_trip() {
281        let d = FtaContentManagementDescriptor {
282            user_defined: true,
283            do_not_scramble: false,
284            control_remote_access_over_internet: ControlRemoteAccess::EnabledManagedDomain,
285            do_not_apply_revocation: true,
286        };
287        let json = serde_json::to_string(&d).unwrap();
288        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
289        let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
290    }
291
292    #[test]
293    fn control_remote_access_full_range_round_trip() {
294        for b in 0..=0xFF_u8 {
295            let cra = ControlRemoteAccess::from_u8(b);
296            assert_eq!(cra.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
297        }
298    }
299}