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