Skip to main content

mediadecode_ffmpeg/
limits.rs

1//! Resource ceilings — the finite budgets every copy across the FFmpeg
2//! boundary is checked against **before** it allocates.
3//!
4//! These seats are tier one and tier two of the [resource governance
5//! contract][gov]: what this crate allocates itself, and the FFmpeg
6//! knobs it sets on the caller's behalf. The contract also states what
7//! they do **not** bound, and what a deployment needing a hard memory
8//! bound puts underneath them — read it before sizing these for a
9//! hostile-input service.
10//!
11//! # Why these exist
12//!
13//! 0.9 made every exit copy (see [the amputation contract][law]). A copy
14//! is a decision to allocate whatever the file asks for, and a container
15//! is untrusted input: a header claiming 100000×100000 pixels, a packet
16//! claiming a gigabyte, a Matroska with a thousand attached "fonts" all
17//! cost nothing to write and everything to honour. Through 0.8 the
18//! frame and packet payloads were *views*, so an absurd claim cost a
19//! refcount; from 0.9 it costs memory, and the claim has to be judged
20//! before it is paid.
21//!
22//! Every seat here is a **finite default**, not an `Option`. There is no
23//! "unlimited" spelling on purpose: the shape that lets a caller ask for
24//! no ceiling is the shape a caller reaches for once, in a hurry, and
25//! never revisits. A caller who needs more says how much more.
26//!
27//! # Two layers, one number
28//!
29//! [`FrameLimits::max_pixels`] is enforced twice: once here, against the
30//! frame this crate is about to copy, and once inside libavcodec, by
31//! writing the same number to `AVCodecContext.max_pixels` when a decoder
32//! is opened. The second is the one that matters most — it makes the
33//! decoder refuse before allocating *its* huge frame, which this crate
34//! would otherwise only get to reject after FFmpeg had already paid for
35//! it.
36//!
37//! # The house shape
38//!
39//! `DEFAULT_*` consts, `Copy` options structs with `new` / getters /
40//! `with_*` / `set_*`, and a `with_*` seat on each session — the same
41//! shape [`crate::VideoDecoder::with_max_probe_pending_bytes`] and its
42//! [`DEFAULT_MAX_PROBE_PENDING_BYTES`](crate::decoder::DEFAULT_MAX_PROBE_PENDING_BYTES)
43//! already established for the probe-replay budget.
44//!
45//! [law]: mediadecode::adapter#the-d-seat-amputation-contract
46//! [gov]: mediadecode::adapter#the-resource-governance-contract
47
48/// Default ceiling on a decoded frame's pixel count — 256 mebipixels.
49///
50/// **Why this number.** The largest picture anything ships is 8K UHD
51/// (7680×4320 ≈ 33 Mpx); 16K×16K, which nothing does, is 268 Mpx. This
52/// default sits exactly there: every real frame passes, and the
53/// hand-written header claiming 100000×100000 (10 Gpx) is refused
54/// before a byte is allocated — by libavcodec first, since the same
55/// number is written to `AVCodecContext.max_pixels`, and by this crate
56/// second.
57///
58/// FFmpeg's own default for that option is `INT_MAX`, i.e. no ceiling
59/// worth the name. Overriding it is the point.
60pub const DEFAULT_MAX_PIXELS: u64 = 256 * 1024 * 1024;
61
62/// Default ceiling on the bytes one decoded frame may export — 512 MiB.
63///
64/// **Why this number.** Pixels alone do not bound the copy: bit depth,
65/// plane count and stride padding all multiply it. The widest realistic
66/// frame is 8K 4:4:4 16-bit with alpha (7680×4320×8 bytes ≈ 253 MiB);
67/// 8K P010 is ~96 MiB and 4K P010 ~24 MiB. 512 MiB clears the worst of
68/// those by 2× and still bounds a single frame to something a process
69/// can survive.
70///
71/// Checked against the sum of what the planes will actually export —
72/// after the stride decision, so it is the number this crate is about
73/// to allocate rather than an estimate of it.
74pub const DEFAULT_MAX_FRAME_BYTES: usize = 512 * 1024 * 1024;
75
76/// Default ceiling on one packet's payload — 1 GiB.
77///
78/// **Why this number.** Deliberately ceiling-class rather than tuned:
79/// `AVPacket.size` is a `c_int`, so 2 GiB is the structural maximum and
80/// this halves it. Real packets are nowhere near — an intra-only 8K
81/// ProRes 4444 XQ frame is ~10 MB, an uncompressed v210 8K frame ~88
82/// MB, and a whole-file attachment (the largest packet shape that
83/// exists) is bounded far below by
84/// [`DEFAULT_MAX_ATTACHMENT_BYTES`]. The job here is to refuse the
85/// forged `size` field, not to second-guess a codec.
86pub const DEFAULT_MAX_PACKET_BYTES: usize = 1024 * 1024 * 1024;
87
88/// Default ceiling on one attachment's payload — 64 MiB.
89///
90/// **Why this number.** An attachment is a whole file: cover art or a
91/// font. A generous cover is a 4000×4000 PNG at ~20 MB; the largest
92/// fonts in circulation are CJK families at ~30 MB. 64 MiB clears both
93/// and is two orders of magnitude under the packet ceiling, which is
94/// right — an attachment is the one payload captured *eagerly*, at
95/// open, before a caller has asked for anything.
96pub const DEFAULT_MAX_ATTACHMENT_BYTES: usize = 64 * 1024 * 1024;
97
98/// Default ceiling on **all** attachments in one file, together — 256
99/// MiB.
100///
101/// **Why this number, and why it is separate.** The per-attachment
102/// ceiling bounds one payload; nothing in it bounds a container that
103/// attaches four hundred of them. A subtitled release with a full ASS
104/// font set attaches perhaps ten to thirty fonts of a few MB each —
105/// call it 100 MB at the high end. 256 MiB clears that and refuses the
106/// file whose attachment table is the attack.
107///
108/// This budget is spent at **open**, because that is when this crate
109/// captures every attachment (the demux tier's "exactly one packet,
110/// before any timed packet" contract is kept by construction, and the
111/// construction is eager). A file that exhausts it fails to open, with
112/// the arm naming which track ran the total past the line.
113pub const DEFAULT_MAX_TOTAL_ATTACHMENT_BYTES: usize = 256 * 1024 * 1024;
114
115/// Default ceiling on one stream's codec-parameter heap — 16 MiB.
116///
117/// **What it bounds.** `AVCodecParameters` has three heap seats and all
118/// three come from the file: `extradata`, every entry of
119/// `coded_side_data`, and a custom `ch_layout` channel map. A track
120/// row's codec ticket mirrors all three, and rebuilding one for a
121/// decoder allocates all three again.
122///
123/// **Why this number.** The honest end of the range is small — H.264
124/// SPS/PPS extradata is tens of bytes, HEVC's a few hundred, FLAC and
125/// ALAC headers a couple of kilobytes. What sets the ceiling is
126/// `coded_side_data`: a MOV `prof` atom carries an **ICC profile**, and
127/// those are legitimately large — a few kilobytes for sRGB, half a
128/// megabyte to two megabytes for a real camera or display profile, and
129/// the largest device-link profiles in circulation reach roughly ten.
130/// 16 MiB clears all of that and still refuses the forged atom.
131pub const DEFAULT_MAX_CODEC_PARAMETER_BYTES: usize = 16 * 1024 * 1024;
132
133/// Default ceiling on **every** stream's codec-parameter heap in one
134/// file, together — 64 MiB.
135///
136/// **Why this number, and why it is separate.** The per-stream ceiling
137/// bounds one track's parameters; nothing in it bounds a container that
138/// declares two hundred tracks each carrying a two-megabyte profile.
139/// Four tracks with a large ICC profile apiece is the realistic high
140/// end, so 64 MiB clears it and refuses the stream table that is the
141/// attack.
142///
143/// Charged over **all** streams, not just the ones a caller will
144/// decode: the track table is built eagerly at open, so every stream's
145/// parameters are mirrored whether or not anybody asks for them.
146pub const DEFAULT_MAX_TOTAL_CODEC_PARAMETER_BYTES: usize = 64 * 1024 * 1024;
147
148/// The defaults have to hold together, and these say how — at compile
149/// time, because every term is a constant and a fact a build can check
150/// is a fact no test run has to.
151///
152/// Each clause is a claim the doc comments above make in prose:
153/// - every ceiling is finite and non-zero (a zero ceiling refuses
154///   everything, which is the opposite failure and just as bad);
155/// - 8K UHD, and the widest realistic frame, pass;
156/// - the 100000×100000 header does not;
157/// - a whole-file attachment budget below the per-attachment one, or a
158///   per-packet ceiling below the per-attachment one, would be
159///   incoherent — the narrower seat could never fire;
160/// - a per-packet ceiling above `c_int::MAX` could never fire either,
161///   since `AVPacket.size` cannot express it.
162const _: () = {
163  assert!(DEFAULT_MAX_PIXELS > 0 && DEFAULT_MAX_PIXELS < u64::MAX);
164  assert!(DEFAULT_MAX_FRAME_BYTES > 0 && DEFAULT_MAX_FRAME_BYTES < usize::MAX);
165  assert!(DEFAULT_MAX_PACKET_BYTES > 0 && DEFAULT_MAX_PACKET_BYTES < usize::MAX);
166  assert!(DEFAULT_MAX_ATTACHMENT_BYTES > 0);
167  assert!(DEFAULT_MAX_TOTAL_ATTACHMENT_BYTES > 0);
168
169  // 8K UHD — the largest picture anything ships — must decode.
170  assert!(7680 * 4320 < DEFAULT_MAX_PIXELS);
171  // And the widest realistic frame: 8K 4:4:4 16-bit with alpha.
172  assert!(7680 * 4320 * 8 < DEFAULT_MAX_FRAME_BYTES);
173
174  // **8K must decode in the widest format that exists, not just the
175  // widest realistic one.** The byte ceiling is pushed into libavcodec
176  // as a pixel ceiling priced at the worst per-pixel cost any format
177  // this build can emit — 16 bytes, reached by `rgbaf32` and its seven
178  // siblings — because a container's declared format is not an upper
179  // bound on what its decoder produces. That makes the effective pixel
180  // ceiling `max_frame_bytes / 16`, and this is the assertion that
181  // keeps 8K inside it: at 33.18 Mpx and 16 bytes an 8K `rgbaf32` frame
182  // is 506 MiB, which 512 MiB clears with about 1% to spare.
183  //
184  // If `DEFAULT_MAX_FRAME_BYTES` is ever lowered, or a future FFmpeg
185  // adds a format wider than 16 bytes per pixel, this fails the build
186  // rather than quietly refusing 8K at run time.
187  assert!(7680 * 4320 * 16 < DEFAULT_MAX_FRAME_BYTES);
188  // The header a fuzzer writes must not.
189  assert!(100_000 * 100_000 > DEFAULT_MAX_PIXELS);
190
191  assert!(DEFAULT_MAX_TOTAL_ATTACHMENT_BYTES >= DEFAULT_MAX_ATTACHMENT_BYTES);
192  assert!(DEFAULT_MAX_PACKET_BYTES >= DEFAULT_MAX_ATTACHMENT_BYTES);
193  assert!(DEFAULT_MAX_PACKET_BYTES <= i32::MAX as usize);
194
195  assert!(DEFAULT_MAX_CODEC_PARAMETER_BYTES > 0);
196  assert!(DEFAULT_MAX_TOTAL_CODEC_PARAMETER_BYTES >= DEFAULT_MAX_CODEC_PARAMETER_BYTES);
197  // A ten-megabyte device-link ICC profile is real media and must pass.
198  assert!(10 * 1024 * 1024 < DEFAULT_MAX_CODEC_PARAMETER_BYTES);
199
200  // **The two ICC policies agree, and this is what keeps them agreeing.**
201  // The same profile can arrive as a track parameter (`coded_side_data`)
202  // or as a decoded still's frame side data, and a ceiling that admits
203  // it on one road and drops it on the other is not a policy, it is an
204  // accident of which road the file took.
205  assert!(DEFAULT_MAX_IMAGE_SIDE_DATA_BYTES >= DEFAULT_MAX_CODEC_PARAMETER_BYTES);
206};
207
208/// What one decoded frame may cost.
209///
210/// Carried by every session that decodes frames and handed to the
211/// conversion that copies them. See the [module docs](self) for why the
212/// seats are finite and how [`Self::max_pixels`] reaches libavcodec as
213/// well as this crate.
214#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
215pub struct FrameLimits {
216  max_pixels: u64,
217  max_frame_bytes: usize,
218  max_image_side_data_bytes: usize,
219}
220
221impl Default for FrameLimits {
222  #[inline]
223  fn default() -> Self {
224    Self::new()
225  }
226}
227
228impl FrameLimits {
229  /// The defaults: [`DEFAULT_MAX_PIXELS`] and
230  /// [`DEFAULT_MAX_FRAME_BYTES`].
231  #[cfg_attr(not(tarpaulin), inline(always))]
232  pub const fn new() -> Self {
233    Self {
234      max_pixels: DEFAULT_MAX_PIXELS,
235      max_frame_bytes: DEFAULT_MAX_FRAME_BYTES,
236      max_image_side_data_bytes: DEFAULT_MAX_IMAGE_SIDE_DATA_BYTES,
237    }
238  }
239
240  /// Most pixels one decoded frame may have.
241  ///
242  /// Also written to `AVCodecContext.max_pixels` when a decoder is
243  /// opened from these limits, so libavcodec refuses an oversized
244  /// picture before allocating it.
245  #[cfg_attr(not(tarpaulin), inline(always))]
246  pub const fn max_pixels(&self) -> u64 {
247    self.max_pixels
248  }
249  /// Most bytes one decoded frame's planes may export, together.
250  #[cfg_attr(not(tarpaulin), inline(always))]
251  pub const fn max_frame_bytes(&self) -> usize {
252    self.max_frame_bytes
253  }
254  /// The ceiling on side data one decoded **still** may carry.
255  #[cfg_attr(not(tarpaulin), inline(always))]
256  pub const fn max_image_side_data_bytes(&self) -> usize {
257    self.max_image_side_data_bytes
258  }
259
260  /// Sets the pixel ceiling (consuming builder).
261  #[cfg_attr(not(tarpaulin), inline(always))]
262  #[must_use]
263  pub const fn with_max_pixels(mut self, value: u64) -> Self {
264    self.max_pixels = value;
265    self
266  }
267  /// Sets the per-frame byte ceiling (consuming builder).
268  #[cfg_attr(not(tarpaulin), inline(always))]
269  #[must_use]
270  pub const fn with_max_frame_bytes(mut self, value: usize) -> Self {
271    self.max_frame_bytes = value;
272    self
273  }
274  /// Sets the decoded-still side-data ceiling (consuming builder).
275  #[cfg_attr(not(tarpaulin), inline(always))]
276  #[must_use]
277  pub const fn with_max_image_side_data_bytes(mut self, value: usize) -> Self {
278    self.max_image_side_data_bytes = value;
279    self
280  }
281
282  /// Sets the pixel ceiling in place.
283  #[cfg_attr(not(tarpaulin), inline(always))]
284  pub const fn set_max_pixels(&mut self, value: u64) -> &mut Self {
285    self.max_pixels = value;
286    self
287  }
288  /// Sets the per-frame byte ceiling in place.
289  #[cfg_attr(not(tarpaulin), inline(always))]
290  pub const fn set_max_frame_bytes(&mut self, value: usize) -> &mut Self {
291    self.max_frame_bytes = value;
292    self
293  }
294  /// Sets the decoded-still side-data ceiling in place.
295  #[cfg_attr(not(tarpaulin), inline(always))]
296  pub const fn set_max_image_side_data_bytes(&mut self, value: usize) -> &mut Self {
297    self.max_image_side_data_bytes = value;
298    self
299  }
300}
301
302/// Default ceiling on the bytes libavformat may **read** while probing
303/// and analysing a container — 5 MiB, which is FFmpeg's own
304/// `probesize` default.
305///
306/// **What this seat is for, and what it is not.** Every other budget in
307/// this crate bounds a copy *this crate* makes. This one bounds work
308/// **libavformat does before this crate is handed anything**:
309/// `avformat_open_input` and `avformat_find_stream_info` build the
310/// attached-picture, extradata and coded-side-data buffers themselves,
311/// so the attachment budgets — which measure this crate's copies —
312/// arrive after the original allocation has already happened.
313///
314/// A parser cannot allocate from bytes it was never given, so bounding
315/// the read is the instrument that reaches furthest back. See
316/// [`DemuxLimits::max_probe_bytes`] for how far it actually reaches and
317/// what it does not.
318pub const DEFAULT_MAX_PROBE_BYTES: u64 = 5 * 1024 * 1024;
319
320/// Default ceiling on the number of streams a container may declare —
321/// FFmpeg's own `max_streams` default.
322///
323/// Each declared stream costs an `AVStream` and its `AVCodecParameters`
324/// inside libavformat, before this crate sees a track table, so a
325/// header claiming a hundred thousand streams is an allocation this
326/// crate's per-track budgets are downstream of.
327pub const DEFAULT_MAX_STREAMS: u32 = 1000;
328
329/// Default ceiling on the number of chapters a container may declare —
330/// 4096.
331///
332/// **Why this number.** A DVD's chapter count tops out at 99, a
333/// Matroska edition rarely reaches three figures, and the fattest real
334/// table anyone ships is an audiobook filing each of its tracks as a
335/// chapter — a few thousand at the far end. 4096 clears all of that and
336/// still bounds this crate's mirror at a few hundred kibibytes.
337///
338/// **Why a ceiling exists at all.** A chapter costs libavformat almost
339/// nothing — an `AVChapter` is four scalars and a dictionary pointer —
340/// so a header can declare an enormous table for very few bytes, and
341/// [`DEFAULT_MAX_STREAMS`] does not reach it: chapters are not streams.
342/// The mirror this crate builds is the larger of the two (an owned
343/// title, a `Timebase`, two `Timestamp`s per row), so the count is
344/// judged before a byte of it is reserved.
345pub const DEFAULT_MAX_CHAPTERS: u32 = 4096;
346
347/// Default ceiling on the bytes every chapter title in one file may
348/// hold together — 1 MiB.
349///
350/// **Why this number.** A chapter title is a line of prose: tens of
351/// bytes, a few hundred at the outside. 1 MiB is 256 bytes for each of
352/// [`DEFAULT_MAX_CHAPTERS`] chapters, so every real table passes with
353/// room to spare.
354///
355/// **Why an aggregate rather than a per-title cap.** A per-title cap
356/// already exists one layer down: the metadata reader refuses any
357/// single dictionary value past 64 KiB rather than truncating it. On
358/// its own that leaves the table's total at the count times that cap —
359/// 256 MiB at the two defaults. This seat is what turns those two
360/// finite numbers into a small one.
361pub const DEFAULT_MAX_TOTAL_CHAPTER_TITLE_BYTES: usize = 1024 * 1024;
362
363/// Default ceiling on the bytes every **stream's** retained metadata in
364/// one file may hold together — 4 MiB.
365///
366/// **What it bounds.** A track row keeps three values off each
367/// stream's metadata dictionary: the `filename` an attachment was
368/// attached under, its declared `mimetype`, and the track's
369/// `language`. All three are mirrored eagerly, for every admitted
370/// stream, before the first packet is read.
371///
372/// **Why a ceiling exists at all.** [`DEFAULT_MAX_STREAMS`] bounds how
373/// many streams a header may declare and says nothing about what each
374/// one may carry. At that ceiling, three values of the 64 KiB a single
375/// metadata value may reach — in bytes that are not UTF-8, so each one
376/// triples on the way through lossy decoding — is roughly 562 MiB
377/// retained during an open that has not been asked for a single
378/// packet. The path entrypoint has no hard read meter to fall back on,
379/// either.
380///
381/// **Why this number.** Real metadata is tiny: a filename is tens of
382/// bytes, a MIME type forty, a language tag three. A file with a full
383/// ASS font set — thirty attachments, each with a name and a type —
384/// spends a few kilobytes. 4 MiB clears every real file by orders of
385/// magnitude and turns the hostile one into a named refusal.
386pub const DEFAULT_MAX_TOTAL_STREAM_METADATA_BYTES: usize = 4 * 1024 * 1024;
387
388/// Default ceiling on the side data one decoded **still** may carry —
389/// the same 16 MiB as [`DEFAULT_MAX_CODEC_PARAMETER_BYTES`], and the
390/// same reason.
391///
392/// **Why the still road needs its own number.** The shared stream
393/// collector caps frame side data at 256 KiB in total and *silently
394/// drops* whatever does not fit. On a video stream that is defensible:
395/// side data there is small, per-frame, and repeated. On a still it is
396/// wrong twice over. A decoded image's side data is dominated by the
397/// one thing that is legitimately megabytes — an **ICC profile** — and
398/// the parameter budget next door already admits those up to 16 MiB, so
399/// the same profile was admitted as a track parameter and swallowed as
400/// a frame annotation. Worse, the drop is positional: entries after the
401/// cap are skipped, and `AV_FRAME_DATA_DISPLAYMATRIX` — the orientation
402/// this crate reads off a still — is a small entry that a large ICC
403/// profile ahead of it pushed out. A picture came back silently rotated
404/// wrong.
405///
406/// So the still road gets a seat sized to what it actually carries, and
407/// over-budget is a **named refusal** rather than a quiet truncation:
408/// side data that cannot be carried whole is a fact about the picture,
409/// not a detail to drop.
410pub const DEFAULT_MAX_IMAGE_SIDE_DATA_BYTES: usize = DEFAULT_MAX_CODEC_PARAMETER_BYTES;
411
412/// Default ceiling on the compressed bytes one **image** decode may be
413/// handed — 64 MiB, the attachment family.
414///
415/// **Why the attachment family and not the packet one.** What
416/// [`crate::FfmpegImageDecoder`] decodes *is* an attachment: a whole
417/// file a container handed over eagerly. When it arrives through the
418/// demuxer it has already been charged against
419/// [`DEFAULT_MAX_ATTACHMENT_BYTES`], and this seat is what keeps the
420/// same ceiling in force when a caller builds the packet itself — the
421/// one road that skips the demux tier entirely. A 1 GiB packet ceiling
422/// here would mean the direct road was a gigabyte more permissive than
423/// the demuxed one for the same bytes.
424pub const DEFAULT_MAX_IMAGE_INPUT_BYTES: usize = DEFAULT_MAX_ATTACHMENT_BYTES;
425
426/// What one packet's payload may cost.
427///
428/// Carried by the boundary conversions and by an open demux session.
429#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
430pub struct PacketLimits {
431  max_packet_bytes: usize,
432}
433
434impl Default for PacketLimits {
435  #[inline]
436  fn default() -> Self {
437    Self::new()
438  }
439}
440
441impl PacketLimits {
442  /// The default: [`DEFAULT_MAX_PACKET_BYTES`].
443  #[cfg_attr(not(tarpaulin), inline(always))]
444  pub const fn new() -> Self {
445    Self {
446      max_packet_bytes: DEFAULT_MAX_PACKET_BYTES,
447    }
448  }
449
450  /// Most bytes one packet's payload may carry.
451  #[cfg_attr(not(tarpaulin), inline(always))]
452  pub const fn max_packet_bytes(&self) -> usize {
453    self.max_packet_bytes
454  }
455
456  /// Sets the per-packet ceiling (consuming builder).
457  #[cfg_attr(not(tarpaulin), inline(always))]
458  #[must_use]
459  pub const fn with_max_packet_bytes(mut self, value: usize) -> Self {
460    self.max_packet_bytes = value;
461    self
462  }
463  /// Sets the per-packet ceiling in place.
464  #[cfg_attr(not(tarpaulin), inline(always))]
465  pub const fn set_max_packet_bytes(&mut self, value: usize) -> &mut Self {
466    self.max_packet_bytes = value;
467    self
468  }
469}
470
471/// What opening and running one **decoder** may spend.
472///
473/// Composes [`FrameLimits`] — what the frames it produces may cost —
474/// with the two things a decoder spends before it has produced
475/// anything: copying the caller's codec parameters into an
476/// `AVCodecContext`, and copying the caller's compressed bytes into an
477/// `AVPacket`.
478///
479/// Taken at `open` by every decoder session in this crate, for the
480/// reason [`FrameLimits`] gives: half of it is written into an
481/// `AVCodecContext` whose ceilings cannot move after `avcodec_open2`.
482#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
483pub struct DecoderLimits {
484  frame: FrameLimits,
485  max_codec_parameter_bytes: usize,
486  max_packet_bytes: usize,
487  max_image_input_bytes: usize,
488}
489
490impl Default for DecoderLimits {
491  #[inline]
492  fn default() -> Self {
493    Self::new()
494  }
495}
496
497impl DecoderLimits {
498  /// The defaults: [`FrameLimits::new`],
499  /// [`DEFAULT_MAX_CODEC_PARAMETER_BYTES`], [`DEFAULT_MAX_PACKET_BYTES`]
500  /// and [`DEFAULT_MAX_IMAGE_INPUT_BYTES`].
501  #[cfg_attr(not(tarpaulin), inline(always))]
502  pub const fn new() -> Self {
503    Self {
504      frame: FrameLimits::new(),
505      max_codec_parameter_bytes: DEFAULT_MAX_CODEC_PARAMETER_BYTES,
506      max_packet_bytes: DEFAULT_MAX_PACKET_BYTES,
507      max_image_input_bytes: DEFAULT_MAX_IMAGE_INPUT_BYTES,
508    }
509  }
510
511  /// What one decoded frame may cost.
512  #[cfg_attr(not(tarpaulin), inline(always))]
513  pub const fn frame(&self) -> FrameLimits {
514    self.frame
515  }
516  /// Most heap bytes the codec parameters this decoder is opened from
517  /// may hold.
518  ///
519  /// Enforced at the choke point every road into libavcodec passes
520  /// through, so a decoder cannot be opened over parameters nobody
521  /// measured.
522  #[cfg_attr(not(tarpaulin), inline(always))]
523  pub const fn max_codec_parameter_bytes(&self) -> usize {
524    self.max_codec_parameter_bytes
525  }
526  /// Most compressed bytes one packet handed to a **stream** decoder
527  /// may carry.
528  #[cfg_attr(not(tarpaulin), inline(always))]
529  pub const fn max_packet_bytes(&self) -> usize {
530    self.max_packet_bytes
531  }
532
533  /// [`Self::max_packet_bytes`] as the [`PacketLimits`] the boundary
534  /// conversions take, so the send leg and the receive leg are handed
535  /// the same seat rather than two numbers that could drift.
536  #[cfg_attr(not(tarpaulin), inline(always))]
537  pub const fn packet_limits(&self) -> PacketLimits {
538    PacketLimits::new().with_max_packet_bytes(self.max_packet_bytes)
539  }
540  /// Most compressed bytes one **image** decode may be handed. See
541  /// [`DEFAULT_MAX_IMAGE_INPUT_BYTES`] for why this is its own seat.
542  #[cfg_attr(not(tarpaulin), inline(always))]
543  pub const fn max_image_input_bytes(&self) -> usize {
544    self.max_image_input_bytes
545  }
546
547  /// Sets the frame ceilings (consuming builder).
548  #[cfg_attr(not(tarpaulin), inline(always))]
549  #[must_use]
550  pub const fn with_frame(mut self, value: FrameLimits) -> Self {
551    self.frame = value;
552    self
553  }
554  /// Sets the codec-parameter ceiling (consuming builder).
555  #[cfg_attr(not(tarpaulin), inline(always))]
556  #[must_use]
557  pub const fn with_max_codec_parameter_bytes(mut self, value: usize) -> Self {
558    self.max_codec_parameter_bytes = value;
559    self
560  }
561  /// Sets the per-packet ceiling (consuming builder).
562  #[cfg_attr(not(tarpaulin), inline(always))]
563  #[must_use]
564  pub const fn with_max_packet_bytes(mut self, value: usize) -> Self {
565    self.max_packet_bytes = value;
566    self
567  }
568  /// Sets the image-input ceiling (consuming builder).
569  #[cfg_attr(not(tarpaulin), inline(always))]
570  #[must_use]
571  pub const fn with_max_image_input_bytes(mut self, value: usize) -> Self {
572    self.max_image_input_bytes = value;
573    self
574  }
575
576  /// Sets the frame ceilings in place.
577  #[cfg_attr(not(tarpaulin), inline(always))]
578  pub const fn set_frame(&mut self, value: FrameLimits) -> &mut Self {
579    self.frame = value;
580    self
581  }
582  /// Sets the codec-parameter ceiling in place.
583  #[cfg_attr(not(tarpaulin), inline(always))]
584  pub const fn set_max_codec_parameter_bytes(&mut self, value: usize) -> &mut Self {
585    self.max_codec_parameter_bytes = value;
586    self
587  }
588  /// Sets the per-packet ceiling in place.
589  #[cfg_attr(not(tarpaulin), inline(always))]
590  pub const fn set_max_packet_bytes(&mut self, value: usize) -> &mut Self {
591    self.max_packet_bytes = value;
592    self
593  }
594  /// Sets the image-input ceiling in place.
595  #[cfg_attr(not(tarpaulin), inline(always))]
596  pub const fn set_max_image_input_bytes(&mut self, value: usize) -> &mut Self {
597    self.max_image_input_bytes = value;
598    self
599  }
600}
601
602/// What one demux session may spend: on any single packet, on any
603/// single attachment, on every attachment in the file together, and on
604/// the container's chapter table.
605///
606/// Handed to [`FfmpegDemuxer::open_with`](crate::FfmpegDemuxer::open_with)
607/// rather than set afterwards, because the attachment budget is spent
608/// *during* the open — every attachment payload is captured before the
609/// first timed packet is read, which is what makes the demux tier's
610/// delivery contract true by construction.
611#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
612pub struct DemuxLimits {
613  packet: PacketLimits,
614  max_attachment_bytes: usize,
615  max_total_attachment_bytes: usize,
616  max_codec_parameter_bytes: usize,
617  max_total_codec_parameter_bytes: usize,
618  max_probe_bytes: u64,
619  max_streams: u32,
620  max_chapters: u32,
621  max_total_chapter_title_bytes: usize,
622  max_total_stream_metadata_bytes: usize,
623}
624
625impl Default for DemuxLimits {
626  #[inline]
627  fn default() -> Self {
628    Self::new()
629  }
630}
631
632impl DemuxLimits {
633  /// The defaults: [`PacketLimits::new`],
634  /// [`DEFAULT_MAX_ATTACHMENT_BYTES`] and
635  /// [`DEFAULT_MAX_TOTAL_ATTACHMENT_BYTES`].
636  #[cfg_attr(not(tarpaulin), inline(always))]
637  pub const fn new() -> Self {
638    Self {
639      packet: PacketLimits::new(),
640      max_attachment_bytes: DEFAULT_MAX_ATTACHMENT_BYTES,
641      max_total_attachment_bytes: DEFAULT_MAX_TOTAL_ATTACHMENT_BYTES,
642      max_codec_parameter_bytes: DEFAULT_MAX_CODEC_PARAMETER_BYTES,
643      max_total_codec_parameter_bytes: DEFAULT_MAX_TOTAL_CODEC_PARAMETER_BYTES,
644      max_probe_bytes: DEFAULT_MAX_PROBE_BYTES,
645      max_streams: DEFAULT_MAX_STREAMS,
646      max_chapters: DEFAULT_MAX_CHAPTERS,
647      max_total_chapter_title_bytes: DEFAULT_MAX_TOTAL_CHAPTER_TITLE_BYTES,
648      max_total_stream_metadata_bytes: DEFAULT_MAX_TOTAL_STREAM_METADATA_BYTES,
649    }
650  }
651
652  /// The ceiling on bytes libavformat may read while probing and
653  /// analysing a container.
654  ///
655  /// # What this bounds, and what it does not
656  ///
657  /// **Bounded:** the total bytes libavformat is handed during
658  /// `avformat_open_input` and `avformat_find_stream_info`. It reaches
659  /// two ways — as `probesize` and `formatprobesize`, which every
660  /// entrypoint sets before the open, and, on the reader entrypoint, as
661  /// a hard byte meter on the `AVIOContext` itself: past the budget the
662  /// reader answers an I/O error, so the parser gets nothing more
663  /// whatever it asks for.
664  ///
665  /// **Not bounded:** allocation *amplification* inside a parser. A
666  /// container can describe, in a handful of bytes, a structure whose
667  /// in-memory form is much larger, and nothing outside libavformat can
668  /// see that happen. What this seat guarantees is that the input to
669  /// that amplification is finite and small; bounding its output is the
670  /// substrate's own hardening territory, and FFmpeg has its own
671  /// `max_streams` / `max_index_size` / `max_picture_buffer` seats for
672  /// exactly that — [`Self::max_streams`] sets the first of them.
673  ///
674  /// **Not bounded on the path entrypoint:** the byte meter needs an
675  /// `AVIOContext` this crate owns, and a path is opened by
676  /// libavformat's own protocol layer. `probesize` and
677  /// `formatprobesize` still apply there; the hard meter does not.
678  /// A caller who wants the meter on a file can open it as a reader.
679  #[cfg_attr(not(tarpaulin), inline(always))]
680  pub const fn max_probe_bytes(&self) -> u64 {
681    self.max_probe_bytes
682  }
683  /// The ceiling on streams a container may declare. See
684  /// [`Self::max_probe_bytes`] for why a seat inside libavformat is
685  /// worth setting at all.
686  #[cfg_attr(not(tarpaulin), inline(always))]
687  pub const fn max_streams(&self) -> u32 {
688    self.max_streams
689  }
690  /// Sets the probe-read ceiling (consuming builder).
691  #[cfg_attr(not(tarpaulin), inline(always))]
692  #[must_use]
693  pub const fn with_max_probe_bytes(mut self, value: u64) -> Self {
694    self.max_probe_bytes = value;
695    self
696  }
697  /// Sets the declared-stream ceiling (consuming builder).
698  #[cfg_attr(not(tarpaulin), inline(always))]
699  #[must_use]
700  pub const fn with_max_streams(mut self, value: u32) -> Self {
701    self.max_streams = value;
702    self
703  }
704
705  /// The ceiling on chapters a container may declare.
706  ///
707  /// Judged **before** the chapter table is reserved, so a header
708  /// claiming an enormous table is refused rather than mirrored. Unlike
709  /// [`Self::max_streams`], which is handed to libavformat and enforced
710  /// inside it, this one is this crate's own: libavformat has no
711  /// `max_chapters` knob, and an `AVChapter` is cheap enough there that
712  /// the probe budget does not reach the count either.
713  ///
714  /// A file over the ceiling **fails to open**, with
715  /// [`TooManyChapters`](crate::TooManyChapters) naming the declared
716  /// count.
717  #[cfg_attr(not(tarpaulin), inline(always))]
718  pub const fn max_chapters(&self) -> u32 {
719    self.max_chapters
720  }
721  /// The ceiling on bytes every chapter title in the file may hold
722  /// together.
723  ///
724  /// Charged title by title as the table is mirrored, and the open
725  /// fails with
726  /// [`ChapterTitleBudgetExhausted`](crate::ChapterTitleBudgetExhausted)
727  /// at the title that crosses it. The charge is made **after** that
728  /// one title is read rather than before, the same way
729  /// [`Self::max_total_attachment_bytes`] is charged: what bounds the
730  /// overshoot is the 64 KiB the metadata reader already refuses any
731  /// single value past.
732  #[cfg_attr(not(tarpaulin), inline(always))]
733  pub const fn max_total_chapter_title_bytes(&self) -> usize {
734    self.max_total_chapter_title_bytes
735  }
736  /// Sets the declared-chapter ceiling (consuming builder).
737  #[cfg_attr(not(tarpaulin), inline(always))]
738  #[must_use]
739  pub const fn with_max_chapters(mut self, value: u32) -> Self {
740    self.max_chapters = value;
741    self
742  }
743  /// Sets the whole-file chapter-title budget (consuming builder).
744  #[cfg_attr(not(tarpaulin), inline(always))]
745  #[must_use]
746  pub const fn with_max_total_chapter_title_bytes(mut self, value: usize) -> Self {
747    self.max_total_chapter_title_bytes = value;
748    self
749  }
750  /// Sets the declared-chapter ceiling in place.
751  #[cfg_attr(not(tarpaulin), inline(always))]
752  pub const fn set_max_chapters(&mut self, value: u32) -> &mut Self {
753    self.max_chapters = value;
754    self
755  }
756  /// Sets the whole-file chapter-title budget in place.
757  #[cfg_attr(not(tarpaulin), inline(always))]
758  pub const fn set_max_total_chapter_title_bytes(&mut self, value: usize) -> &mut Self {
759    self.max_total_chapter_title_bytes = value;
760    self
761  }
762
763  /// The ceiling on bytes every stream's retained metadata may hold
764  /// together — the `filename`, `mimetype` and `language` a track row
765  /// keeps, across every admitted stream.
766  ///
767  /// Charged value by value as the track table is built, and the open
768  /// fails with
769  /// [`TrackMetadataBudgetExhausted`](crate::TrackMetadataBudgetExhausted)
770  /// at the value that crosses it — **before** that value is copied.
771  /// The charge is the *decoded* size, because lossy decoding expands
772  /// bytes that are not UTF-8 threefold, so the raw length would
773  /// under-charge exactly the hostile case.
774  #[cfg_attr(not(tarpaulin), inline(always))]
775  pub const fn max_total_stream_metadata_bytes(&self) -> usize {
776    self.max_total_stream_metadata_bytes
777  }
778  /// Sets the whole-file stream-metadata budget (consuming builder).
779  #[cfg_attr(not(tarpaulin), inline(always))]
780  #[must_use]
781  pub const fn with_max_total_stream_metadata_bytes(mut self, value: usize) -> Self {
782    self.max_total_stream_metadata_bytes = value;
783    self
784  }
785  /// Sets the whole-file stream-metadata budget in place.
786  #[cfg_attr(not(tarpaulin), inline(always))]
787  pub const fn set_max_total_stream_metadata_bytes(&mut self, value: usize) -> &mut Self {
788    self.max_total_stream_metadata_bytes = value;
789    self
790  }
791
792  /// The per-packet budget timed packets are checked against.
793  #[cfg_attr(not(tarpaulin), inline(always))]
794  pub const fn packet(&self) -> PacketLimits {
795    self.packet
796  }
797  /// Most bytes one attachment may carry.
798  #[cfg_attr(not(tarpaulin), inline(always))]
799  pub const fn max_attachment_bytes(&self) -> usize {
800    self.max_attachment_bytes
801  }
802  /// Most bytes every attachment in the file may carry together.
803  #[cfg_attr(not(tarpaulin), inline(always))]
804  pub const fn max_total_attachment_bytes(&self) -> usize {
805    self.max_total_attachment_bytes
806  }
807  /// Most heap bytes one stream's codec parameters may hold —
808  /// `extradata`, `coded_side_data` and a custom channel map together.
809  #[cfg_attr(not(tarpaulin), inline(always))]
810  pub const fn max_codec_parameter_bytes(&self) -> usize {
811    self.max_codec_parameter_bytes
812  }
813  /// Most heap bytes every stream's codec parameters may hold together.
814  #[cfg_attr(not(tarpaulin), inline(always))]
815  pub const fn max_total_codec_parameter_bytes(&self) -> usize {
816    self.max_total_codec_parameter_bytes
817  }
818
819  /// Sets the per-packet budget (consuming builder).
820  #[cfg_attr(not(tarpaulin), inline(always))]
821  #[must_use]
822  pub const fn with_packet(mut self, value: PacketLimits) -> Self {
823    self.packet = value;
824    self
825  }
826  /// Sets the per-attachment ceiling (consuming builder).
827  #[cfg_attr(not(tarpaulin), inline(always))]
828  #[must_use]
829  pub const fn with_max_attachment_bytes(mut self, value: usize) -> Self {
830    self.max_attachment_bytes = value;
831    self
832  }
833  /// Sets the whole-file attachment budget (consuming builder).
834  #[cfg_attr(not(tarpaulin), inline(always))]
835  #[must_use]
836  pub const fn with_max_total_attachment_bytes(mut self, value: usize) -> Self {
837    self.max_total_attachment_bytes = value;
838    self
839  }
840  /// Sets the per-stream codec-parameter ceiling (consuming builder).
841  #[cfg_attr(not(tarpaulin), inline(always))]
842  #[must_use]
843  pub const fn with_max_codec_parameter_bytes(mut self, value: usize) -> Self {
844    self.max_codec_parameter_bytes = value;
845    self
846  }
847  /// Sets the whole-file codec-parameter budget (consuming builder).
848  #[cfg_attr(not(tarpaulin), inline(always))]
849  #[must_use]
850  pub const fn with_max_total_codec_parameter_bytes(mut self, value: usize) -> Self {
851    self.max_total_codec_parameter_bytes = value;
852    self
853  }
854
855  /// Sets the per-packet budget in place.
856  #[cfg_attr(not(tarpaulin), inline(always))]
857  pub const fn set_packet(&mut self, value: PacketLimits) -> &mut Self {
858    self.packet = value;
859    self
860  }
861  /// Sets the per-attachment ceiling in place.
862  #[cfg_attr(not(tarpaulin), inline(always))]
863  pub const fn set_max_attachment_bytes(&mut self, value: usize) -> &mut Self {
864    self.max_attachment_bytes = value;
865    self
866  }
867  /// Sets the whole-file attachment budget in place.
868  #[cfg_attr(not(tarpaulin), inline(always))]
869  pub const fn set_max_total_attachment_bytes(&mut self, value: usize) -> &mut Self {
870    self.max_total_attachment_bytes = value;
871    self
872  }
873  /// Sets the per-stream codec-parameter ceiling in place.
874  #[cfg_attr(not(tarpaulin), inline(always))]
875  pub const fn set_max_codec_parameter_bytes(&mut self, value: usize) -> &mut Self {
876    self.max_codec_parameter_bytes = value;
877    self
878  }
879  /// Sets the whole-file codec-parameter budget in place.
880  #[cfg_attr(not(tarpaulin), inline(always))]
881  pub const fn set_max_total_codec_parameter_bytes(&mut self, value: usize) -> &mut Self {
882    self.max_total_codec_parameter_bytes = value;
883    self
884  }
885}
886
887#[cfg(test)]
888mod tests;