Skip to main content

dvb_si/descriptors/
vbi_data.rs

1//! VBI Data Descriptor — ETSI EN 300 468 §6.2.47 (tag 0x45).
2//!
3//! Table 106 (PDF p. 110). Carried in PMT ES_info for an elementary stream
4//! carrying VBI data (per ETSI EN 301 775). Body is a loop of entries, each a
5//! `data_service_id` byte + an 8-bit `data_service_descriptor_length` + that
6//! many service-descriptor bytes.
7//!
8//! The first loop level (data_service_id + length-delimited service block) is
9//! typed. For `data_service_id` values `0x01`–`0x02`, `0x04`–`0x07` (Table 106/107), each
10//! service-descriptor byte encodes `reserved(2)|field_parity(1)|line_offset(5)`;
11//! other values are kept as raw reserved bytes.
12
13use super::descriptor_body;
14use crate::error::{Error, Result};
15use dvb_common::{Parse, Serialize};
16
17/// Descriptor tag for VBI_data_descriptor.
18pub const TAG: u8 = 0x45;
19const HEADER_LEN: usize = 2;
20const ENTRY_HEADER_LEN: usize = 2;
21const MAX_BODY_LEN: usize = u8::MAX as usize;
22const MAX_SERVICE_LEN: usize = u8::MAX as usize;
23
24/// Per-service-descriptor content, keyed by `data_service_id`.
25#[derive(Debug, Clone, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize))]
27#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
28#[non_exhaustive]
29pub enum VbiService<'a> {
30    /// `data_service_id` 0x01–0x02, 0x04–0x07: each byte is
31    /// `reserved_future_use(2)|field_parity(1)|line_offset(5)` (Table 106).
32    Lines(Vec<VbiLine>),
33    /// `data_service_id` 0x00, 0x03, or 0x08+: raw reserved bytes.
34    Reserved(&'a [u8]),
35}
36
37/// One VBI line entry (Table 106 per-byte layout for ids 0x01–0x07).
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40pub struct VbiLine {
41    /// `field_parity` (1 bit) — `[5]` of the service-descriptor byte.
42    pub field_parity: bool,
43    /// `line_offset` (5 bits) — `[4:0]` of the service-descriptor byte.
44    pub line_offset: u8,
45}
46
47/// One VBI data service entry.
48#[derive(Debug, Clone, PartialEq, Eq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize))]
50#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
51pub struct VbiDataEntry<'a> {
52    /// data_service_id (EN 300 468 Table 107): 0x01 = EBU teletext,
53    /// 0x02 = inverted teletext, 0x04 = VPS, 0x05 = WSS, 0x06 = closed
54    /// captioning, 0x07 = monochrome 4:2:2 samples; others reserved/user.
55    pub data_service_id: u8,
56    /// Per-service content, typed for ids 0x01–0x02, 0x04–0x07.
57    pub service_descriptor: VbiService<'a>,
58}
59
60/// VBI Data Descriptor.
61#[derive(Debug, Clone, PartialEq, Eq)]
62#[cfg_attr(feature = "serde", derive(serde::Serialize))]
63#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
64pub struct VbiDataDescriptor<'a> {
65    /// Service entries in wire order.
66    pub entries: Vec<VbiDataEntry<'a>>,
67}
68
69impl<'a> Parse<'a> for VbiDataDescriptor<'a> {
70    type Error = crate::error::Error;
71    fn parse(bytes: &'a [u8]) -> Result<Self> {
72        let body = descriptor_body(
73            bytes,
74            TAG,
75            "VbiDataDescriptor",
76            "unexpected tag for VBI_data_descriptor",
77        )?;
78        let mut entries = Vec::new();
79        let mut pos = 0;
80        while pos < body.len() {
81            if pos + ENTRY_HEADER_LEN > body.len() {
82                return Err(Error::InvalidDescriptor {
83                    tag: TAG,
84                    reason: "truncated VBI data entry header",
85                });
86            }
87            let data_service_id = body[pos];
88            let svc_len = body[pos + 1] as usize;
89            pos += ENTRY_HEADER_LEN;
90            if pos + svc_len > body.len() {
91                return Err(Error::InvalidDescriptor {
92                    tag: TAG,
93                    reason: "data_service_descriptor_length exceeds descriptor body",
94                });
95            }
96            let svc_bytes = &body[pos..pos + svc_len];
97            pos += svc_len;
98            let service_descriptor = if matches!(data_service_id, 0x01 | 0x02 | 0x04..=0x07) {
99                let lines = svc_bytes
100                    .iter()
101                    .map(|&b| VbiLine {
102                        field_parity: (b & 0x20) != 0,
103                        line_offset: b & 0x1F,
104                    })
105                    .collect();
106                VbiService::Lines(lines)
107            } else {
108                VbiService::Reserved(svc_bytes)
109            };
110            entries.push(VbiDataEntry {
111                data_service_id,
112                service_descriptor,
113            });
114        }
115        Ok(Self { entries })
116    }
117}
118
119impl Serialize for VbiDataDescriptor<'_> {
120    type Error = crate::error::Error;
121    fn serialized_len(&self) -> usize {
122        HEADER_LEN
123            + self
124                .entries
125                .iter()
126                .map(|e| {
127                    ENTRY_HEADER_LEN
128                        + match &e.service_descriptor {
129                            VbiService::Lines(lines) => lines.len(),
130                            VbiService::Reserved(b) => b.len(),
131                        }
132                })
133                .sum::<usize>()
134    }
135
136    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
137        let len = self.serialized_len();
138        if buf.len() < len {
139            return Err(Error::OutputBufferTooSmall {
140                need: len,
141                have: buf.len(),
142            });
143        }
144        let body_len = len - HEADER_LEN;
145        if body_len > MAX_BODY_LEN {
146            return Err(Error::InvalidDescriptor {
147                tag: TAG,
148                reason: "VBI_data_descriptor body exceeds 255 bytes",
149            });
150        }
151        buf[0] = TAG;
152        buf[1] = body_len as u8;
153        let mut pos = HEADER_LEN;
154        for e in &self.entries {
155            let svc_len = match &e.service_descriptor {
156                VbiService::Lines(lines) => lines.len(),
157                VbiService::Reserved(b) => b.len(),
158            };
159            if svc_len > MAX_SERVICE_LEN {
160                return Err(Error::InvalidDescriptor {
161                    tag: TAG,
162                    reason: "service_descriptor exceeds 255 bytes (8-bit length field)",
163                });
164            }
165            buf[pos] = e.data_service_id;
166            buf[pos + 1] = svc_len as u8;
167            pos += ENTRY_HEADER_LEN;
168            match &e.service_descriptor {
169                VbiService::Lines(lines) => {
170                    for line in lines {
171                        buf[pos] =
172                            0xC0 | (u8::from(line.field_parity) << 5) | (line.line_offset & 0x1F);
173                        pos += 1;
174                    }
175                }
176                VbiService::Reserved(b) => {
177                    buf[pos..pos + b.len()].copy_from_slice(b);
178                    pos += b.len();
179                }
180            }
181        }
182        Ok(len)
183    }
184}
185impl<'a> crate::traits::DescriptorDef<'a> for VbiDataDescriptor<'a> {
186    const TAG: u8 = TAG;
187    const NAME: &'static str = "VBI_DATA";
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn parse_single_entry() {
196        // data_service_id=0x01 (EBU teletext), 2 line bytes: 0xC1, 0xC2
197        // 0xC1 = reserved(2)=11 | field_parity=0 | line_offset=00001 → fp=false, lo=1
198        // 0xC2 = reserved(2)=11 | field_parity=0 | line_offset=00010 → fp=false, lo=2
199        let bytes = [TAG, 4, 0x01, 0x02, 0xC1, 0xC2];
200        let d = VbiDataDescriptor::parse(&bytes).unwrap();
201        assert_eq!(d.entries.len(), 1);
202        assert_eq!(d.entries[0].data_service_id, 0x01);
203        match &d.entries[0].service_descriptor {
204            VbiService::Lines(lines) => {
205                assert_eq!(lines.len(), 2);
206                assert!(!lines[0].field_parity);
207                assert_eq!(lines[0].line_offset, 0x01);
208                assert!(!lines[1].field_parity);
209                assert_eq!(lines[1].line_offset, 0x02);
210            }
211            other => panic!("expected Lines, got {other:?}"),
212        }
213    }
214
215    #[test]
216    fn parse_multiple_entries() {
217        // 0xAA = 10_1_01010 → fp=true, lo=0x0A=10
218        // 0xBB = 10_1_11011 → fp=true, lo=0x1B=27
219        // 0xCC = 11_0_01100 → fp=false, lo=0x0C=12
220        let bytes = [TAG, 7, 0x04, 0x01, 0xAA, 0x05, 0x02, 0xBB, 0xCC];
221        let d = VbiDataDescriptor::parse(&bytes).unwrap();
222        assert_eq!(d.entries.len(), 2);
223        assert_eq!(d.entries[0].data_service_id, 0x04);
224        match &d.entries[0].service_descriptor {
225            VbiService::Lines(lines) => {
226                assert_eq!(lines.len(), 1);
227                assert!(lines[0].field_parity);
228                assert_eq!(lines[0].line_offset, 0x0A);
229            }
230            other => panic!("expected Lines, got {other:?}"),
231        }
232        assert_eq!(d.entries[1].data_service_id, 0x05);
233        match &d.entries[1].service_descriptor {
234            VbiService::Lines(lines) => {
235                assert_eq!(lines.len(), 2);
236                assert!(lines[0].field_parity);
237                assert_eq!(lines[0].line_offset, 0x1B);
238                assert!(!lines[1].field_parity);
239                assert_eq!(lines[1].line_offset, 0x0C);
240            }
241            other => panic!("expected Lines, got {other:?}"),
242        }
243    }
244
245    #[test]
246    fn parse_entry_with_empty_service_block() {
247        let bytes = [TAG, 2, 0x06, 0x00];
248        let d = VbiDataDescriptor::parse(&bytes).unwrap();
249        assert_eq!(d.entries.len(), 1);
250        match &d.entries[0].service_descriptor {
251            VbiService::Lines(lines) => assert!(lines.is_empty()),
252            other => panic!("expected Lines, got {other:?}"),
253        }
254    }
255
256    #[test]
257    fn parse_reserved_data_service_id() {
258        let bytes = [TAG, 4, 0x00, 0x02, 0xDE, 0xAD];
259        let d = VbiDataDescriptor::parse(&bytes).unwrap();
260        assert_eq!(d.entries.len(), 1);
261        assert_eq!(d.entries[0].data_service_id, 0x00);
262        match &d.entries[0].service_descriptor {
263            VbiService::Reserved(b) => assert_eq!(*b, &[0xDE, 0xAD]),
264            other => panic!("expected Reserved, got {other:?}"),
265        }
266    }
267
268    #[test]
269    fn parse_rejects_wrong_tag() {
270        assert!(matches!(
271            VbiDataDescriptor::parse(&[0x46, 0]).unwrap_err(),
272            Error::InvalidDescriptor { tag: 0x46, .. }
273        ));
274    }
275
276    #[test]
277    fn parse_rejects_short_buffer() {
278        // declares 4 body bytes, only 2 present
279        let bytes = [TAG, 4, 0x01, 0x02];
280        assert!(matches!(
281            VbiDataDescriptor::parse(&bytes).unwrap_err(),
282            Error::BufferTooShort { .. }
283        ));
284    }
285
286    #[test]
287    fn parse_rejects_inner_length_overrun() {
288        // entry declares 5 service bytes but only 1 remains in body
289        let bytes = [TAG, 3, 0x01, 0x05, 0xAA];
290        assert!(matches!(
291            VbiDataDescriptor::parse(&bytes).unwrap_err(),
292            Error::InvalidDescriptor { tag: TAG, .. }
293        ));
294    }
295
296    #[test]
297    fn empty_descriptor_valid() {
298        let d = VbiDataDescriptor::parse(&[TAG, 0]).unwrap();
299        assert!(d.entries.is_empty());
300    }
301
302    #[test]
303    fn serialize_round_trip() {
304        let d = VbiDataDescriptor {
305            entries: vec![
306                VbiDataEntry {
307                    data_service_id: 0x01,
308                    service_descriptor: VbiService::Lines(vec![
309                        VbiLine {
310                            field_parity: false,
311                            line_offset: 0x01,
312                        },
313                        VbiLine {
314                            field_parity: false,
315                            line_offset: 0x02,
316                        },
317                        VbiLine {
318                            field_parity: false,
319                            line_offset: 0x03,
320                        },
321                    ]),
322                },
323                VbiDataEntry {
324                    data_service_id: 0x04,
325                    service_descriptor: VbiService::Lines(vec![]),
326                },
327            ],
328        };
329        let mut buf = vec![0u8; d.serialized_len()];
330        d.serialize_into(&mut buf).unwrap();
331        assert_eq!(VbiDataDescriptor::parse(&buf).unwrap(), d);
332    }
333
334    #[test]
335    fn serialize_round_trip_reserved() {
336        let d = VbiDataDescriptor {
337            entries: vec![VbiDataEntry {
338                data_service_id: 0x80,
339                service_descriptor: VbiService::Reserved(&[0xDE, 0xAD]),
340            }],
341        };
342        let mut buf = vec![0u8; d.serialized_len()];
343        d.serialize_into(&mut buf).unwrap();
344        assert_eq!(VbiDataDescriptor::parse(&buf).unwrap(), d);
345    }
346
347    #[test]
348    fn byte_identity_with_reserved_bits() {
349        let bytes = [TAG, 4, 0x01, 0x02, 0xC1, 0xC2];
350        let d = VbiDataDescriptor::parse(&bytes).unwrap();
351        let mut buf = vec![0u8; d.serialized_len()];
352        d.serialize_into(&mut buf).unwrap();
353        assert_eq!(buf, bytes);
354    }
355
356    #[test]
357    fn data_service_id_0x03_is_reserved() {
358        let bytes = [TAG, 3, 0x03, 0x01, 0xAA];
359        let d = VbiDataDescriptor::parse(&bytes).unwrap();
360        assert_eq!(d.entries[0].data_service_id, 0x03);
361        match &d.entries[0].service_descriptor {
362            VbiService::Reserved(b) => assert_eq!(*b, &[0xAA]),
363            other => panic!("expected Reserved for id 0x03, got {other:?}"),
364        }
365    }
366
367    #[test]
368    fn serialize_rejects_small_buffer() {
369        let d = VbiDataDescriptor {
370            entries: vec![VbiDataEntry {
371                data_service_id: 0x01,
372                service_descriptor: VbiService::Lines(vec![VbiLine {
373                    field_parity: false,
374                    line_offset: 0x0A,
375                }]),
376            }],
377        };
378        let mut tiny = [0u8; 3];
379        assert!(matches!(
380            d.serialize_into(&mut tiny).unwrap_err(),
381            Error::OutputBufferTooSmall { .. }
382        ));
383    }
384
385    #[cfg(feature = "serde")]
386    #[test]
387    fn serde_serialize_stable() {
388        // Borrowed-byte fields cannot deserialize from a JSON array (serde_json
389        // requires a borrowed-str for &[u8]); assert the Serialize half is
390        // stable, matching the other borrowed descriptors (e.g.
391        // content_identifier) in this crate.
392        let make = || VbiDataDescriptor {
393            entries: vec![VbiDataEntry {
394                data_service_id: 0x01,
395                service_descriptor: VbiService::Lines(vec![
396                    VbiLine {
397                        field_parity: false,
398                        line_offset: 0x01,
399                    },
400                    VbiLine {
401                        field_parity: false,
402                        line_offset: 0x02,
403                    },
404                ]),
405            }],
406        };
407        let json = serde_json::to_string(&make()).unwrap();
408        assert!(json.contains("data_service_id"));
409        assert_eq!(json, serde_json::to_string(&make()).unwrap());
410    }
411}