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