1use crate::error::{Error, Result};
42use crate::traits::Table;
43use dvb_common::{Parse, Serialize};
44
45pub const TABLE_ID: u8 = 0x7C;
47pub const PID: u16 = 0x0000;
50
51pub const FONT_INFO_TYPE_STYLE_WEIGHT: u8 = 0x00;
53pub const FONT_INFO_TYPE_FILE_URI: u8 = 0x01;
55pub const FONT_INFO_TYPE_FONT_SIZE: u8 = 0x02;
57
58const HEADER_LEN: usize = 8;
61const SECTION_LENGTH_PREFIX: usize = 3;
63const CRC_LEN: usize = 4;
65
66#[derive(Debug, Clone, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub enum FontInfo<'a> {
73 StyleWeight {
75 style: u8,
77 weight: u8,
79 },
80 FileUri {
82 format: u8,
84 #[cfg_attr(feature = "serde", serde(borrow))]
86 uri: &'a [u8],
87 },
88 FontSize {
90 size: u16,
92 #[cfg_attr(feature = "serde", serde(borrow))]
94 info: &'a [u8],
95 },
96 LengthDelimited {
98 font_info_type: u8,
100 #[cfg_attr(feature = "serde", serde(borrow))]
102 info: &'a [u8],
103 },
104}
105
106#[derive(Debug, Clone, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109#[cfg_attr(feature = "serde", serde(bound(deserialize = "'de: 'a")))]
110pub struct DownloadableFontInfoSection<'a> {
111 pub font_id_extension: u16,
114 pub font_id: u8,
116 pub version_number: u8,
118 pub current_next_indicator: bool,
120 pub section_number: u8,
122 pub last_section_number: u8,
124 #[cfg_attr(feature = "serde", serde(borrow))]
126 pub font_info: Vec<FontInfo<'a>>,
127}
128
129impl<'a> Parse<'a> for DownloadableFontInfoSection<'a> {
130 type Error = crate::error::Error;
131 fn parse(bytes: &'a [u8]) -> Result<Self> {
132 let min_len = HEADER_LEN + CRC_LEN;
133 if bytes.len() < min_len {
134 return Err(Error::BufferTooShort {
135 need: min_len,
136 have: bytes.len(),
137 what: "DownloadableFontInfoSection",
138 });
139 }
140 if bytes[0] != TABLE_ID {
141 return Err(Error::UnexpectedTableId {
142 table_id: bytes[0],
143 what: "DownloadableFontInfoSection",
144 expected: &[TABLE_ID],
145 });
146 }
147 let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
148 let total = SECTION_LENGTH_PREFIX + section_length;
149 if bytes.len() < total || total < HEADER_LEN + CRC_LEN {
150 return Err(Error::SectionLengthOverflow {
151 declared: section_length,
152 available: bytes.len().saturating_sub(SECTION_LENGTH_PREFIX),
153 });
154 }
155
156 let id_word = u16::from_be_bytes([bytes[3], bytes[4]]);
158 let font_id_extension = id_word >> 7;
159 let font_id = (id_word & 0x7F) as u8;
160 let version_number = (bytes[5] >> 1) & 0x1F;
161 let current_next_indicator = bytes[5] & 0x01 != 0;
162 let section_number = bytes[6];
163 let last_section_number = bytes[7];
164
165 let loop_end = total - CRC_LEN;
166 let mut font_info = Vec::new();
167 let mut pos = HEADER_LEN;
168 while pos < loop_end {
169 let font_info_type = bytes[pos];
170 pos += 1;
171 match font_info_type {
172 FONT_INFO_TYPE_STYLE_WEIGHT => {
173 if pos + 1 > loop_end {
174 return Err(Error::SectionLengthOverflow {
175 declared: 1,
176 available: loop_end - pos,
177 });
178 }
179 let b = bytes[pos];
180 pos += 1;
181 font_info.push(FontInfo::StyleWeight {
182 style: b >> 5,
183 weight: (b >> 1) & 0x0F,
184 });
185 }
186 FONT_INFO_TYPE_FILE_URI => {
187 if pos + 2 > loop_end {
188 return Err(Error::SectionLengthOverflow {
189 declared: 2,
190 available: loop_end - pos,
191 });
192 }
193 let format = bytes[pos] & 0x0F;
194 let uri_length = bytes[pos + 1] as usize;
195 let uri_start = pos + 2;
196 let uri_end = uri_start + uri_length;
197 if uri_end > loop_end {
198 return Err(Error::SectionLengthOverflow {
199 declared: uri_length,
200 available: loop_end - uri_start,
201 });
202 }
203 font_info.push(FontInfo::FileUri {
204 format,
205 uri: &bytes[uri_start..uri_end],
206 });
207 pos = uri_end;
208 }
209 FONT_INFO_TYPE_FONT_SIZE => {
210 if pos + 3 > loop_end {
212 return Err(Error::SectionLengthOverflow {
213 declared: 3,
214 available: loop_end - pos,
215 });
216 }
217 let size = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
218 let info_length = bytes[pos + 2] as usize;
219 let info_start = pos + 3;
220 let info_end = info_start + info_length;
221 if info_end > loop_end {
222 return Err(Error::SectionLengthOverflow {
223 declared: info_length,
224 available: loop_end - info_start,
225 });
226 }
227 font_info.push(FontInfo::FontSize {
228 size,
229 info: &bytes[info_start..info_end],
230 });
231 pos = info_end;
232 }
233 _ => {
234 if pos + 1 > loop_end {
236 return Err(Error::SectionLengthOverflow {
237 declared: 1,
238 available: loop_end - pos,
239 });
240 }
241 let info_length = bytes[pos] as usize;
242 let info_start = pos + 1;
243 let info_end = info_start + info_length;
244 if info_end > loop_end {
245 return Err(Error::SectionLengthOverflow {
246 declared: info_length,
247 available: loop_end - info_start,
248 });
249 }
250 font_info.push(FontInfo::LengthDelimited {
251 font_info_type,
252 info: &bytes[info_start..info_end],
253 });
254 pos = info_end;
255 }
256 }
257 }
258
259 Ok(DownloadableFontInfoSection {
260 font_id_extension,
261 font_id,
262 version_number,
263 current_next_indicator,
264 section_number,
265 last_section_number,
266 font_info,
267 })
268 }
269}
270
271impl Serialize for DownloadableFontInfoSection<'_> {
272 type Error = crate::error::Error;
273 fn serialized_len(&self) -> usize {
274 let loop_bytes: usize = self
275 .font_info
276 .iter()
277 .map(|f| match f {
278 FontInfo::StyleWeight { .. } => 2, FontInfo::FileUri { uri, .. } => 1 + 2 + uri.len(), FontInfo::FontSize { info, .. } => 1 + 2 + 1 + info.len(), FontInfo::LengthDelimited { info, .. } => 1 + 1 + info.len(), })
283 .sum();
284 HEADER_LEN + loop_bytes + CRC_LEN
285 }
286 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
287 let len = self.serialized_len();
288 if buf.len() < len {
289 return Err(Error::OutputBufferTooSmall {
290 need: len,
291 have: buf.len(),
292 });
293 }
294 let section_length = (len - SECTION_LENGTH_PREFIX) as u16;
295 buf[0] = TABLE_ID;
296 buf[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
298 buf[2] = (section_length & 0xFF) as u8;
299 let id_word = ((self.font_id_extension & 0x01FF) << 7) | (self.font_id as u16 & 0x7F);
301 buf[3..5].copy_from_slice(&id_word.to_be_bytes());
302 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
304 buf[6] = self.section_number;
305 buf[7] = self.last_section_number;
306
307 let guard_u8 = |len: usize| -> Result<()> {
310 if len > u8::MAX as usize {
311 return Err(Error::SectionLengthOverflow {
312 declared: len,
313 available: u8::MAX as usize,
314 });
315 }
316 Ok(())
317 };
318
319 let mut pos = HEADER_LEN;
320 for f in &self.font_info {
321 match f {
322 FontInfo::StyleWeight { style, weight } => {
323 buf[pos] = FONT_INFO_TYPE_STYLE_WEIGHT;
324 buf[pos + 1] = ((style & 0x07) << 5) | ((weight & 0x0F) << 1);
326 pos += 2;
327 }
328 FontInfo::FileUri { format, uri } => {
329 guard_u8(uri.len())?;
330 buf[pos] = FONT_INFO_TYPE_FILE_URI;
331 buf[pos + 1] = format & 0x0F;
333 buf[pos + 2] = uri.len() as u8;
334 let s = pos + 3;
335 buf[s..s + uri.len()].copy_from_slice(uri);
336 pos = s + uri.len();
337 }
338 FontInfo::FontSize { size, info } => {
339 guard_u8(info.len())?;
340 buf[pos] = FONT_INFO_TYPE_FONT_SIZE;
341 buf[pos + 1..pos + 3].copy_from_slice(&size.to_be_bytes());
342 buf[pos + 3] = info.len() as u8;
343 let s = pos + 4;
344 buf[s..s + info.len()].copy_from_slice(info);
345 pos = s + info.len();
346 }
347 FontInfo::LengthDelimited {
348 font_info_type,
349 info,
350 } => {
351 guard_u8(info.len())?;
352 buf[pos] = *font_info_type;
353 buf[pos + 1] = info.len() as u8;
354 let s = pos + 2;
355 buf[s..s + info.len()].copy_from_slice(info);
356 pos = s + info.len();
357 }
358 }
359 }
360
361 let crc = dvb_common::crc32_mpeg2::compute(&buf[..pos]);
362 buf[pos..len].copy_from_slice(&crc.to_be_bytes());
363 Ok(len)
364 }
365}
366
367impl<'a> Table<'a> for DownloadableFontInfoSection<'a> {
368 const TABLE_ID: u8 = TABLE_ID;
369 const PID: u16 = PID;
370}
371
372impl<'a> crate::traits::TableDef<'a> for DownloadableFontInfoSection<'a> {
373 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
374 const NAME: &'static str = "DOWNLOADABLE_FONT_INFO";
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 fn build_section(font_id: u8, version: u8, loop_body: &[u8]) -> Vec<u8> {
383 let section_length =
384 (HEADER_LEN - SECTION_LENGTH_PREFIX + loop_body.len() + CRC_LEN) as u16;
385 let id_word = (font_id as u16) & 0x7F;
387 let mut v = vec![
388 TABLE_ID,
389 0xB0 | ((section_length >> 8) as u8 & 0x0F),
390 (section_length & 0xFF) as u8,
391 (id_word >> 8) as u8,
392 (id_word & 0xFF) as u8,
393 0xC0 | (version << 1) | 0x01,
394 0x00,
395 0x00,
396 ];
397 v.extend_from_slice(loop_body);
398 v.extend_from_slice(&[0, 0, 0, 0]);
399 v
400 }
401
402 fn mixed_loop() -> Vec<u8> {
404 let uri = b"https://f.example/Droid.otf";
405 let family = b"Droid Sans";
406 let mut b = vec![
407 FONT_INFO_TYPE_STYLE_WEIGHT, (2u8 << 5) | (2u8 << 1), FONT_INFO_TYPE_FILE_URI, 0x01, uri.len() as u8, ];
413 b.extend_from_slice(uri);
414 b.push(FONT_INFO_TYPE_FONT_SIZE);
416 b.extend_from_slice(&24u16.to_be_bytes());
417 b.push(2);
418 b.extend_from_slice(b"px");
419 b.push(0x03);
421 b.push(family.len() as u8);
422 b.extend_from_slice(family);
423 b
424 }
425
426 #[test]
427 fn parse_header_fields() {
428 let bytes = build_section(0x42, 9, &[]);
429 let sec = DownloadableFontInfoSection::parse(&bytes).unwrap();
430 assert_eq!(sec.font_id, 0x42);
431 assert_eq!(sec.font_id_extension, 0);
432 assert_eq!(sec.version_number, 9);
433 assert!(sec.current_next_indicator);
434 assert!(sec.font_info.is_empty());
435 }
436
437 #[test]
438 fn parse_all_variants() {
439 let bytes = build_section(1, 0, &mixed_loop());
440 let sec = DownloadableFontInfoSection::parse(&bytes).unwrap();
441 assert_eq!(sec.font_info.len(), 4);
442 assert_eq!(
443 sec.font_info[0],
444 FontInfo::StyleWeight {
445 style: 2,
446 weight: 2
447 }
448 );
449 match &sec.font_info[1] {
450 FontInfo::FileUri { format, uri } => {
451 assert_eq!(*format, 1);
452 assert_eq!(*uri, b"https://f.example/Droid.otf");
453 }
454 other => panic!("expected FileUri, got {other:?}"),
455 }
456 match &sec.font_info[2] {
457 FontInfo::FontSize { size, info } => {
458 assert_eq!(*size, 24);
459 assert_eq!(*info, b"px");
460 }
461 other => panic!("expected FontSize, got {other:?}"),
462 }
463 match &sec.font_info[3] {
464 FontInfo::LengthDelimited {
465 font_info_type,
466 info,
467 } => {
468 assert_eq!(*font_info_type, 0x03);
469 assert_eq!(*info, b"Droid Sans");
470 }
471 other => panic!("expected LengthDelimited, got {other:?}"),
472 }
473 }
474
475 #[test]
476 fn reserved_type_round_trips_as_length_delimited() {
477 let mut body = vec![0x77u8, 0x03];
479 body.extend_from_slice(&[0xAA, 0xBB, 0xCC]);
480 let bytes = build_section(1, 0, &body);
481 let sec = DownloadableFontInfoSection::parse(&bytes).unwrap();
482 assert_eq!(
483 sec.font_info[0],
484 FontInfo::LengthDelimited {
485 font_info_type: 0x77,
486 info: &[0xAA, 0xBB, 0xCC]
487 }
488 );
489 }
490
491 #[test]
492 fn parse_rejects_wrong_tag() {
493 let mut bytes = build_section(1, 0, &mixed_loop());
494 bytes[0] = 0x4C; assert!(matches!(
496 DownloadableFontInfoSection::parse(&bytes).unwrap_err(),
497 Error::UnexpectedTableId { table_id: 0x4C, .. }
498 ));
499 }
500
501 #[test]
502 fn rejects_short_buffer() {
503 assert!(matches!(
504 DownloadableFontInfoSection::parse(&[0x7C, 0xB0]).unwrap_err(),
505 Error::BufferTooShort {
506 what: "DownloadableFontInfoSection",
507 ..
508 }
509 ));
510 }
511
512 #[test]
513 fn uri_length_overflow_rejected() {
514 let body = vec![FONT_INFO_TYPE_FILE_URI, 0x01, 0x20];
516 let bytes = build_section(1, 0, &body);
517 assert!(matches!(
518 DownloadableFontInfoSection::parse(&bytes).unwrap_err(),
519 Error::SectionLengthOverflow { .. }
520 ));
521 }
522
523 #[test]
524 fn round_trip_all_variants() {
525 let bytes = build_section(0x33, 4, &mixed_loop());
526 let sec = DownloadableFontInfoSection::parse(&bytes).unwrap();
527 let mut buf = vec![0u8; sec.serialized_len()];
528 sec.serialize_into(&mut buf).unwrap();
529 let re = DownloadableFontInfoSection::parse(&buf).unwrap();
530 assert_eq!(sec, re);
531 }
532
533 #[test]
534 fn table_trait_constants() {
535 assert_eq!(<DownloadableFontInfoSection as Table>::TABLE_ID, 0x7C);
536 assert_eq!(<DownloadableFontInfoSection as Table>::PID, 0x0000);
537 }
538
539 #[test]
540 #[cfg(feature = "serde")]
541 fn serde_json_round_trip() {
542 let bytes = build_section(1, 0, &mixed_loop());
543 let sec = DownloadableFontInfoSection::parse(&bytes).unwrap();
544 let j = serde_json::to_string(&sec).unwrap();
545 let reparsed = DownloadableFontInfoSection::parse(&bytes).unwrap();
551 assert_eq!(serde_json::to_string(&reparsed).unwrap(), j);
552 assert!(j.contains("\"font_id\":1"));
553 }
554}