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//! ®, 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(®, 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(®, 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(®, 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(®, 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}