1use crate::error::{Error, Result};
26use alloc::vec::Vec;
27use dvb_common::{Parse, Serialize};
28
29pub const TABLE_ID: u8 = 0x7B;
31pub const PID: u16 = 0x0000;
35
36pub const AUTH_EXTENSION_FIRST: u16 = 0x0000;
38pub const AUTH_EXTENSION_LAST: u16 = 0x00FF;
40pub const CERTIFICATE_COLLECTION_EXTENSION: u16 = 0x0100;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize))]
46#[non_exhaustive]
47pub enum ReferenceType {
48 Reserved,
50 SameEs,
52 ComponentTagEs,
54 Unallocated(u8),
56}
57
58impl ReferenceType {
59 #[must_use]
60 pub fn from_u8(v: u8) -> Self {
62 match v & 0x0F {
63 0x0 => Self::Reserved,
64 0x1 => Self::SameEs,
65 0x2 => Self::ComponentTagEs,
66 v => Self::Unallocated(v),
67 }
68 }
69
70 #[must_use]
71 pub fn to_u8(self) -> u8 {
73 match self {
74 Self::Reserved => 0x0,
75 Self::SameEs => 0x1,
76 Self::ComponentTagEs => 0x2,
77 Self::Unallocated(v) => v,
78 }
79 }
80
81 #[must_use]
82 pub fn name(self) -> &'static str {
84 match self {
85 Self::Reserved => "Reserved",
86 Self::SameEs => "Same ES",
87 Self::ComponentTagEs => "Component Tag ES",
88 Self::Unallocated(_) => "Unallocated",
89 }
90 }
91}
92dvb_common::impl_spec_display!(ReferenceType, Unallocated);
93
94const HEADER_LEN: usize = 8;
97const SECTION_LENGTH_PREFIX: usize = 3;
99const CRC_LEN: usize = 4;
101
102const AUTH_FIXED_PREFIX: usize = 5;
106
107#[derive(Debug, Clone, PartialEq, Eq)]
113#[cfg_attr(feature = "serde", derive(serde::Serialize))]
114#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
115pub struct SectionHashEntry<'a> {
116 pub reference_type: ReferenceType,
118 pub reference: &'a [u8],
120 pub hash: &'a [u8],
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize))]
127#[non_exhaustive]
128pub enum ProtectionMessageBody<'a> {
129 AuthenticationMessage {
131 section_hash_algorithm_identifier: u8,
133 section_hash_length: u8,
135 signature_algorithm_identifier: u8,
137 hashes: Vec<SectionHashEntry<'a>>,
139 extension_bytes: &'a [u8],
141 signature_key_identifier: &'a [u8],
143 signature: &'a [u8],
145 },
146 CertificateCollection {
148 certificates: Vec<&'a [u8]>,
150 },
151 Raw(&'a [u8]),
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
160#[cfg_attr(feature = "serde", derive(serde::Serialize))]
161#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
162pub struct ProtectionMessageSection<'a> {
163 pub table_id_extension: u16,
166 pub version_number: u8,
168 pub current_next_indicator: bool,
170 pub section_number: u8,
172 pub last_section_number: u8,
174 pub body: ProtectionMessageBody<'a>,
176}
177
178impl<'a> Parse<'a> for ProtectionMessageSection<'a> {
179 type Error = crate::error::Error;
180 fn parse(bytes: &'a [u8]) -> Result<Self> {
181 let min_len = HEADER_LEN + CRC_LEN;
182 if bytes.len() < min_len {
183 return Err(Error::BufferTooShort {
184 need: min_len,
185 have: bytes.len(),
186 what: "ProtectionMessageSection",
187 });
188 }
189 if bytes[0] != TABLE_ID {
190 return Err(Error::UnexpectedTableId {
191 table_id: bytes[0],
192 what: "ProtectionMessageSection",
193 expected: &[TABLE_ID],
194 });
195 }
196 let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
197 let total = super::check_section_length(
198 bytes.len(),
199 SECTION_LENGTH_PREFIX,
200 section_length,
201 HEADER_LEN + CRC_LEN,
202 )?;
203
204 let table_id_extension = u16::from_be_bytes([bytes[3], bytes[4]]);
205 let version_number = (bytes[5] >> 1) & 0x1F;
206 let current_next_indicator = bytes[5] & 0x01 != 0;
207 let section_number = bytes[6];
208 let last_section_number = bytes[7];
209
210 let body_bytes = &bytes[HEADER_LEN..total - CRC_LEN];
211 let body = match table_id_extension {
212 AUTH_EXTENSION_FIRST..=AUTH_EXTENSION_LAST => parse_authentication_message(body_bytes)?,
213 CERTIFICATE_COLLECTION_EXTENSION => parse_certificate_collection(body_bytes)?,
214 _ => ProtectionMessageBody::Raw(body_bytes),
215 };
216
217 Ok(ProtectionMessageSection {
218 table_id_extension,
219 version_number,
220 current_next_indicator,
221 section_number,
222 last_section_number,
223 body,
224 })
225 }
226}
227
228fn parse_authentication_message(body: &[u8]) -> Result<ProtectionMessageBody<'_>> {
230 if body.len() < AUTH_FIXED_PREFIX {
231 return Err(Error::BufferTooShort {
232 need: AUTH_FIXED_PREFIX,
233 have: body.len(),
234 what: "ProtectionMessageSection::AuthenticationMessage",
235 });
236 }
237 let section_hash_algorithm_identifier = body[0];
238 let section_hash_length = body[1];
239 let signature_algorithm_identifier = body[2];
240 let section_hashes_loop_length = (((body[3] & 0x0F) as usize) << 8) | body[4] as usize;
242
243 let loop_start = AUTH_FIXED_PREFIX;
244 let loop_end = loop_start + section_hashes_loop_length;
245 if loop_end > body.len() {
246 return Err(Error::SectionLengthOverflow {
247 declared: section_hashes_loop_length,
248 available: body.len() - loop_start,
249 });
250 }
251
252 let hash_len = section_hash_length as usize;
253 let mut hashes = Vec::new();
254 let mut pos = loop_start;
255 while pos < loop_end {
256 let lead = body[pos];
258 let reference_type = ReferenceType::from_u8(lead >> 4);
259 let reference_length = (lead & 0x0F) as usize;
260 let ref_start = pos + 1;
261 let ref_end = ref_start + reference_length;
262 let hash_end = ref_end + hash_len;
263 if hash_end > loop_end {
264 return Err(Error::SectionLengthOverflow {
265 declared: reference_length + hash_len,
266 available: loop_end - ref_start,
267 });
268 }
269 hashes.push(SectionHashEntry {
270 reference_type,
271 reference: &body[ref_start..ref_end],
272 hash: &body[ref_end..hash_end],
273 });
274 pos = hash_end;
275 }
276
277 if loop_end >= body.len() {
279 return Err(Error::BufferTooShort {
280 need: loop_end + 1,
281 have: body.len(),
282 what: "ProtectionMessageSection::extension_bytes_length",
283 });
284 }
285 let extension_bytes_length = body[loop_end] as usize;
286 let ext_start = loop_end + 1;
287 let ext_end = ext_start + extension_bytes_length;
288 if ext_end > body.len() {
289 return Err(Error::SectionLengthOverflow {
290 declared: extension_bytes_length,
291 available: body.len() - ext_start,
292 });
293 }
294
295 if ext_end >= body.len() {
297 return Err(Error::BufferTooShort {
298 need: ext_end + 1,
299 have: body.len(),
300 what: "ProtectionMessageSection::signature_key_identifier_length",
301 });
302 }
303 let key_id_length = body[ext_end] as usize;
304 let key_start = ext_end + 1;
305 let key_end = key_start + key_id_length;
306 if key_end > body.len() {
307 return Err(Error::SectionLengthOverflow {
308 declared: key_id_length,
309 available: body.len() - key_start,
310 });
311 }
312
313 let signature = &body[key_end..];
315
316 Ok(ProtectionMessageBody::AuthenticationMessage {
317 section_hash_algorithm_identifier,
318 section_hash_length,
319 signature_algorithm_identifier,
320 hashes,
321 extension_bytes: &body[ext_start..ext_end],
322 signature_key_identifier: &body[key_start..key_end],
323 signature,
324 })
325}
326
327fn parse_certificate_collection(body: &[u8]) -> Result<ProtectionMessageBody<'_>> {
329 if body.is_empty() {
330 return Err(Error::BufferTooShort {
331 need: 1,
332 have: 0,
333 what: "ProtectionMessageSection::CertificateCollection",
334 });
335 }
336 let certificate_count = (body[0] & 0x0F) as usize;
338 let mut certificates = Vec::with_capacity(certificate_count);
339 let mut pos = 1;
340 for _ in 0..certificate_count {
341 if pos + 2 > body.len() {
342 return Err(Error::BufferTooShort {
343 need: pos + 2,
344 have: body.len(),
345 what: "ProtectionMessageSection::certificate_length",
346 });
347 }
348 let certificate_length = (((body[pos] & 0x0F) as usize) << 8) | body[pos + 1] as usize;
350 let cert_start = pos + 2;
351 let cert_end = cert_start + certificate_length;
352 if cert_end > body.len() {
353 return Err(Error::SectionLengthOverflow {
354 declared: certificate_length,
355 available: body.len() - cert_start,
356 });
357 }
358 certificates.push(&body[cert_start..cert_end]);
359 pos = cert_end;
360 }
361 Ok(ProtectionMessageBody::CertificateCollection { certificates })
362}
363
364impl ProtectionMessageBody<'_> {
365 fn body_len(&self) -> usize {
367 match self {
368 ProtectionMessageBody::AuthenticationMessage {
369 hashes,
370 extension_bytes,
371 signature_key_identifier,
372 signature,
373 ..
374 } => {
375 let loop_bytes: usize = hashes
376 .iter()
377 .map(|h| 1 + h.reference.len() + h.hash.len())
378 .sum();
379 AUTH_FIXED_PREFIX
380 + loop_bytes
381 + 1
382 + extension_bytes.len()
383 + 1
384 + signature_key_identifier.len()
385 + signature.len()
386 }
387 ProtectionMessageBody::CertificateCollection { certificates } => {
388 1 + certificates.iter().map(|c| 2 + c.len()).sum::<usize>()
389 }
390 ProtectionMessageBody::Raw(raw) => raw.len(),
391 }
392 }
393
394 fn write_into(&self, buf: &mut [u8]) -> Result<usize> {
396 match self {
397 ProtectionMessageBody::AuthenticationMessage {
398 section_hash_algorithm_identifier,
399 section_hash_length,
400 signature_algorithm_identifier,
401 hashes,
402 extension_bytes,
403 signature_key_identifier,
404 signature,
405 } => {
406 buf[0] = *section_hash_algorithm_identifier;
407 buf[1] = *section_hash_length;
408 buf[2] = *signature_algorithm_identifier;
409 let loop_bytes: usize = hashes
410 .iter()
411 .map(|h| 1 + h.reference.len() + h.hash.len())
412 .sum();
413 if loop_bytes > 0x0FFF {
414 return Err(Error::SectionLengthOverflow {
415 declared: loop_bytes,
416 available: 0x0FFF,
417 });
418 }
419 if extension_bytes.len() > u8::MAX as usize {
420 return Err(Error::SectionLengthOverflow {
421 declared: extension_bytes.len(),
422 available: u8::MAX as usize,
423 });
424 }
425 if signature_key_identifier.len() > u8::MAX as usize {
426 return Err(Error::SectionLengthOverflow {
427 declared: signature_key_identifier.len(),
428 available: u8::MAX as usize,
429 });
430 }
431 buf[3] = 0xF0 | ((loop_bytes >> 8) as u8 & 0x0F);
433 buf[4] = (loop_bytes & 0xFF) as u8;
434 let mut pos = AUTH_FIXED_PREFIX;
435 for h in hashes {
436 if h.reference.len() > 0x0F {
437 return Err(Error::SectionLengthOverflow {
438 declared: h.reference.len(),
439 available: 0x0F,
440 });
441 }
442 buf[pos] = (h.reference_type.to_u8() << 4) | (h.reference.len() as u8 & 0x0F);
443 pos += 1;
444 buf[pos..pos + h.reference.len()].copy_from_slice(h.reference);
445 pos += h.reference.len();
446 buf[pos..pos + h.hash.len()].copy_from_slice(h.hash);
447 pos += h.hash.len();
448 }
449 buf[pos] = extension_bytes.len() as u8;
450 pos += 1;
451 buf[pos..pos + extension_bytes.len()].copy_from_slice(extension_bytes);
452 pos += extension_bytes.len();
453 buf[pos] = signature_key_identifier.len() as u8;
454 pos += 1;
455 buf[pos..pos + signature_key_identifier.len()]
456 .copy_from_slice(signature_key_identifier);
457 pos += signature_key_identifier.len();
458 buf[pos..pos + signature.len()].copy_from_slice(signature);
459 pos += signature.len();
460 Ok(pos)
461 }
462 ProtectionMessageBody::CertificateCollection { certificates } => {
463 if certificates.len() > 0x0F {
464 return Err(Error::SectionLengthOverflow {
465 declared: certificates.len(),
466 available: 0x0F,
467 });
468 }
469 buf[0] = 0xF0 | (certificates.len() as u8 & 0x0F);
471 let mut pos = 1;
472 for c in certificates {
473 if c.len() > 0x0FFF {
474 return Err(Error::SectionLengthOverflow {
475 declared: c.len(),
476 available: 0x0FFF,
477 });
478 }
479 buf[pos] = 0xF0 | ((c.len() >> 8) as u8 & 0x0F);
481 buf[pos + 1] = (c.len() & 0xFF) as u8;
482 pos += 2;
483 buf[pos..pos + c.len()].copy_from_slice(c);
484 pos += c.len();
485 }
486 Ok(pos)
487 }
488 ProtectionMessageBody::Raw(raw) => {
489 buf[..raw.len()].copy_from_slice(raw);
490 Ok(raw.len())
491 }
492 }
493 }
494}
495
496impl Serialize for ProtectionMessageSection<'_> {
497 type Error = crate::error::Error;
498 fn serialized_len(&self) -> usize {
499 HEADER_LEN + self.body.body_len() + CRC_LEN
500 }
501 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
502 let len = self.serialized_len();
503 if buf.len() < len {
504 return Err(Error::OutputBufferTooSmall {
505 need: len,
506 have: buf.len(),
507 });
508 }
509 let section_length = (len - SECTION_LENGTH_PREFIX) as u16;
510 buf[0] = TABLE_ID;
511 buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
512 buf[2] = (section_length & 0xFF) as u8;
513 buf[3..5].copy_from_slice(&self.table_id_extension.to_be_bytes());
514 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
516 buf[6] = self.section_number;
517 buf[7] = self.last_section_number;
518 let body_written = self.body.write_into(&mut buf[HEADER_LEN..])?;
519 let body_end = HEADER_LEN + body_written;
520 let crc = dvb_common::crc32_mpeg2::compute(&buf[..body_end]);
521 buf[body_end..len].copy_from_slice(&crc.to_be_bytes());
522 Ok(len)
523 }
524}
525impl<'a> crate::traits::TableDef<'a> for ProtectionMessageSection<'a> {
526 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
527 const NAME: &'static str = "PROTECTION_MESSAGE";
528}
529
530#[cfg(test)]
531mod tests {
532 use super::*;
533
534 fn build_section(extension: u16, version: u8, body: &[u8]) -> Vec<u8> {
536 let section_length = (HEADER_LEN - SECTION_LENGTH_PREFIX + body.len() + CRC_LEN) as u16;
537 let mut v = vec![
538 TABLE_ID,
539 super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F),
540 (section_length & 0xFF) as u8,
541 (extension >> 8) as u8,
542 (extension & 0xFF) as u8,
543 0xC0 | (version << 1) | 0x01,
544 0x00,
545 0x00,
546 ];
547 v.extend_from_slice(body);
548 v.extend_from_slice(&[0, 0, 0, 0]);
549 v
550 }
551
552 fn auth_body() -> Vec<u8> {
554 let reference = [0x01]; let hash = [0xAA, 0xBB, 0xCC, 0xDD]; let mut hashes_loop = vec![(1u8 << 4) | (reference.len() as u8)]; hashes_loop.extend_from_slice(&reference);
558 hashes_loop.extend_from_slice(&hash);
559 let loop_len = hashes_loop.len();
560
561 let mut b = vec![
562 0x00, hash.len() as u8, 0x01, 0xF0 | ((loop_len >> 8) as u8 & 0x0F), (loop_len & 0xFF) as u8, ];
568 b.extend_from_slice(&hashes_loop);
569 b.push(2);
571 b.extend_from_slice(&[0xDE, 0xAD]);
572 b.push(3);
574 b.extend_from_slice(&[0x11, 0x22, 0x33]);
575 b.extend_from_slice(&[0x90, 0x91, 0x92, 0x93, 0x94, 0x95]);
577 b
578 }
579
580 fn cert_body() -> Vec<u8> {
582 let c0: &[u8] = &[0x30, 0x82, 0x01, 0x02];
583 let c1: &[u8] = &[0xAB, 0xCD];
584 let mut b = vec![0xF0 | 0x02]; b.push(0xF0 | ((c0.len() >> 8) as u8 & 0x0F));
586 b.push((c0.len() & 0xFF) as u8);
587 b.extend_from_slice(c0);
588 b.push(0xF0 | ((c1.len() >> 8) as u8 & 0x0F));
589 b.push((c1.len() & 0xFF) as u8);
590 b.extend_from_slice(c1);
591 b
592 }
593
594 #[test]
595 fn parse_authentication_message() {
596 let bytes = build_section(0x0042, 5, &auth_body());
597 let sec = ProtectionMessageSection::parse(&bytes).unwrap();
598 assert_eq!(sec.table_id_extension, 0x0042);
599 assert_eq!(sec.version_number, 5);
600 assert!(sec.current_next_indicator);
601 match sec.body {
602 ProtectionMessageBody::AuthenticationMessage {
603 section_hash_algorithm_identifier,
604 section_hash_length,
605 signature_algorithm_identifier,
606 hashes,
607 extension_bytes,
608 signature_key_identifier,
609 signature,
610 } => {
611 assert_eq!(section_hash_algorithm_identifier, 0x00);
612 assert_eq!(section_hash_length, 4);
613 assert_eq!(signature_algorithm_identifier, 0x01);
614 assert_eq!(hashes.len(), 1);
615 assert_eq!(hashes[0].reference_type, ReferenceType::SameEs);
616 assert_eq!(hashes[0].reference, &[0x01]);
617 assert_eq!(hashes[0].hash, &[0xAA, 0xBB, 0xCC, 0xDD]);
618 assert_eq!(extension_bytes, &[0xDE, 0xAD]);
619 assert_eq!(signature_key_identifier, &[0x11, 0x22, 0x33]);
620 assert_eq!(signature, &[0x90, 0x91, 0x92, 0x93, 0x94, 0x95]);
621 }
622 other => panic!("expected AuthenticationMessage, got {other:?}"),
623 }
624 }
625
626 #[test]
627 fn parse_certificate_collection() {
628 let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 0, &cert_body());
629 let sec = ProtectionMessageSection::parse(&bytes).unwrap();
630 assert_eq!(sec.table_id_extension, 0x0100);
631 match sec.body {
632 ProtectionMessageBody::CertificateCollection { certificates } => {
633 assert_eq!(certificates.len(), 2);
634 assert_eq!(certificates[0], &[0x30, 0x82, 0x01, 0x02]);
635 assert_eq!(certificates[1], &[0xAB, 0xCD]);
636 }
637 other => panic!("expected CertificateCollection, got {other:?}"),
638 }
639 }
640
641 #[test]
642 fn reserved_extension_kept_raw() {
643 let raw = [0x01, 0x02, 0x03, 0x04];
644 let bytes = build_section(0x0200, 0, &raw);
645 let sec = ProtectionMessageSection::parse(&bytes).unwrap();
646 assert!(matches!(sec.body, ProtectionMessageBody::Raw(b) if b == raw));
647 }
648
649 #[test]
650 fn parse_rejects_wrong_tag() {
651 let mut bytes = build_section(0x0000, 0, &auth_body());
652 bytes[0] = 0x4D;
653 assert!(matches!(
654 ProtectionMessageSection::parse(&bytes).unwrap_err(),
655 Error::UnexpectedTableId { table_id: 0x4D, .. }
656 ));
657 }
658
659 #[test]
660 fn rejects_short_buffer() {
661 assert!(matches!(
662 ProtectionMessageSection::parse(&[0x7B, 0xB0]).unwrap_err(),
663 Error::BufferTooShort {
664 what: "ProtectionMessageSection",
665 ..
666 }
667 ));
668 }
669
670 #[test]
671 fn auth_loop_overflow_rejected() {
672 let mut body = vec![0x00, 0x04, 0x01, 0xF0, 0xFF];
673 body.extend_from_slice(&[0x00]);
674 let bytes = build_section(0x0000, 0, &body);
675 assert!(matches!(
676 ProtectionMessageSection::parse(&bytes).unwrap_err(),
677 Error::SectionLengthOverflow { .. }
678 ));
679 }
680
681 #[test]
682 fn cert_length_overflow_rejected() {
683 let body = vec![0xF0 | 0x01, 0x00, 0x10, 0x01];
684 let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 0, &body);
685 assert!(matches!(
686 ProtectionMessageSection::parse(&bytes).unwrap_err(),
687 Error::SectionLengthOverflow { .. }
688 ));
689 }
690
691 #[test]
692 fn round_trip_authentication_message() {
693 let bytes = build_section(0x0042, 7, &auth_body());
694 let sec = ProtectionMessageSection::parse(&bytes).unwrap();
695 let mut buf = vec![0u8; sec.serialized_len()];
696 sec.serialize_into(&mut buf).unwrap();
697 let re = ProtectionMessageSection::parse(&buf).unwrap();
698 assert_eq!(sec, re);
699 }
700
701 #[test]
702 fn round_trip_certificate_collection() {
703 let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 3, &cert_body());
704 let sec = ProtectionMessageSection::parse(&bytes).unwrap();
705 let mut buf = vec![0u8; sec.serialized_len()];
706 sec.serialize_into(&mut buf).unwrap();
707 let re = ProtectionMessageSection::parse(&buf).unwrap();
708 assert_eq!(sec, re);
709 }
710
711 #[test]
712 fn round_trip_raw_reserved() {
713 let bytes = build_section(0xABCD, 1, &[0xDE, 0xAD, 0xBE, 0xEF]);
714 let sec = ProtectionMessageSection::parse(&bytes).unwrap();
715 let mut buf = vec![0u8; sec.serialized_len()];
716 sec.serialize_into(&mut buf).unwrap();
717 let re = ProtectionMessageSection::parse(&buf).unwrap();
718 assert_eq!(sec, re);
719 }
720
721 #[test]
722 fn table_trait_constants() {
723 assert_eq!(TABLE_ID, 0x7B);
724 assert_eq!(PID, 0x0000);
725 }
726
727 #[test]
728 #[cfg(feature = "serde")]
729 fn serde_json_round_trip() {
730 let bytes = build_section(0x0042, 5, &auth_body());
731 let sec = ProtectionMessageSection::parse(&bytes).unwrap();
732 let j = serde_json::to_string(&sec).unwrap();
733 let reparsed = ProtectionMessageSection::parse(&bytes).unwrap();
734 assert_eq!(serde_json::to_string(&reparsed).unwrap(), j);
735 assert!(j.contains("\"signature_algorithm_identifier\":1"));
736 }
737
738 #[test]
739 fn parse_rejects_zero_section_length() {
740 let mut buf = vec![0u8; 64];
741 buf[0] = TABLE_ID;
742 buf[1] = 0xF0;
743 buf[2] = 0x00;
744 for b in &mut buf[3..] {
745 *b = 0xFF;
746 }
747 assert!(matches!(
748 ProtectionMessageSection::parse(&buf).unwrap_err(),
749 Error::SectionLengthOverflow { .. }
750 ));
751 }
752
753 #[test]
754 fn reference_type_full_range_round_trip() {
755 for v in 0u8..=0x0F {
756 let rt = ReferenceType::from_u8(v);
757 assert_eq!(
758 rt.to_u8(),
759 v,
760 "ReferenceType round-trip failed for {v:#04x}"
761 );
762 }
763 }
764}