Skip to main content

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}