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