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    use alloc::{vec, vec::Vec};
593
594    #[test]
595    fn pds_constants_match_the_register() {
596        // Pin the public consts to the vendored TS 101 162 register
597        // (registries/tsPDS.names) so they can't silently drift.
598        assert_eq!(
599            private_data_specifier::private_data_specifier_name(PDS_EACEM),
600            Some("EACEM/EICTA")
601        );
602        assert_eq!(
603            private_data_specifier::private_data_specifier_name(PDS_NORDIG),
604            Some("NorDig")
605        );
606    }
607
608    #[derive(Debug, PartialEq, Eq)]
609    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
610    struct PdsEacem {
611        v: u8,
612    }
613
614    impl<'a> broadcast_common::Parse<'a> for PdsEacem {
615        type Error = crate::error::Error;
616        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
617            if bytes.len() < 3 {
618                return Err(crate::error::Error::BufferTooShort(
619                    broadcast_common::error::BufferTooShort {
620                        need: 3,
621                        have: bytes.len(),
622                        what: "PdsEacem",
623                    },
624                ));
625            }
626            Ok(Self { v: bytes[2] })
627        }
628    }
629
630    impl DescriptorDef<'_> for PdsEacem {
631        const TAG: u8 = 0x83;
632        const NAME: &'static str = "PDS_EACEM";
633    }
634
635    #[derive(Debug, PartialEq, Eq)]
636    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
637    struct PdsNordig {
638        w: u8,
639    }
640
641    impl<'a> broadcast_common::Parse<'a> for PdsNordig {
642        type Error = crate::error::Error;
643        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
644            if bytes.len() < 3 {
645                return Err(crate::error::Error::BufferTooShort(
646                    broadcast_common::error::BufferTooShort {
647                        need: 3,
648                        have: bytes.len(),
649                        what: "PdsNordig",
650                    },
651                ));
652            }
653            Ok(Self { w: bytes[2] })
654        }
655    }
656
657    impl DescriptorDef<'_> for PdsNordig {
658        const TAG: u8 = 0x83;
659        const NAME: &'static str = "PDS_NORDIG";
660    }
661
662    #[derive(Debug, PartialEq, Eq)]
663    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
664    struct PdsAgnostic {
665        z: u8,
666    }
667
668    impl<'a> broadcast_common::Parse<'a> for PdsAgnostic {
669        type Error = crate::error::Error;
670        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
671            if bytes.len() < 3 {
672                return Err(crate::error::Error::BufferTooShort(
673                    broadcast_common::error::BufferTooShort {
674                        need: 3,
675                        have: bytes.len(),
676                        what: "PdsAgnostic",
677                    },
678                ));
679            }
680            Ok(Self { z: bytes[2] })
681        }
682    }
683
684    impl DescriptorDef<'_> for PdsAgnostic {
685        const TAG: u8 = 0x84;
686        const NAME: &'static str = "PDS_AGNOSTIC";
687    }
688
689    fn pds_descriptor(pds: u32) -> Vec<u8> {
690        let mut v = vec![private_data_specifier::TAG, 4];
691        v.extend_from_slice(&pds.to_be_bytes());
692        v
693    }
694
695    #[test]
696    fn pds_scoped_same_tag_resolves_by_pds() {
697        let mut reg = DescriptorRegistry::new();
698        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
699        reg.register_for_pds::<PdsNordig>(PDS_NORDIG);
700
701        let mut bytes = Vec::new();
702        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
703        bytes.extend_from_slice(&[0x83, 0x01, 0xAA]);
704
705        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
706        assert_eq!(items.len(), 2);
707        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
708        match &items[1] {
709            AnyDescriptor::Other { tag, value } => {
710                assert_eq!(*tag, 0x83);
711                let c = value.downcast_ref::<PdsEacem>().unwrap();
712                assert_eq!(c.v, 0xAA);
713            }
714            other => panic!("expected Other (PdsEacem), got {other:?}"),
715        }
716
717        let mut bytes2 = Vec::new();
718        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
719        bytes2.extend_from_slice(&[0x83, 0x01, 0xBB]);
720
721        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
722        match &items2[1] {
723            AnyDescriptor::Other { tag, value } => {
724                assert_eq!(*tag, 0x83);
725                let c = value.downcast_ref::<PdsNordig>().unwrap();
726                assert_eq!(c.w, 0xBB);
727            }
728            other => panic!("expected Other (PdsNordig), got {other:?}"),
729        }
730    }
731
732    #[test]
733    fn pds_scoped_does_not_match_wrong_pds() {
734        let mut reg = DescriptorRegistry::new();
735        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
736
737        let mut bytes = Vec::new();
738        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
739        bytes.extend_from_slice(&[0x83, 0x01, 0xCC]);
740
741        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
742        assert_eq!(items.len(), 2);
743        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
744        match &items[1] {
745            AnyDescriptor::Unknown { tag, .. } => assert_eq!(*tag, 0x83),
746            other => panic!("expected Unknown (wrong PDS), got {other:?}"),
747        }
748    }
749
750    /// AVS2 descriptors (GY/T 420 / T/AI 109.7) and the T/UWA 012.2 tag-62
751    /// descriptor are opt-in and scoped like the AVS3 ones.
752    #[test]
753    fn avs2_and_tuwa_descriptors_are_scoped() {
754        use crate::descriptors::avs2_audio::Avs2AudioDescriptor;
755        use crate::descriptors::avs2_video::Avs2VideoDescriptor;
756        use crate::descriptors::private_data_specifier::{PDS_AVSA, PDS_AVSV};
757        use crate::descriptors::tuwa_avs3_video::TuwaAvs3VideoDescriptor;
758        let mut reg = DescriptorRegistry::new();
759        reg.with_avs2_video()
760            .with_avs2_audio()
761            .with_tuwa_avs3_video();
762
763        let v = [0x40, 5, 0x20, 0x42, 0x00, 0xA9, 0x5F];
764        // Unscoped tag 0x40 is still the DVB network_name_descriptor.
765        let items: Vec<_> = reg.parse_loop(&v).collect::<Result<_, _>>().unwrap();
766        assert!(matches!(items[0], AnyDescriptor::NetworkName(_)));
767        // Scoped by an 'AVSV' registration_descriptor it is the AVS2 descriptor.
768        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'V'];
769        b.extend_from_slice(&v);
770        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
771        match &items[1] {
772            AnyDescriptor::Other { tag: 0x40, value } => {
773                assert!(value.downcast_ref::<Avs2VideoDescriptor>().is_some());
774            }
775            other => panic!("expected Avs2Video, got {other:?}"),
776        }
777        // ... or by a private_data_specifier.
778        let mut b = pds_descriptor(PDS_AVSV);
779        b.extend_from_slice(&v);
780        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
781        assert!(matches!(items[1], AnyDescriptor::Other { tag: 0x40, .. }));
782
783        let a = [0xD3, 2, 6, 0x20];
784        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'A'];
785        b.extend_from_slice(&a);
786        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
787        match &items[1] {
788            AnyDescriptor::Other { tag: 0xD3, value } => {
789                assert!(value.downcast_ref::<Avs2AudioDescriptor>().is_some());
790            }
791            other => panic!("expected Avs2Audio, got {other:?}"),
792        }
793        let mut b = pds_descriptor(PDS_AVSA);
794        b.extend_from_slice(&a);
795        assert!(matches!(
796            reg.parse_loop(&b).collect::<Result<Vec<_>, _>>().unwrap()[1],
797            AnyDescriptor::Other { tag: 0xD3, .. }
798        ));
799        // Without scope tag 0xD3 stays Unknown.
800        let items: Vec<_> = reg.parse_loop(&a).collect::<Result<_, _>>().unwrap();
801        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD3, .. }));
802
803        // T/UWA 012.2 tag 62: PDS-agnostic opt-in.
804        let t = [62, 7, 0x22, 0x42, 0xAA, 0x4F, 9, 14, 9];
805        let items: Vec<_> = reg.parse_loop(&t).collect::<Result<_, _>>().unwrap();
806        match &items[0] {
807            AnyDescriptor::Other { tag: 62, value } => {
808                assert!(value.downcast_ref::<TuwaAvs3VideoDescriptor>().is_some());
809            }
810            other => panic!("expected TuwaAvs3Video, got {other:?}"),
811        }
812        let plain = DescriptorRegistry::new();
813        let items: Vec<_> = plain.parse_loop(&t).collect::<Result<_, _>>().unwrap();
814        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 62, .. }));
815    }
816
817    /// The specs say "the PMT shall contain" the 'AVSV'/'AVSA'
818    /// registration_descriptor without naming the loop, and put the AVS
819    /// descriptor in the ES_info loop: a registration in `program_info` must
820    /// scope the ES_info loops that follow (`avs_scope` + `parse_loop_in_scope`).
821    #[test]
822    fn program_info_registration_scopes_es_info_descriptors() {
823        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
824        use crate::descriptors::private_data_specifier::PDS_AVSV;
825        let program_info = [0x05, 4, b'A', b'V', b'S', b'V'];
826        // ES_info of the stream_type 0xD4 entry: just the AVS3 video descriptor.
827        let es_info = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
828        let mut reg = DescriptorRegistry::new();
829        reg.with_avs3_video();
830
831        // Per-loop scoping alone never sees the registration: Unknown.
832        let items: Vec<_> = reg.parse_loop(&es_info).collect::<Result<_, _>>().unwrap();
833        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD1, .. }));
834
835        // Walk program_info, carry the scope into the ES_info walk.
836        let mut pi = reg.parse_loop(&program_info);
837        assert_eq!(pi.avs_scope(), None, "no entry consumed yet");
838        for item in pi.by_ref() {
839            item.unwrap();
840        }
841        let scope = pi.avs_scope();
842        assert_eq!(scope, Some(PDS_AVSV));
843        let items: Vec<_> = reg
844            .parse_loop_in_scope(&es_info, scope)
845            .collect::<Result<_, _>>()
846            .unwrap();
847        match &items[0] {
848            AnyDescriptor::Other { tag: 0xD1, value } => {
849                assert!(value.downcast_ref::<Avs3VideoDescriptor>().is_some());
850            }
851            other => panic!("expected Avs3Video via inherited scope, got {other:?}"),
852        }
853        // A non-AVS program_info registration opens no scope to inherit.
854        let hevc = [0x05, 4, b'H', b'E', b'V', b'C'];
855        let mut pi = reg.parse_loop(&hevc);
856        for item in pi.by_ref() {
857            item.unwrap();
858        }
859        assert_eq!(pi.avs_scope(), None);
860    }
861
862    /// AVS3_video_descriptor (tag 0xD1): scoped by a 'AVSV' private_data_specifier
863    /// (TS 101 154) or a 'AVSV' registration_descriptor (T/AI 109.6 / GY/T 420).
864    #[test]
865    fn avs3_video_descriptor_scopes() {
866        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
867        use crate::descriptors::private_data_specifier::PDS_AVSV;
868        let desc = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
869        let mut reg = DescriptorRegistry::new();
870        reg.with_avs3_video();
871        let check = |bytes: &[u8], idx: usize| {
872            let items: Vec<_> = reg.parse_loop(bytes).collect::<Result<_, _>>().unwrap();
873            match &items[idx] {
874                AnyDescriptor::Other { tag, value } => {
875                    assert_eq!(*tag, 0xD1);
876                    assert_eq!(
877                        value
878                            .downcast_ref::<Avs3VideoDescriptor>()
879                            .unwrap()
880                            .profile_id,
881                        0x22
882                    );
883                }
884                other => panic!("expected Other, got {other:?}"),
885            }
886        };
887        let mut pds = pds_descriptor(PDS_AVSV);
888        pds.extend_from_slice(&desc);
889        check(&pds, 1);
890        let mut registration = vec![0x05, 4, b'A', b'V', b'S', b'V'];
891        registration.extend_from_slice(&desc);
892        check(&registration, 1);
893        // No scope, or a different registration ('AC-3'), leaves it Unknown.
894        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
895        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD1, .. }));
896        let mut other = vec![0x05, 4, b'A', b'C', b'-', b'3'];
897        other.extend_from_slice(&desc);
898        let items: Vec<_> = reg.parse_loop(&other).collect::<Result<_, _>>().unwrap();
899        assert!(matches!(items[1], AnyDescriptor::Unknown { tag: 0xD1, .. }));
900        // The audio descriptor also accepts the 'AVSA' registration form.
901        let adesc = [0xD2, 2, 0x72, 0x45];
902        let mut areg = DescriptorRegistry::new();
903        areg.with_avs3_audio();
904        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'A'];
905        b.extend_from_slice(&adesc);
906        let items: Vec<_> = areg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
907        assert!(matches!(items[1], AnyDescriptor::Other { tag: 0xD2, .. }));
908    }
909
910    /// T/AI 109.7 clause 8.1.3.4 + TS 101 154 V2.10.1 4.1.8.35: tag 0xD2 is the
911    /// AVS3_audio_descriptor only under private_data_specifier 'AVSA'.
912    #[test]
913    fn avs3_audio_descriptor_is_pds_scoped() {
914        use crate::descriptors::avs3_audio::Avs3AudioDescriptor;
915        use crate::descriptors::private_data_specifier::PDS_AVSA;
916        let desc = [0xD2, 6, 0x03, 0xAA, 0x95, 0x04, 0x00, 0x6A];
917        let mut reg = DescriptorRegistry::new();
918        reg.with_avs3_audio();
919
920        let mut bytes = Vec::new();
921        bytes.extend_from_slice(&pds_descriptor(PDS_AVSA));
922        bytes.extend_from_slice(&desc);
923        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
924        match &items[1] {
925            AnyDescriptor::Other { tag, value } => {
926                assert_eq!(*tag, 0xD2);
927                let d = value.downcast_ref::<Avs3AudioDescriptor>().unwrap();
928                assert_eq!(d.sampling_frequency_index, 3);
929            }
930            other => panic!("expected Other (Avs3AudioDescriptor), got {other:?}"),
931        }
932
933        // Without the 'AVSA' specifier (or under another) the tag stays Unknown.
934        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
935        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD2, .. }));
936        let mut other = Vec::new();
937        other.extend_from_slice(&pds_descriptor(PDS_NORDIG));
938        other.extend_from_slice(&desc);
939        let items: Vec<_> = reg.parse_loop(&other).collect::<Result<_, _>>().unwrap();
940        assert!(matches!(items[1], AnyDescriptor::Unknown { tag: 0xD2, .. }));
941    }
942
943    /// GY/T 270-2013 Table 8: tag 0x86 is the China caption_service_descriptor
944    /// once `with_china_caption_service` is enabled, and stays Unknown otherwise.
945    #[test]
946    fn china_caption_service_is_opt_in() {
947        use crate::descriptors::china_caption_service::ChinaCaptionServiceDescriptor;
948        let desc = [
949            0x86, 9, 0xE1, b'c', b'h', b'i', 0xC1, 0xC2, 0xFF, 0xE1, 0x00,
950        ];
951        let plain = DescriptorRegistry::new();
952        let items: Vec<_> = plain.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
953        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0x86, .. }));
954        let mut reg = DescriptorRegistry::new();
955        reg.with_china_caption_service();
956        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
957        match &items[0] {
958            AnyDescriptor::Other { tag, value } => {
959                assert_eq!(*tag, 0x86);
960                let d = value
961                    .downcast_ref::<ChinaCaptionServiceDescriptor>()
962                    .unwrap();
963                assert_eq!(d.services.len(), 1);
964            }
965            other => panic!("expected Other, got {other:?}"),
966        }
967    }
968
969    #[test]
970    fn atsc_caption_service_is_opt_in() {
971        use crate::descriptors::atsc_caption_service::AtscCaptionServiceDescriptor;
972        let desc = [0x86, 7, 0xE1, b'e', b'n', b'g', 0xC1, 0x7F, 0xFF];
973        let plain = DescriptorRegistry::new();
974        let items: Vec<_> = plain.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
975        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0x86, .. }));
976        let mut reg = DescriptorRegistry::new();
977        reg.with_atsc_caption_service();
978        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
979        match &items[0] {
980            AnyDescriptor::Other { value, .. } => {
981                let d = value
982                    .downcast_ref::<AtscCaptionServiceDescriptor>()
983                    .unwrap();
984                assert_eq!(d.services.len(), 1);
985            }
986            other => panic!("expected Other, got {other:?}"),
987        }
988    }
989
990    #[test]
991    fn pds_agnostic_matches_without_pds() {
992        let mut reg = DescriptorRegistry::new();
993        reg.register::<PdsAgnostic>();
994
995        let bytes = [0x84, 0x01, 0xDD];
996        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
997        assert_eq!(items.len(), 1);
998        match &items[0] {
999            AnyDescriptor::Other { tag, value } => {
1000                assert_eq!(*tag, 0x84);
1001                let c = value.downcast_ref::<PdsAgnostic>().unwrap();
1002                assert_eq!(c.z, 0xDD);
1003            }
1004            other => panic!("expected Other, got {other:?}"),
1005        }
1006    }
1007
1008    #[test]
1009    fn pds_scoped_takes_precedence_over_agnostic() {
1010        // Two parsers compete for the SAME tag 0x83: one PDS-agnostic, one
1011        // scoped to EACEM. With no PDS active the agnostic one wins; once an
1012        // EACEM private_data_specifier appears, the scoped one takes over.
1013        #[derive(Debug, PartialEq, Eq)]
1014        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1015        struct Agnostic83 {
1016            a: u8,
1017        }
1018
1019        impl<'a> broadcast_common::Parse<'a> for Agnostic83 {
1020            type Error = crate::error::Error;
1021            fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
1022                if bytes.len() < 3 {
1023                    return Err(crate::error::Error::BufferTooShort(
1024                        broadcast_common::error::BufferTooShort {
1025                            need: 3,
1026                            have: bytes.len(),
1027                            what: "Agnostic83",
1028                        },
1029                    ));
1030                }
1031                Ok(Self { a: bytes[2] })
1032            }
1033        }
1034
1035        impl DescriptorDef<'_> for Agnostic83 {
1036            const TAG: u8 = 0x83;
1037            const NAME: &'static str = "AGNOSTIC_83";
1038        }
1039
1040        let mut reg = DescriptorRegistry::new();
1041        reg.register::<Agnostic83>();
1042        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
1043
1044        // No PDS → agnostic wins
1045        let items: Vec<_> = reg
1046            .parse_loop(&[0x83, 0x01, 0xEE])
1047            .collect::<Result<_, _>>()
1048            .unwrap();
1049        match &items[0] {
1050            AnyDescriptor::Other { value, .. } => {
1051                assert!(value.downcast_ref::<Agnostic83>().is_some());
1052                assert!(value.downcast_ref::<PdsEacem>().is_none());
1053            }
1054            other => panic!("expected Other, got {other:?}"),
1055        }
1056
1057        // With PDS EACEM → PDS-scoped wins
1058        let mut bytes = Vec::new();
1059        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1060        bytes.extend_from_slice(&[0x83, 0x01, 0xFF]);
1061        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1062        match &items[1] {
1063            AnyDescriptor::Other { value, .. } => {
1064                assert!(value.downcast_ref::<PdsEacem>().is_some());
1065                assert!(value.downcast_ref::<Agnostic83>().is_none());
1066            }
1067            other => panic!("expected Other, got {other:?}"),
1068        }
1069    }
1070
1071    fn logical_channel_descriptor(service_id: u16, visible: bool, lcn: u16) -> Vec<u8> {
1072        // One entry: tag=0x83, len=4, entry(4 bytes)
1073        let mut v = vec![crate::descriptors::logical_channel::TAG, 4];
1074        v.extend_from_slice(&service_id.to_be_bytes());
1075        let flags = (u8::from(visible) << 7) | ((lcn >> 8) as u8 & 0x03);
1076        v.push(flags);
1077        v.push((lcn & 0xFF) as u8);
1078        v
1079    }
1080
1081    #[test]
1082    fn logical_channel_pds_scoped_matches_correct_pds() {
1083        let mut reg = DescriptorRegistry::new();
1084        reg.with_logical_channel_for_pds(PDS_EACEM);
1085
1086        let mut bytes = Vec::new();
1087        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1088        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
1089
1090        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1091        assert_eq!(items.len(), 2);
1092        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
1093        match &items[1] {
1094            AnyDescriptor::LogicalChannel(lc) => {
1095                assert_eq!(lc.entries.len(), 1);
1096                assert_eq!(lc.entries[0].service_id, 0x1234);
1097                assert!(lc.entries[0].visible_service);
1098                assert_eq!(lc.entries[0].logical_channel_number, 101);
1099            }
1100            other => panic!("expected LogicalChannel, got {other:?}"),
1101        }
1102    }
1103
1104    #[test]
1105    fn logical_channel_pds_scoped_rejects_no_pds() {
1106        let mut reg = DescriptorRegistry::new();
1107        reg.with_logical_channel_for_pds(PDS_EACEM);
1108
1109        let bytes = logical_channel_descriptor(0x1234, true, 101);
1110        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1111        assert_eq!(items.len(), 1);
1112        assert!(matches!(
1113            &items[0],
1114            AnyDescriptor::Unknown { tag: 0x83, .. }
1115        ));
1116    }
1117
1118    #[test]
1119    fn logical_channel_pds_scoped_rejects_wrong_pds() {
1120        let mut reg = DescriptorRegistry::new();
1121        reg.with_logical_channel_for_pds(PDS_EACEM);
1122
1123        let mut bytes = Vec::new();
1124        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
1125        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
1126
1127        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1128        assert_eq!(items.len(), 2);
1129        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
1130        assert!(matches!(
1131            &items[1],
1132            AnyDescriptor::Unknown { tag: 0x83, .. }
1133        ));
1134    }
1135
1136    #[test]
1137    fn logical_channel_pds_scoped_multiple_pds() {
1138        let mut reg = DescriptorRegistry::new();
1139        reg.with_logical_channel_for_pds(PDS_EACEM);
1140        reg.with_logical_channel_for_pds(PDS_NORDIG);
1141
1142        // EACEM → LogicalChannel
1143        let mut bytes = Vec::new();
1144        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1145        bytes.extend_from_slice(&logical_channel_descriptor(0x0001, true, 1));
1146        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1147        assert!(matches!(&items[1], AnyDescriptor::LogicalChannel(_)));
1148
1149        // NorDig → LogicalChannel
1150        let mut bytes2 = Vec::new();
1151        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
1152        bytes2.extend_from_slice(&logical_channel_descriptor(0x0002, false, 2));
1153        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
1154        assert!(matches!(&items2[1], AnyDescriptor::LogicalChannel(_)));
1155    }
1156
1157    /// Same program_info-registration layout through `iter_with_extensions`.
1158    #[test]
1159    fn program_info_registration_scopes_es_info_with_extensions() {
1160        use crate::descriptors::any::DescriptorLoop;
1161        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
1162        use crate::descriptors::extension::registry::ExtensionRegistry;
1163        use crate::descriptors::private_data_specifier::PDS_AVSV;
1164        let program_info = [0x05, 4, b'A', b'V', b'S', b'V'];
1165        let es_info = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
1166        let mut reg = DescriptorRegistry::new();
1167        reg.with_avs3_video();
1168        let ext = ExtensionRegistry::new();
1169
1170        let items: Vec<_> = DescriptorLoop::new(&es_info)
1171            .iter_with_extensions(&reg, &ext)
1172            .collect::<Result<_, _>>()
1173            .unwrap();
1174        assert!(matches!(
1175            &items[0],
1176            ExtIterItem::Descriptor(AnyDescriptor::Unknown { tag: 0xD1, .. })
1177        ));
1178
1179        let mut pi = DescriptorLoop::new(&program_info).iter_with_extensions(&reg, &ext);
1180        for item in pi.by_ref() {
1181            item.unwrap();
1182        }
1183        let scope = pi.avs_scope();
1184        assert_eq!(scope, Some(PDS_AVSV));
1185        let items: Vec<_> = DescriptorLoop::new(&es_info)
1186            .iter_with_extensions_in_scope(&reg, &ext, scope)
1187            .collect::<Result<_, _>>()
1188            .unwrap();
1189        match &items[0] {
1190            ExtIterItem::Descriptor(AnyDescriptor::Other { tag: 0xD1, value }) => {
1191                assert!(value.downcast_ref::<Avs3VideoDescriptor>().is_some());
1192            }
1193            other => panic!("expected Avs3Video via inherited scope, got {other:?}"),
1194        }
1195    }
1196
1197    #[test]
1198    fn iter_with_extensions_surfaces_custom_extension() {
1199        use crate::descriptors::any::{AnyDescriptor, DescriptorLoop};
1200        use crate::descriptors::extension::registry::ExtensionRegistry;
1201
1202        #[derive(Debug, PartialEq, Eq)]
1203        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1204        struct MyCustomExt {
1205            payload: Vec<u8>,
1206        }
1207
1208        impl<'a> broadcast_common::Parse<'a> for MyCustomExt {
1209            type Error = crate::error::Error;
1210            fn parse(sel: &'a [u8]) -> crate::Result<Self> {
1211                Ok(Self {
1212                    payload: sel.to_vec(),
1213                })
1214            }
1215        }
1216
1217        impl crate::descriptors::extension::ExtensionBodyDef<'_> for MyCustomExt {
1218            const TAG_EXTENSION: u8 = 0x42;
1219            const NAME: &'static str = "MY_CUSTOM_EXT";
1220        }
1221
1222        let mut ext_reg = ExtensionRegistry::new();
1223        ext_reg.register::<MyCustomExt>();
1224
1225        let desc_reg = DescriptorRegistry::new();
1226
1227        // Build a descriptor loop with a short_event + a 0x7F extension with tag_extension 0x42
1228        let mut loop_bytes = vec![
1229            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
1230        ];
1231        // extension: tag=0x7F, length=3, tag_extension=0x42, selector=[0xAB, 0xCD]
1232        loop_bytes.extend_from_slice(&[0x7F, 0x03, 0x42, 0xAB, 0xCD]);
1233
1234        let dl = DescriptorLoop::new(&loop_bytes);
1235        let items: Vec<_> = dl
1236            .iter_with_extensions(&desc_reg, &ext_reg)
1237            .collect::<Result<_, _>>()
1238            .unwrap();
1239        assert_eq!(items.len(), 2);
1240        // First item is a regular descriptor
1241        assert!(matches!(
1242            &items[0],
1243            ExtIterItem::Descriptor(AnyDescriptor::ShortEvent(_))
1244        ));
1245        // Second item surfaces the custom extension body (not Raw!)
1246        match &items[1] {
1247            ExtIterItem::CustomExtension {
1248                tag_extension,
1249                value,
1250            } => {
1251                assert_eq!(*tag_extension, 0x42);
1252                let concrete = value.downcast_ref::<MyCustomExt>().unwrap();
1253                assert_eq!(concrete.payload, &[0xAB, 0xCD]);
1254            }
1255            other => panic!("expected CustomExtension, got {other:?}"),
1256        }
1257    }
1258}