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 /// Segment with a *recognized* segment_type whose typed parse
43 /// returned `Err` (distinct from [`Self::Unknown`], whose
44 /// segment_type has no typed implementation at all — issue
45 /// #1108/DS-W1: a bug that makes every real instance of a typed
46 /// parser fail could otherwise hide as `Unknown` passthrough and
47 /// still round-trip byte-exact). Re-serializes the raw bytes
48 /// verbatim, same as `Unknown`.
49 Malformed {
50 /// The raw segment_type byte.
51 segment_type: u8,
52 /// The page_id from the segment header.
53 page_id: u16,
54 /// The segment body bytes (same slicing as `Unknown::data`).
55 data: &$lt [u8],
56 /// Why the typed parse failed.
57 #[cfg_attr(feature = "serde", serde(skip))]
58 err: crate::error::Error,
59 },
60 }
61
62 $(
63 impl<$lt> From<$($path)::+ $(<$plt>)?> for AnySegment<$lt> {
64 fn from(s: $($path)::+ $(<$plt>)?) -> Self {
65 Self::$variant(s)
66 }
67 }
68 )+
69
70 impl<$lt> AnySegment<$lt> {
71 /// Every segment_type the generated dispatcher routes.
72 pub const DISPATCHED_SEGMENT_TYPES: &'static [u8] = &[$($seg_type),+];
73
74 /// Diagnostic name of the contained segment — the type's
75 /// [`SegmentDef::NAME`](crate::traits::SegmentDef::NAME)
76 /// (`"PAGE_COMPOSITION"`, `"OBJECT_DATA"`, …); `"UNKNOWN"` for
77 /// [`AnySegment::Unknown`], `"MALFORMED"` for
78 /// [`AnySegment::Malformed`].
79 #[must_use]
80 pub fn name(&self) -> &'static str {
81 match self {
82 $(
83 Self::$variant(_) =>
84 <$($path)::+ as crate::traits::SegmentDef>::NAME,
85 )+
86 Self::Unknown { .. } => "UNKNOWN",
87 Self::Malformed { .. } => "MALFORMED",
88 }
89 }
90
91 /// Parse one full segment (including 6-byte generic header) by its segment_type.
92 ///
93 /// `None` means no typed implementation exists for `segment_type` (the
94 /// caller turns that into [`AnySegment::Unknown`]). `Some(Err)`
95 /// is a typed parse failure for a recognized segment_type (the
96 /// caller turns that into [`AnySegment::Malformed`]).
97 pub(crate) fn dispatch(segment_type: u8, full: &$lt [u8]) -> Option<crate::Result<Self>> {
98 match segment_type {
99 $(
100 $seg_type => Some(<$($path)::+>::parse(full).map(Self::$variant)),
101 )+
102 _ => None,
103 }
104 }
105
106 pub(crate) fn serialized_len(&self) -> usize {
107 match self {
108 $(
109 Self::$variant(s) => s.serialized_len(),
110 )+
111 Self::Unknown { data, .. } => (6 + data.len()),
112 Self::Malformed { data, .. } => (6 + data.len()),
113 }
114 }
115
116 pub(crate) fn serialize_into(&self, buf: &mut [u8]) -> crate::Result<usize> {
117 match self {
118 $(
119 Self::$variant(s) => s.serialize_into(buf),
120 )+
121 Self::Unknown { segment_type, page_id, data } => {
122 Self::serialize_raw_segment(*segment_type, *page_id, data, buf)
123 }
124 Self::Malformed { segment_type, page_id, data, .. } => {
125 Self::serialize_raw_segment(*segment_type, *page_id, data, buf)
126 }
127 }
128 }
129
130 /// Shared raw re-serialization for `Unknown`/`Malformed`, both of
131 /// which round-trip their body bytes verbatim.
132 fn serialize_raw_segment(
133 segment_type: u8,
134 page_id: u16,
135 data: &[u8],
136 buf: &mut [u8],
137 ) -> crate::Result<usize> {
138 let len = 6 + data.len();
139 broadcast_common::len::need_out(buf, len)?;
140 buf[0] = crate::pes_data_field::SYNC_BYTE;
141 buf[1] = segment_type;
142 buf[2..4].copy_from_slice(&page_id.to_be_bytes());
143 let seg_len = crate::segments::check_segment_length(data.len())?;
144 buf[4..6].copy_from_slice(&seg_len.to_be_bytes());
145 buf[6..len].copy_from_slice(data);
146 Ok(len)
147 }
148 }
149
150 #[cfg(test)]
151 mod macro_drift {
152 #[test]
153 fn segment_type_literals_match_segment_def() {
154 use crate::traits::SegmentDef;
155 $(
156 assert_eq!(
157 $seg_type,
158 <$($path)::+ as SegmentDef>::SEGMENT_TYPE,
159 concat!("segment_type literal drift for ", stringify!($variant)),
160 );
161 assert!(
162 !<$($path)::+ as SegmentDef>::NAME.is_empty(),
163 concat!("empty NAME for ", stringify!($variant)),
164 );
165 )+
166 }
167 }
168 };
169}
170
171declare_segments! {'a;
172 PageComposition = 0x10 => crate::segments::page_composition::PageCompositionSegment,
173 RegionComposition = 0x11 => crate::segments::region_composition::RegionCompositionSegment,
174 ClutDefinition = 0x12 => crate::segments::clut_definition::ClutDefinitionSegment,
175 ObjectData = 0x13 => crate::segments::object_data::ObjectDataSegment<'a>,
176 DisplayDefinition = 0x14 => crate::segments::display_definition::DisplayDefinitionSegment,
177 DisparitySignalling = 0x15 => crate::segments::disparity_signalling::DisparitySignallingSegment,
178 AlternativeClut = 0x16 => crate::segments::alternative_clut::AlternativeClutSegment,
179 EndOfDisplaySet = 0x80 => crate::segments::end_of_display_set::EndOfDisplaySetSegment,
180 Stuffing = 0xFF => crate::segments::stuffing::StuffingSegment<'a>,
181}