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