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        // Every variant is covariant in `$lt`: typed variants hold only
72        // lifetime-parametrised views, `Unknown` holds `&$lt [u8]`, and the
73        // `Other` value is a `'static` `Box<dyn DescriptorObject>`. The derive
74        // accepts the `'static` field unchanged, so the impl is sound.
75        #[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
76        #[non_exhaustive]
77        pub enum AnyDescriptor<$lt> {
78            $(
79                #[allow(missing_docs)]
80                $variant($($path)::+ $(<$plt>)?),
81            )+
82            $($(
83                #[allow(missing_docs)]
84                $nd_variant($($nd_path)::+ $(<$nd_plt>)?),
85            )+)?
86            /// Runtime-registered custom descriptor (see [`DescriptorRegistry`]).
87            ///
88            /// [`DescriptorRegistry`]: crate::descriptors::registry::DescriptorRegistry
89            Other {
90                /// The raw descriptor_tag byte.
91                tag: u8,
92                /// The parsed, type-erased descriptor value. Call `downcast_ref`
93                /// on it (see [`DescriptorObject`](crate::descriptors::registry::DescriptorObject))
94                /// to recover the concrete type.
95                #[cfg_attr(
96                    feature = "serde",
97                    serde(serialize_with = "crate::descriptors::registry::serialize_erased")
98                )]
99                value: Box<dyn crate::descriptors::registry::DescriptorObject>,
100            },
101            /// Tag with no typed implementation; `body` is the payload sans
102            /// the 2-byte (tag, length) header.
103            Unknown {
104                /// The raw descriptor_tag byte.
105                tag: u8,
106                /// The raw payload bytes (descriptor_length bytes).
107                body: &$lt [u8],
108            },
109        }
110
111        $(
112            impl<$lt> From<$($path)::+ $(<$plt>)?> for AnyDescriptor<$lt> {
113                fn from(d: $($path)::+ $(<$plt>)?) -> Self {
114                    Self::$variant(d)
115                }
116            }
117        )+
118        $($(
119            impl<$lt> From<$($nd_path)::+ $(<$nd_plt>)?> for AnyDescriptor<$lt> {
120                fn from(d: $($nd_path)::+ $(<$nd_plt>)?) -> Self {
121                    Self::$nd_variant(d)
122                }
123            }
124        )+)?
125
126        impl<$lt> AnyDescriptor<$lt> {
127            /// Every tag the generated dispatcher routes (excludes `@no_dispatch`
128            /// variants and [`AnyDescriptor::Unknown`]).
129            pub const DISPATCHED_TAGS: &'static [u8] = &[$($tag),+];
130
131            /// Diagnostic name of the contained descriptor — the type's
132            /// [`DescriptorDef::NAME`](crate::traits::DescriptorDef::NAME)
133            /// (`"SHORT_EVENT"`, `"NETWORK_NAME"`, …); `"CUSTOM"` for
134            /// [`AnyDescriptor::Other`] (runtime-registered) and `"UNKNOWN"`
135            /// for [`AnyDescriptor::Unknown`].
136            #[must_use]
137            pub fn name(&self) -> &'static str {
138                match self {
139                    $(
140                        Self::$variant(_) =>
141                            <$($path)::+ as crate::traits::DescriptorDef>::NAME,
142                    )+
143                    $($(
144                        Self::$nd_variant(_) =>
145                            <$($nd_path)::+ as crate::traits::DescriptorDef>::NAME,
146                    )+)?
147                    Self::Other { .. } => "CUSTOM",
148                    Self::Unknown { .. } => "UNKNOWN",
149                }
150            }
151
152            /// Parse one full descriptor (2-byte header included) by its tag.
153            ///
154            /// `None` means no typed implementation exists for `tag` (the
155            /// caller turns that into [`AnyDescriptor::Unknown`]). `Some(Err)`
156            /// is a typed parse failure for a recognised tag.
157            pub(crate) fn dispatch(tag: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
158                use dvb_common::Parse;
159                match tag {
160                    $(
161                        $tag => Some(<$($path)::+>::parse(full).map(Self::$variant)),
162                    )+
163                    _ => None,
164                }
165            }
166        }
167
168        #[cfg(test)]
169        mod macro_drift {
170            #[test]
171            fn tag_literals_match_descriptor_def() {
172                use crate::traits::DescriptorDef;
173                $(
174                    assert_eq!(
175                        $tag,
176                        <$($path)::+ as DescriptorDef>::TAG,
177                        concat!("tag literal drift for ", stringify!($variant)),
178                    );
179                    assert!(
180                        !<$($path)::+ as DescriptorDef>::NAME.is_empty(),
181                        concat!("empty NAME for ", stringify!($variant)),
182                    );
183                )+
184                $($(
185                    assert!(
186                        !<$($nd_path)::+ as DescriptorDef>::NAME.is_empty(),
187                        concat!("empty NAME for ", stringify!($nd_variant)),
188                    );
189                )+)?
190            }
191        }
192    };
193}
194
195declare_descriptors! {'a;
196    // MPEG-2 systems descriptors (ISO/IEC 13818-1) used outside table context.
197    Registration = 0x05 => crate::descriptors::registration::RegistrationDescriptor<'a>,
198    DataStreamAlignment = 0x06 => crate::descriptors::data_stream_alignment::DataStreamAlignmentDescriptor,
199    Ca = 0x09 => crate::descriptors::ca::CaDescriptor<'a>,
200    Iso639Language = 0x0A => crate::descriptors::iso_639_language::Iso639LanguageDescriptor,
201    PrivateDataIndicator = 0x0F => crate::descriptors::private_data_indicator::PrivateDataIndicatorDescriptor,
202    // DVB descriptors (ETSI EN 300 468) — contiguous 0x40..=0x7F.
203    NetworkName = 0x40 => crate::descriptors::network_name::NetworkNameDescriptor<'a>,
204    ServiceList = 0x41 => crate::descriptors::service_list::ServiceListDescriptor,
205    Stuffing = 0x42 => crate::descriptors::stuffing::StuffingDescriptor<'a>,
206    SatelliteDeliverySystem = 0x43 => crate::descriptors::satellite_delivery_system::SatelliteDeliverySystemDescriptor,
207    CableDeliverySystem = 0x44 => crate::descriptors::cable_delivery_system::CableDeliverySystemDescriptor,
208    VbiData = 0x45 => crate::descriptors::vbi_data::VbiDataDescriptor<'a>,
209    VbiTeletext = 0x46 => crate::descriptors::vbi_teletext::VbiTeletextDescriptor,
210    BouquetName = 0x47 => crate::descriptors::bouquet_name::BouquetNameDescriptor<'a>,
211    Service = 0x48 => crate::descriptors::service::ServiceDescriptor<'a>,
212    CountryAvailability = 0x49 => crate::descriptors::country_availability::CountryAvailabilityDescriptor,
213    Linkage = 0x4A => crate::descriptors::linkage::LinkageDescriptor<'a>,
214    NvodReference = 0x4B => crate::descriptors::nvod_reference::NvodReferenceDescriptor,
215    TimeShiftedService = 0x4C => crate::descriptors::time_shifted_service::TimeShiftedServiceDescriptor,
216    ShortEvent = 0x4D => crate::descriptors::short_event::ShortEventDescriptor<'a>,
217    ExtendedEvent = 0x4E => crate::descriptors::extended_event::ExtendedEventDescriptor<'a>,
218    TimeShiftedEvent = 0x4F => crate::descriptors::time_shifted_event::TimeShiftedEventDescriptor,
219    Component = 0x50 => crate::descriptors::component::ComponentDescriptor<'a>,
220    Mosaic = 0x51 => crate::descriptors::mosaic::MosaicDescriptor,
221    StreamIdentifier = 0x52 => crate::descriptors::stream_identifier::StreamIdentifierDescriptor,
222    CaIdentifier = 0x53 => crate::descriptors::ca_identifier::CaIdentifierDescriptor,
223    Content = 0x54 => crate::descriptors::content::ContentDescriptor,
224    ParentalRating = 0x55 => crate::descriptors::parental_rating::ParentalRatingDescriptor,
225    Teletext = 0x56 => crate::descriptors::teletext::TeletextDescriptor,
226    Telephone = 0x57 => crate::descriptors::telephone::TelephoneDescriptor<'a>,
227    LocalTimeOffset = 0x58 => crate::descriptors::local_time_offset::LocalTimeOffsetDescriptor,
228    Subtitling = 0x59 => crate::descriptors::subtitling::SubtitlingDescriptor,
229    TerrestrialDeliverySystem = 0x5A => crate::descriptors::terrestrial_delivery_system::TerrestrialDeliverySystemDescriptor,
230    MultilingualNetworkName = 0x5B => crate::descriptors::multilingual_network_name::MultilingualNetworkNameDescriptor<'a>,
231    MultilingualBouquetName = 0x5C => crate::descriptors::multilingual_bouquet_name::MultilingualBouquetNameDescriptor<'a>,
232    MultilingualServiceName = 0x5D => crate::descriptors::multilingual_service_name::MultilingualServiceNameDescriptor<'a>,
233    MultilingualComponent = 0x5E => crate::descriptors::multilingual_component::MultilingualComponentDescriptor<'a>,
234    PrivateDataSpecifier = 0x5F => crate::descriptors::private_data_specifier::PrivateDataSpecifierDescriptor,
235    ServiceMove = 0x60 => crate::descriptors::service_move::ServiceMoveDescriptor,
236    ShortSmoothingBuffer = 0x61 => crate::descriptors::short_smoothing_buffer::ShortSmoothingBufferDescriptor<'a>,
237    FrequencyList = 0x62 => crate::descriptors::frequency_list::FrequencyListDescriptor,
238    PartialTransportStream = 0x63 => crate::descriptors::partial_transport_stream::PartialTransportStreamDescriptor,
239    DataBroadcast = 0x64 => crate::descriptors::data_broadcast::DataBroadcastDescriptor<'a>,
240    Scrambling = 0x65 => crate::descriptors::scrambling::ScramblingDescriptor,
241    DataBroadcastId = 0x66 => crate::descriptors::data_broadcast_id::DataBroadcastIdDescriptor<'a>,
242    TransportStream = 0x67 => crate::descriptors::transport_stream::TransportStreamDescriptor<'a>,
243    Dsng = 0x68 => crate::descriptors::dsng::DsngDescriptor<'a>,
244    Pdc = 0x69 => crate::descriptors::pdc::PdcDescriptor,
245    Ac3 = 0x6A => crate::descriptors::ac3::Ac3Descriptor<'a>,
246    AncillaryData = 0x6B => crate::descriptors::ancillary_data::AncillaryDataDescriptor,
247    CellList = 0x6C => crate::descriptors::cell_list::CellListDescriptor,
248    CellFrequencyLink = 0x6D => crate::descriptors::cell_frequency_link::CellFrequencyLinkDescriptor,
249    AnnouncementSupport = 0x6E => crate::descriptors::announcement_support::AnnouncementSupportDescriptor,
250    ApplicationSignalling = 0x6F => crate::descriptors::application_signalling::ApplicationSignallingDescriptor,
251    AdaptationFieldData = 0x70 => crate::descriptors::adaptation_field_data::AdaptationFieldDataDescriptor,
252    ServiceIdentifier = 0x71 => crate::descriptors::service_identifier::ServiceIdentifierDescriptor<'a>,
253    ServiceAvailability = 0x72 => crate::descriptors::service_availability::ServiceAvailabilityDescriptor,
254    DefaultAuthority = 0x73 => crate::descriptors::default_authority::DefaultAuthorityDescriptor<'a>,
255    RelatedContent = 0x74 => crate::descriptors::related_content::RelatedContentDescriptor,
256    TvaId = 0x75 => crate::descriptors::tva_id::TvaIdDescriptor,
257    ContentIdentifier = 0x76 => crate::descriptors::content_identifier::ContentIdentifierDescriptor<'a>,
258    TimeSliceFecIdentifier = 0x77 => crate::descriptors::time_slice_fec_identifier::TimeSliceFecIdentifierDescriptor<'a>,
259    EcmRepetitionRate = 0x78 => crate::descriptors::ecm_repetition_rate::EcmRepetitionRateDescriptor<'a>,
260    S2SatelliteDeliverySystem = 0x79 => crate::descriptors::s2_satellite_delivery_system::S2SatelliteDeliverySystemDescriptor,
261    EnhancedAc3 = 0x7A => crate::descriptors::enhanced_ac3::EnhancedAc3Descriptor<'a>,
262    Dts = 0x7B => crate::descriptors::dts::DtsDescriptor<'a>,
263    Aac = 0x7C => crate::descriptors::aac::AacDescriptor<'a>,
264    XaitLocation = 0x7D => crate::descriptors::xait_location::XaitLocationDescriptor,
265    FtaContentManagement = 0x7E => crate::descriptors::fta_content_management::FtaContentManagementDescriptor,
266    Extension = 0x7F => crate::descriptors::extension::ExtensionDescriptor<'a>;
267    // Private / context-dependent: variant exists but is NOT auto-dispatched.
268    // 0x83 logical_channel requires private_data_specifier context; enabled
269    // via the descriptor registry (Task 4).
270    @no_dispatch
271    LogicalChannel => crate::descriptors::logical_channel::LogicalChannelDescriptor,
272}
273
274/// Lazily walk a raw descriptor loop. Never panics.
275///
276/// Per-descriptor parse errors yield `Err` and iteration continues (the
277/// descriptor_length field bounds each entry, so the walker can always
278/// advance past a malformed body). A truncated final header or body yields
279/// one `Err` and then the iterator fuses (returns `None` forever after).
280#[must_use]
281pub fn parse_loop(bytes: &[u8]) -> DescriptorIter<'_> {
282    DescriptorIter {
283        bytes,
284        pos: 0,
285        fused: false,
286    }
287}
288
289/// Extract the next descriptor-loop entry (tag + full-entry slice) from the
290/// raw byte stream. Shared by [`DescriptorIter::next`] and
291/// [`RegistryIter::next`](crate::descriptors::registry::RegistryIter::next).
292///
293/// On success, advances `pos` past the entry and returns `(tag, full)`.
294/// On truncation, sets `fused = true` and returns `Some(Err(..))`.
295/// When the loop is exhausted, returns `None`.
296pub(crate) fn next_loop_entry<'a>(
297    bytes: &'a [u8],
298    pos: &mut usize,
299    fused: &mut bool,
300) -> Option<crate::Result<(u8, &'a [u8])>> {
301    if *fused || *pos >= bytes.len() {
302        return None;
303    }
304    let rem = &bytes[*pos..];
305    if rem.len() < 2 {
306        *fused = true;
307        return Some(Err(crate::Error::BufferTooShort {
308            need: 2,
309            have: rem.len(),
310            what: "descriptor header in loop",
311        }));
312    }
313    let tag = rem[0];
314    let len = rem[1] as usize;
315    let total = 2 + len;
316    if rem.len() < total {
317        *fused = true;
318        return Some(Err(crate::Error::BufferTooShort {
319            need: total,
320            have: rem.len(),
321            what: "descriptor body in loop",
322        }));
323    }
324    let full = &rem[..total];
325    *pos += total;
326    Some(Ok((tag, full)))
327}
328
329/// Iterator over a raw descriptor loop; see [`parse_loop`].
330#[derive(Debug, Clone)]
331pub struct DescriptorIter<'a> {
332    bytes: &'a [u8],
333    pos: usize,
334    fused: bool,
335}
336
337impl<'a> Iterator for DescriptorIter<'a> {
338    type Item = crate::Result<AnyDescriptor<'a>>;
339
340    fn next(&mut self) -> Option<Self::Item> {
341        let (tag, full) = match next_loop_entry(self.bytes, &mut self.pos, &mut self.fused)? {
342            Ok(v) => v,
343            Err(e) => return Some(Err(e)),
344        };
345        Some(match AnyDescriptor::dispatch(tag, full) {
346            Some(res) => res,
347            None => Ok(AnyDescriptor::Unknown {
348                tag,
349                body: &full[2..],
350            }),
351        })
352    }
353}
354
355impl std::iter::FusedIterator for DescriptorIter<'_> {}
356
357/// A raw descriptor loop, borrowed from the section. Zero-copy: walk it
358/// typed via [`DescriptorLoop::iter`]; serde serializes the typed walk.
359///
360/// This is the table-loop analogue of [`crate::text::DvbText`]: it wraps the
361/// raw `descriptor()` sequence (the variable-length region inside a table) and
362/// decodes — i.e. dispatches each entry to a typed [`AnyDescriptor`] — only on
363/// demand. Parsing stays zero-copy; the typed walk happens when you call
364/// [`DescriptorLoop::iter`] or serialize.
365///
366/// ```
367/// use dvb_si::descriptors::{AnyDescriptor, DescriptorLoop};
368///
369/// // short_event (tag 0x4D, "eng" / "Hi") then an unknown private tag 0xA7.
370/// let raw = [
371///     0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00,
372///     0xA7, 0x02, 0xCA, 0xFE,
373/// ];
374/// let loop_ = DescriptorLoop::new(&raw);
375/// let items: Vec<_> = loop_.iter().collect();
376/// assert_eq!(items.len(), 2);
377/// assert!(matches!(items[0].as_ref().unwrap(), AnyDescriptor::ShortEvent(_)));
378/// assert_eq!(loop_.raw(), &raw[..]); // bytes preserved verbatim
379/// ```
380#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
381#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
382pub struct DescriptorLoop<'a>(&'a [u8]);
383
384impl<'a> DescriptorLoop<'a> {
385    /// Wrap a raw descriptor-loop slice (the `descriptor()` bytes only — no
386    /// enclosing length field).
387    #[must_use]
388    pub const fn new(raw: &'a [u8]) -> Self {
389        Self(raw)
390    }
391
392    /// The raw wire bytes of the loop, verbatim. These are what a serializer
393    /// writes back; use them for the byte length of the loop.
394    #[must_use]
395    pub const fn raw(&self) -> &'a [u8] {
396        self.0
397    }
398
399    /// Lazily walk the loop, yielding one typed [`AnyDescriptor`] per entry
400    /// (or [`AnyDescriptor::Unknown`] for tags with no implementation).
401    /// Delegates to [`parse_loop`]; never panics.
402    #[must_use]
403    pub fn iter(&self) -> DescriptorIter<'a> {
404        parse_loop(self.0)
405    }
406
407    /// Walk this loop through a [`DescriptorRegistry`](crate::descriptors::registry::DescriptorRegistry)
408    /// so registered private/custom descriptors (and PDS-scoped tags) are
409    /// produced as [`AnyDescriptor::Other`]; mirrors [`iter`](Self::iter)
410    /// otherwise.
411    ///
412    /// See [`DescriptorRegistry::parse_loop`](crate::descriptors::registry::DescriptorRegistry::parse_loop)
413    /// for precedence rules and PDS-scoped dispatch.
414    #[must_use]
415    pub fn iter_with<'r>(
416        &self,
417        registry: &'r crate::descriptors::registry::DescriptorRegistry,
418    ) -> crate::descriptors::registry::RegistryIter<'r, 'a> {
419        registry.parse_loop(self.0)
420    }
421
422    /// Walk this loop through both a [`DescriptorRegistry`](crate::descriptors::registry::DescriptorRegistry) and an
423    /// [`ExtensionRegistry`](crate::descriptors::extension::registry::ExtensionRegistry),
424    /// so that custom-registered extension bodies (tag `0x7F` with a known
425    /// `descriptor_tag_extension`) are surfaced as
426    /// [`ExtIterItem::CustomExtension`](crate::descriptors::registry::ExtIterItem::CustomExtension) with the type-erased value available
427    /// for downcast. All other descriptors follow the normal
428    /// [`iter_with`](Self::iter_with) precedence.
429    ///
430    /// This is the only way to reach private extension bodies during a
431    /// descriptor-loop walk; [`iter_with`](Self::iter_with) alone produces
432    /// the built-in [`ExtensionBody::Raw`](crate::descriptors::extension::ExtensionBody::Raw)
433    /// for unrecognised `descriptor_tag_extension` values.
434    #[must_use]
435    pub fn iter_with_extensions<'r>(
436        &self,
437        desc_reg: &'r crate::descriptors::registry::DescriptorRegistry,
438        ext_reg: &'r crate::descriptors::extension::registry::ExtensionRegistry,
439    ) -> crate::descriptors::registry::ExtRegistryIter<'r, 'a> {
440        crate::descriptors::registry::ExtRegistryIter::new(desc_reg, ext_reg, self.0)
441    }
442}
443
444impl<'a> std::ops::Deref for DescriptorLoop<'a> {
445    /// Derefs to the raw wire bytes — `len()`/indexing are **byte counts for
446    /// serialization, not entry counts**. To count entries, use
447    /// [`DescriptorLoop::iter`].
448    type Target = [u8];
449    fn deref(&self) -> &[u8] {
450        self.0
451    }
452}
453
454impl<'a> From<&'a [u8]> for DescriptorLoop<'a> {
455    fn from(raw: &'a [u8]) -> Self {
456        Self(raw)
457    }
458}
459
460impl std::fmt::Debug for DescriptorLoop<'_> {
461    /// Cheap: prints the byte length, not the decoded entries.
462    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
463        write!(f, "DescriptorLoop(<{} bytes>)", self.0.len())
464    }
465}
466
467impl<'a> IntoIterator for &DescriptorLoop<'a> {
468    type Item = crate::Result<AnyDescriptor<'a>>;
469    type IntoIter = DescriptorIter<'a>;
470    fn into_iter(self) -> Self::IntoIter {
471        self.iter()
472    }
473}
474
475#[cfg(feature = "serde")]
476impl serde::Serialize for DescriptorLoop<'_> {
477    /// Serializes as a sequence of the typed walk: each `Ok(d)` becomes the
478    /// [`AnyDescriptor`] (camelCase external tagging), and each `Err(e)`
479    /// becomes a `{"parseError": "<Display>"}` map — parse errors are surfaced,
480    /// never silently dropped.
481    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
482        struct Entry<'a>(crate::Result<AnyDescriptor<'a>>);
483        impl serde::Serialize for Entry<'_> {
484            fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
485                match &self.0 {
486                    Ok(d) => d.serialize(s),
487                    Err(e) => {
488                        use serde::ser::SerializeMap;
489                        let mut m = s.serialize_map(Some(1))?;
490                        m.serialize_entry("parseError", &e.to_string())?;
491                        m.end()
492                    }
493                }
494            }
495        }
496        s.collect_seq(self.iter().map(Entry))
497    }
498}
499// Serialize-only: the typed walk decodes DVB text and dispatches per-tag —
500// there is no lossless way to reconstruct the raw loop bytes from the
501// serialized form. Structs holding a DescriptorLoop derive Serialize only.
502// To reconstruct, keep the wire bytes and re-`parse` the table.
503
504#[cfg(test)]
505mod tests {
506    use super::*;
507
508    #[test]
509    fn unknown_tag_yields_unknown_with_body_sans_header() {
510        // tag 0xA7 (no typed impl), length 2, body [0xDE, 0xAD].
511        let bytes = [0xA7, 0x02, 0xDE, 0xAD];
512        let items: Vec<_> = parse_loop(&bytes).collect();
513        assert_eq!(items.len(), 1);
514        match items[0].as_ref().unwrap() {
515            AnyDescriptor::Unknown { tag, body } => {
516                assert_eq!(*tag, 0xA7);
517                assert_eq!(*body, &[0xDE, 0xAD]);
518            }
519            other => panic!("expected Unknown, got {other:?}"),
520        }
521    }
522
523    #[test]
524    fn empty_loop_yields_nothing() {
525        assert_eq!(parse_loop(&[]).count(), 0);
526    }
527
528    #[test]
529    fn logical_channel_0x83_is_not_dispatched() {
530        // 0x83 has a variant but no dispatcher entry → Unknown, never panics.
531        let bytes = [0x83, 0x04, 0x00, 0x01, 0xFC, 0x01];
532        let items: Vec<_> = parse_loop(&bytes).collect();
533        assert_eq!(items.len(), 1);
534        assert!(matches!(
535            items[0].as_ref().unwrap(),
536            AnyDescriptor::Unknown { tag: 0x83, .. }
537        ));
538    }
539
540    #[test]
541    fn descriptor_loop_iter_matches_parse_loop() {
542        let raw = [
543            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
544            0xA7, 0x02, 0xCA, 0xFE, // unknown 0xA7
545        ];
546        let via_loop: Vec<_> = DescriptorLoop::new(&raw)
547            .iter()
548            .map(|r| format!("{r:?}"))
549            .collect();
550        let via_fn: Vec<_> = parse_loop(&raw).map(|r| format!("{r:?}")).collect();
551        assert_eq!(via_loop, via_fn);
552        // raw()/Deref expose the wire bytes (byte length, not entry count).
553        assert_eq!(DescriptorLoop::new(&raw).raw(), &raw[..]);
554        assert_eq!(DescriptorLoop::new(&raw).len(), raw.len());
555        // IntoIterator for &DescriptorLoop.
556        let count = (&DescriptorLoop::new(&raw)).into_iter().count();
557        assert_eq!(count, 2);
558    }
559
560    #[test]
561    fn descriptor_loop_debug_is_cheap() {
562        let raw = [0x4D, 0x02, 0x01, 0x02];
563        assert_eq!(
564            format!("{:?}", DescriptorLoop::new(&raw)),
565            "DescriptorLoop(<4 bytes>)"
566        );
567    }
568
569    #[test]
570    fn iter_with_custom_tag_yields_other() {
571        use crate::descriptors::registry::DescriptorRegistry;
572        use crate::traits::DescriptorDef;
573
574        #[derive(Debug, PartialEq, Eq)]
575        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
576        struct MyTag0xA7 {
577            x: u8,
578        }
579
580        impl<'a> dvb_common::Parse<'a> for MyTag0xA7 {
581            type Error = crate::error::Error;
582            fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
583                if bytes.len() < 3 {
584                    return Err(crate::error::Error::BufferTooShort {
585                        need: 3,
586                        have: bytes.len(),
587                        what: "MyTag0xA7",
588                    });
589                }
590                Ok(Self { x: bytes[2] })
591            }
592        }
593
594        impl<'a> DescriptorDef<'a> for MyTag0xA7 {
595            const TAG: u8 = 0xA7;
596            const NAME: &'static str = "MY_TAG_0xA7";
597        }
598
599        let mut reg = DescriptorRegistry::new();
600        reg.register::<MyTag0xA7>();
601
602        let raw = [
603            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, 0xA7, 0x02, 0xCA, 0xFE,
604        ];
605        let loop_ = DescriptorLoop::new(&raw);
606        let items: Vec<_> = loop_.iter_with(&reg).collect::<Result<_, _>>().unwrap();
607        assert_eq!(items.len(), 2);
608        assert!(matches!(items[0], AnyDescriptor::ShortEvent(_)));
609        match &items[1] {
610            AnyDescriptor::Other { tag, value } => {
611                assert_eq!(*tag, 0xA7);
612                assert_eq!(value.downcast_ref::<MyTag0xA7>().unwrap().x, 0xCA);
613            }
614            other => panic!("expected Other, got {other:?}"),
615        }
616    }
617
618    #[test]
619    fn iter_with_empty_registry_matches_iter_for_builtin() {
620        let raw = [
621            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, 0xA7, 0x02, 0xCA, 0xFE,
622        ];
623        let loop_ = DescriptorLoop::new(&raw);
624        let reg = crate::descriptors::registry::DescriptorRegistry::new();
625        let via_iter: Vec<_> = loop_.iter().collect();
626        let via_iter_with: Vec<_> = loop_.iter_with(&reg).collect();
627
628        assert_eq!(via_iter.len(), via_iter_with.len());
629        for (a, b) in via_iter.iter().zip(via_iter_with.iter()) {
630            match (a, b) {
631                (Ok(AnyDescriptor::ShortEvent(_)), Ok(AnyDescriptor::ShortEvent(_))) => {}
632                (
633                    Ok(AnyDescriptor::Unknown { tag: t1, body: b1 }),
634                    Ok(AnyDescriptor::Unknown { tag: t2, body: b2 }),
635                ) => {
636                    assert_eq!(t1, t2);
637                    assert_eq!(b1, b2);
638                }
639                (Err(_), Err(_)) => {}
640                (l, r) => panic!("mismatch: {l:?} vs {r:?}"),
641            }
642        }
643    }
644
645    #[cfg(feature = "serde")]
646    #[test]
647    fn descriptor_loop_serializes_typed_unknown_and_parse_error() {
648        // [valid short_event, unknown tag 0xA7, truncated final entry].
649        // A truncated final entry (declared len 5, only 1 body byte present)
650        // makes the walker yield a final Err → {"parseError": …}.
651        let raw = [
652            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
653            0xA7, 0x02, 0xCA, 0xFE, // unknown 0xA7
654            0x55, 0x05, 0x00, // parental_rating header claims 5 bytes; only 1 present
655        ];
656        let v = serde_json::to_value(DescriptorLoop::new(&raw)).unwrap();
657        let arr = v.as_array().expect("sequence");
658        assert_eq!(arr.len(), 3);
659        // 1. typed short_event under the camelCase variant key.
660        assert!(arr[0].get("shortEvent").is_some(), "got {}", arr[0]);
661        assert_eq!(arr[0]["shortEvent"]["event_name"], "Hi");
662        // 2. unknown tag carries its raw body bytes.
663        let unknown = arr[1].get("unknown").expect("unknown variant");
664        assert_eq!(unknown["tag"], 0xA7);
665        assert_eq!(unknown["body"], serde_json::json!([0xCA, 0xFE]));
666        // 3. truncated entry → parseError, never silently dropped.
667        assert!(
668            arr[2].get("parseError").is_some(),
669            "expected parseError, got {}",
670            arr[2]
671        );
672    }
673}