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. PDS-scoped logical-channel opt-in (tag 0x83 + active PDS in
206///    [`with_logical_channel_for_pds`][Self::with_logical_channel_for_pds])
207///    → [`AnyDescriptor::LogicalChannel`]
208/// 4. Logical-channel opt-in (tag 0x83 + [`with_logical_channel`][Self::with_logical_channel]
209///    enabled) → [`AnyDescriptor::LogicalChannel`]
210/// 5. Built-in dispatch (internal `AnyDescriptor::dispatch`) → typed variant
211/// 6. Unknown → [`AnyDescriptor::Unknown`]
212#[derive(Default)]
213pub struct DescriptorRegistry {
214    custom: BTreeMap<(Option<u32>, u8), CustomParse>,
215    logical_channel: bool,
216    logical_channel_pds: alloc::collections::BTreeSet<u32>,
217}
218
219impl DescriptorRegistry {
220    /// Create an empty registry (built-in dispatch only; 0x83 disabled).
221    #[must_use]
222    pub fn new() -> Self {
223        Self::default()
224    }
225
226    /// Register an owned custom descriptor type for its
227    /// [`DescriptorDef::TAG`][crate::traits::DescriptorDef::TAG].
228    ///
229    /// # Owned types only
230    ///
231    /// `T` must be `'static` — no borrowed slices.  The registered value is
232    /// type-erased as `Box<dyn DescriptorObject>`; `dyn Any` downcast requires
233    /// the concrete type to be `'static`.
234    ///
235    /// Registering a type whose `TAG` is already used by a built-in **overrides**
236    /// the built-in for that tag.
237    ///
238    /// Re-registering the same tag replaces the prior custom parser (last wins).
239    /// A failing custom parse surfaces the client's `Parse::Error` unwrapped —
240    /// embed identifying context (type/tag) in your error's `what`/`reason` fields.
241    ///
242    /// The registration is PDS-agnostic: it matches the tag regardless of which
243    /// `private_data_specifier` (if any) is active in the loop.  A
244    /// PDS-scoped registration via [`register_for_pds`][Self::register_for_pds]
245    /// takes precedence over this when the matching PDS is active.
246    pub fn register<T>(&mut self) -> &mut Self
247    where
248        T: for<'a> crate::traits::DescriptorDef<'a> + DescriptorObject + 'static,
249    {
250        let tag = <T as crate::traits::DescriptorDef<'static>>::TAG;
251        self.custom.insert(
252            (None, tag),
253            Box::new(|b| {
254                Ok(Box::new(<T as dvb_common::Parse>::parse(b)?) as Box<dyn DescriptorObject>)
255            }),
256        );
257        self
258    }
259
260    /// Register an owned custom descriptor type scoped to a specific
261    /// `private_data_specifier` value.
262    ///
263    /// The type is only dispatched when a preceding
264    /// [`private_data_specifier_descriptor`][crate::descriptors::private_data_specifier]
265    /// (tag `0x5F`) in the same descriptor loop has set the active PDS to
266    /// `pds`.  A PDS-scoped registration takes precedence over a PDS-agnostic
267    /// [`register`][Self::register] of the same tag when that PDS is active.
268    ///
269    /// Re-registering the same `(pds, tag)` pair replaces the prior custom
270    /// parser (last wins).
271    pub fn register_for_pds<T>(&mut self, pds: u32) -> &mut Self
272    where
273        T: for<'a> crate::traits::DescriptorDef<'a> + DescriptorObject + 'static,
274    {
275        let tag = <T as crate::traits::DescriptorDef<'static>>::TAG;
276        self.custom.insert(
277            (Some(pds), tag),
278            Box::new(|b| {
279                Ok(Box::new(<T as dvb_common::Parse>::parse(b)?) as Box<dyn DescriptorObject>)
280            }),
281        );
282        self
283    }
284
285    /// Enable the 0x83 logical_channel built-in.
286    ///
287    /// By default 0x83 is not auto-dispatched because it is a private tag
288    /// whose semantics depend on a `private_data_specifier` context.  Call
289    /// this when you know the loop is from an EACEM/NorDig/D-Book stream.
290    pub fn with_logical_channel(&mut self) -> &mut Self {
291        self.logical_channel = true;
292        self
293    }
294
295    /// Enable the 0x83 logical_channel built-in ONLY when the active
296    /// `private_data_specifier` (set by a preceding 0x5F descriptor in the
297    /// same loop) matches `pds`.
298    ///
299    /// This is a safer opt-in than [`with_logical_channel`][Self::with_logical_channel]:
300    /// tag 0x83 under a different (or absent) PDS stays `Unknown`.
301    ///
302    /// Common values are [`crate::descriptors::PDS_EACEM`] and
303    /// [`crate::descriptors::PDS_NORDIG`].  A per-PDS opt-in takes precedence
304    /// over the global [`with_logical_channel`][Self::with_logical_channel]
305    /// opt-in when the matching PDS is active.
306    pub fn with_logical_channel_for_pds(&mut self, pds: u32) -> &mut Self {
307        self.logical_channel_pds.insert(pds);
308        self
309    }
310
311    /// Lazily walk a raw descriptor loop using this registry's configuration.
312    ///
313    /// Semantics mirror [`crate::descriptors::parse_loop`]: per-descriptor
314    /// parse errors yield `Err` and iteration continues; a truncated final
315    /// header or body yields one `Err` then fuses.
316    ///
317    /// A `private_data_specifier_descriptor` (tag `0x5F`) in the loop
318    /// automatically updates the iterator's PDS context, scoping subsequent
319    /// private-tag dispatch.
320    #[must_use]
321    pub fn parse_loop<'r, 'a>(&'r self, bytes: &'a [u8]) -> RegistryIter<'r, 'a> {
322        RegistryIter {
323            registry: self,
324            bytes,
325            pos: 0,
326            fused: false,
327            current_pds: None,
328        }
329    }
330}
331
332// ---------------------------------------------------------------------------
333// RegistryIter
334// ---------------------------------------------------------------------------
335
336/// Lazy iterator over a raw descriptor loop, driven by a [`DescriptorRegistry`].
337///
338/// Returned by [`DescriptorRegistry::parse_loop`].
339pub struct RegistryIter<'r, 'a> {
340    registry: &'r DescriptorRegistry,
341    bytes: &'a [u8],
342    pos: usize,
343    fused: bool,
344    current_pds: Option<u32>,
345}
346
347/// Shared precedence ladder for both [`RegistryIter`] and [`ExtRegistryIter`].
348///
349/// Returns the [`AnyDescriptor`] that results from applying the six-step
350/// precedence: PDS-scoped custom → PDS-agnostic custom → PDS-scoped
351/// logical-channel → logical-channel opt-in → built-in dispatch → Unknown.
352pub(crate) fn dispatch_entry<'a>(
353    registry: &DescriptorRegistry,
354    current_pds: Option<u32>,
355    tag: u8,
356    full: &'a [u8],
357) -> crate::Result<AnyDescriptor<'a>> {
358    if let Some(pds) = current_pds {
359        if let Some(parse_fn) = registry.custom.get(&(Some(pds), tag)) {
360            return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
361        }
362    }
363    if let Some(parse_fn) = registry.custom.get(&(None, tag)) {
364        return parse_fn(full).map(|value| AnyDescriptor::Other { tag, value });
365    }
366    if let Some(pds) = current_pds {
367        if tag == crate::descriptors::logical_channel::TAG
368            && registry.logical_channel_pds.contains(&pds)
369        {
370            use dvb_common::Parse;
371            return crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
372                .map(AnyDescriptor::LogicalChannel);
373        }
374    }
375    if registry.logical_channel && tag == crate::descriptors::logical_channel::TAG {
376        use dvb_common::Parse;
377        return crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
378            .map(AnyDescriptor::LogicalChannel);
379    }
380    if let Some(res) = AnyDescriptor::dispatch(tag, full) {
381        return res;
382    }
383    Ok(AnyDescriptor::Unknown {
384        tag,
385        body: &full[2..],
386    })
387}
388
389fn update_pds(current: &mut Option<u32>, tag: u8, full: &[u8]) {
390    if tag == crate::descriptors::private_data_specifier::TAG {
391        use dvb_common::Parse;
392        if let Ok(pds) =
393            crate::descriptors::private_data_specifier::PrivateDataSpecifierDescriptor::parse(full)
394        {
395            *current = Some(pds.private_data_specifier);
396        }
397    }
398}
399
400impl<'r, 'a> Iterator for RegistryIter<'r, 'a> {
401    type Item = crate::Result<AnyDescriptor<'a>>;
402
403    fn next(&mut self) -> Option<Self::Item> {
404        let (tag, full) = match crate::descriptors::any::next_loop_entry(
405            self.bytes,
406            &mut self.pos,
407            &mut self.fused,
408        )? {
409            Ok(v) => v,
410            Err(e) => return Some(Err(e)),
411        };
412
413        update_pds(&mut self.current_pds, tag, full);
414
415        Some(dispatch_entry(self.registry, self.current_pds, tag, full))
416    }
417}
418
419impl core::iter::FusedIterator for RegistryIter<'_, '_> {}
420
421// ---------------------------------------------------------------------------
422// ExtIterItem — item type for iter_with_extensions
423// ---------------------------------------------------------------------------
424
425/// Item produced by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
426///
427/// Extends [`AnyDescriptor`] with a third arm for custom-registered extension
428/// bodies whose `descriptor_tag_extension` is recognised by an
429/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
430/// The `value` field can be downcast to the concrete type via
431/// `downcast_ref` on the value (see [`ExtensionObject`](super::extension::registry::ExtensionObject))
432#[derive(Debug)]
433#[cfg_attr(feature = "serde", derive(serde::Serialize))]
434#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
435#[non_exhaustive]
436pub enum ExtIterItem<'a> {
437    /// A regular descriptor (including built-in extension descriptors).
438    Descriptor(AnyDescriptor<'a>),
439    /// A custom-registered extension body (tag `0x7F`, known `descriptor_tag_extension`).
440    CustomExtension {
441        /// The `descriptor_tag_extension` byte.
442        tag_extension: u8,
443        /// The parsed, type-erased extension body value. Call `downcast_ref`
444        /// on it (see [`ExtensionObject`](super::extension::registry::ExtensionObject)) to recover the concrete type.
445        #[cfg_attr(
446            feature = "serde",
447            serde(serialize_with = "super::extension::registry::serialize_erased")
448        )]
449        value: Box<dyn super::extension::registry::ExtensionObject>,
450    },
451}
452
453// ---------------------------------------------------------------------------
454// ExtRegistryIter — iterator for iter_with_extensions
455// ---------------------------------------------------------------------------
456
457/// Lazy iterator over a raw descriptor loop, driven by both a
458/// [`DescriptorRegistry`] and an
459/// [`ExtensionRegistry`](super::extension::registry::ExtensionRegistry).
460///
461/// Returned by [`DescriptorLoop::iter_with_extensions`](super::any::DescriptorLoop::iter_with_extensions).
462pub struct ExtRegistryIter<'r, 'a> {
463    desc_reg: &'r DescriptorRegistry,
464    ext_reg: &'r super::extension::registry::ExtensionRegistry,
465    bytes: &'a [u8],
466    pos: usize,
467    fused: bool,
468    current_pds: Option<u32>,
469}
470
471impl<'r, 'a> ExtRegistryIter<'r, 'a> {
472    pub(crate) fn new(
473        desc_reg: &'r DescriptorRegistry,
474        ext_reg: &'r super::extension::registry::ExtensionRegistry,
475        bytes: &'a [u8],
476    ) -> Self {
477        Self {
478            desc_reg,
479            ext_reg,
480            bytes,
481            pos: 0,
482            fused: false,
483            current_pds: None,
484        }
485    }
486}
487
488impl<'r, 'a> Iterator for ExtRegistryIter<'r, 'a> {
489    type Item = crate::Result<ExtIterItem<'a>>;
490
491    fn next(&mut self) -> Option<Self::Item> {
492        let (tag, full) = match crate::descriptors::any::next_loop_entry(
493            self.bytes,
494            &mut self.pos,
495            &mut self.fused,
496        )? {
497            Ok(v) => v,
498            Err(e) => return Some(Err(e)),
499        };
500
501        update_pds(&mut self.current_pds, tag, full);
502
503        let len = full.len() - 2;
504        if tag == crate::descriptors::extension::TAG && len >= 1 {
505            let tag_extension = full[2];
506            if self.ext_reg.has_custom(tag_extension) {
507                return Some(match self.ext_reg.parse_body(tag_extension, &full[3..]) {
508                    Ok(super::extension::registry::RegisteredExtension::Custom {
509                        tag_extension,
510                        value,
511                    }) => Ok(ExtIterItem::CustomExtension {
512                        tag_extension,
513                        value,
514                    }),
515                    Ok(super::extension::registry::RegisteredExtension::Builtin(d)) => {
516                        Ok(ExtIterItem::Descriptor(AnyDescriptor::Extension(d)))
517                    }
518                    Err(e) => Err(e),
519                });
520            }
521        }
522
523        Some(
524            dispatch_entry(self.desc_reg, self.current_pds, tag, full).map(ExtIterItem::Descriptor),
525        )
526    }
527}
528
529impl core::iter::FusedIterator for ExtRegistryIter<'_, '_> {}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534    use crate::descriptors::private_data_specifier;
535    use crate::descriptors::private_data_specifier::{PDS_EACEM, PDS_NORDIG};
536    use crate::traits::DescriptorDef;
537
538    #[test]
539    fn pds_constants_match_the_register() {
540        // Pin the public consts to the vendored TS 101 162 register
541        // (registries/tsPDS.names) so they can't silently drift.
542        assert_eq!(
543            private_data_specifier::private_data_specifier_name(PDS_EACEM),
544            Some("EACEM/EICTA")
545        );
546        assert_eq!(
547            private_data_specifier::private_data_specifier_name(PDS_NORDIG),
548            Some("NorDig")
549        );
550    }
551
552    #[derive(Debug, PartialEq, Eq)]
553    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
554    struct PdsEacem {
555        v: u8,
556    }
557
558    impl<'a> dvb_common::Parse<'a> for PdsEacem {
559        type Error = crate::error::Error;
560        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
561            if bytes.len() < 3 {
562                return Err(crate::error::Error::BufferTooShort {
563                    need: 3,
564                    have: bytes.len(),
565                    what: "PdsEacem",
566                });
567            }
568            Ok(Self { v: bytes[2] })
569        }
570    }
571
572    impl<'a> DescriptorDef<'a> for PdsEacem {
573        const TAG: u8 = 0x83;
574        const NAME: &'static str = "PDS_EACEM";
575    }
576
577    #[derive(Debug, PartialEq, Eq)]
578    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
579    struct PdsNordig {
580        w: u8,
581    }
582
583    impl<'a> dvb_common::Parse<'a> for PdsNordig {
584        type Error = crate::error::Error;
585        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
586            if bytes.len() < 3 {
587                return Err(crate::error::Error::BufferTooShort {
588                    need: 3,
589                    have: bytes.len(),
590                    what: "PdsNordig",
591                });
592            }
593            Ok(Self { w: bytes[2] })
594        }
595    }
596
597    impl<'a> DescriptorDef<'a> for PdsNordig {
598        const TAG: u8 = 0x83;
599        const NAME: &'static str = "PDS_NORDIG";
600    }
601
602    #[derive(Debug, PartialEq, Eq)]
603    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
604    struct PdsAgnostic {
605        z: u8,
606    }
607
608    impl<'a> dvb_common::Parse<'a> for PdsAgnostic {
609        type Error = crate::error::Error;
610        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
611            if bytes.len() < 3 {
612                return Err(crate::error::Error::BufferTooShort {
613                    need: 3,
614                    have: bytes.len(),
615                    what: "PdsAgnostic",
616                });
617            }
618            Ok(Self { z: bytes[2] })
619        }
620    }
621
622    impl<'a> DescriptorDef<'a> for PdsAgnostic {
623        const TAG: u8 = 0x84;
624        const NAME: &'static str = "PDS_AGNOSTIC";
625    }
626
627    fn pds_descriptor(pds: u32) -> Vec<u8> {
628        let mut v = vec![private_data_specifier::TAG, 4];
629        v.extend_from_slice(&pds.to_be_bytes());
630        v
631    }
632
633    #[test]
634    fn pds_scoped_same_tag_resolves_by_pds() {
635        let mut reg = DescriptorRegistry::new();
636        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
637        reg.register_for_pds::<PdsNordig>(PDS_NORDIG);
638
639        let mut bytes = Vec::new();
640        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
641        bytes.extend_from_slice(&[0x83, 0x01, 0xAA]);
642
643        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
644        assert_eq!(items.len(), 2);
645        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
646        match &items[1] {
647            AnyDescriptor::Other { tag, value } => {
648                assert_eq!(*tag, 0x83);
649                let c = value.downcast_ref::<PdsEacem>().unwrap();
650                assert_eq!(c.v, 0xAA);
651            }
652            other => panic!("expected Other (PdsEacem), got {other:?}"),
653        }
654
655        let mut bytes2 = Vec::new();
656        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
657        bytes2.extend_from_slice(&[0x83, 0x01, 0xBB]);
658
659        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
660        match &items2[1] {
661            AnyDescriptor::Other { tag, value } => {
662                assert_eq!(*tag, 0x83);
663                let c = value.downcast_ref::<PdsNordig>().unwrap();
664                assert_eq!(c.w, 0xBB);
665            }
666            other => panic!("expected Other (PdsNordig), got {other:?}"),
667        }
668    }
669
670    #[test]
671    fn pds_scoped_does_not_match_wrong_pds() {
672        let mut reg = DescriptorRegistry::new();
673        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
674
675        let mut bytes = Vec::new();
676        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
677        bytes.extend_from_slice(&[0x83, 0x01, 0xCC]);
678
679        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
680        assert_eq!(items.len(), 2);
681        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
682        match &items[1] {
683            AnyDescriptor::Unknown { tag, .. } => assert_eq!(*tag, 0x83),
684            other => panic!("expected Unknown (wrong PDS), got {other:?}"),
685        }
686    }
687
688    #[test]
689    fn pds_agnostic_matches_without_pds() {
690        let mut reg = DescriptorRegistry::new();
691        reg.register::<PdsAgnostic>();
692
693        let bytes = [0x84, 0x01, 0xDD];
694        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
695        assert_eq!(items.len(), 1);
696        match &items[0] {
697            AnyDescriptor::Other { tag, value } => {
698                assert_eq!(*tag, 0x84);
699                let c = value.downcast_ref::<PdsAgnostic>().unwrap();
700                assert_eq!(c.z, 0xDD);
701            }
702            other => panic!("expected Other, got {other:?}"),
703        }
704    }
705
706    #[test]
707    fn pds_scoped_takes_precedence_over_agnostic() {
708        // Two parsers compete for the SAME tag 0x83: one PDS-agnostic, one
709        // scoped to EACEM. With no PDS active the agnostic one wins; once an
710        // EACEM private_data_specifier appears, the scoped one takes over.
711        #[derive(Debug, PartialEq, Eq)]
712        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
713        struct Agnostic83 {
714            a: u8,
715        }
716
717        impl<'a> dvb_common::Parse<'a> for Agnostic83 {
718            type Error = crate::error::Error;
719            fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
720                if bytes.len() < 3 {
721                    return Err(crate::error::Error::BufferTooShort {
722                        need: 3,
723                        have: bytes.len(),
724                        what: "Agnostic83",
725                    });
726                }
727                Ok(Self { a: bytes[2] })
728            }
729        }
730
731        impl<'a> DescriptorDef<'a> for Agnostic83 {
732            const TAG: u8 = 0x83;
733            const NAME: &'static str = "AGNOSTIC_83";
734        }
735
736        let mut reg = DescriptorRegistry::new();
737        reg.register::<Agnostic83>();
738        reg.register_for_pds::<PdsEacem>(PDS_EACEM);
739
740        // No PDS → agnostic wins
741        let items: Vec<_> = reg
742            .parse_loop(&[0x83, 0x01, 0xEE])
743            .collect::<Result<_, _>>()
744            .unwrap();
745        match &items[0] {
746            AnyDescriptor::Other { value, .. } => {
747                assert!(value.downcast_ref::<Agnostic83>().is_some());
748                assert!(value.downcast_ref::<PdsEacem>().is_none());
749            }
750            other => panic!("expected Other, got {other:?}"),
751        }
752
753        // With PDS EACEM → PDS-scoped wins
754        let mut bytes = Vec::new();
755        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
756        bytes.extend_from_slice(&[0x83, 0x01, 0xFF]);
757        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
758        match &items[1] {
759            AnyDescriptor::Other { value, .. } => {
760                assert!(value.downcast_ref::<PdsEacem>().is_some());
761                assert!(value.downcast_ref::<Agnostic83>().is_none());
762            }
763            other => panic!("expected Other, got {other:?}"),
764        }
765    }
766
767    fn logical_channel_descriptor(service_id: u16, visible: bool, lcn: u16) -> Vec<u8> {
768        // One entry: tag=0x83, len=4, entry(4 bytes)
769        let mut v = vec![crate::descriptors::logical_channel::TAG, 4];
770        v.extend_from_slice(&service_id.to_be_bytes());
771        let flags = (u8::from(visible) << 7) | ((lcn >> 8) as u8 & 0x03);
772        v.push(flags);
773        v.push((lcn & 0xFF) as u8);
774        v
775    }
776
777    #[test]
778    fn logical_channel_pds_scoped_matches_correct_pds() {
779        let mut reg = DescriptorRegistry::new();
780        reg.with_logical_channel_for_pds(PDS_EACEM);
781
782        let mut bytes = Vec::new();
783        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
784        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
785
786        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
787        assert_eq!(items.len(), 2);
788        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
789        match &items[1] {
790            AnyDescriptor::LogicalChannel(lc) => {
791                assert_eq!(lc.entries.len(), 1);
792                assert_eq!(lc.entries[0].service_id, 0x1234);
793                assert!(lc.entries[0].visible_service);
794                assert_eq!(lc.entries[0].logical_channel_number, 101);
795            }
796            other => panic!("expected LogicalChannel, got {other:?}"),
797        }
798    }
799
800    #[test]
801    fn logical_channel_pds_scoped_rejects_no_pds() {
802        let mut reg = DescriptorRegistry::new();
803        reg.with_logical_channel_for_pds(PDS_EACEM);
804
805        let bytes = logical_channel_descriptor(0x1234, true, 101);
806        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
807        assert_eq!(items.len(), 1);
808        assert!(matches!(
809            &items[0],
810            AnyDescriptor::Unknown { tag: 0x83, .. }
811        ));
812    }
813
814    #[test]
815    fn logical_channel_pds_scoped_rejects_wrong_pds() {
816        let mut reg = DescriptorRegistry::new();
817        reg.with_logical_channel_for_pds(PDS_EACEM);
818
819        let mut bytes = Vec::new();
820        bytes.extend_from_slice(&pds_descriptor(PDS_NORDIG));
821        bytes.extend_from_slice(&logical_channel_descriptor(0x1234, true, 101));
822
823        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
824        assert_eq!(items.len(), 2);
825        assert!(matches!(items[0], AnyDescriptor::PrivateDataSpecifier(_)));
826        assert!(matches!(
827            &items[1],
828            AnyDescriptor::Unknown { tag: 0x83, .. }
829        ));
830    }
831
832    #[test]
833    fn logical_channel_pds_scoped_multiple_pds() {
834        let mut reg = DescriptorRegistry::new();
835        reg.with_logical_channel_for_pds(PDS_EACEM);
836        reg.with_logical_channel_for_pds(PDS_NORDIG);
837
838        // EACEM → LogicalChannel
839        let mut bytes = Vec::new();
840        bytes.extend_from_slice(&pds_descriptor(PDS_EACEM));
841        bytes.extend_from_slice(&logical_channel_descriptor(0x0001, true, 1));
842        let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
843        assert!(matches!(&items[1], AnyDescriptor::LogicalChannel(_)));
844
845        // NorDig → LogicalChannel
846        let mut bytes2 = Vec::new();
847        bytes2.extend_from_slice(&pds_descriptor(PDS_NORDIG));
848        bytes2.extend_from_slice(&logical_channel_descriptor(0x0002, false, 2));
849        let items2: Vec<_> = reg.parse_loop(&bytes2).collect::<Result<_, _>>().unwrap();
850        assert!(matches!(&items2[1], AnyDescriptor::LogicalChannel(_)));
851    }
852
853    #[test]
854    fn iter_with_extensions_surfaces_custom_extension() {
855        use crate::descriptors::any::{AnyDescriptor, DescriptorLoop};
856        use crate::descriptors::extension::registry::ExtensionRegistry;
857
858        #[derive(Debug, PartialEq, Eq)]
859        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
860        struct MyCustomExt {
861            payload: Vec<u8>,
862        }
863
864        impl<'a> dvb_common::Parse<'a> for MyCustomExt {
865            type Error = crate::error::Error;
866            fn parse(sel: &'a [u8]) -> crate::Result<Self> {
867                Ok(Self {
868                    payload: sel.to_vec(),
869                })
870            }
871        }
872
873        impl<'a> crate::descriptors::extension::ExtensionBodyDef<'a> for MyCustomExt {
874            const TAG_EXTENSION: u8 = 0x42;
875            const NAME: &'static str = "MY_CUSTOM_EXT";
876        }
877
878        let mut ext_reg = ExtensionRegistry::new();
879        ext_reg.register::<MyCustomExt>();
880
881        let desc_reg = DescriptorRegistry::new();
882
883        // Build a descriptor loop with a short_event + a 0x7F extension with tag_extension 0x42
884        let mut loop_bytes = vec![
885            0x4D, 0x07, b'e', b'n', b'g', 0x02, b'H', b'i', 0x00, // short_event
886        ];
887        // extension: tag=0x7F, length=3, tag_extension=0x42, selector=[0xAB, 0xCD]
888        loop_bytes.extend_from_slice(&[0x7F, 0x03, 0x42, 0xAB, 0xCD]);
889
890        let dl = DescriptorLoop::new(&loop_bytes);
891        let items: Vec<_> = dl
892            .iter_with_extensions(&desc_reg, &ext_reg)
893            .collect::<Result<_, _>>()
894            .unwrap();
895        assert_eq!(items.len(), 2);
896        // First item is a regular descriptor
897        assert!(matches!(
898            &items[0],
899            ExtIterItem::Descriptor(AnyDescriptor::ShortEvent(_))
900        ));
901        // Second item surfaces the custom extension body (not Raw!)
902        match &items[1] {
903            ExtIterItem::CustomExtension {
904                tag_extension,
905                value,
906            } => {
907                assert_eq!(*tag_extension, 0x42);
908                let concrete = value.downcast_ref::<MyCustomExt>().unwrap();
909                assert_eq!(concrete.payload, &[0xAB, 0xCD]);
910            }
911            other => panic!("expected CustomExtension, got {other:?}"),
912        }
913    }
914}