Skip to main content

mediadecode_ffmpeg/
buffer.rs

1//! The **amputation seam**: FFmpeg's bytes leave here, copied once,
2//! as Rust-owned memory.
3//!
4//! An `AVPacket`'s payload and an `AVFrame`'s planes both live in
5//! `AVBufferRef`s — FFmpeg's own refcounted allocations. Through 0.8
6//! this crate handed those out directly, wrapped in an `FfmpegBuffer`
7//! whose `AsRef<[u8]>` pointed straight into libavcodec's memory. That
8//! type is gone. Every byte that crosses this boundary is now copied
9//! into an [`FfmpegBytes`], which is what the core's
10//! [D-seat amputation contract][law] requires: owned, `Send + Sync`,
11//! clone-is-a-refcount-bump, and with no FFmpeg lifetime riding along.
12//!
13//! What is left in this module is everything the copy still has to
14//! *judge*. A packet's payload has to be proved to lie inside the
15//! buffer that owns it before a byte of it is read, its side data has
16//! to be carried whole or refused, and its flags have to fit the
17//! portable set — so [`PacketBufferError`] and its payload structs
18//! outlive the buffer type they were written for. The bounds check in
19//! particular matters *more* now, not less: 0.8 formed a view over the
20//! claimed range, 0.9 reads it.
21//!
22//! # The one thing the amputation costs
23//!
24//! The `Arc<[u8]>` behind [`FfmpegBytes`] has no fallible constructor
25//! on stable Rust, so the copy itself aborts on allocation failure
26//! rather than returning an error.
27//! Everything that bounds *how much* can be asked for — the side-data
28//! entry and byte caps, the plane-geometry checks — is unchanged and
29//! still runs before any allocation, so a hostile stream cannot reach
30//! that abort by demanding memory; only a genuinely exhausted
31//! allocator can.
32//!
33//! [`payload_of`] is where the per-packet half of that bounding lives:
34//! every packet body this crate copies passes through it, and it
35//! refuses an over-budget claim before reading a byte. See
36//! [`crate::limits`] for the budgets and their defaults.
37//!
38//! # The funnel's accounting
39//!
40//! Every [`FfmpegBytes`] in this crate is built by
41//! [`FfmpegBytes::copy_from_slice`], [`FfmpegBytes::from_rows`] or
42//! [`FfmpegBytes::empty`], and **every one of those call sites is
43//! bounded before it allocates**. The table is kept here, beside the
44//! constructors, so that a new exit has to answer the question the
45//! existing ones already answered — the discipline is inherited by
46//! being written down where the next author will be standing.
47//!
48//! Three review rounds found bypasses that each looked like an
49//! exception: a plane path with no ceiling, an attachment whose
50//! payload was copied by `avcodec_parameters_copy` before its budget
51//! was charged, a resampler amplifying a small input into a huge
52//! output, and then `coded_side_data` — a third heap seat on the same
53//! wholesale parameter copy, where a MOV `prof` atom puts an ICC
54//! profile. None of them were exceptions. They were rows nobody had
55//! written down.
56//!
57//! The third one is why the table below has a section it did not need
58//! at first. `FfmpegBytes` is not the only place this crate copies
59//! attacker-sized bytes: `AVCodecParameters` has heap seats of its own,
60//! and the wholesale FFI copy that used to duplicate them took every
61//! one — including any this crate had never enumerated. That copy is
62//! gone; see
63//! [`bounded_clone_parameters`](crate::extras::bounded_clone_parameters).
64//!
65//! | construction site | what it carries | what bounds it |
66//! |---|---|---|
67//! | [`payload_of`] | any packet's payload | its `budget` argument — [`PacketLimits::max_packet_bytes`](crate::PacketLimits::max_packet_bytes) for timed packets, [`DemuxLimits::max_attachment_bytes`](crate::DemuxLimits::max_attachment_bytes) for attachments — judged against the declared `size` before a byte is read |
68//! | `convert::copy_out_planes`, tight stride | one video or image plane | [`FrameLimits::max_pixels`](crate::FrameLimits::max_pixels) and [`FrameLimits::max_frame_bytes`](crate::FrameLimits::max_frame_bytes), both in a judge-pass that runs before any plane is allocated; `max_pixels` also reaches libavcodec |
69//! | `convert::copy_out_planes`, padded stride | one compacted plane, via [`FfmpegBytes::from_rows`] | the same pre-pass |
70//! | `convert::av_frame_to_audio_frame` | one audio plane | `max_frame_bytes`, checked over `plane_bytes × plane_count` before the loop |
71//! | `convert::collect_side_data` | one frame side-data entry | `SIDE_DATA_MAX_ENTRIES` (64) and `SIDE_DATA_MAX_TOTAL_BYTES` (256 KiB), plus `try_reserve_exact` |
72//! | `boundary::packet_side_data` | one packet side-data entry | the same two caps, as refusals rather than truncation |
73//! | `convert::av_subtitle_to_subtitle_frame`, text | concatenated cue text | `SUBTITLE_MAX_TEXT_BYTES_PER_RECT` (64 KiB), `SUBTITLE_MAX_TEXT_TOTAL_BYTES` (256 KiB), `SUBTITLE_MAX_RECTS` (64) |
74//! | …, bitmap | one paletted rect | `SUBTITLE_MAX_BITMAP_BYTES_PER_RECT` (16 MiB), `SUBTITLE_MAX_BITMAP_TOTAL_BYTES` (32 MiB), `SUBTITLE_MAX_RECTS` |
75//! | …, palette | an RGBA palette | structurally fixed at 256 × 4 bytes by the format |
76//! | `demuxer::extradata_payload` | a synthesized attachment (a font) | `demuxer::admit_attachments`, which charges every attachment in the file — per-attachment **and** aggregate — before the track loop allocates anything; re-checked here against the per-attachment ceiling |
77//! | `demuxer::attached_pic_payload` | a hoisted cover-art packet | the same admission pass, then `payload_of`'s budget |
78//! | `resampler::finish_output` | one converted audio plane | `FfmpegResampler::check_output_bytes`, against `max_frame_bytes`, run before the output `AVFrame` is allocated |
79//! | every [`FfmpegBytes::empty`] site | nothing | structurally zero: placeholder plane slots, a payload-less packet, a null palette, a marker side-data entry |
80//!
81//! # The other heap this crate copies
82//!
83//! `AVCodecParameters` is not an [`FfmpegBytes`] and never passes
84//! through this module, but it is the same class of exposure — three
85//! heap seats, all sized by the file — so its rows belong in the same
86//! accounting.
87//!
88//! | construction site | what it carries | what bounds it |
89//! |---|---|---|
90//! | [`bounded_clone_parameters`](crate::extras::bounded_clone_parameters), `extradata` | SPS/PPS and codec headers | [`DemuxLimits::max_codec_parameter_bytes`](crate::DemuxLimits::max_codec_parameter_bytes), measured by `measure_parameters` before the copy |
91//! | …, `coded_side_data` | the descriptor array and each entry's payload — a MOV `prof` atom's ICC profile among them | the same seat, counting the array as well as the payloads |
92//! | …, `ch_layout` custom map | a channel map | the same seat; the one FFmpeg call left on this path (`av_channel_layout_copy`) copies exactly this field, at a size measured first |
93//! | `demuxer::admit_streams` | nothing — it only measures | runs over **every** stream before the track loop clones anything, and charges the whole-file [`max_total_codec_parameter_bytes`](crate::DemuxLimits::max_total_codec_parameter_bytes) |
94//! | `decoder::build_codec_context` → `avcodec_parameters_to_context` | the same three seats, copied *into* an `AVCodecContext` | **the choke point**: measured and admitted against [`DecoderLimits::max_codec_parameter_bytes`](crate::DecoderLimits::max_codec_parameter_bytes) right there. Every decoder in this crate opens through this function — the four session `open`s, the HW probe's `build_state`, its per-backend advances, the software fallback — and none of them reaches `avcodec_parameters_to_context` any other way |
95//! | `image::FfmpegImageDecoder::decode` → `boundary::try_packet_copy` | the caller's compressed bytes, duplicated into an `AVPacket` | [`DecoderLimits::max_image_input_bytes`](crate::DecoderLimits::max_image_input_bytes), defaulting to the attachment family so the direct road is no more permissive than the demuxed one |
96//! | `boundary::ffmpeg_packet_from_{video,audio,subtitle}_packet` → `try_packet_copy` | the caller's compressed bytes, duplicated into an `AVPacket` — **the send leg** | [`DecoderLimits::max_packet_bytes`](crate::DecoderLimits::max_packet_bytes), judged before the allocation. The same seat the receive leg (`payload_of`) judges, so a byte count refused coming out of a container is refused going into a decoder |
97//! | still `pal8` palette plane | a fixed `AVPALETTE_SIZE` run | the **format**, not a seat: 256 × `AV_PIX_FMT_RGB32`, always, with no number a file gets to choose |
98//!
99//! # The rule
100//!
101//! **A carrier whose size comes from a file is bounded by a seat in
102//! [`crate::limits`]; a carrier whose size is a property of a format is
103//! bounded by that format.** There is no third kind, and a site that
104//! looks like one has not been thought about yet.
105//!
106//! And the corollary the third round bought: **no code path hands
107//! attacker-sized data to a wholesale FFI copy** — a copy that
108//! duplicates every field of a struct duplicates the fields nobody
109//! enumerated, which is a budget bypass that arrives with the next
110//! FFmpeg release rather than with the next commit.
111//!
112//! # The substrate's knobs, and where this crate stops
113//!
114//! Everything above is **tier one** of the [resource governance
115//! contract][gov]: allocations this crate makes itself, each bounded by
116//! a named seat or by a format. This table is that tier's proof.
117//!
118//! Tier two is the other half — FFmpeg's own resource knobs, set at
119//! every point libavcodec and libavformat offer one. They bound
120//! allocations this crate does not make and could not otherwise see:
121//!
122//! | knob | where it is set | what it bounds |
123//! |---|---|---|
124//! | `AVCodecContext.max_pixels` | every opened decoder | the caller's pixel limit, **verbatim**, applied by `ff_set_dimensions` to the raw dimensions. Extent, not cost: what a frame *costs* is the byte judge's question, two rows down |
125//! | the `get_format` coded-dims ask | the hardware road | the **pool's own declared extent**, asked of `avcodec_get_hw_frames_parameters` before the pool is initialised — `max_pixels` is applied to the *display* dims, which a cropped stream can make 2000x smaller |
126//! | the `get_format` byte judge | the hardware road | the **pool's** cost, priced through [`crate::footprint`] against `max_frame_bytes`. **Fails closed**: a pool that will not declare its dimensions and layout is a pool that cannot be judged, and the codec-alignment fallback that used to stand in could answer *smaller* than the pool it was standing in for |
127//! | the `get_buffer2` byte judge | every software decode | what the allocator will actually take for this frame — pictures and audio both, priced through [`crate::footprint`] against the caller's own `max_frame_bytes`, carried in the codec context's callback state |
128//! | the pre-transfer judge | every `av_hwframe_transfer_data` | the CPU destination a hardware download allocates, priced at the frames-context pool dims — folding **every** candidate format FFmpeg may pick, priceable or not, since FFmpeg does the picking |
129//! | `probesize` / `formatprobesize` | both demux entrypoints | what the format probe and stream analysis may consume |
130//! | `max_streams` | both demux entrypoints | the `AVStream` array a header can conjure |
131//! | the `AVIOContext` byte meter | the **reader** demux entrypoint | total bytes libavformat is handed, hard — past the budget the reader stops answering |
132//!
133//! Two of those knobs used to carry *translated* byte ceilings —
134//! `max_pixels` as `min(the caller's limit, bytes / 16)` and
135//! `max_samples` as `bytes / 8` — so that the byte budget could bite
136//! before libavcodec allocated. Both translations charged every stream
137//! the worst format in existence, and both over-refused ordinary media:
138//! a 1920x1080 `yuv420p` frame under a 4 MiB budget, a 6-channel `s16`
139//! frame under 64 KiB. They are gone. The byte budget is enforced by
140//! the `get_buffer2` judge, which is *itself* a pre-allocation seat —
141//! `get_buffer2` **is** the allocation — and prices the frame's real
142//! format at its real dimensions. An exact judge at the allocation
143//! beats an approximate one before it.
144//!
145//! Where a layout cannot be priced at all, these judges charge
146//! [`crate::footprint::video_frame_bytes_upper_bound`] — the same
147//! dimension alignment and per-plane overhead at the widest per-pixel
148//! rate the census finds — rather than a bare `w * h * rate`, which
149//! omits both and could land *below* the accurate path it was standing
150//! in for. A conservative fallback that can under-state is not
151//! conservative.
152//!
153//! **These are defense in depth, not a proof.** Each bounds what it was
154//! built to bound; together they cover every interposition point FFmpeg
155//! exposes, which is not the same as covering FFmpeg.
156//!
157//! ## What the demux seats cannot reach, and why they exist anyway
158//!
159//! `avformat_open_input` and `avformat_find_stream_info` build the
160//! attached picture, the extradata and the coded side data out of the
161//! file themselves. The attachment and parameter seats in the table
162//! above therefore measure this crate's *copies* of buffers libavformat
163//! has already allocated — too late, by construction, to have prevented
164//! the original.
165//!
166//! A parser cannot allocate from bytes it was never handed, so the
167//! input is bounded instead: that is what the probe knobs and the byte
168//! meter are for. What is **not** bounded is allocation *amplification*
169//! inside a parser — a container can describe, in a handful of bytes, a
170//! structure whose in-memory form is far larger, and nothing outside
171//! libavformat can observe it happen. Bounding that output is the
172//! substrate's own hardening territory; FFmpeg keeps `max_streams`,
173//! `max_index_size` and `max_picture_buffer` for it, and this crate
174//! sets the first.
175//!
176//! The hard meter also does not reach the **path** entrypoint: it needs
177//! an `AVIOContext` this crate owns, and a path is opened by
178//! libavformat's own protocol layer. The probe knobs still apply there;
179//! a caller who wants the meter on a file opens it as a reader.
180//!
181//! That gap is **tier three**, and it is named rather than hedged: see
182//! the [contract][gov] for the boundary and for the OS-level instrument
183//! a deployment needing a hard memory bound puts underneath all of
184//! this. This crate is not a hypervisor for FFmpeg, and its seats
185//! compose with that instrument rather than replacing it.
186//!
187//! [gov]: mediadecode::adapter#the-resource-governance-contract
188//!
189//! And the capstone, which is what every seat in this table is finally
190//! for: **a judge must dominate the allocator's arithmetic, not the
191//! payload's.** A budget compared against what the bytes weigh is not a
192//! budget on what will be spent — see [`crate::footprint`] for the
193//! measured gap and for the two judges that were caught paying it.
194//!
195//! And the corollary the ninth bought, which is about *whether* to
196//! carry at all rather than how much: **a payload that carries
197//! addresses instead of bytes is uncarriable.** `AV_PKT_FLAG_TRUSTED`
198//! marks one — the wrapped-`AVFrame` producers use it for a body that
199//! is an `AVFrame` pointer structure — and copying it mints a carrier
200//! that passes every property this table exists to guarantee and
201//! dangles the moment its source drops. It is refused on both legs
202//! ([`payload_of`] and the reverse builders), because either alone
203//! leaves the loop open. See [`TrustedPayload`].
204//!
205//! And the corollary the seventh bought, about the *inputs* to every
206//! guard above rather than the guards themselves: **a number a file
207//! chooses is judged or refused, never clipped.** A seat that bounds a
208//! byte product still trusts the fields the product is computed from,
209//! so a clamped sample count or channel count does not trip any budget
210//! — it produces a smaller, plausible frame that no ceiling has any
211//! reason to stop. Two of those were live on the audio path (a floored
212//! negative `nb_samples`, a channel count clipped to `u8::MAX`), and
213//! both turned a malformed header into a well-formed-looking frame,
214//! which is strictly worse than an error. The audio road now carries no
215//! lossy clamp; the one floor left, `sample_rate`, is censused at its
216//! site with the reason it is metadata and sizes nothing.
217//!
218//! [law]: mediadecode::adapter#the-d-seat-amputation-contract
219
220use std::{
221  fmt,
222  sync::{Arc, OnceLock},
223};
224
225use derive_more::{IsVariant, TryUnwrap, Unwrap};
226
227/// The bytes every packet and frame this crate produces are carried in.
228///
229/// Owned, `Send + Sync`, `'static`, and clone-is-a-refcount-bump: the
230/// core's [D-seat amputation contract][law], satisfied. Nothing inside
231/// reaches back into libavcodec.
232///
233/// # Why it is opaque
234///
235/// The obvious spelling was the bare `Arc<[u8]>` this type wraps, and
236/// 0.9.0's first cut used it. It is opaque for one reason, and the
237/// reason is not aesthetics:
238///
239/// **`Arc<[u8]>` is one storage strategy, and it is not going to be the
240/// only one.** Every exit currently allocates, copies, and frees per
241/// frame; a decode loop at 4K is asking the global allocator for eight
242/// megabytes sixty times a second and handing it back. The recorded
243/// answer is a plane pool — reusable slabs handed out at the boundary
244/// and returned when the last consumer drops them
245/// ([issue #35](https://github.com/findit-studio/mediadecode/issues/35)).
246/// A pooled slab is a different carrier with the same contract: still
247/// owned, still `Send + Sync`, still refcount-cloned, still holding no
248/// FFmpeg lifetime.
249///
250/// If the carrier were `Arc<[u8]>` in the public aliases, adding the
251/// pool would change the type of every frame and every packet in the
252/// crate — a breaking release for a change consumers cannot observe.
253/// Behind this newtype it is a new arm of a **private** enum: no
254/// signature moves, no consumer recompiles differently, and the
255/// `AsRef<[u8]>` a consumer actually programs against is unchanged.
256/// That extension point *is* this type's justification for existing.
257///
258/// The enum has exactly one arm today. It gains the second when the
259/// pool is built and not before — this codebase does not carry members
260/// nothing can produce.
261///
262/// [law]: mediadecode::adapter#the-d-seat-amputation-contract
263#[derive(Clone, Default, PartialEq, Eq, Hash)]
264pub struct FfmpegBytes(Inner);
265
266/// The storage behind [`FfmpegBytes`]. **Private, and the point.**
267///
268/// One arm today; see the type's own docs for the arm that is coming
269/// and why it can arrive without a breaking release.
270#[derive(Clone, PartialEq, Eq, Hash)]
271enum Inner {
272  /// A refcounted slice, allocated by the copy at the boundary.
273  Shared(Arc<[u8]>),
274}
275
276impl Default for Inner {
277  #[inline]
278  fn default() -> Self {
279    Self::Shared(shared_empty())
280  }
281}
282
283impl FfmpegBytes {
284  /// Copies `bytes` into a fresh carrier.
285  ///
286  /// **The copy site.** Every exit in this crate lands here or on
287  /// [`Self::empty`], so "one copy at the boundary" is a property of
288  /// one constructor rather than a promise thirty call sites keep —
289  /// and it is the one place a future pooled arm has to be taught
290  /// about.
291  ///
292  /// Public because the reverse direction needs it: a consumer
293  /// building a packet to feed back into a decoder has bytes and needs
294  /// a carrier, and the alternative is an opaque type nobody outside
295  /// this crate can construct.
296  ///
297  /// A zero-length copy lands on the shared empty allocation rather
298  /// than minting its own.
299  #[inline]
300  pub fn copy_from_slice(bytes: &[u8]) -> Self {
301    if bytes.is_empty() {
302      return Self::empty();
303    }
304    Self(Inner::Shared(Arc::from(bytes)))
305  }
306
307  /// The zero-length carrier, shared.
308  ///
309  /// Placeholder plane slots and payload-less packets are frequent — a
310  /// video frame allocates four slots and populates one to three of
311  /// them — and each would otherwise be its own `Arc` header
312  /// allocation. One empty allocation for the process, cloned by
313  /// refcount, instead.
314  #[inline]
315  pub fn empty() -> Self {
316    Self(Inner::Shared(shared_empty()))
317  }
318
319  /// Builds a carrier of `rows * row_bytes` bytes by writing each row
320  /// in turn — **one allocation, no staging buffer**.
321  ///
322  /// This is the road a padded plane takes. FFmpeg lays such a plane
323  /// out `linesize` bytes per row while only the first `row_bytes` of
324  /// each are the decoder's output, so the copy has to be row-wise and
325  /// the destination is contiguous. The obvious spelling — build a
326  /// `Vec`, then `Arc::from` it — allocates the whole plane **twice**
327  /// and copies it twice, so a 250 MiB frame peaks at 750 MiB counting
328  /// FFmpeg's own. Writing the rows straight into
329  /// `Arc::new_uninit_slice` leaves the unavoidable 2×: FFmpeg's plane
330  /// and ours.
331  ///
332  /// `row(i)` must answer a slice of exactly `row_bytes`; a shorter or
333  /// longer one is a bug in the caller's geometry and panics rather
334  /// than leaving the tail of the allocation uninitialised. That
335  /// assertion is what discharges the initialisation contract for the
336  /// `assume_init` below: the loop visits every row, each row fills its
337  /// full width, and `rows * row_bytes` is the whole allocation.
338  ///
339  /// Crate-internal: the public face is [`Self::copy_from_slice`], and
340  /// this shape only makes sense to a caller that already holds a
341  /// strided picture.
342  ///
343  /// # Panics
344  ///
345  /// If `rows * row_bytes` overflows `usize`, or if `row(i)` answers a
346  /// slice that is not `row_bytes` long. Callers reach this only after
347  /// the geometry has been validated and the total checked against
348  /// [`FrameLimits`](crate::FrameLimits), so both are unreachable from
349  /// input.
350  pub(crate) fn from_rows<'a>(
351    rows: usize,
352    row_bytes: usize,
353    mut row: impl FnMut(usize) -> &'a [u8],
354  ) -> Option<Self> {
355    let len = rows.checked_mul(row_bytes)?;
356    if len == 0 {
357      return Some(Self::empty());
358    }
359    let mut uninit = Arc::<[u8]>::new_uninit_slice(len);
360    {
361      let slots =
362        Arc::get_mut(&mut uninit).expect("the allocation was made here and has not been shared");
363      for index in 0..rows {
364        let source = row(index);
365        if source.len() != row_bytes {
366          // A length that arrives from a caller is an input, not a
367          // promise: refuse rather than copy `row_bytes` out of a
368          // shorter slice. The half-built `Arc` drops with this
369          // return, and every byte of it is still `MaybeUninit`.
370          return None;
371        }
372        let start = index * row_bytes;
373        // `MaybeUninit<u8>` has the same layout as `u8`, so the source
374        // slice can be viewed as one and copied wholesale.
375        let destination = &mut slots[start..start + row_bytes];
376        // SAFETY: `&[u8]` and `&[MaybeUninit<u8>]` have identical
377        // layout, and the cast is read-only on the source side.
378        let source: &[core::mem::MaybeUninit<u8>] = unsafe {
379          core::slice::from_raw_parts(
380            source.as_ptr().cast::<core::mem::MaybeUninit<u8>>(),
381            row_bytes,
382          )
383        };
384        destination.copy_from_slice(source);
385      }
386    }
387    // SAFETY: the loop above wrote every one of the `rows * row_bytes`
388    // slots — `rows` iterations, each filling exactly `row_bytes`
389    // consecutive bytes starting at `index * row_bytes`, with the
390    // length of each source row checked before the copy and the whole
391    // gather abandoned if one disagreed. Nothing in the allocation is
392    // left uninitialised on this road.
393    Some(Self(Inner::Shared(unsafe { uninit.assume_init() })))
394  }
395
396  /// The bytes, as a slice.
397  ///
398  /// The same answer [`AsRef::as_ref`] gives; inherent so a caller
399  /// reaching through a `&FfmpegBytes` does not have to name the trait.
400  #[inline]
401  pub fn as_slice(&self) -> &[u8] {
402    match &self.0 {
403      Inner::Shared(bytes) => bytes,
404    }
405  }
406
407  /// Number of bytes carried.
408  #[inline]
409  pub fn len(&self) -> usize {
410    self.as_slice().len()
411  }
412
413  /// `true` when this carries no bytes.
414  #[inline]
415  pub fn is_empty(&self) -> bool {
416    self.as_slice().is_empty()
417  }
418
419  /// `true` when both handles name the same allocation — a clone of
420  /// one another, rather than two copies that happen to be equal.
421  ///
422  /// The property the amputation contract is really about: `Clone` on
423  /// a message is a refcount bump. `PartialEq` answers a different
424  /// question (do these hold the same bytes), and a test that wants to
425  /// prove the clone did not copy has to ask this one.
426  #[inline]
427  pub fn ptr_eq(&self, other: &Self) -> bool {
428    match (&self.0, &other.0) {
429      (Inner::Shared(a), Inner::Shared(b)) => Arc::ptr_eq(a, b),
430    }
431  }
432}
433
434impl AsRef<[u8]> for FfmpegBytes {
435  #[inline]
436  fn as_ref(&self) -> &[u8] {
437    self.as_slice()
438  }
439}
440
441impl fmt::Debug for FfmpegBytes {
442  /// Length only, never the bytes.
443  ///
444  /// A derived `Debug` would print a decoded 4K plane one integer at a
445  /// time; this type is reached from the derived `Debug` of every
446  /// packet, frame and side-data entry in the crate, so the terse form
447  /// is the one that keeps those useful. Mirrors what `FfmpegBuffer`'s
448  /// own hand-written `Debug` did through 0.8.
449  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450    f.debug_struct("FfmpegBytes")
451      .field("len", &self.len())
452      .finish()
453  }
454}
455
456/// The process-wide empty `Arc`, so a zero-length carrier costs a
457/// refcount bump rather than an allocation.
458fn shared_empty() -> Arc<[u8]> {
459  static EMPTY: OnceLock<Arc<[u8]>> = OnceLock::new();
460  EMPTY.get_or_init(|| Arc::from(&[][..])).clone()
461}
462
463/// Payload for [`PacketBufferError::PacketTooLarge`].
464///
465/// A packet's payload is larger than the budget in force.
466///
467/// Refused **before** the copy: 0.8 answered a claimed payload with a
468/// refcount, so an absurd `size` cost nothing; 0.9 answers it with an
469/// allocation, so the claim has to be judged first.
470#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
471#[error("a {bytes}-byte packet payload exceeds the {limit}-byte budget")]
472pub struct PacketTooLarge {
473  bytes: usize,
474  limit: usize,
475}
476
477impl PacketTooLarge {
478  /// Constructs a `PacketTooLarge` payload.
479  #[cfg_attr(not(tarpaulin), inline(always))]
480  pub const fn new(bytes: usize, limit: usize) -> Self {
481    Self { bytes, limit }
482  }
483  /// The payload length the packet declared.
484  #[cfg_attr(not(tarpaulin), inline(always))]
485  pub const fn bytes(&self) -> usize {
486    self.bytes
487  }
488  /// The budget in force.
489  #[cfg_attr(not(tarpaulin), inline(always))]
490  pub const fn limit(&self) -> usize {
491    self.limit
492  }
493}
494
495/// Payload for [`PacketBufferError::Bounds`].
496///
497/// The payload does not lie inside the packet's own buffer.
498/// `AVPacket` guarantees it does; a packet that says otherwise is
499/// malformed, and wrapping it would hand out a view over memory the
500/// buffer does not own.
501#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
502#[error("a {len}-byte payload at offset {offset} does not lie inside a {size}-byte buffer")]
503pub struct Bounds {
504  offset: usize,
505  len: usize,
506  size: usize,
507}
508
509impl Bounds {
510  /// Constructs a `Bounds` payload.
511  #[cfg_attr(not(tarpaulin), inline(always))]
512  pub const fn new(offset: usize, len: usize, size: usize) -> Self {
513    Self { offset, len, size }
514  }
515  /// Where the payload starts inside the buffer.
516  #[cfg_attr(not(tarpaulin), inline(always))]
517  pub const fn offset(&self) -> usize {
518    self.offset
519  }
520  /// The payload's length in bytes.
521  #[cfg_attr(not(tarpaulin), inline(always))]
522  pub const fn len(&self) -> usize {
523    self.len
524  }
525  /// `true` when the payload is zero bytes long.
526  #[cfg_attr(not(tarpaulin), inline(always))]
527  pub const fn is_empty(&self) -> bool {
528    self.len == 0
529  }
530  /// The buffer's own length in bytes.
531  #[cfg_attr(not(tarpaulin), inline(always))]
532  pub const fn size(&self) -> usize {
533    self.size
534  }
535}
536
537/// Payload for [`PacketBufferError::SideDataEntries`].
538///
539/// A packet declares more side-data entries than this crate will
540/// walk, or a negative count.
541///
542/// The cap bounds the work a crafted packet can demand *before* it is
543/// refused. It cannot trip on anything FFmpeg's own packet API
544/// produces: both `av_packet_new_side_data` and
545/// `av_packet_add_side_data` replace an entry of the same type, so a
546/// packet carries at most one entry per named type — forty-three in
547/// this build, and the cap tracks that number if it ever grows past
548/// the floor.
549#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
550#[error("a packet declaring {count} side-data entries cannot be carried (limit {cap})")]
551pub struct SideDataEntries {
552  count: i32,
553  cap: usize,
554}
555
556impl SideDataEntries {
557  /// Constructs a `SideDataEntries` payload.
558  #[cfg_attr(not(tarpaulin), inline(always))]
559  pub const fn new(count: i32, cap: usize) -> Self {
560    Self { count, cap }
561  }
562  /// The count the packet declared.
563  #[cfg_attr(not(tarpaulin), inline(always))]
564  pub const fn count(&self) -> i32 {
565    self.count
566  }
567  /// The most entries this crate will walk.
568  #[cfg_attr(not(tarpaulin), inline(always))]
569  pub const fn cap(&self) -> usize {
570    self.cap
571  }
572}
573
574/// Payload for [`PacketBufferError::SideDataArray`].
575///
576/// A packet declares side-data entries and carries no array to read
577/// them from.
578///
579/// Malformed, and named rather than read as "no side data": a null
580/// array with a positive count is the same silent loss as a truncated
581/// copy, reached through the pointer instead of the cap.
582#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
583#[error("a packet declaring {count} side-data entries carries no array")]
584pub struct SideDataArray {
585  count: i32,
586}
587
588impl SideDataArray {
589  /// Constructs a `SideDataArray` payload.
590  #[cfg_attr(not(tarpaulin), inline(always))]
591  pub const fn new(count: i32) -> Self {
592    Self { count }
593  }
594  /// The count the packet declared.
595  #[cfg_attr(not(tarpaulin), inline(always))]
596  pub const fn count(&self) -> i32 {
597    self.count
598  }
599}
600
601/// Payload for [`PacketBufferError::SideDataPayload`].
602#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
603#[error("side-data entry {index} declares {size} bytes and carries no data")]
604pub struct SideDataPayload {
605  index: usize,
606  size: usize,
607}
608
609impl SideDataPayload {
610  /// Constructs a `SideDataPayload` payload.
611  #[cfg_attr(not(tarpaulin), inline(always))]
612  pub const fn new(index: usize, size: usize) -> Self {
613    Self { index, size }
614  }
615  /// The entry's position in the packet's array.
616  #[cfg_attr(not(tarpaulin), inline(always))]
617  pub const fn index(&self) -> usize {
618    self.index
619  }
620  /// The length the entry declared.
621  #[cfg_attr(not(tarpaulin), inline(always))]
622  pub const fn size(&self) -> usize {
623    self.size
624  }
625}
626
627/// Payload for [`PacketBufferError::SideDataBytes`].
628///
629/// A packet's side data is larger than this crate will copy.
630#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
631#[error("{bytes} bytes of side data cannot be carried (limit {cap})")]
632pub struct SideDataBytes {
633  bytes: usize,
634  cap: usize,
635}
636
637impl SideDataBytes {
638  /// Constructs a `SideDataBytes` payload.
639  #[cfg_attr(not(tarpaulin), inline(always))]
640  pub const fn new(bytes: usize, cap: usize) -> Self {
641    Self { bytes, cap }
642  }
643  /// The total the packet's entries reached.
644  #[cfg_attr(not(tarpaulin), inline(always))]
645  pub const fn bytes(&self) -> usize {
646    self.bytes
647  }
648  /// The most bytes this crate will copy.
649  #[cfg_attr(not(tarpaulin), inline(always))]
650  pub const fn cap(&self) -> usize {
651    self.cap
652  }
653}
654
655/// Payload for [`PacketBufferError::UnrepresentableFlags`].
656///
657/// A packet carries flag bits the portable vocabulary cannot hold.
658///
659/// `mediadecode`'s `PacketFlags` is a `u8` bit set, and every packet
660/// flag FFmpeg names today lives in that byte — so this cannot fire
661/// against this build. It exists so that the day one does not, the
662/// packet is refused by name instead of arriving with a bit quietly
663/// missing: the same rule the rest of this boundary keeps.
664#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
665#[error("packet flags {raw:#x} do not fit the portable flag set")]
666pub struct UnrepresentableFlags {
667  raw: i32,
668}
669
670impl UnrepresentableFlags {
671  /// Constructs an `UnrepresentableFlags` payload.
672  #[cfg_attr(not(tarpaulin), inline(always))]
673  pub const fn new(raw: i32) -> Self {
674    Self { raw }
675  }
676  /// `AVPacket.flags` as FFmpeg wrote it.
677  #[cfg_attr(not(tarpaulin), inline(always))]
678  pub const fn raw(&self) -> i32 {
679    self.raw
680  }
681}
682
683/// Payload for [`PacketBufferError::SideDataAlloc`].
684///
685/// Out of memory copying a side-data entry.
686#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
687#[error("out of memory copying {size} bytes of side data")]
688pub struct SideDataAlloc {
689  size: usize,
690}
691
692impl SideDataAlloc {
693  /// Constructs a `SideDataAlloc` payload.
694  #[cfg_attr(not(tarpaulin), inline(always))]
695  pub const fn new(size: usize) -> Self {
696    Self { size }
697  }
698  /// The entry's length in bytes.
699  #[cfg_attr(not(tarpaulin), inline(always))]
700  pub const fn size(&self) -> usize {
701    self.size
702  }
703}
704/// Why a packet could not be carried across the boundary — its payload,
705/// or the side data that comes with it.
706///
707/// Every arm means the bytes are real and this crate could not carry
708/// them — never that there were none. "No payload" is `Ok(None)` from
709/// [`payload_of`], and keeping the two apart is the whole point of the
710/// type: a demuxer that reads a malformed packet as an empty marker
711/// drops a video packet and carries on as though the file said so. The
712/// side-data arms exist for the same reason one tier along — a packet
713/// whose side data cannot be carried whole is refused, never delivered
714/// with some of it.
715#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error, IsVariant, Unwrap, TryUnwrap)]
716#[unwrap(ref, ref_mut)]
717#[try_unwrap(ref, ref_mut)]
718pub enum PacketBufferError {
719  /// The payload is larger than the budget in force. Refused before
720  /// the copy.
721  #[error(transparent)]
722  PacketTooLarge(#[from] PacketTooLarge),
723
724  /// The payload does not lie inside the packet's own buffer.
725  #[error(transparent)]
726  Bounds(#[from] Bounds),
727
728  /// A packet declares more side-data entries than this crate will
729  /// walk, or a negative count.
730  #[error(transparent)]
731  SideDataEntries(#[from] SideDataEntries),
732
733  /// A packet declares side-data entries and carries no array to read
734  /// them from.
735  #[error(transparent)]
736  SideDataArray(#[from] SideDataArray),
737
738  /// A side-data entry declares bytes it does not carry.
739  #[error(transparent)]
740  SideDataPayload(#[from] SideDataPayload),
741
742  /// A packet's side data is larger than this crate will copy.
743  #[error(transparent)]
744  SideDataBytes(#[from] SideDataBytes),
745
746  /// A packet carries flag bits the portable vocabulary cannot hold.
747  #[error(transparent)]
748  UnrepresentableFlags(#[from] UnrepresentableFlags),
749
750  /// A packet is marked `AV_PKT_FLAG_TRUSTED`, so its payload may hold
751  /// pointers rather than bytes. See [`TrustedPayload`].
752  #[error(transparent)]
753  TrustedPayload(#[from] TrustedPayload),
754
755  /// The capture itself failed — an allocation on the owned lane, a
756  /// refcount on the view lane. See [`CaptureFailed`].
757  #[error(transparent)]
758  CaptureFailed(#[from] CaptureFailed),
759
760  /// The payload's buffer is referenced by something other than the
761  /// packet it came from. See [`SharedPayload`].
762  #[error(transparent)]
763  SharedPayload(#[from] SharedPayload),
764
765  /// Out of memory copying a side-data entry.
766  #[error(transparent)]
767  SideDataAlloc(#[from] SideDataAlloc),
768}
769
770impl PacketBufferError {
771  /// Whether the demux session should **park** the packet this refusal
772  /// came from and re-attempt it on the next pull.
773  ///
774  /// Deliberately not public, and deliberately named for the decision
775  /// rather than for a property of the error. It was briefly public as
776  /// `is_transient`, which promised more than an error enum can know:
777  /// whether retrying helps depends on *what was retried*.
778  /// `SharedPayload` is permanent for a caller who keeps their other
779  /// reference and retryable the moment they drop it;
780  /// `CaptureFailed` is worth another attempt only if the packet still
781  /// exists to attempt, which on a **consuming** conversion it does
782  /// not. Only the demux loop knows both halves — it still holds the
783  /// packet, and it knows nobody else does.
784  ///
785  /// So this answers one question for one caller. An allocation that
786  /// failed says nothing about the packet, and the demux loop is
787  /// holding the bytes; everything else is a fact about the packet
788  /// itself, and parking it would answer every later pull with the same
789  /// error instead of letting the session make progress.
790  ///
791  /// **The door left open:** a public retry signal would have to know
792  /// which operation produced the error and what the caller still
793  /// holds — an operation-aware answer, not a property of this enum.
794  /// If one is ever wanted it is designed then, not approximated now.
795  #[inline]
796  pub(crate) const fn parks_in_demux(&self) -> bool {
797    matches!(self, Self::CaptureFailed(_) | Self::SideDataAlloc(_))
798  }
799}
800
801/// `AV_PKT_FLAG_TRUSTED` as the bit the portable `PacketFlags` byte
802/// carries it in.
803///
804/// The core vocabulary deliberately does not *name* this flag — it is
805/// FFmpeg's, not a portable fact about packets — but `from_bits_retain`
806/// keeps the bit, so this crate can recognise its own flag coming back
807/// without the core growing a constant for it.
808pub(crate) const TRUSTED_BIT: u8 = ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED as u8;
809
810/// Compile-time proof that the flag really does fit the byte, so the
811/// cast above cannot silently become a different bit.
812const _: () = {
813  assert!(
814    ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED > 0
815      && ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED <= u8::MAX as std::ffi::c_int,
816    "AV_PKT_FLAG_TRUSTED no longer fits the portable flag byte",
817  );
818};
819
820/// Payload for [`PacketBufferError::TrustedPayload`] and
821/// [`crate::boundary::PacketBuildError::TrustedPayload`].
822///
823/// A packet carrying `AV_PKT_FLAG_TRUSTED`, refused on both legs.
824///
825/// # Why a flag makes a payload uncarriable
826///
827/// `AV_PKT_FLAG_TRUSTED` is FFmpeg's marker for a packet whose bytes
828/// came from a source the *decoder* may treat as its own — and the
829/// wrapped-AVFrame producers use it for exactly that: the payload is
830/// not media, it is a **structure containing pointers to other live
831/// objects** (an `AVFrame` and its buffers), passed by address between
832/// components inside one FFmpeg pipeline.
833///
834/// This crate copies bytes. A pointer copied by value is not owned by
835/// the copy — and that is not a gap this crate can close, because there
836/// is no bound on what a payload's pointers might reach. So the
837/// amputation has a corollary:
838///
839/// > **A payload that carries addresses instead of bytes cannot be
840/// > carried.** Copying it produces a message that looks owned, is
841/// > `Send + Sync + 'static` by every type-level test, and dangles the
842/// > moment its source is dropped — a use-after-free reachable through
843/// > entirely safe API.
844///
845/// Refusing is not conservatism, it is the only correct answer: the
846/// contract this crate exists to keep says every byte leaving FFmpeg is
847/// copied once into memory Rust owns, and a pointer cannot be.
848///
849/// Refused at **both** legs, because either one alone leaves the loop
850/// open: copy-out ([`payload_of`]) is where such a packet would enter
851/// the graph, and the reverse builders are where a flag that survived
852/// some other route would be handed back to a decoder that trusts it.
853#[derive(Debug, Clone, Copy, PartialEq, Eq)]
854pub struct TrustedPayload {
855  len: usize,
856}
857
858impl TrustedPayload {
859  /// Constructs a `TrustedPayload` payload.
860  #[inline]
861  pub const fn new(len: usize) -> Self {
862    Self { len }
863  }
864  /// How many bytes the packet declared.
865  #[inline]
866  pub const fn len(&self) -> usize {
867    self.len
868  }
869  /// Whether the refused packet declared no bytes.
870  #[inline]
871  pub const fn is_empty(&self) -> bool {
872    self.len == 0
873  }
874}
875
876impl core::fmt::Display for TrustedPayload {
877  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
878    write!(
879      f,
880      "packet of {} bytes carries AV_PKT_FLAG_TRUSTED; a payload that may hold \
881       pointers to other objects cannot be copied into an owned carrier",
882      self.len,
883    )
884  }
885}
886
887impl std::error::Error for TrustedPayload {}
888
889/// The payload of a raw `AVPacket`, copied out.
890///
891/// Shared by the four timed boundary conversions, the attachment
892/// conversion, and the demuxer's capture of `AVStream.attached_pic` —
893/// an `AVPacket` embedded in the stream by value, which no safe
894/// wrapper reaches. One implementation, so the empty-versus-malformed
895/// distinction cannot drift between them.
896///
897/// `Ok(None)` means the packet carries no payload at all: an empty
898/// marker, which some demuxers emit. That is a fact about the packet
899/// and is kept apart from [`PacketBufferError`], which is a failure to
900/// take a payload that *is* there.
901///
902/// A packet whose `buf` is null — a stack- or arena-allocated
903/// `AVPacket` — still reads as "no payload", exactly as it did before
904/// the amputation. It is tempting now that the bytes are copied to
905/// serve those from `data` / `size` directly, and that is precisely the
906/// case with no owning buffer to bound the read against: the claim
907/// would have to be taken on faith.
908///
909/// # Safety
910///
911/// `pkt` must be a live `*const AVPacket` for the duration of this
912/// call.
913pub(crate) unsafe fn payload_of<C: crate::FfmpegCarrier + crate::CarrierOps>(
914  pkt: *const ffmpeg_next::ffi::AVPacket,
915  budget: usize,
916  provenance: PayloadProvenance,
917) -> Result<Option<C::Buffer>, PacketBufferError> {
918  // SAFETY: `pkt` is live per the contract above; `.buf`, `.data` and
919  // `.size` are public fields on `AVPacket`, and `buf` may be null
920  // (stack-allocated packets).
921  let buf_ptr = unsafe { (*pkt).buf };
922  let data_ptr = unsafe { (*pkt).data };
923  let size_raw = unsafe { (*pkt).size };
924  // **The uncarriable-payload refusal, ahead of everything.** See
925  // [`TrustedPayload`]: this flag marks a payload that may be a
926  // structure of pointers into other live objects rather than media
927  // bytes, and copying those bytes would mint an owned-looking carrier
928  // full of addresses that dangle as soon as the source is dropped.
929  //
930  // Judged before the empty-payload answer as well as before the copy:
931  // "there is nothing to take here" is the wrong reply to a packet this
932  // crate must not take *anything* from.
933  //
934  // SAFETY: `pkt` is live per the contract; `flags` is a public `c_int`
935  // field, read as the integer it is.
936  let flags_raw = unsafe { (*pkt).flags };
937  if flags_raw & ffmpeg_next::ffi::AV_PKT_FLAG_TRUSTED != 0 {
938    return Err(PacketBufferError::TrustedPayload(TrustedPayload::new(
939      size_raw.max(0) as usize,
940    )));
941  }
942  if buf_ptr.is_null() || data_ptr.is_null() || size_raw <= 0 {
943    return Ok(None);
944  }
945  let len = size_raw as usize;
946  // **The budget, before anything is read or allocated.** Judged on
947  // the declared length rather than on what the copy turns out to
948  // cost, because the point is to refuse without paying. Ahead of the
949  // bounds check too: a forged `size` is exactly what both exist for,
950  // and the cheaper judgement goes first.
951  if len > budget {
952    return Err(PacketBufferError::PacketTooLarge(PacketTooLarge::new(
953      len, budget,
954    )));
955  }
956  // SAFETY: `buf_ptr` is a live `AVBufferRef` owned by the packet.
957  let buf_data = unsafe { (*buf_ptr).data };
958  let size = unsafe { (*buf_ptr).size };
959  if buf_data.is_null() {
960    return Err(PacketBufferError::Bounds(Bounds::new(0, len, size)));
961  }
962  // `AVPacket` guarantees `data` lies within
963  // `buf->data .. buf->data + buf->size`. Checked before the copy, not
964  // instead of it: 0.8 formed a view over the claimed range and a
965  // malformed `size` handed out a slice nobody read; 0.9 reads every
966  // byte of it, so an unchecked claim is an out-of-bounds read rather
967  // than a latent one.
968  let offset = (data_ptr as usize).wrapping_sub(buf_data as usize);
969  match offset.checked_add(len) {
970    Some(end) if end <= size => {}
971    _ => {
972      return Err(PacketBufferError::Bounds(Bounds::new(offset, len, size)));
973    }
974  }
975  // **The sharing question, asked before any byte is read.**
976  //
977  // Everything below reads the payload — the view lane by handing out a
978  // span over it, the owned lane by copying it — so a buffer somebody
979  // else references has to be classified before it is touched. What
980  // matters is not the count but **who** the other holder is, and only
981  // the caller of this function knows that: see [`PayloadProvenance`]
982  // for the dichotomy and [`PayloadProvenance::route`] for the table.
983  //
984  // Placed here rather than inside the capture so the ordering is a
985  // property of this function rather than of two lane impls: nothing
986  // between the bounds proof and this decision touches the payload.
987  //
988  // SAFETY: `buf_ptr` is a live `AVBufferRef` owned by the packet;
989  // `av_buffer_get_ref_count` only reads its atomic.
990  let references = unsafe { ffmpeg_next::ffi::av_buffer_get_ref_count(buf_ptr.cast_const()) };
991  let route = provenance.route(references != 1);
992  if route == CaptureRoute::Refuse {
993    return Err(PacketBufferError::SharedPayload(SharedPayload::new(
994      references,
995    )));
996  }
997
998  // **The capture, and the only step the two lanes spell differently.**
999  // Everything above — the `TRUSTED` refusal, the empty answer, the
1000  // budget, the extent proof — is shared, which is what keeps the view
1001  // lane from having to re-earn a single one of them.
1002  //
1003  // The **packet-payload** capture, not the general one: this range is
1004  // an `AVPacket`'s payload inside that packet's own buffer, which is
1005  // the one place libavformat's trailing-padding contract applies. The
1006  // view lane records that, and the send leg is the only thing that
1007  // reads it back — see `boundary::share_or_copy`.
1008  //
1009  // SAFETY: `offset + len` was just proved to lie inside `buf_ptr`'s
1010  // own `size`, and `buf_ptr` is a live `AVBufferRef` the packet owns.
1011  let carried = match route {
1012    CaptureRoute::Capture => unsafe { C::capture_packet_payload(buf_ptr, offset, len) },
1013    CaptureRoute::Copy => {
1014      // A demux-delivered packet whose buffer libavformat also holds.
1015      // Reading it here is race-free — every other reference is
1016      // C-owned, no `ffmpeg_next::Packet` wraps one, and this crate
1017      // holds the `AVFormatContext` exclusively for the duration of
1018      // the call — but the copy is what keeps that argument confined
1019      // to *this* call instead of to the carrier's whole life.
1020      //
1021      // SAFETY: the extent was proved above and `buf_data` is
1022      // non-null.
1023      let bytes = unsafe { core::slice::from_raw_parts(buf_data.add(offset).cast_const(), len) };
1024      C::from_bytes(bytes)
1025    }
1026    // Answered before the payload was touched.
1027    CaptureRoute::Refuse => unreachable!("a refusal returns above"),
1028  };
1029  carried
1030    .map(Some)
1031    .ok_or(PacketBufferError::CaptureFailed(CaptureFailed::new(len)))
1032}
1033
1034#[cfg(test)]
1035mod tests {
1036  use super::*;
1037  use crate::limits::DEFAULT_MAX_PACKET_BYTES;
1038  use ffmpeg_next::{Packet, packet::Ref};
1039
1040  #[test]
1041  fn a_real_payload_is_copied_out_whole() {
1042    let packet = Packet::copy(&[1u8, 2, 3, 4]);
1043    // SAFETY: `packet` owns a live `AVPacket` for the call.
1044    let payload = unsafe {
1045      payload_of::<crate::Owned>(
1046        packet.as_ptr(),
1047        DEFAULT_MAX_PACKET_BYTES,
1048        PayloadProvenance::CallerSupplied,
1049      )
1050    }
1051    .expect("a well-formed packet is carriable")
1052    .expect("present");
1053    assert_eq!(payload.as_ref(), &[1, 2, 3, 4]);
1054  }
1055
1056  #[test]
1057  fn the_copy_outlives_the_packet_it_came_from() {
1058    // The whole point of the amputation: FFmpeg's allocation is gone
1059    // and the bytes are still here.
1060    let packet = Packet::copy(&[9u8, 8, 7]);
1061    // SAFETY: `packet` owns a live `AVPacket` for the call.
1062    let payload = unsafe {
1063      payload_of::<crate::Owned>(
1064        packet.as_ptr(),
1065        DEFAULT_MAX_PACKET_BYTES,
1066        PayloadProvenance::CallerSupplied,
1067      )
1068    }
1069    .expect("carriable")
1070    .expect("present");
1071    let shared = payload.clone();
1072    assert!(shared.ptr_eq(&payload), "the clone copied the bytes");
1073    drop(packet);
1074    drop(payload);
1075    assert_eq!(shared.as_ref(), &[9, 8, 7]);
1076  }
1077
1078  #[test]
1079  fn an_empty_packet_has_no_payload_rather_than_a_failure() {
1080    let packet = Packet::empty();
1081    // SAFETY: `packet` owns a live `AVPacket` for the call.
1082    assert!(
1083      unsafe {
1084        payload_of::<crate::Owned>(
1085          packet.as_ptr(),
1086          DEFAULT_MAX_PACKET_BYTES,
1087          PayloadProvenance::CallerSupplied,
1088        )
1089      }
1090      .expect("not a failure")
1091      .is_none()
1092    );
1093  }
1094
1095  #[test]
1096  fn a_payload_outside_its_own_buffer_is_refused_before_a_byte_is_read() {
1097    use ffmpeg_next::packet::Mut;
1098    let mut packet = Packet::copy(&[1u8, 2, 3, 4]);
1099    // SAFETY: `packet` owns a live `AVPacket`; `size` is a public
1100    // field. The forged claim is the read this check exists to stop.
1101    unsafe {
1102      (*packet.as_mut_ptr()).size = 1 << 20;
1103    }
1104    // SAFETY: `packet` owns a live `AVPacket` for the call.
1105    assert!(matches!(
1106      unsafe {
1107        payload_of::<crate::Owned>(
1108          packet.as_ptr(),
1109          DEFAULT_MAX_PACKET_BYTES,
1110          PayloadProvenance::CallerSupplied,
1111        )
1112      },
1113      Err(PacketBufferError::Bounds(_)),
1114    ));
1115  }
1116
1117  #[test]
1118  fn the_shared_empty_carrier_is_one_allocation() {
1119    let a = FfmpegBytes::empty();
1120    let b = FfmpegBytes::empty();
1121    assert!(a.is_empty());
1122    assert_eq!(a.len(), 0);
1123    assert!(a.ptr_eq(&b), "the empty carrier is shared, not remade");
1124    // And a zero-length copy lands on that same allocation rather than
1125    // minting its own.
1126    assert!(FfmpegBytes::copy_from_slice(&[]).ptr_eq(&a));
1127  }
1128
1129  #[test]
1130  fn copy_out_is_owned_and_shareable() {
1131    fn owned_and_shareable<T: Send + Sync + Clone + 'static>(_: &T) {}
1132    let carrier = FfmpegBytes::copy_from_slice(&[4u8, 5, 6]);
1133    owned_and_shareable(&carrier);
1134    assert_eq!(carrier.as_ref(), &[4, 5, 6]);
1135    // Terse `Debug` — the bytes never reach a log line through it.
1136    let rendered = format!("{carrier:?}");
1137    assert!(rendered.contains("len: 3"), "got {rendered}");
1138    assert!(!rendered.contains('4'), "got {rendered}");
1139  }
1140}
1141
1142/// Where the packet a payload is taken from came from — and therefore
1143/// what a second reference to its buffer can do.
1144///
1145/// The dichotomy is **delivered by libavformat** versus **handed over
1146/// by a caller**, and it is about who can *write*, not how many
1147/// references there are.
1148///
1149/// * A packet this crate's own read loop just took from
1150///   `av_read_frame` — or hoisted out of `AVStream.attached_pic` — may
1151///   well share its buffer, and every other reference to it is
1152///   libavformat's. FFmpeg writes through a buffer only after
1153///   `av_buffer_make_writable`, which *copies* when the buffer is
1154///   shared; and while this crate is reading, it holds the
1155///   `AVFormatContext` exclusively, so no libavformat code is running
1156///   at all. There is no safe-Rust `data_mut` on any of those
1157///   references, because no `ffmpeg_next::Packet` wraps them.
1158/// * A packet a **caller** hands over may share its buffer with
1159///   another `ffmpeg_next::Packet` — and that type's `data_mut` writes
1160///   in place from safe code, without consulting writability. That is
1161///   the writer the uniqueness rule exists for, and it may be on
1162///   another thread.
1163///
1164/// Spelled out as a parameter rather than assumed at the call sites,
1165/// because a blanket "refcount must be one" looked right and was twice
1166/// wrong: it refused every embedded cover picture, and then every
1167/// packet from a queue-backed subtitle demuxer, which delivers
1168/// `av_packet_ref`s of originals it keeps in its own queue.
1169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1170pub(crate) enum PayloadProvenance {
1171  /// A packet a caller handed to a public conversion. A second
1172  /// reference may be a `Packet` with a safe `data_mut`; shared is
1173  /// refused by name.
1174  CallerSupplied,
1175  /// A packet this crate's demux loop just received from
1176  /// `av_read_frame`. Secondary references are libavformat's own.
1177  DemuxDelivered,
1178  /// The container's parked picture, whether hoisted at open or queued
1179  /// as a stream's first packet. Written once while the container was
1180  /// opened and never again.
1181  AttachedPicture,
1182}
1183
1184/// How a payload of a given provenance may be captured once its extent
1185/// is proved.
1186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1187pub(crate) enum CaptureRoute {
1188  /// The ordinary road: the view lane takes a window, the owned lane
1189  /// copies.
1190  Capture,
1191  /// Both lanes copy. The bytes are safe to *read* — no safe-Rust
1192  /// writer exists — but no long-lived window may be opened onto a
1193  /// buffer somebody else also holds unless something stronger than
1194  /// "nobody is writing right now" is true of it.
1195  Copy,
1196  /// Refuse without reading a byte.
1197  Refuse,
1198}
1199
1200impl PayloadProvenance {
1201  /// What to do with a payload whose buffer has `shared` other
1202  /// references.
1203  ///
1204  /// | provenance | unique | shared | the argument |
1205  /// |---|---|---|---|
1206  /// | [`Self::CallerSupplied`] | capture | **refuse** | a second `Packet`'s `data_mut` writes in place from safe code, possibly on another thread |
1207  /// | [`Self::DemuxDelivered`] | capture | **copy** | the read is race-free — every other reference is C-owned and the context is held exclusively — but a *window* would outlive that exclusivity, and FFmpeg's copy-on-write discipline is a weaker guarantee than this crate wants under a long-lived span |
1208  /// | [`Self::AttachedPicture`] | capture | **capture** | `AVStream.attached_pic` is written once while the container opens and never again, so a window onto it is as stable as one onto a private buffer |
1209  ///
1210  /// The middle row is the deliberate one. Sharing there would have
1211  /// rested on "libavformat honours its own writability rules
1212  /// forever"; copying rests on "nothing can be writing while we hold
1213  /// the context", which is a fact about *this* call and needs no
1214  /// promise about anyone's future behaviour. Subtitle queues — the
1215  /// shape that produced this row — carry payloads measured in bytes,
1216  /// so the copy is not a cost worth an argument.
1217  #[inline]
1218  pub(crate) const fn route(self, shared: bool) -> CaptureRoute {
1219    match (self, shared) {
1220      (_, false) | (Self::AttachedPicture, true) => CaptureRoute::Capture,
1221      (Self::DemuxDelivered, true) => CaptureRoute::Copy,
1222      (Self::CallerSupplied, true) => CaptureRoute::Refuse,
1223    }
1224  }
1225}
1226
1227/// A packet whose payload buffer somebody else still references.
1228///
1229/// **Refused without reading a byte of it, and that is the whole
1230/// point.** A refcount above one is exactly the state in which another
1231/// handle to the same allocation may exist — `ffmpeg_next::Packet`
1232/// hands out `&mut [u8]` through `data_mut` from entirely safe code,
1233/// and a `Packet` is `Send`, so that handle may be on another thread
1234/// writing right now. Forming a `&[u8]` over those bytes is a data race
1235/// whether the bytes are then viewed *or copied*: the copy needs the
1236/// read, and the read is the race.
1237///
1238/// An earlier round answered this shape with a silent copy, reasoning
1239/// that a copy is always sound and keeps the API total. That was wrong
1240/// in the direction that matters — it traded soundness for totality.
1241/// The refcount protects the allocation's *lifetime*; it says nothing
1242/// about who may be writing into it.
1243///
1244/// The ordinary roads never see this: a packet from `av_read_frame` is
1245/// uniquely referenced, and so is one the caller cloned successfully.
1246/// What produces it is a second reference the caller may not know they
1247/// have — see `ffmpeg_next::Packet::clone`, which ignores
1248/// `av_packet_make_writable`'s return code.
1249#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1250#[error(
1251  "packet payload buffer is shared ({references} references): its bytes cannot be read \
1252   without racing whoever else holds it"
1253)]
1254pub struct SharedPayload {
1255  references: i32,
1256}
1257
1258impl SharedPayload {
1259  /// Constructs a `SharedPayload` payload.
1260  #[inline]
1261  #[must_use]
1262  pub const fn new(references: i32) -> Self {
1263    Self { references }
1264  }
1265
1266  /// References the payload's buffer had when it was refused.
1267  #[inline]
1268  #[must_use]
1269  pub const fn references(&self) -> i32 {
1270    self.references
1271  }
1272}
1273
1274/// Payload for [`PacketBufferError::CaptureFailed`].
1275///
1276/// The proofs all passed and the carrier still could not be formed:
1277/// `av_buffer_alloc` returned null on the owned lane, or
1278/// `av_buffer_ref` did on the view lane. Distinct from
1279/// [`Bounds`] on purpose — a malformed packet and an exhausted
1280/// allocator are different facts, and 0.8 reported both as an absent
1281/// payload.
1282#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
1283#[error("could not capture a {len}-byte payload: the allocator or the refcount refused")]
1284pub struct CaptureFailed {
1285  len: usize,
1286}
1287
1288impl CaptureFailed {
1289  /// Constructs a `CaptureFailed` payload.
1290  #[inline]
1291  pub const fn new(len: usize) -> Self {
1292    Self { len }
1293  }
1294  /// Bytes the capture was for.
1295  #[inline]
1296  pub const fn len(&self) -> usize {
1297    self.len
1298  }
1299  /// Whether the refused capture was of no bytes.
1300  #[inline]
1301  pub const fn is_empty(&self) -> bool {
1302    self.len == 0
1303  }
1304}