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