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