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//! # Owned types only
10//!
11//! Registered types must be `'static` (i.e. owned — no borrowed slices).
12//! This is required because the parsed value is heap-allocated as a
13//! `Box<dyn DescriptorObject>` whose concrete type is erased; `dyn Any`
14//! downcast demands `'static`.  If your wire layout contains borrowed bytes,
15//! copy them into a `Vec<u8>` in the struct.
16//!
17//! # Example
18//!
19//! ```rust,no_run
20//! use dvb_si::descriptors::{DescriptorRegistry, DescriptorObject, AnyDescriptor};
21//! use dvb_si::traits::DescriptorDef;
22//! use dvb_common::Parse;
23//!
24//! #[derive(Debug, serde::Serialize)]
25//! struct MyPrivate { x: u8 }
26//!
27//! impl<'a> Parse<'a> for MyPrivate {
28//!     type Error = dvb_si::Error;
29//!     fn parse(bytes: &'a [u8]) -> dvb_si::Result<Self> {
30//!         if bytes.len() < 3 {
31//!             return Err(dvb_si::Error::BufferTooShort {
32//!                 need: 3, have: bytes.len(), what: "MyPrivate",
33//!             });
34//!         }
35//!         Ok(Self { x: bytes[2] })
36//!     }
37//! }
38//!
39//! impl<'a> DescriptorDef<'a> for MyPrivate {
40//!     const TAG: u8 = 0xA7;
41//!     const NAME: &'static str = "MY_PRIVATE";
42//! }
43//!
44//! let mut reg = DescriptorRegistry::new();
45//! reg.register::<MyPrivate>().with_logical_channel();
46//!
47//! let bytes = [0xA7, 0x01, 0x42u8];
48//! let items: Vec<_> = reg.parse_loop(&bytes).collect::<Result<_, _>>().unwrap();
49//! if let AnyDescriptor::Other { tag, ref value } = items[0] {
50//!     assert_eq!(tag, 0xA7);
51//!     assert_eq!(value.as_any().downcast_ref::<MyPrivate>().unwrap().x, 0x42);
52//! }
53//! ```
54
55use std::any::Any;
56use std::collections::HashMap;
57
58use crate::descriptors::any::AnyDescriptor;
59
60// ---------------------------------------------------------------------------
61// DescriptorObject trait
62// ---------------------------------------------------------------------------
63
64/// Object-safe face of a runtime-registered descriptor value.
65///
66/// Registered types must be owned (`'static`) because the `dyn Any` downcast
67/// path requires it.  See the [module docs][self] for details.
68///
69/// Implemented automatically via the blanket impl for any `T` satisfying the
70/// supertraits; you do not need to write this by hand.
71#[cfg(not(feature = "serde"))]
72pub trait DescriptorObject: std::fmt::Debug + Any + Send + Sync {
73    /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
74    fn as_any(&self) -> &dyn Any;
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(feature = "serde")]
85pub trait DescriptorObject: std::fmt::Debug + Any + Send + Sync + erased_serde::Serialize {
86    /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
87    fn as_any(&self) -> &dyn Any;
88}
89
90// Blanket impl — no-serde arm.
91#[cfg(not(feature = "serde"))]
92impl<T> DescriptorObject for T
93where
94    T: std::fmt::Debug + Any + Send + Sync,
95{
96    fn as_any(&self) -> &dyn Any {
97        self
98    }
99}
100
101// Blanket impl — serde arm.
102#[cfg(feature = "serde")]
103impl<T> DescriptorObject for T
104where
105    T: std::fmt::Debug + Any + Send + Sync + serde::Serialize,
106{
107    fn as_any(&self) -> &dyn Any {
108        self
109    }
110}
111
112// ---------------------------------------------------------------------------
113// Erased serialisation helper (serde-gated)
114// ---------------------------------------------------------------------------
115
116/// `serialize_with` helper used on [`AnyDescriptor::Other`]'s `value` field.
117///
118/// Delegates to [`erased_serde::serialize`] so the concrete type's
119/// `serde::Serialize` impl is invoked through the trait object.
120///
121/// The `&Box<T>` is required by serde's `serialize_with` codegen — the field
122/// type is `Box<dyn DescriptorObject>` so serde passes `&Box<dyn DescriptorObject>`.
123#[cfg(feature = "serde")]
124#[allow(clippy::borrowed_box)]
125pub(crate) fn serialize_erased<S: serde::Serializer>(
126    v: &Box<dyn DescriptorObject>,
127    s: S,
128) -> Result<S::Ok, S::Error> {
129    erased_serde::serialize(&**v, s)
130}
131
132// ---------------------------------------------------------------------------
133// Internal parse closure type
134// ---------------------------------------------------------------------------
135
136/// A heap-allocated parse closure that takes a full descriptor (header + body)
137/// and returns an owned, type-erased descriptor value.
138pub(crate) type CustomParse =
139    Box<dyn for<'a> Fn(&'a [u8]) -> crate::Result<Box<dyn DescriptorObject>> + Send + Sync>;
140
141// ---------------------------------------------------------------------------
142// DescriptorRegistry
143// ---------------------------------------------------------------------------
144
145/// Runtime-configurable descriptor registry.
146///
147/// By default the registry has no custom parsers and 0x83 logical_channel is
148/// disabled (it is a private tag that requires `private_data_specifier`
149/// context).  Use [`register`][Self::register] and
150/// [`with_logical_channel`][Self::with_logical_channel] to opt in.
151///
152/// Walk a byte slice with [`parse_loop`][Self::parse_loop]; it returns a lazy
153/// [`RegistryIter`] with identical truncation/fuse/error-continue semantics to
154/// the free [`crate::descriptors::parse_loop`].
155///
156/// # Precedence (per entry)
157///
158/// 1. Custom-registered parser (tag in the [`custom`][Self::register] map) →
159///    [`AnyDescriptor::Other`]
160/// 2. Logical-channel opt-in (tag 0x83 + [`with_logical_channel`][Self::with_logical_channel]
161///    enabled) → [`AnyDescriptor::LogicalChannel`]
162/// 3. Built-in dispatch (internal `AnyDescriptor::dispatch`) → typed variant
163/// 4. Unknown → [`AnyDescriptor::Unknown`]
164#[derive(Default)]
165pub struct DescriptorRegistry {
166    custom: HashMap<u8, CustomParse>,
167    logical_channel: bool,
168}
169
170impl DescriptorRegistry {
171    /// Create an empty registry (built-in dispatch only; 0x83 disabled).
172    #[must_use]
173    pub fn new() -> Self {
174        Self::default()
175    }
176
177    /// Register an owned custom descriptor type for its
178    /// [`DescriptorDef::TAG`][crate::traits::DescriptorDef::TAG].
179    ///
180    /// # Owned types only
181    ///
182    /// `T` must be `'static` — no borrowed slices.  The registered value is
183    /// type-erased as `Box<dyn DescriptorObject>`; `dyn Any` downcast requires
184    /// the concrete type to be `'static`.
185    ///
186    /// Registering a type whose `TAG` is already used by a built-in **overrides**
187    /// the built-in for that tag.
188    ///
189    /// Re-registering the same tag replaces the prior custom parser (last wins).
190    /// A failing custom parse surfaces the client's `Parse::Error` unwrapped —
191    /// embed identifying context (type/tag) in your error's `what`/`reason` fields.
192    pub fn register<T>(&mut self) -> &mut Self
193    where
194        T: for<'a> crate::traits::DescriptorDef<'a> + DescriptorObject + 'static,
195    {
196        // We need to name the TAG without a lifetime — use the 'static elision.
197        // `for<'a> DescriptorDef<'a>` guarantees the const is the same for all
198        // lifetimes, so calling it with 'static here is fine.
199        let tag = <T as crate::traits::DescriptorDef<'static>>::TAG;
200        self.custom.insert(
201            tag,
202            Box::new(|b| {
203                Ok(Box::new(<T as dvb_common::Parse>::parse(b)?) as Box<dyn DescriptorObject>)
204            }),
205        );
206        self
207    }
208
209    /// Enable the 0x83 logical_channel built-in.
210    ///
211    /// By default 0x83 is not auto-dispatched because it is a private tag
212    /// whose semantics depend on a `private_data_specifier` context.  Call
213    /// this when you know the loop is from an EACEM/NorDig/D-Book stream.
214    pub fn with_logical_channel(&mut self) -> &mut Self {
215        self.logical_channel = true;
216        self
217    }
218
219    /// Lazily walk a raw descriptor loop using this registry's configuration.
220    ///
221    /// Semantics mirror [`crate::descriptors::parse_loop`]: per-descriptor
222    /// parse errors yield `Err` and iteration continues; a truncated final
223    /// header or body yields one `Err` then fuses.
224    #[must_use]
225    pub fn parse_loop<'r, 'a>(&'r self, bytes: &'a [u8]) -> RegistryIter<'r, 'a> {
226        RegistryIter {
227            registry: self,
228            bytes,
229            pos: 0,
230            fused: false,
231        }
232    }
233}
234
235// ---------------------------------------------------------------------------
236// RegistryIter
237// ---------------------------------------------------------------------------
238
239/// Lazy iterator over a raw descriptor loop, driven by a [`DescriptorRegistry`].
240///
241/// Returned by [`DescriptorRegistry::parse_loop`].
242pub struct RegistryIter<'r, 'a> {
243    registry: &'r DescriptorRegistry,
244    bytes: &'a [u8],
245    pos: usize,
246    fused: bool,
247}
248
249impl<'r, 'a> Iterator for RegistryIter<'r, 'a> {
250    type Item = crate::Result<AnyDescriptor<'a>>;
251
252    fn next(&mut self) -> Option<Self::Item> {
253        if self.fused || self.pos >= self.bytes.len() {
254            return None;
255        }
256        let rem = &self.bytes[self.pos..];
257        // --- shared loop-walk arithmetic (mirrors DescriptorIter::next) ---
258        if rem.len() < 2 {
259            self.fused = true;
260            return Some(Err(crate::Error::BufferTooShort {
261                need: 2,
262                have: rem.len(),
263                what: "descriptor header in loop",
264            }));
265        }
266        let tag = rem[0];
267        let len = rem[1] as usize;
268        let total = 2 + len;
269        if rem.len() < total {
270            self.fused = true;
271            return Some(Err(crate::Error::BufferTooShort {
272                need: total,
273                have: rem.len(),
274                what: "descriptor body in loop",
275            }));
276        }
277        let full = &rem[..total];
278        self.pos += total;
279        // --- precedence ---
280        // 1. Custom-registered parser
281        if let Some(parse_fn) = self.registry.custom.get(&tag) {
282            return Some(match parse_fn(full) {
283                Ok(value) => Ok(AnyDescriptor::Other { tag, value }),
284                Err(e) => Err(e),
285            });
286        }
287        // 2. Logical-channel opt-in (0x83)
288        if self.registry.logical_channel && tag == crate::descriptors::logical_channel::TAG {
289            use dvb_common::Parse;
290            return Some(
291                crate::descriptors::logical_channel::LogicalChannelDescriptor::parse(full)
292                    .map(AnyDescriptor::LogicalChannel),
293            );
294        }
295        // 3. Built-in dispatch
296        if let Some(res) = AnyDescriptor::dispatch(tag, full) {
297            return Some(res);
298        }
299        // 4. Unknown
300        Some(Ok(AnyDescriptor::Unknown {
301            tag,
302            body: &full[2..],
303        }))
304    }
305}
306
307impl std::iter::FusedIterator for RegistryIter<'_, '_> {}