1use super::descriptor_body;
9use crate::error::{Error, Result};
10use crate::text::LangCode;
11use dvb_common::{Parse, Serialize};
12
13pub const TAG: u8 = 0x46;
15const HEADER_LEN: usize = 2;
16const ENTRY_LEN: usize = 5;
17const LANG_LEN: usize = 3;
18const MAX_BODY_LEN: usize = u8::MAX as usize;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24pub struct VbiTeletextEntry {
25 pub language_code: LangCode,
27 pub teletext_type: u8,
29 pub magazine_number: u8,
31 pub page_number: u8,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize))]
38pub struct VbiTeletextDescriptor {
39 pub entries: Vec<VbiTeletextEntry>,
41}
42
43impl<'a> Parse<'a> for VbiTeletextDescriptor {
44 type Error = crate::error::Error;
45 fn parse(bytes: &'a [u8]) -> Result<Self> {
46 let body = descriptor_body(
47 bytes,
48 TAG,
49 "VbiTeletextDescriptor",
50 "unexpected tag for VBI_teletext_descriptor",
51 )?;
52 if body.len() % ENTRY_LEN != 0 {
53 return Err(Error::InvalidDescriptor {
54 tag: TAG,
55 reason: "descriptor_length must be a multiple of 5",
56 });
57 }
58 let mut entries = Vec::with_capacity(body.len() / ENTRY_LEN);
59 for chunk in body.chunks_exact(ENTRY_LEN) {
60 let language_code = LangCode([chunk[0], chunk[1], chunk[2]]);
61 let type_and_mag = chunk[LANG_LEN];
62 entries.push(VbiTeletextEntry {
63 language_code,
64 teletext_type: (type_and_mag >> 3) & 0x1F,
65 magazine_number: type_and_mag & 0x07,
66 page_number: chunk[LANG_LEN + 1],
67 });
68 }
69 Ok(Self { entries })
70 }
71}
72
73impl Serialize for VbiTeletextDescriptor {
74 type Error = crate::error::Error;
75 fn serialized_len(&self) -> usize {
76 HEADER_LEN + ENTRY_LEN * self.entries.len()
77 }
78
79 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
80 let len = self.serialized_len();
81 if buf.len() < len {
82 return Err(Error::OutputBufferTooSmall {
83 need: len,
84 have: buf.len(),
85 });
86 }
87 let body_len = ENTRY_LEN * self.entries.len();
88 if body_len > MAX_BODY_LEN {
90 return Err(Error::InvalidDescriptor {
91 tag: TAG,
92 reason: "VBI_teletext_descriptor body exceeds 255 bytes",
93 });
94 }
95 buf[0] = TAG;
96 buf[1] = body_len as u8;
97 let mut pos = HEADER_LEN;
98 for e in &self.entries {
99 buf[pos..pos + LANG_LEN].copy_from_slice(&e.language_code.0);
100 buf[pos + LANG_LEN] = ((e.teletext_type & 0x1F) << 3) | (e.magazine_number & 0x07);
101 buf[pos + LANG_LEN + 1] = e.page_number;
102 pos += ENTRY_LEN;
103 }
104 Ok(len)
105 }
106}
107impl<'a> crate::traits::DescriptorDef<'a> for VbiTeletextDescriptor {
108 const TAG: u8 = TAG;
109 const NAME: &'static str = "VBI_TELETEXT";
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn parse_single_entry() {
118 let bytes = [TAG, 5, b'e', b'n', b'g', (1 << 3) | 2, 0x10];
119 let d = VbiTeletextDescriptor::parse(&bytes).unwrap();
120 assert_eq!(d.entries.len(), 1);
121 assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
122 assert_eq!(d.entries[0].teletext_type, 1);
123 assert_eq!(d.entries[0].magazine_number, 2);
124 assert_eq!(d.entries[0].page_number, 0x10);
125 }
126
127 #[test]
128 fn parse_multiple_entries() {
129 let bytes = [
130 TAG,
131 10,
132 b'e',
133 b'n',
134 b'g',
135 (1 << 3) | 1,
136 0x10,
137 b'f',
138 b'r',
139 b'a',
140 (2 << 3) | 1,
141 0x20,
142 ];
143 let d = VbiTeletextDescriptor::parse(&bytes).unwrap();
144 assert_eq!(d.entries.len(), 2);
145 assert_eq!(d.entries[1].teletext_type, 2);
146 assert_eq!(d.entries[1].language_code, LangCode(*b"fra"));
147 }
148
149 #[test]
150 fn parse_rejects_wrong_tag() {
151 assert!(matches!(
152 VbiTeletextDescriptor::parse(&[0x47, 0]).unwrap_err(),
153 Error::InvalidDescriptor { tag: 0x47, .. }
154 ));
155 }
156
157 #[test]
158 fn parse_rejects_short_buffer() {
159 let bytes = [TAG, 5, b'e', b'n'];
160 assert!(matches!(
161 VbiTeletextDescriptor::parse(&bytes).unwrap_err(),
162 Error::BufferTooShort { .. }
163 ));
164 }
165
166 #[test]
167 fn parse_rejects_length_not_multiple_of_5() {
168 let bytes = [TAG, 4, 0, 0, 0, 0];
169 assert!(matches!(
170 VbiTeletextDescriptor::parse(&bytes).unwrap_err(),
171 Error::InvalidDescriptor { tag: TAG, .. }
172 ));
173 }
174
175 #[test]
176 fn empty_descriptor_valid() {
177 let d = VbiTeletextDescriptor::parse(&[TAG, 0]).unwrap();
178 assert!(d.entries.is_empty());
179 }
180
181 #[test]
182 fn serialize_round_trip() {
183 let d = VbiTeletextDescriptor {
184 entries: vec![VbiTeletextEntry {
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!(VbiTeletextDescriptor::parse(&buf).unwrap(), d);
194 }
195
196 #[test]
197 fn serialize_rejects_over_range_body() {
198 let d = VbiTeletextDescriptor {
200 entries: vec![
201 VbiTeletextEntry {
202 language_code: LangCode(*b"eng"),
203 teletext_type: 1,
204 magazine_number: 1,
205 page_number: 0,
206 };
207 52
208 ],
209 };
210 let mut buf = vec![0u8; d.serialized_len()];
211 assert!(matches!(
212 d.serialize_into(&mut buf).unwrap_err(),
213 Error::InvalidDescriptor { tag: TAG, .. }
214 ));
215 }
216
217 #[cfg(feature = "serde")]
218 #[test]
219 fn serde_round_trip() {
220 let d = VbiTeletextDescriptor {
221 entries: vec![VbiTeletextEntry {
222 language_code: LangCode(*b"eng"),
223 teletext_type: 2,
224 magazine_number: 1,
225 page_number: 0x10,
226 }],
227 };
228 let json = serde_json::to_string(&d).unwrap();
229 let _v: serde_json::Value = serde_json::from_str(&json).unwrap();
231 }
232}