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