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