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