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