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