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