Skip to main content

dvb_si/descriptors/ait/
application.rs

1//! Application Descriptor — ETSI TS 102 809 §5.3.5.3, Table 20
2//! (AIT tag 0x00).
3//!
4//! Carried in the AIT per-application descriptor loop. Lists the
5//! application's profile/version triplets, a visibility/service-bound flags
6//! byte, a priority, and a list of transport protocol labels.
7
8use crate::descriptors::descriptor_body;
9use crate::error::{Error, Result};
10use alloc::vec::Vec;
11use dvb_common::{Parse, Serialize};
12
13/// Descriptor tag for application_descriptor (AIT namespace).
14pub const TAG: u8 = 0x00;
15const HEADER_LEN: usize = 2;
16const PROFILE_ENTRY_LEN: usize = 5;
17const FLAGS_LEN: usize = 1;
18const PRIORITY_LEN: usize = 1;
19
20/// 2-bit visibility field — ETSI TS 102 809 §5.2.6.1 Table 5.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[non_exhaustive]
24pub enum Visibility {
25    /// 0 — not visible to users or applications.
26    NotVisibleAll,
27    /// 1 — not visible to users, visible to applications.
28    NotVisibleUsers,
29    /// 2 — reserved_future_use.
30    ReservedFutureUse,
31    /// 3 — visible to users and applications.
32    VisibleAll,
33    /// Catch-all for any other 2-bit value (should not occur).
34    Other(u8),
35}
36
37impl Visibility {
38    /// Decode from the 2-bit wire value.
39    #[must_use]
40    pub fn from_u8(v: u8) -> Self {
41        match v & 0x03 {
42            0 => Self::NotVisibleAll,
43            1 => Self::NotVisibleUsers,
44            2 => Self::ReservedFutureUse,
45            3 => Self::VisibleAll,
46            other => Self::Other(other),
47        }
48    }
49
50    /// Encode to the 2-bit wire value.
51    #[must_use]
52    pub fn to_u8(self) -> u8 {
53        match self {
54            Self::NotVisibleAll => 0,
55            Self::NotVisibleUsers => 1,
56            Self::ReservedFutureUse => 2,
57            Self::VisibleAll => 3,
58            Self::Other(v) => v & 0x03,
59        }
60    }
61
62    /// Spec name.
63    #[must_use]
64    pub fn name(self) -> &'static str {
65        match self {
66            Self::NotVisibleAll => "NOT_VISIBLE_ALL",
67            Self::NotVisibleUsers => "NOT_VISIBLE_USERS",
68            Self::ReservedFutureUse => "reserved_future_use",
69            Self::VisibleAll => "VISIBLE_ALL",
70            Self::Other(_) => "reserved",
71        }
72    }
73}
74dvb_common::impl_spec_display!(Visibility, Other);
75
76/// One application profile entry — Table 4.
77#[derive(Debug, Clone, PartialEq, Eq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize))]
79pub struct ApplicationProfile {
80    /// 16-bit application profile identifier.
81    pub profile: u16,
82    /// Major version.
83    pub version_major: u8,
84    /// Minor version.
85    pub version_minor: u8,
86    /// Micro version.
87    pub version_micro: u8,
88}
89
90/// Application Descriptor (AIT tag 0x00).
91#[derive(Debug, Clone, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct ApplicationDescriptor {
94    /// Profile/version entries.
95    pub profiles: Vec<ApplicationProfile>,
96    /// 1-bit service_bound_flag.
97    pub service_bound_flag: bool,
98    /// 2-bit visibility (Table 5).
99    pub visibility: Visibility,
100    /// Application priority.
101    pub application_priority: u8,
102    /// Transport protocol labels (one per transport protocol descriptor).
103    pub transport_protocol_labels: Vec<u8>,
104}
105
106impl<'a> Parse<'a> for ApplicationDescriptor {
107    type Error = crate::error::Error;
108    fn parse(bytes: &'a [u8]) -> Result<Self> {
109        let body = descriptor_body(
110            bytes,
111            TAG,
112            "ApplicationDescriptor",
113            "unexpected tag for application_descriptor",
114        )?;
115        if body.is_empty() {
116            return Err(Error::InvalidDescriptor {
117                tag: TAG,
118                reason: "application_descriptor body is empty",
119            });
120        }
121        let profiles_length = body[0] as usize;
122        let profiles_end = 1 + profiles_length;
123        if profiles_end > body.len() {
124            return Err(Error::InvalidDescriptor {
125                tag: TAG,
126                reason: "application_profiles_length runs past descriptor end",
127            });
128        }
129        if profiles_length % PROFILE_ENTRY_LEN != 0 {
130            return Err(Error::InvalidDescriptor {
131                tag: TAG,
132                reason: "application_profiles_length not a multiple of 5",
133            });
134        }
135        let mut profiles = Vec::with_capacity(profiles_length / PROFILE_ENTRY_LEN);
136        let mut pos = 1;
137        while pos < profiles_end {
138            let profile = u16::from_be_bytes([body[pos], body[pos + 1]]);
139            let version_major = body[pos + 2];
140            let version_minor = body[pos + 3];
141            let version_micro = body[pos + 4];
142            profiles.push(ApplicationProfile {
143                profile,
144                version_major,
145                version_minor,
146                version_micro,
147            });
148            pos += PROFILE_ENTRY_LEN;
149        }
150        if profiles_end + FLAGS_LEN + PRIORITY_LEN > body.len() {
151            return Err(Error::InvalidDescriptor {
152                tag: TAG,
153                reason: "flags/priority bytes missing after profiles",
154            });
155        }
156        let flags_byte = body[profiles_end];
157        let service_bound_flag = (flags_byte & 0x80) != 0;
158        let visibility = Visibility::from_u8((flags_byte >> 5) & 0x03);
159        let application_priority = body[profiles_end + FLAGS_LEN];
160        let labels_start = profiles_end + FLAGS_LEN + PRIORITY_LEN;
161        let transport_protocol_labels = body[labels_start..].to_vec();
162        Ok(Self {
163            profiles,
164            service_bound_flag,
165            visibility,
166            application_priority,
167            transport_protocol_labels,
168        })
169    }
170}
171
172impl Serialize for ApplicationDescriptor {
173    type Error = crate::error::Error;
174    fn serialized_len(&self) -> usize {
175        HEADER_LEN
176            + 1
177            + self.profiles.len() * PROFILE_ENTRY_LEN
178            + FLAGS_LEN
179            + PRIORITY_LEN
180            + self.transport_protocol_labels.len()
181    }
182
183    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
184        let profiles_length = self.profiles.len() * PROFILE_ENTRY_LEN;
185        if profiles_length > u8::MAX as usize {
186            return Err(Error::InvalidDescriptor {
187                tag: TAG,
188                reason: "application_profiles_length exceeds 255 bytes",
189            });
190        }
191        let len = self.serialized_len();
192        let body_len = len - HEADER_LEN;
193        if body_len > u8::MAX as usize {
194            return Err(Error::InvalidDescriptor {
195                tag: TAG,
196                reason: "application_descriptor body exceeds 255 bytes",
197            });
198        }
199        if buf.len() < len {
200            return Err(Error::OutputBufferTooSmall {
201                need: len,
202                have: buf.len(),
203            });
204        }
205        buf[0] = TAG;
206        buf[1] = body_len as u8;
207        buf[2] = profiles_length as u8;
208        let mut pos = 3;
209        for p in &self.profiles {
210            buf[pos..pos + 2].copy_from_slice(&p.profile.to_be_bytes());
211            buf[pos + 2] = p.version_major;
212            buf[pos + 3] = p.version_minor;
213            buf[pos + 4] = p.version_micro;
214            pos += PROFILE_ENTRY_LEN;
215        }
216        let flags_byte = (u8::from(self.service_bound_flag) << 7)
217            | ((self.visibility.to_u8() & 0x03) << 5)
218            | 0x1F;
219        buf[pos] = flags_byte;
220        buf[pos + 1] = self.application_priority;
221        pos += FLAGS_LEN + PRIORITY_LEN;
222        for (i, &label) in self.transport_protocol_labels.iter().enumerate() {
223            buf[pos + i] = label;
224        }
225        Ok(len)
226    }
227}
228
229impl<'a> crate::traits::DescriptorDef<'a> for ApplicationDescriptor {
230    const TAG: u8 = TAG;
231    const NAME: &'static str = "APPLICATION";
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    /// Body layout: profiles_length(1) + 1 profile(5) + flags(1) + priority(1) + 2 labels(2) = 10.
239    fn build_single_profile_two_labels() -> [u8; 12] {
240        [
241            TAG, 10, // header
242            5,  // profiles_length = 1 entry × 5
243            0x00, 0x01, // profile = 1
244            2, 3, 4,    // major=2, minor=3, micro=4
245            0x9F, // service_bound=1, visibility=NOT_VISIBLE_ALL(0), rfu=11111
246            0x0A, // priority=10
247            0x01, 0x02, // two transport labels
248        ]
249    }
250
251    #[test]
252    fn parse_single_profile_with_labels() {
253        let bytes = build_single_profile_two_labels();
254        let d = ApplicationDescriptor::parse(&bytes).unwrap();
255        assert_eq!(d.profiles.len(), 1);
256        assert_eq!(d.profiles[0].profile, 1);
257        assert_eq!(d.profiles[0].version_major, 2);
258        assert_eq!(d.profiles[0].version_minor, 3);
259        assert_eq!(d.profiles[0].version_micro, 4);
260        assert!(d.service_bound_flag);
261        assert_eq!(d.visibility, Visibility::NotVisibleAll);
262        assert_eq!(d.application_priority, 0x0A);
263        assert_eq!(d.transport_protocol_labels, [0x01, 0x02]);
264    }
265
266    #[test]
267    fn parse_visible_all() {
268        // Body: profiles_length(1) + 1 profile(5) + flags(1) + priority(1) + 1 label(1) = 9.
269        let bytes = [
270            TAG, 9, 5, 0x00, 0x10, 1, 0, 0,
271            0x6F, // service_bound=0, visibility=VISIBLE_ALL(3), rfu=11111
272            0xFF, // priority
273            0x42,
274        ];
275        let d = ApplicationDescriptor::parse(&bytes).unwrap();
276        assert!(!d.service_bound_flag);
277        assert_eq!(d.visibility, Visibility::VisibleAll);
278    }
279
280    #[test]
281    fn parse_rejects_short_body() {
282        let err = ApplicationDescriptor::parse(&[TAG]).unwrap_err();
283        assert!(matches!(err, Error::BufferTooShort { .. }));
284    }
285
286    #[test]
287    fn serialize_round_trip() {
288        let d = ApplicationDescriptor {
289            profiles: alloc::vec![ApplicationProfile {
290                profile: 0x0010,
291                version_major: 1,
292                version_minor: 2,
293                version_micro: 3,
294            }],
295            service_bound_flag: true,
296            visibility: Visibility::VisibleAll,
297            application_priority: 5,
298            transport_protocol_labels: alloc::vec![0x01],
299        };
300        let mut buf = vec![0u8; d.serialized_len()];
301        d.serialize_into(&mut buf).unwrap();
302        let re = ApplicationDescriptor::parse(&buf).unwrap();
303        assert_eq!(d, re);
304        assert_eq!(buf[0], TAG);
305    }
306
307    #[test]
308    fn serialize_byte_identical() {
309        let bytes = build_single_profile_two_labels();
310        let d = ApplicationDescriptor::parse(&bytes).unwrap();
311        let mut buf = vec![0u8; d.serialized_len()];
312        d.serialize_into(&mut buf).unwrap();
313        assert_eq!(buf.as_slice(), &bytes[..]);
314    }
315
316    #[test]
317    fn visibility_round_trip() {
318        for v in [
319            Visibility::NotVisibleAll,
320            Visibility::NotVisibleUsers,
321            Visibility::ReservedFutureUse,
322            Visibility::VisibleAll,
323        ] {
324            assert_eq!(Visibility::from_u8(v.to_u8()), v);
325        }
326    }
327}