Skip to main content

dvb_si/descriptors/
teletext.rs

1//! Teletext Descriptor — ETSI EN 300 468 §6.2.44 (tag 0x56).
2//!
3//! Carried inside PMT's ES_info loop. Enumerates teletext components: one
4//! entry per 3-char language code + type/magazine/page triple (5 bytes).
5
6use crate::error::{Error, Result};
7use crate::text::LangCode;
8use crate::traits::Descriptor;
9use dvb_common::{Parse, Serialize};
10
11/// Descriptor tag for teletext_descriptor.
12pub const TAG: u8 = 0x56;
13const HEADER_LEN: usize = 2;
14const ENTRY_LEN: usize = 5;
15const LANG_LEN: usize = 3;
16
17/// One teletext component.
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct TeletextEntry {
21    /// ISO 639-2 language code of this teletext service.
22    pub language_code: LangCode,
23    /// 5-bit teletext_type (ETSI Table 102): 1 = initial page, 2 = subtitle, etc.
24    pub teletext_type: u8,
25    /// 3-bit teletext_magazine_number.
26    pub magazine_number: u8,
27    /// 8-bit BCD teletext_page_number.
28    pub page_number: u8,
29}
30
31/// Teletext Descriptor.
32#[derive(Debug, Clone, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct TeletextDescriptor {
35    /// Teletext components listed in wire order.
36    pub entries: Vec<TeletextEntry>,
37}
38
39impl<'a> Parse<'a> for TeletextDescriptor {
40    type Error = crate::error::Error;
41    fn parse(bytes: &'a [u8]) -> Result<Self> {
42        if bytes.len() < HEADER_LEN {
43            return Err(Error::BufferTooShort {
44                need: HEADER_LEN,
45                have: bytes.len(),
46                what: "TeletextDescriptor header",
47            });
48        }
49        if bytes[0] != TAG {
50            return Err(Error::InvalidDescriptor {
51                tag: bytes[0],
52                reason: "unexpected tag for teletext_descriptor",
53            });
54        }
55        let length = bytes[1] as usize;
56        if bytes.len() < HEADER_LEN + length {
57            return Err(Error::BufferTooShort {
58                need: HEADER_LEN + length,
59                have: bytes.len(),
60                what: "TeletextDescriptor body",
61            });
62        }
63        if length % ENTRY_LEN != 0 {
64            return Err(Error::InvalidDescriptor {
65                tag: TAG,
66                reason: "teletext_descriptor length must be a multiple of 5",
67            });
68        }
69        let body = &bytes[HEADER_LEN..HEADER_LEN + length];
70        let mut entries = Vec::with_capacity(length / ENTRY_LEN);
71        for chunk in body.chunks_exact(ENTRY_LEN) {
72            let language_code = LangCode([chunk[0], chunk[1], chunk[2]]);
73            let type_and_mag = chunk[LANG_LEN];
74            let teletext_type = (type_and_mag >> 3) & 0x1F;
75            let magazine_number = type_and_mag & 0x07;
76            let page_number = chunk[LANG_LEN + 1];
77            entries.push(TeletextEntry {
78                language_code,
79                teletext_type,
80                magazine_number,
81                page_number,
82            });
83        }
84        Ok(Self { entries })
85    }
86}
87
88impl Serialize for TeletextDescriptor {
89    type Error = crate::error::Error;
90    fn serialized_len(&self) -> usize {
91        HEADER_LEN + self.entries.len() * ENTRY_LEN
92    }
93
94    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
95        let len = self.serialized_len();
96        if buf.len() < len {
97            return Err(Error::OutputBufferTooSmall {
98                need: len,
99                have: buf.len(),
100            });
101        }
102        buf[0] = TAG;
103        buf[1] = (self.entries.len() * ENTRY_LEN) as u8;
104        let mut pos = HEADER_LEN;
105        for e in &self.entries {
106            buf[pos..pos + LANG_LEN].copy_from_slice(&e.language_code.0);
107            buf[pos + LANG_LEN] = ((e.teletext_type & 0x1F) << 3) | (e.magazine_number & 0x07);
108            buf[pos + LANG_LEN + 1] = e.page_number;
109            pos += ENTRY_LEN;
110        }
111        Ok(len)
112    }
113}
114
115impl<'a> Descriptor<'a> for TeletextDescriptor {
116    const TAG: u8 = TAG;
117    fn descriptor_length(&self) -> u8 {
118        (self.entries.len() * ENTRY_LEN) as u8
119    }
120}
121
122impl<'a> crate::traits::DescriptorDef<'a> for TeletextDescriptor {
123    const TAG: u8 = TAG;
124    const NAME: &'static str = "TELETEXT";
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn parse_single_entry() {
133        // lang=eng, type=1, mag=2, page=0x10
134        let bytes = [TAG, 5, b'e', b'n', b'g', (1 << 3) | 2, 0x10];
135        let d = TeletextDescriptor::parse(&bytes).unwrap();
136        assert_eq!(d.entries.len(), 1);
137        assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
138        assert_eq!(d.entries[0].teletext_type, 1);
139        assert_eq!(d.entries[0].magazine_number, 2);
140        assert_eq!(d.entries[0].page_number, 0x10);
141    }
142
143    #[test]
144    fn parse_multiple_entries() {
145        let bytes = [
146            TAG,
147            10,
148            b'e',
149            b'n',
150            b'g',
151            (1 << 3) | 1,
152            0x10,
153            b'f',
154            b'r',
155            b'a',
156            (2 << 3) | 1,
157            0x20,
158        ];
159        let d = TeletextDescriptor::parse(&bytes).unwrap();
160        assert_eq!(d.entries.len(), 2);
161        assert_eq!(d.entries[1].teletext_type, 2);
162    }
163
164    #[test]
165    fn parse_rejects_wrong_tag() {
166        assert!(matches!(
167            TeletextDescriptor::parse(&[0x57, 0]).unwrap_err(),
168            Error::InvalidDescriptor { tag: 0x57, .. }
169        ));
170    }
171
172    #[test]
173    fn parse_rejects_length_not_multiple_of_5() {
174        let bytes = [TAG, 4, 0, 0, 0, 0];
175        assert!(matches!(
176            TeletextDescriptor::parse(&bytes).unwrap_err(),
177            Error::InvalidDescriptor { .. }
178        ));
179    }
180
181    #[test]
182    fn serialize_round_trip() {
183        let d = TeletextDescriptor {
184            entries: vec![TeletextEntry {
185                language_code: LangCode(*b"fra"),
186                teletext_type: 2,
187                magazine_number: 8 & 0x07,
188                page_number: 0x88,
189            }],
190        };
191        let mut buf = vec![0u8; d.serialized_len()];
192        d.serialize_into(&mut buf).unwrap();
193        assert_eq!(TeletextDescriptor::parse(&buf).unwrap(), d);
194    }
195
196    #[test]
197    fn empty_descriptor_valid() {
198        let bytes = [TAG, 0];
199        let d = TeletextDescriptor::parse(&bytes).unwrap();
200        assert_eq!(d.entries.len(), 0);
201    }
202}