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 alloc::vec::Vec;
27use broadcast_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// No fixed PID: this table's PID is signalled (PMT / descriptors). A `PID`
32// constant of `0x0000` used to sit here, which is the real PAT PID, so a
33// caller filtering on it would pick up PAT traffic (audit r02-W15/O12).
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/// Reference type coding — ETSI TS 102 809 §9.4.3 Table 45.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize))]
45#[non_exhaustive]
46pub enum ReferenceType {
47    /// 0x0 — reserved for future use.
48    Reserved,
49    /// 0x1 — same ES (table_id of the payload section).
50    SameEs,
51    /// 0x2 — component tag ES (component_tag of the payload section).
52    ComponentTagEs,
53    /// 0x3..=0xF — reserved.
54    Unallocated(u8),
55}
56
57impl ReferenceType {
58    #[must_use]
59    /// Decode from the wire value.  Every value maps (lossless).
60    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    /// Encode to the wire value.  Inverse of `from_u8` / `from_u16`.
71    pub const 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    /// Human-readable spec display name.
82    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}
91broadcast_common::impl_spec_display!(ReferenceType, Unallocated);
92
93/// table_id(1) + section_length hi/lo(2) + extension(2) + version/cni(1)
94/// + section_number(1) + last_section_number(1) = 8-byte common header.
95const HEADER_LEN: usize = 8;
96/// `section_length` counts from just after the field (byte 3) to end of section.
97const SECTION_LENGTH_PREFIX: usize = 3;
98use super::CRC_LEN;
99
100/// Authentication-message fixed body bytes after the common header, before the
101/// hash loop: section_hash_algorithm_identifier(1) + section_hash_length(1)
102/// + signature_algorithm_identifier(1) + reserved(4)|section_hashes_loop_length(12)(2).
103const AUTH_FIXED_PREFIX: usize = 5;
104
105/// The 12-bit `section_hashes_loop_length` / `certificate_length` maximum
106/// (ETSI TS 103 192 §9.4.3/§9.4.4).
107const LOOP_LEN_MAX: usize = 0x0FFF;
108/// The 4-bit `reference_length` / `certificate_count` maximum.
109const NIBBLE_MAX: usize = 0x0F;
110/// The four `reserved = '1111'` bits preceding a 4-bit length/count field.
111/// The four `reserved = '1111'` bits preceding the 12-bit loop-length field
112/// (low nibble of the field's first wire byte).
113const LOOP_LEN_HI_MASK: u8 = 0x0F;
114
115/// One entry in the authentication message section-hash loop (§9.4.3 Table 42).
116///
117/// Each entry pairs a reference (locating the payload section the hash covers)
118/// with the truncated hash itself. `reference` length is the 4-bit
119/// `reference_length`; `hash` length is the section-wide `section_hash_length`.
120#[derive(Debug, Clone, PartialEq, Eq)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize))]
122#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
123pub struct SectionHashEntry<'a> {
124    /// 4-bit `reference_type` (§9.4.3 Table 45): 1 = same ES, 2 = component_tag ES.
125    pub reference_type: ReferenceType,
126    /// `reference_byte` field — its semantics depend on `reference_type`.
127    pub reference: &'a [u8],
128    /// The (possibly truncated) section hash, `section_hash_length` bytes.
129    pub hash: &'a [u8],
130}
131
132/// Discriminated protection-message body, selected by `table_id_extension`.
133#[derive(Debug, Clone, PartialEq, Eq)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize))]
135#[non_exhaustive]
136pub enum ProtectionMessageBody<'a> {
137    /// Authentication message (extension `0x0000..=0x00FF`; §9.4.3 Table 42).
138    AuthenticationMessage {
139        /// `section_hash_algorithm_identifier` (§9.4.3 Table 43): 0 = SHA-256, 1 = SHA-512.
140        section_hash_algorithm_identifier: u8,
141        /// `section_hash_length` — bytes per hash in each loop entry.
142        section_hash_length: u8,
143        /// `signature_algorithm_identifier` (§9.4.3 Table 44).
144        signature_algorithm_identifier: u8,
145        /// Section-hash loop entries in wire order.
146        hashes: Vec<SectionHashEntry<'a>>,
147        /// `extension_byte` payload (length-prefixed by `extension_bytes_length`).
148        extension_bytes: &'a [u8],
149        /// `signature_key_identifier_byte` payload (length-prefixed).
150        signature_key_identifier: &'a [u8],
151        /// Detached signature — runs from after the key identifier to the CRC.
152        signature: &'a [u8],
153        /// `reserved(4)` before `section_hashes_loop_length` (`1111`);
154        /// preserved from the wire.
155        #[cfg_attr(feature = "serde", serde(skip))]
156        reserved_section_hashes_loop_length: u8,
157    },
158    /// Certificate collection message (extension `0x0100`; §9.5.4.9 Table 51).
159    CertificateCollection {
160        /// DER-encoded DVBCertificate byte runs, one slice per `certificate_length` loop entry.
161        certificates: Vec<&'a [u8]>,
162        /// `reserved(4)` before `certificate_count` (`1111`); preserved from
163        /// the wire.
164        #[cfg_attr(feature = "serde", serde(skip))]
165        reserved_certificate_count: u8,
166        /// `reserved(4)` before each `certificate_length` (`1111`), parallel
167        /// to `certificates`; entries missing from the vector default to
168        /// all-ones on serialize. Preserved from the wire.
169        #[cfg_attr(feature = "serde", serde(skip))]
170        reserved_certificate_lengths: Vec<u8>,
171    },
172    /// Reserved extension (`0x0101..=0xFFFF`) — body preserved verbatim.
173    Raw(&'a [u8]),
174}
175
176impl<'a> ProtectionMessageBody<'a> {
177    /// A certificate-collection body with the spec `reserved` fill.
178    #[must_use]
179    pub fn certificate_collection(certificates: Vec<&'a [u8]>) -> Self {
180        Self::CertificateCollection {
181            certificates,
182            reserved_certificate_count: super::RESERVED_4,
183            reserved_certificate_lengths: Vec::new(),
184        }
185    }
186}
187
188/// Protection message section (TS 102 809 §9; Tables 42 / 51).
189///
190/// Typed fields cover the common section header; [`ProtectionMessageSection::body`]
191/// carries the typed, discriminated body.
192#[derive(Debug, Clone, PartialEq, Eq)]
193#[cfg_attr(feature = "serde", derive(serde::Serialize))]
194#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
195#[non_exhaustive]
196pub struct ProtectionMessageSection<'a> {
197    /// `table_id_extension` — `authentication_group_id` for authentication
198    /// messages, `trust_message_id` (0x0100) for certificate collections.
199    pub table_id_extension: u16,
200    /// 5-bit version_number.
201    pub version_number: u8,
202    /// current_next_indicator bit (spec mandates 1).
203    pub current_next_indicator: bool,
204    /// section_number.
205    pub section_number: u8,
206    /// last_section_number.
207    pub last_section_number: u8,
208    /// Discriminated body, selected by `table_id_extension`.
209    pub body: ProtectionMessageBody<'a>,
210    /// Header bit fields (`reserved_future_use`, `reserved(2)` x2; default
211    /// [`SectionBits::dvb`](super::SectionBits::dvb)).
212    #[cfg_attr(feature = "serde", serde(skip))]
213    pub bits: super::SectionBits,
214}
215
216impl<'a> ProtectionMessageSection<'a> {
217    /// Construct a protection message section with the spec `reserved` fill.
218    #[must_use]
219    pub fn new(
220        table_id_extension: u16,
221        version_number: u8,
222        current_next_indicator: bool,
223        section_number: u8,
224        last_section_number: u8,
225        body: ProtectionMessageBody<'a>,
226    ) -> Self {
227        Self {
228            table_id_extension,
229            version_number,
230            current_next_indicator,
231            section_number,
232            last_section_number,
233            body,
234            bits: super::SectionBits::dvb(),
235        }
236    }
237}
238
239impl<'a> Parse<'a> for ProtectionMessageSection<'a> {
240    type Error = crate::error::Error;
241    fn parse(bytes: &'a [u8]) -> Result<Self> {
242        let min_len = HEADER_LEN + CRC_LEN;
243        if bytes.len() < min_len {
244            return Err(Error::BufferTooShort {
245                need: min_len,
246                have: bytes.len(),
247                what: "ProtectionMessageSection",
248            });
249        }
250        if bytes[0] != TABLE_ID {
251            return Err(Error::UnexpectedTableId {
252                table_id: bytes[0],
253                what: "ProtectionMessageSection",
254                expected: &[TABLE_ID],
255            });
256        }
257        let section_length = super::section_length_of(bytes);
258        let total = super::check_section_length(
259            bytes.len(),
260            SECTION_LENGTH_PREFIX,
261            section_length,
262            HEADER_LEN + CRC_LEN,
263        )?;
264
265        let table_id_extension = crate::wire::be_u16(bytes, 3, "protection_message")?;
266        let version_number = super::version_number_of(bytes[5]);
267        let current_next_indicator = super::current_next_of(bytes[5]);
268        let section_number = bytes[6];
269        let last_section_number = bytes[7];
270
271        let body_bytes = &bytes[HEADER_LEN..total - CRC_LEN];
272        let body = match table_id_extension {
273            AUTH_EXTENSION_FIRST..=AUTH_EXTENSION_LAST => parse_authentication_message(body_bytes)?,
274            CERTIFICATE_COLLECTION_EXTENSION => parse_certificate_collection(body_bytes)?,
275            _ => ProtectionMessageBody::Raw(body_bytes),
276        };
277
278        Ok(ProtectionMessageSection {
279            table_id_extension,
280            version_number,
281            current_next_indicator,
282            section_number,
283            last_section_number,
284            body,
285            bits: super::SectionBits::parse(bytes[1], Some(bytes[5])),
286        })
287    }
288}
289
290/// Parse the authentication-message body (§9.4.3 Table 42, PDF pp. 70-71).
291fn parse_authentication_message(body: &[u8]) -> Result<ProtectionMessageBody<'_>> {
292    if body.len() < AUTH_FIXED_PREFIX {
293        return Err(Error::BufferTooShort {
294            need: AUTH_FIXED_PREFIX,
295            have: body.len(),
296            what: "ProtectionMessageSection::AuthenticationMessage",
297        });
298    }
299    let section_hash_algorithm_identifier = body[0];
300    let section_hash_length = body[1];
301    let signature_algorithm_identifier = body[2];
302    // bytes[3] high nibble = reserved; section_hashes_loop_length is 12 bits.
303    let section_hashes_loop_length = super::desc_loop_len_of(body[3], body[4]);
304
305    let loop_start = AUTH_FIXED_PREFIX;
306    let loop_end = loop_start + section_hashes_loop_length;
307    if loop_end > body.len() {
308        return Err(Error::SectionLengthOverflow {
309            declared: section_hashes_loop_length,
310            available: body.len() - loop_start,
311        });
312    }
313
314    let hash_len = section_hash_length as usize;
315    let mut hashes = Vec::new();
316    let mut pos = loop_start;
317    while pos < loop_end {
318        // reference_type(4) | reference_length(4)
319        let lead = body[pos];
320        let reference_type = ReferenceType::from_u8(lead >> 4);
321        let reference_length = (lead & LOOP_LEN_HI_MASK) as usize;
322        let ref_start = pos + 1;
323        let ref_end = ref_start + reference_length;
324        let hash_end = ref_end + hash_len;
325        if hash_end > loop_end {
326            return Err(Error::SectionLengthOverflow {
327                declared: reference_length + hash_len,
328                available: loop_end - ref_start,
329            });
330        }
331        hashes.push(SectionHashEntry {
332            reference_type,
333            reference: &body[ref_start..ref_end],
334            hash: &body[ref_end..hash_end],
335        });
336        pos = hash_end;
337    }
338
339    // extension_bytes_length(8) + extension bytes (§9.4.3 Table 42 tail).
340    if loop_end >= body.len() {
341        return Err(Error::BufferTooShort {
342            need: loop_end + 1,
343            have: body.len(),
344            what: "ProtectionMessageSection::extension_bytes_length",
345        });
346    }
347    let extension_bytes_length = body[loop_end] as usize;
348    let ext_start = loop_end + 1;
349    let ext_end = ext_start + extension_bytes_length;
350    if ext_end > body.len() {
351        return Err(Error::SectionLengthOverflow {
352            declared: extension_bytes_length,
353            available: body.len() - ext_start,
354        });
355    }
356
357    // signature_key_identifier_length(8) + key id bytes (Table 43 spillover, PDF p. 71).
358    if ext_end >= body.len() {
359        return Err(Error::BufferTooShort {
360            need: ext_end + 1,
361            have: body.len(),
362            what: "ProtectionMessageSection::signature_key_identifier_length",
363        });
364    }
365    let key_id_length = body[ext_end] as usize;
366    let key_start = ext_end + 1;
367    let key_end = key_start + key_id_length;
368    if key_end > body.len() {
369        return Err(Error::SectionLengthOverflow {
370            declared: key_id_length,
371            available: body.len() - key_start,
372        });
373    }
374
375    // signature_byte loop runs to the end of the body (i.e. up to the CRC_32).
376    let signature = &body[key_end..];
377
378    Ok(ProtectionMessageBody::AuthenticationMessage {
379        section_hash_algorithm_identifier,
380        section_hash_length,
381        signature_algorithm_identifier,
382        hashes,
383        extension_bytes: &body[ext_start..ext_end],
384        signature_key_identifier: &body[key_start..key_end],
385        signature,
386        reserved_section_hashes_loop_length: super::len_reserved_of(body[3]),
387    })
388}
389
390/// Parse the certificate-collection body (§9.5.4.9 Table 51, PDF p. 91).
391fn parse_certificate_collection(body: &[u8]) -> Result<ProtectionMessageBody<'_>> {
392    if body.is_empty() {
393        return Err(Error::BufferTooShort {
394            need: 1,
395            have: 0,
396            what: "ProtectionMessageSection::CertificateCollection",
397        });
398    }
399    // byte 0: reserved(4) | certificate_count(4)
400    let certificate_count = (body[0] & LOOP_LEN_HI_MASK) as usize;
401    let mut certificates = Vec::with_capacity(certificate_count);
402    let mut reserved_certificate_lengths = Vec::with_capacity(certificate_count);
403    let mut pos = 1;
404    for _ in 0..certificate_count {
405        if pos + 2 > body.len() {
406            return Err(Error::BufferTooShort {
407                need: pos + 2,
408                have: body.len(),
409                what: "ProtectionMessageSection::certificate_length",
410            });
411        }
412        // reserved(4) | certificate_length(12)
413        let certificate_length = super::desc_loop_len_of(body[pos], body[pos + 1]);
414        let cert_start = pos + 2;
415        let cert_end = cert_start + certificate_length;
416        if cert_end > body.len() {
417            return Err(Error::SectionLengthOverflow {
418                declared: certificate_length,
419                available: body.len() - cert_start,
420            });
421        }
422        reserved_certificate_lengths.push(super::len_reserved_of(body[pos]));
423        certificates.push(&body[cert_start..cert_end]);
424        pos = cert_end;
425    }
426    // Reject trailing bytes after the last certificate (r02-W18): silently
427    // dropping them would break the parse -> serialize byte-identity
428    // invariant.
429    if pos != body.len() {
430        return Err(Error::BufferTooShort {
431            need: body.len() - pos,
432            have: 0,
433            what: "ProtectionMessageSection::CertificateCollection trailing bytes",
434        });
435    }
436    Ok(ProtectionMessageBody::CertificateCollection {
437        certificates,
438        reserved_certificate_count: super::len_reserved_of(body[0]),
439        reserved_certificate_lengths,
440    })
441}
442
443impl ProtectionMessageBody<'_> {
444    /// Serialized length of the body (excluding common header and CRC).
445    fn body_len(&self) -> usize {
446        match self {
447            ProtectionMessageBody::AuthenticationMessage {
448                hashes,
449                extension_bytes,
450                signature_key_identifier,
451                signature,
452                ..
453            } => {
454                let loop_bytes: usize = hashes
455                    .iter()
456                    .map(|h| 1 + h.reference.len() + h.hash.len())
457                    .sum();
458                AUTH_FIXED_PREFIX
459                    + loop_bytes
460                    + 1
461                    + extension_bytes.len()
462                    + 1
463                    + signature_key_identifier.len()
464                    + signature.len()
465            }
466            ProtectionMessageBody::CertificateCollection { certificates, .. } => {
467                1 + certificates.iter().map(|c| 2 + c.len()).sum::<usize>()
468            }
469            ProtectionMessageBody::Raw(raw) => raw.len(),
470        }
471    }
472
473    /// Write the body into `buf`, returning the number of bytes written.
474    fn write_into(&self, buf: &mut [u8]) -> Result<usize> {
475        match self {
476            ProtectionMessageBody::AuthenticationMessage {
477                section_hash_algorithm_identifier,
478                section_hash_length,
479                signature_algorithm_identifier,
480                hashes,
481                extension_bytes,
482                signature_key_identifier,
483                signature,
484                reserved_section_hashes_loop_length,
485            } => {
486                buf[0] = *section_hash_algorithm_identifier;
487                buf[1] = *section_hash_length;
488                buf[2] = *signature_algorithm_identifier;
489                let loop_bytes: usize = hashes
490                    .iter()
491                    .map(|h| 1 + h.reference.len() + h.hash.len())
492                    .sum();
493                if loop_bytes > LOOP_LEN_MAX {
494                    return Err(Error::SectionLengthOverflow {
495                        declared: loop_bytes,
496                        available: LOOP_LEN_MAX,
497                    });
498                }
499                if extension_bytes.len() > u8::MAX as usize {
500                    return Err(Error::SectionLengthOverflow {
501                        declared: extension_bytes.len(),
502                        available: u8::MAX as usize,
503                    });
504                }
505                if signature_key_identifier.len() > u8::MAX as usize {
506                    return Err(Error::SectionLengthOverflow {
507                        declared: signature_key_identifier.len(),
508                        available: u8::MAX as usize,
509                    });
510                }
511                // reserved(4) emitted 1s | section_hashes_loop_length(12).
512                super::write_desc_loop_len_r(
513                    &mut buf[3..5],
514                    loop_bytes,
515                    *reserved_section_hashes_loop_length,
516                )?;
517                let mut pos = AUTH_FIXED_PREFIX;
518                for h in hashes {
519                    if h.reference.len() > NIBBLE_MAX {
520                        return Err(Error::SectionLengthOverflow {
521                            declared: h.reference.len(),
522                            available: NIBBLE_MAX,
523                        });
524                    }
525                    // Every hash entry is exactly `section_hash_length`
526                    // bytes on the wire (the parser slices it that way
527                    // unconditionally) — a different length here would
528                    // misframe every later entry (r02-W18).
529                    if h.hash.len() != *section_hash_length as usize {
530                        return Err(Error::ValueOutOfRange {
531                            field: "Hash.hash",
532                            reason: "length must equal section_hash_length",
533                        });
534                    }
535                    buf[pos] = (broadcast_common::len::fit_mask(
536                        h.reference_type.to_u8(),
537                        0x0F,
538                        "reference_type",
539                    )? << 4)
540                        | broadcast_common::len::fit_bits(
541                            h.reference.len() as u64,
542                            4,
543                            "h.reference",
544                        )? as u8;
545                    pos += 1;
546                    buf[pos..pos + h.reference.len()].copy_from_slice(h.reference);
547                    pos += h.reference.len();
548                    buf[pos..pos + h.hash.len()].copy_from_slice(h.hash);
549                    pos += h.hash.len();
550                }
551                buf[pos] = broadcast_common::len::fit_u8(extension_bytes.len(), "extension_bytes")?;
552                pos += 1;
553                buf[pos..pos + extension_bytes.len()].copy_from_slice(extension_bytes);
554                pos += extension_bytes.len();
555                buf[pos] = broadcast_common::len::fit_u8(
556                    signature_key_identifier.len(),
557                    "signature_key_identifier",
558                )?;
559                pos += 1;
560                buf[pos..pos + signature_key_identifier.len()]
561                    .copy_from_slice(signature_key_identifier);
562                pos += signature_key_identifier.len();
563                buf[pos..pos + signature.len()].copy_from_slice(signature);
564                pos += signature.len();
565                Ok(pos)
566            }
567            ProtectionMessageBody::CertificateCollection {
568                certificates,
569                reserved_certificate_count,
570                reserved_certificate_lengths,
571            } => {
572                if certificates.len() > NIBBLE_MAX {
573                    return Err(Error::SectionLengthOverflow {
574                        declared: certificates.len(),
575                        available: NIBBLE_MAX,
576                    });
577                }
578                // reserved(4) emitted 1s | certificate_count(4).
579                buf[0] = (broadcast_common::len::fit_mask(
580                    *reserved_certificate_count,
581                    super::RESERVED_4,
582                    "reserved(4)",
583                )? << 4)
584                    | broadcast_common::len::fit_bits(certificates.len() as u64, 4, "certificates")?
585                        as u8;
586                let mut pos = 1;
587                for (i, c) in certificates.iter().enumerate() {
588                    if c.len() > LOOP_LEN_MAX {
589                        return Err(Error::SectionLengthOverflow {
590                            declared: c.len(),
591                            available: LOOP_LEN_MAX,
592                        });
593                    }
594                    // reserved(4) emitted 1s | certificate_length(12).
595                    super::write_desc_loop_len_r(
596                        &mut buf[pos..pos + 2],
597                        c.len(),
598                        reserved_certificate_lengths
599                            .get(i)
600                            .copied()
601                            .unwrap_or(super::RESERVED_4),
602                    )?;
603                    pos += 2;
604                    buf[pos..pos + c.len()].copy_from_slice(c);
605                    pos += c.len();
606                }
607                Ok(pos)
608            }
609            ProtectionMessageBody::Raw(raw) => {
610                buf[..raw.len()].copy_from_slice(raw);
611                Ok(raw.len())
612            }
613        }
614    }
615}
616
617impl Serialize for ProtectionMessageSection<'_> {
618    type Error = crate::error::Error;
619    fn serialized_len(&self) -> usize {
620        HEADER_LEN + self.body.body_len() + CRC_LEN
621    }
622    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
623        let len = self.serialized_len();
624        if buf.len() < len {
625            return Err(Error::OutputBufferTooSmall {
626                need: len,
627                have: buf.len(),
628            });
629        }
630        buf[0] = TABLE_ID;
631        buf[1] = self.bits.b1()?;
632        super::write_section_length(buf, len - SECTION_LENGTH_PREFIX)?;
633        buf[3..5].copy_from_slice(&self.table_id_extension.to_be_bytes());
634        // reserved(2)=11, version_number(5), current_next_indicator(1).
635        buf[5] = self
636            .bits
637            .b5(self.version_number, self.current_next_indicator)?;
638        buf[6] = self.section_number;
639        buf[7] = self.last_section_number;
640        let body_written = self.body.write_into(&mut buf[HEADER_LEN..])?;
641        let body_end = HEADER_LEN + body_written;
642        super::write_crc(buf, body_end);
643        Ok(len)
644    }
645}
646impl<'a> crate::traits::TableDef<'a> for ProtectionMessageSection<'a> {
647    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
648    const NAME: &'static str = "PROTECTION_MESSAGE";
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654
655    /// Wrap a body in the 8-byte common header + placeholder CRC.
656    fn build_section(extension: u16, version: u8, body: &[u8]) -> Vec<u8> {
657        let section_length = (HEADER_LEN - SECTION_LENGTH_PREFIX + body.len() + CRC_LEN) as u16;
658        let mut v = vec![
659            TABLE_ID,
660            super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F),
661            (section_length & 0xFF) as u8,
662            (extension >> 8) as u8,
663            (extension & 0xFF) as u8,
664            0xC0 | (version << 1) | 0x01,
665            0x00,
666            0x00,
667        ];
668        v.extend_from_slice(body);
669        v.extend_from_slice(&[0, 0, 0, 0]);
670        v
671    }
672
673    /// Build an authentication-message body: one hash entry + ext + key id + signature.
674    fn auth_body() -> Vec<u8> {
675        let reference = [0x01]; // reference_type 1 => table_id of payload section
676        let hash = [0xAA, 0xBB, 0xCC, 0xDD]; // section_hash_length = 4
677        let mut hashes_loop = vec![(1u8 << 4) | (reference.len() as u8)]; // reference_type 1, length 1
678        hashes_loop.extend_from_slice(&reference);
679        hashes_loop.extend_from_slice(&hash);
680        let loop_len = hashes_loop.len();
681
682        let mut b = vec![
683            0x00,                                  // section_hash_algorithm_identifier = SHA-256
684            hash.len() as u8,                      // section_hash_length = 4
685            0x01,                                  // signature_algorithm_identifier = ed25519
686            0xF0 | ((loop_len >> 8) as u8 & 0x0F), // reserved | loop_length hi
687            (loop_len & 0xFF) as u8,               // loop_length lo
688        ];
689        b.extend_from_slice(&hashes_loop);
690        // extension_bytes_length + bytes
691        b.push(2);
692        b.extend_from_slice(&[0xDE, 0xAD]);
693        // signature_key_identifier_length + bytes
694        b.push(3);
695        b.extend_from_slice(&[0x11, 0x22, 0x33]);
696        // signature (runs to CRC)
697        b.extend_from_slice(&[0x90, 0x91, 0x92, 0x93, 0x94, 0x95]);
698        b
699    }
700
701    /// Build a certificate-collection body with two certificate slices.
702    fn cert_body() -> Vec<u8> {
703        let c0: &[u8] = &[0x30, 0x82, 0x01, 0x02];
704        let c1: &[u8] = &[0xAB, 0xCD];
705        let mut b = vec![0xF0 | 0x02]; // reserved | certificate_count = 2
706        b.push(0xF0 | ((c0.len() >> 8) as u8 & 0x0F));
707        b.push((c0.len() & 0xFF) as u8);
708        b.extend_from_slice(c0);
709        b.push(0xF0 | ((c1.len() >> 8) as u8 & 0x0F));
710        b.push((c1.len() & 0xFF) as u8);
711        b.extend_from_slice(c1);
712        b
713    }
714
715    #[test]
716    fn parse_authentication_message() {
717        let bytes = build_section(0x0042, 5, &auth_body());
718        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
719        assert_eq!(sec.table_id_extension, 0x0042);
720        assert_eq!(sec.version_number, 5);
721        assert!(sec.current_next_indicator);
722        match sec.body {
723            ProtectionMessageBody::AuthenticationMessage {
724                section_hash_algorithm_identifier,
725                section_hash_length,
726                signature_algorithm_identifier,
727                hashes,
728                extension_bytes,
729                signature_key_identifier,
730                signature,
731                ..
732            } => {
733                assert_eq!(section_hash_algorithm_identifier, 0x00);
734                assert_eq!(section_hash_length, 4);
735                assert_eq!(signature_algorithm_identifier, 0x01);
736                assert_eq!(hashes.len(), 1);
737                assert_eq!(hashes[0].reference_type, ReferenceType::SameEs);
738                assert_eq!(hashes[0].reference, &[0x01]);
739                assert_eq!(hashes[0].hash, &[0xAA, 0xBB, 0xCC, 0xDD]);
740                assert_eq!(extension_bytes, &[0xDE, 0xAD]);
741                assert_eq!(signature_key_identifier, &[0x11, 0x22, 0x33]);
742                assert_eq!(signature, &[0x90, 0x91, 0x92, 0x93, 0x94, 0x95]);
743            }
744            other => panic!("expected AuthenticationMessage, got {other:?}"),
745        }
746    }
747
748    #[test]
749    fn parse_certificate_collection() {
750        let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 0, &cert_body());
751        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
752        assert_eq!(sec.table_id_extension, 0x0100);
753        match sec.body {
754            ProtectionMessageBody::CertificateCollection { certificates, .. } => {
755                assert_eq!(certificates.len(), 2);
756                assert_eq!(certificates[0], &[0x30, 0x82, 0x01, 0x02]);
757                assert_eq!(certificates[1], &[0xAB, 0xCD]);
758            }
759            other => panic!("expected CertificateCollection, got {other:?}"),
760        }
761    }
762
763    #[test]
764    fn reserved_extension_kept_raw() {
765        let raw = [0x01, 0x02, 0x03, 0x04];
766        let bytes = build_section(0x0200, 0, &raw);
767        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
768        assert!(matches!(sec.body, ProtectionMessageBody::Raw(b) if b == raw));
769    }
770
771    #[test]
772    fn parse_rejects_wrong_tag() {
773        let mut bytes = build_section(0x0000, 0, &auth_body());
774        bytes[0] = 0x4D;
775        assert!(matches!(
776            ProtectionMessageSection::parse(&bytes).unwrap_err(),
777            Error::UnexpectedTableId { table_id: 0x4D, .. }
778        ));
779    }
780
781    #[test]
782    fn rejects_short_buffer() {
783        assert!(matches!(
784            ProtectionMessageSection::parse(&[0x7B, 0xB0]).unwrap_err(),
785            Error::BufferTooShort {
786                what: "ProtectionMessageSection",
787                ..
788            }
789        ));
790    }
791
792    #[test]
793    fn auth_loop_overflow_rejected() {
794        let mut body = vec![0x00, 0x04, 0x01, 0xF0, 0xFF];
795        body.extend_from_slice(&[0x00]);
796        let bytes = build_section(0x0000, 0, &body);
797        assert!(matches!(
798            ProtectionMessageSection::parse(&bytes).unwrap_err(),
799            Error::SectionLengthOverflow { .. }
800        ));
801    }
802
803    #[test]
804    fn cert_length_overflow_rejected() {
805        let body = vec![0xF0 | 0x01, 0x00, 0x10, 0x01];
806        let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 0, &body);
807        assert!(matches!(
808            ProtectionMessageSection::parse(&bytes).unwrap_err(),
809            Error::SectionLengthOverflow { .. }
810        ));
811    }
812
813    #[test]
814    fn round_trip_authentication_message() {
815        let bytes = build_section(0x0042, 7, &auth_body());
816        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
817        let mut buf = vec![0u8; sec.serialized_len()];
818        sec.serialize_into(&mut buf).unwrap();
819        let re = ProtectionMessageSection::parse(&buf).unwrap();
820        assert_eq!(sec, re);
821    }
822
823    #[test]
824    fn round_trip_certificate_collection() {
825        let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 3, &cert_body());
826        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
827        let mut buf = vec![0u8; sec.serialized_len()];
828        sec.serialize_into(&mut buf).unwrap();
829        let re = ProtectionMessageSection::parse(&buf).unwrap();
830        assert_eq!(sec, re);
831    }
832
833    /// Regression for r02-W18: a `Hash.hash` whose length disagrees with
834    /// `section_hash_length` must be rejected, not written verbatim (which
835    /// would misframe every later hash entry on re-parse).
836    #[test]
837    fn serialize_rejects_hash_length_disagreeing_with_section_hash_length() {
838        let bytes = build_section(0x0042, 5, &auth_body());
839        let mut sec = ProtectionMessageSection::parse(&bytes).unwrap();
840        match &mut sec.body {
841            ProtectionMessageBody::AuthenticationMessage { hashes, .. } => {
842                hashes[0].hash = &[0xAA, 0xBB]; // section_hash_length is 4
843            }
844            other => panic!("expected AuthenticationMessage, got {other:?}"),
845        }
846        let mut buf = vec![0u8; sec.serialized_len()];
847        assert!(matches!(
848            sec.serialize_into(&mut buf).unwrap_err(),
849            Error::ValueOutOfRange { .. }
850        ));
851    }
852
853    /// Regression for r02-W18: bytes left over after the last certificate in
854    /// a `CertificateCollection` body must be rejected, not silently
855    /// dropped.
856    #[test]
857    fn parse_certificate_collection_rejects_trailing_bytes() {
858        let mut body = cert_body();
859        body.push(0xFF); // one byte past the last certificate
860        let bytes = build_section(CERTIFICATE_COLLECTION_EXTENSION, 0, &body);
861        assert!(matches!(
862            ProtectionMessageSection::parse(&bytes).unwrap_err(),
863            Error::BufferTooShort { .. }
864        ));
865    }
866
867    #[test]
868    fn round_trip_raw_reserved() {
869        let bytes = build_section(0xABCD, 1, &[0xDE, 0xAD, 0xBE, 0xEF]);
870        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
871        let mut buf = vec![0u8; sec.serialized_len()];
872        sec.serialize_into(&mut buf).unwrap();
873        let re = ProtectionMessageSection::parse(&buf).unwrap();
874        assert_eq!(sec, re);
875    }
876
877    #[test]
878    fn table_trait_constants() {
879        assert_eq!(TABLE_ID, 0x7B);
880    }
881
882    #[test]
883    #[cfg(feature = "serde")]
884    fn serde_json_round_trip() {
885        let bytes = build_section(0x0042, 5, &auth_body());
886        let sec = ProtectionMessageSection::parse(&bytes).unwrap();
887        let j = serde_json::to_string(&sec).unwrap();
888        let reparsed = ProtectionMessageSection::parse(&bytes).unwrap();
889        assert_eq!(serde_json::to_string(&reparsed).unwrap(), j);
890        assert!(j.contains("\"signature_algorithm_identifier\":1"));
891    }
892
893    #[test]
894    fn parse_rejects_zero_section_length() {
895        let mut buf = vec![0u8; 64];
896        buf[0] = TABLE_ID;
897        buf[1] = 0xF0;
898        buf[2] = 0x00;
899        for b in &mut buf[3..] {
900            *b = 0xFF;
901        }
902        assert!(matches!(
903            ProtectionMessageSection::parse(&buf).unwrap_err(),
904            Error::SectionLengthOverflow { .. }
905        ));
906    }
907
908    /// section_length is 12 bits (max 4095 = HEADER_LEN(8) -
909    /// SECTION_LENGTH_PREFIX(3) + body_len + CRC_LEN(4), so 4086 is the
910    /// largest Raw body that fits).
911    #[test]
912    fn serialize_rejects_raw_body_exceeding_section_length() {
913        let raw = vec![0xAAu8; 4087];
914        let sec =
915            ProtectionMessageSection::new(0x0200, 0, true, 0, 0, ProtectionMessageBody::Raw(&raw));
916        let mut buf = vec![0u8; sec.serialized_len()];
917        assert!(matches!(
918            sec.serialize_into(&mut buf).unwrap_err(),
919            Error::FieldOverflow(_)
920        ));
921    }
922
923    #[test]
924    fn serialize_accepts_raw_body_at_section_length_boundary() {
925        let raw = vec![0xAAu8; 4086];
926        let sec =
927            ProtectionMessageSection::new(0x0200, 0, true, 0, 0, ProtectionMessageBody::Raw(&raw));
928        let mut buf = vec![0u8; sec.serialized_len()];
929        sec.serialize_into(&mut buf).expect("boundary fits");
930        let reparsed = ProtectionMessageSection::parse(&buf).expect("reparse");
931        assert_eq!(sec, reparsed);
932    }
933
934    #[test]
935    fn reference_type_full_range_round_trip() {
936        for v in 0u8..=0x0F {
937            let rt = ReferenceType::from_u8(v);
938            assert_eq!(
939                rt.to_u8(),
940                v,
941                "ReferenceType round-trip failed for {v:#04x}"
942            );
943        }
944    }
945}