1use crate::compatibility::CompatibilityDescriptor;
15use crate::descriptors::DescriptorLoop;
16use crate::error::{Error, Result};
17use dvb_common::{Parse, Serialize};
18
19pub const TABLE_ID: u8 = 0x4B;
21
22pub const PID: u16 = 0x0000;
24
25const HEADER_LEN: usize = 3;
26const FIXED_BODY_LEN: usize = 9;
27const COMMON_DESC_LEN_FIELD: usize = 2;
28const CRC_LEN: usize = 4;
29const MIN_SECTION_LEN: usize = HEADER_LEN + FIXED_BODY_LEN + COMMON_DESC_LEN_FIELD + CRC_LEN;
30
31const OFFSET_ACTION_TYPE: usize = HEADER_LEN;
32const OFFSET_OUI_HASH: usize = HEADER_LEN + 1;
33const OFFSET_FLAGS: usize = HEADER_LEN + 2;
34const OFFSET_SECTION_NUMBER: usize = HEADER_LEN + 3;
35const OFFSET_LAST_SECTION_NUMBER: usize = HEADER_LEN + 4;
36const OFFSET_OUI: usize = HEADER_LEN + 5;
37const OFFSET_PROCESSING_ORDER: usize = HEADER_LEN + 8;
38const OFFSET_COMMON_DESC_LEN: usize = HEADER_LEN + FIXED_BODY_LEN;
39
40const VERSION_NUMBER_MASK: u8 = 0x3E;
41const VERSION_NUMBER_SHIFT: u8 = 1;
42const CURRENT_NEXT_MASK: u8 = 0x01;
43const LENGTH_HIGH_NIBBLE_MASK: u8 = 0x0F;
44const FLAGS_RESERVED_BITS: u8 = 0xC0;
45const RESERVED_NIBBLE: u8 = 0xF0;
46
47const PLATFORM_LOOP_LEN_FIELD: usize = 2;
48const DESC_LOOP_LEN_FIELD: usize = 2;
49
50#[derive(Debug, Clone, PartialEq, Eq)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize))]
59pub struct UntPlatform<'a> {
60 pub compatibility_descriptor: CompatibilityDescriptor<'a>,
62 pub target_operational_pairs: Vec<(DescriptorLoop<'a>, DescriptorLoop<'a>)>,
65}
66
67fn unt_platform_serialized_len(p: &UntPlatform) -> usize {
68 p.compatibility_descriptor.serialized_len()
69 + PLATFORM_LOOP_LEN_FIELD
70 + p.target_operational_pairs
71 .iter()
72 .map(|(t, o)| DESC_LOOP_LEN_FIELD + t.len() + DESC_LOOP_LEN_FIELD + o.len())
73 .sum::<usize>()
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize))]
84#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
85pub struct UntSection<'a> {
86 pub action_type: u8,
88 pub oui_hash: u8,
90 pub version_number: u8,
92 pub current_next_indicator: bool,
94 pub section_number: u8,
96 pub last_section_number: u8,
98 pub oui: u32,
100 pub processing_order: u8,
102 pub common_descriptors: DescriptorLoop<'a>,
105 pub platforms: Vec<UntPlatform<'a>>,
107}
108
109impl<'a> Parse<'a> for UntSection<'a> {
110 type Error = crate::error::Error;
111
112 fn parse(bytes: &'a [u8]) -> Result<Self> {
113 if bytes.len() < MIN_SECTION_LEN {
114 return Err(Error::BufferTooShort {
115 need: MIN_SECTION_LEN,
116 have: bytes.len(),
117 what: "UntSection",
118 });
119 }
120 if bytes[0] != TABLE_ID {
121 return Err(Error::UnexpectedTableId {
122 table_id: bytes[0],
123 what: "UntSection",
124 expected: &[TABLE_ID],
125 });
126 }
127
128 let section_length =
129 (((bytes[1] & LENGTH_HIGH_NIBBLE_MASK) as usize) << 8) | bytes[2] as usize;
130 let total =
131 super::check_section_length(bytes.len(), HEADER_LEN, section_length, MIN_SECTION_LEN)?;
132
133 let action_type = bytes[OFFSET_ACTION_TYPE];
134 let oui_hash = bytes[OFFSET_OUI_HASH];
135 let flags_byte = bytes[OFFSET_FLAGS];
136 let version_number = (flags_byte & VERSION_NUMBER_MASK) >> VERSION_NUMBER_SHIFT;
137 let current_next_indicator = (flags_byte & CURRENT_NEXT_MASK) != 0;
138 let section_number = bytes[OFFSET_SECTION_NUMBER];
139 let last_section_number = bytes[OFFSET_LAST_SECTION_NUMBER];
140 let oui = ((bytes[OFFSET_OUI] as u32) << 16)
141 | ((bytes[OFFSET_OUI + 1] as u32) << 8)
142 | (bytes[OFFSET_OUI + 2] as u32);
143 let processing_order = bytes[OFFSET_PROCESSING_ORDER];
144
145 let cdl = (((bytes[OFFSET_COMMON_DESC_LEN] & LENGTH_HIGH_NIBBLE_MASK) as usize) << 8)
146 | bytes[OFFSET_COMMON_DESC_LEN + 1] as usize;
147 let common_desc_start = OFFSET_COMMON_DESC_LEN + COMMON_DESC_LEN_FIELD;
148 let common_desc_end = common_desc_start + cdl;
149 if common_desc_end > total - CRC_LEN {
150 return Err(Error::SectionLengthOverflow {
151 declared: cdl,
152 available: (total - CRC_LEN).saturating_sub(common_desc_start),
153 });
154 }
155 let common_descriptors = DescriptorLoop::new(&bytes[common_desc_start..common_desc_end]);
156
157 let payload_end = total - CRC_LEN;
158 let mut pos = common_desc_end;
159 let mut platforms = Vec::new();
160 while pos < payload_end {
161 if pos + crate::compatibility::COMPAT_DESC_LEN_FIELD > payload_end {
162 return Err(Error::BufferTooShort {
163 need: pos + crate::compatibility::COMPAT_DESC_LEN_FIELD,
164 have: payload_end,
165 what: "UntSection compatibilityDescriptorLength",
166 });
167 }
168 let compat_desc_len = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize;
169 let compat_total = crate::compatibility::COMPAT_DESC_LEN_FIELD + compat_desc_len;
170 if pos + compat_total > payload_end {
171 return Err(Error::SectionLengthOverflow {
172 declared: compat_desc_len,
173 available: payload_end
174 .saturating_sub(pos + crate::compatibility::COMPAT_DESC_LEN_FIELD),
175 });
176 }
177 let compatibility_descriptor =
178 CompatibilityDescriptor::parse(&bytes[pos..pos + compat_total])?;
179 pos += compat_total;
180
181 if pos + PLATFORM_LOOP_LEN_FIELD > payload_end {
182 return Err(Error::BufferTooShort {
183 need: pos + PLATFORM_LOOP_LEN_FIELD,
184 have: payload_end,
185 what: "UntSection platform_loop_length",
186 });
187 }
188 let platform_loop_length = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]) as usize;
189 pos += PLATFORM_LOOP_LEN_FIELD;
190 let platform_end = pos + platform_loop_length;
191 if platform_end > payload_end {
192 return Err(Error::SectionLengthOverflow {
193 declared: platform_loop_length,
194 available: payload_end.saturating_sub(pos),
195 });
196 }
197
198 let mut target_operational_pairs = Vec::new();
199 while pos < platform_end {
200 if pos + DESC_LOOP_LEN_FIELD > platform_end {
201 return Err(Error::BufferTooShort {
202 need: pos + DESC_LOOP_LEN_FIELD,
203 have: platform_end,
204 what: "UntSection target_descriptor_loop length",
205 });
206 }
207 let target_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
208 let target_start = pos + DESC_LOOP_LEN_FIELD;
209 let target_end = target_start + target_len;
210 if target_end > platform_end {
211 return Err(Error::SectionLengthOverflow {
212 declared: target_len,
213 available: platform_end.saturating_sub(target_start),
214 });
215 }
216 let target_descriptors = DescriptorLoop::new(&bytes[target_start..target_end]);
217 pos = target_end;
218
219 if pos + DESC_LOOP_LEN_FIELD > platform_end {
220 return Err(Error::BufferTooShort {
221 need: pos + DESC_LOOP_LEN_FIELD,
222 have: platform_end,
223 what: "UntSection operational_descriptor_loop length",
224 });
225 }
226 let op_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
227 let op_start = pos + DESC_LOOP_LEN_FIELD;
228 let op_end = op_start + op_len;
229 if op_end > platform_end {
230 return Err(Error::SectionLengthOverflow {
231 declared: op_len,
232 available: platform_end.saturating_sub(op_start),
233 });
234 }
235 let operational_descriptors = DescriptorLoop::new(&bytes[op_start..op_end]);
236 pos = op_end;
237
238 target_operational_pairs.push((target_descriptors, operational_descriptors));
239 }
240 if pos != platform_end {
241 return Err(Error::SectionLengthOverflow {
242 declared: platform_loop_length,
243 available: pos.saturating_sub(platform_end - platform_loop_length),
244 });
245 }
246
247 platforms.push(UntPlatform {
248 compatibility_descriptor,
249 target_operational_pairs,
250 });
251 }
252
253 Ok(UntSection {
254 action_type,
255 oui_hash,
256 version_number,
257 current_next_indicator,
258 section_number,
259 last_section_number,
260 oui,
261 processing_order,
262 common_descriptors,
263 platforms,
264 })
265 }
266}
267
268impl Serialize for UntSection<'_> {
269 type Error = crate::error::Error;
270
271 fn serialized_len(&self) -> usize {
272 HEADER_LEN
273 + FIXED_BODY_LEN
274 + COMMON_DESC_LEN_FIELD
275 + self.common_descriptors.len()
276 + self
277 .platforms
278 .iter()
279 .map(unt_platform_serialized_len)
280 .sum::<usize>()
281 + CRC_LEN
282 }
283
284 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
285 let len = self.serialized_len();
286 if buf.len() < len {
287 return Err(Error::OutputBufferTooSmall {
288 need: len,
289 have: buf.len(),
290 });
291 }
292
293 let section_length = (len - HEADER_LEN) as u16;
294 if section_length > 0x0FFF {
295 return Err(Error::SectionLengthOverflow {
296 declared: section_length as usize,
297 available: 0x0FFF,
298 });
299 }
300 buf[0] = TABLE_ID;
301 buf[1] =
302 super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & LENGTH_HIGH_NIBBLE_MASK);
303 buf[2] = (section_length & 0xFF) as u8;
304
305 buf[OFFSET_ACTION_TYPE] = self.action_type;
306 buf[OFFSET_OUI_HASH] = self.oui_hash;
307 buf[OFFSET_FLAGS] = FLAGS_RESERVED_BITS
308 | ((self.version_number & 0x1F) << VERSION_NUMBER_SHIFT)
309 | u8::from(self.current_next_indicator);
310 buf[OFFSET_SECTION_NUMBER] = self.section_number;
311 buf[OFFSET_LAST_SECTION_NUMBER] = self.last_section_number;
312 buf[OFFSET_OUI] = ((self.oui >> 16) & 0xFF) as u8;
313 buf[OFFSET_OUI + 1] = ((self.oui >> 8) & 0xFF) as u8;
314 buf[OFFSET_OUI + 2] = (self.oui & 0xFF) as u8;
315 buf[OFFSET_PROCESSING_ORDER] = self.processing_order;
316
317 let cdl = self.common_descriptors.len() as u16;
318 buf[OFFSET_COMMON_DESC_LEN] =
319 RESERVED_NIBBLE | ((cdl >> 8) as u8 & LENGTH_HIGH_NIBBLE_MASK);
320 buf[OFFSET_COMMON_DESC_LEN + 1] = (cdl & 0xFF) as u8;
321
322 let common_start = OFFSET_COMMON_DESC_LEN + COMMON_DESC_LEN_FIELD;
323 let common_end = common_start + self.common_descriptors.len();
324 buf[common_start..common_end].copy_from_slice(self.common_descriptors.raw());
325
326 let mut pos = common_end;
327 for platform in &self.platforms {
328 let written = platform
329 .compatibility_descriptor
330 .serialize_into(&mut buf[pos..])?;
331 pos += written;
332
333 let inner_len: usize = platform
334 .target_operational_pairs
335 .iter()
336 .map(|(t, o)| DESC_LOOP_LEN_FIELD + t.len() + DESC_LOOP_LEN_FIELD + o.len())
337 .sum();
338 buf[pos..pos + PLATFORM_LOOP_LEN_FIELD]
339 .copy_from_slice(&(inner_len as u16).to_be_bytes());
340 pos += PLATFORM_LOOP_LEN_FIELD;
341
342 for (target_descriptors, operational_descriptors) in &platform.target_operational_pairs
343 {
344 let tl = target_descriptors.len() as u16;
345 buf[pos] = RESERVED_NIBBLE | ((tl >> 8) as u8 & 0x0F);
346 buf[pos + 1] = (tl & 0xFF) as u8;
347 pos += DESC_LOOP_LEN_FIELD;
348 buf[pos..pos + target_descriptors.len()].copy_from_slice(target_descriptors.raw());
349 pos += target_descriptors.len();
350
351 let ol = operational_descriptors.len() as u16;
352 buf[pos] = RESERVED_NIBBLE | ((ol >> 8) as u8 & 0x0F);
353 buf[pos + 1] = (ol & 0xFF) as u8;
354 pos += DESC_LOOP_LEN_FIELD;
355 buf[pos..pos + operational_descriptors.len()]
356 .copy_from_slice(operational_descriptors.raw());
357 pos += operational_descriptors.len();
358 }
359 }
360
361 let crc_pos = len - CRC_LEN;
362 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
363 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
364 Ok(len)
365 }
366}
367impl<'a> crate::traits::TableDef<'a> for UntSection<'a> {
368 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
369 const NAME: &'static str = "UPDATE_NOTIFICATION";
370}
371
372#[cfg(test)]
373mod tests {
374 use super::*;
375
376 #[test]
377 fn parse_happy_path() {
378 let oui: u32 = 0x00_01_5A;
379 let oui_hash: u8 = 0x01 ^ 0x5A;
380 let common_descs: &[u8] = &[0x66, 0x04, 0x00, 0x0A, 0x00, 0x00];
381 let unt = UntSection {
382 action_type: 0x01,
383 oui_hash,
384 version_number: 7,
385 current_next_indicator: true,
386 section_number: 0,
387 last_section_number: 0,
388 oui,
389 processing_order: 0x00,
390 common_descriptors: DescriptorLoop::new(common_descs),
391 platforms: vec![UntPlatform {
392 compatibility_descriptor: CompatibilityDescriptor {
393 descriptors: vec![],
394 },
395 target_operational_pairs: vec![(
396 DescriptorLoop::new(&[]),
397 DescriptorLoop::new(&[]),
398 )],
399 }],
400 };
401 let sl = unt.serialized_len();
402 let mut buf = vec![0u8; sl];
403 unt.serialize_into(&mut buf).unwrap();
404 let parsed = UntSection::parse(&buf).unwrap();
405 assert_eq!(parsed.action_type, 0x01);
406 assert_eq!(parsed.oui_hash, oui_hash);
407 assert_eq!(parsed.version_number, 7);
408 assert!(parsed.current_next_indicator);
409 assert_eq!(parsed.oui, oui);
410 assert_eq!(parsed.common_descriptors.raw(), common_descs);
411 assert_eq!(parsed.platforms.len(), 1);
412 assert!(parsed.platforms[0]
413 .compatibility_descriptor
414 .descriptors
415 .is_empty());
416 }
417
418 #[test]
419 fn parse_empty_platforms() {
420 let unt = UntSection {
421 action_type: 0x01,
422 oui_hash: 0x5B,
423 version_number: 1,
424 current_next_indicator: false,
425 section_number: 1,
426 last_section_number: 2,
427 oui: 0x00015A,
428 processing_order: 0x01,
429 common_descriptors: DescriptorLoop::new(&[]),
430 platforms: Vec::new(),
431 };
432 let mut buf = vec![0u8; unt.serialized_len()];
433 unt.serialize_into(&mut buf).unwrap();
434 let parsed = UntSection::parse(&buf).unwrap();
435 assert!(!parsed.current_next_indicator);
436 assert!(parsed.platforms.is_empty());
437 }
438
439 #[test]
440 fn byte_exact_round_trip() {
441 let target_desc: &[u8] = &[0x09, 0x01, 0xAA];
442 let op_desc: &[u8] = &[0x0A, 0x01, 0xBB];
443 let unt = UntSection {
444 action_type: 0x01,
445 oui_hash: 0x5B,
446 version_number: 15,
447 current_next_indicator: true,
448 section_number: 2,
449 last_section_number: 5,
450 oui: 0x00015A,
451 processing_order: 0x02,
452 common_descriptors: DescriptorLoop::new(&[0x66, 0x04, 0x00, 0x0A, 0x00, 0x00]),
453 platforms: vec![UntPlatform {
454 compatibility_descriptor: CompatibilityDescriptor {
455 descriptors: vec![],
456 },
457 target_operational_pairs: vec![(
458 DescriptorLoop::new(target_desc),
459 DescriptorLoop::new(op_desc),
460 )],
461 }],
462 };
463 let mut buf = vec![0u8; unt.serialized_len()];
464 unt.serialize_into(&mut buf).unwrap();
465 let re = UntSection::parse(&buf).unwrap();
466 let mut buf2 = vec![0u8; re.serialized_len()];
467 re.serialize_into(&mut buf2).unwrap();
468 assert_eq!(buf, buf2, "byte-exact re-serialize");
469 let re = UntSection::parse(&buf).unwrap();
470 assert_eq!(re.platforms.len(), 1);
471 assert!(re.platforms[0]
472 .compatibility_descriptor
473 .descriptors
474 .is_empty());
475 assert_eq!(re.platforms[0].target_operational_pairs.len(), 1);
476 assert_eq!(
477 re.platforms[0].target_operational_pairs[0].0.raw(),
478 target_desc
479 );
480 assert_eq!(re.platforms[0].target_operational_pairs[0].1.raw(), op_desc);
481 }
482
483 #[test]
484 fn round_trip_platform_with_multiple_pairs() {
485 let t0: &[u8] = &[0x09, 0x01, 0xAA];
489 let o0: &[u8] = &[0x0A, 0x01, 0xBB];
490 let t1: &[u8] = &[0x01, 0x02, 0xCC, 0xDD];
491 let o1: &[u8] = &[];
492 let unt = UntSection {
493 action_type: 0x01,
494 oui_hash: 0x5B,
495 version_number: 15,
496 current_next_indicator: true,
497 section_number: 0,
498 last_section_number: 0,
499 oui: 0x00015A,
500 processing_order: 0x02,
501 common_descriptors: DescriptorLoop::new(&[]),
502 platforms: vec![UntPlatform {
503 compatibility_descriptor: CompatibilityDescriptor {
504 descriptors: vec![],
505 },
506 target_operational_pairs: vec![
507 (DescriptorLoop::new(t0), DescriptorLoop::new(o0)),
508 (DescriptorLoop::new(t1), DescriptorLoop::new(o1)),
509 ],
510 }],
511 };
512 let mut buf = vec![0u8; unt.serialized_len()];
513 unt.serialize_into(&mut buf).unwrap();
514 let re = UntSection::parse(&buf).unwrap();
515 assert_eq!(re.platforms.len(), 1);
516 let pairs = &re.platforms[0].target_operational_pairs;
517 assert_eq!(pairs.len(), 2, "both pairs must survive the round-trip");
518 assert_eq!(pairs[0].0.raw(), t0);
519 assert_eq!(pairs[0].1.raw(), o0);
520 assert_eq!(pairs[1].0.raw(), t1);
521 assert_eq!(pairs[1].1.raw(), o1);
522 let mut buf2 = vec![0u8; unt.serialized_len()];
524 unt.serialize_into(&mut buf2).unwrap();
525 assert_eq!(buf, buf2, "byte-exact re-serialize");
526 }
527
528 #[test]
529 fn round_trip_platform_with_nonempty_compat() {
530 use crate::compatibility::{CompatibilityDescriptorEntry, SubDescriptor};
534 let unt = UntSection {
535 action_type: 0x01,
536 oui_hash: 0x5B,
537 version_number: 3,
538 current_next_indicator: true,
539 section_number: 0,
540 last_section_number: 0,
541 oui: 0x00015A,
542 processing_order: 0x00,
543 common_descriptors: DescriptorLoop::new(&[]),
544 platforms: vec![UntPlatform {
545 compatibility_descriptor: CompatibilityDescriptor {
546 descriptors: vec![CompatibilityDescriptorEntry {
547 descriptor_type: 0x01,
548 specifier_type: 0x01,
549 specifier_data: [0x00, 0x15, 0x0A],
550 model: 0x1234,
551 version: 0x0001,
552 sub_descriptors: vec![SubDescriptor {
553 sub_descriptor_type: 0x05,
554 data: &[0xAA, 0xBB],
555 }],
556 }],
557 },
558 target_operational_pairs: vec![(
559 DescriptorLoop::new(&[]),
560 DescriptorLoop::new(&[]),
561 )],
562 }],
563 };
564 let mut buf = vec![0u8; unt.serialized_len()];
565 unt.serialize_into(&mut buf).unwrap();
566 let re = UntSection::parse(&buf).unwrap();
567 assert_eq!(re, unt);
568 let entry = &re.platforms[0].compatibility_descriptor.descriptors[0];
569 assert_eq!(entry.descriptor_type, 0x01);
570 assert_eq!(entry.model, 0x1234);
571 assert_eq!(entry.sub_descriptors[0].data, &[0xAA, 0xBB]);
572 let mut buf2 = vec![0u8; unt.serialized_len()];
573 unt.serialize_into(&mut buf2).unwrap();
574 assert_eq!(buf, buf2, "byte-exact re-serialize");
575 }
576
577 #[test]
578 fn parse_rejects_wrong_table_id() {
579 let unt = UntSection {
580 action_type: 0x01,
581 oui_hash: 0x5B,
582 version_number: 0,
583 current_next_indicator: true,
584 section_number: 0,
585 last_section_number: 0,
586 oui: 0x00015A,
587 processing_order: 0x00,
588 common_descriptors: DescriptorLoop::new(&[]),
589 platforms: Vec::new(),
590 };
591 let mut buf = vec![0u8; unt.serialized_len()];
592 unt.serialize_into(&mut buf).unwrap();
593 buf[0] = 0x4A;
594 assert!(matches!(
595 UntSection::parse(&buf).unwrap_err(),
596 Error::UnexpectedTableId { table_id: 0x4A, .. }
597 ));
598 }
599
600 #[test]
601 fn parse_rejects_short_buffer() {
602 assert!(matches!(
603 UntSection::parse(&[TABLE_ID, 0x00]).unwrap_err(),
604 Error::BufferTooShort { .. }
605 ));
606 }
607
608 #[test]
609 fn serialize_rejects_small_output_buffer() {
610 let unt = UntSection {
611 action_type: 0x01,
612 oui_hash: 0x5B,
613 version_number: 0,
614 current_next_indicator: true,
615 section_number: 0,
616 last_section_number: 0,
617 oui: 0x00015A,
618 processing_order: 0x00,
619 common_descriptors: DescriptorLoop::new(&[]),
620 platforms: Vec::new(),
621 };
622 let mut buf = vec![0u8; unt.serialized_len() - 1];
623 assert!(matches!(
624 unt.serialize_into(&mut buf).unwrap_err(),
625 Error::OutputBufferTooSmall { .. }
626 ));
627 }
628
629 #[test]
630 fn parse_rejects_zero_section_length() {
631 let mut buf = vec![0u8; 64];
632 buf[0] = TABLE_ID;
633 buf[1] = 0xF0;
634 buf[2] = 0x00;
635 for b in &mut buf[3..] {
636 *b = 0xFF;
637 }
638 assert!(matches!(
639 UntSection::parse(&buf).unwrap_err(),
640 Error::SectionLengthOverflow { .. }
641 ));
642 }
643
644 #[test]
645 fn parse_handwritten_unt_no_platforms() {
646 let mut bytes: Vec<u8> = vec![
647 0x4B, 0xF0, 0x0F, 0x01, 0x5B, 0xC1, 0x00, 0x00, 0x00, 0x01, 0x5A, 0x00, 0xF0, 0x00,
648 ];
649 let crc = dvb_common::crc32_mpeg2::compute(&bytes);
650 bytes.extend_from_slice(&crc.to_be_bytes());
651 let unt = UntSection::parse(&bytes).unwrap();
652 assert_eq!(unt.action_type, 0x01);
653 assert_eq!(unt.oui, 0x00015A);
654 assert!(unt.current_next_indicator);
655 assert!(unt.platforms.is_empty());
656 }
657}