Skip to main content

mpeg_ts/
owned.rs

1//! Owned 188-byte TS packet with pre-parsed header fields.
2//!
3//! [`OwnedTsPacket`] complements the zero-copy [`crate::ts::TsPacket`] (which holds
4//! a borrowed `&[u8; 188]`) with an **owned** `[u8; 188]` suitable for queuing,
5//! cloning, and in-place mutation — e.g. for mux pipelines that must rewrite the
6//! continuity counter or splice in a new payload.
7//!
8//! Header parsing delegates to [`crate::ts::TsHeader::parse`]; no bit-twiddling
9//! is duplicated here.
10
11use crate::error::{Error, Result};
12use crate::pid::Pid;
13use crate::ts::{
14    ADAPTATION_FLAG, AF_PCR_FLAG, AdaptationField, AdaptationFieldControl, CC_MASK, PCR_FIELD_LEN,
15    Pcr, ScramblingControl, TS_PACKET_SIZE, TS_SYNC_BYTE, TsHeader,
16};
17
18/// The 13-bit PID value used for null packets — `0x1FFF`
19/// (ISO/IEC 13818-1 §2.4.3.3, Table 2-3).
20const NULL_PID: Pid = crate::pid::well_known::NULL;
21
22/// Owned 188-byte TS packet with pre-parsed header fields.
23///
24/// The raw bytes are stored privately; the parsed flags (`pid`, `pusi`, etc.)
25/// are pre-extracted at construction time so hot paths avoid repeated byte
26/// masking. The fields are **private** (audit r01-W8, #1074): a public copy
27/// of a header field that a caller could assign would silently drift from
28/// the bytes that [`raw`](Self::raw) serialises. Read them through the
29/// getters; the only mutation entry points are the associated functions that
30/// edit a raw `[u8; 188]` (`set_pcr`, `set_continuity_counter`) followed by a
31/// re-[`parse`](Self::parse).
32///
33/// # Payload access
34///
35/// Use [`payload`](Self::payload) / [`payload_mut`](Self::payload_mut) to obtain
36/// a slice that correctly skips the 4-byte header **and** any adaptation field.
37///
38/// # Building packets
39///
40/// [`serialize_with_payload`](Self::serialize_with_payload) constructs a plain
41/// payload-only packet (no adaptation field) filled with 0xFF stuffing.
42#[derive(Clone, Debug)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44pub struct OwnedTsPacket {
45    /// The raw 188 bytes (serialized as a byte sequence).
46    #[cfg_attr(feature = "serde", serde(serialize_with = "serialize_raw_bytes"))]
47    raw: [u8; TS_PACKET_SIZE],
48    /// 13-bit PID extracted from bytes 1–2.
49    pid: Pid,
50    /// Payload Unit Start Indicator (byte 1 bit 6).
51    pusi: bool,
52    /// Adaptation field present flag (byte 3 bit 5).
53    has_adaptation: bool,
54    /// Payload present flag (byte 3 bit 4).
55    has_payload: bool,
56    /// Transport Error Indicator (byte 1 bit 7).
57    tei: bool,
58    /// 2-bit transport_scrambling_control (byte 3 bits 7–6).
59    scrambling: u8,
60    /// 4-bit continuity_counter (byte 3 bits 3–0).
61    continuity_counter: u8,
62    /// Discontinuity flag: `true` if the adaptation-field `discontinuity_indicator`
63    /// was set in the source packet, or if the caller marks this as a
64    /// continuity-counter discontinuity boundary. Defaults to `false` on parse.
65    pub discontinuity: bool,
66}
67
68/// Serialize a `[u8; N]` as a variable-length byte sequence so serde's
69/// blanket impls (only up to `[u8; 32]`) are not required.
70#[cfg(feature = "serde")]
71fn serialize_raw_bytes<S: serde::Serializer>(
72    bytes: &[u8; TS_PACKET_SIZE],
73    s: S,
74) -> core::result::Result<S::Ok, S::Error> {
75    use serde::ser::SerializeSeq;
76    let mut seq = s.serialize_seq(Some(bytes.len()))?;
77    for b in bytes {
78        seq.serialize_element(b)?;
79    }
80    seq.end()
81}
82
83impl OwnedTsPacket {
84    /// The raw 188 bytes.
85    pub fn raw(&self) -> &[u8; TS_PACKET_SIZE] {
86        &self.raw
87    }
88
89    /// 13-bit PID (bytes 1–2).
90    pub fn pid(&self) -> Pid {
91        self.pid
92    }
93
94    /// Payload Unit Start Indicator (byte 1 bit 6).
95    pub fn pusi(&self) -> bool {
96        self.pusi
97    }
98
99    /// Adaptation field present flag (byte 3 bit 5).
100    pub fn has_adaptation(&self) -> bool {
101        self.has_adaptation
102    }
103
104    /// Payload present flag (byte 3 bit 4).
105    pub fn has_payload(&self) -> bool {
106        self.has_payload
107    }
108
109    /// Transport Error Indicator (byte 1 bit 7).
110    pub fn tei(&self) -> bool {
111        self.tei
112    }
113
114    /// Raw 2-bit `transport_scrambling_control` (byte 3 bits 7–6); see
115    /// [`scrambling_control`](Self::scrambling_control) for the typed view.
116    pub fn scrambling(&self) -> u8 {
117        self.scrambling
118    }
119
120    /// 4-bit continuity_counter (byte 3 bits 3–0).
121    pub fn continuity_counter(&self) -> u8 {
122        self.continuity_counter
123    }
124
125    /// Parse a 188-byte owned TS packet.
126    ///
127    /// Returns [`Error::InvalidSync`] if `raw[0] != 0x47`.
128    /// Header bit-parsing is delegated to [`TsHeader::parse`].
129    /// The `discontinuity` field defaults to `false`; set it manually if needed.
130    pub fn parse(raw: [u8; TS_PACKET_SIZE]) -> Result<Self> {
131        if raw[0] != TS_SYNC_BYTE {
132            return Err(Error::InvalidSync(broadcast_common::error::InvalidSync {
133                what: "TS sync byte",
134                expected: u32::from(TS_SYNC_BYTE),
135                found: u32::from(raw[0]),
136            }));
137        }
138        let hdr = TsHeader::parse(&raw[..4])?;
139        Ok(Self {
140            raw,
141            pid: hdr.pid,
142            pusi: hdr.pusi,
143            has_adaptation: hdr.has_adaptation,
144            has_payload: hdr.has_payload,
145            tei: hdr.tei,
146            scrambling: hdr.scrambling,
147            continuity_counter: hdr.continuity_counter,
148            discontinuity: false,
149        })
150    }
151
152    /// Typed view of the 2-bit `transport_scrambling_control` field.
153    ///
154    /// See [`ScramblingControl`] for the spec citation (H.222.0 Table 2-4 +
155    /// ETSI TS 100 289 §5.1 Table 1).
156    pub fn scrambling_control(&self) -> ScramblingControl {
157        ScramblingControl::from_bits(self.scrambling)
158    }
159
160    /// Typed view of the `adaptation_field_control` 2-bit field, derived from the
161    /// stored `has_adaptation`/`has_payload` booleans.
162    ///
163    /// See [`AdaptationFieldControl`] for the spec citation (H.222.0 Table 2-5).
164    pub fn adaptation_field_control(&self) -> AdaptationFieldControl {
165        AdaptationFieldControl::from_flags(self.has_adaptation, self.has_payload)
166    }
167
168    /// Return the payload bytes (after the 4-byte header and any adaptation field).
169    ///
170    /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
171    /// adaptation field consumed all remaining bytes.
172    pub fn payload(&self) -> Option<&[u8]> {
173        if !self.has_payload {
174            return None;
175        }
176        let offset = self.payload_offset();
177        if offset < TS_PACKET_SIZE {
178            Some(&self.raw[offset..])
179        } else {
180            None
181        }
182    }
183
184    /// Return a mutable slice of the payload bytes.
185    ///
186    /// Returns `None` when [`has_payload`](Self::has_payload) is `false` or the
187    /// adaptation field consumed all remaining bytes.
188    pub fn payload_mut(&mut self) -> Option<&mut [u8]> {
189        if !self.has_payload {
190            return None;
191        }
192        let offset = self.payload_offset();
193        if offset < TS_PACKET_SIZE {
194            Some(&mut self.raw[offset..])
195        } else {
196            None
197        }
198    }
199
200    /// Compute the byte offset of the first payload byte inside `raw`.
201    ///
202    /// The 4-byte header is always present; if `has_adaptation` is set, the next
203    /// byte is the adaptation-field length, and the payload starts after those
204    /// `1 + af_len` bytes.
205    #[inline]
206    fn payload_offset(&self) -> usize {
207        let mut offset = 4;
208        if self.has_adaptation {
209            // raw[4] is the adaptation_field_length byte
210            let af_len = self.raw[4] as usize;
211            offset += 1 + af_len;
212        }
213        offset
214    }
215
216    /// Maximum payload bytes a plain (no-adaptation-field) TS packet can carry:
217    /// 188 bytes minus the 4-byte header.
218    pub const MAX_PAYLOAD_LEN: usize = TS_PACKET_SIZE - 4;
219
220    /// Build a 188-byte payload-only TS packet (no adaptation field).
221    ///
222    /// The packet is initialised to `0xFF` (MPEG-TS stuffing), the 4-byte header
223    /// is written via [`TsHeader::serialize_into`], then `payload` is copied
224    /// starting at byte 4. Any unfilled bytes remain `0xFF`.
225    ///
226    /// Returns [`Error::PayloadTooLarge`] if `payload` is longer than
227    /// [`MAX_PAYLOAD_LEN`](Self::MAX_PAYLOAD_LEN) (184 bytes) — the fixed
228    /// per-packet payload capacity would previously be silently truncated
229    /// (#1129).
230    ///
231    /// # Panics
232    ///
233    /// Never panics — serializing a 4-byte header into a 188-byte buffer cannot
234    /// fail.
235    pub fn serialize_with_payload(
236        pid: Pid,
237        pusi: bool,
238        cc: u8,
239        payload: &[u8],
240    ) -> Result<[u8; TS_PACKET_SIZE]> {
241        if payload.len() > Self::MAX_PAYLOAD_LEN {
242            return Err(Error::PayloadTooLarge {
243                len: payload.len(),
244                max: Self::MAX_PAYLOAD_LEN,
245            });
246        }
247        let mut pkt = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
248        let hdr = TsHeader {
249            tei: false,
250            pusi,
251            pid,
252            scrambling: 0,
253            has_adaptation: false,
254            has_payload: true,
255            continuity_counter: cc & CC_MASK,
256        };
257        // Cannot fail: buf is 188 bytes, need 4.
258        hdr.serialize_into(&mut pkt)
259            .expect("serialize TsHeader into 188-byte buf");
260        pkt[4..4 + payload.len()].copy_from_slice(payload);
261        Ok(pkt)
262    }
263
264    /// Build a 188-byte null packet (PID `0x1FFF`) with `0xFF`-stuffed payload.
265    ///
266    /// Null packets carry PID `0x1FFF` and have no meaningful payload
267    /// (ISO/IEC 13818-1 §2.4.1). The continuity counter `cc` is masked to 4
268    /// bits. Transport scrambling is `00` (not scrambled).
269    ///
270    /// # Example
271    ///
272    /// ```
273    /// use mpeg_ts::OwnedTsPacket;
274    /// use mpeg_ts::ts::{TsPacket, TS_PACKET_SIZE};
275    /// let raw = OwnedTsPacket::null_packet(3);
276    /// assert_eq!(raw.len(), TS_PACKET_SIZE);
277    /// let pkt = TsPacket::parse(&raw).unwrap();
278    /// assert_eq!(pkt.header.pid, mpeg_ts::pid::well_known::NULL);
279    /// assert_eq!(pkt.header.continuity_counter, 3);
280    /// ```
281    #[must_use]
282    pub fn null_packet(cc: u8) -> [u8; TS_PACKET_SIZE] {
283        // Cannot fail: an empty payload never exceeds MAX_PAYLOAD_LEN.
284        Self::serialize_with_payload(NULL_PID, false, cc, &[])
285            .expect("empty payload always fits a TS packet")
286    }
287
288    /// Overwrite the `continuity_counter` field in `packet` without re-parsing.
289    ///
290    /// Writes the low 4 bits of `cc` into byte 3 bits `[3:0]` in place
291    /// (ISO/IEC 13818-1 §2.4.3.3). The other bits of byte 3 are preserved.
292    ///
293    /// # Example
294    ///
295    /// ```
296    /// use mpeg_ts::OwnedTsPacket;
297    /// use mpeg_ts::pid::Pid;
298    /// use mpeg_ts::ts::TsPacket;
299    /// let mut raw = OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), false, 0, &[]).unwrap();
300    /// OwnedTsPacket::set_continuity_counter(&mut raw, 7);
301    /// let pkt = TsPacket::parse(&raw).unwrap();
302    /// assert_eq!(pkt.header.continuity_counter, 7);
303    /// ```
304    pub fn set_continuity_counter(packet: &mut [u8; TS_PACKET_SIZE], cc: u8) {
305        // Byte 3 bits [3:0] = continuity_counter (ISO/IEC 13818-1 §2.4.3.3).
306        packet[3] = (packet[3] & !CC_MASK) | (cc & CC_MASK);
307    }
308
309    /// Overwrite the PCR value in an existing adaptation field in `packet`.
310    ///
311    /// The packet must already have `adaptation_field_control` with adaptation
312    /// present (`has_adaptation == true`) and the PCR flag set in the adaptation
313    /// field flags byte. This function locates the 6-byte PCR field and
314    /// overwrites it with the encoding of `pcr`.
315    ///
316    /// # Errors
317    ///
318    /// - [`Error::BufferTooShort`] — the adaptation field does not contain a
319    ///   PCR slot (no adaptation field, zero-length field, or PCR flag not set).
320    ///
321    /// # Example
322    ///
323    /// ```
324    /// use mpeg_ts::ts::{AdaptationField, AF_PCR_FLAG, Pcr, TsPacket,
325    ///                    TS_PACKET_SIZE, TS_SYNC_BYTE,
326    ///                    ADAPTATION_FLAG, PAYLOAD_FLAG};
327    /// use mpeg_ts::OwnedTsPacket;
328    ///
329    /// // Build a packet that has a PCR slot (adaptation_field_length = 7).
330    /// let mut raw = [0xAAu8; TS_PACKET_SIZE];
331    /// raw[0] = TS_SYNC_BYTE;
332    /// raw[1] = 0x00; raw[2] = 0x64; // PID = 100
333    /// raw[3] = ADAPTATION_FLAG | PAYLOAD_FLAG;
334    /// raw[4] = 7;  // adaptation_field_length
335    /// raw[5] = AF_PCR_FLAG;
336    /// raw[6..12].copy_from_slice(&[0u8; 6]);
337    ///
338    /// let new_pcr = Pcr { base: 10_000, extension: 0 };
339    /// OwnedTsPacket::set_pcr(&mut raw, new_pcr).unwrap();
340    ///
341    /// let pkt = TsPacket::parse(&raw).unwrap();
342    /// let af = pkt.adaptation_field().unwrap().unwrap();
343    /// assert_eq!(af.pcr, Some(new_pcr));
344    /// ```
345    pub fn set_pcr(packet: &mut [u8; TS_PACKET_SIZE], pcr: Pcr) -> Result<()> {
346        // Byte 3: check adaptation_field_control has adaptation bit set
347        // (ADAPTATION_FLAG = bit 5, ISO/IEC 13818-1 §2.4.3.3).
348        if packet[3] & ADAPTATION_FLAG == 0 {
349            return Err(Error::BufferTooShort(
350                broadcast_common::error::BufferTooShort {
351                    need: 6,
352                    have: 0,
353                    what: "set_pcr: no adaptation field",
354                },
355            ));
356        }
357        let af_len = packet[4] as usize;
358        // W7 (#1074): the flags byte (1) + PCR field (PCR_FIELD_LEN = 6) must
359        // both fit inside `af_len`, not just the flags byte. A malformed
360        // adaptation field with `af_len` between 1 and 6 (PCR flag set
361        // anyway) previously passed this check and then wrote 6 PCR bytes at
362        // a fixed offset — 5 of which land past what `af_len` declares as
363        // the adaptation field, i.e. into the payload.
364        const REQUIRED_AF_LEN: usize = 1 + PCR_FIELD_LEN;
365        if af_len < REQUIRED_AF_LEN {
366            return Err(Error::BufferTooShort(
367                broadcast_common::error::BufferTooShort {
368                    need: REQUIRED_AF_LEN,
369                    have: af_len,
370                    what: "set_pcr: adaptation field too short for flags byte + PCR",
371                },
372            ));
373        }
374        // Byte 5: adaptation field flags byte (AF_PCR_FLAG = 0x10, §2.4.3.4).
375        if packet[5] & AF_PCR_FLAG == 0 {
376            return Err(Error::BufferTooShort(
377                broadcast_common::error::BufferTooShort {
378                    need: 6,
379                    have: 0,
380                    what: "set_pcr: PCR flag not set in adaptation field",
381                },
382            ));
383        }
384        // PCR occupies the 6 bytes starting at offset 6 (after header + af_len byte + flags byte).
385        let pcr_start = 6usize;
386        let pcr_end = pcr_start + PCR_FIELD_LEN;
387        packet[pcr_start..pcr_end].copy_from_slice(&pcr.to_field_bytes());
388        Ok(())
389    }
390
391    /// Decode the adaptation field from this packet, if present.
392    ///
393    /// Returns `None` when no adaptation field is present, and
394    /// `Some(Err(..))` when the adaptation field bytes are malformed.
395    /// The returned [`AdaptationField`] borrows from `self.raw`.
396    pub fn adaptation_field(&self) -> Option<crate::Result<AdaptationField<'_>>> {
397        if self.raw[3] & ADAPTATION_FLAG == 0 {
398            return None;
399        }
400        let af_len = self.raw[4] as usize;
401        if af_len == 0 || 5 + af_len > TS_PACKET_SIZE {
402            return None;
403        }
404        Some(AdaptationField::parse(&self.raw[5..5 + af_len]))
405    }
406}
407
408#[cfg(test)]
409mod tests {
410    use super::*;
411
412    /// The getters decode the header bytes literally (H.222.0 §2.4.3.2): a
413    /// hand-built header 0x47 | TEI=1 PUSI=1 prio=0 PID=0x1ABC | scr=0b10
414    /// afc=0b01 cc=0xC.
415    #[test]
416    fn getters_report_the_raw_header_fields() {
417        let mut raw = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
418        raw[0] = TS_SYNC_BYTE;
419        raw[1] = 0b1101_1010; // TEI=1, PUSI=1, priority=0, PID[12:8]=0b11010
420        raw[2] = 0xBC;
421        raw[3] = 0b1001_1100; // scrambling=0b10, afc=0b01 (payload only), cc=0xC
422        let pkt = OwnedTsPacket::parse(raw).unwrap();
423        assert!(pkt.tei());
424        assert!(pkt.pusi());
425        assert_eq!(pkt.pid(), Pid::new(0x1ABC));
426        assert_eq!(pkt.scrambling(), 0b10);
427        assert!(!pkt.has_adaptation());
428        assert!(pkt.has_payload());
429        assert_eq!(pkt.continuity_counter(), 0xC);
430        assert_eq!(pkt.raw(), &raw);
431    }
432
433    #[test]
434    fn owned_round_trip_and_payload_mut() {
435        let payload = [0xAAu8; 184];
436        let mut pkt = OwnedTsPacket::parse(
437            OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), true, 7, &payload).unwrap(),
438        )
439        .unwrap();
440        assert_eq!(pkt.pid(), Pid::new(0x0100));
441        assert!(pkt.pusi);
442        assert_eq!(pkt.continuity_counter, 7);
443        assert_eq!(pkt.payload().unwrap()[..184], payload[..]);
444        pkt.payload_mut().unwrap()[0] = 0x55;
445        assert_eq!(pkt.payload().unwrap()[0], 0x55);
446        // discontinuity defaults to false
447        assert!(!pkt.discontinuity);
448    }
449
450    /// W9 (#1129): `serialize_with_payload` used to silently truncate a
451    /// payload over 184 bytes (`payload.len().min(184)`) instead of
452    /// erroring, dropping data with no signal.
453    #[test]
454    fn oversized_payload_is_rejected_not_truncated() {
455        let payload = [0xAAu8; 185];
456        assert_eq!(
457            OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), true, 0, &payload),
458            Err(Error::PayloadTooLarge { len: 185, max: 184 })
459        );
460    }
461
462    /// The boundary: exactly 184 bytes (the fixed payload capacity) still
463    /// serializes and round-trips.
464    #[test]
465    fn max_payload_len_round_trips() {
466        let payload = [0xBBu8; OwnedTsPacket::MAX_PAYLOAD_LEN];
467        let raw =
468            OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), true, 0, &payload).unwrap();
469        let pkt = OwnedTsPacket::parse(raw).unwrap();
470        assert_eq!(pkt.payload().unwrap(), &payload[..]);
471    }
472
473    #[test]
474    fn owned_scrambling_control_accessor() {
475        let make = |scrambling_bits: u8| -> OwnedTsPacket {
476            let mut raw =
477                OwnedTsPacket::serialize_with_payload(Pid::new(0x0100), false, 0, &[]).unwrap();
478            // byte 3 bits [7:6] = scrambling
479            raw[3] = (raw[3] & 0x3F) | (scrambling_bits << 6);
480            OwnedTsPacket::parse(raw).unwrap()
481        };
482        assert_eq!(
483            make(0b00).scrambling_control(),
484            ScramblingControl::NotScrambled
485        );
486        assert_eq!(make(0b01).scrambling_control(), ScramblingControl::Reserved);
487        assert_eq!(make(0b10).scrambling_control(), ScramblingControl::EvenKey);
488        assert_eq!(make(0b11).scrambling_control(), ScramblingControl::OddKey);
489    }
490
491    #[test]
492    fn owned_adaptation_field_control_accessor() {
493        let make = |afc_bits: u8| -> OwnedTsPacket {
494            let mut raw = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
495            raw[0] = TS_SYNC_BYTE;
496            raw[1] = 0x00;
497            raw[2] = 0x00;
498            raw[3] = (afc_bits << 4) & 0x30;
499            if afc_bits & 0b10 != 0 {
500                raw[4] = 0; // adaptation_field_length = 0
501            }
502            OwnedTsPacket::parse(raw).unwrap()
503        };
504        assert_eq!(
505            make(0b00).adaptation_field_control(),
506            AdaptationFieldControl::Reserved
507        );
508        assert_eq!(
509            make(0b01).adaptation_field_control(),
510            AdaptationFieldControl::PayloadOnly
511        );
512        assert_eq!(
513            make(0b10).adaptation_field_control(),
514            AdaptationFieldControl::AdaptationOnly
515        );
516        assert_eq!(
517            make(0b11).adaptation_field_control(),
518            AdaptationFieldControl::AdaptationAndPayload
519        );
520    }
521
522    /// W7 (#1074): `set_pcr` only checked `af_len >= 1` (room for the flags
523    /// byte), not `af_len >= 1 + PCR_FIELD_LEN` (room for the PCR itself). A
524    /// malformed adaptation field with `af_len` between 1 and 6 but the PCR
525    /// flag set anyway let `set_pcr` write its fixed 6-byte PCR field 6 bytes
526    /// past the header regardless — spilling into whatever follows the
527    /// (too-short) declared adaptation field, i.e. the payload.
528    #[test]
529    fn set_pcr_rejects_adaptation_field_too_short_for_pcr() {
530        let mut raw = [crate::ts::STUFFING_BYTE; TS_PACKET_SIZE];
531        raw[0] = TS_SYNC_BYTE;
532        raw[1] = 0x00;
533        raw[2] = 0x00;
534        raw[3] = ADAPTATION_FLAG | crate::ts::PAYLOAD_FLAG;
535        raw[4] = 1; // adaptation_field_length: only room for the flags byte
536        raw[5] = AF_PCR_FLAG; // PCR flag set anyway (malformed: no room for PCR)
537        // raw[6..] is payload (0xFF stuffing here).
538        let payload_before = raw[6..].to_vec();
539
540        let pcr = Pcr {
541            base: 10_000,
542            extension: 0,
543        };
544        let err = OwnedTsPacket::set_pcr(&mut raw, pcr).unwrap_err();
545        assert!(matches!(err, Error::BufferTooShort(..)));
546        assert_eq!(
547            &raw[6..],
548            payload_before.as_slice(),
549            "payload must be untouched"
550        );
551    }
552}