Skip to main content

dvb_t2mi/payload/
registry.rs

1//! Runtime payload registry — open registration of client private packet types.
2//!
3//! [`PayloadRegistry`] is a runtime-configurable dispatch engine that allows
4//! clients to register their own private T2-MI payload types alongside (or in
5//! place of) the built-ins.  Registered custom parsers win over built-in
6//! dispatch when [`crate::payload::AnyPayload::dispatch_with`] is used.
7//!
8//! # Owned types only
9//!
10//! Registered types must be `'static` (i.e. owned — no borrowed slices).
11//! This is required because the parsed value is heap-allocated as a
12//! `Box<dyn PayloadObject>` whose concrete type is erased; `dyn Any`
13//! downcast demands `'static`.  If your wire layout contains borrowed bytes,
14//! copy them into a `Vec<u8>` in the struct.
15//!
16//! # Example
17//!
18//! ```rust,no_run
19//! use dvb_t2mi::payload::{PayloadRegistry, AnyPayload};
20//! use dvb_t2mi::traits::PayloadDef;
21//! use dvb_common::Parse;
22//!
23//! // A registered type must be `serde::Serialize` only when the `serde`
24//! // feature is on (that is what `PayloadObject` requires there).
25//! #[derive(Debug)]
26//! #[cfg_attr(feature = "serde", derive(serde::Serialize))]
27//! struct MyPrivate { x: u8 }
28//!
29//! impl<'a> Parse<'a> for MyPrivate {
30//!     type Error = dvb_t2mi::Error;
31//!     fn parse(bytes: &'a [u8]) -> dvb_t2mi::Result<Self> {
32//!         if bytes.is_empty() {
33//!             return Err(dvb_t2mi::Error::BufferTooShort {
34//!                 need: 1, have: 0, what: "MyPrivate",
35//!             });
36//!         }
37//!         Ok(Self { x: bytes[0] })
38//!     }
39//! }
40//!
41//! impl<'a> PayloadDef<'a> for MyPrivate {
42//!     const PACKET_TYPE: u8 = 0x40;
43//!     const NAME: &'static str = "MY_PRIVATE";
44//! }
45//!
46//! let mut reg = PayloadRegistry::new();
47//! reg.register::<MyPrivate>();
48//!
49//! let bytes = [0x42u8];
50//! let result = AnyPayload::dispatch_with(
51//!     &reg, 0x40, &bytes,
52//! ).unwrap().unwrap();
53//! if let AnyPayload::Other { packet_type, ref value } = result {
54//!     assert_eq!(packet_type, 0x40);
55//!     assert_eq!(value.downcast_ref::<MyPrivate>().unwrap().x, 0x42);
56//! }
57//! ```
58
59use std::any::Any;
60use std::collections::HashMap;
61
62// ---------------------------------------------------------------------------
63// PayloadObject trait
64// ---------------------------------------------------------------------------
65
66/// Object-safe face of a runtime-registered payload value.
67///
68/// Registered types must be owned (`'static`) because the `dyn Any` downcast
69/// path requires it.  See the [module docs][self] for details.
70///
71/// Implemented automatically via the blanket impl for any `T` satisfying the
72/// supertraits; you do not need to write this by hand.
73#[cfg(not(feature = "serde"))]
74pub trait PayloadObject: std::fmt::Debug + Any + Send + Sync {
75    /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
76    fn as_any(&self) -> &dyn Any;
77}
78
79/// Object-safe face of a runtime-registered payload value.
80///
81/// Registered types must be owned (`'static`) because the `dyn Any` downcast
82/// path requires it.  See the [module docs][self] for details.
83///
84/// Implemented automatically via the blanket impl for any `T` satisfying the
85/// supertraits; you do not need to write this by hand.
86#[cfg(feature = "serde")]
87pub trait PayloadObject: std::fmt::Debug + Any + Send + Sync + erased_serde::Serialize {
88    /// Borrow as `&dyn Any` so the caller can downcast to the concrete type.
89    fn as_any(&self) -> &dyn Any;
90}
91
92// Blanket impl — no-serde arm.
93#[cfg(not(feature = "serde"))]
94impl<T> PayloadObject for T
95where
96    T: std::fmt::Debug + Any + Send + Sync,
97{
98    fn as_any(&self) -> &dyn Any {
99        self
100    }
101}
102
103// Blanket impl — serde arm.
104#[cfg(feature = "serde")]
105impl<T> PayloadObject for T
106where
107    T: std::fmt::Debug + Any + Send + Sync + serde::Serialize,
108{
109    fn as_any(&self) -> &dyn Any {
110        self
111    }
112}
113
114// Downcast helpers ON THE TRAIT OBJECT (not the blanket).
115//
116// These MUST be inherent methods on `dyn PayloadObject` rather than something
117// callable on `Box<dyn PayloadObject>` via the blanket impl. The blanket
118// `impl<T> PayloadObject for T` also covers `Box<dyn PayloadObject>` itself
119// whenever the box satisfies the bounds (it does under `--no-default-features`,
120// where the bound is just `Debug + Any + Send + Sync`). So `the_box.as_any()`
121// resolves to the *box's* impl and reports the box's `TypeId`, not the inner
122// value's — a silent downcast failure. Calling through `dyn PayloadObject`
123// (which `Box` derefs to) always hits the inner value. Always downcast via
124// these methods, never `the_box.as_any()`.
125impl dyn PayloadObject {
126    /// Downcast a registered payload to its concrete type `T`.
127    ///
128    /// Works for `Box<dyn PayloadObject>` (it derefs to the trait object) under
129    /// every feature configuration.
130    #[must_use]
131    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
132        self.as_any().downcast_ref::<T>()
133    }
134
135    /// `true` if the registered payload's concrete type is `T`.
136    #[must_use]
137    pub fn is<T: Any>(&self) -> bool {
138        self.as_any().is::<T>()
139    }
140}
141
142// ---------------------------------------------------------------------------
143// Erased serialisation helper (serde-gated)
144// ---------------------------------------------------------------------------
145
146/// `serialize_with` helper used on [`crate::payload::AnyPayload::Other`]'s `value` field.
147///
148/// Delegates to [`erased_serde::serialize`] so the concrete type's
149/// `serde::Serialize` impl is invoked through the trait object.
150///
151/// The `&Box<T>` is required by serde's `serialize_with` codegen — the field
152/// type is `Box<dyn PayloadObject>` so serde passes `&Box<dyn PayloadObject>`.
153#[cfg(feature = "serde")]
154#[allow(clippy::borrowed_box)]
155pub(crate) fn serialize_erased<S: serde::Serializer>(
156    v: &Box<dyn PayloadObject>,
157    s: S,
158) -> Result<S::Ok, S::Error> {
159    erased_serde::serialize(&**v, s)
160}
161
162// ---------------------------------------------------------------------------
163// Internal parse closure type
164// ---------------------------------------------------------------------------
165
166/// A heap-allocated parse closure that takes raw payload bytes and returns an
167/// owned, type-erased payload value.
168pub(crate) type CustomParse =
169    Box<dyn for<'a> Fn(&'a [u8]) -> crate::Result<Box<dyn PayloadObject>> + Send + Sync>;
170
171// ---------------------------------------------------------------------------
172// PayloadRegistry
173// ---------------------------------------------------------------------------
174
175/// Runtime-configurable payload registry.
176///
177/// By default the registry has no custom parsers.  Use
178/// [`register`][Self::register] to add private types, then call
179/// [`crate::payload::AnyPayload::dispatch_with`] to dispatch through the registry.
180///
181/// # Precedence (per entry)
182///
183/// 1. Custom-registered parser (packet_type in the [`register`][Self::register]
184///    map) → [`crate::payload::AnyPayload::Other`]
185/// 2. Built-in dispatch (internal [`crate::payload::AnyPayload::dispatch`]) → typed variant
186/// 3. Unknown → [`crate::payload::AnyPayload::Unknown`]
187#[derive(Default)]
188pub struct PayloadRegistry {
189    custom: HashMap<u8, CustomParse>,
190}
191
192impl PayloadRegistry {
193    /// Create an empty registry (built-in dispatch only).
194    #[must_use]
195    pub fn new() -> Self {
196        Self::default()
197    }
198
199    /// Register an owned custom payload type for its
200    /// [`PayloadDef::PACKET_TYPE`][crate::traits::PayloadDef::PACKET_TYPE].
201    ///
202    /// # Owned types only
203    ///
204    /// `T` must be `'static` — no borrowed slices.  The registered value is
205    /// type-erased as `Box<dyn PayloadObject>`; `dyn Any` downcast requires
206    /// the concrete type to be `'static`.
207    ///
208    /// Registering a type whose `PACKET_TYPE` is already used by a built-in
209    /// **overrides** the built-in for that packet_type (custom wins precedence
210    /// in [`crate::payload::AnyPayload::dispatch_with`]).
211    ///
212    /// Re-registering the same packet_type replaces the prior custom parser
213    /// (last wins).  A failing custom parse surfaces the client's
214    /// `Parse::Error` unwrapped — embed identifying context (type/ packet_type)
215    /// in your error's `what`/`reason` fields.
216    pub fn register<T>(&mut self) -> &mut Self
217    where
218        T: for<'a> crate::traits::PayloadDef<'a> + PayloadObject + 'static,
219    {
220        // Name PACKET_TYPE without a lifetime — `for<'a> PayloadDef<'a>`
221        // guarantees the const is identical for all lifetimes.
222        let packet_type = <T as crate::traits::PayloadDef<'static>>::PACKET_TYPE;
223        self.custom.insert(
224            packet_type,
225            Box::new(|b| {
226                Ok(Box::new(<T as dvb_common::Parse>::parse(b)?) as Box<dyn PayloadObject>)
227            }),
228        );
229        self
230    }
231
232    /// Look up a custom parser for `packet_type` — `None` if not registered.
233    #[must_use]
234    pub(crate) fn lookup(&self, packet_type: u8) -> Option<&CustomParse> {
235        self.custom.get(&packet_type)
236    }
237}
238
239// ---------------------------------------------------------------------------
240// Tests
241// ---------------------------------------------------------------------------
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use dvb_common::Parse;
247
248    // A custom owned payload type for testing, using an unused packet_type.
249    #[derive(Debug, PartialEq)]
250    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
251    struct TestPayload {
252        val: u8,
253    }
254
255    const TEST_PACKET_TYPE: u8 = 0x40;
256
257    impl<'a> Parse<'a> for TestPayload {
258        type Error = crate::Error;
259
260        fn parse(bytes: &'a [u8]) -> crate::Result<Self> {
261            if bytes.is_empty() {
262                return Err(crate::Error::BufferTooShort {
263                    need: 1,
264                    have: 0,
265                    what: "TestPayload",
266                });
267            }
268            Ok(Self { val: bytes[0] })
269        }
270    }
271
272    impl<'a> crate::traits::PayloadDef<'a> for TestPayload {
273        const PACKET_TYPE: u8 = TEST_PACKET_TYPE;
274        const NAME: &'static str = "TEST_PAYLOAD";
275    }
276
277    #[test]
278    fn register_and_dispatch_returns_other() {
279        let mut reg = PayloadRegistry::new();
280        reg.register::<TestPayload>();
281
282        let bytes = [0x42u8];
283        let result = crate::payload::AnyPayload::dispatch_with(&reg, TEST_PACKET_TYPE, &bytes);
284        let parsed = result.unwrap().unwrap();
285        match parsed {
286            crate::payload::AnyPayload::Other {
287                packet_type,
288                ref value,
289            } => {
290                assert_eq!(packet_type, TEST_PACKET_TYPE);
291                let tp = value.downcast_ref::<TestPayload>().unwrap();
292                assert_eq!(tp.val, 0x42);
293            }
294            _ => panic!("expected Other, got {parsed:?}"),
295        }
296    }
297
298    #[test]
299    fn dispatch_with_falls_back_to_builtin() {
300        let reg = PayloadRegistry::new();
301        // 0x00 is Bbframe — not in registry, falls back to built-in.
302        let bytes = [0x00, 0x00, 0x00]; // minimal valid BBFrame payload
303        let result = crate::payload::AnyPayload::dispatch_with(&reg, 0x00, &bytes);
304        let parsed = result.unwrap().unwrap();
305        assert!(
306            matches!(parsed, crate::payload::AnyPayload::Bbframe(_)),
307            "expected Bbframe, got {parsed:?}"
308        );
309    }
310
311    #[test]
312    fn custom_overrides_builtin_packet_type() {
313        // Use a type that claims the built-in 0x00 Bbframe packet_type.
314        #[derive(Debug)]
315        #[cfg_attr(feature = "serde", derive(serde::Serialize))]
316        struct OverridePayload;
317
318        const OVERRIDE_PT: u8 = 0x00;
319
320        impl<'a> Parse<'a> for OverridePayload {
321            type Error = crate::Error;
322            fn parse(_bytes: &'a [u8]) -> crate::Result<Self> {
323                Ok(Self)
324            }
325        }
326
327        impl<'a> crate::traits::PayloadDef<'a> for OverridePayload {
328            const PACKET_TYPE: u8 = OVERRIDE_PT;
329            const NAME: &'static str = "OVERRIDE";
330        }
331
332        let mut reg = PayloadRegistry::new();
333        reg.register::<OverridePayload>();
334
335        let bytes = [0x00, 0x00, 0x00];
336        let result = crate::payload::AnyPayload::dispatch_with(&reg, 0x00, &bytes);
337        let parsed = result.unwrap().unwrap();
338        assert!(
339            matches!(
340                parsed,
341                crate::payload::AnyPayload::Other {
342                    packet_type: 0x00,
343                    ..
344                }
345            ),
346            "expected Other override for 0x00, got {parsed:?}"
347        );
348    }
349
350    #[cfg(feature = "serde")]
351    #[test]
352    fn serde_other_round_trips_through_json() {
353        let mut reg = PayloadRegistry::new();
354        reg.register::<TestPayload>();
355
356        let bytes = [0x7Fu8];
357        let result = crate::payload::AnyPayload::dispatch_with(&reg, TEST_PACKET_TYPE, &bytes);
358        let parsed = result.unwrap().unwrap();
359
360        let json = serde_json::to_value(&parsed).unwrap();
361        let obj = json
362            .as_object()
363            .unwrap()
364            .get("other")
365            .expect("expected 'other' key");
366        assert_eq!(obj["packet_type"], TEST_PACKET_TYPE);
367        assert_eq!(obj["value"]["val"], 0x7F);
368    }
369}