Skip to main content

dvb_subtitle/
pes_data_field.rs

1//! PES data field parsing — ETSI EN 300 743 §7.2, Table 3.
2//!
3//! The top-level structure that wraps the subtitling segments within a
4//! DVB subtitle PES packet.
5
6use crate::any::AnySegment;
7use crate::error::{Error, Result};
8use broadcast_common::len::{need_in, need_out};
9use broadcast_common::{Parse, Serialize};
10
11/// The required `data_identifier` value for DVB subtitles.
12pub const DATA_IDENTIFIER: u8 = 0x20;
13/// The required `subtitle_stream_id` value.
14pub const SUBTITLE_STREAM_ID: u8 = 0x00;
15/// The sync_byte that prefixes every subtitling_segment.
16pub const SYNC_BYTE: u8 = 0x0F;
17/// The `end_of_PES_data_field_marker` value.
18pub const END_OF_PES_MARKER: u8 = 0xFF;
19
20/// The minimum PES data field: data_identifier(1) + subtitle_stream_id(1) + end_marker(1) = 3 bytes.
21const MIN_FIELD_LEN: usize = 3;
22/// Generic segment header: sync_byte(1) + segment_type(1) + page_id(2) + segment_length(2) = 6 bytes.
23const SEGMENT_HEADER_LEN: usize = 6;
24
25/// The top-level PES data field structure for DVB subtitles.
26///
27/// Contains the data_identifier, subtitle_stream_id, one or more
28/// subtitling segments, and the end-of-PES marker.
29#[derive(Debug, Clone, PartialEq, Eq)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize))]
31pub struct PesDataField<'a> {
32    /// The subtitle_stream_id (must be 0x00).
33    pub subtitle_stream_id: u8,
34    /// The parsed subtitling segments.
35    #[cfg_attr(feature = "serde", serde(borrow))]
36    pub segments: alloc::vec::Vec<AnySegment<'a>>,
37    /// Raw bytes after the last parsed segment up to and including the end marker.
38    /// Preserved for byte-exact round-trip on data with trailing stuff or truncated segments.
39    #[cfg_attr(feature = "serde", serde(skip))]
40    pub(crate) suffix: &'a [u8],
41}
42
43impl<'a> Parse<'a> for PesDataField<'a> {
44    type Error = Error;
45
46    fn parse(bytes: &'a [u8]) -> Result<Self> {
47        need_in(bytes, MIN_FIELD_LEN, "PES_data_field")?;
48        if bytes[0] != DATA_IDENTIFIER {
49            return Err(Error::BadDataIdentifier(bytes[0]));
50        }
51        let subtitle_stream_id = bytes[1];
52        if subtitle_stream_id != SUBTITLE_STREAM_ID {
53            // Per spec it must be 0x00 but we parse it anyway
54        }
55
56        let mut pos: usize = 2;
57        let mut segments = alloc::vec::Vec::new();
58
59        // Read segments: each starts with sync_byte 0x0F
60        while pos < bytes.len() && bytes[pos] == SYNC_BYTE {
61            if pos + SEGMENT_HEADER_LEN > bytes.len() {
62                // Truncated segment header — stop and let suffix capture
63                break;
64            }
65            let segment_length = u16::from_be_bytes([bytes[pos + 4], bytes[pos + 5]]) as usize;
66            let segment_end = pos + SEGMENT_HEADER_LEN + segment_length;
67            if segment_end > bytes.len() {
68                // Segment data exceeds available bytes (truncated PES) —
69                // stop the loop and let suffix capture the remainder verbatim
70                break;
71            }
72
73            let seg_bytes = &bytes[pos..segment_end];
74            let segment_type = bytes[pos + 1];
75
76            match AnySegment::dispatch(segment_type, seg_bytes) {
77                Some(Ok(seg)) => segments.push(seg),
78                Some(Err(e)) => {
79                    // A recognised segment_type whose typed parse rejected the
80                    // body. Kept distinct from `Unknown` (DS-W1/#1108): folding
81                    // this into `Unknown` let a broken typed parser hide behind
82                    // the same byte-exact raw-passthrough round-trip as a
83                    // genuinely unimplemented segment_type, so the real-fixture
84                    // gate couldn't fail even if every typed parse of this
85                    // segment_type was broken.
86                    segments.push(AnySegment::Malformed {
87                        segment_type,
88                        page_id: u16::from_be_bytes([seg_bytes[2], seg_bytes[3]]),
89                        data: &seg_bytes[SEGMENT_HEADER_LEN..],
90                        err: e,
91                    });
92                }
93                None => {
94                    // Unknown segment_type: preserve as raw
95                    segments.push(AnySegment::Unknown {
96                        segment_type,
97                        page_id: u16::from_be_bytes([seg_bytes[2], seg_bytes[3]]),
98                        data: &seg_bytes[SEGMENT_HEADER_LEN..],
99                    });
100                }
101            }
102
103            pos = segment_end;
104        }
105
106        // Preserve trailing bytes (including end marker) for byte-exact round-trip
107        let suffix = &bytes[pos..];
108        if let [marker] = suffix
109            && *marker != END_OF_PES_MARKER
110        {
111            return Err(Error::BadEndOfPesMarker(*marker));
112        }
113
114        Ok(PesDataField {
115            subtitle_stream_id,
116            segments,
117            suffix,
118        })
119    }
120}
121
122impl Serialize for PesDataField<'_> {
123    type Error = Error;
124
125    fn serialized_len(&self) -> usize {
126        2 + self.suffix.len()
127            + self
128                .segments
129                .iter()
130                .map(|s| s.serialized_len())
131                .sum::<usize>()
132    }
133
134    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
135        let len = self.serialized_len();
136        need_out(buf, len)?;
137        buf[0] = DATA_IDENTIFIER;
138        buf[1] = self.subtitle_stream_id;
139        let mut off = 2;
140        for seg in &self.segments {
141            let seg_len = seg.serialized_len();
142            seg.serialize_into(&mut buf[off..off + seg_len])?;
143            off += seg_len;
144        }
145        buf[off..off + self.suffix.len()].copy_from_slice(self.suffix);
146        Ok(len)
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use broadcast_common::Serialize;
154
155    #[test]
156    fn round_trip_single_end_of_display_set() {
157        let bytes = [
158            0x20, 0x00, // data_identifier + subtitle_stream_id
159            0x0F, 0x80, 0x00, 0x01, 0x00, 0x00, // end of display set
160            0xFF, // end marker
161        ];
162        let field = PesDataField::parse(&bytes).unwrap();
163        assert_eq!(field.segments.len(), 1);
164        let out = field.try_to_bytes().unwrap();
165        assert_eq!(out, bytes);
166    }
167
168    #[test]
169    fn round_trip_multiple_segments() {
170        let bytes = [
171            0x20, 0x00, // Display definition
172            0x0F, 0x14, 0x00, 0x01, 0x00, 0x05, 0x30, 0x02, 0xCF, 0x01, 0x1F,
173            // Page composition: 1 region = 2 fixed + 6 = 8 body → seg_len=0x08
174            0x0F, 0x10, 0x00, 0x01, 0x00, 0x08, 0x0A, 0x08, 0x01, 0x00, 0x00, 0x64, 0x00, 0x32,
175            // End of display set
176            0x0F, 0x80, 0x00, 0x01, 0x00, 0x00, 0xFF,
177        ];
178        let field = PesDataField::parse(&bytes).unwrap();
179        assert_eq!(field.segments.len(), 3);
180        assert_eq!(field.segments[0].name(), "DISPLAY_DEFINITION");
181        assert_eq!(field.segments[1].name(), "PAGE_COMPOSITION");
182        assert_eq!(field.segments[2].name(), "END_OF_DISPLAY_SET");
183        let out = field.try_to_bytes().unwrap();
184        assert_eq!(out, bytes);
185    }
186
187    #[test]
188    fn unknown_segment_preserved() {
189        let bytes = [
190            0x20, 0x00, 0x0F, 0xA0, 0x00, 0x01, 0x00, 0x02, 0xCA, 0xFE, // unknown 0xA0
191            0xFF,
192        ];
193        let field = PesDataField::parse(&bytes).unwrap();
194        assert_eq!(field.segments.len(), 1);
195        assert_eq!(field.segments[0].name(), "UNKNOWN");
196        let out = field.try_to_bytes().unwrap();
197        assert_eq!(out, bytes);
198    }
199
200    #[test]
201    fn malformed_typed_segment_distinct_from_unknown() {
202        // segment_type 0x80 (END_OF_DISPLAY_SET) is a recognised type, but its
203        // typed parser requires segment_length == 0 (Table 28); a
204        // full-length, well-framed body with segment_length = 2 is a typed
205        // parse failure, not an unrecognised segment_type. DS-W1 (#1108):
206        // pre-fix this fell back to the SAME `AnySegment::Unknown` a truly
207        // unimplemented segment_type gets, so a broken typed parser for a
208        // real segment type could hide behind a passing byte-exact
209        // round-trip forever.
210        let bytes = [
211            0x20, 0x00, // data_identifier + subtitle_stream_id
212            0x0F, 0x80, 0x00, 0x01, 0x00, 0x02, 0xCA,
213            0xFE, // end_of_display_set, len=2 (invalid)
214            0xFF,
215        ];
216        let field = PesDataField::parse(&bytes).unwrap();
217        assert_eq!(field.segments.len(), 1);
218        assert_eq!(field.segments[0].name(), "MALFORMED");
219        assert!(!matches!(field.segments[0], AnySegment::Unknown { .. }));
220        assert!(matches!(
221            field.segments[0],
222            AnySegment::Malformed {
223                segment_type: 0x80,
224                ..
225            }
226        ));
227        // Still round-trips byte-exact, same as Unknown.
228        let out = field.try_to_bytes().unwrap();
229        assert_eq!(out, bytes);
230    }
231
232    #[test]
233    fn bad_data_identifier() {
234        let bytes = [0x00, 0x00, 0xFF];
235        let err = PesDataField::parse(&bytes).unwrap_err();
236        assert!(matches!(err, Error::BadDataIdentifier(0x00)));
237    }
238
239    #[test]
240    fn wrong_end_marker_after_the_last_segment_is_rejected() {
241        let bytes = [
242            0x20, 0x00, // data_identifier + subtitle_stream_id
243            0x0F, 0x80, 0x00, 0x01, 0x00, 0x00, // end of display set
244            0xFE, // end_of_PES_data_field_marker must be 0xFF
245        ];
246        assert_eq!(
247            PesDataField::parse(&bytes).unwrap_err(),
248            Error::BadEndOfPesMarker(0xFE)
249        );
250    }
251
252    #[test]
253    fn wrong_end_marker_with_no_segments_is_rejected() {
254        assert_eq!(
255            PesDataField::parse(&[0x20, 0x00, 0x00]).unwrap_err(),
256            Error::BadEndOfPesMarker(0x00)
257        );
258    }
259
260    #[test]
261    fn truncated_segment_still_parses_with_the_remainder_kept() {
262        let bytes = [
263            0x20, 0x00, //
264            0x0F, 0x10, 0x00, 0x01, 0x00, 0x20, 0x01, // segment_length 0x20, 1 byte present
265        ];
266        let field = PesDataField::parse(&bytes).unwrap();
267        assert_eq!(field.segments.len(), 0);
268        assert_eq!(field.try_to_bytes().unwrap(), bytes);
269    }
270}