Skip to main content

dvb_si/descriptors/ait/
mod.rs

1//! AIT descriptor namespace — [`AnyAitDescriptor`] + [`parse_ait_loop`].
2//!
3//! The AIT (Application Information Table, ETSI TS 102 809 §5.3) uses a
4//! SEPARATE descriptor namespace from the EN 300 468 SI descriptors. Low tag
5//! bytes mean different things (e.g. AIT tag 0x02 = transport_protocol, but
6//! SI tag 0x02 = video_stream). This module mirrors the structure of
7//! [`crate::descriptors::any`] for the AIT namespace.
8//!
9//! [`AnyAitDescriptor`] is generated from a single declarative list
10//! (`declare_ait_descriptors!`) — one line per AIT descriptor tag. The list
11//! produces the enum, `From` impls, `name()`, `dispatch()`,
12//! `DISPATCHED_TAGS`, and a drift test that pins each tag literal to the
13//! type's [`crate::traits::DescriptorDef::TAG`].
14//!
15//! [`parse_ait_loop`] lazily walks a raw AIT descriptor loop (the
16//! `descriptor()` sequence inside AIT common or per-application loops),
17//! yielding one [`AnyAitDescriptor`] per entry.
18//!
19//! ```
20//! use dvb_si::descriptors::ait::{parse_ait_loop, AnyAitDescriptor};
21//!
22//! // An AIT descriptor loop: application_usage (tag 0x16, usage_type=0x01)
23//! // then an unknown tag 0xFE.
24//! let loop_bytes = [
25//!     0x16, 0x01, 0x01,             // application_usage: Digital Text
26//!     0xFE, 0x02, 0xCA, 0xFE,       // unknown 0xFE
27//! ];
28//! let items: Vec<_> = parse_ait_loop(&loop_bytes).collect();
29//! assert_eq!(items.len(), 2);
30//! match items[0].as_ref().unwrap() {
31//!     AnyAitDescriptor::ApplicationUsage(au) => {
32//!         assert_eq!(au.usage_type, 0x01);
33//!     }
34//!     other => panic!("expected ApplicationUsage, got {other:?}"),
35//! }
36//! assert!(matches!(
37//!     items[1].as_ref().unwrap(),
38//!     AnyAitDescriptor::Unknown { tag: 0xFE, .. }
39//! ));
40//! ```
41
42pub mod application;
43pub mod application_name;
44pub mod application_usage;
45pub mod external_application_authorisation;
46pub mod simple_application_boundary;
47pub mod simple_application_location;
48pub mod transport_protocol;
49
50pub use application::ApplicationDescriptor;
51pub use application::Visibility;
52pub use application_name::ApplicationNameDescriptor;
53pub use application_usage::ApplicationUsageDescriptor;
54pub use external_application_authorisation::ExternalApplicationAuthorisationDescriptor;
55pub use simple_application_boundary::SimpleApplicationBoundaryDescriptor;
56pub use simple_application_location::SimpleApplicationLocationDescriptor;
57pub use transport_protocol::TransportProtocolDescriptor;
58
59/// Declares [`AnyAitDescriptor`] + its dispatcher from one tag list.
60///
61/// Mirrors the `declare_descriptors!` macro in `crate::descriptors::any`.
62/// Each line is `Variant = 0xTAG => crate::descriptors::ait::module::Type[<'a>]`.
63macro_rules! declare_ait_descriptors {
64    (
65        $lt:lifetime;
66        $( $variant:ident = $tag:literal => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
67    ) => {
68        /// Every crate-implemented AIT descriptor, plus an `Unknown` fallthrough.
69        ///
70        /// AIT tags belong to a separate namespace from EN 300 468 SI tags
71        /// (ETSI TS 102 809 §5.3). The same numeric tag value may map to
72        /// a different type here.
73        #[derive(Debug)]
74        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
75        #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
76        #[non_exhaustive]
77        pub enum AnyAitDescriptor<$lt> {
78            $(
79                #[allow(missing_docs)]
80                $variant($($path)::+ $(<$plt>)?),
81            )+
82            /// Tag with no typed implementation; `body` is the payload sans
83            /// the 2-byte (tag, length) header.
84            Unknown {
85                /// The raw descriptor_tag byte.
86                tag: u8,
87                /// The raw payload bytes (descriptor_length bytes).
88                body: &$lt [u8],
89            },
90        }
91
92        $(
93            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyAitDescriptor<$lt> {
94                fn from(d: $($path)::+ $(<$plt>)?) -> Self {
95                    Self::$variant(d)
96                }
97            }
98        )+
99
100        impl<$lt> AnyAitDescriptor<$lt> {
101            /// Every tag the generated dispatcher routes (excludes
102            /// [`AnyAitDescriptor::Unknown`]).
103            pub const DISPATCHED_TAGS: &'static [u8] = &[$($tag),+];
104
105            /// Diagnostic name of the contained AIT descriptor — the type's
106            /// [`DescriptorDef::NAME`](crate::traits::DescriptorDef::NAME)
107            /// (`"APPLICATION"`, `"TRANSPORT_PROTOCOL"`, …); `"UNKNOWN"`
108            /// for [`AnyAitDescriptor::Unknown`].
109            #[must_use]
110            pub fn name(&self) -> &'static str {
111                match self {
112                    $(
113                        Self::$variant(_) =>
114                            <$($path)::+ as crate::traits::DescriptorDef>::NAME,
115                    )+
116                    Self::Unknown { .. } => "UNKNOWN",
117                }
118            }
119
120            /// Parse one full AIT descriptor (2-byte header included) by its tag.
121            ///
122            /// `None` means no typed implementation exists for `tag` (the
123            /// caller turns that into [`AnyAitDescriptor::Unknown`]). `Some(Err)`
124            /// is a typed parse failure for a recognised tag.
125            pub(crate) fn dispatch(tag: u8, full: &$lt [u8]) -> Option<crate::error::Result<Self>> {
126                use dvb_common::Parse;
127                match tag {
128                    $(
129                        $tag => Some(<$($path)::+>::parse(full).map(Self::$variant)),
130                    )+
131                    _ => None,
132                }
133            }
134        }
135
136        #[cfg(test)]
137        mod macro_drift {
138            #[test]
139            fn tag_literals_match_descriptor_def() {
140                use crate::traits::DescriptorDef;
141                $(
142                    assert_eq!(
143                        $tag,
144                        <$($path)::+ as DescriptorDef>::TAG,
145                        concat!("tag literal drift for ", stringify!($variant)),
146                    );
147                    assert!(
148                        !<$($path)::+ as DescriptorDef>::NAME.is_empty(),
149                        concat!("empty NAME for ", stringify!($variant)),
150                    );
151                )+
152            }
153        }
154    };
155}
156
157declare_ait_descriptors! {'a;
158    Application              = 0x00 => crate::descriptors::ait::application::ApplicationDescriptor,
159    ApplicationName          = 0x01 => crate::descriptors::ait::application_name::ApplicationNameDescriptor<'a>,
160    TransportProtocol        = 0x02 => crate::descriptors::ait::transport_protocol::TransportProtocolDescriptor<'a>,
161    ExternalAppAuthorisation = 0x05 => crate::descriptors::ait::external_application_authorisation::ExternalApplicationAuthorisationDescriptor,
162    SimpleAppLocation        = 0x15 => crate::descriptors::ait::simple_application_location::SimpleApplicationLocationDescriptor<'a>,
163    ApplicationUsage         = 0x16 => crate::descriptors::ait::application_usage::ApplicationUsageDescriptor,
164    SimpleAppBoundary        = 0x17 => crate::descriptors::ait::simple_application_boundary::SimpleApplicationBoundaryDescriptor<'a>,
165}
166
167/// Lazily walk a raw AIT descriptor loop. Never panics.
168///
169/// Per-descriptor parse errors yield `Err` and iteration continues (the
170/// `descriptor_length` field bounds each entry, so the walker can always
171/// advance past a malformed body). A truncated final header or body yields
172/// one `Err` and then the iterator fuses.
173#[must_use]
174pub fn parse_ait_loop(bytes: &[u8]) -> AitDescriptorIter<'_> {
175    AitDescriptorIter {
176        bytes,
177        pos: 0,
178        fused: false,
179    }
180}
181
182/// Iterator over a raw AIT descriptor loop; see [`parse_ait_loop`].
183#[derive(Debug, Clone)]
184pub struct AitDescriptorIter<'a> {
185    bytes: &'a [u8],
186    pos: usize,
187    fused: bool,
188}
189
190impl<'a> Iterator for AitDescriptorIter<'a> {
191    type Item = crate::error::Result<AnyAitDescriptor<'a>>;
192
193    fn next(&mut self) -> Option<Self::Item> {
194        let (tag, full) = match crate::descriptors::any::next_loop_entry(
195            self.bytes,
196            &mut self.pos,
197            &mut self.fused,
198        )? {
199            Ok(v) => v,
200            Err(e) => return Some(Err(e)),
201        };
202        Some(match AnyAitDescriptor::dispatch(tag, full) {
203            Some(res) => res,
204            None => Ok(AnyAitDescriptor::Unknown {
205                tag,
206                body: &full[2..],
207            }),
208        })
209    }
210}
211
212impl core::iter::FusedIterator for AitDescriptorIter<'_> {}
213
214/// A raw AIT descriptor loop, borrowed from the section. Zero-copy: walk it
215/// typed via [`AitDescriptorLoop::iter`]; serde serializes the typed walk.
216///
217/// This mirrors [`crate::descriptors::DescriptorLoop`] for the AIT namespace.
218#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
219#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
220pub struct AitDescriptorLoop<'a>(&'a [u8]);
221
222impl<'a> AitDescriptorLoop<'a> {
223    /// Wrap a raw AIT descriptor-loop slice (the `descriptor()` bytes only —
224    /// no enclosing length field).
225    #[must_use]
226    pub const fn new(raw: &'a [u8]) -> Self {
227        Self(raw)
228    }
229
230    /// The raw wire bytes of the loop, verbatim.
231    #[must_use]
232    pub const fn raw(&self) -> &'a [u8] {
233        self.0
234    }
235
236    /// Lazily walk the loop, yielding one typed [`AnyAitDescriptor`] per entry
237    /// (or [`AnyAitDescriptor::Unknown`] for tags with no implementation).
238    /// Delegates to [`parse_ait_loop`]; never panics.
239    #[must_use]
240    pub fn iter(&self) -> AitDescriptorIter<'a> {
241        parse_ait_loop(self.0)
242    }
243}
244
245#[cfg(feature = "serde")]
246impl serde::Serialize for AitDescriptorLoop<'_> {
247    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
248        use alloc::vec::Vec;
249        let items: Vec<crate::error::Result<AnyAitDescriptor<'_>>> = self.iter().collect();
250        s.collect_seq(items.into_iter().filter_map(|r| r.ok()))
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn walk_ait_loop_yields_typed_variants() {
260        // application_usage (0x16) + simple_app_location (0x15) + unknown (0xFE).
261        let buf = alloc::vec![
262            0x16, 0x01, 0x01, 0x15, 0x05, b'/', b'a', b'p', b'p', b'/', 0xFE, 0x02, 0xCA, 0xFE,
263        ];
264        let items: Vec<_> = parse_ait_loop(&buf).collect();
265        assert_eq!(items.len(), 3);
266
267        match items[0].as_ref().unwrap() {
268            AnyAitDescriptor::ApplicationUsage(au) => {
269                assert_eq!(au.usage_type, 0x01);
270            }
271            other => panic!("expected ApplicationUsage, got {other:?}"),
272        }
273
274        match items[1].as_ref().unwrap() {
275            AnyAitDescriptor::SimpleAppLocation(loc) => {
276                assert_eq!(loc.initial_path_bytes.raw(), b"/app/");
277            }
278            other => panic!("expected SimpleAppLocation, got {other:?}"),
279        }
280
281        match items[2].as_ref().unwrap() {
282            AnyAitDescriptor::Unknown { tag, body } => {
283                assert_eq!(*tag, 0xFE);
284                assert_eq!(*body, &[0xCA, 0xFE]);
285            }
286            other => panic!("expected Unknown, got {other:?}"),
287        }
288    }
289
290    #[test]
291    fn ait_descriptor_loop_iter() {
292        let buf = [0x16, 0x01, 0x01];
293        let loop_ = AitDescriptorLoop::new(&buf);
294        let items: Vec<_> = loop_.iter().collect();
295        assert_eq!(items.len(), 1);
296        assert!(matches!(
297            items[0].as_ref().unwrap(),
298            AnyAitDescriptor::ApplicationUsage(_)
299        ));
300        assert_eq!(loop_.raw(), &buf[..]);
301    }
302
303    #[test]
304    fn dispatched_tags_covers_all_known() {
305        assert_eq!(
306            AnyAitDescriptor::DISPATCHED_TAGS,
307            &[0x00, 0x01, 0x02, 0x05, 0x15, 0x16, 0x17]
308        );
309    }
310}