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 dvb_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 std::any::Any;
69use std::collections::HashMap;
70
71use crate::descriptors::any::AnyDescriptor;
72
73// ---------------------------------------------------------------------------
74// DescriptorObject trait
75// ---------------------------------------------------------------------------
76
77/// Object-safe face of a runtime-registered descriptor value.
78///
79/// Registered types must be owned (`'static`) because the `dyn Any` downcast
80/// path requires it.  See the [module docs][self] for details.
81///
82/// Implemented automatically via the blanket impl for any `T` satisfying the
83/// supertraits; you do not need to write this by hand.
84#[cfg(not(feature = "serde"))]
85pub trait DescriptorObject: std::fmt::Debug + Any + Send + Sync {
86    /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
87    fn as_any(&self) -> &dyn Any;
88}
89
90/// Object-safe face of a runtime-registered descriptor value.
91///
92/// Registered types must be owned (`'static`) because the `dyn Any` downcast
93/// path requires it.  See the [module docs][self] for details.
94///
95/// Implemented automatically via the blanket impl for any `T` satisfying the
96/// supertraits; you do not need to write this by hand.
97#[cfg(feature = "serde")]
98pub trait DescriptorObject: std::fmt::Debug + Any + Send + Sync + erased_serde::Serialize {
99    /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
100    fn as_any(&self) -> &dyn Any;
101}
102
103// Blanket impl — no-serde arm.
104#[cfg(not(feature = "serde"))]
105impl<T> DescriptorObject for T
106where
107    T: std::fmt::Debug + Any + Send + Sync,
108{
109    fn as_any(&self) -> &dyn Any {
110        self
111    }
112}
113
114// Blanket impl — serde arm.
115#[cfg(feature = "serde")]
116impl<T> DescriptorObject for T
117where
118    T: std::fmt::Debug + Any + Send + Sync + serde::Serialize,
119{
120    fn as_any(&self) -> &dyn Any {
121        self
122    }
123}
124
125// Downcast helpers ON THE TRAIT OBJECT (not the blanket).
126//
127// The blanket `impl<T> DescriptorObject for T` also covers `Box<dyn
128// DescriptorObject>` itself whenever the box satisfies the bounds — it does
129// under `--no-default-features`, where the bound is just `Debug + Any + Send +
130// Sync`. So `the_box.as_any()` resolves to the *box's* impl and reports the
131// box's `TypeId`, not the inner value's — a silent downcast failure. (Under
132// `serde` the extra `serde::Serialize` bound excludes the box, which is why the
133// footgun only bites without default features.) Calling through `dyn
134// DescriptorObject` (which `Box` derefs to) always hits the inner value, so
135// always downcast via these methods rather than `the_box.as_any()`.
136impl dyn DescriptorObject {
137    /// Downcast a registered descriptor to its concrete type `T`.
138    ///
139    /// Works for `Box<dyn DescriptorObject>` (it derefs to the trait object)
140    /// under every feature configuration.
141    #[must_use]
142    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
143        self.as_any().downcast_ref::<T>()
144    }
145
146    /// `true` if the registered descriptor's concrete type is `T`.
147    #[must_use]
148    pub fn is<T: Any>(&self) -> bool {
149        self.as_any().is::<T>()
150    }
151}
152
153// ---------------------------------------------------------------------------
154// Erased serialisation helper (serde-gated)
155// ---------------------------------------------------------------------------
156
157/// `serialize_with` helper used on [`AnyDescriptor::Other`]'s `value` field.
158///
159/// Delegates to [`erased_serde::serialize`] so the concrete type's
160/// `serde::Serialize` impl is invoked through the trait object.
161///
162/// The `&Box<T>` is required by serde's `serialize_with` codegen — the field
163/// type is `Box<dyn DescriptorObject>` so serde passes `&Box<dyn DescriptorObject>`.
164#[cfg(feature = "serde")]
165#[allow(clippy::borrowed_box)]
166pub(crate) fn serialize_erased<S: serde::Serializer>(
167    v: &Box<dyn DescriptorObject>,
168    s: S,
169) -> Result<S::Ok, S::Error> {
170    erased_serde::serialize(&**v, s)
171}
172
173// ---------------------------------------------------------------------------
174// Internal parse closure type
175// ---------------------------------------------------------------------------
176
177/// A heap-allocated parse closure that takes a full descriptor (header + body)
178/// and returns an owned, type-erased descriptor value.
179pub(crate) type CustomParse =
180    Box<dyn for<'a> Fn(&'a [u8]) -> crate::Result<Box<dyn DescriptorObject>> + Send + Sync>;
181
182// ---------------------------------------------------------------------------
183// DescriptorRegistry
184// ---------------------------------------------------------------------------
185
186/// Runtime-configurable descriptor registry.
187///
188/// By default the registry has no custom parsers and 0x83 logical_channel is
189/// disabled (it is a private tag that requires `private_data_specifier`
190/// context).  Use [`register`][Self::register] and
191/// [`with_logical_channel`][Self::with_logical_channel] to opt in.
192///
193/// Walk a byte slice with [`parse_loop`][Self::parse_loop]; it returns a lazy
194/// [`RegistryIter`] with identical truncation/fuse/error-continue semantics to
195/// the free [`crate::descriptors::parse_loop`].
196///
197/// # Precedence (per entry)
198///
199/// 1. PDS-scoped custom parser (if the current `private_data_specifier` matches
200///    a [`register_for_pds`][Self::register_for_pds] entry) →
201///    [`AnyDescriptor::Other`]
202/// 2. PDS-agnostic custom-registered parser (tag in the [`custom`][Self::register]
203///    map) → [`AnyDescriptor::Other`]
204/// 3. Logical-channel opt-in (tag 0x83 + [`with_logical_channel`][Self::with_logical_channel]
205///    enabled) → [`AnyDescriptor::LogicalChannel`]
206/// 4. Built-in dispatch (internal `AnyDescriptor::dispatch`) → typed variant
207/// 5. Unknown → [`AnyDescriptor::Unknown`]
208#[derive(Default)]
209pub struct DescriptorRegistry {
210    custom: HashMap<(Option<u32>, u8), CustomParse>,
211    logical_channel: bool,
212}
213
214impl DescriptorRegistry {
215    /// Create an empty registry (built-in dispatch only; 0x83 disabled).
216    #[must_use]
217    pub fn new() -> Self {
218        Self::default()
219    }
220
221    /// Register an owned custom descriptor type for its
222    /// [`DescriptorDef::TAG`][crate::traits::DescriptorDef::TAG].
223    ///
224    /// # Owned types only
225    ///
226    /// `T` must be `'static` — no borrowed slices.  The registered value is
227    /// type-erased as `Box<dyn DescriptorObject>`; `dyn Any` downcast requires
228    /// the concrete type to be `'static`.
229    ///
230    /// Registering a type whose `TAG` is already used by a built-in **overrides**
231    /// the built-in for that tag.
232    ///
233    /// Re-registering the same tag replaces the prior custom parser (last wins).
234    /// A failing custom parse surfaces the client's `Parse::Error` unwrapped —
235    /// embed identifying context (type/tag) in your error's `what`/`reason` fields.
236    ///
237    /// The registration is PDS-agnostic: it matches the tag regardless of which
238    /// `private_data_specifier` (if any) is active in the loop.  A
239    /// PDS-scoped registration via [`register_for_pds`][Self::register_for_pds]
240    /// takes precedence over this when the matching PDS is active.
241    pub fn register<T>(&mut self) -> &mut Self
242    where
243        T: for<'a> crate::traits::DescriptorDef<'a> + DescriptorObject + 'static,
244    {
245        let tag = <T as crate::traits::DescriptorDef<'static>>::TAG;
246        self.custom.insert(
247            (None, tag),
248            Box::new(|b| {
249                Ok(Box::new(<T as dvb_common::Parse>::parse(b)?) as Box<dyn DescriptorObject>)
250            }),
251        );
252        self
253    }
254
255    /// Register an owned custom descriptor type scoped to a specific
256    /// `private_data_specifier` value.
257    ///
258    /// The type is only dispatched when a preceding
259    /// [`private_data_specifier_descriptor`][crate::descriptors::private_data_specifier]
260    /// (tag `0x5F`) in the same descriptor loop has set the active PDS to
261    /// `pds`.  A PDS-scoped registration takes precedence over a PDS-agnostic
262    /// [`register`][Self::register] of the same tag when that PDS is active.
263    ///
264    /// Re-registering the same `(pds, tag)` pair replaces the prior custom
265    /// parser (last wins).
266    pub fn register_for_pds<T>(&mut self, pds: u32) -> &mut Self
267    where
268        T: for<'a> crate::traits::DescriptorDef<'a> + DescriptorObject + 'static,
269    {
270        let tag = <T as crate::traits::DescriptorDef<'static>>::TAG;
271        self.custom.insert(
272            (Some(pds), tag),
273            Box::new(|b| {
274                Ok(Box::new(<T as dvb_common::Parse>::parse(b)?) as Box<dyn DescriptorObject>)
275            }),
276        );
277        self
278    }
279
280    /// Enable the 0x83 logical_channel built-in.
281    ///
282    /// By default 0x83 is not auto-dispatched because it is a private tag
283    /// whose semantics depend on a `private_data_specifier` context.  Call
284    /// this when you know the loop is from an EACEM/NorDig/D-Book stream.
285    pub fn with_logical_channel(&mut self) -> &mut Self {
286        self.logical_channel = true;
287        self
288    }
289
290    /// Lazily walk a raw descriptor loop using this registry's configuration.
291    ///
292    /// Semantics mirror [`crate::descriptors::parse_loop`]: per-descriptor
293    /// parse errors yield `Err` and iteration continues; a truncated final
294    /// header or body yields one `Err` then fuses.
295    ///
296    /// A `private_data_specifier_descriptor` (tag `0x5F`) in the loop
297    /// automatically updates the iterator's PDS context, scoping subsequent
298    /// private-tag dispatch.
299    #[must_use]
300    pub fn parse_loop<'r, 'a>(&'r self, bytes: &'a [u8]) -> RegistryIter<'r, 'a> {
301        RegistryIter {
302            registry: self,
303            bytes,
304            pos: 0,
305            fused: false,
306            current_pds: None,
307        }
308    }
309}
310
311// ---------------------------------------------------------------------------
312// RegistryIter
313// ---------------------------------------------------------------------------
314
315/// Lazy iterator over a raw descriptor loop, driven by a [`DescriptorRegistry`].
316///
317/// Returned by [`DescriptorRegistry::parse_loop`].
318pub struct RegistryIter<'r, 'a> {
319    registry: &'r DescriptorRegistry,
320    bytes: &'a [u8],
321    pos: usize,
322    fused: bool,
323    current_pds: Option<u32>,
324}
325
326/// Shared precedence ladder for both [`RegistryIter`] and [`ExtRegistryIter`].
327///
328/// Returns the [`AnyDescriptor`] that results from applying the five-step
329/// precedence: PDS-scoped custom → PDS-agnostic custom → logical-channel
330/// opt-in → built-in dispatch → Unknown.
331pub(crate) fn dispatch_entry<'a>(
332    registry: &DescriptorRegistry,
333    current_pds: Option<u32>,
334    tag: u8,
335    full: &'a [u8],
336) -> crate::Result<AnyDescriptor<'a>> {
337    if let Some(pds) = current_pds {
338        if let Some(parse_fn) = registry.custom.get(&(Some(pds), tag)) {
339            return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
340        }
341    }
342    if let Some(parse_fn) = registry.custom.get(&(None, tag)) {
343        return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
344    }
345    if registry.logical_channel && tag == crate::descriptors::logical_channel::TAG {
346        use dvb_common::Parse;
347        return crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
348            .map(AnyDescriptor::LogicalChannel);
349    }
350    if let Some(res) = AnyDescriptor::dispatch(tag, full) {
351        return res;
352    }
353    Ok(AnyDescriptor::Unknown {
354        tag,
355        body: &full[2..],
356    })
357}
358
359fn update_pds(current: &mut Option<u32>, tag: u8, full: &[u8]) {
360    if tag == crate::descriptors::private_data_specifier::TAG {
361        use dvb_common::Parse;
362        if let Ok(pds) =
363            crate::descriptors::private_data_specifier::PrivateDataSpecifierDescriptor::parse(full)
364        {
365            *current = Some(pds.private_data_specifier);
366        }
367    }
368}
369
370impl<'r, 'a> Iterator for RegistryIter<'r, 'a> {
371    type Item = crate::Result<AnyDescriptor<'a>>;
372
373    fn next(&mut self) -> Option<Self::Item> {
374        let (tag, full) = match crate::descriptors::any::next_loop_entry(
375            self.bytes,
376            &mut self.pos,
377            &mut self.fused,
378        )? {
379            Ok(v) => v,
380            Err(e) => return Some(Err(e)),
381        };
382
383        update_pds(&mut self.current_pds, tag, full);
384
385        Some(dispatch_entry(self.registry, self.current_pds, tag, full))
386    }
387}
388
389impl std::iter::FusedIterator for RegistryIter<'_, '_> {}
390
391// ---------------------------------------------------------------------------
392// ExtIterItem — item type for iter_with_extensions
393// ---------------------------------------------------------------------------
394
395/// Item produced by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
396///
397/// Extends [`AnyDescriptor`] with a third arm for custom-registered extension
398/// bodies whose `descriptor_tag_extension` is recognised by an
399/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
400/// The `value` field can be downcast to the concrete type via
401/// `downcast_ref` on the value (see [`ExtensionObject`](super::extension::registry::ExtensionObject))
402#[derive(Debug)]
403#[cfg_attr(feature = "serde", derive(serde::Serialize))]
404#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
405#[non_exhaustive]
406pub enum ExtIterItem<'a> {
407    /// A regular descriptor (including built-in extension descriptors).
408    Descriptor(AnyDescriptor<'a>),
409    /// A custom-registered extension body (tag `0x7F`, known `descriptor_tag_extension`).
410    CustomExtension {
411        /// The `descriptor_tag_extension` byte.
412        tag_extension: u8,
413        /// The parsed, type-erased extension body value. Call `downcast_ref`
414        /// on it (see [`ExtensionObject`](super::extension::registry::ExtensionObject)) to recover the concrete type.
415        #[cfg_attr(
416            feature = "serde",
417            serde(serialize_with = "super::extension::registry::serialize_erased")
418        )]
419        value: Box<dyn super::extension::registry::ExtensionObject>,
420    },
421}
422
423// ---------------------------------------------------------------------------
424// ExtRegistryIter — iterator for iter_with_extensions
425// ---------------------------------------------------------------------------
426
427/// Lazy iterator over a raw descriptor loop, driven by both a
428/// [`DescriptorRegistry`] and an
429/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
430///
431/// Returned by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
432pub struct ExtRegistryIter<'r, 'a> {
433    desc_reg: &'r DescriptorRegistry,
434    ext_reg: &'r super::extension::registry::ExtensionRegistry,
435    bytes: &'a [u8],
436    pos: usize,
437    fused: bool,
438    current_pds: Option<u32>,
439}
440
441impl<'r, 'a> ExtRegistryIter<'r, 'a> {
442    pub(crate) fn new(
443        desc_reg: &'r DescriptorRegistry,
444        ext_reg: &'r super::extension::registry::ExtensionRegistry,
445        bytes: &'a [u8],
446    ) -> Self {
447        Self {
448            desc_reg,
449            ext_reg,
450            bytes,
451            pos: 0,
452            fused: false,
453            current_pds: None,
454        }
455    }
456}
457
458impl<'r, 'a> Iterator for ExtRegistryIter<'r, 'a> {
459    type Item = crate::Result<ExtIterItem<'a>>;
460
461    fn next(&mut self) -> Option<Self::Item> {
462        let (tag, full) = match crate::descriptors::any::next_loop_entry(
463            self.bytes,
464            &mut self.pos,
465            &mut self.fused,
466        )? {
467            Ok(v) => v,
468            Err(e) => return Some(Err(e)),
469        };
470
471        update_pds(&mut self.current_pds, tag, full);
472
473        let len = full.len() - 2;
474        if tag == crate::descriptors::extension::TAG && len >= 1 {
475            let tag_extension = full[2];
476            if self.ext_reg.has_custom(tag_extension) {
477                return Some(match self.ext_reg.parse_body(tag_extension, &full[3..]) {
478                    Ok(super::extension::registry::RegisteredExtension::Custom {
479                        tag_extension,
480                        value,
481                    }) => Ok(ExtIterItem::CustomExtension {
482                        tag_extension,
483                        value,
484                    }),
485                    Ok(super::extension::registry::RegisteredExtension::Builtin(d)) => {
486                        Ok(ExtIterItem::Descriptor(AnyDescriptor::Extension(d)))
487                    }
488                    Err(e) => Err(e),
489                });
490            }
491        }
492
493        Some(
494            dispatch_entry(self.desc_reg, self.current_pds, tag, full).map(ExtIterItem::Descriptor),
495        )
496    }
497}
498
499impl std::iter::FusedIterator for ExtRegistryIter<'_, '_> {}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504    use crate::descriptors::private_data_specifier;
505    use crate::traits::DescriptorDef;
506
507    const PDS_EACEM: u32 = 0x0000_0028;
508    const PDS_NORDIG: u32 = 0x0000_0031;
509
510    #[derive(Debug, PartialEq, Eq)]
511    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
512    struct PdsEacem {
513        v: u8,
514    }
515
516    impl<'a> dvb_common::Parse<'a> for PdsEacem {
517        type Error = crate::error::Error;
518        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
519            if bytes.len() < 3 {
520                return Err(crate::error::Error::BufferTooShort {
521                    need: 3,
522                    have: bytes.len(),
523                    what: "PdsEacem",
524                });
525            }
526            Ok(Self { v: bytes[2] })
527        }
528    }
529
530    impl<'a> DescriptorDef<'a> for PdsEacem {
531        const TAG: u8 = 0x83;
532        const NAME: &'static str = "PDS_EACEM";
533    }
534
535    #[derive(Debug, PartialEq, Eq)]
536    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
537    struct PdsNordig {
538        w: u8,
539    }
540
541    impl<'a> dvb_common::Parse<'a> for PdsNordig {
542        type Error = crate::error::Error;
543        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
544            if bytes.len() < 3 {
545                return Err(crate::error::Error::BufferTooShort {
546                    need: 3,
547                    have: bytes.len(),
548                    what: "PdsNordig",
549                });
550            }
551            Ok(Self { w: bytes[2] })
552        }
553    }
554
555    impl<'a> DescriptorDef<'a> for PdsNordig {
556        const TAG: u8 = 0x83;
557        const NAME: &'static str = "PDS_NORDIG";
558    }
559
560    #[derive(Debug, PartialEq, Eq)]
561    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
562    struct PdsAgnostic {
563        z: u8,
564    }
565
566    impl<'a> dvb_common::Parse<'a> for PdsAgnostic {
567        type Error = crate::error::Error;
568        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
569            if bytes.len() < 3 {
570                return Err(crate::error::Error::BufferTooShort {
571                    need: 3,
572                    have: bytes.len(),
573                    what: "PdsAgnostic",
574                });
575            }
576            Ok(Self { z: bytes[2] })
577        }
578    }
579
580    impl<'a> DescriptorDef<'a> for PdsAgnostic {
581        const TAG: u8 = 0x84;
582        const NAME: &'static str = "PDS_AGNOSTIC";
583    }
584
585    fn pds_descriptor(pds: u32) -> Vec<u8> {
586        let mut v = vec![private_data_specifier::TAG, 4];
587        v.extend_from_slice(&pds.to_be_bytes());
588        v
589    }
590
591    #[test]
592    fn pds_scoped_same_tag_resolves_by_pds() {
593        let mut reg = DescriptorRegistry::new();
594        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
595        reg.register_for_pds::<PdsNordig>(PDS_NORDIG);
596
597        let mut bytes = Vec::new();
598        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
599        bytes.extend_from_slice(&[0x83, 0x01, 0xAA]);
600
601        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
602        assert_eq!(items.len(), 2);
603        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
604        match &items[1] {
605            AnyDescriptor::Other { tag, value } => {
606                assert_eq!(*tag, 0x83);
607                let c = value.downcast_ref::<PdsEacem>().unwrap();
608                assert_eq!(c.v, 0xAA);
609            }
610            other => panic!("expected Other (PdsEacem), got {other:?}"),
611        }
612
613        let mut bytes2 = Vec::new();
614        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
615        bytes2.extend_from_slice(&[0x83, 0x01, 0xBB]);
616
617        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
618        match &items2[1] {
619            AnyDescriptor::Other { tag, value } => {
620                assert_eq!(*tag, 0x83);
621                let c = value.downcast_ref::<PdsNordig>().unwrap();
622                assert_eq!(c.w, 0xBB);
623            }
624            other => panic!("expected Other (PdsNordig), got {other:?}"),
625        }
626    }
627
628    #[test]
629    fn pds_scoped_does_not_match_wrong_pds() {
630        let mut reg = DescriptorRegistry::new();
631        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
632
633        let mut bytes = Vec::new();
634        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
635        bytes.extend_from_slice(&[0x83, 0x01, 0xCC]);
636
637        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
638        assert_eq!(items.len(), 2);
639        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
640        match &items[1] {
641            AnyDescriptor::Unknown { tag, .. } => assert_eq!(*tag, 0x83),
642            other => panic!("expected Unknown (wrong PDS), got {other:?}"),
643        }
644    }
645
646    #[test]
647    fn pds_agnostic_matches_without_pds() {
648        let mut reg = DescriptorRegistry::new();
649        reg.register::<PdsAgnostic>();
650
651        let bytes = [0x84, 0x01, 0xDD];
652        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
653        assert_eq!(items.len(), 1);
654        match &items[0] {
655            AnyDescriptor::Other { tag, value } => {
656                assert_eq!(*tag, 0x84);
657                let c = value.downcast_ref::<PdsAgnostic>().unwrap();
658                assert_eq!(c.z, 0xDD);
659            }
660            other => panic!("expected Other, got {other:?}"),
661        }
662    }
663
664    #[test]
665    fn pds_scoped_takes_precedence_over_agnostic() {
666        // Two parsers compete for the SAME tag 0x83: one PDS-agnostic, one
667        // scoped to EACEM. With no PDS active the agnostic one wins; once an
668        // EACEM private_data_specifier appears, the scoped one takes over.
669        #[derive(Debug, PartialEq, Eq)]
670        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
671        struct Agnostic83 {
672            a: u8,
673        }
674
675        impl<'a> dvb_common::Parse<'a> for Agnostic83 {
676            type Error = crate::error::Error;
677            fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
678                if bytes.len() < 3 {
679                    return Err(crate::error::Error::BufferTooShort {
680                        need: 3,
681                        have: bytes.len(),
682                        what: "Agnostic83",
683                    });
684                }
685                Ok(Self { a: bytes[2] })
686            }
687        }
688
689        impl<'a> DescriptorDef<'a> for Agnostic83 {
690            const TAG: u8 = 0x83;
691            const NAME: &'static str = "AGNOSTIC_83";
692        }
693
694        let mut reg = DescriptorRegistry::new();
695        reg.register::<Agnostic83>();
696        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
697
698        // No PDS → agnostic wins
699        let items: Vec<_> = reg
700            .parse_loop(&[0x83, 0x01, 0xEE])
701            .collect::<Result<_, _>>()
702            .unwrap();
703        match &items[0] {
704            AnyDescriptor::Other { value, .. } => {
705                assert!(value.downcast_ref::<Agnostic83>().is_some());
706                assert!(value.downcast_ref::<PdsEacem>().is_none());
707            }
708            other => panic!("expected Other, got {other:?}"),
709        }
710
711        // With PDS EACEM → PDS-scoped wins
712        let mut bytes = Vec::new();
713        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
714        bytes.extend_from_slice(&[0x83, 0x01, 0xFF]);
715        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
716        match &items[1] {
717            AnyDescriptor::Other { value, .. } => {
718                assert!(value.downcast_ref::<PdsEacem>().is_some());
719                assert!(value.downcast_ref::<Agnostic83>().is_none());
720            }
721            other => panic!("expected Other, got {other:?}"),
722        }
723    }
724
725    #[test]
726    fn iter_with_extensions_surfaces_custom_extension() {
727        use crate::descriptors::any::{AnyDescriptor, DescriptorLoop};
728        use crate::descriptors::extension::registry::ExtensionRegistry;
729
730        #[derive(Debug, PartialEq, Eq)]
731        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
732        struct MyCustomExt {
733            payload: Vec<u8>,
734        }
735
736        impl<'a> dvb_common::Parse<'a> for MyCustomExt {
737            type Error = crate::error::Error;
738            fn parse(sel: &'a [u8]) -> crate::Result<Self> {
739                Ok(Self {
740                    payload: sel.to_vec(),
741                })
742            }
743        }
744
745        impl<'a> crate::descriptors::extension::ExtensionBodyDef<'a> for MyCustomExt {
746            const TAG_EXTENSION: u8 = 0x42;
747            const NAME: &'static str = "MY_CUSTOM_EXT";
748        }
749
750        let mut ext_reg = ExtensionRegistry::new();
751        ext_reg.register::<MyCustomExt>();
752
753        let desc_reg = DescriptorRegistry::new();
754
755        // Build a descriptor loop with a short_event + a 0x7F extension with tag_extension 0x42
756        let mut loop_bytes = vec![
757            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
758        ];
759        // extension: tag=0x7F, length=3, tag_extension=0x42, selector=[0xAB, 0xCD]
760        loop_bytes.extend_from_slice(&[0x7F, 0x03, 0x42, 0xAB, 0xCD]);
761
762        let dl = DescriptorLoop::new(&loop_bytes);
763        let items: Vec<_> = dl
764            .iter_with_extensions(&desc_reg, &ext_reg)
765            .collect::<Result<_, _>>()
766            .unwrap();
767        assert_eq!(items.len(), 2);
768        // First item is a regular descriptor
769        assert!(matches!(
770            &items[0],
771            ExtIterItem::Descriptor(AnyDescriptor::ShortEvent(_))
772        ));
773        // Second item surfaces the custom extension body (not Raw!)
774        match &items[1] {
775            ExtIterItem::CustomExtension {
776                tag_extension,
777                value,
778            } => {
779                assert_eq!(*tag_extension, 0x42);
780                let concrete = value.downcast_ref::<MyCustomExt>().unwrap();
781                assert_eq!(concrete.payload, &[0xAB, 0xCD]);
782            }
783            other => panic!("expected CustomExtension, got {other:?}"),
784        }
785    }
786}