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 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}