Skip to main content

dvb_si/descriptors/
registry.rs

1//! Runtime descriptor registry — open registration of client private tags.
2//!
3//! [`DescriptorRegistry`] is a runtime-configurable walk engine that mirrors
4//! the semantics of the free [`crate::descriptors::parse_loop`] but allows
5//! clients to register their own private descriptor types.  Registered custom
6//! parsers win over built-in dispatch; the 0x83 logical_channel built-in is
7//! opt-in via [`DescriptorRegistry::with_logical_channel`].
8//!
9//! # PDS-scoped registration
10//!
11//! DVB private descriptor tags (`0x80..=0xFE`) are ambiguous without a
12//! preceding [`private_data_specifier_descriptor`][crate::descriptors::private_data_specifier]
13//! (tag `0x5F`) that scopes them.  Use [`register_for_pds`][DescriptorRegistry::register_for_pds]
14//! to register a type that is only dispatched when the active
15//! `private_data_specifier` matches.  A PDS-scoped registration takes precedence
16//! over a PDS-agnostic [`register`][DescriptorRegistry::register] of the same tag when that
17//! PDS is active.
18//!
19//! # Owned types only
20//!
21//! Registered types must be `'static` (i.e. owned — no borrowed slices).
22//! This is required because the parsed value is heap-allocated as a
23//! `Box<dyn DescriptorObject>` whose concrete type is erased; `dyn Any`
24//! downcast demands `'static`.  If your wire layout contains borrowed bytes,
25//! copy them into a `Vec<u8>` in the struct.
26//!
27//! # Example
28//!
29//! ```rust,no_run
30//! use dvb_si::descriptors::{DescriptorRegistry, AnyDescriptor};
31//! use dvb_si::traits::DescriptorDef;
32//! use broadcast_common::Parse;
33//!
34//! // A registered type must be `serde::Serialize` only when the `serde`
35//! // feature is on (that is what `DescriptorObject` requires there).
36//! #[derive(Debug)]
37//! #[cfg_attr(feature = "serde", derive(serde::Serialize))]
38//! struct MyPrivate { x: u8 }
39//!
40//! impl<'a> Parse<'a> for MyPrivate {
41//!     type Error = dvb_si::Error;
42//!     fn parse(bytes: &'a [u8]) -> dvb_si::Result<Self> {
43//!         if bytes.len() < 3 {
44//!             return Err(dvb_si::Error::BufferTooShort(broadcast_common::error::BufferTooShort {
45//!                 need: 3, have: bytes.len(), what: "MyPrivate",
46//!             }));
47//!         }
48//!         Ok(Self { x: bytes[2] })
49//!     }
50//! }
51//!
52//! impl<'a> DescriptorDef<'a> for MyPrivate {
53//!     const TAG: u8 = 0xA7;
54//!     const NAME: &'static str = "MY_PRIVATE";
55//! }
56//!
57//! let mut reg = DescriptorRegistry::new();
58//! reg.register::<MyPrivate>().with_logical_channel();
59//!
60//! let bytes = [0xA7, 0x01, 0x42u8];
61//! let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
62//! if let AnyDescriptor::Other { tag, ref value } = items[0] {
63//!     assert_eq!(tag, 0xA7);
64//!     assert_eq!(value.downcast_ref::<MyPrivate>().unwrap().x, 0x42);
65//! }
66//! ```
67
68use alloc::boxed::Box;
69use alloc::collections::BTreeMap;
70
71use crate::descriptors::any::AnyDescriptor;
72use crate::descriptors::private_data_specifier::PDS_NORDIG;
73
74broadcast_common::declare_object_trait! {
75    /// Object-safe face of a runtime-registered descriptor value.
76    ///
77    /// Registered types must be owned (`'static`) because the `dyn Any` downcast
78    /// path requires it.  See the [module docs][self] for details.
79    ///
80    /// Implemented automatically via the blanket impl for any `T` satisfying the
81    /// supertraits; you do not need to write this by hand.
82    pub trait DescriptorObject, "descriptor"
83}
84
85// ---------------------------------------------------------------------------
86// Internal parse closure type
87// ---------------------------------------------------------------------------
88
89/// A heap-allocated parse closure that takes a full descriptor (header + body)
90/// and returns an owned, type-erased descriptor value.
91pub(crate) type CustomParse =
92    Box<dyn for<'a> Fn(&'a [u8]) -> crate::Result<Box<dyn DescriptorObject>> + Send + Sync>;
93
94// ---------------------------------------------------------------------------
95// DescriptorRegistry
96// ---------------------------------------------------------------------------
97
98/// Runtime-configurable descriptor registry.
99///
100/// By default the registry has no custom parsers and 0x83 logical_channel is
101/// disabled (it is a private tag that requires `private_data_specifier`
102/// context).  Use [`register`][Self::register] and
103/// [`with_logical_channel`][Self::with_logical_channel] to opt in.
104///
105/// Walk a byte slice with [`parse_loop`][Self::parse_loop]; it returns a lazy
106/// [`RegistryIter`] with identical truncation/fuse/error-continue semantics to
107/// the free [`crate::descriptors::parse_loop`].
108///
109/// # Precedence (per entry)
110///
111/// 1. PDS-scoped custom parser (if the current `private_data_specifier` matches
112///    a [`register_for_pds`][Self::register_for_pds] entry) →
113///    [`AnyDescriptor::Other`]
114/// 2. PDS-agnostic custom-registered parser (tag in the [`custom`][Self::register]
115///    map) → [`AnyDescriptor::Other`]
116/// 3. PDS-scoped logical-channel opt-in (tag 0x83 + active PDS in
117///    [`with_logical_channel_for_pds`][Self::with_logical_channel_for_pds])
118///    → [`AnyDescriptor::LogicalChannel`]
119/// 4. Logical-channel opt-in (tag 0x83 + [`with_logical_channel`][Self::with_logical_channel]
120///    enabled) → [`AnyDescriptor::LogicalChannel`]
121/// 5. Built-in dispatch (internal `AnyDescriptor::dispatch`) → typed variant
122/// 6. Unknown → [`AnyDescriptor::Unknown`]
123#[derive(Default)]
124pub struct DescriptorRegistry {
125    custom: BTreeMap<(Option<u32>, u8), CustomParse>,
126    logical_channel: bool,
127    logical_channel_pds: alloc::collections::BTreeSet<u32>,
128}
129
130impl DescriptorRegistry {
131    /// Create an empty registry (built-in dispatch only; 0x83 disabled).
132    #[must_use]
133    pub fn new() -> Self {
134        Self::default()
135    }
136
137    /// Register an owned custom descriptor type for its
138    /// [`DescriptorDef::TAG`][crate::traits::DescriptorDef::TAG].
139    ///
140    /// # Owned types only
141    ///
142    /// `T` must be `'static` — no borrowed slices.  The registered value is
143    /// type-erased as `Box<dyn DescriptorObject>`; `dyn Any` downcast requires
144    /// the concrete type to be `'static`.
145    ///
146    /// Registering a type whose `TAG` is already used by a built-in **overrides**
147    /// the built-in for that tag.
148    ///
149    /// Re-registering the same tag replaces the prior custom parser (last wins).
150    /// A failing custom parse surfaces the client's `Parse::Error` unwrapped —
151    /// embed identifying context (type/tag) in your error's `what`/`reason` fields.
152    ///
153    /// The registration is PDS-agnostic: it matches the tag regardless of which
154    /// `private_data_specifier` (if any) is active in the loop.  A
155    /// PDS-scoped registration via [`register_for_pds`][Self::register_for_pds]
156    /// takes precedence over this when the matching PDS is active.
157    pub fn register<T>(&mut self) -> &mut Self
158    where
159        T: for<'a> crate::traits::DescriptorDef<'a> + DescriptorObject + 'static,
160    {
161        let tag = <T as crate::traits::DescriptorDef<'static>>::TAG;
162        self.custom.insert(
163            (None, tag),
164            Box::new(|b| {
165                Ok(Box::new(<T as broadcast_common::Parse>::parse(b)?)
166                    as Box<dyn DescriptorObject>)
167            }),
168        );
169        self
170    }
171
172    /// Register an owned custom descriptor type scoped to a specific
173    /// `private_data_specifier` value.
174    ///
175    /// The type is only dispatched when a preceding
176    /// [`private_data_specifier_descriptor`][crate::descriptors::private_data_specifier]
177    /// (tag `0x5F`) in the same descriptor loop has set the active PDS to
178    /// `pds`.  A PDS-scoped registration takes precedence over a PDS-agnostic
179    /// [`register`][Self::register] of the same tag when that PDS is active.
180    ///
181    /// Re-registering the same `(pds, tag)` pair replaces the prior custom
182    /// parser (last wins).
183    pub fn register_for_pds<T>(&mut self, pds: u32) -> &mut Self
184    where
185        T: for<'a> crate::traits::DescriptorDef<'a> + DescriptorObject + 'static,
186    {
187        let tag = <T as crate::traits::DescriptorDef<'static>>::TAG;
188        self.custom.insert(
189            (Some(pds), tag),
190            Box::new(|b| {
191                Ok(Box::new(<T as broadcast_common::Parse>::parse(b)?)
192                    as Box<dyn DescriptorObject>)
193            }),
194        );
195        self
196    }
197
198    /// Enable the 0x83 logical_channel built-in.
199    ///
200    /// By default 0x83 is not auto-dispatched because it is a private tag
201    /// whose semantics depend on a `private_data_specifier` context.  Call
202    /// this when you know the loop is from an EACEM/NorDig/D-Book stream.
203    pub fn with_logical_channel(&mut self) -> &mut Self {
204        self.logical_channel = true;
205        self
206    }
207
208    /// Enable the 0x83 logical_channel built-in ONLY when the active
209    /// `private_data_specifier` (set by a preceding 0x5F descriptor in the
210    /// same loop) matches `pds`.
211    ///
212    /// This is a safer opt-in than [`with_logical_channel`][Self::with_logical_channel]:
213    /// tag 0x83 under a different (or absent) PDS stays `Unknown`.
214    ///
215    /// Common values are [`crate::descriptors::PDS_EACEM`] and
216    /// [`crate::descriptors::PDS_NORDIG`].  A per-PDS opt-in takes precedence
217    /// over the global [`with_logical_channel`][Self::with_logical_channel]
218    /// opt-in when the matching PDS is active.
219    pub fn with_logical_channel_for_pds(&mut self, pds: u32) -> &mut Self {
220        self.logical_channel_pds.insert(pds);
221        self
222    }
223
224    /// Register both NorDig logical channel descriptors (v1 tag 0x83 and
225    /// v2 tag 0x87) for PDS_NORDIG (0x00000029). See
226    /// [`register_for_pds`][Self::register_for_pds].
227    pub fn with_nordig_lcn(&mut self) -> &mut Self {
228        self.register_for_pds::<crate::descriptors::nordig::NordigLogicalChannelV1>(PDS_NORDIG);
229        self.register_for_pds::<crate::descriptors::nordig::NordigLogicalChannelV2>(PDS_NORDIG);
230        self
231    }
232
233    /// Register the AVS3_audio_descriptor (tag 0xD2, T/AI 109.7 clause 8.1.3.4)
234    /// for `private_data_specifier` [`crate::descriptors::PDS_AVSA`] ('AVSA',
235    /// `0x41565341`) - the specifier EN 300 468 V1.20.1 Annex O / TS 101 154
236    /// V2.10.1 section 4.1.8.35 require to precede it. Under any other (or
237    /// no) specifier tag 0xD2 stays `Unknown`.
238    pub fn with_avs3_audio(&mut self) -> &mut Self {
239        self.register_for_pds::<crate::descriptors::avs3_audio::Avs3AudioDescriptor>(
240            crate::descriptors::PDS_AVSA,
241        );
242        self
243    }
244
245    /// Register the AVS3_video_descriptor (tag 0xD1, T/AI 109.6 clause 9.3.2 /
246    /// GY/T 420 clause 7.3.3.2) for [`crate::descriptors::PDS_AVSV`] ('AVSV',
247    /// `0x41565356`). The scope is established either by a preceding
248    /// `private_data_specifier_descriptor` (ETSI TS 101 154 V2.10.1 section
249    /// 4.1.8) or by a `registration_descriptor` whose `format_identifier` is
250    /// 'AVSV' (the Chinese documents' form). Otherwise tag 0xD1 stays `Unknown`.
251    pub fn with_avs3_video(&mut self) -> &mut Self {
252        self.register_for_pds::<crate::descriptors::avs3_video::Avs3VideoDescriptor>(
253            crate::descriptors::PDS_AVSV,
254        );
255        self
256    }
257
258    /// Register the AVS2_video_descriptor (tag 0x40, GY/T 420-2025 clause 7.2.3)
259    /// for 'AVSV' (private_data_specifier or `registration_descriptor`
260    /// `format_identifier`, GY/T 420 clause 7.2.2). Tag 0x40 stays the DVB
261    /// `network_name_descriptor` outside that scope.
262    pub fn with_avs2_video(&mut self) -> &mut Self {
263        self.register_for_pds::<crate::descriptors::avs2_video::Avs2VideoDescriptor>(
264            crate::descriptors::PDS_AVSV,
265        );
266        self
267    }
268
269    /// Register the AVS2_audio_descriptor (tag 0xD3, T/AI 109.7-2024 clause
270    /// 8.2.3.4) for 'AVSA' (private_data_specifier or `registration_descriptor`
271    /// `format_identifier`, clause 8.2.3.3).
272    pub fn with_avs2_audio(&mut self) -> &mut Self {
273        self.register_for_pds::<crate::descriptors::avs2_audio::Avs2AudioDescriptor>(
274            crate::descriptors::PDS_AVSA,
275        );
276        self
277    }
278
279    /// Register the older T/UWA 012.2-2023 AVS3 video descriptor (tag 62,
280    /// clause 5.4.1). The standard gives it no private_data_specifier, so this
281    /// is a PDS-agnostic registration of tag 0x3E.
282    pub fn with_tuwa_avs3_video(&mut self) -> &mut Self {
283        self.register::<crate::descriptors::tuwa_avs3_video::TuwaAvs3VideoDescriptor>()
284    }
285
286    /// Register the China `caption_service_descriptor` (tag 0x86, GY/T 270-2013
287    /// clause 6.4 Table 8). The standard gives it no private_data_specifier, so
288    /// this is a PDS-agnostic registration of tag 0x86: enable it only for
289    /// Chinese DTV streams (tag 0x86 is the ATSC caption_service_descriptor
290    /// elsewhere, with a different layout).
291    pub fn with_china_caption_service(&mut self) -> &mut Self {
292        self.register::<crate::descriptors::china_caption_service::ChinaCaptionServiceDescriptor>()
293    }
294
295    /// Register the ATSC `caption_service_descriptor` (tag 0x86, ATSC A/65:2013
296    /// §6.9.2 Table 6.26). PDS-agnostic registration of tag 0x86: enable it for
297    /// ATSC streams only, and not together with
298    /// [`Self::with_china_caption_service`], which claims the same tag.
299    pub fn with_atsc_caption_service(&mut self) -> &mut Self {
300        self.register::<crate::descriptors::atsc_caption_service::AtscCaptionServiceDescriptor>()
301    }
302
303    /// Lazily walk a raw descriptor loop using this registry's configuration.
304    ///
305    /// Semantics mirror [`crate::descriptors::parse_loop`]: per-descriptor
306    /// parse errors yield `Err` and iteration continues; a truncated final
307    /// header or body yields one `Err` then fuses.
308    ///
309    /// A `private_data_specifier_descriptor` (tag `0x5F`) in the loop
310    /// automatically updates the iterator's PDS context, scoping subsequent
311    /// private-tag dispatch.
312    #[must_use]
313    pub fn parse_loop<'r, 'a>(&'r self, bytes: &'a [u8]) -> RegistryIter<'r, 'a> {
314        RegistryIter {
315            registry: self,
316            bytes,
317            pos: 0,
318            fused: false,
319            current_pds: None,
320        }
321    }
322
323    /// Like [`parse_loop`](Self::parse_loop) but starting inside the scope an
324    /// enclosing loop already opened (`inherited`, from
325    /// [`RegistryIter::avs_scope`]).
326    ///
327    /// T/AI 109.7 clause 8.1.3 and GY/T 420 clauses 7.2.2 / 7.3.3.1 / 7.4.3.1
328    /// put the `registration_descriptor` under "Program and program element
329    /// descriptors" and say only that "the PMT shall contain" it, without
330    /// naming the `program_info` or the `ES_info` loop, while the AVS
331    /// descriptors themselves sit in the ES_info loop (T/AI 109.7 clause
332    /// 8.1.3.4, GY/T 420 clause 7.3.1 d). A registration in the `program_info`
333    /// loop therefore has to scope the AVS descriptors of the elementary-stream
334    /// loops that follow: walk `program_info` with `parse_loop`, take the scope
335    /// with `avs_scope`, and walk each ES_info loop with this method.
336    #[must_use]
337    pub fn parse_loop_in_scope<'r, 'a>(
338        &'r self,
339        bytes: &'a [u8],
340        inherited: Option<u32>,
341    ) -> RegistryIter<'r, 'a> {
342        RegistryIter {
343            registry: self,
344            bytes,
345            pos: 0,
346            fused: false,
347            current_pds: inherited,
348        }
349    }
350}
351
352// ---------------------------------------------------------------------------
353// RegistryIter
354// ---------------------------------------------------------------------------
355
356/// Lazy iterator over a raw descriptor loop, driven by a [`DescriptorRegistry`].
357///
358/// Returned by [`DescriptorRegistry::parse_loop`].
359pub struct RegistryIter<'r, 'a> {
360    registry: &'r DescriptorRegistry,
361    bytes: &'a [u8],
362    pos: usize,
363    fused: bool,
364    current_pds: Option<u32>,
365}
366
367/// Shared precedence ladder for both [`RegistryIter`] and [`ExtRegistryIter`].
368///
369/// Returns the [`AnyDescriptor`] that results from applying the six-step
370/// precedence: PDS-scoped custom → PDS-agnostic custom → PDS-scoped
371/// logical-channel → logical-channel opt-in → built-in dispatch → Unknown.
372pub(crate) fn dispatch_entry<'a>(
373    registry: &DescriptorRegistry,
374    current_pds: Option<u32>,
375    tag: u8,
376    full: &'a [u8],
377) -> crate::Result<AnyDescriptor<'a>> {
378    if let Some(pds) = current_pds
379        && let Some(parse_fn) = registry.custom.get(&(Some(pds), tag))
380    {
381        return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
382    }
383    if let Some(parse_fn) = registry.custom.get(&(None, tag)) {
384        return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
385    }
386    if let Some(pds) = current_pds
387        && tag == crate::descriptors::logical_channel::TAG
388        && registry.logical_channel_pds.contains(&pds)
389    {
390        use broadcast_common::Parse;
391        return crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
392            .map(AnyDescriptor::LogicalChannel);
393    }
394    if registry.logical_channel && tag == crate::descriptors::logical_channel::TAG {
395        use broadcast_common::Parse;
396        return crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
397            .map(AnyDescriptor::LogicalChannel);
398    }
399    if let Some(res) = AnyDescriptor::dispatch(tag, full) {
400        return res;
401    }
402    Ok(AnyDescriptor::Unknown {
403        tag,
404        body: &full[2..],
405    })
406}
407
408fn update_pds(current: &mut Option<u32>, tag: u8, full: &[u8]) {
409    // The Chinese AVS3 documents (T/AI 109.6/109.7, GY/T 420) scope their
410    // descriptors with a registration_descriptor whose format_identifier is the
411    // same four ASCII bytes as the DVB private_data_specifier ('AVSV'/'AVSA'),
412    // so such a registration opens the same scope. Any other registration
413    // leaves the context untouched.
414    if tag == crate::descriptors::registration::TAG {
415        use broadcast_common::Parse;
416        if let Ok(reg) = crate::descriptors::registration::RegistrationDescriptor::parse(full) {
417            let id = u32::from_be_bytes(reg.format_identifier);
418            if id == crate::descriptors::PDS_AVSA || id == crate::descriptors::PDS_AVSV {
419                *current = Some(id);
420            }
421        }
422    }
423    if tag == crate::descriptors::private_data_specifier::TAG {
424        use broadcast_common::Parse;
425        if let Ok(pds) =
426            crate::descriptors::private_data_specifier::PrivateDataSpecifierDescriptor::parse(full)
427        {
428            *current = Some(pds.private_data_specifier);
429        }
430    }
431}
432
433impl RegistryIter<'_, '_> {
434    /// The AVS scope ('AVSV' / 'AVSA') the entries walked so far opened, via a
435    /// `private_data_specifier_descriptor` or a `registration_descriptor`
436    /// `format_identifier`; `None` when no AVS scope is open. Feed it to
437    /// [`DescriptorRegistry::parse_loop_in_scope`] for the elementary-stream
438    /// loops that follow a `program_info` loop.
439    #[must_use]
440    pub fn avs_scope(&self) -> Option<u32> {
441        self.current_pds
442            .filter(|p| *p == crate::descriptors::PDS_AVSA || *p == crate::descriptors::PDS_AVSV)
443    }
444}
445
446impl<'a> Iterator for RegistryIter<'_, 'a> {
447    type Item = crate::Result<AnyDescriptor<'a>>;
448
449    fn next(&mut self) -> Option<Self::Item> {
450        let (tag, full) = match crate::descriptors::any::next_loop_entry(
451            self.bytes,
452            &mut self.pos,
453            &mut self.fused,
454        )? {
455            Ok(v) => v,
456            Err(e) => return Some(Err(e)),
457        };
458
459        update_pds(&mut self.current_pds, tag, full);
460
461        Some(dispatch_entry(self.registry, self.current_pds, tag, full))
462    }
463}
464
465impl core::iter::FusedIterator for RegistryIter<'_, '_> {}
466
467// ---------------------------------------------------------------------------
468// ExtIterItem — item type for iter_with_extensions
469// ---------------------------------------------------------------------------
470
471/// Item produced by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
472///
473/// Extends [`AnyDescriptor`] with a third arm for custom-registered extension
474/// bodies whose `descriptor_tag_extension` is recognised by an
475/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
476/// The `value` field can be downcast to the concrete type via
477/// `downcast_ref` on the value (see [`ExtensionObject`](super::extension::registry::ExtensionObject))
478#[derive(Debug)]
479#[cfg_attr(feature = "serde", derive(serde::Serialize))]
480#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
481#[non_exhaustive]
482pub enum ExtIterItem<'a> {
483    /// A regular descriptor (including built-in extension descriptors).
484    Descriptor(AnyDescriptor<'a>),
485    /// A custom-registered extension body (tag `0x7F`, known `descriptor_tag_extension`).
486    CustomExtension {
487        /// The `descriptor_tag_extension` byte.
488        tag_extension: u8,
489        /// The parsed, type-erased extension body value. Call `downcast_ref`
490        /// on it (see [`ExtensionObject`](super::extension::registry::ExtensionObject)) to recover the concrete type.
491        #[cfg_attr(
492            feature = "serde",
493            serde(serialize_with = "super::extension::registry::serialize_erased")
494        )]
495        value: Box<dyn super::extension::registry::ExtensionObject>,
496    },
497}
498
499// ---------------------------------------------------------------------------
500// ExtRegistryIter — iterator for iter_with_extensions
501// ---------------------------------------------------------------------------
502
503/// Lazy iterator over a raw descriptor loop, driven by both a
504/// [`DescriptorRegistry`] and an
505/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
506///
507/// Returned by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
508pub struct ExtRegistryIter<'r, 'a> {
509    desc_reg: &'r DescriptorRegistry,
510    ext_reg: &'r super::extension::registry::ExtensionRegistry,
511    bytes: &'a [u8],
512    pos: usize,
513    fused: bool,
514    current_pds: Option<u32>,
515}
516
517impl<'r, 'a> ExtRegistryIter<'r, 'a> {
518    pub(crate) fn new(
519        desc_reg: &'r DescriptorRegistry,
520        ext_reg: &'r super::extension::registry::ExtensionRegistry,
521        bytes: &'a [u8],
522        inherited: Option<u32>,
523    ) -> Self {
524        Self {
525            desc_reg,
526            ext_reg,
527            bytes,
528            pos: 0,
529            fused: false,
530            current_pds: inherited,
531        }
532    }
533
534    /// The AVS scope ('AVSV' / 'AVSA') the entries walked so far opened; see
535    /// [`RegistryIter::avs_scope`].
536    #[must_use]
537    pub fn avs_scope(&self) -> Option<u32> {
538        self.current_pds
539            .filter(|p| *p == crate::descriptors::PDS_AVSA || *p == crate::descriptors::PDS_AVSV)
540    }
541}
542
543impl<'a> Iterator for ExtRegistryIter<'_, 'a> {
544    type Item = crate::Result<ExtIterItem<'a>>;
545
546    fn next(&mut self) -> Option<Self::Item> {
547        let (tag, full) = match crate::descriptors::any::next_loop_entry(
548            self.bytes,
549            &mut self.pos,
550            &mut self.fused,
551        )? {
552            Ok(v) => v,
553            Err(e) => return Some(Err(e)),
554        };
555
556        update_pds(&mut self.current_pds, tag, full);
557
558        let len = full.len() - 2;
559        if tag == crate::descriptors::extension::TAG && len >= 1 {
560            let tag_extension = full[2];
561            if self.ext_reg.has_custom(tag_extension) {
562                return Some(match self.ext_reg.parse_body(tag_extension, &full[3..]) {
563                    Ok(super::extension::registry::RegisteredExtension::Custom {
564                        tag_extension,
565                        value,
566                    }) => Ok(ExtIterItem::CustomExtension {
567                        tag_extension,
568                        value,
569                    }),
570                    Ok(super::extension::registry::RegisteredExtension::Builtin(d)) => {
571                        Ok(ExtIterItem::Descriptor(AnyDescriptor::Extension(d)))
572                    }
573                    Err(e) => Err(e),
574                });
575            }
576        }
577
578        Some(
579            dispatch_entry(self.desc_reg, self.current_pds, tag, full).map(ExtIterItem::Descriptor),
580        )
581    }
582}
583
584impl core::iter::FusedIterator for ExtRegistryIter<'_, '_> {}
585
586#[cfg(test)]
587mod tests {
588    use super::*;
589    use crate::descriptors::private_data_specifier;
590    use crate::descriptors::private_data_specifier::{PDS_EACEM, PDS_NORDIG};
591    use crate::traits::DescriptorDef;
592
593    #[test]
594    fn pds_constants_match_the_register() {
595        // Pin the public consts to the vendored TS 101 162 register
596        // (registries/tsPDS.names) so they can't silently drift.
597        assert_eq!(
598            private_data_specifier::private_data_specifier_name(PDS_EACEM),
599            Some("EACEM/EICTA")
600        );
601        assert_eq!(
602            private_data_specifier::private_data_specifier_name(PDS_NORDIG),
603            Some("NorDig")
604        );
605    }
606
607    #[derive(Debug, PartialEq, Eq)]
608    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
609    struct PdsEacem {
610        v: u8,
611    }
612
613    impl<'a> broadcast_common::Parse<'a> for PdsEacem {
614        type Error = crate::error::Error;
615        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
616            if bytes.len() < 3 {
617                return Err(crate::error::Error::BufferTooShort(
618                    broadcast_common::error::BufferTooShort {
619                        need: 3,
620                        have: bytes.len(),
621                        what: "PdsEacem",
622                    },
623                ));
624            }
625            Ok(Self { v: bytes[2] })
626        }
627    }
628
629    impl DescriptorDef<'_> for PdsEacem {
630        const TAG: u8 = 0x83;
631        const NAME: &'static str = "PDS_EACEM";
632    }
633
634    #[derive(Debug, PartialEq, Eq)]
635    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
636    struct PdsNordig {
637        w: u8,
638    }
639
640    impl<'a> broadcast_common::Parse<'a> for PdsNordig {
641        type Error = crate::error::Error;
642        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
643            if bytes.len() < 3 {
644                return Err(crate::error::Error::BufferTooShort(
645                    broadcast_common::error::BufferTooShort {
646                        need: 3,
647                        have: bytes.len(),
648                        what: "PdsNordig",
649                    },
650                ));
651            }
652            Ok(Self { w: bytes[2] })
653        }
654    }
655
656    impl DescriptorDef<'_> for PdsNordig {
657        const TAG: u8 = 0x83;
658        const NAME: &'static str = "PDS_NORDIG";
659    }
660
661    #[derive(Debug, PartialEq, Eq)]
662    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
663    struct PdsAgnostic {
664        z: u8,
665    }
666
667    impl<'a> broadcast_common::Parse<'a> for PdsAgnostic {
668        type Error = crate::error::Error;
669        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
670            if bytes.len() < 3 {
671                return Err(crate::error::Error::BufferTooShort(
672                    broadcast_common::error::BufferTooShort {
673                        need: 3,
674                        have: bytes.len(),
675                        what: "PdsAgnostic",
676                    },
677                ));
678            }
679            Ok(Self { z: bytes[2] })
680        }
681    }
682
683    impl DescriptorDef<'_> for PdsAgnostic {
684        const TAG: u8 = 0x84;
685        const NAME: &'static str = "PDS_AGNOSTIC";
686    }
687
688    fn pds_descriptor(pds: u32) -> Vec<u8> {
689        let mut v = vec![private_data_specifier::TAG, 4];
690        v.extend_from_slice(&pds.to_be_bytes());
691        v
692    }
693
694    #[test]
695    fn pds_scoped_same_tag_resolves_by_pds() {
696        let mut reg = DescriptorRegistry::new();
697        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
698        reg.register_for_pds::<PdsNordig>(PDS_NORDIG);
699
700        let mut bytes = Vec::new();
701        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
702        bytes.extend_from_slice(&[0x83, 0x01, 0xAA]);
703
704        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
705        assert_eq!(items.len(), 2);
706        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
707        match &items[1] {
708            AnyDescriptor::Other { tag, value } => {
709                assert_eq!(*tag, 0x83);
710                let c = value.downcast_ref::<PdsEacem>().unwrap();
711                assert_eq!(c.v, 0xAA);
712            }
713            other => panic!("expected Other (PdsEacem), got {other:?}"),
714        }
715
716        let mut bytes2 = Vec::new();
717        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
718        bytes2.extend_from_slice(&[0x83, 0x01, 0xBB]);
719
720        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
721        match &items2[1] {
722            AnyDescriptor::Other { tag, value } => {
723                assert_eq!(*tag, 0x83);
724                let c = value.downcast_ref::<PdsNordig>().unwrap();
725                assert_eq!(c.w, 0xBB);
726            }
727            other => panic!("expected Other (PdsNordig), got {other:?}"),
728        }
729    }
730
731    #[test]
732    fn pds_scoped_does_not_match_wrong_pds() {
733        let mut reg = DescriptorRegistry::new();
734        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
735
736        let mut bytes = Vec::new();
737        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
738        bytes.extend_from_slice(&[0x83, 0x01, 0xCC]);
739
740        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
741        assert_eq!(items.len(), 2);
742        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
743        match &items[1] {
744            AnyDescriptor::Unknown { tag, .. } => assert_eq!(*tag, 0x83),
745            other => panic!("expected Unknown (wrong PDS), got {other:?}"),
746        }
747    }
748
749    /// AVS2 descriptors (GY/T 420 / T/AI 109.7) and the T/UWA 012.2 tag-62
750    /// descriptor are opt-in and scoped like the AVS3 ones.
751    #[test]
752    fn avs2_and_tuwa_descriptors_are_scoped() {
753        use crate::descriptors::avs2_audio::Avs2AudioDescriptor;
754        use crate::descriptors::avs2_video::Avs2VideoDescriptor;
755        use crate::descriptors::private_data_specifier::{PDS_AVSA, PDS_AVSV};
756        use crate::descriptors::tuwa_avs3_video::TuwaAvs3VideoDescriptor;
757        let mut reg = DescriptorRegistry::new();
758        reg.with_avs2_video()
759            .with_avs2_audio()
760            .with_tuwa_avs3_video();
761
762        let v = [0x40, 5, 0x20, 0x42, 0x00, 0xA9, 0x5F];
763        // Unscoped tag 0x40 is still the DVB network_name_descriptor.
764        let items: Vec<_> = reg.parse_loop(&v).collect::<Result<_, _>>().unwrap();
765        assert!(matches!(items[0], AnyDescriptor::NetworkName(_)));
766        // Scoped by an 'AVSV' registration_descriptor it is the AVS2 descriptor.
767        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'V'];
768        b.extend_from_slice(&v);
769        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
770        match &items[1] {
771            AnyDescriptor::Other { tag: 0x40, value } => {
772                assert!(value.downcast_ref::<Avs2VideoDescriptor>().is_some());
773            }
774            other => panic!("expected Avs2Video, got {other:?}"),
775        }
776        // ... or by a private_data_specifier.
777        let mut b = pds_descriptor(PDS_AVSV);
778        b.extend_from_slice(&v);
779        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
780        assert!(matches!(items[1], AnyDescriptor::Other { tag: 0x40, .. }));
781
782        let a = [0xD3, 2, 6, 0x20];
783        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'A'];
784        b.extend_from_slice(&a);
785        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
786        match &items[1] {
787            AnyDescriptor::Other { tag: 0xD3, value } => {
788                assert!(value.downcast_ref::<Avs2AudioDescriptor>().is_some());
789            }
790            other => panic!("expected Avs2Audio, got {other:?}"),
791        }
792        let mut b = pds_descriptor(PDS_AVSA);
793        b.extend_from_slice(&a);
794        assert!(matches!(
795            reg.parse_loop(&b).collect::<Result<Vec<_>, _>>().unwrap()[1],
796            AnyDescriptor::Other { tag: 0xD3, .. }
797        ));
798        // Without scope tag 0xD3 stays Unknown.
799        let items: Vec<_> = reg.parse_loop(&a).collect::<Result<_, _>>().unwrap();
800        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD3, .. }));
801
802        // T/UWA 012.2 tag 62: PDS-agnostic opt-in.
803        let t = [62, 7, 0x22, 0x42, 0xAA, 0x4F, 9, 14, 9];
804        let items: Vec<_> = reg.parse_loop(&t).collect::<Result<_, _>>().unwrap();
805        match &items[0] {
806            AnyDescriptor::Other { tag: 62, value } => {
807                assert!(value.downcast_ref::<TuwaAvs3VideoDescriptor>().is_some());
808            }
809            other => panic!("expected TuwaAvs3Video, got {other:?}"),
810        }
811        let plain = DescriptorRegistry::new();
812        let items: Vec<_> = plain.parse_loop(&t).collect::<Result<_, _>>().unwrap();
813        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 62, .. }));
814    }
815
816    /// The specs say "the PMT shall contain" the 'AVSV'/'AVSA'
817    /// registration_descriptor without naming the loop, and put the AVS
818    /// descriptor in the ES_info loop: a registration in `program_info` must
819    /// scope the ES_info loops that follow (`avs_scope` + `parse_loop_in_scope`).
820    #[test]
821    fn program_info_registration_scopes_es_info_descriptors() {
822        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
823        use crate::descriptors::private_data_specifier::PDS_AVSV;
824        let program_info = [0x05, 4, b'A', b'V', b'S', b'V'];
825        // ES_info of the stream_type 0xD4 entry: just the AVS3 video descriptor.
826        let es_info = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
827        let mut reg = DescriptorRegistry::new();
828        reg.with_avs3_video();
829
830        // Per-loop scoping alone never sees the registration: Unknown.
831        let items: Vec<_> = reg.parse_loop(&es_info).collect::<Result<_, _>>().unwrap();
832        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD1, .. }));
833
834        // Walk program_info, carry the scope into the ES_info walk.
835        let mut pi = reg.parse_loop(&program_info);
836        assert_eq!(pi.avs_scope(), None, "no entry consumed yet");
837        for item in pi.by_ref() {
838            item.unwrap();
839        }
840        let scope = pi.avs_scope();
841        assert_eq!(scope, Some(PDS_AVSV));
842        let items: Vec<_> = reg
843            .parse_loop_in_scope(&es_info, scope)
844            .collect::<Result<_, _>>()
845            .unwrap();
846        match &items[0] {
847            AnyDescriptor::Other { tag: 0xD1, value } => {
848                assert!(value.downcast_ref::<Avs3VideoDescriptor>().is_some());
849            }
850            other => panic!("expected Avs3Video via inherited scope, got {other:?}"),
851        }
852        // A non-AVS program_info registration opens no scope to inherit.
853        let hevc = [0x05, 4, b'H', b'E', b'V', b'C'];
854        let mut pi = reg.parse_loop(&hevc);
855        for item in pi.by_ref() {
856            item.unwrap();
857        }
858        assert_eq!(pi.avs_scope(), None);
859    }
860
861    /// AVS3_video_descriptor (tag 0xD1): scoped by a 'AVSV' private_data_specifier
862    /// (TS 101 154) or a 'AVSV' registration_descriptor (T/AI 109.6 / GY/T 420).
863    #[test]
864    fn avs3_video_descriptor_scopes() {
865        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
866        use crate::descriptors::private_data_specifier::PDS_AVSV;
867        let desc = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
868        let mut reg = DescriptorRegistry::new();
869        reg.with_avs3_video();
870        let check = |bytes: &[u8], idx: usize| {
871            let items: Vec<_> = reg.parse_loop(bytes).collect::<Result<_, _>>().unwrap();
872            match &items[idx] {
873                AnyDescriptor::Other { tag, value } => {
874                    assert_eq!(*tag, 0xD1);
875                    assert_eq!(
876                        value
877                            .downcast_ref::<Avs3VideoDescriptor>()
878                            .unwrap()
879                            .profile_id,
880                        0x22
881                    );
882                }
883                other => panic!("expected Other, got {other:?}"),
884            }
885        };
886        let mut pds = pds_descriptor(PDS_AVSV);
887        pds.extend_from_slice(&desc);
888        check(&pds, 1);
889        let mut registration = vec![0x05, 4, b'A', b'V', b'S', b'V'];
890        registration.extend_from_slice(&desc);
891        check(&registration, 1);
892        // No scope, or a different registration ('AC-3'), leaves it Unknown.
893        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
894        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD1, .. }));
895        let mut other = vec![0x05, 4, b'A', b'C', b'-', b'3'];
896        other.extend_from_slice(&desc);
897        let items: Vec<_> = reg.parse_loop(&other).collect::<Result<_, _>>().unwrap();
898        assert!(matches!(items[1], AnyDescriptor::Unknown { tag: 0xD1, .. }));
899        // The audio descriptor also accepts the 'AVSA' registration form.
900        let adesc = [0xD2, 2, 0x72, 0x45];
901        let mut areg = DescriptorRegistry::new();
902        areg.with_avs3_audio();
903        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'A'];
904        b.extend_from_slice(&adesc);
905        let items: Vec<_> = areg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
906        assert!(matches!(items[1], AnyDescriptor::Other { tag: 0xD2, .. }));
907    }
908
909    /// T/AI 109.7 clause 8.1.3.4 + TS 101 154 V2.10.1 4.1.8.35: tag 0xD2 is the
910    /// AVS3_audio_descriptor only under private_data_specifier 'AVSA'.
911    #[test]
912    fn avs3_audio_descriptor_is_pds_scoped() {
913        use crate::descriptors::avs3_audio::Avs3AudioDescriptor;
914        use crate::descriptors::private_data_specifier::PDS_AVSA;
915        let desc = [0xD2, 6, 0x03, 0xAA, 0x95, 0x04, 0x00, 0x6A];
916        let mut reg = DescriptorRegistry::new();
917        reg.with_avs3_audio();
918
919        let mut bytes = Vec::new();
920        bytes.extend_from_slice(&pds_descriptor(PDS_AVSA));
921        bytes.extend_from_slice(&desc);
922        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
923        match &items[1] {
924            AnyDescriptor::Other { tag, value } => {
925                assert_eq!(*tag, 0xD2);
926                let d = value.downcast_ref::<Avs3AudioDescriptor>().unwrap();
927                assert_eq!(d.sampling_frequency_index, 3);
928            }
929            other => panic!("expected Other (Avs3AudioDescriptor), got {other:?}"),
930        }
931
932        // Without the 'AVSA' specifier (or under another) the tag stays Unknown.
933        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
934        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD2, .. }));
935        let mut other = Vec::new();
936        other.extend_from_slice(&pds_descriptor(PDS_NORDIG));
937        other.extend_from_slice(&desc);
938        let items: Vec<_> = reg.parse_loop(&other).collect::<Result<_, _>>().unwrap();
939        assert!(matches!(items[1], AnyDescriptor::Unknown { tag: 0xD2, .. }));
940    }
941
942    /// GY/T 270-2013 Table 8: tag 0x86 is the China caption_service_descriptor
943    /// once `with_china_caption_service` is enabled, and stays Unknown otherwise.
944    #[test]
945    fn china_caption_service_is_opt_in() {
946        use crate::descriptors::china_caption_service::ChinaCaptionServiceDescriptor;
947        let desc = [
948            0x86, 9, 0xE1, b'c', b'h', b'i', 0xC1, 0xC2, 0xFF, 0xE1, 0x00,
949        ];
950        let plain = DescriptorRegistry::new();
951        let items: Vec<_> = plain.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
952        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0x86, .. }));
953        let mut reg = DescriptorRegistry::new();
954        reg.with_china_caption_service();
955        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
956        match &items[0] {
957            AnyDescriptor::Other { tag, value } => {
958                assert_eq!(*tag, 0x86);
959                let d = value
960                    .downcast_ref::<ChinaCaptionServiceDescriptor>()
961                    .unwrap();
962                assert_eq!(d.services.len(), 1);
963            }
964            other => panic!("expected Other, got {other:?}"),
965        }
966    }
967
968    #[test]
969    fn atsc_caption_service_is_opt_in() {
970        use crate::descriptors::atsc_caption_service::AtscCaptionServiceDescriptor;
971        let desc = [0x86, 7, 0xE1, b'e', b'n', b'g', 0xC1, 0x7F, 0xFF];
972        let plain = DescriptorRegistry::new();
973        let items: Vec<_> = plain.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
974        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0x86, .. }));
975        let mut reg = DescriptorRegistry::new();
976        reg.with_atsc_caption_service();
977        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
978        match &items[0] {
979            AnyDescriptor::Other { value, .. } => {
980                let d = value
981                    .downcast_ref::<AtscCaptionServiceDescriptor>()
982                    .unwrap();
983                assert_eq!(d.services.len(), 1);
984            }
985            other => panic!("expected Other, got {other:?}"),
986        }
987    }
988
989    #[test]
990    fn pds_agnostic_matches_without_pds() {
991        let mut reg = DescriptorRegistry::new();
992        reg.register::<PdsAgnostic>();
993
994        let bytes = [0x84, 0x01, 0xDD];
995        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
996        assert_eq!(items.len(), 1);
997        match &items[0] {
998            AnyDescriptor::Other { tag, value } => {
999                assert_eq!(*tag, 0x84);
1000                let c = value.downcast_ref::<PdsAgnostic>().unwrap();
1001                assert_eq!(c.z, 0xDD);
1002            }
1003            other => panic!("expected Other, got {other:?}"),
1004        }
1005    }
1006
1007    #[test]
1008    fn pds_scoped_takes_precedence_over_agnostic() {
1009        // Two parsers compete for the SAME tag 0x83: one PDS-agnostic, one
1010        // scoped to EACEM. With no PDS active the agnostic one wins; once an
1011        // EACEM private_data_specifier appears, the scoped one takes over.
1012        #[derive(Debug, PartialEq, Eq)]
1013        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1014        struct Agnostic83 {
1015            a: u8,
1016        }
1017
1018        impl<'a> broadcast_common::Parse<'a> for Agnostic83 {
1019            type Error = crate::error::Error;
1020            fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
1021                if bytes.len() < 3 {
1022                    return Err(crate::error::Error::BufferTooShort(
1023                        broadcast_common::error::BufferTooShort {
1024                            need: 3,
1025                            have: bytes.len(),
1026                            what: "Agnostic83",
1027                        },
1028                    ));
1029                }
1030                Ok(Self { a: bytes[2] })
1031            }
1032        }
1033
1034        impl DescriptorDef<'_> for Agnostic83 {
1035            const TAG: u8 = 0x83;
1036            const NAME: &'static str = "AGNOSTIC_83";
1037        }
1038
1039        let mut reg = DescriptorRegistry::new();
1040        reg.register::<Agnostic83>();
1041        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
1042
1043        // No PDS → agnostic wins
1044        let items: Vec<_> = reg
1045            .parse_loop(&[0x83, 0x01, 0xEE])
1046            .collect::<Result<_, _>>()
1047            .unwrap();
1048        match &items[0] {
1049            AnyDescriptor::Other { value, .. } => {
1050                assert!(value.downcast_ref::<Agnostic83>().is_some());
1051                assert!(value.downcast_ref::<PdsEacem>().is_none());
1052            }
1053            other => panic!("expected Other, got {other:?}"),
1054        }
1055
1056        // With PDS EACEM → PDS-scoped wins
1057        let mut bytes = Vec::new();
1058        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1059        bytes.extend_from_slice(&[0x83, 0x01, 0xFF]);
1060        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1061        match &items[1] {
1062            AnyDescriptor::Other { value, .. } => {
1063                assert!(value.downcast_ref::<PdsEacem>().is_some());
1064                assert!(value.downcast_ref::<Agnostic83>().is_none());
1065            }
1066            other => panic!("expected Other, got {other:?}"),
1067        }
1068    }
1069
1070    fn logical_channel_descriptor(service_id: u16, visible: bool, lcn: u16) -> Vec<u8> {
1071        // One entry: tag=0x83, len=4, entry(4 bytes)
1072        let mut v = vec![crate::descriptors::logical_channel::TAG, 4];
1073        v.extend_from_slice(&service_id.to_be_bytes());
1074        let flags = (u8::from(visible) << 7) | ((lcn >> 8) as u8 & 0x03);
1075        v.push(flags);
1076        v.push((lcn & 0xFF) as u8);
1077        v
1078    }
1079
1080    #[test]
1081    fn logical_channel_pds_scoped_matches_correct_pds() {
1082        let mut reg = DescriptorRegistry::new();
1083        reg.with_logical_channel_for_pds(PDS_EACEM);
1084
1085        let mut bytes = Vec::new();
1086        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1087        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
1088
1089        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1090        assert_eq!(items.len(), 2);
1091        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
1092        match &items[1] {
1093            AnyDescriptor::LogicalChannel(lc) => {
1094                assert_eq!(lc.entries.len(), 1);
1095                assert_eq!(lc.entries[0].service_id, 0x1234);
1096                assert!(lc.entries[0].visible_service);
1097                assert_eq!(lc.entries[0].logical_channel_number, 101);
1098            }
1099            other => panic!("expected LogicalChannel, got {other:?}"),
1100        }
1101    }
1102
1103    #[test]
1104    fn logical_channel_pds_scoped_rejects_no_pds() {
1105        let mut reg = DescriptorRegistry::new();
1106        reg.with_logical_channel_for_pds(PDS_EACEM);
1107
1108        let bytes = logical_channel_descriptor(0x1234, true, 101);
1109        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1110        assert_eq!(items.len(), 1);
1111        assert!(matches!(
1112            &items[0],
1113            AnyDescriptor::Unknown { tag: 0x83, .. }
1114        ));
1115    }
1116
1117    #[test]
1118    fn logical_channel_pds_scoped_rejects_wrong_pds() {
1119        let mut reg = DescriptorRegistry::new();
1120        reg.with_logical_channel_for_pds(PDS_EACEM);
1121
1122        let mut bytes = Vec::new();
1123        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
1124        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
1125
1126        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1127        assert_eq!(items.len(), 2);
1128        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
1129        assert!(matches!(
1130            &items[1],
1131            AnyDescriptor::Unknown { tag: 0x83, .. }
1132        ));
1133    }
1134
1135    #[test]
1136    fn logical_channel_pds_scoped_multiple_pds() {
1137        let mut reg = DescriptorRegistry::new();
1138        reg.with_logical_channel_for_pds(PDS_EACEM);
1139        reg.with_logical_channel_for_pds(PDS_NORDIG);
1140
1141        // EACEM → LogicalChannel
1142        let mut bytes = Vec::new();
1143        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1144        bytes.extend_from_slice(&logical_channel_descriptor(0x0001, true, 1));
1145        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1146        assert!(matches!(&items[1], AnyDescriptor::LogicalChannel(_)));
1147
1148        // NorDig → LogicalChannel
1149        let mut bytes2 = Vec::new();
1150        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
1151        bytes2.extend_from_slice(&logical_channel_descriptor(0x0002, false, 2));
1152        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
1153        assert!(matches!(&items2[1], AnyDescriptor::LogicalChannel(_)));
1154    }
1155
1156    /// Same program_info-registration layout through `iter_with_extensions`.
1157    #[test]
1158    fn program_info_registration_scopes_es_info_with_extensions() {
1159        use crate::descriptors::any::DescriptorLoop;
1160        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
1161        use crate::descriptors::extension::registry::ExtensionRegistry;
1162        use crate::descriptors::private_data_specifier::PDS_AVSV;
1163        let program_info = [0x05, 4, b'A', b'V', b'S', b'V'];
1164        let es_info = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
1165        let mut reg = DescriptorRegistry::new();
1166        reg.with_avs3_video();
1167        let ext = ExtensionRegistry::new();
1168
1169        let items: Vec<_> = DescriptorLoop::new(&es_info)
1170            .iter_with_extensions(&reg, &ext)
1171            .collect::<Result<_, _>>()
1172            .unwrap();
1173        assert!(matches!(
1174            &items[0],
1175            ExtIterItem::Descriptor(AnyDescriptor::Unknown { tag: 0xD1, .. })
1176        ));
1177
1178        let mut pi = DescriptorLoop::new(&program_info).iter_with_extensions(&reg, &ext);
1179        for item in pi.by_ref() {
1180            item.unwrap();
1181        }
1182        let scope = pi.avs_scope();
1183        assert_eq!(scope, Some(PDS_AVSV));
1184        let items: Vec<_> = DescriptorLoop::new(&es_info)
1185            .iter_with_extensions_in_scope(&reg, &ext, scope)
1186            .collect::<Result<_, _>>()
1187            .unwrap();
1188        match &items[0] {
1189            ExtIterItem::Descriptor(AnyDescriptor::Other { tag: 0xD1, value }) => {
1190                assert!(value.downcast_ref::<Avs3VideoDescriptor>().is_some());
1191            }
1192            other => panic!("expected Avs3Video via inherited scope, got {other:?}"),
1193        }
1194    }
1195
1196    #[test]
1197    fn iter_with_extensions_surfaces_custom_extension() {
1198        use crate::descriptors::any::{AnyDescriptor, DescriptorLoop};
1199        use crate::descriptors::extension::registry::ExtensionRegistry;
1200
1201        #[derive(Debug, PartialEq, Eq)]
1202        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1203        struct MyCustomExt {
1204            payload: Vec<u8>,
1205        }
1206
1207        impl<'a> broadcast_common::Parse<'a> for MyCustomExt {
1208            type Error = crate::error::Error;
1209            fn parse(sel: &'a [u8]) -> crate::Result<Self> {
1210                Ok(Self {
1211                    payload: sel.to_vec(),
1212                })
1213            }
1214        }
1215
1216        impl crate::descriptors::extension::ExtensionBodyDef<'_> for MyCustomExt {
1217            const TAG_EXTENSION: u8 = 0x42;
1218            const NAME: &'static str = "MY_CUSTOM_EXT";
1219        }
1220
1221        let mut ext_reg = ExtensionRegistry::new();
1222        ext_reg.register::<MyCustomExt>();
1223
1224        let desc_reg = DescriptorRegistry::new();
1225
1226        // Build a descriptor loop with a short_event + a 0x7F extension with tag_extension 0x42
1227        let mut loop_bytes = vec![
1228            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
1229        ];
1230        // extension: tag=0x7F, length=3, tag_extension=0x42, selector=[0xAB, 0xCD]
1231        loop_bytes.extend_from_slice(&[0x7F, 0x03, 0x42, 0xAB, 0xCD]);
1232
1233        let dl = DescriptorLoop::new(&loop_bytes);
1234        let items: Vec<_> = dl
1235            .iter_with_extensions(&desc_reg, &ext_reg)
1236            .collect::<Result<_, _>>()
1237            .unwrap();
1238        assert_eq!(items.len(), 2);
1239        // First item is a regular descriptor
1240        assert!(matches!(
1241            &items[0],
1242            ExtIterItem::Descriptor(AnyDescriptor::ShortEvent(_))
1243        ));
1244        // Second item surfaces the custom extension body (not Raw!)
1245        match &items[1] {
1246            ExtIterItem::CustomExtension {
1247                tag_extension,
1248                value,
1249            } => {
1250                assert_eq!(*tag_extension, 0x42);
1251                let concrete = value.downcast_ref::<MyCustomExt>().unwrap();
1252                assert_eq!(concrete.payload, &[0xAB, 0xCD]);
1253            }
1254            other => panic!("expected CustomExtension, got {other:?}"),
1255        }
1256    }
1257}