dvb_si/descriptors/extension/registry.rs
1//! Runtime extension-descriptor registry — open registration of client
2//! `descriptor_tag_extension` values.
3//!
4//! [`ExtensionRegistry`] mirrors [`DescriptorRegistry`][super::super::registry::DescriptorRegistry]
5//! for the second-level dispatch inside an extension_descriptor (tag `0x7F`):
6//! a registered custom `descriptor_tag_extension` is parsed into a client-supplied
7//! owned type and surfaced as [`RegisteredExtension::Custom`]; unregistered
8//! extensions fall through to the built-in [`ExtensionDescriptor`] via
9//! [`RegisteredExtension::Builtin`].
10//!
11//! # Owned types only
12//!
13//! Registered types must be `'static` (i.e. owned — no borrowed slices).
14//! This is required because the parsed value is heap-allocated as a
15//! `Box<dyn ExtensionObject>` whose concrete type is erased; `dyn Any`
16//! downcast demands `'static`. If your wire layout contains borrowed bytes,
17//! copy them into a `Vec<u8>` in the struct.
18//!
19//! [`DescriptorRegistry`]: super::super::registry::DescriptorRegistry
20
21use alloc::boxed::Box;
22use alloc::collections::BTreeMap;
23use core::any::Any;
24
25use super::{validate_and_split, ExtensionBodyDef, ExtensionDescriptor};
26use crate::error::Result;
27
28// ---------------------------------------------------------------------------
29// ExtensionObject trait
30// ---------------------------------------------------------------------------
31
32/// Object-safe face of a runtime-registered extension body value.
33///
34/// Registered types must be owned (`'static`) because the `dyn Any` downcast
35/// path requires it. See the [module docs][self] for details.
36///
37/// Implemented automatically via the blanket impl for any `T` satisfying the
38/// supertraits; you do not need to write this by hand.
39#[cfg(not(feature = "serde"))]
40pub trait ExtensionObject: core::fmt::Debug + Any + Send + Sync {
41 /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
42 fn as_any(&self) -> &dyn Any;
43}
44
45/// Object-safe face of a runtime-registered extension body value.
46///
47/// Registered types must be owned (`'static`) because the `dyn Any` downcast
48/// path requires it. See the [module docs][self] for details.
49///
50/// Implemented automatically via the blanket impl for any `T` satisfying the
51/// supertraits; you do not need to write this by hand.
52#[cfg(feature = "serde")]
53pub trait ExtensionObject: core::fmt::Debug + Any + Send + Sync + erased_serde::Serialize {
54 /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
55 fn as_any(&self) -> &dyn Any;
56}
57
58// Blanket impl — no-serde arm.
59#[cfg(not(feature = "serde"))]
60impl<T> ExtensionObject for T
61where
62 T: core::fmt::Debug + Any + Send + Sync,
63{
64 fn as_any(&self) -> &dyn Any {
65 self
66 }
67}
68
69// Blanket impl — serde arm.
70#[cfg(feature = "serde")]
71impl<T> ExtensionObject for T
72where
73 T: core::fmt::Debug + Any + Send + Sync + serde::Serialize,
74{
75 fn as_any(&self) -> &dyn Any {
76 self
77 }
78}
79
80// Downcast helpers ON THE TRAIT OBJECT (not the blanket).
81//
82// The blanket `impl<T> ExtensionObject for T` also covers `Box<dyn
83// ExtensionObject>` itself whenever the box satisfies the bounds — it does
84// under `--no-default-features`, where the bound is just `Debug + Any + Send +
85// Sync`. So `the_box.as_any()` resolves to the *box's* impl and reports the
86// box's `TypeId`, not the inner value's — a silent downcast failure. (Under
87// `serde` the extra `serde::Serialize` bound excludes the box, which is why the
88// footgun only bites without default features.) Calling through `dyn
89// ExtensionObject` (which `Box` derefs to) always hits the inner value, so
90// always downcast via these methods rather than `the_box.as_any()`.
91impl dyn ExtensionObject {
92 /// Downcast a registered extension body to its concrete type `T`.
93 ///
94 /// Works for `Box<dyn ExtensionObject>` (it derefs to the trait object)
95 /// under every feature configuration.
96 #[must_use]
97 pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
98 self.as_any().downcast_ref::<T>()
99 }
100
101 /// `true` if the registered extension body's concrete type is `T`.
102 #[must_use]
103 pub fn is<T: Any>(&self) -> bool {
104 self.as_any().is::<T>()
105 }
106}
107
108// ---------------------------------------------------------------------------
109// Erased serialisation helper (serde-gated)
110// ---------------------------------------------------------------------------
111
112/// `serialize_with` helper used on [`RegisteredExtension::Custom`]'s `value`
113/// field.
114///
115/// Delegates to [`erased_serde::serialize`] so the concrete type's
116/// `serde::Serialize` impl is invoked through the trait object.
117///
118/// The `&Box<T>` is required by serde's `serialize_with` codegen — the field
119/// type is `Box<dyn ExtensionObject>` so serde passes `&Box<dyn ExtensionObject>`.
120#[cfg(feature = "serde")]
121#[allow(clippy::borrowed_box)]
122pub(crate) fn serialize_erased<S: serde::Serializer>(
123 v: &Box<dyn ExtensionObject>,
124 s: S,
125) -> core::result::Result<S::Ok, S::Error> {
126 erased_serde::serialize(&**v, s)
127}
128
129// ---------------------------------------------------------------------------
130// Internal parse closure type
131// ---------------------------------------------------------------------------
132
133/// A heap-allocated parse closure that takes the selector bytes (everything
134/// after `descriptor_tag_extension`) and returns an owned, type-erased
135/// extension body value.
136pub(crate) type CustomParse =
137 Box<dyn for<'a> Fn(&'a [u8]) -> Result<Box<dyn ExtensionObject>> + Send + Sync>;
138
139// ---------------------------------------------------------------------------
140// ExtensionRegistry
141// ---------------------------------------------------------------------------
142
143/// Runtime-configurable extension-descriptor registry.
144///
145/// By default the registry has no custom parsers; all `descriptor_tag_extension`
146/// values fall through to the built-in [`ExtensionDescriptor`]/[`ExtensionBody`]
147/// dispatch. Use [`register`][Self::register] to add a custom type.
148///
149/// Call [`parse`][Self::parse] on a full extension_descriptor (tag `0x7F`)
150/// byte slice; it returns a [`RegisteredExtension`].
151///
152/// [`ExtensionBody`]: super::ExtensionBody
153#[derive(Default)]
154pub struct ExtensionRegistry {
155 custom: BTreeMap<u8, CustomParse>,
156}
157
158impl ExtensionRegistry {
159 /// Create an empty registry (built-in dispatch only).
160 #[must_use]
161 pub fn new() -> Self {
162 Self::default()
163 }
164
165 /// Returns `true` if a custom parser is registered for the given
166 /// `descriptor_tag_extension`.
167 #[must_use]
168 pub fn has_custom(&self, tag_extension: u8) -> bool {
169 self.custom.contains_key(&tag_extension)
170 }
171
172 /// Register an owned custom extension body type for its
173 /// [`ExtensionBodyDef::TAG_EXTENSION`].
174 ///
175 /// # Owned types only
176 ///
177 /// `T` must be `'static` — no borrowed slices. The registered value is
178 /// type-erased as `Box<dyn ExtensionObject>`; `dyn Any` downcast requires
179 /// the concrete type to be `'static`.
180 ///
181 /// Re-registering the same `TAG_EXTENSION` replaces the prior custom
182 /// parser (last wins).
183 pub fn register<T>(&mut self) -> &mut Self
184 where
185 T: for<'a> ExtensionBodyDef<'a> + ExtensionObject + 'static,
186 {
187 let tag_ext = T::TAG_EXTENSION;
188 self.custom.insert(
189 tag_ext,
190 Box::new(|sel| {
191 Ok(Box::new(<T as dvb_common::Parse>::parse(sel)?) as Box<dyn ExtensionObject>)
192 }),
193 );
194 self
195 }
196
197 /// Parse the already-split (tag_extension, selector) pair into a
198 /// [`RegisteredExtension`], checking for a registered custom parser first
199 /// and falling back to the built-in dispatch.
200 pub fn parse_body<'a>(
201 &self,
202 tag_extension: u8,
203 selector: &'a [u8],
204 ) -> Result<RegisteredExtension<'a>> {
205 if let Some(parse_fn) = self.custom.get(&tag_extension) {
206 let value = parse_fn(selector)?;
207 Ok(RegisteredExtension::Custom {
208 tag_extension,
209 value,
210 })
211 } else {
212 let body = super::parse_body(tag_extension, selector)?;
213 Ok(RegisteredExtension::Builtin(ExtensionDescriptor {
214 tag_extension,
215 body,
216 }))
217 }
218 }
219
220 /// Parse a full extension_descriptor (tag `0x7F`) byte slice.
221 ///
222 /// Validates the tag, length, and minimum body size (same checks as
223 /// `ExtensionDescriptor::parse`). If the `descriptor_tag_extension`
224 /// has a registered custom parser, returns [`RegisteredExtension::Custom`];
225 /// otherwise returns [`RegisteredExtension::Builtin`] with the standard
226 /// built-in dispatch.
227 pub fn parse<'a>(&self, bytes: &'a [u8]) -> Result<RegisteredExtension<'a>> {
228 let (tag_extension, sel) = validate_and_split(bytes)?;
229 self.parse_body(tag_extension, sel)
230 }
231}
232
233// ---------------------------------------------------------------------------
234// RegisteredExtension
235// ---------------------------------------------------------------------------
236
237/// Output of [`ExtensionRegistry::parse`]: built-in or custom extension.
238#[derive(Debug)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize))]
240#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
241#[non_exhaustive]
242pub enum RegisteredExtension<'a> {
243 /// Built-in [`ExtensionDescriptor`] (no custom parser registered for this
244 /// `descriptor_tag_extension`).
245 Builtin(super::ExtensionDescriptor<'a>),
246 /// Custom-registered extension body.
247 Custom {
248 /// The `descriptor_tag_extension` byte.
249 tag_extension: u8,
250 /// The parsed, type-erased value. Call `downcast_ref` on it (see
251 /// [`ExtensionObject`]) to recover the concrete type.
252 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_erased"))]
253 value: Box<dyn ExtensionObject>,
254 },
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260 use crate::descriptors::extension::{ExtensionBodyDef, TAG, TAG_EXTENSION_LEN};
261 use crate::error::Error;
262
263 const TEST_TAG_EXTENSION: u8 = 0x40;
264
265 #[derive(Debug, PartialEq, Eq)]
266 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
267 struct MyExtBody {
268 payload: Vec<u8>,
269 }
270
271 impl<'a> ExtensionBodyDef<'a> for MyExtBody {
272 const TAG_EXTENSION: u8 = TEST_TAG_EXTENSION;
273 const NAME: &'static str = "MY_EXT_BODY";
274 }
275
276 impl<'a> dvb_common::Parse<'a> for MyExtBody {
277 type Error = crate::error::Error;
278 fn parse(sel: &'a [u8]) -> Result<Self> {
279 Ok(Self {
280 payload: sel.to_vec(),
281 })
282 }
283 }
284
285 fn wrap_ext(tag_ext: u8, sel: &[u8]) -> Vec<u8> {
286 let mut v = vec![TAG, (sel.len() + TAG_EXTENSION_LEN) as u8, tag_ext];
287 v.extend_from_slice(sel);
288 v
289 }
290
291 #[test]
292 fn custom_extension_parsed_and_downcastable() {
293 let mut reg = ExtensionRegistry::new();
294 reg.register::<MyExtBody>();
295
296 let sel = [0xDE, 0xAD, 0xBE];
297 let bytes = wrap_ext(TEST_TAG_EXTENSION, &sel);
298 let re = reg.parse(&bytes).unwrap();
299 match re {
300 RegisteredExtension::Custom {
301 tag_extension,
302 value,
303 } => {
304 assert_eq!(tag_extension, TEST_TAG_EXTENSION);
305 let concrete = value
306 .downcast_ref::<MyExtBody>()
307 .expect("downcast should succeed");
308 assert_eq!(concrete.payload, sel);
309 }
310 other => panic!("expected Custom, got {other:?}"),
311 }
312 }
313
314 #[test]
315 fn unregistered_tag_extension_yields_builtin() {
316 use crate::descriptors::extension::ExtensionBody;
317 let reg = ExtensionRegistry::new();
318 // service_relocated (0x0B) has a fixed 6-byte selector, which is simple.
319 let d = crate::descriptors::extension::ExtensionDescriptor {
320 tag_extension: 0x0B,
321 body: ExtensionBody::ServiceRelocated(
322 crate::descriptors::extension::ServiceRelocated {
323 old_original_network_id: 1,
324 old_transport_stream_id: 2,
325 old_service_id: 3,
326 },
327 ),
328 };
329 let mut buf = vec![0u8; d.serialized_len()];
330 use dvb_common::Serialize;
331 d.serialize_into(&mut buf).unwrap();
332
333 let re = reg.parse(&buf).unwrap();
334 match re {
335 RegisteredExtension::Builtin(d) => {
336 assert_eq!(d.tag_extension, 0x0B);
337 assert!(matches!(d.body, ExtensionBody::ServiceRelocated(_)));
338 }
339 other => panic!("expected Builtin, got {other:?}"),
340 }
341 }
342
343 #[test]
344 fn unknown_tag_extension_yields_builtin_raw() {
345 use crate::descriptors::extension::ExtensionBody;
346 let reg = ExtensionRegistry::new();
347 let sel = [0xAA, 0xBB];
348 let bytes = wrap_ext(0xFE, &sel);
349 let re = reg.parse(&bytes).unwrap();
350 match re {
351 RegisteredExtension::Builtin(d) => {
352 assert_eq!(d.tag_extension, 0xFE);
353 assert!(matches!(d.body, ExtensionBody::Raw(b) if b == sel));
354 }
355 other => panic!("expected Builtin, got {other:?}"),
356 }
357 }
358
359 #[test]
360 fn parse_rejects_wrong_tag() {
361 let reg = ExtensionRegistry::new();
362 let raw = [0x43, 1, 0x04];
363 assert!(matches!(
364 reg.parse(&raw).unwrap_err(),
365 Error::InvalidDescriptor { tag: 0x43, .. }
366 ));
367 }
368
369 #[test]
370 fn parse_rejects_short_buffer() {
371 let reg = ExtensionRegistry::new();
372 let raw = [TAG];
373 assert!(matches!(
374 reg.parse(&raw).unwrap_err(),
375 Error::BufferTooShort { .. }
376 ));
377 }
378
379 #[test]
380 fn parse_rejects_empty_body() {
381 let reg = ExtensionRegistry::new();
382 let raw = [TAG, 0];
383 assert!(matches!(
384 reg.parse(&raw).unwrap_err(),
385 Error::InvalidDescriptor { tag: TAG, .. }
386 ));
387 }
388
389 #[cfg(feature = "serde")]
390 #[test]
391 fn custom_variant_serializes_via_erased_serde() {
392 let mut reg = ExtensionRegistry::new();
393 reg.register::<MyExtBody>();
394
395 let bytes = wrap_ext(TEST_TAG_EXTENSION, &[0x01, 0x02]);
396 let re = reg.parse(&bytes).unwrap();
397 let json = serde_json::to_value(&re).unwrap();
398 let custom = json.get("custom").expect("expected 'custom' key");
399 assert_eq!(custom["tag_extension"], TEST_TAG_EXTENSION as u64);
400 assert_eq!(custom["value"]["payload"], serde_json::json!([1, 2]));
401 }
402}