Skip to main content

dvb_si/descriptors/
any.rs

1//! Unified descriptor dispatch: [`AnyDescriptor`] + [`parse_loop`].
2//!
3//! [`AnyDescriptor`] is generated from a single declarative list
4//! (`declare_descriptors!`) — one line per crate-implemented descriptor tag.
5//! The list is the single source of truth: it produces the enum, the
6//! `From<T>` conversions, and the tag → type dispatcher, and a drift test
7//! pins each tag literal to the type's [`crate::traits::DescriptorDef::TAG`].
8//!
9//! [`parse_loop`] lazily walks a raw descriptor loop (the variable-length
10//! `descriptor()` sequence inside tables), yielding one [`AnyDescriptor`] per
11//! entry. It never panics: a malformed entry whose length is known yields an
12//! `Err` and iteration continues; a truncated final header/body yields one
13//! final `Err` and then fuses.
14//!
15//! ```
16//! use dvb_si::descriptors::{parse_loop, AnyDescriptor};
17//!
18//! // A two-descriptor loop: short_event (tag 0x4D, "eng" / "News") then an
19//! // unrecognised private tag 0xA7 — the walker yields a typed value for the
20//! // first and `Unknown` for the second, never panicking.
21//! let loop_bytes = [
22//!     0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
23//!     0xA7, 0x02, 0xCA, 0xFE,                               // unknown 0xA7
24//! ];
25//! let items: Vec<_> = parse_loop(&loop_bytes).collect();
26//! assert_eq!(items.len(), 2);
27//! match items[0].as_ref().unwrap() {
28//!     AnyDescriptor::ShortEvent(se) => {
29//!         assert_eq!(se.language_code.as_str(), "eng");
30//!         assert_eq!(se.event_name.decode(), "Hi");
31//!     }
32//!     other => panic!("expected ShortEvent, got {other:?}"),
33//! }
34//! assert!(matches!(items[1].as_ref().unwrap(), AnyDescriptor::Unknown { tag: 0xA7, .. }));
35//! ```
36//!
37//! # Adding a descriptor
38//!
39//! 1. Create the module with the wire layout, a `pub const TAG: u8`, and the
40//!    symmetric [`dvb_common::Parse`]/[`dvb_common::Serialize`] impls +
41//!    round-trip tests (copy an existing module).
42//! 2. `impl DescriptorDef` for the type (`TAG` from the module const, `NAME`
43//!    in SCREAMING_SNAKE without the `_descriptor` suffix).
44//! 3. Add one line to the `declare_descriptors!` invocation below — the enum
45//!    variant, dispatcher arm, and drift test are generated from it.
46//! 4. The integration completeness test walks the generated
47//!    [`AnyDescriptor::DISPATCHED_TAGS`] automatically — no test edits needed.
48
49/// Declares [`AnyDescriptor`] + its dispatcher from one tag list.
50///
51/// Each line is `Variant = 0xTAG => module::Type[<'a>]`. The optional trailing
52/// `@no_dispatch …` section adds variants that are NOT reachable from the
53/// generated dispatcher (private / context-dependent tags such as 0x83
54/// logical_channel) — the variant exists for callers that opt in via the
55/// registry, but `dispatch` never produces it.
56macro_rules! declare_descriptors {
57    (
58        $lt:lifetime;
59        $( $variant:ident = $tag:literal => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
60        $( ; @no_dispatch $( $nd_variant:ident => $($nd_path:ident)::+ $(<$nd_plt:lifetime>)? ),+ $(,)? )?
61    ) => {
62        /// Every crate-implemented descriptor, plus an `Unknown` fallthrough.
63        ///
64        /// serde uses external tagging with camelCase variant keys —
65        /// a parsed short_event_descriptor serializes as `{"shortEvent": {…}}`.
66        /// Variant names map 1:1 to the descriptor modules; see each module
67        /// for the wire layout.
68        #[derive(Debug)]
69        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
70        #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
71        #[non_exhaustive]
72        pub enum AnyDescriptor<$lt> {
73            $(
74                #[allow(missing_docs)]
75                $variant($($path)::+ $(<$plt>)?),
76            )+
77            $($(
78                #[allow(missing_docs)]
79                $nd_variant($($nd_path)::+ $(<$nd_plt>)?),
80            )+)?
81            /// Runtime-registered custom descriptor (see [`DescriptorRegistry`]).
82            ///
83            /// [`DescriptorRegistry`]: crate::descriptors::registry::DescriptorRegistry
84            Other {
85                /// The raw descriptor_tag byte.
86                tag: u8,
87                /// The parsed, type-erased descriptor value. Use
88                /// [`DescriptorObject::as_any`][crate::descriptors::registry::DescriptorObject::as_any]
89                /// followed by `downcast_ref` to recover the concrete type.
90                #[cfg_attr(
91                    feature = "serde",
92                    serde(serialize_with = "crate::descriptors::registry::serialize_erased")
93                )]
94                value: Box<dyn crate::descriptors::registry::DescriptorObject>,
95            },
96            /// Tag with no typed implementation; `body` is the payload sans
97            /// the 2-byte (tag, length) header.
98            Unknown {
99                /// The raw descriptor_tag byte.
100                tag: u8,
101                /// The raw payload bytes (descriptor_length bytes).
102                body: &$lt [u8],
103            },
104        }
105
106        $(
107            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyDescriptor<$lt> {
108                fn from(d: $($path)::+ $(<$plt>)?) -> Self {
109                    Self::$variant(d)
110                }
111            }
112        )+
113        $($(
114            impl<$lt> From<$($nd_path)::+ $(<$nd_plt>)?> for AnyDescriptor<$lt> {
115                fn from(d: $($nd_path)::+ $(<$nd_plt>)?) -> Self {
116                    Self::$nd_variant(d)
117                }
118            }
119        )+)?
120
121        impl<$lt> AnyDescriptor<$lt> {
122            /// Every tag the generated dispatcher routes (excludes `@no_dispatch`
123            /// variants and [`AnyDescriptor::Unknown`]).
124            pub const DISPATCHED_TAGS: &'static [u8] = &[$($tag),+];
125
126            /// Parse one full descriptor (2-byte header included) by its tag.
127            ///
128            /// `None` means no typed implementation exists for `tag` (the
129            /// caller turns that into [`AnyDescriptor::Unknown`]). `Some(Err)`
130            /// is a typed parse failure for a recognised tag.
131            pub(crate) fn dispatch(tag: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
132                use dvb_common::Parse;
133                match tag {
134                    $(
135                        $tag => Some(<$($path)::+>::parse(full).map(Self::$variant)),
136                    )+
137                    _ => None,
138                }
139            }
140        }
141
142        #[cfg(test)]
143        mod macro_drift {
144            #[test]
145            fn tag_literals_match_descriptor_def() {
146                use crate::traits::DescriptorDef;
147                $(
148                    assert_eq!(
149                        $tag,
150                        <$($path)::+ as DescriptorDef>::TAG,
151                        concat!("tag literal drift for ", stringify!($variant)),
152                    );
153                    assert!(
154                        !<$($path)::+ as DescriptorDef>::NAME.is_empty(),
155                        concat!("empty NAME for ", stringify!($variant)),
156                    );
157                )+
158                $($(
159                    assert!(
160                        !<$($nd_path)::+ as DescriptorDef>::NAME.is_empty(),
161                        concat!("empty NAME for ", stringify!($nd_variant)),
162                    );
163                )+)?
164            }
165        }
166    };
167}
168
169declare_descriptors! {'a;
170    // MPEG-2 systems descriptors (ISO/IEC 13818-1) used outside table context.
171    Registration = 0x05 => crate::descriptors::registration::RegistrationDescriptor<'a>,
172    DataStreamAlignment = 0x06 => crate::descriptors::data_stream_alignment::DataStreamAlignmentDescriptor,
173    Ca = 0x09 => crate::descriptors::ca::CaDescriptor<'a>,
174    Iso639Language = 0x0A => crate::descriptors::iso_639_language::Iso639LanguageDescriptor,
175    PrivateDataIndicator = 0x0F => crate::descriptors::private_data_indicator::PrivateDataIndicatorDescriptor,
176    // DVB descriptors (ETSI EN 300 468) — contiguous 0x40..=0x7F.
177    NetworkName = 0x40 => crate::descriptors::network_name::NetworkNameDescriptor<'a>,
178    ServiceList = 0x41 => crate::descriptors::service_list::ServiceListDescriptor,
179    Stuffing = 0x42 => crate::descriptors::stuffing::StuffingDescriptor<'a>,
180    SatelliteDeliverySystem = 0x43 => crate::descriptors::satellite_delivery_system::SatelliteDeliverySystemDescriptor,
181    CableDeliverySystem = 0x44 => crate::descriptors::cable_delivery_system::CableDeliverySystemDescriptor,
182    VbiData = 0x45 => crate::descriptors::vbi_data::VbiDataDescriptor<'a>,
183    VbiTeletext = 0x46 => crate::descriptors::vbi_teletext::VbiTeletextDescriptor,
184    BouquetName = 0x47 => crate::descriptors::bouquet_name::BouquetNameDescriptor<'a>,
185    Service = 0x48 => crate::descriptors::service::ServiceDescriptor<'a>,
186    CountryAvailability = 0x49 => crate::descriptors::country_availability::CountryAvailabilityDescriptor,
187    Linkage = 0x4A => crate::descriptors::linkage::LinkageDescriptor<'a>,
188    NvodReference = 0x4B => crate::descriptors::nvod_reference::NvodReferenceDescriptor,
189    TimeShiftedService = 0x4C => crate::descriptors::time_shifted_service::TimeShiftedServiceDescriptor,
190    ShortEvent = 0x4D => crate::descriptors::short_event::ShortEventDescriptor<'a>,
191    ExtendedEvent = 0x4E => crate::descriptors::extended_event::ExtendedEventDescriptor<'a>,
192    TimeShiftedEvent = 0x4F => crate::descriptors::time_shifted_event::TimeShiftedEventDescriptor,
193    Component = 0x50 => crate::descriptors::component::ComponentDescriptor<'a>,
194    Mosaic = 0x51 => crate::descriptors::mosaic::MosaicDescriptor,
195    StreamIdentifier = 0x52 => crate::descriptors::stream_identifier::StreamIdentifierDescriptor,
196    CaIdentifier = 0x53 => crate::descriptors::ca_identifier::CaIdentifierDescriptor,
197    Content = 0x54 => crate::descriptors::content::ContentDescriptor,
198    ParentalRating = 0x55 => crate::descriptors::parental_rating::ParentalRatingDescriptor,
199    Teletext = 0x56 => crate::descriptors::teletext::TeletextDescriptor,
200    Telephone = 0x57 => crate::descriptors::telephone::TelephoneDescriptor<'a>,
201    LocalTimeOffset = 0x58 => crate::descriptors::local_time_offset::LocalTimeOffsetDescriptor,
202    Subtitling = 0x59 => crate::descriptors::subtitling::SubtitlingDescriptor,
203    TerrestrialDeliverySystem = 0x5A => crate::descriptors::terrestrial_delivery_system::TerrestrialDeliverySystemDescriptor,
204    MultilingualNetworkName = 0x5B => crate::descriptors::multilingual_network_name::MultilingualNetworkNameDescriptor<'a>,
205    MultilingualBouquetName = 0x5C => crate::descriptors::multilingual_bouquet_name::MultilingualBouquetNameDescriptor<'a>,
206    MultilingualServiceName = 0x5D => crate::descriptors::multilingual_service_name::MultilingualServiceNameDescriptor<'a>,
207    MultilingualComponent = 0x5E => crate::descriptors::multilingual_component::MultilingualComponentDescriptor<'a>,
208    PrivateDataSpecifier = 0x5F => crate::descriptors::private_data_specifier::PrivateDataSpecifierDescriptor,
209    ServiceMove = 0x60 => crate::descriptors::service_move::ServiceMoveDescriptor,
210    ShortSmoothingBuffer = 0x61 => crate::descriptors::short_smoothing_buffer::ShortSmoothingBufferDescriptor<'a>,
211    FrequencyList = 0x62 => crate::descriptors::frequency_list::FrequencyListDescriptor,
212    PartialTransportStream = 0x63 => crate::descriptors::partial_transport_stream::PartialTransportStreamDescriptor,
213    DataBroadcast = 0x64 => crate::descriptors::data_broadcast::DataBroadcastDescriptor<'a>,
214    Scrambling = 0x65 => crate::descriptors::scrambling::ScramblingDescriptor,
215    DataBroadcastId = 0x66 => crate::descriptors::data_broadcast_id::DataBroadcastIdDescriptor<'a>,
216    TransportStream = 0x67 => crate::descriptors::transport_stream::TransportStreamDescriptor<'a>,
217    Dsng = 0x68 => crate::descriptors::dsng::DsngDescriptor<'a>,
218    Pdc = 0x69 => crate::descriptors::pdc::PdcDescriptor,
219    Ac3 = 0x6A => crate::descriptors::ac3::Ac3Descriptor<'a>,
220    AncillaryData = 0x6B => crate::descriptors::ancillary_data::AncillaryDataDescriptor,
221    CellList = 0x6C => crate::descriptors::cell_list::CellListDescriptor,
222    CellFrequencyLink = 0x6D => crate::descriptors::cell_frequency_link::CellFrequencyLinkDescriptor,
223    AnnouncementSupport = 0x6E => crate::descriptors::announcement_support::AnnouncementSupportDescriptor,
224    ApplicationSignalling = 0x6F => crate::descriptors::application_signalling::ApplicationSignallingDescriptor,
225    AdaptationFieldData = 0x70 => crate::descriptors::adaptation_field_data::AdaptationFieldDataDescriptor,
226    ServiceIdentifier = 0x71 => crate::descriptors::service_identifier::ServiceIdentifierDescriptor<'a>,
227    ServiceAvailability = 0x72 => crate::descriptors::service_availability::ServiceAvailabilityDescriptor,
228    DefaultAuthority = 0x73 => crate::descriptors::default_authority::DefaultAuthorityDescriptor<'a>,
229    RelatedContent = 0x74 => crate::descriptors::related_content::RelatedContentDescriptor,
230    TvaId = 0x75 => crate::descriptors::tva_id::TvaIdDescriptor,
231    ContentIdentifier = 0x76 => crate::descriptors::content_identifier::ContentIdentifierDescriptor<'a>,
232    TimeSliceFecIdentifier = 0x77 => crate::descriptors::time_slice_fec_identifier::TimeSliceFecIdentifierDescriptor<'a>,
233    EcmRepetitionRate = 0x78 => crate::descriptors::ecm_repetition_rate::EcmRepetitionRateDescriptor<'a>,
234    S2SatelliteDeliverySystem = 0x79 => crate::descriptors::s2_satellite_delivery_system::S2SatelliteDeliverySystemDescriptor,
235    EnhancedAc3 = 0x7A => crate::descriptors::enhanced_ac3::EnhancedAc3Descriptor<'a>,
236    Dts = 0x7B => crate::descriptors::dts::DtsDescriptor<'a>,
237    Aac = 0x7C => crate::descriptors::aac::AacDescriptor<'a>,
238    XaitLocation = 0x7D => crate::descriptors::xait_location::XaitLocationDescriptor,
239    FtaContentManagement = 0x7E => crate::descriptors::fta_content_management::FtaContentManagementDescriptor,
240    Extension = 0x7F => crate::descriptors::extension::ExtensionDescriptor<'a>;
241    // Private / context-dependent: variant exists but is NOT auto-dispatched.
242    // 0x83 logical_channel requires private_data_specifier context; enabled
243    // via the descriptor registry (Task 4).
244    @no_dispatch
245    LogicalChannel => crate::descriptors::logical_channel::LogicalChannelDescriptor,
246}
247
248/// Lazily walk a raw descriptor loop. Never panics.
249///
250/// Per-descriptor parse errors yield `Err` and iteration continues (the
251/// descriptor_length field bounds each entry, so the walker can always
252/// advance past a malformed body). A truncated final header or body yields
253/// one `Err` and then the iterator fuses (returns `None` forever after).
254#[must_use]
255pub fn parse_loop(bytes: &[u8]) -> DescriptorIter<'_> {
256    DescriptorIter {
257        bytes,
258        pos: 0,
259        fused: false,
260    }
261}
262
263/// Iterator over a raw descriptor loop; see [`parse_loop`].
264#[derive(Debug, Clone)]
265pub struct DescriptorIter<'a> {
266    bytes: &'a [u8],
267    pos: usize,
268    fused: bool,
269}
270
271impl<'a> Iterator for DescriptorIter<'a> {
272    type Item = crate::Result<AnyDescriptor<'a>>;
273
274    fn next(&mut self) -> Option<Self::Item> {
275        if self.fused || self.pos >= self.bytes.len() {
276            return None;
277        }
278        let rem = &self.bytes[self.pos..];
279        if rem.len() < 2 {
280            self.fused = true;
281            return Some(Err(crate::Error::BufferTooShort {
282                need: 2,
283                have: rem.len(),
284                what: "descriptor header in loop",
285            }));
286        }
287        let tag = rem[0];
288        let len = rem[1] as usize;
289        let total = 2 + len;
290        if rem.len() < total {
291            self.fused = true;
292            return Some(Err(crate::Error::BufferTooShort {
293                need: total,
294                have: rem.len(),
295                what: "descriptor body in loop",
296            }));
297        }
298        let full = &rem[..total];
299        self.pos += total;
300        Some(match AnyDescriptor::dispatch(tag, full) {
301            // Ok(typed) or Err(typed parse error) — either way, the length
302            // field already advanced `pos`, so iteration continues.
303            Some(res) => res,
304            None => Ok(AnyDescriptor::Unknown {
305                tag,
306                body: &full[2..],
307            }),
308        })
309    }
310}
311
312impl std::iter::FusedIterator for DescriptorIter<'_> {}
313
314#[cfg(test)]
315mod tests {
316    use super::*;
317
318    #[test]
319    fn unknown_tag_yields_unknown_with_body_sans_header() {
320        // tag 0xA7 (no typed impl), length 2, body [0xDE, 0xAD].
321        let bytes = [0xA7, 0x02, 0xDE, 0xAD];
322        let items: Vec<_> = parse_loop(&bytes).collect();
323        assert_eq!(items.len(), 1);
324        match items[0].as_ref().unwrap() {
325            AnyDescriptor::Unknown { tag, body } => {
326                assert_eq!(*tag, 0xA7);
327                assert_eq!(*body, &[0xDE, 0xAD]);
328            }
329            other => panic!("expected Unknown, got {other:?}"),
330        }
331    }
332
333    #[test]
334    fn empty_loop_yields_nothing() {
335        assert_eq!(parse_loop(&[]).count(), 0);
336    }
337
338    #[test]
339    fn logical_channel_0x83_is_not_dispatched() {
340        // 0x83 has a variant but no dispatcher entry → Unknown, never panics.
341        let bytes = [0x83, 0x04, 0x00, 0x01, 0xFC, 0x01];
342        let items: Vec<_> = parse_loop(&bytes).collect();
343        assert_eq!(items.len(), 1);
344        assert!(matches!(
345            items[0].as_ref().unwrap(),
346            AnyDescriptor::Unknown { tag: 0x83, .. }
347        ));
348    }
349}