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 dvb_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                    // Malformed but recognised segment — skip it per §7.2.0.2
85                    segments.push(AnySegment::Unknown {
86                        segment_type,
87                        page_id: u16::from_be_bytes([seg_bytes[2], seg_bytes[3]]),
88                        data: &seg_bytes[SEGMENT_HEADER_LEN..],
89                    });
90                }
91                None => {
92                    // Unknown segment_type: preserve as raw
93                    segments.push(AnySegment::Unknown {
94                        segment_type,
95                        page_id: u16::from_be_bytes([seg_bytes[2], seg_bytes[3]]),
96                        data: &seg_bytes[SEGMENT_HEADER_LEN..],
97                    });
98                }
99            }
100
101            pos = segment_end;
102        }
103
104        // Preserve trailing bytes (including end marker) for byte-exact round-trip
105        let suffix = &bytes[pos..];
106
107        Ok(PesDataField {
108            subtitle_stream_id,
109            segments,
110            suffix,
111        })
112    }
113}
114
115impl Serialize for PesDataField<'_> {
116    type Error = Error;
117
118    fn serialized_len(&self) -> usize {
119        2 + self.suffix.len()
120            + self
121                .segments
122                .iter()
123                .map(|s| s.serialized_len())
124                .sum::<usize>()
125    }
126
127    fn serialize_into(&self, buf: &mut [u8]) -> core::result::Result<usize, Self::Error> {
128        let len = self.serialized_len();
129        if buf.len() < len {
130            return Err(Error::BufferTooShort {
131                need: len,
132                have: buf.len(),
133                what: "PES_data_field serialize",
134            });
135        }
136        buf[0] = DATA_IDENTIFIER;
137        buf[1] = self.subtitle_stream_id;
138        let mut off = 2;
139        for seg in &self.segments {
140            let seg_len = seg.serialized_len();
141            seg.serialize_into(&mut buf[off..off + seg_len])?;
142            off += seg_len;
143        }
144        buf[off..off + self.suffix.len()].copy_from_slice(self.suffix);
145        Ok(len)
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use dvb_common::Serialize;
153
154    #[test]
155    fn round_trip_single_end_of_display_set() {
156        let bytes = [
157            0x20, 0x00, // data_identifier + subtitle_stream_id
158            0x0F, 0x80, 0x00, 0x01, 0x00, 0x00, // end of display set
159            0xFF, // end marker
160        ];
161        let field = PesDataField::parse(&bytes).unwrap();
162        assert_eq!(field.segments.len(), 1);
163        let out = field.to_bytes();
164        assert_eq!(out, bytes);
165    }
166
167    #[test]
168    fn round_trip_multiple_segments() {
169        let bytes = [
170            0x20, 0x00, // Display definition
171            0x0F, 0x14, 0x00, 0x01, 0x00, 0x05, 0x30, 0x02, 0xCF, 0x01, 0x1F,
172            // Page composition: 1 region = 2 fixed + 6 = 8 body → seg_len=0x08
173            0x0F, 0x10, 0x00, 0x01, 0x00, 0x08, 0x0A, 0x08, 0x01, 0x00, 0x00, 0x64, 0x00, 0x32,
174            // End of display set
175            0x0F, 0x80, 0x00, 0x01, 0x00, 0x00, 0xFF,
176        ];
177        let field = PesDataField::parse(&bytes).unwrap();
178        assert_eq!(field.segments.len(), 3);
179        assert_eq!(field.segments[0].name(), "DISPLAY_DEFINITION");
180        assert_eq!(field.segments[1].name(), "PAGE_COMPOSITION");
181        assert_eq!(field.segments[2].name(), "END_OF_DISPLAY_SET");
182        let out = field.to_bytes();
183        assert_eq!(out, bytes);
184    }
185
186    #[test]
187    fn unknown_segment_preserved() {
188        let bytes = [
189            0x20, 0x00, 0x0F, 0xA0, 0x00, 0x01, 0x00, 0x02, 0xCA, 0xFE, // unknown 0xA0
190            0xFF,
191        ];
192        let field = PesDataField::parse(&bytes).unwrap();
193        assert_eq!(field.segments.len(), 1);
194        assert_eq!(field.segments[0].name(), "UNKNOWN");
195        let out = field.to_bytes();
196        assert_eq!(out, bytes);
197    }
198
199    #[test]
200    fn bad_data_identifier() {
201        let bytes = [0x00, 0x00, 0xFF];
202        let err = PesDataField::parse(&bytes).unwrap_err();
203        assert!(matches!(err, Error::BadDataIdentifier(0x00)));
204    }
205
206    #[test]
207    fn no_end_marker_is_ok() {
208        // Per forward-compatibility §7.2.0.2, a missing end marker is tolerated
209        let bytes = [0x20, 0x00, 0x00];
210        let field = PesDataField::parse(&bytes).unwrap();
211        assert_eq!(field.segments.len(), 0);
212    }
213}