dvb_subtitle/any.rs
1//! Unified segment dispatch: [`AnySegment`] + segment parsing.
2//!
3//! [`AnySegment`] is generated from a single declarative list
4//! (`declare_segments!`) — one line per crate-implemented segment type.
5//! The list is the single source of truth: it produces the enum, the
6//! `From<T>` conversions, and the segment_type → type dispatcher, and
7//! a drift test pins each segment_type literal to the type's
8//! [`crate::traits::SegmentDef::SEGMENT_TYPE`].
9
10use broadcast_common::{Parse, Serialize};
11
12/// Declares [`AnySegment`] + its dispatcher from one segment type list.
13///
14/// Each line is `Variant = 0xSEGMENT_TYPE => module::Type[<'a>]`.
15macro_rules! declare_segments {
16 (
17 $lt:lifetime;
18 $( $variant:ident = $seg_type:literal => $($path:ident)::+ $(<$plt:lifetime>)? ),+ $(,)?
19 ) => {
20 /// Every crate-implemented segment type, plus an `Unknown` fallthrough.
21 ///
22 /// serde uses external tagging with camelCase variant keys.
23 #[derive(Debug, Clone, PartialEq, Eq)]
24 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
25 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
26 #[non_exhaustive]
27 pub enum AnySegment<$lt> {
28 $(
29 #[allow(missing_docs)]
30 $variant($($path)::+ $(<$plt>)?),
31 )+
32 /// Segment with an unrecognized or unsupported segment_type; `data` is the
33 /// segment body sans the 6-byte header (4 bytes of generic segment framing).
34 Unknown {
35 /// The raw segment_type byte.
36 segment_type: u8,
37 /// The page_id from the segment header.
38 page_id: u16,
39 /// The segment body bytes (starting after the 4-byte sync_byte+segment_type+page_id+segment_length header).
40 data: &$lt [u8],
41 },
42 }
43
44 $(
45 impl<$lt> From<$($path)::+ $(<$plt>)?> for AnySegment<$lt> {
46 fn from(s: $($path)::+ $(<$plt>)?) -> Self {
47 Self::$variant(s)
48 }
49 }
50 )+
51
52 impl<$lt> AnySegment<$lt> {
53 /// Every segment_type the generated dispatcher routes.
54 pub const DISPATCHED_SEGMENT_TYPES: &'static [u8] = &[$($seg_type),+];
55
56 /// Diagnostic name of the contained segment — the type's
57 /// [`SegmentDef::NAME`](crate::traits::SegmentDef::NAME)
58 /// (`"PAGE_COMPOSITION"`, `"OBJECT_DATA"`, …); `"UNKNOWN"` for
59 /// [`AnySegment::Unknown`].
60 #[must_use]
61 pub fn name(&self) -> &'static str {
62 match self {
63 $(
64 Self::$variant(_) =>
65 <$($path)::+ as crate::traits::SegmentDef>::NAME,
66 )+
67 Self::Unknown { .. } => "UNKNOWN",
68 }
69 }
70
71 /// Parse one full segment (including 6-byte generic header) by its segment_type.
72 ///
73 /// `None` means no typed implementation exists for `segment_type` (the
74 /// caller turns that into [`AnySegment::Unknown`]). `Some(Err)`
75 /// is a typed parse failure for a recognized segment_type.
76 pub(crate) fn dispatch(segment_type: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
77 match segment_type {
78 $(
79 $seg_type => Some(<$($path)::+>::parse(full).map(Self::$variant)),
80 )+
81 _ => None,
82 }
83 }
84
85 pub(crate) fn serialized_len(&self) -> usize {
86 match self {
87 $(
88 Self::$variant(s) => s.serialized_len(),
89 )+
90 Self::Unknown { data, .. } => (6 + data.len()),
91 }
92 }
93
94 pub(crate) fn serialize_into(&self, buf: &mut [u8]) -> crate::Result<usize> {
95 match self {
96 $(
97 Self::$variant(s) => s.serialize_into(buf),
98 )+
99 Self::Unknown { segment_type, page_id, data } => {
100 let len = 6 + data.len();
101 if buf.len() < len {
102 return Err(crate::error::Error::BufferTooShort {
103 need: len,
104 have: buf.len(),
105 what: "Unknown segment serialize",
106 });
107 }
108 buf[0] = 0x0F;
109 buf[1] = *segment_type;
110 buf[2..4].copy_from_slice(&page_id.to_be_bytes());
111 let seg_len = data.len() as u16;
112 buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
113 buf[6..len].copy_from_slice(data);
114 Ok(len)
115 }
116 }
117 }
118 }
119
120 #[cfg(test)]
121 mod macro_drift {
122 #[test]
123 fn segment_type_literals_match_segment_def() {
124 use crate::traits::SegmentDef;
125 $(
126 assert_eq!(
127 $seg_type,
128 <$($path)::+ as SegmentDef>::SEGMENT_TYPE,
129 concat!("segment_type literal drift for ", stringify!($variant)),
130 );
131 assert!(
132 !<$($path)::+ as SegmentDef>::NAME.is_empty(),
133 concat!("empty NAME for ", stringify!($variant)),
134 );
135 )+
136 }
137 }
138 };
139}
140
141declare_segments! {'a;
142 PageComposition = 0x10 => crate::segments::page_composition::PageCompositionSegment,
143 RegionComposition = 0x11 => crate::segments::region_composition::RegionCompositionSegment,
144 ClutDefinition = 0x12 => crate::segments::clut_definition::ClutDefinitionSegment,
145 ObjectData = 0x13 => crate::segments::object_data::ObjectDataSegment<'a>,
146 DisplayDefinition = 0x14 => crate::segments::display_definition::DisplayDefinitionSegment,
147 DisparitySignalling = 0x15 => crate::segments::disparity_signalling::DisparitySignallingSegment,
148 AlternativeClut = 0x16 => crate::segments::alternative_clut::AlternativeClutSegment,
149 EndOfDisplaySet = 0x80 => crate::segments::end_of_display_set::EndOfDisplaySetSegment,
150 Stuffing = 0xFF => crate::segments::stuffing::StuffingSegment<'a>,
151}