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<'_, '_> {}