Skip to main content

dvb_si/descriptors/
registry.rs

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