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}
79dvb_common::impl_spec_display!(TeletextType, Reserved);
80
81#[derive(Debug, Clone, PartialEq, Eq)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize))]
84pub struct TeletextEntry {
85 pub language_code: LangCode,
87 pub teletext_type: TeletextType,
89 pub magazine_number: u8,
91 pub page_number: u8,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize))]
98pub struct TeletextDescriptor {
99 pub entries: Vec<TeletextEntry>,
101}
102
103impl<'a> Parse<'a> for TeletextDescriptor {
104 type Error = crate::error::Error;
105 fn parse(bytes: &'a [u8]) -> Result<Self> {
106 let body = descriptor_body(
107 bytes,
108 TAG,
109 "TeletextDescriptor",
110 "unexpected tag for teletext_descriptor",
111 )?;
112 if body.len() % ENTRY_LEN != 0 {
113 return Err(Error::InvalidDescriptor {
114 tag: TAG,
115 reason: "teletext_descriptor length must be a multiple of 5",
116 });
117 }
118 let mut entries = Vec::with_capacity(body.len() / ENTRY_LEN);
119 for chunk in body.chunks_exact(ENTRY_LEN) {
120 let language_code = LangCode([chunk[0], chunk[1], chunk[2]]);
121 let type_and_mag = chunk[LANG_LEN];
122 let teletext_type = TeletextType::from_u8((type_and_mag >> 3) & 0x1F);
123 let magazine_number = type_and_mag & 0x07;
124 let page_number = chunk[LANG_LEN + 1];
125 entries.push(TeletextEntry {
126 language_code,
127 teletext_type,
128 magazine_number,
129 page_number,
130 });
131 }
132 Ok(Self { entries })
133 }
134}
135
136impl Serialize for TeletextDescriptor {
137 type Error = crate::error::Error;
138 fn serialized_len(&self) -> usize {
139 HEADER_LEN + self.entries.len() * ENTRY_LEN
140 }
141
142 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
143 let len = self.serialized_len();
144 if buf.len() < len {
145 return Err(Error::OutputBufferTooSmall {
146 need: len,
147 have: buf.len(),
148 });
149 }
150 buf[0] = TAG;
151 buf[1] = (self.entries.len() * ENTRY_LEN) as u8;
152 let mut pos = HEADER_LEN;
153 for e in &self.entries {
154 buf[pos..pos + LANG_LEN].copy_from_slice(&e.language_code.0);
155 buf[pos + LANG_LEN] =
156 ((e.teletext_type.to_u8() & 0x1F) << 3) | (e.magazine_number & 0x07);
157 buf[pos + LANG_LEN + 1] = e.page_number;
158 pos += ENTRY_LEN;
159 }
160 Ok(len)
161 }
162}
163impl<'a> crate::traits::DescriptorDef<'a> for TeletextDescriptor {
164 const TAG: u8 = TAG;
165 const NAME: &'static str = "TELETEXT";
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn parse_single_entry() {
174 let bytes = [TAG, 5, b'e', b'n', b'g', (1 << 3) | 2, 0x10];
176 let d = TeletextDescriptor::parse(&bytes).unwrap();
177 assert_eq!(d.entries.len(), 1);
178 assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
179 assert_eq!(d.entries[0].teletext_type, TeletextType::InitialPage);
180 assert_eq!(d.entries[0].magazine_number, 2);
181 assert_eq!(d.entries[0].page_number, 0x10);
182 }
183
184 #[test]
185 fn parse_multiple_entries() {
186 let bytes = [
187 TAG,
188 10,
189 b'e',
190 b'n',
191 b'g',
192 (1 << 3) | 1,
193 0x10,
194 b'f',
195 b'r',
196 b'a',
197 (2 << 3) | 1,
198 0x20,
199 ];
200 let d = TeletextDescriptor::parse(&bytes).unwrap();
201 assert_eq!(d.entries.len(), 2);
202 assert_eq!(d.entries[1].teletext_type, TeletextType::SubtitlePage);
203 }
204
205 #[test]
206 fn parse_rejects_wrong_tag() {
207 assert!(matches!(
208 TeletextDescriptor::parse(&[0x57, 0]).unwrap_err(),
209 Error::InvalidDescriptor { tag: 0x57, .. }
210 ));
211 }
212
213 #[test]
214 fn parse_rejects_length_not_multiple_of_5() {
215 let bytes = [TAG, 4, 0, 0, 0, 0];
216 assert!(matches!(
217 TeletextDescriptor::parse(&bytes).unwrap_err(),
218 Error::InvalidDescriptor { .. }
219 ));
220 }
221
222 #[test]
223 fn serialize_round_trip() {
224 let d = TeletextDescriptor {
225 entries: vec![TeletextEntry {
226 language_code: LangCode(*b"fra"),
227 teletext_type: TeletextType::SubtitlePage,
228 magazine_number: 8 & 0x07,
229 page_number: 0x88,
230 }],
231 };
232 let mut buf = vec![0u8; d.serialized_len()];
233 d.serialize_into(&mut buf).unwrap();
234 assert_eq!(TeletextDescriptor::parse(&buf).unwrap(), d);
235 }
236
237 #[test]
238 fn empty_descriptor_valid() {
239 let bytes = [TAG, 0];
240 let d = TeletextDescriptor::parse(&bytes).unwrap();
241 assert_eq!(d.entries.len(), 0);
242 }
243
244 #[test]
245 fn teletext_type_full_range_round_trip() {
246 for b in 0..=0xFF_u8 {
247 let tt = TeletextType::from_u8(b);
248 assert_eq!(tt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
249 }
250 }
251
252 #[test]
253 fn teletext_type_name_for_known() {
254 assert_eq!(TeletextType::InitialPage.name(), "initial teletext page");
255 assert_eq!(
256 TeletextType::HearingImpairedSubtitlePage.name(),
257 "teletext subtitle page for hearing impaired people"
258 );
259 assert_eq!(TeletextType::Reserved(0x06).name(), "reserved");
260 }
261}