Skip to main content

dvb_si/tables/
protection_message.rs

1//! Protection message sections — ETSI TS 102 809 v1.3.1 §9 (table_id 0x7B).
2//!
3//! Protection messages are MPEG-2 private sections (ISO/IEC 13818-1) used by
4//! the application-signalling authentication scheme. They share one table_id
5//! (0x7B) and are discriminated by the 16-bit `table_id_extension`
6//! ([`ProtectionMessageBody`], §9.3.4 Table 41, PDF p. 66):
7//!
8//! - `0x0000..=0x00FF` — **authentication message** (§9.4.3 Table 42, PDF
9//!   pp. 70-71). The extension *is* the `authentication_group_id`. Carries a
10//!   loop of section hashes plus a detached signature.
11//! - `0x0100` — **certificate collection message** (§9.5.4.9 Table 51, PDF
12//!   p. 91). The extension is the fixed `trust_message_id` 0x0100. Carries a
13//!   count-prefixed list of DER-encoded DVBCertificates.
14//! - `0x0101..=0xFFFF` — reserved for future use; preserved as a raw body so
15//!   parse → serialize stays byte-exact ([`ProtectionMessageBody::Raw`]).
16//!
17//! Mirrors the SAT precedent (`sat.rs`): a typed common section header plus a
18//! discriminated typed body. Variable-length inner loops (hash entries,
19//! certificate slices) are exposed as borrowed slices.
20//!
21//! Per crate contract this parser does NOT verify CRC_32 (use
22//! `Section::validate_crc`). Reserved bits are ignored on parse and emitted as
23//! 1s on serialize, except spec-mandated zero fields which are emitted 0.
24
25use crate::error::{Error, Result};
26use dvb_common::{Parse, Serialize};
27
28/// table_id for all protection messages (TS 102 809 §9.3.4; coded per EN 300 468 §5.1.3).
29pub const TABLE_ID: u8 = 0x7B;
30/// Protection messages have no well-known PID — they are carried on the PID(s)
31/// of the protectable elementary stream, signalled via the protection message
32/// descriptor (§9.3.3). Mirrors the `dsmcc.rs` "no fixed PID" convention.
33pub const PID: u16 = 0x0000;
34
35/// `table_id_extension` value (inclusive) of the first authentication message (§9.3.4 Table 41).
36pub const AUTH_EXTENSION_FIRST: u16 = 0x0000;
37/// `table_id_extension` value (inclusive) of the last authentication message (§9.3.4 Table 41).
38pub const AUTH_EXTENSION_LAST: u16 = 0x00FF;
39/// `table_id_extension` (`trust_message_id`) of the certificate collection message (§9.3.4 Table 41).
40pub const CERTIFICATE_COLLECTION_EXTENSION: u16 = 0x0100;
41
42/// table_id(1) + section_length hi/lo(2) + extension(2) + version/cni(1)
43/// + section_number(1) + last_section_number(1) = 8-byte common header.
44const HEADER_LEN: usize = 8;
45/// `section_length` counts from just after the field (byte 3) to end of section.
46const SECTION_LENGTH_PREFIX: usize = 3;
47/// CRC_32 trailer.
48const CRC_LEN: usize = 4;
49
50/// Authentication-message fixed body bytes after the common header, before the
51/// hash loop: section_hash_algorithm_identifier(1) + section_hash_length(1)
52/// + signature_algorithm_identifier(1) + reserved(4)|section_hashes_loop_length(12)(2).
53const AUTH_FIXED_PREFIX: usize = 5;
54
55/// One entry in the authentication message section-hash loop (§9.4.3 Table 42).
56///
57/// Each entry pairs a reference (locating the payload section the hash covers)
58/// with the truncated hash itself. `reference` length is the 4-bit
59/// `reference_length`; `hash` length is the section-wide `section_hash_length`.
60#[derive(Debug, Clone, PartialEq, Eq)]
61#[cfg_attr(feature = "serde", derive(serde::Serialize))]
62#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
63pub struct SectionHashEntry<'a> {
64    /// 4-bit `reference_type` (§9.4.3 Table 45): 1 = same ES, 2 = component_tag ES.
65    pub reference_type: u8,
66    /// `reference_byte` field — its semantics depend on `reference_type`.
67    pub reference: &'a [u8],
68    /// The (possibly truncated) section hash, `section_hash_length` bytes.
69    pub hash: &'a [u8],
70}
71
72/// Discriminated protection-message body, selected by `table_id_extension`.
73#[derive(Debug, Clone, PartialEq, Eq)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize))]
75#[non_exhaustive]
76pub enum ProtectionMessageBody<'a> {
77    /// Authentication message (extension `0x0000..=0x00FF`; §9.4.3 Table 42).
78    AuthenticationMessage {
79        /// `section_hash_algorithm_identifier` (§9.4.3 Table 43): 0 = SHA-256, 1 = SHA-512.
80        section_hash_algorithm_identifier: u8,
81        /// `section_hash_length` — bytes per hash in each loop entry.
82        section_hash_length: u8,
83        /// `signature_algorithm_identifier` (§9.4.3 Table 44).
84        signature_algorithm_identifier: u8,
85        /// Section-hash loop entries in wire order.
86        hashes: Vec<SectionHashEntry<'a>>,
87        /// `extension_byte` payload (length-prefixed by `extension_bytes_length`).
88        extension_bytes: &'a [u8],
89        /// `signature_key_identifier_byte` payload (length-prefixed).
90        signature_key_identifier: &'a [u8],
91        /// Detached signature — runs from after the key identifier to the CRC.
92        signature: &'a [u8],
93    },
94    /// Certificate collection message (extension `0x0100`; §9.5.4.9 Table 51).
95    CertificateCollection {
96        /// DER-encoded DVBCertificate byte runs, one slice per `certificate_length` loop entry.
97        certificates: Vec<&'a [u8]>,
98    },
99    /// Reserved extension (`0x0101..=0xFFFF`) — body preserved verbatim.
100    Raw(&'a [u8]),
101}
102
103/// Protection message section (TS 102 809 §9; Tables 42 / 51).
104///
105/// Typed fields cover the common section header; [`ProtectionMessageSection::body`]
106/// carries the typed, discriminated body.
107#[derive(Debug, Clone, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
109#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
110pub struct ProtectionMessageSection<'a> {
111    /// `table_id_extension` — `authentication_group_id` for authentication
112    /// messages, `trust_message_id` (0x0100) for certificate collections.
113    pub table_id_extension: u16,
114    /// 5-bit version_number.
115    pub version_number: u8,
116    /// current_next_indicator bit (spec mandates 1).
117    pub current_next_indicator: bool,
118    /// section_number.
119    pub section_number: u8,
120    /// last_section_number.
121    pub last_section_number: u8,
122    /// Discriminated body, selected by `table_id_extension`.
123    pub body: ProtectionMessageBody<'a>,
124}
125
126impl<'a> Parse<'a> for ProtectionMessageSection<'a> {
127    type Error = crate::error::Error;
128    fn parse(bytes: &'a [u8]) -> Result<Self> {
129        let min_len = HEADER_LEN + CRC_LEN;
130        if bytes.len() < min_len {
131            return Err(Error::BufferTooShort {
132                need: min_len,
133                have: bytes.len(),
134                what: "ProtectionMessageSection",
135            });
136        }
137        if bytes[0] != TABLE_ID {
138            return Err(Error::UnexpectedTableId {
139                table_id: bytes[0],
140                what: "ProtectionMessageSection",
141                expected: &[TABLE_ID],
142            });
143        }
144        let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
145        let total = super::check_section_length(
146            bytes.len(),
147            SECTION_LENGTH_PREFIX,
148            section_length,
149            HEADER_LEN + CRC_LEN,
150        )?;
151
152        let table_id_extension = u16::from_be_bytes([bytes[3], bytes[4]]);
153        let version_number = (bytes[5] >> 1) & 0x1F;
154        let current_next_indicator = bytes[5] & 0x01 != 0;
155        let section_number = bytes[6];
156        let last_section_number = bytes[7];
157
158        let body_bytes = &bytes[HEADER_LEN..total - CRC_LEN];
159        let body = match table_id_extension {
160            AUTH_EXTENSION_FIRST..=AUTH_EXTENSION_LAST => parse_authentication_message(body_bytes)?,
161            CERTIFICATE_COLLECTION_EXTENSION => parse_certificate_collection(body_bytes)?,
162            _ => ProtectionMessageBody::Raw(body_bytes),
163        };
164
165        Ok(ProtectionMessageSection {
166            table_id_extension,
167            version_number,
168            current_next_indicator,
169            section_number,
170            last_section_number,
171            body,
172        })
173    }
174}
175
176/// Parse the authentication-message body (§9.4.3 Table 42, PDF pp. 70-71).
177fn parse_authentication_message(body: &[u8]) -> Result<ProtectionMessageBody<'_>> {
178    if body.len() < AUTH_FIXED_PREFIX {
179        return Err(Error::BufferTooShort {
180            need: AUTH_FIXED_PREFIX,
181            have: body.len(),
182            what: "ProtectionMessageSection::AuthenticationMessage",
183        });
184    }
185    let section_hash_algorithm_identifier = body[0];
186    let section_hash_length = body[1];
187    let signature_algorithm_identifier = body[2];
188    // bytes[3] high nibble = reserved; section_hashes_loop_length is 12 bits.
189    let section_hashes_loop_length = (((body[3] & 0x0F) as usize) << 8) | body[4] as usize;
190
191    let loop_start = AUTH_FIXED_PREFIX;
192    let loop_end = loop_start + section_hashes_loop_length;
193    if loop_end > body.len() {
194        return Err(Error::SectionLengthOverflow {
195            declared: section_hashes_loop_length,
196            available: body.len() - loop_start,
197        });
198    }
199
200    let hash_len = section_hash_length as usize;
201    let mut hashes = Vec::new();
202    let mut pos = loop_start;
203    while pos < loop_end {
204        // reference_type(4) | reference_length(4)
205        let lead = body[pos];
206        let reference_type = lead >> 4;
207        let reference_length = (lead & 0x0F) as usize;
208        let ref_start = pos + 1;
209        let ref_end = ref_start + reference_length;
210        let hash_end = ref_end + hash_len;
211        if hash_end > loop_end {
212            return Err(Error::SectionLengthOverflow {
213                declared: reference_length + hash_len,
214                available: loop_end - ref_start,
215            });
216        }
217        hashes.push(SectionHashEntry {
218            reference_type,
219            reference: &body[ref_start..ref_end],
220            hash: &body[ref_end..hash_end],
221        });
222        pos = hash_end;
223    }
224
225    // extension_bytes_length(8) + extension bytes (§9.4.3 Table 42 tail).
226    if loop_end >= body.len() {
227        return Err(Error::BufferTooShort {
228            need: loop_end + 1,
229            have: body.len(),
230            what: "ProtectionMessageSection::extension_bytes_length",
231        });
232    }
233    let extension_bytes_length = body[loop_end] as usize;
234    let ext_start = loop_end + 1;
235    let ext_end = ext_start + extension_bytes_length;
236    if ext_end > body.len() {
237        return Err(Error::SectionLengthOverflow {
238            declared: extension_bytes_length,
239            available: body.len() - ext_start,
240        });
241    }
242
243    // signature_key_identifier_length(8) + key id bytes (Table 43 spillover, PDF p. 71).
244    if ext_end >= body.len() {
245        return Err(Error::BufferTooShort {
246            need: ext_end + 1,
247            have: body.len(),
248            what: "ProtectionMessageSection::signature_key_identifier_length",
249        });
250    }
251    let key_id_length = body[ext_end] as usize;
252    let key_start = ext_end + 1;
253    let key_end = key_start + key_id_length;
254    if key_end > body.len() {
255        return Err(Error::SectionLengthOverflow {
256            declared: key_id_length,
257            available: body.len() - key_start,
258        });
259    }
260
261    // signature_byte loop runs to the end of the body (i.e. up to the CRC_32).
262    let signature = &body[key_end..];
263
264    Ok(ProtectionMessageBody::AuthenticationMessage {
265        section_hash_algorithm_identifier,
266        section_hash_length,
267        signature_algorithm_identifier,
268        hashes,
269        extension_bytes: &body[ext_start..ext_end],
270        signature_key_identifier: &body[key_start..key_end],
271        signature,
272    })
273}
274
275/// Parse the certificate-collection body (§9.5.4.9 Table 51, PDF p. 91).
276fn parse_certificate_collection(body: &[u8]) -> Result<ProtectionMessageBody<'_>> {
277    if body.is_empty() {
278        return Err(Error::BufferTooShort {
279            need: 1,
280            have: 0,
281            what: "ProtectionMessageSection::CertificateCollection",
282        });
283    }
284    // byte 0: reserved(4) | certificate_count(4)
285    let certificate_count = (body[0] & 0x0F) as usize;
286    let mut certificates = Vec::with_capacity(certificate_count);
287    let mut pos = 1;
288    for _ in 0..certificate_count {
289        if pos + 2 > body.len() {
290            return Err(Error::BufferTooShort {
291                need: pos + 2,
292                have: body.len(),
293                what: "ProtectionMessageSection::certificate_length",
294            });
295        }
296        // reserved(4) | certificate_length(12)
297        let certificate_length = (((body[pos] & 0x0F) as usize) << 8) | body[pos + 1] as usize;
298        let cert_start = pos + 2;
299        let cert_end = cert_start + certificate_length;
300        if cert_end > body.len() {
301            return Err(Error::SectionLengthOverflow {
302                declared: certificate_length,
303                available: body.len() - cert_start,
304            });
305        }
306        certificates.push(&body[cert_start..cert_end]);
307        pos = cert_end;
308    }
309    Ok(ProtectionMessageBody::CertificateCollection { certificates })
310}
311
312impl ProtectionMessageBody<'_> {
313    /// Serialized length of the body (excluding common header and CRC).
314    fn body_len(&self) -> usize {
315        match self {
316            ProtectionMessageBody::AuthenticationMessage {
317                hashes,
318                extension_bytes,
319                signature_key_identifier,
320                signature,
321                ..
322            } => {
323                let loop_bytes: usize = hashes
324                    .iter()
325                    .map(|h| 1 + h.reference.len() + h.hash.len())
326                    .sum();
327                AUTH_FIXED_PREFIX
328                    + loop_bytes
329                    + 1
330                    + extension_bytes.len()
331                    + 1
332                    + signature_key_identifier.len()
333                    + signature.len()
334            }
335            ProtectionMessageBody::CertificateCollection { certificates } => {
336                1 + certificates.iter().map(|c| 2 + c.len()).sum::<usize>()
337            }
338            ProtectionMessageBody::Raw(raw) => raw.len(),
339        }
340    }
341
342    /// Write the body into `buf`, returning the number of bytes written.
343    /// Over-range length/count fields error rather than silently truncating
344    /// (the crate's strict serialize idiom — cf. `mpe.rs` scrambling guards).
345    fn write_into(&self, buf: &mut [u8]) -> Result<usize> {
346        match self {
347            ProtectionMessageBody::AuthenticationMessage {
348                section_hash_algorithm_identifier,
349                section_hash_length,
350                signature_algorithm_identifier,
351                hashes,
352                extension_bytes,
353                signature_key_identifier,
354                signature,
355            } => {
356                buf[0] = *section_hash_algorithm_identifier;
357                buf[1] = *section_hash_length;
358                buf[2] = *signature_algorithm_identifier;
359                let loop_bytes: usize = hashes
360                    .iter()
361                    .map(|h| 1 + h.reference.len() + h.hash.len())
362                    .sum();
363                if loop_bytes > 0x0FFF {
364                    return Err(Error::SectionLengthOverflow {
365                        declared: loop_bytes,
366                        available: 0x0FFF,
367                    });
368                }
369                if extension_bytes.len() > u8::MAX as usize {
370                    return Err(Error::SectionLengthOverflow {
371                        declared: extension_bytes.len(),
372                        available: u8::MAX as usize,
373                    });
374                }
375                if signature_key_identifier.len() > u8::MAX as usize {
376                    return Err(Error::SectionLengthOverflow {
377                        declared: signature_key_identifier.len(),
378                        available: u8::MAX as usize,
379                    });
380                }
381                // reserved(4) emitted 1s | section_hashes_loop_length(12).
382                buf[3] = 0xF0 | ((loop_bytes >> 8) as u8 & 0x0F);
383                buf[4] = (loop_bytes & 0xFF) as u8;
384                let mut pos = AUTH_FIXED_PREFIX;
385                for h in hashes {
386                    // reference_length is a 4-bit field.
387                    if h.reference.len() > 0x0F {
388                        return Err(Error::SectionLengthOverflow {
389                            declared: h.reference.len(),
390                            available: 0x0F,
391                        });
392                    }
393                    buf[pos] = (h.reference_type << 4) | (h.reference.len() as u8 & 0x0F);
394                    pos += 1;
395                    buf[pos..pos + h.reference.len()].copy_from_slice(h.reference);
396                    pos += h.reference.len();
397                    buf[pos..pos + h.hash.len()].copy_from_slice(h.hash);
398                    pos += h.hash.len();
399                }
400                buf[pos] = extension_bytes.len() as u8;
401                pos += 1;
402                buf[pos..pos + extension_bytes.len()].copy_from_slice(extension_bytes);
403                pos += extension_bytes.len();
404                buf[pos] = signature_key_identifier.len() as u8;
405                pos += 1;
406                buf[pos..pos + signature_key_identifier.len()]
407                    .copy_from_slice(signature_key_identifier);
408                pos += signature_key_identifier.len();
409                buf[pos..pos + signature.len()].copy_from_slice(signature);
410                pos += signature.len();
411                Ok(pos)
412            }
413            ProtectionMessageBody::CertificateCollection { certificates } => {
414                // certificate_count is a 4-bit field.
415                if certificates.len() > 0x0F {
416                    return Err(Error::SectionLengthOverflow {
417                        declared: certificates.len(),
418                        available: 0x0F,
419                    });
420                }
421                // reserved(4) emitted 1s | certificate_count(4).
422                buf[0] = 0xF0 | (certificates.len() as u8 & 0x0F);
423                let mut pos = 1;
424                for c in certificates {
425                    // certificate_length is a 12-bit field.
426                    if c.len() > 0x0FFF {
427                        return Err(Error::SectionLengthOverflow {
428                            declared: c.len(),
429                            available: 0x0FFF,
430                        });
431                    }
432                    // reserved(4) emitted 1s | certificate_length(12).
433                    buf[pos] = 0xF0 | ((c.len() >> 8) as u8 & 0x0F);
434                    buf[pos + 1] = (c.len() & 0xFF) as u8;
435                    pos += 2;
436                    buf[pos..pos + c.len()].copy_from_slice(c);
437                    pos += c.len();
438                }
439                Ok(pos)
440            }
441            ProtectionMessageBody::Raw(raw) => {
442                buf[..raw.len()].copy_from_slice(raw);
443                Ok(raw.len())
444            }
445        }
446    }
447}
448
449impl Serialize for ProtectionMessageSection<'_> {
450    type Error = crate::error::Error;
451    fn serialized_len(&self) -> usize {
452        HEADER_LEN + self.body.body_len() + CRC_LEN
453    }
454    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
455        let len = self.serialized_len();
456        if buf.len() < len {
457            return Err(Error::OutputBufferTooSmall {
458                need: len,
459                have: buf.len(),
460            });
461        }
462        let section_length = (len - SECTION_LENGTH_PREFIX) as u16;
463        buf[0] = TABLE_ID;
464        buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
465        buf[2] = (section_length & 0xFF) as u8;
466        buf[3..5].copy_from_slice(&self.table_id_extension.to_be_bytes());
467        // reserved(2)=11, version_number(5), current_next_indicator(1).
468        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
469        buf[6] = self.section_number;
470        buf[7] = self.last_section_number;
471        let body_written = self.body.write_into(&mut buf[HEADER_LEN..])?;
472        let body_end = HEADER_LEN + body_written;
473        let crc = dvb_common::crc32_mpeg2::compute(&buf[..body_end]);
474        buf[body_end..len].copy_from_slice(&crc.to_be_bytes());
475        Ok(len)
476    }
477}
478impl<'a> crate::traits::TableDef<'a> for ProtectionMessageSection<'a> {
479    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
480    const NAME: &'static str = "PROTECTION_MESSAGE";
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486
487    /// Wrap a body in the 8-byte common header + placeholder CRC.
488    fn build_section(extension: u16, version: u8, body: &[u8]) -> Vec<u8> {
489        let section_length = (HEADER_LEN - SECTION_LENGTH_PREFIX + body.len() + CRC_LEN) as u16;
490        let mut v = vec![
491            TABLE_ID,
492            super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F),
493            (section_length & 0xFF) as u8,
494            (extension >> 8) as u8,
495            (extension & 0xFF) as u8,
496            0xC0 | (version << 1) | 0x01,
497            0x00,
498            0x00,
499        ];
500        v.extend_from_slice(body);
501        v.extend_from_slice(&[0, 0, 0, 0]);
502        v
503    }
504
505    /// Build an authentication-message body: one hash entry + ext + key id + signature.
506    fn auth_body() -> Vec<u8> {
507        let reference = [0x01]; // reference_type 1 => table_id of payload section
508        let hash = [0xAA, 0xBB, 0xCC, 0xDD]; // section_hash_length = 4
509        let mut hashes_loop = vec![(1u8 << 4) | (reference.len() as u8)]; // reference_type 1, length 1
510        hashes_loop.extend_from_slice(&reference);
511        hashes_loop.extend_from_slice(&hash);
512        let loop_len = hashes_loop.len();
513
514        let mut b = vec![
515            0x00,                                  // section_hash_algorithm_identifier = SHA-256
516            hash.len() as u8,                      // section_hash_length = 4
517            0x01,                                  // signature_algorithm_identifier = ed25519
518            0xF0 | ((loop_len >> 8) as u8 & 0x0F), // reserved | loop_length hi
519            (loop_len & 0xFF) as u8,               // loop_length lo
520        ];
521        b.extend_from_slice(&hashes_loop);
522        // extension_bytes_length + bytes
523        b.push(2);
524        b.extend_from_slice(&[0xDE, 0xAD]);
525        // signature_key_identifier_length + bytes
526        b.push(3);
527        b.extend_from_slice(&[0x11, 0x22, 0x33]);
528        // signature (runs to CRC)
529        b.extend_from_slice(&[0x90, 0x91, 0x92, 0x93, 0x94, 0x95]);
530        b
531    }
532
533    /// Build a certificate-collection body with two certificate slices.
534    fn cert_body() -> Vec<u8> {
535        let c0: &[u8] = &[0x30, 0x82, 0x01, 0x02];
536        let c1: &[u8] = &[0xAB, 0xCD];
537        let mut b = vec![0xF0 | 0x02]; // reserved | certificate_count = 2
538        b.push(0xF0 | ((c0.len() >> 8) as u8 & 0x0F));
539        b.push((c0.len() & 0xFF) as u8);
540        b.extend_from_slice(c0);
541        b.push(0xF0 | ((c1.len() >> 8) as u8 & 0x0F));
542        b.push((c1.len() & 0xFF) as u8);
543        b.extend_from_slice(c1);
544        b
545    }
546
547    #[test]
548    fn parse_authentication_message() {
549        let bytes = build_section(0x0042, 5, &auth_body());
550        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
551        assert_eq!(sec.table_id_extension, 0x0042);
552        assert_eq!(sec.version_number, 5);
553        assert!(sec.current_next_indicator);
554        match sec.body {
555            ProtectionMessageBody::AuthenticationMessage {
556                section_hash_algorithm_identifier,
557                section_hash_length,
558                signature_algorithm_identifier,
559                hashes,
560                extension_bytes,
561                signature_key_identifier,
562                signature,
563            } => {
564                assert_eq!(section_hash_algorithm_identifier, 0x00);
565                assert_eq!(section_hash_length, 4);
566                assert_eq!(signature_algorithm_identifier, 0x01);
567                assert_eq!(hashes.len(), 1);
568                assert_eq!(hashes[0].reference_type, 1);
569                assert_eq!(hashes[0].reference, &[0x01]);
570                assert_eq!(hashes[0].hash, &[0xAA, 0xBB, 0xCC, 0xDD]);
571                assert_eq!(extension_bytes, &[0xDE, 0xAD]);
572                assert_eq!(signature_key_identifier, &[0x11, 0x22, 0x33]);
573                assert_eq!(signature, &[0x90, 0x91, 0x92, 0x93, 0x94, 0x95]);
574            }
575            other => panic!("expected AuthenticationMessage, got {other:?}"),
576        }
577    }
578
579    #[test]
580    fn parse_certificate_collection() {
581        let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 0, &cert_body());
582        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
583        assert_eq!(sec.table_id_extension, 0x0100);
584        match sec.body {
585            ProtectionMessageBody::CertificateCollection { certificates } => {
586                assert_eq!(certificates.len(), 2);
587                assert_eq!(certificates[0], &[0x30, 0x82, 0x01, 0x02]);
588                assert_eq!(certificates[1], &[0xAB, 0xCD]);
589            }
590            other => panic!("expected CertificateCollection, got {other:?}"),
591        }
592    }
593
594    #[test]
595    fn reserved_extension_kept_raw() {
596        let raw = [0x01, 0x02, 0x03, 0x04];
597        let bytes = build_section(0x0200, 0, &raw);
598        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
599        assert!(matches!(sec.body, ProtectionMessageBody::Raw(b) if b == raw));
600    }
601
602    #[test]
603    fn parse_rejects_wrong_tag() {
604        let mut bytes = build_section(0x0000, 0, &auth_body());
605        bytes[0] = 0x4D;
606        assert!(matches!(
607            ProtectionMessageSection::parse(&bytes).unwrap_err(),
608            Error::UnexpectedTableId { table_id: 0x4D, .. }
609        ));
610    }
611
612    #[test]
613    fn rejects_short_buffer() {
614        assert!(matches!(
615            ProtectionMessageSection::parse(&[0x7B, 0xB0]).unwrap_err(),
616            Error::BufferTooShort {
617                what: "ProtectionMessageSection",
618                ..
619            }
620        ));
621    }
622
623    #[test]
624    fn auth_loop_overflow_rejected() {
625        // Declare a section_hashes_loop_length that overruns the body.
626        let mut body = vec![0x00, 0x04, 0x01, 0xF0, 0xFF]; // loop_len = 0xFF, body far shorter
627        body.extend_from_slice(&[0x00]); // 1 stray byte
628        let bytes = build_section(0x0000, 0, &body);
629        assert!(matches!(
630            ProtectionMessageSection::parse(&bytes).unwrap_err(),
631            Error::SectionLengthOverflow { .. }
632        ));
633    }
634
635    #[test]
636    fn cert_length_overflow_rejected() {
637        // certificate_count = 1 but certificate_length overruns the body.
638        let body = vec![0xF0 | 0x01, 0x00, 0x10, 0x01]; // length 0x010 = 16, only 1 byte present
639        let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 0, &body);
640        assert!(matches!(
641            ProtectionMessageSection::parse(&bytes).unwrap_err(),
642            Error::SectionLengthOverflow { .. }
643        ));
644    }
645
646    #[test]
647    fn round_trip_authentication_message() {
648        let bytes = build_section(0x0042, 7, &auth_body());
649        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
650        let mut buf = vec![0u8; sec.serialized_len()];
651        sec.serialize_into(&mut buf).unwrap();
652        let re = ProtectionMessageSection::parse(&buf).unwrap();
653        assert_eq!(sec, re);
654    }
655
656    #[test]
657    fn round_trip_certificate_collection() {
658        let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 3, &cert_body());
659        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
660        let mut buf = vec![0u8; sec.serialized_len()];
661        sec.serialize_into(&mut buf).unwrap();
662        let re = ProtectionMessageSection::parse(&buf).unwrap();
663        assert_eq!(sec, re);
664    }
665
666    #[test]
667    fn round_trip_raw_reserved() {
668        let bytes = build_section(0xABCD, 1, &[0xDE, 0xAD, 0xBE, 0xEF]);
669        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
670        let mut buf = vec![0u8; sec.serialized_len()];
671        sec.serialize_into(&mut buf).unwrap();
672        let re = ProtectionMessageSection::parse(&buf).unwrap();
673        assert_eq!(sec, re);
674    }
675
676    #[test]
677    fn table_trait_constants() {
678        assert_eq!(TABLE_ID, 0x7B);
679        assert_eq!(PID, 0x0000);
680    }
681
682    #[test]
683    #[cfg(feature = "serde")]
684    fn serde_json_round_trip() {
685        let bytes = build_section(0x0042, 5, &auth_body());
686        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
687        let j = serde_json::to_string(&sec).unwrap();
688        // The borrowed `&[u8]` fields (reference/hash/extension/signature)
689        // cannot be JSON-deserialized zero-copy (serde_json renders them as
690        // number sequences, not borrowed byte arrays) — the crate-wide
691        // constraint that affects every borrowed-slice table (cf. mpe.rs).
692        // Exercise the derive through the WIRE form: a re-parse must serialize
693        // to byte-identical JSON, pinning the Serialize impl.
694        let reparsed = ProtectionMessageSection::parse(&bytes).unwrap();
695        assert_eq!(serde_json::to_string(&reparsed).unwrap(), j);
696        assert!(j.contains("\"signature_algorithm_identifier\":1"));
697    }
698
699    #[test]
700    fn parse_rejects_zero_section_length() {
701        let mut buf = vec![0u8; 64];
702        buf[0] = TABLE_ID;
703        buf[1] = 0xF0;
704        buf[2] = 0x00;
705        for b in &mut buf[3..] {
706            *b = 0xFF;
707        }
708        assert!(matches!(
709            ProtectionMessageSection::parse(&buf).unwrap_err(),
710            Error::SectionLengthOverflow { .. }
711        ));
712    }
713}