1use crate::descriptors::DescriptorLoop;
10use crate::error::{Error, Result};
11use crate::text::DvbText;
12use dvb_common::{Parse, Serialize};
13
14pub const TABLE_ID: u8 = 0x79;
16pub const PID: u16 = 0x0016;
18
19const HEADER_LEN: usize = 3;
20const EXTENSION_HEADER_LEN: usize = 6;
21const COMMON_DESC_LEN_FIELD: usize = 2;
22const CRC_LEN: usize = 4;
23const MIN_LEN: usize = HEADER_LEN + EXTENSION_HEADER_LEN + COMMON_DESC_LEN_FIELD + CRC_LEN;
24
25const RP_INFO_LEN_FIELD: usize = 2;
26const RP_NAME_LEN_FIELD: usize = 1;
27const RP_DESC_LEN_FIELD: usize = 2;
28const CA_NAME_LEN_FIELD: usize = 1;
29const CA_HEADER_LEN: usize = 2;
30
31const RESERVED_NIBBLE: u8 = 0xF0;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36#[non_exhaustive]
37pub enum CridAuthorityPolicy {
38 Permanent,
40 Transient,
42 Either,
44 Reserved,
46}
47
48impl CridAuthorityPolicy {
49 #[must_use]
50 pub fn from_u8(v: u8) -> Self {
52 match v & 0x03 {
53 0 => Self::Permanent,
54 1 => Self::Transient,
55 2 => Self::Either,
56 _ => Self::Reserved,
57 }
58 }
59
60 #[must_use]
61 pub fn to_u8(self) -> u8 {
63 match self {
64 Self::Permanent => 0,
65 Self::Transient => 1,
66 Self::Either => 2,
67 Self::Reserved => 3,
68 }
69 }
70
71 #[must_use]
72 pub fn name(self) -> &'static str {
74 match self {
75 Self::Permanent => "Permanent",
76 Self::Transient => "Transient",
77 Self::Either => "Either",
78 Self::Reserved => "Reserved",
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize))]
86#[non_exhaustive]
87pub enum ContextIdType {
88 BouquetId,
90 OriginalNetworkId,
92 NetworkId,
94 DvbReserved(u8),
96 UserDefined(u8),
98}
99
100impl ContextIdType {
101 #[must_use]
102 pub fn from_u8(v: u8) -> Self {
104 match v {
105 0x00 => Self::BouquetId,
106 0x01 => Self::OriginalNetworkId,
107 0x02 => Self::NetworkId,
108 v if v < 0x80 => Self::DvbReserved(v),
109 _ => Self::UserDefined(v),
110 }
111 }
112
113 #[must_use]
114 pub fn to_u8(self) -> u8 {
116 match self {
117 Self::BouquetId => 0x00,
118 Self::OriginalNetworkId => 0x01,
119 Self::NetworkId => 0x02,
120 Self::DvbReserved(v) | Self::UserDefined(v) => v,
121 }
122 }
123
124 #[must_use]
125 pub fn name(self) -> &'static str {
127 match self {
128 Self::BouquetId => "Bouquet ID",
129 Self::OriginalNetworkId => "Original Network ID",
130 Self::NetworkId => "Network ID",
131 Self::DvbReserved(_) => "DVB Reserved",
132 Self::UserDefined(_) => "User Defined",
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
139#[cfg_attr(feature = "serde", derive(serde::Serialize))]
140pub struct CridAuthority<'a> {
141 pub name: DvbText<'a>,
143 pub crid_authority_policy: CridAuthorityPolicy,
146 pub descriptors: DescriptorLoop<'a>,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
152#[cfg_attr(feature = "serde", derive(serde::Serialize))]
153pub struct ResolutionProvider<'a> {
154 pub name: DvbText<'a>,
156 pub descriptors: DescriptorLoop<'a>,
158 pub crid_authorities: Vec<CridAuthority<'a>>,
160}
161
162fn crid_authority_serialized_len(ca: &CridAuthority) -> usize {
163 CA_NAME_LEN_FIELD + ca.name.len() + CA_HEADER_LEN + ca.descriptors.len()
164}
165
166fn resolution_provider_serialized_len(rp: &ResolutionProvider) -> usize {
167 RP_NAME_LEN_FIELD
168 + rp.name.len()
169 + RP_DESC_LEN_FIELD
170 + rp.descriptors.len()
171 + rp.crid_authorities
172 .iter()
173 .map(crid_authority_serialized_len)
174 .sum::<usize>()
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
183#[cfg_attr(feature = "serde", derive(serde::Serialize))]
184#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
185pub struct RntSection<'a> {
186 pub context_id: u16,
188 pub version_number: u8,
190 pub current_next_indicator: bool,
192 pub section_number: u8,
194 pub last_section_number: u8,
196 pub context_id_type: ContextIdType,
198 pub common_descriptors: DescriptorLoop<'a>,
201 pub resolution_providers: Vec<ResolutionProvider<'a>>,
203}
204
205impl<'a> Parse<'a> for RntSection<'a> {
206 type Error = crate::error::Error;
207
208 fn parse(bytes: &'a [u8]) -> Result<Self> {
209 if bytes.len() < MIN_LEN {
210 return Err(Error::BufferTooShort {
211 need: MIN_LEN,
212 have: bytes.len(),
213 what: "RntSection",
214 });
215 }
216 if bytes[0] != TABLE_ID {
217 return Err(Error::UnexpectedTableId {
218 table_id: bytes[0],
219 what: "RntSection",
220 expected: &[TABLE_ID],
221 });
222 }
223
224 let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
225 let total =
226 super::check_section_length(bytes.len(), HEADER_LEN, section_length as usize, MIN_LEN)?;
227
228 let context_id = u16::from_be_bytes([bytes[3], bytes[4]]);
229 let version_number = (bytes[5] >> 1) & 0x1F;
230 let current_next_indicator = (bytes[5] & 0x01) != 0;
231 let section_number = bytes[6];
232 let last_section_number = bytes[7];
233 let context_id_type = ContextIdType::from_u8(bytes[8]);
234
235 let common_desc_len_pos = HEADER_LEN + EXTENSION_HEADER_LEN;
236 let common_descriptors_length = (((bytes[common_desc_len_pos] & 0x0F) as usize) << 8)
237 | bytes[common_desc_len_pos + 1] as usize;
238 let common_desc_start = common_desc_len_pos + COMMON_DESC_LEN_FIELD;
239 let common_desc_end = common_desc_start + common_descriptors_length;
240 if common_desc_end > total - CRC_LEN {
241 return Err(Error::SectionLengthOverflow {
242 declared: common_descriptors_length,
243 available: (total - CRC_LEN).saturating_sub(common_desc_start),
244 });
245 }
246 let common_descriptors = DescriptorLoop::new(&bytes[common_desc_start..common_desc_end]);
247
248 let payload_end = total - CRC_LEN;
249 let mut pos = common_desc_end;
250 let mut resolution_providers = Vec::new();
251
252 while pos < payload_end {
253 if pos + RP_INFO_LEN_FIELD > payload_end {
254 return Err(Error::BufferTooShort {
255 need: pos + RP_INFO_LEN_FIELD,
256 have: payload_end,
257 what: "RntSection resolution_provider_info_length",
258 });
259 }
260 let rp_info_length = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
261 pos += RP_INFO_LEN_FIELD;
262 let rp_end = pos + rp_info_length;
263 if rp_end > payload_end {
264 return Err(Error::SectionLengthOverflow {
265 declared: rp_info_length,
266 available: payload_end.saturating_sub(pos),
267 });
268 }
269
270 if pos + RP_NAME_LEN_FIELD > rp_end {
271 return Err(Error::BufferTooShort {
272 need: pos + RP_NAME_LEN_FIELD,
273 have: rp_end,
274 what: "RntSection resolution_provider_name_length",
275 });
276 }
277 let name_len = bytes[pos] as usize;
278 pos += RP_NAME_LEN_FIELD;
279 if pos + name_len > rp_end {
280 return Err(Error::BufferTooShort {
281 need: pos + name_len,
282 have: rp_end,
283 what: "RntSection resolution_provider_name",
284 });
285 }
286 let name = DvbText::new(&bytes[pos..pos + name_len]);
287 pos += name_len;
288
289 if pos + RP_DESC_LEN_FIELD > rp_end {
290 return Err(Error::BufferTooShort {
291 need: pos + RP_DESC_LEN_FIELD,
292 have: rp_end,
293 what: "RntSection resolution_provider_descriptors_length",
294 });
295 }
296 let rp_desc_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
297 pos += RP_DESC_LEN_FIELD;
298 let rp_desc_start = pos;
299 let rp_desc_end = rp_desc_start + rp_desc_len;
300 if rp_desc_end > rp_end {
301 return Err(Error::SectionLengthOverflow {
302 declared: rp_desc_len,
303 available: rp_end.saturating_sub(rp_desc_start),
304 });
305 }
306 let descriptors = DescriptorLoop::new(&bytes[rp_desc_start..rp_desc_end]);
307 pos = rp_desc_end;
308
309 let mut crid_authorities = Vec::new();
310 while pos < rp_end {
311 if pos + CA_NAME_LEN_FIELD > rp_end {
312 return Err(Error::BufferTooShort {
313 need: pos + CA_NAME_LEN_FIELD,
314 have: rp_end,
315 what: "RntSection CRID_authority_name_length",
316 });
317 }
318 let ca_name_len = bytes[pos] as usize;
319 pos += CA_NAME_LEN_FIELD;
320 if pos + ca_name_len > rp_end {
321 return Err(Error::BufferTooShort {
322 need: pos + ca_name_len,
323 have: rp_end,
324 what: "RntSection CRID_authority_name",
325 });
326 }
327 let ca_name = DvbText::new(&bytes[pos..pos + ca_name_len]);
328 pos += ca_name_len;
329
330 if pos + CA_HEADER_LEN > rp_end {
331 return Err(Error::BufferTooShort {
332 need: pos + CA_HEADER_LEN,
333 have: rp_end,
334 what: "RntSection CRID_authority header",
335 });
336 }
337 let ca_packed = bytes[pos];
338 let crid_authority_policy = CridAuthorityPolicy::from_u8((ca_packed >> 4) & 0x03);
339 let ca_desc_len = (((ca_packed & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
340 pos += CA_HEADER_LEN;
341 let ca_desc_start = pos;
342 let ca_desc_end = ca_desc_start + ca_desc_len;
343 if ca_desc_end > rp_end {
344 return Err(Error::SectionLengthOverflow {
345 declared: ca_desc_len,
346 available: rp_end.saturating_sub(ca_desc_start),
347 });
348 }
349 let ca_descriptors = DescriptorLoop::new(&bytes[ca_desc_start..ca_desc_end]);
350 pos = ca_desc_end;
351
352 crid_authorities.push(CridAuthority {
353 name: ca_name,
354 crid_authority_policy,
355 descriptors: ca_descriptors,
356 });
357 }
358
359 resolution_providers.push(ResolutionProvider {
360 name,
361 descriptors,
362 crid_authorities,
363 });
364 pos = rp_end;
365 }
366
367 Ok(RntSection {
368 context_id,
369 version_number,
370 current_next_indicator,
371 section_number,
372 last_section_number,
373 context_id_type,
374 common_descriptors,
375 resolution_providers,
376 })
377 }
378}
379
380impl Serialize for RntSection<'_> {
381 type Error = crate::error::Error;
382
383 fn serialized_len(&self) -> usize {
384 HEADER_LEN
385 + EXTENSION_HEADER_LEN
386 + COMMON_DESC_LEN_FIELD
387 + self.common_descriptors.len()
388 + self
389 .resolution_providers
390 .iter()
391 .map(|rp| RP_INFO_LEN_FIELD + resolution_provider_serialized_len(rp))
392 .sum::<usize>()
393 + CRC_LEN
394 }
395
396 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
397 let len = self.serialized_len();
398 if buf.len() < len {
399 return Err(Error::OutputBufferTooSmall {
400 need: len,
401 have: buf.len(),
402 });
403 }
404
405 let section_length = (len - HEADER_LEN) as u16;
406 if section_length > 0x0FFF {
407 return Err(Error::SectionLengthOverflow {
408 declared: section_length as usize,
409 available: 0x0FFF,
410 });
411 }
412 buf[0] = TABLE_ID;
413 buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
414 buf[2] = (section_length & 0xFF) as u8;
415
416 buf[3..5].copy_from_slice(&self.context_id.to_be_bytes());
417 buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
418 buf[6] = self.section_number;
419 buf[7] = self.last_section_number;
420 buf[8] = self.context_id_type.to_u8();
421
422 let cdl = self.common_descriptors.len() as u16;
423 let cdl_pos = HEADER_LEN + EXTENSION_HEADER_LEN;
424 buf[cdl_pos] = RESERVED_NIBBLE | ((cdl >> 8) as u8 & 0x0F);
425 buf[cdl_pos + 1] = (cdl & 0xFF) as u8;
426
427 let cd_start = cdl_pos + COMMON_DESC_LEN_FIELD;
428 let cd_end = cd_start + self.common_descriptors.len();
429 buf[cd_start..cd_end].copy_from_slice(self.common_descriptors.raw());
430
431 let mut pos = cd_end;
432 for rp in &self.resolution_providers {
433 let rp_body_len = resolution_provider_serialized_len(rp);
434 let rp_info_length = rp_body_len as u16;
435 buf[pos] = RESERVED_NIBBLE | ((rp_info_length >> 8) as u8 & 0x0F);
436 buf[pos + 1] = (rp_info_length & 0xFF) as u8;
437 pos += RP_INFO_LEN_FIELD;
438
439 if rp.name.len() > u8::MAX as usize {
440 return Err(Error::ValueOutOfRange {
441 field: "resolution_provider_name_length",
442 reason: "exceeds 255 bytes",
443 });
444 }
445 buf[pos] = rp.name.len() as u8;
446 pos += RP_NAME_LEN_FIELD;
447 buf[pos..pos + rp.name.len()].copy_from_slice(rp.name.raw());
448 pos += rp.name.len();
449
450 let rdl = rp.descriptors.len() as u16;
451 buf[pos] = RESERVED_NIBBLE | ((rdl >> 8) as u8 & 0x0F);
452 buf[pos + 1] = (rdl & 0xFF) as u8;
453 pos += RP_DESC_LEN_FIELD;
454 buf[pos..pos + rp.descriptors.len()].copy_from_slice(rp.descriptors.raw());
455 pos += rp.descriptors.len();
456
457 for ca in &rp.crid_authorities {
458 if ca.name.len() > u8::MAX as usize {
459 return Err(Error::ValueOutOfRange {
460 field: "crid_authority_name_length",
461 reason: "exceeds 255 bytes",
462 });
463 }
464 buf[pos] = ca.name.len() as u8;
465 pos += CA_NAME_LEN_FIELD;
466 buf[pos..pos + ca.name.len()].copy_from_slice(ca.name.raw());
467 pos += ca.name.len();
468
469 let adl = ca.descriptors.len() as u16;
470 buf[pos] = 0xC0
471 | ((ca.crid_authority_policy.to_u8() & 0x03) << 4)
472 | ((adl >> 8) as u8 & 0x0F);
473 buf[pos + 1] = (adl & 0xFF) as u8;
474 pos += CA_HEADER_LEN;
475 buf[pos..pos + ca.descriptors.len()].copy_from_slice(ca.descriptors.raw());
476 pos += ca.descriptors.len();
477 }
478 }
479
480 let crc_pos = len - CRC_LEN;
481 let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
482 buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
483 Ok(len)
484 }
485}
486impl<'a> crate::traits::TableDef<'a> for RntSection<'a> {
487 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
488 const NAME: &'static str = "RESOLUTION_PROVIDER_NOTIFICATION";
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn parse_happy_path() {
497 let common_desc = [0x83u8, 0x02, 0xAB, 0xCD];
498 let rp = ResolutionProvider {
499 name: DvbText::new(b"bb"),
500 descriptors: DescriptorLoop::new(&[]),
501 crid_authorities: vec![CridAuthority {
502 name: DvbText::new(b"au"),
503 crid_authority_policy: CridAuthorityPolicy::Transient,
504 descriptors: DescriptorLoop::new(&[]),
505 }],
506 };
507 let rnt = RntSection {
508 context_id: 0x0042,
509 version_number: 3,
510 current_next_indicator: true,
511 section_number: 0,
512 last_section_number: 0,
513 context_id_type: ContextIdType::BouquetId,
514 common_descriptors: DescriptorLoop::new(&common_desc),
515 resolution_providers: vec![rp],
516 };
517 let mut buf = vec![0u8; rnt.serialized_len()];
518 rnt.serialize_into(&mut buf).unwrap();
519 let parsed = RntSection::parse(&buf).unwrap();
520 assert_eq!(parsed.context_id, 0x0042);
521 assert_eq!(parsed.version_number, 3);
522 assert!(parsed.current_next_indicator);
523 assert_eq!(parsed.context_id_type, ContextIdType::BouquetId);
524 assert_eq!(parsed.resolution_providers.len(), 1);
525 assert_eq!(parsed.resolution_providers[0].name.raw(), b"bb");
526 assert_eq!(parsed.resolution_providers[0].crid_authorities.len(), 1);
527 assert_eq!(
528 parsed.resolution_providers[0].crid_authorities[0].crid_authority_policy,
529 CridAuthorityPolicy::Transient
530 );
531 assert_eq!(
532 parsed.resolution_providers[0].crid_authorities[0]
533 .name
534 .raw(),
535 b"au"
536 );
537 }
538
539 #[test]
540 fn parse_no_descriptors_no_providers() {
541 let rnt = RntSection {
542 context_id: 0x0000,
543 version_number: 0,
544 current_next_indicator: false,
545 section_number: 0,
546 last_section_number: 0,
547 context_id_type: ContextIdType::BouquetId,
548 common_descriptors: DescriptorLoop::new(&[]),
549 resolution_providers: Vec::new(),
550 };
551 let mut buf = vec![0u8; rnt.serialized_len()];
552 rnt.serialize_into(&mut buf).unwrap();
553 let parsed = RntSection::parse(&buf).unwrap();
554 assert_eq!(parsed.common_descriptors.len(), 0);
555 assert!(parsed.resolution_providers.is_empty());
556 }
557
558 #[test]
559 fn byte_exact_round_trip() {
560 let rp = ResolutionProvider {
561 name: DvbText::new(b"provider"),
562 descriptors: DescriptorLoop::new(&[0x40, 0x03, b'R', b'N', b'T']),
563 crid_authorities: vec![
564 CridAuthority {
565 name: DvbText::new(b"auth1"),
566 crid_authority_policy: CridAuthorityPolicy::Permanent,
567 descriptors: DescriptorLoop::new(&[]),
568 },
569 CridAuthority {
570 name: DvbText::new(b"auth2"),
571 crid_authority_policy: CridAuthorityPolicy::Either,
572 descriptors: DescriptorLoop::new(&[0x42, 0x00]),
573 },
574 ],
575 };
576 let rnt = RntSection {
577 context_id: 0xABCD,
578 version_number: 15,
579 current_next_indicator: true,
580 section_number: 1,
581 last_section_number: 2,
582 context_id_type: ContextIdType::NetworkId,
583 common_descriptors: DescriptorLoop::new(&[0x40, 0x03, b'R', b'N', b'T']),
584 resolution_providers: vec![rp],
585 };
586 let mut buf = vec![0u8; rnt.serialized_len()];
587 rnt.serialize_into(&mut buf).unwrap();
588 let re = RntSection::parse(&buf).unwrap();
589 let mut buf2 = vec![0u8; re.serialized_len()];
590 re.serialize_into(&mut buf2).unwrap();
591 assert_eq!(buf, buf2, "byte-exact re-serialize");
592 let re = RntSection::parse(&buf).unwrap();
593 assert_eq!(re.resolution_providers.len(), 1);
594 assert_eq!(re.resolution_providers[0].name.raw(), b"provider");
595 assert_eq!(re.resolution_providers[0].crid_authorities.len(), 2);
596 assert_eq!(
597 re.resolution_providers[0].crid_authorities[1].crid_authority_policy,
598 CridAuthorityPolicy::Either
599 );
600 }
601
602 #[test]
603 fn parse_rejects_wrong_table_id() {
604 let rnt = RntSection {
605 context_id: 0x0001,
606 version_number: 0,
607 current_next_indicator: true,
608 section_number: 0,
609 last_section_number: 0,
610 context_id_type: ContextIdType::BouquetId,
611 common_descriptors: DescriptorLoop::new(&[]),
612 resolution_providers: Vec::new(),
613 };
614 let mut buf = vec![0u8; rnt.serialized_len()];
615 rnt.serialize_into(&mut buf).unwrap();
616 buf[0] = 0x70;
617 assert!(matches!(
618 RntSection::parse(&buf).unwrap_err(),
619 Error::UnexpectedTableId { table_id: 0x70, .. }
620 ));
621 }
622
623 #[test]
624 fn parse_rejects_short_buffer() {
625 assert!(matches!(
626 RntSection::parse(&[0x79, 0x00]).unwrap_err(),
627 Error::BufferTooShort { .. }
628 ));
629 }
630
631 #[test]
632 fn serialize_rejects_too_small_buffer() {
633 let rnt = RntSection {
634 context_id: 0x0001,
635 version_number: 0,
636 current_next_indicator: true,
637 section_number: 0,
638 last_section_number: 0,
639 context_id_type: ContextIdType::BouquetId,
640 common_descriptors: DescriptorLoop::new(&[]),
641 resolution_providers: Vec::new(),
642 };
643 let mut buf = vec![0u8; 2];
644 assert!(matches!(
645 rnt.serialize_into(&mut buf).unwrap_err(),
646 Error::OutputBufferTooSmall { .. }
647 ));
648 }
649
650 #[test]
651 fn parse_rejects_zero_section_length() {
652 let mut buf = vec![0u8; 64];
653 buf[0] = TABLE_ID;
654 buf[1] = 0xF0;
655 buf[2] = 0x00;
656 for b in &mut buf[3..] {
657 *b = 0xFF;
658 }
659 assert!(matches!(
660 RntSection::parse(&buf).unwrap_err(),
661 Error::SectionLengthOverflow { .. }
662 ));
663 }
664
665 #[test]
666 fn parse_handwritten_rnt_no_providers() {
667 let mut bytes: Vec<u8> = vec![
668 0x79, 0xF0, 0x0C, 0x00, 0x42, 0xC7, 0x00, 0x00, 0x01, 0xF0, 0x00,
669 ];
670 let crc = dvb_common::crc32_mpeg2::compute(&bytes);
671 bytes.extend_from_slice(&crc.to_be_bytes());
672 let rnt = RntSection::parse(&bytes).unwrap();
673 assert_eq!(rnt.context_id, 0x0042);
674 assert_eq!(rnt.version_number, 3);
675 assert!(rnt.current_next_indicator);
676 assert!(rnt.resolution_providers.is_empty());
677 }
678
679 #[test]
680 fn crid_authority_policy_round_trip() {
681 for v in 0u8..=3 {
682 let p = CridAuthorityPolicy::from_u8(v);
683 assert_eq!(p.to_u8(), v, "CridAuthorityPolicy round-trip for {v}");
684 }
685 }
686
687 #[test]
688 fn context_id_type_full_range_round_trip() {
689 for byte in 0u8..=0xFF {
690 let ct = ContextIdType::from_u8(byte);
691 assert_eq!(
692 ct.to_u8(),
693 byte,
694 "ContextIdType round-trip failed for {byte:#04x}"
695 );
696 }
697 }
698}