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}