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;
23
24use super::{ExtensionBodyDef, ExtensionDescriptor, validate_and_split};
25use crate::error::Result;
26
27broadcast_common::declare_object_trait! {
28    /// Object-safe face of a runtime-registered extension descriptor value.
29    ///
30    /// Registered types must be owned (`'static`) because the `dyn Any` downcast
31    /// path requires it.  See the [module docs][self] for details.
32    ///
33    /// Implemented automatically via the blanket impl for any `T` satisfying the
34    /// supertraits; you do not need to write this by hand.
35    pub trait ExtensionObject, "extension descriptor"
36}
37
38// ---------------------------------------------------------------------------
39// Internal parse closure type
40// ---------------------------------------------------------------------------
41
42/// A heap-allocated parse closure that takes the selector bytes (everything
43/// after `descriptor_tag_extension`) and returns an owned, type-erased
44/// extension body value.
45pub(crate) type CustomParse =
46    Box<dyn for<'a> Fn(&'a [u8]) -> Result<Box<dyn ExtensionObject>> + Send + Sync>;
47
48// ---------------------------------------------------------------------------
49// ExtensionRegistry
50// ---------------------------------------------------------------------------
51
52/// Runtime-configurable extension-descriptor registry.
53///
54/// By default the registry has no custom parsers; all `descriptor_tag_extension`
55/// values fall through to the built-in [`ExtensionDescriptor`]/[`ExtensionBody`]
56/// dispatch.  Use [`register`][Self::register] to add a custom type.
57///
58/// Call [`parse`][Self::parse] on a full extension_descriptor (tag `0x7F`)
59/// byte slice; it returns a [`RegisteredExtension`].
60///
61/// [`ExtensionBody`]: super::ExtensionBody
62#[derive(Default)]
63pub struct ExtensionRegistry {
64    custom: BTreeMap<u8, CustomParse>,
65}
66
67impl ExtensionRegistry {
68    /// Create an empty registry (built-in dispatch only).
69    #[must_use]
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Returns `true` if a custom parser is registered for the given
75    /// `descriptor_tag_extension`.
76    #[must_use]
77    pub fn has_custom(&self, tag_extension: u8) -> bool {
78        self.custom.contains_key(&tag_extension)
79    }
80
81    /// Register an owned custom extension body type for its
82    /// [`ExtensionBodyDef::TAG_EXTENSION`].
83    ///
84    /// # Owned types only
85    ///
86    /// `T` must be `'static` — no borrowed slices.  The registered value is
87    /// type-erased as `Box<dyn ExtensionObject>`; `dyn Any` downcast requires
88    /// the concrete type to be `'static`.
89    ///
90    /// Re-registering the same `TAG_EXTENSION` replaces the prior custom
91    /// parser (last wins).
92    pub fn register<T>(&mut self) -> &mut Self
93    where
94        T: for<'a> ExtensionBodyDef<'a> + ExtensionObject + 'static,
95    {
96        let tag_ext = T::TAG_EXTENSION;
97        self.custom.insert(
98            tag_ext,
99            Box::new(|sel| {
100                Ok(Box::new(<T as broadcast_common::Parse>::parse(sel)?)
101                    as Box<dyn ExtensionObject>)
102            }),
103        );
104        self
105    }
106
107    /// Parse the already-split (tag_extension, selector) pair into a
108    /// [`RegisteredExtension`], checking for a registered custom parser first
109    /// and falling back to the built-in dispatch.
110    pub fn parse_body<'a>(
111        &self,
112        tag_extension: u8,
113        selector: &'a [u8],
114    ) -> Result<RegisteredExtension<'a>> {
115        if let Some(parse_fn) = self.custom.get(&tag_extension) {
116            let value = parse_fn(selector)?;
117            Ok(RegisteredExtension::Custom {
118                tag_extension,
119                value,
120            })
121        } else {
122            let body = super::parse_body(tag_extension, selector)?;
123            Ok(RegisteredExtension::Builtin(ExtensionDescriptor {
124                tag_extension,
125                body,
126            }))
127        }
128    }
129
130    /// Parse a full extension_descriptor (tag `0x7F`) byte slice.
131    ///
132    /// Validates the tag, length, and minimum body size (same checks as
133    /// `ExtensionDescriptor::parse`).  If the `descriptor_tag_extension`
134    /// has a registered custom parser, returns [`RegisteredExtension::Custom`];
135    /// otherwise returns [`RegisteredExtension::Builtin`] with the standard
136    /// built-in dispatch.
137    pub fn parse<'a>(&self, bytes: &'a [u8]) -> Result<RegisteredExtension<'a>> {
138        let (tag_extension, sel) = validate_and_split(bytes)?;
139        self.parse_body(tag_extension, sel)
140    }
141}
142
143// ---------------------------------------------------------------------------
144// RegisteredExtension
145// ---------------------------------------------------------------------------
146
147/// Output of [`ExtensionRegistry::parse`]: built-in or custom extension.
148#[derive(Debug)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize))]
150#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
151#[non_exhaustive]
152pub enum RegisteredExtension<'a> {
153    /// Built-in [`ExtensionDescriptor`] (no custom parser registered for this
154    /// `descriptor_tag_extension`).
155    Builtin(super::ExtensionDescriptor<'a>),
156    /// Custom-registered extension body.
157    Custom {
158        /// The `descriptor_tag_extension` byte.
159        tag_extension: u8,
160        /// The parsed, type-erased value. Call `downcast_ref` on it (see
161        /// [`ExtensionObject`]) to recover the concrete type.
162        #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_erased"))]
163        value: Box<dyn ExtensionObject>,
164    },
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use crate::descriptors::extension::{ExtensionBodyDef, TAG, TAG_EXTENSION_LEN};
171    use crate::error::Error;
172    use alloc::{vec, vec::Vec};
173
174    const TEST_TAG_EXTENSION: u8 = 0x40;
175
176    #[derive(Debug, PartialEq, Eq)]
177    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
178    struct MyExtBody {
179        payload: Vec<u8>,
180    }
181
182    impl ExtensionBodyDef<'_> for MyExtBody {
183        const TAG_EXTENSION: u8 = TEST_TAG_EXTENSION;
184        const NAME: &'static str = "MY_EXT_BODY";
185    }
186
187    impl<'a> broadcast_common::Parse<'a> for MyExtBody {
188        type Error = crate::error::Error;
189        fn parse(sel: &'a [u8]) -> Result<Self> {
190            Ok(Self {
191                payload: sel.to_vec(),
192            })
193        }
194    }
195
196    fn wrap_ext(tag_ext: u8, sel: &[u8]) -> Vec<u8> {
197        let mut v = vec![TAG, (sel.len() + TAG_EXTENSION_LEN) as u8, tag_ext];
198        v.extend_from_slice(sel);
199        v
200    }
201
202    #[test]
203    fn custom_extension_parsed_and_downcastable() {
204        let mut reg = ExtensionRegistry::new();
205        reg.register::<MyExtBody>();
206
207        let sel = [0xDE, 0xAD, 0xBE];
208        let bytes = wrap_ext(TEST_TAG_EXTENSION, &sel);
209        let re = reg.parse(&bytes).unwrap();
210        match re {
211            RegisteredExtension::Custom {
212                tag_extension,
213                value,
214            } => {
215                assert_eq!(tag_extension, TEST_TAG_EXTENSION);
216                let concrete = value
217                    .downcast_ref::<MyExtBody>()
218                    .expect("downcast should succeed");
219                assert_eq!(concrete.payload, sel);
220            }
221            other => panic!("expected Custom, got {other:?}"),
222        }
223    }
224
225    #[test]
226    fn unregistered_tag_extension_yields_builtin() {
227        use crate::descriptors::extension::ExtensionBody;
228        let reg = ExtensionRegistry::new();
229        // service_relocated (0x0B) has a fixed 6-byte selector, which is simple.
230        let d = crate::descriptors::extension::ExtensionDescriptor {
231            tag_extension: 0x0B,
232            body: ExtensionBody::ServiceRelocated(
233                crate::descriptors::extension::ServiceRelocated {
234                    old_original_network_id: 1,
235                    old_transport_stream_id: 2,
236                    old_service_id: 3,
237                },
238            ),
239        };
240        let mut buf = vec![0u8; d.serialized_len()];
241        use broadcast_common::Serialize;
242        d.serialize_into(&mut buf).unwrap();
243
244        let re = reg.parse(&buf).unwrap();
245        match re {
246            RegisteredExtension::Builtin(d) => {
247                assert_eq!(d.tag_extension, 0x0B);
248                assert!(matches!(d.body, ExtensionBody::ServiceRelocated(_)));
249            }
250            other => panic!("expected Builtin, got {other:?}"),
251        }
252    }
253
254    #[test]
255    fn unknown_tag_extension_yields_builtin_raw() {
256        use crate::descriptors::extension::ExtensionBody;
257        let reg = ExtensionRegistry::new();
258        let sel = [0xAA, 0xBB];
259        let bytes = wrap_ext(0xFE, &sel);
260        let re = reg.parse(&bytes).unwrap();
261        match re {
262            RegisteredExtension::Builtin(d) => {
263                assert_eq!(d.tag_extension, 0xFE);
264                assert!(matches!(d.body, ExtensionBody::Raw(b) if b == sel));
265            }
266            other => panic!("expected Builtin, got {other:?}"),
267        }
268    }
269
270    #[test]
271    fn parse_rejects_wrong_tag() {
272        let reg = ExtensionRegistry::new();
273        let raw = [0x43, 1, 0x04];
274        assert!(matches!(
275            reg.parse(&raw).unwrap_err(),
276            Error::InvalidDescriptor { tag: 0x43, .. }
277        ));
278    }
279
280    #[test]
281    fn parse_rejects_short_buffer() {
282        let reg = ExtensionRegistry::new();
283        let raw = [TAG];
284        assert!(matches!(
285            reg.parse(&raw).unwrap_err(),
286            Error::BufferTooShort(..)
287        ));
288    }
289
290    #[test]
291    fn parse_rejects_empty_body() {
292        let reg = ExtensionRegistry::new();
293        let raw = [TAG, 0];
294        assert!(matches!(
295            reg.parse(&raw).unwrap_err(),
296            Error::InvalidDescriptor { tag: TAG, .. }
297        ));
298    }
299
300    #[cfg(feature = "serde")]
301    #[test]
302    fn custom_variant_serializes_via_erased_serde() {
303        let mut reg = ExtensionRegistry::new();
304        reg.register::<MyExtBody>();
305
306        let bytes = wrap_ext(TEST_TAG_EXTENSION, &[0x01, 0x02]);
307        let re = reg.parse(&bytes).unwrap();
308        let json = serde_json::to_value(&re).unwrap();
309        let custom = json.get("custom").expect("expected 'custom' key");
310        assert_eq!(custom["tag_extension"], TEST_TAG_EXTENSION as u64);
311        assert_eq!(custom["value"]["payload"], serde_json::json!([1, 2]));
312    }
313}