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