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 broadcast_common::Parse>::parse(sel)?)
192 as Box<dyn ExtensionObject>)
193 }),
194 );
195 self
196 }
197
198 /// Parse the already-split (tag_extension, selector) pair into a
199 /// [`RegisteredExtension`], checking for a registered custom parser first
200 /// and falling back to the built-in dispatch.
201 pub fn parse_body<'a>(
202 &self,
203 tag_extension: u8,
204 selector: &'a [u8],
205 ) -> Result<RegisteredExtension<'a>> {
206 if let Some(parse_fn) = self.custom.get(&tag_extension) {
207 let value = parse_fn(selector)?;
208 Ok(RegisteredExtension::Custom {
209 tag_extension,
210 value,
211 })
212 } else {
213 let body = super::parse_body(tag_extension, selector)?;
214 Ok(RegisteredExtension::Builtin(ExtensionDescriptor {
215 tag_extension,
216 body,
217 }))
218 }
219 }
220
221 /// Parse a full extension_descriptor (tag `0x7F`) byte slice.
222 ///
223 /// Validates the tag, length, and minimum body size (same checks as
224 /// `ExtensionDescriptor::parse`). If the `descriptor_tag_extension`
225 /// has a registered custom parser, returns [`RegisteredExtension::Custom`];
226 /// otherwise returns [`RegisteredExtension::Builtin`] with the standard
227 /// built-in dispatch.
228 pub fn parse<'a>(&self, bytes: &'a [u8]) -> Result<RegisteredExtension<'a>> {
229 let (tag_extension, sel) = validate_and_split(bytes)?;
230 self.parse_body(tag_extension, sel)
231 }
232}
233
234// ---------------------------------------------------------------------------
235// RegisteredExtension
236// ---------------------------------------------------------------------------
237
238/// Output of [`ExtensionRegistry::parse`]: built-in or custom extension.
239#[derive(Debug)]
240#[cfg_attr(feature = "serde", derive(serde::Serialize))]
241#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
242#[non_exhaustive]
243pub enum RegisteredExtension<'a> {
244 /// Built-in [`ExtensionDescriptor`] (no custom parser registered for this
245 /// `descriptor_tag_extension`).
246 Builtin(super::ExtensionDescriptor<'a>),
247 /// Custom-registered extension body.
248 Custom {
249 /// The `descriptor_tag_extension` byte.
250 tag_extension: u8,
251 /// The parsed, type-erased value. Call `downcast_ref` on it (see
252 /// [`ExtensionObject`]) to recover the concrete type.
253 #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_erased"))]
254 value: Box<dyn ExtensionObject>,
255 },
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::descriptors::extension::{ExtensionBodyDef, TAG, TAG_EXTENSION_LEN};
262 use crate::error::Error;
263
264 const TEST_TAG_EXTENSION: u8 = 0x40;
265
266 #[derive(Debug, PartialEq, Eq)]
267 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
268 struct MyExtBody {
269 payload: Vec<u8>,
270 }
271
272 impl<'a> ExtensionBodyDef<'a> for MyExtBody {
273 const TAG_EXTENSION: u8 = TEST_TAG_EXTENSION;
274 const NAME: &'static str = "MY_EXT_BODY";
275 }
276
277 impl<'a> broadcast_common::Parse<'a> for MyExtBody {
278 type Error = crate::error::Error;
279 fn parse(sel: &'a [u8]) -> Result<Self> {
280 Ok(Self {
281 payload: sel.to_vec(),
282 })
283 }
284 }
285
286 fn wrap_ext(tag_ext: u8, sel: &[u8]) -> Vec<u8> {
287 let mut v = vec![TAG, (sel.len() + TAG_EXTENSION_LEN) as u8, tag_ext];
288 v.extend_from_slice(sel);
289 v
290 }
291
292 #[test]
293 fn custom_extension_parsed_and_downcastable() {
294 let mut reg = ExtensionRegistry::new();
295 reg.register::<MyExtBody>();
296
297 let sel = [0xDE, 0xAD, 0xBE];
298 let bytes = wrap_ext(TEST_TAG_EXTENSION, &sel);
299 let re = reg.parse(&bytes).unwrap();
300 match re {
301 RegisteredExtension::Custom {
302 tag_extension,
303 value,
304 } => {
305 assert_eq!(tag_extension, TEST_TAG_EXTENSION);
306 let concrete = value
307 .downcast_ref::<MyExtBody>()
308 .expect("downcast should succeed");
309 assert_eq!(concrete.payload, sel);
310 }
311 other => panic!("expected Custom, got {other:?}"),
312 }
313 }
314
315 #[test]
316 fn unregistered_tag_extension_yields_builtin() {
317 use crate::descriptors::extension::ExtensionBody;
318 let reg = ExtensionRegistry::new();
319 // service_relocated (0x0B) has a fixed 6-byte selector, which is simple.
320 let d = crate::descriptors::extension::ExtensionDescriptor {
321 tag_extension: 0x0B,
322 body: ExtensionBody::ServiceRelocated(
323 crate::descriptors::extension::ServiceRelocated {
324 old_original_network_id: 1,
325 old_transport_stream_id: 2,
326 old_service_id: 3,
327 },
328 ),
329 };
330 let mut buf = vec![0u8; d.serialized_len()];
331 use broadcast_common::Serialize;
332 d.serialize_into(&mut buf).unwrap();
333
334 let re = reg.parse(&buf).unwrap();
335 match re {
336 RegisteredExtension::Builtin(d) => {
337 assert_eq!(d.tag_extension, 0x0B);
338 assert!(matches!(d.body, ExtensionBody::ServiceRelocated(_)));
339 }
340 other => panic!("expected Builtin, got {other:?}"),
341 }
342 }
343
344 #[test]
345 fn unknown_tag_extension_yields_builtin_raw() {
346 use crate::descriptors::extension::ExtensionBody;
347 let reg = ExtensionRegistry::new();
348 let sel = [0xAA, 0xBB];
349 let bytes = wrap_ext(0xFE, &sel);
350 let re = reg.parse(&bytes).unwrap();
351 match re {
352 RegisteredExtension::Builtin(d) => {
353 assert_eq!(d.tag_extension, 0xFE);
354 assert!(matches!(d.body, ExtensionBody::Raw(b) if b == sel));
355 }
356 other => panic!("expected Builtin, got {other:?}"),
357 }
358 }
359
360 #[test]
361 fn parse_rejects_wrong_tag() {
362 let reg = ExtensionRegistry::new();
363 let raw = [0x43, 1, 0x04];
364 assert!(matches!(
365 reg.parse(&raw).unwrap_err(),
366 Error::InvalidDescriptor { tag: 0x43, .. }
367 ));
368 }
369
370 #[test]
371 fn parse_rejects_short_buffer() {
372 let reg = ExtensionRegistry::new();
373 let raw = [TAG];
374 assert!(matches!(
375 reg.parse(&raw).unwrap_err(),
376 Error::BufferTooShort { .. }
377 ));
378 }
379
380 #[test]
381 fn parse_rejects_empty_body() {
382 let reg = ExtensionRegistry::new();
383 let raw = [TAG, 0];
384 assert!(matches!(
385 reg.parse(&raw).unwrap_err(),
386 Error::InvalidDescriptor { tag: TAG, .. }
387 ));
388 }
389
390 #[cfg(feature = "serde")]
391 #[test]
392 fn custom_variant_serializes_via_erased_serde() {
393 let mut reg = ExtensionRegistry::new();
394 reg.register::<MyExtBody>();
395
396 let bytes = wrap_ext(TEST_TAG_EXTENSION, &[0x01, 0x02]);
397 let re = reg.parse(&bytes).unwrap();
398 let json = serde_json::to_value(&re).unwrap();
399 let custom = json.get("custom").expect("expected 'custom' key");
400 assert_eq!(custom["tag_extension"], TEST_TAG_EXTENSION as u64);
401 assert_eq!(custom["value"]["payload"], serde_json::json!([1, 2]));
402 }
403}