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