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 [`broadcast_common::Parse`]/[`broadcast_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
49use alloc::boxed::Box;
50#[cfg(feature = "serde")]
51use alloc::string::ToString;
52
53/// Declares [`AnyDescriptor`] + its dispatcher from one tag list.
54///
55/// Each line is `Variant = 0xTAG => module::Type[<'a>]`. The optional trailing
56/// `@no_dispatch …` section adds variants that are NOT reachable from the
57/// generated dispatcher (private / context-dependent tags such as 0x83
58/// logical_channel) — the variant exists for callers that opt in via the
59/// registry, but `dispatch` never produces it.
60macro_rules! declare_descriptors {
61    (
62        $lt:lifetime;
63        $( $variant:ident = $tag:literal => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
64        $( ; @no_dispatch $( $nd_variant:ident => $($nd_path:ident)::+ $(<$nd_plt:lifetime>)? ),+ $(,)? )?
65    ) => {
66        /// Every crate-implemented descriptor, plus an `Unknown` fallthrough.
67        ///
68        /// serde uses external tagging with camelCase variant keys —
69        /// a parsed short_event_descriptor serializes as `{"shortEvent": {…}}`.
70        /// Variant names map 1:1 to the descriptor modules; see each module
71        /// for the wire layout.
72        #[derive(Debug)]
73        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
74        #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
75        // Every variant is covariant in `$lt`: typed variants hold only
76        // lifetime-parametrised views, `Unknown` holds `&$lt [u8]`, and the
77        // `Other` value is a `'static` `Box<dyn DescriptorObject>`. The derive
78        // accepts the `'static` field unchanged, so the impl is sound.
79        #[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
80        #[non_exhaustive]
81        pub enum AnyDescriptor<$lt> {
82            $(
83                #[allow(missing_docs)]
84                $variant($($path)::+ $(<$plt>)?),
85            )+
86            $($(
87                #[allow(missing_docs)]
88                $nd_variant($($nd_path)::+ $(<$nd_plt>)?),
89            )+)?
90            /// Runtime-registered custom descriptor (see [`DescriptorRegistry`]).
91            ///
92            /// [`DescriptorRegistry`]: crate::descriptors::registry::DescriptorRegistry
93            Other {
94                /// The raw descriptor_tag byte.
95                tag: u8,
96                /// The parsed, type-erased descriptor value. Call `downcast_ref`
97                /// on it (see [`DescriptorObject`](crate::descriptors::registry::DescriptorObject))
98                /// to recover the concrete type.
99                #[cfg_attr(
100                    feature = "serde",
101                    serde(serialize_with = "crate::descriptors::registry::serialize_erased")
102                )]
103                value: Box<dyn crate::descriptors::registry::DescriptorObject>,
104            },
105            /// Tag with no typed implementation; `body` is the payload sans
106            /// the 2-byte (tag, length) header.
107            Unknown {
108                /// The raw descriptor_tag byte.
109                tag: u8,
110                /// The raw payload bytes (descriptor_length bytes).
111                body: &$lt [u8],
112            },
113        }
114
115        $(
116            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyDescriptor<$lt> {
117                fn from(d: $($path)::+ $(<$plt>)?) -> Self {
118                    Self::$variant(d)
119                }
120            }
121        )+
122        $($(
123            impl<$lt> From<$($nd_path)::+ $(<$nd_plt>)?> for AnyDescriptor<$lt> {
124                fn from(d: $($nd_path)::+ $(<$nd_plt>)?) -> Self {
125                    Self::$nd_variant(d)
126                }
127            }
128        )+)?
129
130        impl<$lt> AnyDescriptor<$lt> {
131            /// Every tag the generated dispatcher routes (excludes `@no_dispatch`
132            /// variants and [`AnyDescriptor::Unknown`]).
133            pub const DISPATCHED_TAGS: &'static [u8] = &[$($tag),+];
134
135            /// Diagnostic name of the contained descriptor — the type's
136            /// [`DescriptorDef::NAME`](crate::traits::DescriptorDef::NAME)
137            /// (`"SHORT_EVENT"`, `"NETWORK_NAME"`, …); `"CUSTOM"` for
138            /// [`AnyDescriptor::Other`] (runtime-registered) and `"UNKNOWN"`
139            /// for [`AnyDescriptor::Unknown`].
140            #[must_use]
141            pub fn name(&self) -> &'static str {
142                match self {
143                    $(
144                        Self::$variant(_) =>
145                            <$($path)::+ as crate::traits::DescriptorDef>::NAME,
146                    )+
147                    $($(
148                        Self::$nd_variant(_) =>
149                            <$($nd_path)::+ as crate::traits::DescriptorDef>::NAME,
150                    )+)?
151                    Self::Other { .. } => "CUSTOM",
152                    Self::Unknown { .. } => "UNKNOWN",
153                }
154            }
155
156            /// Re-serialize the typed descriptor to its full wire form
157            /// (2-byte header included). `None` for [`AnyDescriptor::Other`]
158            /// (type-erased); `Unknown` re-emits its raw bytes. Used by the
159            /// real-capture byte-identity survey.
160            #[doc(hidden)]
161            pub fn reserialize(&self) -> Option<core::result::Result<alloc::vec::Vec<u8>, alloc::string::String>> {
162                use broadcast_common::Serialize;
163                fn go<S: Serialize>(d: &S) -> core::result::Result<alloc::vec::Vec<u8>, alloc::string::String>
164                where
165                    S::Error: core::fmt::Debug,
166                {
167                    let mut out = alloc::vec![0u8; d.serialized_len()];
168                    d.serialize_into(&mut out).map_err(|e| alloc::format!("{e:?}"))?;
169                    Ok(out)
170                }
171                match self {
172                    $( Self::$variant(d) => Some(go(d)), )+
173                    $($( Self::$nd_variant(d) => Some(go(d)), )+)?
174                    Self::Other { .. } => None,
175                    Self::Unknown { tag, body } => {
176                        Some(
177                            broadcast_common::len::fit_u8(body.len(), "descriptor_length")
178                                .map(|n| {
179                                    let mut v = alloc::vec![*tag, n];
180                                    v.extend_from_slice(body);
181                                    v
182                                })
183                                .map_err(|e| alloc::format!("{e:?}")),
184                        )
185                    }
186                }
187            }
188
189            /// Parse one full descriptor (2-byte header included) by its tag.
190            ///
191            /// `None` means no typed implementation exists for `tag` (the
192            /// caller turns that into [`AnyDescriptor::Unknown`]). `Some(Err)`
193            /// is a typed parse failure for a recognised tag.
194            pub(crate) fn dispatch(tag: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
195                use broadcast_common::Parse;
196                match tag {
197                    $(
198                        $tag => Some(<$($path)::+>::parse(full).map(Self::$variant)),
199                    )+
200                    _ => None,
201                }
202            }
203        }
204
205        #[cfg(test)]
206        mod macro_drift {
207            #[test]
208            fn tag_literals_match_descriptor_def() {
209                use crate::traits::DescriptorDef;
210                $(
211                    assert_eq!(
212                        $tag,
213                        <$($path)::+ as DescriptorDef>::TAG,
214                        concat!("tag literal drift for ", stringify!($variant)),
215                    );
216                    assert!(
217                        !<$($path)::+ as DescriptorDef>::NAME.is_empty(),
218                        concat!("empty NAME for ", stringify!($variant)),
219                    );
220                )+
221                $($(
222                    assert!(
223                        !<$($nd_path)::+ as DescriptorDef>::NAME.is_empty(),
224                        concat!("empty NAME for ", stringify!($nd_variant)),
225                    );
226                )+)?
227            }
228        }
229    };
230}
231
232declare_descriptors! {'a;
233    // MPEG-2 systems descriptors (ISO/IEC 13818-1) used outside table context.
234    VideoStream = 0x02 => crate::descriptors::video_stream::VideoStreamDescriptor,
235    AudioStream = 0x03 => crate::descriptors::audio_stream::AudioStreamDescriptor,
236    Hierarchy = 0x04 => crate::descriptors::hierarchy::HierarchyDescriptor,
237    Registration = 0x05 => crate::descriptors::registration::RegistrationDescriptor<'a>,
238    DataStreamAlignment = 0x06 => crate::descriptors::data_stream_alignment::DataStreamAlignmentDescriptor,
239    TargetBackgroundGrid = 0x07 => crate::descriptors::target_background_grid::TargetBackgroundGridDescriptor,
240    VideoWindow = 0x08 => crate::descriptors::video_window::VideoWindowDescriptor,
241    Ca = 0x09 => crate::descriptors::ca::CaDescriptor<'a>,
242    Iso639Language = 0x0A => crate::descriptors::iso_639_language::Iso639LanguageDescriptor,
243    SystemClock = 0x0B => crate::descriptors::system_clock::SystemClockDescriptor,
244    MultiplexBufferUtilization = 0x0C => crate::descriptors::multiplex_buffer_utilization::MultiplexBufferUtilizationDescriptor,
245    Copyright = 0x0D => crate::descriptors::copyright::CopyrightDescriptor<'a>,
246    MaximumBitrate = 0x0E => crate::descriptors::maximum_bitrate::MaximumBitrateDescriptor,
247    PrivateDataIndicator = 0x0F => crate::descriptors::private_data_indicator::PrivateDataIndicatorDescriptor,
248    SmoothingBuffer = 0x10 => crate::descriptors::smoothing_buffer::SmoothingBufferDescriptor,
249    Std = 0x11 => crate::descriptors::std::StdDescriptor,
250    Ibp = 0x12 => crate::descriptors::ibp::IbpDescriptor,
251    CarouselIdentifier = 0x13 => crate::descriptors::carousel_identifier::CarouselIdentifierDescriptor<'a>,
252    AssociationTag = 0x14 => crate::descriptors::association_tag::AssociationTagDescriptor<'a>,
253    DeferredAssociationTags = 0x15 => crate::descriptors::deferred_association_tags::DeferredAssociationTagsDescriptor<'a>,
254    Mpeg4Video = 0x1B => crate::descriptors::mpeg4_video::Mpeg4VideoDescriptor,
255    Mpeg4Audio = 0x1C => crate::descriptors::mpeg4_audio::Mpeg4AudioDescriptor,
256    Iod = 0x1D => crate::descriptors::iod::IodDescriptor<'a>,
257    Sl = 0x1E => crate::descriptors::sl::SlDescriptor,
258    Fmc = 0x1F => crate::descriptors::fmc::FmcDescriptor,
259    ExternalEsId = 0x20 => crate::descriptors::external_es_id::ExternalEsIdDescriptor,
260    Muxcode = 0x21 => crate::descriptors::muxcode::MuxcodeDescriptor<'a>,
261    FmxBufferSize = 0x22 => crate::descriptors::fmx_buffer_size::FmxBufferSizeDescriptor<'a>,
262    MultiplexBuffer = 0x23 => crate::descriptors::multiplex_buffer::MultiplexBufferDescriptor,
263    ContentLabeling = 0x24 => crate::descriptors::content_labeling::ContentLabelingDescriptor<'a>,
264    MetadataPointer = 0x25 => crate::descriptors::metadata_pointer::MetadataPointerDescriptor<'a>,
265    Metadata = 0x26 => crate::descriptors::metadata::MetadataDescriptor<'a>,
266    MetadataStd = 0x27 => crate::descriptors::metadata_std::MetadataStdDescriptor,
267    AvcVideo = 0x28 => crate::descriptors::avc_video::AvcVideoDescriptor,
268    AvcTimingAndHrd = 0x2A => crate::descriptors::avc_timing_and_hrd::AvcTimingAndHrdDescriptor,
269    Mpeg2AacAudio = 0x2B => crate::descriptors::mpeg2_aac_audio::Mpeg2AacAudioDescriptor,
270    FlexMuxTiming = 0x2C => crate::descriptors::flex_mux_timing::FlexMuxTimingDescriptor,
271    Mpeg4Text = 0x2D => crate::descriptors::mpeg4_text::Mpeg4TextDescriptor<'a>,
272    Mpeg4AudioExtension = 0x2E => crate::descriptors::mpeg4_audio_extension::Mpeg4AudioExtensionDescriptor,
273    AuxiliaryVideoStream = 0x2F => crate::descriptors::auxiliary_video_stream::AuxiliaryVideoStreamDescriptor<'a>,
274    SvcExtension = 0x30 => crate::descriptors::svc_extension::SvcExtensionDescriptor,
275    MvcExtension = 0x31 => crate::descriptors::mvc_extension::MvcExtensionDescriptor,
276    J2kVideo = 0x32 => crate::descriptors::j2k_video::J2kVideoDescriptor<'a>,
277    MvcOperationPoint = 0x33 => crate::descriptors::mvc_operation_point::MvcOperationPointDescriptor,
278    Mpeg2StereoscopicVideoFormat = 0x34 => crate::descriptors::stereoscopic::Mpeg2StereoscopicVideoFormatDescriptor,
279    StereoscopicProgramInfo = 0x35 => crate::descriptors::stereoscopic::StereoscopicProgramInfoDescriptor,
280    StereoscopicVideoInfo = 0x36 => crate::descriptors::stereoscopic::StereoscopicVideoInfoDescriptor,
281    TransportProfile = 0x37 => crate::descriptors::transport_profile::TransportProfileDescriptor,
282    HevcVideo = 0x38 => crate::descriptors::hevc_video::HevcVideoDescriptor,
283    VvcVideo = 0x39 => crate::descriptors::vvc_video::VvcVideoDescriptor,
284    EvcVideo = 0x3A => crate::descriptors::evc_video::EvcVideoDescriptor,
285    Mpeg2Extension = 0x3F => crate::descriptors::mpeg2_extension::Mpeg2ExtensionDescriptor,
286    // DVB descriptors (ETSI EN 300 468) — contiguous 0x40..=0x7F.
287    NetworkName = 0x40 => crate::descriptors::network_name::NetworkNameDescriptor<'a>,
288    ServiceList = 0x41 => crate::descriptors::service_list::ServiceListDescriptor,
289    Stuffing = 0x42 => crate::descriptors::stuffing::StuffingDescriptor<'a>,
290    SatelliteDeliverySystem = 0x43 => crate::descriptors::satellite_delivery_system::SatelliteDeliverySystemDescriptor,
291    CableDeliverySystem = 0x44 => crate::descriptors::cable_delivery_system::CableDeliverySystemDescriptor,
292    VbiData = 0x45 => crate::descriptors::vbi_data::VbiDataDescriptor<'a>,
293    VbiTeletext = 0x46 => crate::descriptors::vbi_teletext::VbiTeletextDescriptor,
294    BouquetName = 0x47 => crate::descriptors::bouquet_name::BouquetNameDescriptor<'a>,
295    Service = 0x48 => crate::descriptors::service::ServiceDescriptor<'a>,
296    CountryAvailability = 0x49 => crate::descriptors::country_availability::CountryAvailabilityDescriptor,
297    Linkage = 0x4A => crate::descriptors::linkage::LinkageDescriptor<'a>,
298    NvodReference = 0x4B => crate::descriptors::nvod_reference::NvodReferenceDescriptor,
299    TimeShiftedService = 0x4C => crate::descriptors::time_shifted_service::TimeShiftedServiceDescriptor,
300    ShortEvent = 0x4D => crate::descriptors::short_event::ShortEventDescriptor<'a>,
301    ExtendedEvent = 0x4E => crate::descriptors::extended_event::ExtendedEventDescriptor<'a>,
302    TimeShiftedEvent = 0x4F => crate::descriptors::time_shifted_event::TimeShiftedEventDescriptor,
303    Component = 0x50 => crate::descriptors::component::ComponentDescriptor<'a>,
304    Mosaic = 0x51 => crate::descriptors::mosaic::MosaicDescriptor,
305    StreamIdentifier = 0x52 => crate::descriptors::stream_identifier::StreamIdentifierDescriptor,
306    CaIdentifier = 0x53 => crate::descriptors::ca_identifier::CaIdentifierDescriptor,
307    Content = 0x54 => crate::descriptors::content::ContentDescriptor,
308    ParentalRating = 0x55 => crate::descriptors::parental_rating::ParentalRatingDescriptor,
309    Teletext = 0x56 => crate::descriptors::teletext::TeletextDescriptor,
310    Telephone = 0x57 => crate::descriptors::telephone::TelephoneDescriptor<'a>,
311    LocalTimeOffset = 0x58 => crate::descriptors::local_time_offset::LocalTimeOffsetDescriptor,
312    Subtitling = 0x59 => crate::descriptors::subtitling::SubtitlingDescriptor,
313    TerrestrialDeliverySystem = 0x5A => crate::descriptors::terrestrial_delivery_system::TerrestrialDeliverySystemDescriptor,
314    MultilingualNetworkName = 0x5B => crate::descriptors::multilingual_network_name::MultilingualNetworkNameDescriptor<'a>,
315    MultilingualBouquetName = 0x5C => crate::descriptors::multilingual_bouquet_name::MultilingualBouquetNameDescriptor<'a>,
316    MultilingualServiceName = 0x5D => crate::descriptors::multilingual_service_name::MultilingualServiceNameDescriptor<'a>,
317    MultilingualComponent = 0x5E => crate::descriptors::multilingual_component::MultilingualComponentDescriptor<'a>,
318    PrivateDataSpecifier = 0x5F => crate::descriptors::private_data_specifier::PrivateDataSpecifierDescriptor,
319    ServiceMove = 0x60 => crate::descriptors::service_move::ServiceMoveDescriptor,
320    ShortSmoothingBuffer = 0x61 => crate::descriptors::short_smoothing_buffer::ShortSmoothingBufferDescriptor<'a>,
321    FrequencyList = 0x62 => crate::descriptors::frequency_list::FrequencyListDescriptor,
322    PartialTransportStream = 0x63 => crate::descriptors::partial_transport_stream::PartialTransportStreamDescriptor,
323    DataBroadcast = 0x64 => crate::descriptors::data_broadcast::DataBroadcastDescriptor<'a>,
324    Scrambling = 0x65 => crate::descriptors::scrambling::ScramblingDescriptor,
325    DataBroadcastId = 0x66 => crate::descriptors::data_broadcast_id::DataBroadcastIdDescriptor<'a>,
326    TransportStream = 0x67 => crate::descriptors::transport_stream::TransportStreamDescriptor<'a>,
327    Dsng = 0x68 => crate::descriptors::dsng::DsngDescriptor<'a>,
328    Pdc = 0x69 => crate::descriptors::pdc::PdcDescriptor,
329    Ac3 = 0x6A => crate::descriptors::ac3::Ac3Descriptor<'a>,
330    AncillaryData = 0x6B => crate::descriptors::ancillary_data::AncillaryDataDescriptor,
331    CellList = 0x6C => crate::descriptors::cell_list::CellListDescriptor,
332    CellFrequencyLink = 0x6D => crate::descriptors::cell_frequency_link::CellFrequencyLinkDescriptor,
333    AnnouncementSupport = 0x6E => crate::descriptors::announcement_support::AnnouncementSupportDescriptor,
334    ApplicationSignalling = 0x6F => crate::descriptors::application_signalling::ApplicationSignallingDescriptor,
335    AdaptationFieldData = 0x70 => crate::descriptors::adaptation_field_data::AdaptationFieldDataDescriptor,
336    ServiceIdentifier = 0x71 => crate::descriptors::service_identifier::ServiceIdentifierDescriptor<'a>,
337    ServiceAvailability = 0x72 => crate::descriptors::service_availability::ServiceAvailabilityDescriptor,
338    DefaultAuthority = 0x73 => crate::descriptors::default_authority::DefaultAuthorityDescriptor<'a>,
339    RelatedContent = 0x74 => crate::descriptors::related_content::RelatedContentDescriptor,
340    TvaId = 0x75 => crate::descriptors::tva_id::TvaIdDescriptor,
341    ContentIdentifier = 0x76 => crate::descriptors::content_identifier::ContentIdentifierDescriptor<'a>,
342    TimeSliceFecIdentifier = 0x77 => crate::descriptors::time_slice_fec_identifier::TimeSliceFecIdentifierDescriptor<'a>,
343    EcmRepetitionRate = 0x78 => crate::descriptors::ecm_repetition_rate::EcmRepetitionRateDescriptor<'a>,
344    S2SatelliteDeliverySystem = 0x79 => crate::descriptors::s2_satellite_delivery_system::S2SatelliteDeliverySystemDescriptor,
345    EnhancedAc3 = 0x7A => crate::descriptors::enhanced_ac3::EnhancedAc3Descriptor<'a>,
346    Dts = 0x7B => crate::descriptors::dts::DtsDescriptor<'a>,
347    Aac = 0x7C => crate::descriptors::aac::AacDescriptor<'a>,
348    XaitLocation = 0x7D => crate::descriptors::xait_location::XaitLocationDescriptor,
349    FtaContentManagement = 0x7E => crate::descriptors::fta_content_management::FtaContentManagementDescriptor,
350    Extension = 0x7F => crate::descriptors::extension::ExtensionDescriptor<'a>;
351    // Private / context-dependent: variant exists but is NOT auto-dispatched.
352    // 0x83 logical_channel requires private_data_specifier context; enabled
353    // via the descriptor registry (Task 4).
354    @no_dispatch
355    // ORDER: this no-dispatch tail is ordered by descriptor tag (0x3E, 0x40, 0x83, 0x86, 0x87, 0xD1, 0xD2, 0xD3), not alphabetically.
356    TuwaAvs3Video => crate::descriptors::tuwa_avs3_video::TuwaAvs3VideoDescriptor,
357    Avs2Video => crate::descriptors::avs2_video::Avs2VideoDescriptor,
358    LogicalChannel => crate::descriptors::logical_channel::LogicalChannelDescriptor,
359    NordigLogicalChannelV1 => crate::descriptors::nordig::NordigLogicalChannelV1,
360    ChinaCaptionService => crate::descriptors::china_caption_service::ChinaCaptionServiceDescriptor,
361    AtscCaptionService => crate::descriptors::atsc_caption_service::AtscCaptionServiceDescriptor,
362    NordigLogicalChannelV2 => crate::descriptors::nordig::NordigLogicalChannelV2,
363    Avs3Video => crate::descriptors::avs3_video::Avs3VideoDescriptor,
364    Avs3Audio => crate::descriptors::avs3_audio::Avs3AudioDescriptor,
365    Avs2Audio => crate::descriptors::avs2_audio::Avs2AudioDescriptor,
366}
367
368/// Lazily walk a raw descriptor loop. Never panics.
369///
370/// Per-descriptor parse errors yield `Err` and iteration continues (the
371/// descriptor_length field bounds each entry, so the walker can always
372/// advance past a malformed body). A truncated final header or body yields
373/// one `Err` and then the iterator fuses (returns `None` forever after).
374#[must_use]
375pub fn parse_loop(bytes: &[u8]) -> DescriptorIter<'_> {
376    DescriptorIter {
377        bytes,
378        pos: 0,
379        fused: false,
380    }
381}
382
383/// Extract the next descriptor-loop entry (tag + full-entry slice) from the
384/// raw byte stream. Shared by [`DescriptorIter::next`] and
385/// [`RegistryIter::next`](crate::descriptors::registry::RegistryIter::next).
386///
387/// On success, advances `pos` past the entry and returns `(tag, full)`.
388/// On truncation, sets `fused = true` and returns `Some(Err(..))`.
389/// When the loop is exhausted, returns `None`.
390pub(crate) fn next_loop_entry<'a>(
391    bytes: &'a [u8],
392    pos: &mut usize,
393    fused: &mut bool,
394) -> Option<crate::Result<(u8, &'a [u8])>> {
395    if *fused || *pos >= bytes.len() {
396        return None;
397    }
398    let rem = &bytes[*pos..];
399    if rem.len() < 2 {
400        *fused = true;
401        return Some(Err(crate::Error::BufferTooShort(
402            broadcast_common::error::BufferTooShort {
403                need: 2,
404                have: rem.len(),
405                what: "descriptor header in loop",
406            },
407        )));
408    }
409    let tag = rem[0];
410    let len = rem[1] as usize;
411    let total = 2 + len;
412    if rem.len() < total {
413        *fused = true;
414        return Some(Err(crate::Error::BufferTooShort(
415            broadcast_common::error::BufferTooShort {
416                need: total,
417                have: rem.len(),
418                what: "descriptor body in loop",
419            },
420        )));
421    }
422    let full = &rem[..total];
423    *pos += total;
424    Some(Ok((tag, full)))
425}
426
427/// Iterator over a raw descriptor loop; see [`parse_loop`].
428#[derive(Debug, Clone)]
429pub struct DescriptorIter<'a> {
430    bytes: &'a [u8],
431    pos: usize,
432    fused: bool,
433}
434
435impl<'a> Iterator for DescriptorIter<'a> {
436    type Item = crate::Result<AnyDescriptor<'a>>;
437
438    fn next(&mut self) -> Option<Self::Item> {
439        let (tag, full) = match next_loop_entry(self.bytes, &mut self.pos, &mut self.fused)? {
440            Ok(v) => v,
441            Err(e) => return Some(Err(e)),
442        };
443        Some(match AnyDescriptor::dispatch(tag, full) {
444            Some(res) => res,
445            None => Ok(AnyDescriptor::Unknown {
446                tag,
447                body: &full[2..],
448            }),
449        })
450    }
451}
452
453impl core::iter::FusedIterator for DescriptorIter<'_> {}
454
455/// A raw descriptor loop, borrowed from the section. Zero-copy: walk it
456/// typed via [`DescriptorLoop::iter`]; serde serializes the typed walk.
457///
458/// This is the table-loop analogue of [`crate::text::DvbText`]: it wraps the
459/// raw `descriptor()` sequence (the variable-length region inside a table) and
460/// decodes — i.e. dispatches each entry to a typed [`AnyDescriptor`] — only on
461/// demand. Parsing stays zero-copy; the typed walk happens when you call
462/// [`DescriptorLoop::iter`] or serialize.
463///
464/// ```
465/// use dvb_si::descriptors::{AnyDescriptor, DescriptorLoop};
466///
467/// // short_event (tag 0x4D, "eng" / "Hi") then an unknown private tag 0xA7.
468/// let raw = [
469///     0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00,
470///     0xA7, 0x02, 0xCA, 0xFE,
471/// ];
472/// let loop_ = DescriptorLoop::new(&raw);
473/// let items: Vec<_> = loop_.iter().collect();
474/// assert_eq!(items.len(), 2);
475/// assert!(matches!(items[0].as_ref().unwrap(), AnyDescriptor::ShortEvent(_)));
476/// assert_eq!(loop_.raw(), &raw[..]); // bytes preserved verbatim
477/// ```
478#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
479#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
480pub struct DescriptorLoop<'a>(&'a [u8]);
481
482/// Bytes of a descriptor header: `descriptor_tag` (1) + `descriptor_length` (1).
483const DESC_HEADER_LEN: usize = 2;
484
485impl<'a> DescriptorLoop<'a> {
486    /// Wrap a raw descriptor-loop slice (the `descriptor()` bytes only — no
487    /// enclosing length field).
488    #[must_use]
489    pub const fn new(raw: &'a [u8]) -> Self {
490        Self(raw)
491    }
492
493    /// The raw wire bytes of the loop, verbatim. These are what a serializer
494    /// writes back; use them for the byte length of the loop.
495    #[must_use]
496    pub const fn raw(&self) -> &'a [u8] {
497        self.0
498    }
499
500    /// Lazily walk the loop, yielding one typed [`AnyDescriptor`] per entry
501    /// (or [`AnyDescriptor::Unknown`] for tags with no implementation).
502    /// Delegates to [`parse_loop`]; never panics.
503    #[must_use]
504    pub fn iter(&self) -> DescriptorIter<'a> {
505        parse_loop(self.0)
506    }
507
508    /// Walk the loop's `(tag, body)` pairs by the TLV structure only — never
509    /// typed-parses a body. Each entry is `descriptor_tag` (1 byte) +
510    /// `descriptor_length` (1 byte) + that many body bytes; a truncated final
511    /// entry (declared length running past the buffer) simply ends iteration.
512    ///
513    /// Use this for body-agnostic structural scans (e.g. "is tag X present?")
514    /// where a malformed or empty body should not hide the tag — unlike
515    /// [`iter`](Self::iter), which yields `Err`/`Unknown` for bodies that fail
516    /// to typed-parse.
517    pub fn raw_tags(&self) -> impl Iterator<Item = (u8, &'a [u8])> {
518        let b = self.0;
519        let mut pos = 0usize;
520        core::iter::from_fn(move || {
521            if pos + DESC_HEADER_LEN > b.len() {
522                return None;
523            }
524            let tag = b[pos];
525            let len = b[pos + 1] as usize;
526            let end = pos + DESC_HEADER_LEN + len;
527            if end > b.len() {
528                return None; // truncated final entry
529            }
530            let body = &b[pos + DESC_HEADER_LEN..end];
531            pos = end;
532            Some((tag, body))
533        })
534    }
535
536    /// True if the loop contains a descriptor with `tag`, regardless of whether
537    /// its body parses (or even fits). Pure structural walk: checks the
538    /// `descriptor_tag` byte at each entry header, so an empty (`[tag, 0x00]`)
539    /// or truncated (`[tag, 0x01]`) descriptor still counts as present.
540    #[must_use]
541    pub fn contains_tag(&self, tag: u8) -> bool {
542        let b = self.0;
543        let mut pos = 0usize;
544        while pos < b.len() {
545            if b[pos] == tag {
546                return true;
547            }
548            // Need the length byte to advance to the next header; without it
549            // the tag byte just checked was the last thing in the loop.
550            if pos + 1 >= b.len() {
551                break;
552            }
553            pos += DESC_HEADER_LEN + b[pos + 1] as usize;
554        }
555        false
556    }
557
558    /// Walk this loop through a [`DescriptorRegistry`](crate::descriptors::registry::DescriptorRegistry)
559    /// so registered private/custom descriptors (and PDS-scoped tags) are
560    /// produced as [`AnyDescriptor::Other`]; mirrors [`iter`](Self::iter)
561    /// otherwise.
562    ///
563    /// See [`DescriptorRegistry::parse_loop`](crate::descriptors::registry::DescriptorRegistry::parse_loop)
564    /// for precedence rules and PDS-scoped dispatch.
565    #[must_use]
566    pub fn iter_with<'r>(
567        &self,
568        registry: &'r crate::descriptors::registry::DescriptorRegistry,
569    ) -> crate::descriptors::registry::RegistryIter<'r, 'a> {
570        registry.parse_loop(self.0)
571    }
572
573    /// Walk this loop through both a [`DescriptorRegistry`](crate::descriptors::registry::DescriptorRegistry) and an
574    /// [`ExtensionRegistry`](crate::descriptors::extension::registry::ExtensionRegistry),
575    /// so that custom-registered extension bodies (tag `0x7F` with a known
576    /// `descriptor_tag_extension`) are surfaced as
577    /// [`ExtIterItem::CustomExtension`](crate::descriptors::registry::ExtIterItem::CustomExtension) with the type-erased value available
578    /// for downcast. All other descriptors follow the normal
579    /// [`iter_with`](Self::iter_with) precedence.
580    ///
581    /// This is the only way to reach private extension bodies during a
582    /// descriptor-loop walk; [`iter_with`](Self::iter_with) alone produces
583    /// the built-in [`ExtensionBody::Raw`](crate::descriptors::extension::ExtensionBody::Raw)
584    /// for unrecognised `descriptor_tag_extension` values.
585    #[must_use]
586    pub fn iter_with_extensions<'r>(
587        &self,
588        desc_reg: &'r crate::descriptors::registry::DescriptorRegistry,
589        ext_reg: &'r crate::descriptors::extension::registry::ExtensionRegistry,
590    ) -> crate::descriptors::registry::ExtRegistryIter<'r, 'a> {
591        crate::descriptors::registry::ExtRegistryIter::new(desc_reg, ext_reg, self.0, None)
592    }
593
594    /// [`iter_with_extensions`](Self::iter_with_extensions) starting inside a
595    /// scope an enclosing loop opened (`inherited`, from
596    /// [`ExtRegistryIter::avs_scope`](crate::descriptors::registry::ExtRegistryIter::avs_scope)):
597    /// a `program_info` 'AVSV'/'AVSA' registration scopes the AVS descriptors of
598    /// the ES_info loops (see `DescriptorRegistry::parse_loop_in_scope`).
599    #[must_use]
600    pub fn iter_with_extensions_in_scope<'r>(
601        &self,
602        desc_reg: &'r crate::descriptors::registry::DescriptorRegistry,
603        ext_reg: &'r crate::descriptors::extension::registry::ExtensionRegistry,
604        inherited: Option<u32>,
605    ) -> crate::descriptors::registry::ExtRegistryIter<'r, 'a> {
606        crate::descriptors::registry::ExtRegistryIter::new(desc_reg, ext_reg, self.0, inherited)
607    }
608}
609
610impl core::ops::Deref for DescriptorLoop<'_> {
611    /// Derefs to the raw wire bytes — `len()`/indexing are **byte counts for
612    /// serialization, not entry counts**. To count entries, use
613    /// [`DescriptorLoop::iter`].
614    type Target = [u8];
615    fn deref(&self) -> &[u8] {
616        self.0
617    }
618}
619
620impl<'a> From<&'a [u8]> for DescriptorLoop<'a> {
621    fn from(raw: &'a [u8]) -> Self {
622        Self(raw)
623    }
624}
625
626impl core::fmt::Debug for DescriptorLoop<'_> {
627    /// Cheap: prints the byte length, not the decoded entries.
628    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
629        write!(f, "DescriptorLoop(<{} bytes>)", self.0.len())
630    }
631}
632
633impl<'a> IntoIterator for &DescriptorLoop<'a> {
634    type Item = crate::Result<AnyDescriptor<'a>>;
635    type IntoIter = DescriptorIter<'a>;
636    fn into_iter(self) -> Self::IntoIter {
637        self.iter()
638    }
639}
640
641#[cfg(feature = "serde")]
642impl serde::Serialize for DescriptorLoop<'_> {
643    /// Serializes as a sequence of the typed walk: each `Ok(d)` becomes the
644    /// [`AnyDescriptor`] (camelCase external tagging), and each `Err(e)`
645    /// becomes a `{"parseError": "<Display>"}` map — parse errors are surfaced,
646    /// never silently dropped.
647    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
648        struct Entry<'a>(crate::Result<AnyDescriptor<'a>>);
649        impl serde::Serialize for Entry<'_> {
650            fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
651                match &self.0 {
652                    Ok(d) => d.serialize(s),
653                    Err(e) => {
654                        use serde::ser::SerializeMap;
655                        let mut m = s.serialize_map(Some(1))?;
656                        m.serialize_entry("parseError", &e.to_string())?;
657                        m.end()
658                    }
659                }
660            }
661        }
662        s.collect_seq(self.iter().map(Entry))
663    }
664}
665// Serialize-only: the typed walk decodes DVB text and dispatches per-tag —
666// there is no lossless way to reconstruct the raw loop bytes from the
667// serialized form. Structs holding a DescriptorLoop derive Serialize only.
668// To reconstruct, keep the wire bytes and re-`parse` the table.
669
670#[cfg(test)]
671mod tests {
672    use super::*;
673
674    #[test]
675    fn contains_tag_detects_present_tag_regardless_of_body() {
676        // Well-formed AC-3 (0x6A) with a flags byte.
677        assert!(DescriptorLoop::new(&[0x6A, 0x01, 0x80]).contains_tag(0x6A));
678        // Empty body — malformed for AC-3, but the tag is present.
679        assert!(DescriptorLoop::new(&[0x6A, 0x00]).contains_tag(0x6A));
680        // Truncated — declares 1 body byte that isn't there; tag still present.
681        assert!(DescriptorLoop::new(&[0x6A, 0x01]).contains_tag(0x6A));
682        // Present after an earlier well-formed entry.
683        assert!(DescriptorLoop::new(&[0x09, 0x02, 0x00, 0x00, 0x7A, 0x00]).contains_tag(0x7A));
684        // Absent.
685        assert!(!DescriptorLoop::new(&[0x09, 0x02, 0x00, 0x00]).contains_tag(0x6A));
686        assert!(!DescriptorLoop::new(&[]).contains_tag(0x6A));
687    }
688
689    #[test]
690    fn raw_tags_walks_tlv_without_typed_parsing() {
691        // Two complete entries: tag 0x6A (empty body) + tag 0x40 (2-byte body).
692        let loop_ = DescriptorLoop::new(&[0x6A, 0x00, 0x40, 0x02, 0xAA, 0xBB]);
693        let pairs: Vec<_> = loop_.raw_tags().collect();
694        assert_eq!(pairs, vec![(0x6A, &[][..]), (0x40, &[0xAA, 0xBB][..])]);
695        // First entry [0x40,len=1,0xAA] complete, then a truncated [0x6A, len=5]
696        // entry whose declared body runs past the buffer.
697        let trunc = DescriptorLoop::new(&[0x40, 0x01, 0xAA, 0x6A, 0x05]);
698        let tags: Vec<u8> = trunc.raw_tags().map(|(t, _)| t).collect();
699        assert_eq!(tags, vec![0x40]); // the truncated 0x6A entry is dropped by raw_tags
700        // ...but contains_tag still sees the truncated tag.
701        assert!(trunc.contains_tag(0x6A));
702    }
703
704    #[test]
705    fn unknown_tag_yields_unknown_with_body_sans_header() {
706        // tag 0xA7 (no typed impl), length 2, body [0xDE, 0xAD].
707        let bytes = [0xA7, 0x02, 0xDE, 0xAD];
708        let items: Vec<_> = parse_loop(&bytes).collect();
709        assert_eq!(items.len(), 1);
710        match items[0].as_ref().unwrap() {
711            AnyDescriptor::Unknown { tag, body } => {
712                assert_eq!(*tag, 0xA7);
713                assert_eq!(*body, &[0xDE, 0xAD]);
714            }
715            other => panic!("expected Unknown, got {other:?}"),
716        }
717    }
718
719    #[test]
720    fn empty_loop_yields_nothing() {
721        assert_eq!(parse_loop(&[]).count(), 0);
722    }
723
724    #[test]
725    fn logical_channel_0x83_is_not_dispatched() {
726        // 0x83 has a variant but no dispatcher entry → Unknown, never panics.
727        let bytes = [0x83, 0x04, 0x00, 0x01, 0xFC, 0x01];
728        let items: Vec<_> = parse_loop(&bytes).collect();
729        assert_eq!(items.len(), 1);
730        assert!(matches!(
731            items[0].as_ref().unwrap(),
732            AnyDescriptor::Unknown { tag: 0x83, .. }
733        ));
734    }
735
736    #[test]
737    fn descriptor_loop_iter_matches_parse_loop() {
738        let raw = [
739            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
740            0xA7, 0x02, 0xCA, 0xFE, // unknown 0xA7
741        ];
742        let via_loop: Vec<_> = DescriptorLoop::new(&raw)
743            .iter()
744            .map(|r| format!("{r:?}"))
745            .collect();
746        let via_fn: Vec<_> = parse_loop(&raw).map(|r| format!("{r:?}")).collect();
747        assert_eq!(via_loop, via_fn);
748        // raw()/Deref expose the wire bytes (byte length, not entry count).
749        assert_eq!(DescriptorLoop::new(&raw).raw(), &raw[..]);
750        assert_eq!(DescriptorLoop::new(&raw).len(), raw.len());
751        // IntoIterator for &DescriptorLoop.
752        let count = (&DescriptorLoop::new(&raw)).into_iter().count();
753        assert_eq!(count, 2);
754    }
755
756    #[test]
757    fn descriptor_loop_debug_is_cheap() {
758        let raw = [0x4D, 0x02, 0x01, 0x02];
759        assert_eq!(
760            format!("{:?}", DescriptorLoop::new(&raw)),
761            "DescriptorLoop(<4 bytes>)"
762        );
763    }
764
765    #[test]
766    fn iter_with_custom_tag_yields_other() {
767        use crate::descriptors::registry::DescriptorRegistry;
768        use crate::traits::DescriptorDef;
769
770        #[derive(Debug, PartialEq, Eq)]
771        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
772        struct MyTag0xA7 {
773            x: u8,
774        }
775
776        impl<'a> broadcast_common::Parse<'a> for MyTag0xA7 {
777            type Error = crate::error::Error;
778            fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
779                if bytes.len() < 3 {
780                    return Err(crate::error::Error::BufferTooShort(
781                        broadcast_common::error::BufferTooShort {
782                            need: 3,
783                            have: bytes.len(),
784                            what: "MyTag0xA7",
785                        },
786                    ));
787                }
788                Ok(Self { x: bytes[2] })
789            }
790        }
791
792        impl DescriptorDef<'_> for MyTag0xA7 {
793            const TAG: u8 = 0xA7;
794            const NAME: &'static str = "MY_TAG_0xA7";
795        }
796
797        let mut reg = DescriptorRegistry::new();
798        reg.register::<MyTag0xA7>();
799
800        let raw = [
801            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, 0xA7, 0x02, 0xCA, 0xFE,
802        ];
803        let loop_ = DescriptorLoop::new(&raw);
804        let items: Vec<_> = loop_.iter_with(&reg).collect::<Result<_, _>>().unwrap();
805        assert_eq!(items.len(), 2);
806        assert!(matches!(items[0], AnyDescriptor::ShortEvent(_)));
807        match &items[1] {
808            AnyDescriptor::Other { tag, value } => {
809                assert_eq!(*tag, 0xA7);
810                assert_eq!(value.downcast_ref::<MyTag0xA7>().unwrap().x, 0xCA);
811            }
812            other => panic!("expected Other, got {other:?}"),
813        }
814    }
815
816    #[test]
817    fn iter_with_empty_registry_matches_iter_for_builtin() {
818        let raw = [
819            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, 0xA7, 0x02, 0xCA, 0xFE,
820        ];
821        let loop_ = DescriptorLoop::new(&raw);
822        let reg = crate::descriptors::registry::DescriptorRegistry::new();
823        let via_iter: Vec<_> = loop_.iter().collect();
824        let via_iter_with: Vec<_> = loop_.iter_with(&reg).collect();
825
826        assert_eq!(via_iter.len(), via_iter_with.len());
827        for (a, b) in via_iter.iter().zip(via_iter_with.iter()) {
828            match (a, b) {
829                (Ok(AnyDescriptor::ShortEvent(_)), Ok(AnyDescriptor::ShortEvent(_))) => {}
830                (
831                    Ok(AnyDescriptor::Unknown { tag: t1, body: b1 }),
832                    Ok(AnyDescriptor::Unknown { tag: t2, body: b2 }),
833                ) => {
834                    assert_eq!(t1, t2);
835                    assert_eq!(b1, b2);
836                }
837                (Err(_), Err(_)) => {}
838                (l, r) => panic!("mismatch: {l:?} vs {r:?}"),
839            }
840        }
841    }
842
843    #[cfg(feature = "serde")]
844    #[test]
845    fn descriptor_loop_serializes_typed_unknown_and_parse_error() {
846        // [valid short_event, unknown tag 0xA7, truncated final entry].
847        // A truncated final entry (declared len 5, only 1 body byte present)
848        // makes the walker yield a final Err → {"parseError": …}.
849        let raw = [
850            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
851            0xA7, 0x02, 0xCA, 0xFE, // unknown 0xA7
852            0x55, 0x05, 0x00, // parental_rating header claims 5 bytes; only 1 present
853        ];
854        let v = serde_json::to_value(DescriptorLoop::new(&raw)).unwrap();
855        let arr = v.as_array().expect("sequence");
856        assert_eq!(arr.len(), 3);
857        // 1. typed short_event under the camelCase variant key.
858        assert!(arr[0].get("shortEvent").is_some(), "got {}", arr[0]);
859        assert_eq!(arr[0]["shortEvent"]["event_name"], "Hi");
860        // 2. unknown tag carries its raw body bytes.
861        let unknown = arr[1].get("unknown").expect("unknown variant");
862        assert_eq!(unknown["tag"], 0xA7);
863        assert_eq!(unknown["body"], serde_json::json!([0xCA, 0xFE]));
864        // 3. truncated entry → parseError, never silently dropped.
865        assert!(
866            arr[2].get("parseError").is_some(),
867            "expected parseError, got {}",
868            arr[2]
869        );
870    }
871}