1use super::descriptor_body;
7use crate::error::{Error, Result};
8use crate::text::LangCode;
9use dvb_common::{Parse, Serialize};
10
11pub const TAG: u8 = 0x56;
13const HEADER_LEN: usize = 2;
14const ENTRY_LEN: usize = 5;
15const LANG_LEN: usize = 3;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[non_exhaustive]
21pub enum TeletextType {
22 InitialPage,
24 SubtitlePage,
26 AdditionalInformationPage,
28 ProgrammeSchedulePage,
30 HearingImpairedSubtitlePage,
32 Reserved(u8),
34}
35
36impl TeletextType {
37 #[must_use]
38 pub fn from_u8(v: u8) -> Self {
41 match v {
42 0x01 => Self::InitialPage,
43 0x02 => Self::SubtitlePage,
44 0x03 => Self::AdditionalInformationPage,
45 0x04 => Self::ProgrammeSchedulePage,
46 0x05 => Self::HearingImpairedSubtitlePage,
47 v => Self::Reserved(v),
48 }
49 }
50
51 #[must_use]
52 pub fn to_u8(self) -> u8 {
54 match self {
55 Self::InitialPage => 0x01,
56 Self::SubtitlePage => 0x02,
57 Self::AdditionalInformationPage => 0x03,
58 Self::ProgrammeSchedulePage => 0x04,
59 Self::HearingImpairedSubtitlePage => 0x05,
60 Self::Reserved(v) => v,
61 }
62 }
63
64 #[must_use]
65 pub fn name(self) -> &'static str {
67 match self {
68 Self::InitialPage => "initial teletext page",
69 Self::SubtitlePage => "teletext subtitle page",
70 Self::AdditionalInformationPage => "additional information page",
71 Self::ProgrammeSchedulePage => "programme schedule page",
72 Self::HearingImpairedSubtitlePage => {
73 "teletext subtitle page for hearing impaired people"
74 }
75 Self::Reserved(_) => "reserved",
76 }
77 }
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize))]
83pub struct TeletextEntry {
84 pub language_code: LangCode,
86 pub teletext_type: TeletextType,
88 pub magazine_number: u8,
90 pub page_number: u8,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96#[cfg_attr(feature = "serde", derive(serde::Serialize))]
97pub struct TeletextDescriptor {
98 pub entries: Vec<TeletextEntry>,
100}
101
102impl<'a> Parse<'a> for TeletextDescriptor {
103 type Error = crate::error::Error;
104 fn parse(bytes: &'a [u8]) -> Result<Self> {
105 let body = descriptor_body(
106 bytes,
107 TAG,
108 "TeletextDescriptor",
109 "unexpected tag for teletext_descriptor",
110 )?;
111 if body.len() % ENTRY_LEN != 0 {
112 return Err(Error::InvalidDescriptor {
113 tag: TAG,
114 reason: "teletext_descriptor length must be a multiple of 5",
115 });
116 }
117 let mut entries = Vec::with_capacity(body.len() / ENTRY_LEN);
118 for chunk in body.chunks_exact(ENTRY_LEN) {
119 let language_code = LangCode([chunk[0], chunk[1], chunk[2]]);
120 let type_and_mag = chunk[LANG_LEN];
121 let teletext_type = TeletextType::from_u8((type_and_mag >> 3) & 0x1F);
122 let magazine_number = type_and_mag & 0x07;
123 let page_number = chunk[LANG_LEN + 1];
124 entries.push(TeletextEntry {
125 language_code,
126 teletext_type,
127 magazine_number,
128 page_number,
129 });
130 }
131 Ok(Self { entries })
132 }
133}
134
135impl Serialize for TeletextDescriptor {
136 type Error = crate::error::Error;
137 fn serialized_len(&self) -> usize {
138 HEADER_LEN + self.entries.len() * ENTRY_LEN
139 }
140
141 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
142 let len = self.serialized_len();
143 if buf.len() < len {
144 return Err(Error::OutputBufferTooSmall {
145 need: len,
146 have: buf.len(),
147 });
148 }
149 buf[0] = TAG;
150 buf[1] = (self.entries.len() * ENTRY_LEN) as u8;
151 let mut pos = HEADER_LEN;
152 for e in &self.entries {
153 buf[pos..pos + LANG_LEN].copy_from_slice(&e.language_code.0);
154 buf[pos + LANG_LEN] =
155 ((e.teletext_type.to_u8() & 0x1F) << 3) | (e.magazine_number & 0x07);
156 buf[pos + LANG_LEN + 1] = e.page_number;
157 pos += ENTRY_LEN;
158 }
159 Ok(len)
160 }
161}
162impl<'a> crate::traits::DescriptorDef<'a> for TeletextDescriptor {
163 const TAG: u8 = TAG;
164 const NAME: &'static str = "TELETEXT";
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn parse_single_entry() {
173 let bytes = [TAG, 5, b'e', b'n', b'g', (1 << 3) | 2, 0x10];
175 let d = TeletextDescriptor::parse(&bytes).unwrap();
176 assert_eq!(d.entries.len(), 1);
177 assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
178 assert_eq!(d.entries[0].teletext_type, TeletextType::InitialPage);
179 assert_eq!(d.entries[0].magazine_number, 2);
180 assert_eq!(d.entries[0].page_number, 0x10);
181 }
182
183 #[test]
184 fn parse_multiple_entries() {
185 let bytes = [
186 TAG,
187 10,
188 b'e',
189 b'n',
190 b'g',
191 (1 << 3) | 1,
192 0x10,
193 b'f',
194 b'r',
195 b'a',
196 (2 << 3) | 1,
197 0x20,
198 ];
199 let d = TeletextDescriptor::parse(&bytes).unwrap();
200 assert_eq!(d.entries.len(), 2);
201 assert_eq!(d.entries[1].teletext_type, TeletextType::SubtitlePage);
202 }
203
204 #[test]
205 fn parse_rejects_wrong_tag() {
206 assert!(matches!(
207 TeletextDescriptor::parse(&[0x57, 0]).unwrap_err(),
208 Error::InvalidDescriptor { tag: 0x57, .. }
209 ));
210 }
211
212 #[test]
213 fn parse_rejects_length_not_multiple_of_5() {
214 let bytes = [TAG, 4, 0, 0, 0, 0];
215 assert!(matches!(
216 TeletextDescriptor::parse(&bytes).unwrap_err(),
217 Error::InvalidDescriptor { .. }
218 ));
219 }
220
221 #[test]
222 fn serialize_round_trip() {
223 let d = TeletextDescriptor {
224 entries: vec![TeletextEntry {
225 language_code: LangCode(*b"fra"),
226 teletext_type: TeletextType::SubtitlePage,
227 magazine_number: 8 & 0x07,
228 page_number: 0x88,
229 }],
230 };
231 let mut buf = vec![0u8; d.serialized_len()];
232 d.serialize_into(&mut buf).unwrap();
233 assert_eq!(TeletextDescriptor::parse(&buf).unwrap(), d);
234 }
235
236 #[test]
237 fn empty_descriptor_valid() {
238 let bytes = [TAG, 0];
239 let d = TeletextDescriptor::parse(&bytes).unwrap();
240 assert_eq!(d.entries.len(), 0);
241 }
242
243 #[test]
244 fn teletext_type_full_range_round_trip() {
245 for b in 0..=0xFF_u8 {
246 let tt = TeletextType::from_u8(b);
247 assert_eq!(tt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
248 }
249 }
250
251 #[test]
252 fn teletext_type_name_for_known() {
253 assert_eq!(TeletextType::InitialPage.name(), "initial teletext page");
254 assert_eq!(
255 TeletextType::HearingImpairedSubtitlePage.name(),
256 "teletext subtitle page for hearing impaired people"
257 );
258 assert_eq!(TeletextType::Reserved(0x06).name(), "reserved");
259 }
260}