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}
88dvb_common::impl_spec_display!(ControlRemoteAccess, Reserved);
89
90/// FTA Content Management Descriptor.
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct FtaContentManagementDescriptor {
94    /// 1-bit user_defined flag.
95    pub user_defined: bool,
96    /// 1-bit do_not_scramble flag.
97    pub do_not_scramble: bool,
98    /// 2-bit control_remote_access_over_internet field (Table 58).
99    pub control_remote_access_over_internet: ControlRemoteAccess,
100    /// 1-bit do_not_apply_revocation flag.
101    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        // reserved_future_use (3 bits) ignored on parse (§5.1).
121        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        // reserved_future_use 3 bits emitted as 1s (§5.1).
153        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        // user_defined=1, reserved=000, do_not_scramble=1, cra=10, revocation=1
183        // = 1 000 1 10 1 = 0b1000_1101 = 0x8D
184        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        // reserved bits all set, everything else zero: 0 111 0 00 0 = 0x70.
198        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        // Reserved 3 bits emitted as 1s: 1 111 1 10 1 = 0xFD.
245        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), // 3 bits
270            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        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
290        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}