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    /// Register the ATSC `caption_service_descriptor` (tag 0x86, ATSC A/65:2013
386    /// §6.9.2 Table 6.26). PDS-agnostic registration of tag 0x86: enable it for
387    /// ATSC streams only, and not together with
388    /// [`Self::with_china_caption_service`], which claims the same tag.
389    pub fn with_atsc_caption_service(&mut self) -> &mut Self {
390        self.register::<crate::descriptors::atsc_caption_service::AtscCaptionServiceDescriptor>()
391    }
392
393    /// Lazily walk a raw descriptor loop using this registry's configuration.
394    ///
395    /// Semantics mirror [`crate::descriptors::parse_loop`]: per-descriptor
396    /// parse errors yield `Err` and iteration continues; a truncated final
397    /// header or body yields one `Err` then fuses.
398    ///
399    /// A `private_data_specifier_descriptor` (tag `0x5F`) in the loop
400    /// automatically updates the iterator's PDS context, scoping subsequent
401    /// private-tag dispatch.
402    #[must_use]
403    pub fn parse_loop<'r, 'a>(&'r self, bytes: &'a [u8]) -> RegistryIter<'r, 'a> {
404        RegistryIter {
405            registry: self,
406            bytes,
407            pos: 0,
408            fused: false,
409            current_pds: None,
410        }
411    }
412
413    /// Like [`parse_loop`](Self::parse_loop) but starting inside the scope an
414    /// enclosing loop already opened (`inherited`, from
415    /// [`RegistryIter::avs_scope`]).
416    ///
417    /// T/AI 109.7 clause 8.1.3 and GY/T 420 clauses 7.2.2 / 7.3.3.1 / 7.4.3.1
418    /// put the `registration_descriptor` under "Program and program element
419    /// descriptors" and say only that "the PMT shall contain" it, without
420    /// naming the `program_info` or the `ES_info` loop, while the AVS
421    /// descriptors themselves sit in the ES_info loop (T/AI 109.7 clause
422    /// 8.1.3.4, GY/T 420 clause 7.3.1 d). A registration in the `program_info`
423    /// loop therefore has to scope the AVS descriptors of the elementary-stream
424    /// loops that follow: walk `program_info` with `parse_loop`, take the scope
425    /// with `avs_scope`, and walk each ES_info loop with this method.
426    #[must_use]
427    pub fn parse_loop_in_scope<'r, 'a>(
428        &'r self,
429        bytes: &'a [u8],
430        inherited: Option<u32>,
431    ) -> RegistryIter<'r, 'a> {
432        RegistryIter {
433            registry: self,
434            bytes,
435            pos: 0,
436            fused: false,
437            current_pds: inherited,
438        }
439    }
440}
441
442// ---------------------------------------------------------------------------
443// RegistryIter
444// ---------------------------------------------------------------------------
445
446/// Lazy iterator over a raw descriptor loop, driven by a [`DescriptorRegistry`].
447///
448/// Returned by [`DescriptorRegistry::parse_loop`].
449pub struct RegistryIter<'r, 'a> {
450    registry: &'r DescriptorRegistry,
451    bytes: &'a [u8],
452    pos: usize,
453    fused: bool,
454    current_pds: Option<u32>,
455}
456
457/// Shared precedence ladder for both [`RegistryIter`] and [`ExtRegistryIter`].
458///
459/// Returns the [`AnyDescriptor`] that results from applying the six-step
460/// precedence: PDS-scoped custom → PDS-agnostic custom → PDS-scoped
461/// logical-channel → logical-channel opt-in → built-in dispatch → Unknown.
462pub(crate) fn dispatch_entry<'a>(
463    registry: &DescriptorRegistry,
464    current_pds: Option<u32>,
465    tag: u8,
466    full: &'a [u8],
467) -> crate::Result<AnyDescriptor<'a>> {
468    if let Some(pds) = current_pds
469        && let Some(parse_fn) = registry.custom.get(&(Some(pds), tag))
470    {
471        return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
472    }
473    if let Some(parse_fn) = registry.custom.get(&(None, tag)) {
474        return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
475    }
476    if let Some(pds) = current_pds
477        && tag == crate::descriptors::logical_channel::TAG
478        && registry.logical_channel_pds.contains(&pds)
479    {
480        use broadcast_common::Parse;
481        return crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
482            .map(AnyDescriptor::LogicalChannel);
483    }
484    if registry.logical_channel && tag == crate::descriptors::logical_channel::TAG {
485        use broadcast_common::Parse;
486        return crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
487            .map(AnyDescriptor::LogicalChannel);
488    }
489    if let Some(res) = AnyDescriptor::dispatch(tag, full) {
490        return res;
491    }
492    Ok(AnyDescriptor::Unknown {
493        tag,
494        body: &full[2..],
495    })
496}
497
498fn update_pds(current: &mut Option<u32>, tag: u8, full: &[u8]) {
499    // The Chinese AVS3 documents (T/AI 109.6/109.7, GY/T 420) scope their
500    // descriptors with a registration_descriptor whose format_identifier is the
501    // same four ASCII bytes as the DVB private_data_specifier ('AVSV'/'AVSA'),
502    // so such a registration opens the same scope. Any other registration
503    // leaves the context untouched.
504    if tag == crate::descriptors::registration::TAG {
505        use broadcast_common::Parse;
506        if let Ok(reg) = crate::descriptors::registration::RegistrationDescriptor::parse(full) {
507            let id = u32::from_be_bytes(reg.format_identifier);
508            if id == crate::descriptors::PDS_AVSA || id == crate::descriptors::PDS_AVSV {
509                *current = Some(id);
510            }
511        }
512    }
513    if tag == crate::descriptors::private_data_specifier::TAG {
514        use broadcast_common::Parse;
515        if let Ok(pds) =
516            crate::descriptors::private_data_specifier::PrivateDataSpecifierDescriptor::parse(full)
517        {
518            *current = Some(pds.private_data_specifier);
519        }
520    }
521}
522
523impl RegistryIter<'_, '_> {
524    /// The AVS scope ('AVSV' / 'AVSA') the entries walked so far opened, via a
525    /// `private_data_specifier_descriptor` or a `registration_descriptor`
526    /// `format_identifier`; `None` when no AVS scope is open. Feed it to
527    /// [`DescriptorRegistry::parse_loop_in_scope`] for the elementary-stream
528    /// loops that follow a `program_info` loop.
529    #[must_use]
530    pub fn avs_scope(&self) -> Option<u32> {
531        self.current_pds
532            .filter(|p| *p == crate::descriptors::PDS_AVSA || *p == crate::descriptors::PDS_AVSV)
533    }
534}
535
536impl<'a> Iterator for RegistryIter<'_, 'a> {
537    type Item = crate::Result<AnyDescriptor<'a>>;
538
539    fn next(&mut self) -> Option<Self::Item> {
540        let (tag, full) = match crate::descriptors::any::next_loop_entry(
541            self.bytes,
542            &mut self.pos,
543            &mut self.fused,
544        )? {
545            Ok(v) => v,
546            Err(e) => return Some(Err(e)),
547        };
548
549        update_pds(&mut self.current_pds, tag, full);
550
551        Some(dispatch_entry(self.registry, self.current_pds, tag, full))
552    }
553}
554
555impl core::iter::FusedIterator for RegistryIter<'_, '_> {}
556
557// ---------------------------------------------------------------------------
558// ExtIterItem — item type for iter_with_extensions
559// ---------------------------------------------------------------------------
560
561/// Item produced by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
562///
563/// Extends [`AnyDescriptor`] with a third arm for custom-registered extension
564/// bodies whose `descriptor_tag_extension` is recognised by an
565/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
566/// The `value` field can be downcast to the concrete type via
567/// `downcast_ref` on the value (see [`ExtensionObject`](super::extension::registry::ExtensionObject))
568#[derive(Debug)]
569#[cfg_attr(feature = "serde", derive(serde::Serialize))]
570#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
571#[non_exhaustive]
572pub enum ExtIterItem<'a> {
573    /// A regular descriptor (including built-in extension descriptors).
574    Descriptor(AnyDescriptor<'a>),
575    /// A custom-registered extension body (tag `0x7F`, known `descriptor_tag_extension`).
576    CustomExtension {
577        /// The `descriptor_tag_extension` byte.
578        tag_extension: u8,
579        /// The parsed, type-erased extension body value. Call `downcast_ref`
580        /// on it (see [`ExtensionObject`](super::extension::registry::ExtensionObject)) to recover the concrete type.
581        #[cfg_attr(
582            feature = "serde",
583            serde(serialize_with = "super::extension::registry::serialize_erased")
584        )]
585        value: Box<dyn super::extension::registry::ExtensionObject>,
586    },
587}
588
589// ---------------------------------------------------------------------------
590// ExtRegistryIter — iterator for iter_with_extensions
591// ---------------------------------------------------------------------------
592
593/// Lazy iterator over a raw descriptor loop, driven by both a
594/// [`DescriptorRegistry`] and an
595/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
596///
597/// Returned by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
598pub struct ExtRegistryIter<'r, 'a> {
599    desc_reg: &'r DescriptorRegistry,
600    ext_reg: &'r super::extension::registry::ExtensionRegistry,
601    bytes: &'a [u8],
602    pos: usize,
603    fused: bool,
604    current_pds: Option<u32>,
605}
606
607impl<'r, 'a> ExtRegistryIter<'r, 'a> {
608    pub(crate) fn new(
609        desc_reg: &'r DescriptorRegistry,
610        ext_reg: &'r super::extension::registry::ExtensionRegistry,
611        bytes: &'a [u8],
612        inherited: Option<u32>,
613    ) -> Self {
614        Self {
615            desc_reg,
616            ext_reg,
617            bytes,
618            pos: 0,
619            fused: false,
620            current_pds: inherited,
621        }
622    }
623
624    /// The AVS scope ('AVSV' / 'AVSA') the entries walked so far opened; see
625    /// [`RegistryIter::avs_scope`].
626    #[must_use]
627    pub fn avs_scope(&self) -> Option<u32> {
628        self.current_pds
629            .filter(|p| *p == crate::descriptors::PDS_AVSA || *p == crate::descriptors::PDS_AVSV)
630    }
631}
632
633impl<'a> Iterator for ExtRegistryIter<'_, 'a> {
634    type Item = crate::Result<ExtIterItem<'a>>;
635
636    fn next(&mut self) -> Option<Self::Item> {
637        let (tag, full) = match crate::descriptors::any::next_loop_entry(
638            self.bytes,
639            &mut self.pos,
640            &mut self.fused,
641        )? {
642            Ok(v) => v,
643            Err(e) => return Some(Err(e)),
644        };
645
646        update_pds(&mut self.current_pds, tag, full);
647
648        let len = full.len() - 2;
649        if tag == crate::descriptors::extension::TAG && len >= 1 {
650            let tag_extension = full[2];
651            if self.ext_reg.has_custom(tag_extension) {
652                return Some(match self.ext_reg.parse_body(tag_extension, &full[3..]) {
653                    Ok(super::extension::registry::RegisteredExtension::Custom {
654                        tag_extension,
655                        value,
656                    }) => Ok(ExtIterItem::CustomExtension {
657                        tag_extension,
658                        value,
659                    }),
660                    Ok(super::extension::registry::RegisteredExtension::Builtin(d)) => {
661                        Ok(ExtIterItem::Descriptor(AnyDescriptor::Extension(d)))
662                    }
663                    Err(e) => Err(e),
664                });
665            }
666        }
667
668        Some(
669            dispatch_entry(self.desc_reg, self.current_pds, tag, full).map(ExtIterItem::Descriptor),
670        )
671    }
672}
673
674impl core::iter::FusedIterator for ExtRegistryIter<'_, '_> {}
675
676#[cfg(test)]
677mod tests {
678    use super::*;
679    use crate::descriptors::private_data_specifier;
680    use crate::descriptors::private_data_specifier::{PDS_EACEM, PDS_NORDIG};
681    use crate::traits::DescriptorDef;
682
683    #[test]
684    fn pds_constants_match_the_register() {
685        // Pin the public consts to the vendored TS 101 162 register
686        // (registries/tsPDS.names) so they can't silently drift.
687        assert_eq!(
688            private_data_specifier::private_data_specifier_name(PDS_EACEM),
689            Some("EACEM/EICTA")
690        );
691        assert_eq!(
692            private_data_specifier::private_data_specifier_name(PDS_NORDIG),
693            Some("NorDig")
694        );
695    }
696
697    #[derive(Debug, PartialEq, Eq)]
698    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
699    struct PdsEacem {
700        v: u8,
701    }
702
703    impl<'a> broadcast_common::Parse<'a> for PdsEacem {
704        type Error = crate::error::Error;
705        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
706            if bytes.len() < 3 {
707                return Err(crate::error::Error::BufferTooShort {
708                    need: 3,
709                    have: bytes.len(),
710                    what: "PdsEacem",
711                });
712            }
713            Ok(Self { v: bytes[2] })
714        }
715    }
716
717    impl DescriptorDef<'_> for PdsEacem {
718        const TAG: u8 = 0x83;
719        const NAME: &'static str = "PDS_EACEM";
720    }
721
722    #[derive(Debug, PartialEq, Eq)]
723    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
724    struct PdsNordig {
725        w: u8,
726    }
727
728    impl<'a> broadcast_common::Parse<'a> for PdsNordig {
729        type Error = crate::error::Error;
730        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
731            if bytes.len() < 3 {
732                return Err(crate::error::Error::BufferTooShort {
733                    need: 3,
734                    have: bytes.len(),
735                    what: "PdsNordig",
736                });
737            }
738            Ok(Self { w: bytes[2] })
739        }
740    }
741
742    impl DescriptorDef<'_> for PdsNordig {
743        const TAG: u8 = 0x83;
744        const NAME: &'static str = "PDS_NORDIG";
745    }
746
747    #[derive(Debug, PartialEq, Eq)]
748    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
749    struct PdsAgnostic {
750        z: u8,
751    }
752
753    impl<'a> broadcast_common::Parse<'a> for PdsAgnostic {
754        type Error = crate::error::Error;
755        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
756            if bytes.len() < 3 {
757                return Err(crate::error::Error::BufferTooShort {
758                    need: 3,
759                    have: bytes.len(),
760                    what: "PdsAgnostic",
761                });
762            }
763            Ok(Self { z: bytes[2] })
764        }
765    }
766
767    impl DescriptorDef<'_> for PdsAgnostic {
768        const TAG: u8 = 0x84;
769        const NAME: &'static str = "PDS_AGNOSTIC";
770    }
771
772    fn pds_descriptor(pds: u32) -> Vec<u8> {
773        let mut v = vec![private_data_specifier::TAG, 4];
774        v.extend_from_slice(&pds.to_be_bytes());
775        v
776    }
777
778    #[test]
779    fn pds_scoped_same_tag_resolves_by_pds() {
780        let mut reg = DescriptorRegistry::new();
781        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
782        reg.register_for_pds::<PdsNordig>(PDS_NORDIG);
783
784        let mut bytes = Vec::new();
785        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
786        bytes.extend_from_slice(&[0x83, 0x01, 0xAA]);
787
788        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
789        assert_eq!(items.len(), 2);
790        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
791        match &items[1] {
792            AnyDescriptor::Other { tag, value } => {
793                assert_eq!(*tag, 0x83);
794                let c = value.downcast_ref::<PdsEacem>().unwrap();
795                assert_eq!(c.v, 0xAA);
796            }
797            other => panic!("expected Other (PdsEacem), got {other:?}"),
798        }
799
800        let mut bytes2 = Vec::new();
801        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
802        bytes2.extend_from_slice(&[0x83, 0x01, 0xBB]);
803
804        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
805        match &items2[1] {
806            AnyDescriptor::Other { tag, value } => {
807                assert_eq!(*tag, 0x83);
808                let c = value.downcast_ref::<PdsNordig>().unwrap();
809                assert_eq!(c.w, 0xBB);
810            }
811            other => panic!("expected Other (PdsNordig), got {other:?}"),
812        }
813    }
814
815    #[test]
816    fn pds_scoped_does_not_match_wrong_pds() {
817        let mut reg = DescriptorRegistry::new();
818        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
819
820        let mut bytes = Vec::new();
821        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
822        bytes.extend_from_slice(&[0x83, 0x01, 0xCC]);
823
824        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
825        assert_eq!(items.len(), 2);
826        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
827        match &items[1] {
828            AnyDescriptor::Unknown { tag, .. } => assert_eq!(*tag, 0x83),
829            other => panic!("expected Unknown (wrong PDS), got {other:?}"),
830        }
831    }
832
833    /// AVS2 descriptors (GY/T 420 / T/AI 109.7) and the T/UWA 012.2 tag-62
834    /// descriptor are opt-in and scoped like the AVS3 ones.
835    #[test]
836    fn avs2_and_tuwa_descriptors_are_scoped() {
837        use crate::descriptors::avs2_audio::Avs2AudioDescriptor;
838        use crate::descriptors::avs2_video::Avs2VideoDescriptor;
839        use crate::descriptors::private_data_specifier::{PDS_AVSA, PDS_AVSV};
840        use crate::descriptors::tuwa_avs3_video::TuwaAvs3VideoDescriptor;
841        let mut reg = DescriptorRegistry::new();
842        reg.with_avs2_video()
843            .with_avs2_audio()
844            .with_tuwa_avs3_video();
845
846        let v = [0x40, 5, 0x20, 0x42, 0x00, 0xA9, 0x5F];
847        // Unscoped tag 0x40 is still the DVB network_name_descriptor.
848        let items: Vec<_> = reg.parse_loop(&v).collect::<Result<_, _>>().unwrap();
849        assert!(matches!(items[0], AnyDescriptor::NetworkName(_)));
850        // Scoped by an 'AVSV' registration_descriptor it is the AVS2 descriptor.
851        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'V'];
852        b.extend_from_slice(&v);
853        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
854        match &items[1] {
855            AnyDescriptor::Other { tag: 0x40, value } => {
856                assert!(value.downcast_ref::<Avs2VideoDescriptor>().is_some());
857            }
858            other => panic!("expected Avs2Video, got {other:?}"),
859        }
860        // ... or by a private_data_specifier.
861        let mut b = pds_descriptor(PDS_AVSV);
862        b.extend_from_slice(&v);
863        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
864        assert!(matches!(items[1], AnyDescriptor::Other { tag: 0x40, .. }));
865
866        let a = [0xD3, 2, 6, 0x20];
867        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'A'];
868        b.extend_from_slice(&a);
869        let items: Vec<_> = reg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
870        match &items[1] {
871            AnyDescriptor::Other { tag: 0xD3, value } => {
872                assert!(value.downcast_ref::<Avs2AudioDescriptor>().is_some());
873            }
874            other => panic!("expected Avs2Audio, got {other:?}"),
875        }
876        let mut b = pds_descriptor(PDS_AVSA);
877        b.extend_from_slice(&a);
878        assert!(matches!(
879            reg.parse_loop(&b).collect::<Result<Vec<_>, _>>().unwrap()[1],
880            AnyDescriptor::Other { tag: 0xD3, .. }
881        ));
882        // Without scope tag 0xD3 stays Unknown.
883        let items: Vec<_> = reg.parse_loop(&a).collect::<Result<_, _>>().unwrap();
884        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD3, .. }));
885
886        // T/UWA 012.2 tag 62: PDS-agnostic opt-in.
887        let t = [62, 7, 0x22, 0x42, 0xAA, 0x4F, 9, 14, 9];
888        let items: Vec<_> = reg.parse_loop(&t).collect::<Result<_, _>>().unwrap();
889        match &items[0] {
890            AnyDescriptor::Other { tag: 62, value } => {
891                assert!(value.downcast_ref::<TuwaAvs3VideoDescriptor>().is_some());
892            }
893            other => panic!("expected TuwaAvs3Video, got {other:?}"),
894        }
895        let plain = DescriptorRegistry::new();
896        let items: Vec<_> = plain.parse_loop(&t).collect::<Result<_, _>>().unwrap();
897        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 62, .. }));
898    }
899
900    /// The specs say "the PMT shall contain" the 'AVSV'/'AVSA'
901    /// registration_descriptor without naming the loop, and put the AVS
902    /// descriptor in the ES_info loop: a registration in `program_info` must
903    /// scope the ES_info loops that follow (`avs_scope` + `parse_loop_in_scope`).
904    #[test]
905    fn program_info_registration_scopes_es_info_descriptors() {
906        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
907        use crate::descriptors::private_data_specifier::PDS_AVSV;
908        let program_info = [0x05, 4, b'A', b'V', b'S', b'V'];
909        // ES_info of the stream_type 0xD4 entry: just the AVS3 video descriptor.
910        let es_info = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
911        let mut reg = DescriptorRegistry::new();
912        reg.with_avs3_video();
913
914        // Per-loop scoping alone never sees the registration: Unknown.
915        let items: Vec<_> = reg.parse_loop(&es_info).collect::<Result<_, _>>().unwrap();
916        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD1, .. }));
917
918        // Walk program_info, carry the scope into the ES_info walk.
919        let mut pi = reg.parse_loop(&program_info);
920        assert_eq!(pi.avs_scope(), None, "no entry consumed yet");
921        for item in pi.by_ref() {
922            item.unwrap();
923        }
924        let scope = pi.avs_scope();
925        assert_eq!(scope, Some(PDS_AVSV));
926        let items: Vec<_> = reg
927            .parse_loop_in_scope(&es_info, scope)
928            .collect::<Result<_, _>>()
929            .unwrap();
930        match &items[0] {
931            AnyDescriptor::Other { tag: 0xD1, value } => {
932                assert!(value.downcast_ref::<Avs3VideoDescriptor>().is_some());
933            }
934            other => panic!("expected Avs3Video via inherited scope, got {other:?}"),
935        }
936        // A non-AVS program_info registration opens no scope to inherit.
937        let hevc = [0x05, 4, b'H', b'E', b'V', b'C'];
938        let mut pi = reg.parse_loop(&hevc);
939        for item in pi.by_ref() {
940            item.unwrap();
941        }
942        assert_eq!(pi.avs_scope(), None);
943    }
944
945    /// AVS3_video_descriptor (tag 0xD1): scoped by a 'AVSV' private_data_specifier
946    /// (TS 101 154) or a 'AVSV' registration_descriptor (T/AI 109.6 / GY/T 420).
947    #[test]
948    fn avs3_video_descriptor_scopes() {
949        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
950        use crate::descriptors::private_data_specifier::PDS_AVSV;
951        let desc = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
952        let mut reg = DescriptorRegistry::new();
953        reg.with_avs3_video();
954        let check = |bytes: &[u8], idx: usize| {
955            let items: Vec<_> = reg.parse_loop(bytes).collect::<Result<_, _>>().unwrap();
956            match &items[idx] {
957                AnyDescriptor::Other { tag, value } => {
958                    assert_eq!(*tag, 0xD1);
959                    assert_eq!(
960                        value
961                            .downcast_ref::<Avs3VideoDescriptor>()
962                            .unwrap()
963                            .profile_id,
964                        0x22
965                    );
966                }
967                other => panic!("expected Other, got {other:?}"),
968            }
969        };
970        let mut pds = pds_descriptor(PDS_AVSV);
971        pds.extend_from_slice(&desc);
972        check(&pds, 1);
973        let mut registration = vec![0x05, 4, b'A', b'V', b'S', b'V'];
974        registration.extend_from_slice(&desc);
975        check(&registration, 1);
976        // No scope, or a different registration ('AC-3'), leaves it Unknown.
977        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
978        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD1, .. }));
979        let mut other = vec![0x05, 4, b'A', b'C', b'-', b'3'];
980        other.extend_from_slice(&desc);
981        let items: Vec<_> = reg.parse_loop(&other).collect::<Result<_, _>>().unwrap();
982        assert!(matches!(items[1], AnyDescriptor::Unknown { tag: 0xD1, .. }));
983        // The audio descriptor also accepts the 'AVSA' registration form.
984        let adesc = [0xD2, 2, 0x72, 0x45];
985        let mut areg = DescriptorRegistry::new();
986        areg.with_avs3_audio();
987        let mut b = vec![0x05, 4, b'A', b'V', b'S', b'A'];
988        b.extend_from_slice(&adesc);
989        let items: Vec<_> = areg.parse_loop(&b).collect::<Result<_, _>>().unwrap();
990        assert!(matches!(items[1], AnyDescriptor::Other { tag: 0xD2, .. }));
991    }
992
993    /// T/AI 109.7 clause 8.1.3.4 + TS 101 154 V2.10.1 4.1.8.35: tag 0xD2 is the
994    /// AVS3_audio_descriptor only under private_data_specifier 'AVSA'.
995    #[test]
996    fn avs3_audio_descriptor_is_pds_scoped() {
997        use crate::descriptors::avs3_audio::Avs3AudioDescriptor;
998        use crate::descriptors::private_data_specifier::PDS_AVSA;
999        let desc = [0xD2, 6, 0x03, 0xAA, 0x95, 0x04, 0x00, 0x6A];
1000        let mut reg = DescriptorRegistry::new();
1001        reg.with_avs3_audio();
1002
1003        let mut bytes = Vec::new();
1004        bytes.extend_from_slice(&pds_descriptor(PDS_AVSA));
1005        bytes.extend_from_slice(&desc);
1006        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1007        match &items[1] {
1008            AnyDescriptor::Other { tag, value } => {
1009                assert_eq!(*tag, 0xD2);
1010                let d = value.downcast_ref::<Avs3AudioDescriptor>().unwrap();
1011                assert_eq!(d.sampling_frequency_index, 3);
1012            }
1013            other => panic!("expected Other (Avs3AudioDescriptor), got {other:?}"),
1014        }
1015
1016        // Without the 'AVSA' specifier (or under another) the tag stays Unknown.
1017        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
1018        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0xD2, .. }));
1019        let mut other = Vec::new();
1020        other.extend_from_slice(&pds_descriptor(PDS_NORDIG));
1021        other.extend_from_slice(&desc);
1022        let items: Vec<_> = reg.parse_loop(&other).collect::<Result<_, _>>().unwrap();
1023        assert!(matches!(items[1], AnyDescriptor::Unknown { tag: 0xD2, .. }));
1024    }
1025
1026    /// GY/T 270-2013 Table 8: tag 0x86 is the China caption_service_descriptor
1027    /// once `with_china_caption_service` is enabled, and stays Unknown otherwise.
1028    #[test]
1029    fn china_caption_service_is_opt_in() {
1030        use crate::descriptors::china_caption_service::ChinaCaptionServiceDescriptor;
1031        let desc = [
1032            0x86, 9, 0xE1, b'c', b'h', b'i', 0xC1, 0xC2, 0xFF, 0xE1, 0x00,
1033        ];
1034        let plain = DescriptorRegistry::new();
1035        let items: Vec<_> = plain.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
1036        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0x86, .. }));
1037        let mut reg = DescriptorRegistry::new();
1038        reg.with_china_caption_service();
1039        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
1040        match &items[0] {
1041            AnyDescriptor::Other { tag, value } => {
1042                assert_eq!(*tag, 0x86);
1043                let d = value
1044                    .downcast_ref::<ChinaCaptionServiceDescriptor>()
1045                    .unwrap();
1046                assert_eq!(d.services.len(), 1);
1047            }
1048            other => panic!("expected Other, got {other:?}"),
1049        }
1050    }
1051
1052    #[test]
1053    fn atsc_caption_service_is_opt_in() {
1054        use crate::descriptors::atsc_caption_service::AtscCaptionServiceDescriptor;
1055        let desc = [0x86, 7, 0xE1, b'e', b'n', b'g', 0xC1, 0x7F, 0xFF];
1056        let plain = DescriptorRegistry::new();
1057        let items: Vec<_> = plain.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
1058        assert!(matches!(items[0], AnyDescriptor::Unknown { tag: 0x86, .. }));
1059        let mut reg = DescriptorRegistry::new();
1060        reg.with_atsc_caption_service();
1061        let items: Vec<_> = reg.parse_loop(&desc).collect::<Result<_, _>>().unwrap();
1062        match &items[0] {
1063            AnyDescriptor::Other { value, .. } => {
1064                let d = value
1065                    .downcast_ref::<AtscCaptionServiceDescriptor>()
1066                    .unwrap();
1067                assert_eq!(d.services.len(), 1);
1068            }
1069            other => panic!("expected Other, got {other:?}"),
1070        }
1071    }
1072
1073    #[test]
1074    fn pds_agnostic_matches_without_pds() {
1075        let mut reg = DescriptorRegistry::new();
1076        reg.register::<PdsAgnostic>();
1077
1078        let bytes = [0x84, 0x01, 0xDD];
1079        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1080        assert_eq!(items.len(), 1);
1081        match &items[0] {
1082            AnyDescriptor::Other { tag, value } => {
1083                assert_eq!(*tag, 0x84);
1084                let c = value.downcast_ref::<PdsAgnostic>().unwrap();
1085                assert_eq!(c.z, 0xDD);
1086            }
1087            other => panic!("expected Other, got {other:?}"),
1088        }
1089    }
1090
1091    #[test]
1092    fn pds_scoped_takes_precedence_over_agnostic() {
1093        // Two parsers compete for the SAME tag 0x83: one PDS-agnostic, one
1094        // scoped to EACEM. With no PDS active the agnostic one wins; once an
1095        // EACEM private_data_specifier appears, the scoped one takes over.
1096        #[derive(Debug, PartialEq, Eq)]
1097        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1098        struct Agnostic83 {
1099            a: u8,
1100        }
1101
1102        impl<'a> broadcast_common::Parse<'a> for Agnostic83 {
1103            type Error = crate::error::Error;
1104            fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
1105                if bytes.len() < 3 {
1106                    return Err(crate::error::Error::BufferTooShort {
1107                        need: 3,
1108                        have: bytes.len(),
1109                        what: "Agnostic83",
1110                    });
1111                }
1112                Ok(Self { a: bytes[2] })
1113            }
1114        }
1115
1116        impl DescriptorDef<'_> for Agnostic83 {
1117            const TAG: u8 = 0x83;
1118            const NAME: &'static str = "AGNOSTIC_83";
1119        }
1120
1121        let mut reg = DescriptorRegistry::new();
1122        reg.register::<Agnostic83>();
1123        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
1124
1125        // No PDS → agnostic wins
1126        let items: Vec<_> = reg
1127            .parse_loop(&[0x83, 0x01, 0xEE])
1128            .collect::<Result<_, _>>()
1129            .unwrap();
1130        match &items[0] {
1131            AnyDescriptor::Other { value, .. } => {
1132                assert!(value.downcast_ref::<Agnostic83>().is_some());
1133                assert!(value.downcast_ref::<PdsEacem>().is_none());
1134            }
1135            other => panic!("expected Other, got {other:?}"),
1136        }
1137
1138        // With PDS EACEM → PDS-scoped wins
1139        let mut bytes = Vec::new();
1140        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1141        bytes.extend_from_slice(&[0x83, 0x01, 0xFF]);
1142        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1143        match &items[1] {
1144            AnyDescriptor::Other { value, .. } => {
1145                assert!(value.downcast_ref::<PdsEacem>().is_some());
1146                assert!(value.downcast_ref::<Agnostic83>().is_none());
1147            }
1148            other => panic!("expected Other, got {other:?}"),
1149        }
1150    }
1151
1152    fn logical_channel_descriptor(service_id: u16, visible: bool, lcn: u16) -> Vec<u8> {
1153        // One entry: tag=0x83, len=4, entry(4 bytes)
1154        let mut v = vec![crate::descriptors::logical_channel::TAG, 4];
1155        v.extend_from_slice(&service_id.to_be_bytes());
1156        let flags = (u8::from(visible) << 7) | ((lcn >> 8) as u8 & 0x03);
1157        v.push(flags);
1158        v.push((lcn & 0xFF) as u8);
1159        v
1160    }
1161
1162    #[test]
1163    fn logical_channel_pds_scoped_matches_correct_pds() {
1164        let mut reg = DescriptorRegistry::new();
1165        reg.with_logical_channel_for_pds(PDS_EACEM);
1166
1167        let mut bytes = Vec::new();
1168        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1169        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
1170
1171        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1172        assert_eq!(items.len(), 2);
1173        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
1174        match &items[1] {
1175            AnyDescriptor::LogicalChannel(lc) => {
1176                assert_eq!(lc.entries.len(), 1);
1177                assert_eq!(lc.entries[0].service_id, 0x1234);
1178                assert!(lc.entries[0].visible_service);
1179                assert_eq!(lc.entries[0].logical_channel_number, 101);
1180            }
1181            other => panic!("expected LogicalChannel, got {other:?}"),
1182        }
1183    }
1184
1185    #[test]
1186    fn logical_channel_pds_scoped_rejects_no_pds() {
1187        let mut reg = DescriptorRegistry::new();
1188        reg.with_logical_channel_for_pds(PDS_EACEM);
1189
1190        let bytes = logical_channel_descriptor(0x1234, true, 101);
1191        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1192        assert_eq!(items.len(), 1);
1193        assert!(matches!(
1194            &items[0],
1195            AnyDescriptor::Unknown { tag: 0x83, .. }
1196        ));
1197    }
1198
1199    #[test]
1200    fn logical_channel_pds_scoped_rejects_wrong_pds() {
1201        let mut reg = DescriptorRegistry::new();
1202        reg.with_logical_channel_for_pds(PDS_EACEM);
1203
1204        let mut bytes = Vec::new();
1205        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
1206        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
1207
1208        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1209        assert_eq!(items.len(), 2);
1210        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
1211        assert!(matches!(
1212            &items[1],
1213            AnyDescriptor::Unknown { tag: 0x83, .. }
1214        ));
1215    }
1216
1217    #[test]
1218    fn logical_channel_pds_scoped_multiple_pds() {
1219        let mut reg = DescriptorRegistry::new();
1220        reg.with_logical_channel_for_pds(PDS_EACEM);
1221        reg.with_logical_channel_for_pds(PDS_NORDIG);
1222
1223        // EACEM → LogicalChannel
1224        let mut bytes = Vec::new();
1225        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
1226        bytes.extend_from_slice(&logical_channel_descriptor(0x0001, true, 1));
1227        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
1228        assert!(matches!(&items[1], AnyDescriptor::LogicalChannel(_)));
1229
1230        // NorDig → LogicalChannel
1231        let mut bytes2 = Vec::new();
1232        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
1233        bytes2.extend_from_slice(&logical_channel_descriptor(0x0002, false, 2));
1234        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
1235        assert!(matches!(&items2[1], AnyDescriptor::LogicalChannel(_)));
1236    }
1237
1238    /// Same program_info-registration layout through `iter_with_extensions`.
1239    #[test]
1240    fn program_info_registration_scopes_es_info_with_extensions() {
1241        use crate::descriptors::any::DescriptorLoop;
1242        use crate::descriptors::avs3_video::Avs3VideoDescriptor;
1243        use crate::descriptors::extension::registry::ExtensionRegistry;
1244        use crate::descriptors::private_data_specifier::PDS_AVSV;
1245        let program_info = [0x05, 4, b'A', b'V', b'S', b'V'];
1246        let es_info = [0xD1, 8, 0x22, 0x42, 0xAA, 0x4B, 9, 14, 9, 0xFF];
1247        let mut reg = DescriptorRegistry::new();
1248        reg.with_avs3_video();
1249        let ext = ExtensionRegistry::new();
1250
1251        let items: Vec<_> = DescriptorLoop::new(&es_info)
1252            .iter_with_extensions(&reg, &ext)
1253            .collect::<Result<_, _>>()
1254            .unwrap();
1255        assert!(matches!(
1256            &items[0],
1257            ExtIterItem::Descriptor(AnyDescriptor::Unknown { tag: 0xD1, .. })
1258        ));
1259
1260        let mut pi = DescriptorLoop::new(&program_info).iter_with_extensions(&reg, &ext);
1261        for item in pi.by_ref() {
1262            item.unwrap();
1263        }
1264        let scope = pi.avs_scope();
1265        assert_eq!(scope, Some(PDS_AVSV));
1266        let items: Vec<_> = DescriptorLoop::new(&es_info)
1267            .iter_with_extensions_in_scope(&reg, &ext, scope)
1268            .collect::<Result<_, _>>()
1269            .unwrap();
1270        match &items[0] {
1271            ExtIterItem::Descriptor(AnyDescriptor::Other { tag: 0xD1, value }) => {
1272                assert!(value.downcast_ref::<Avs3VideoDescriptor>().is_some());
1273            }
1274            other => panic!("expected Avs3Video via inherited scope, got {other:?}"),
1275        }
1276    }
1277
1278    #[test]
1279    fn iter_with_extensions_surfaces_custom_extension() {
1280        use crate::descriptors::any::{AnyDescriptor, DescriptorLoop};
1281        use crate::descriptors::extension::registry::ExtensionRegistry;
1282
1283        #[derive(Debug, PartialEq, Eq)]
1284        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
1285        struct MyCustomExt {
1286            payload: Vec<u8>,
1287        }
1288
1289        impl<'a> broadcast_common::Parse<'a> for MyCustomExt {
1290            type Error = crate::error::Error;
1291            fn parse(sel: &'a [u8]) -> crate::Result<Self> {
1292                Ok(Self {
1293                    payload: sel.to_vec(),
1294                })
1295            }
1296        }
1297
1298        impl crate::descriptors::extension::ExtensionBodyDef<'_> for MyCustomExt {
1299            const TAG_EXTENSION: u8 = 0x42;
1300            const NAME: &'static str = "MY_CUSTOM_EXT";
1301        }
1302
1303        let mut ext_reg = ExtensionRegistry::new();
1304        ext_reg.register::<MyCustomExt>();
1305
1306        let desc_reg = DescriptorRegistry::new();
1307
1308        // Build a descriptor loop with a short_event + a 0x7F extension with tag_extension 0x42
1309        let mut loop_bytes = vec![
1310            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
1311        ];
1312        // extension: tag=0x7F, length=3, tag_extension=0x42, selector=[0xAB, 0xCD]
1313        loop_bytes.extend_from_slice(&[0x7F, 0x03, 0x42, 0xAB, 0xCD]);
1314
1315        let dl = DescriptorLoop::new(&loop_bytes);
1316        let items: Vec<_> = dl
1317            .iter_with_extensions(&desc_reg, &ext_reg)
1318            .collect::<Result<_, _>>()
1319            .unwrap();
1320        assert_eq!(items.len(), 2);
1321        // First item is a regular descriptor
1322        assert!(matches!(
1323            &items[0],
1324            ExtIterItem::Descriptor(AnyDescriptor::ShortEvent(_))
1325        ));
1326        // Second item surfaces the custom extension body (not Raw!)
1327        match &items[1] {
1328            ExtIterItem::CustomExtension {
1329                tag_extension,
1330                value,
1331            } => {
1332                assert_eq!(*tag_extension, 0x42);
1333                let concrete = value.downcast_ref::<MyCustomExt>().unwrap();
1334                assert_eq!(concrete.payload, &[0xAB, 0xCD]);
1335            }
1336            other => panic!("expected CustomExtension, got {other:?}"),
1337        }
1338    }
1339}