1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! Error type returned by every parser and builder in this crate.
use crate::TrackId;
use alloc::string::String;
use thiserror::Error;
/// Crate-wide result alias.
pub type Result<T> = core::result::Result<T, Error>;
/// Error variants that parsers + builders can return.
///
/// Spec references inside `#[error(...)]` strings quote clauses from
/// ISO/IEC 14496-12:2015 (§4.2) where applicable.
// No longer `Eq` (only `PartialEq`): `Error::HlsAttrValue` wraps
// `broadcast_hls::Error`, which itself carries a non-`Eq` `f64` (issue
// #1140 T12 decimal validation).
#[derive(Debug, Error, PartialEq, Clone)]
#[non_exhaustive]
pub enum Error {
/// Input buffer was shorter than the smallest valid encoding for the type.
#[error(transparent)]
BufferTooShort(#[from] broadcast_common::error::BufferTooShort),
/// Box size was declared as 1 (triggers largesize) but fewer than 8 bytes available.
#[error("largesize indicated but buffer too short: need {need}, have {have}")]
LargesizeBufferTooShort {
/// Bytes required for largesize.
need: usize,
/// Bytes actually available.
have: usize,
},
/// Box type was 'uuid' but fewer than the required 16 bytes of usertype available.
#[error("uuid box indicated but buffer too short: need {need}, have {have}")]
UuidBufferTooShort {
/// Bytes required for usertype.
need: usize,
/// Bytes actually available.
have: usize,
},
/// A box claimed a size smaller than its header, which is impossible.
#[error("box size {size} is smaller than header ({header_size} bytes)")]
BoxSizeUnderflow {
/// Declared size.
size: u64,
/// Minimum header bytes.
header_size: usize,
},
/// Write buffer passed to `serialize_into` was smaller than `serialized_len()`.
#[error(transparent)]
OutputBufferTooSmall(#[from] broadcast_common::error::OutputBufferTooSmall),
/// A field had an invalid or reserved value.
#[error(transparent)]
InvalidValue(#[from] broadcast_common::error::InvalidValue),
/// A field was missing or structurally invalid.
#[error(transparent)]
InvalidField(#[from] broadcast_common::error::InvalidField),
/// A box did not carry the four-CC the parser expected.
#[error("unexpected box: expected {expected}")]
UnexpectedBox {
/// The four-CC (or description) the parser required.
expected: &'static str,
},
/// A caller-supplied argument violated a documented precondition (e.g. an
/// empty track list or a non-positive segment duration passed to the
/// [`Segmenter`](crate::segmenter::Segmenter)).
#[error("invalid input: {0}")]
InvalidInput(&'static str),
/// The zero-width `trun`s (no per-sample fields, so the buffer length cannot
/// bound their `sample_count`) of one `moof` — or of the run of `moof`s a
/// demuxer holds before their `mdat` — declare more samples in total than
/// [`MAX_ZERO_WIDTH_TRUN_SAMPLES`](crate::movie_fragment::MAX_ZERO_WIDTH_TRUN_SAMPLES)
/// allows. The allowance is aggregate: a 16-byte `trun` may legally declare
/// 2^20 samples, so a per-`trun` cap would let N such `trun`s allocate
/// N x 40 MiB.
#[error(
"trun sample_count with no per-sample fields exceeds MAX_ZERO_WIDTH_TRUN_SAMPLES \
({limit}, aggregate per moof)"
)]
ZeroWidthTrunBudgetExceeded {
/// The aggregate allowance (`MAX_ZERO_WIDTH_TRUN_SAMPLES`).
limit: usize,
},
/// A [`CodecConfig`](crate::pipeline::CodecConfig) has no ISOBMFF/fMP4
/// carriage in this crate (e.g. the WebM-native VP8 / Vorbis codecs, which
/// are carried in the IR for `{WebM} → IR → {WebM}` and inspection only).
#[error("codec {codec} has no ISOBMFF/fMP4 carriage in this crate")]
UnsupportedCodec {
/// The codec name (e.g. `"VP8"`, `"Vorbis"`).
codec: &'static str,
},
/// One program's set of elementary streams does not fit the PES
/// `stream_id` families (ISO/IEC 13818-1 Table 2-22).
///
/// The video family is `0xE0..=0xEF` and the audio family `0xC0..=0xDF`, so
/// at most 16 video and 32 audio elementary streams can be numbered in one
/// program. A 17th video stream would take `0xF0` (`ECM_stream`) and a 33rd
/// audio stream `0xE0` — inside the video range — so the muxer refuses
/// rather than mislabelling the stream (`TsMux` output, audit r05-W22).
#[error("too many {family} elementary streams: {max} is the stream_id family limit")]
TooManyElementaryStreams {
/// The `stream_id` family that overflowed (`"video"` or `"audio"`).
family: &'static str,
/// The family's size (16 video, 32 audio).
max: u8,
},
/// A CENC protection scheme ([`CencScheme`](broadcast_common::CencScheme))
/// this crate has no cipher implementation for was handed to the encrypt
/// or decrypt path.
///
/// `CencScheme` is `#[non_exhaustive]` and defined in `broadcast-common`
/// (issue #878), so a scheme ISO/IEC 23001-7 defines but this crate does
/// not implement (`cens`, `cbc1`) can reach a cipher-dispatch site here.
/// Those sites reject rather than fall back to a different cipher:
/// guessing would silently produce garbage plaintext (decrypt) or
/// content protected under a scheme the manifest does not advertise
/// (encrypt).
#[error("CENC scheme '{scheme}' has no cipher implementation in this crate")]
UnsupportedCencScheme {
/// The scheme that could not be applied.
scheme: broadcast_common::CencScheme,
},
/// A [`CodecConfig`](crate::pipeline::CodecConfig) has no Matroska CodecID
/// mapping in [`MkvMux`](crate::mkv_mux::MkvMux) (e.g. `Vvc`/`Flac`/`Ac4`/
/// `MpegH`/`Mpeg2Video`/`MpegAudio`/`Dts`/`Subtitle`/`Data` — see that
/// module's docs for the mapped set). Distinct from [`Error::UnsupportedCodec`]
/// (that variant's message names the ISOBMFF/fMP4 carriage this variant is
/// not about).
#[error("codec {codec} has no Matroska CodecID mapping in this crate")]
UnsupportedMkvCodec {
/// The codec name (e.g. `"VVC"`, `"FLAC"`, `"Subtitle"`).
codec: &'static str,
},
/// The MPEG-1/2 Program Stream framing could not be parsed
/// ([`PsDemux`](crate::PsDemux) input — ISO/IEC 13818-1 §2.5, via `mpeg_ps`).
#[error("program stream: {0}")]
Ps(#[from] mpeg_ps::Error),
/// An `emsg` (Event Message Box, ISO/IEC 23009-1 §5.10.3.3) could not be
/// serialized (e.g. the box would exceed the 4-byte `size` field range).
#[error("emsg serialize: {0}")]
EmsgSerialize(#[from] mp4_emsg::Error),
/// A demuxed ISOBMFF sample entry (`stsd` entry, ISO/IEC 14496-12:2015
/// §8.5.2) describes a codec this crate has no
/// [`CodecConfig`](crate::pipeline::CodecConfig) reconstruction for —
/// e.g. a proprietary or as-yet-unimplemented FourCC
/// ([`Fmp4Demux`](crate::media::Fmp4Demux), media plane step 2d). Once a
/// genuine gap (not merely undecoded metadata: `stpp`/`wvtt`/`ac-4` all
/// reconstruct), the track is rejected rather than silently dropped, so
/// the caller learns *which* sample entry it was.
#[error("sample entry '{fourcc}' has no CodecConfig reconstruction in this crate")]
UnsupportedSampleEntry {
/// The rejected sample entry's four-CC, decoded lossily as text (a
/// FourCC is nominally ASCII but not guaranteed for an unrecognised
/// box).
fourcc: String,
},
/// A fMP4/CMAF mux entry point ([`build_init_segment`](crate::pipeline::build_init_segment)
/// and every packager built on it — [`CmafMux`](crate::media::CmafMux),
/// [`ProgressiveMux`](crate::progressive::ProgressiveMux),
/// [`Segmenter`](crate::segmenter::Segmenter),
/// [`LlSegmenter`](crate::ll_dash::LlSegmenter),
/// [`LlHlsSegmenter`](crate::ll_hls::LlHlsSegmenter)) was given a
/// [`CodecConfig::Data`](crate::pipeline::CodecConfig::Data) track
/// (issue #557/#576: an opaque PMT-carried elementary stream with no
/// ISOBMFF sample entry in this crate) — CMAF/fMP4 output cannot carry
/// it. Names the offending track (media plane step 2d; applied uniformly
/// to every mux entry point in media plane step-2 fix wave 1, B2-B4) so
/// the caller can pre-filter it out (e.g. with
/// [`Media::select_tracks_by`](crate::media::Media::select_tracks_by))
/// rather than have it silently vanish from the output.
#[error(
"cannot CMAF-mux track {track_id} (PMT stream_type 0x{stream_type:02X}): \
CodecConfig::Data has no ISOBMFF sample entry in this crate"
)]
UnmuxableDataTrack {
/// The offending track's [`TrackSpec::track_id`](crate::pipeline::TrackSpec::track_id).
track_id: TrackId,
/// The track's preserved PMT `stream_type` (ISO/IEC 13818-1 Table 2-34).
stream_type: u8,
},
/// A fMP4/CMAF mux entry point (see [`Error::UnmuxableDataTrack`] for the
/// full list) was given a
/// [`CodecConfig::Subtitle`](crate::pipeline::CodecConfig::Subtitle)
/// track (B1, media plane step-2 fix wave 1): reconstructing the `stpp`/
/// `wvtt` sample entry needs more than the format tag this crate carries
/// (`TODO(#753)`), so CMAF/fMP4 output cannot carry it yet. Names the
/// offending track so the caller can pre-filter it out (e.g. with
/// [`Media::select_tracks_by`](crate::media::Media::select_tracks_by))
/// rather than have the whole package call fail opaquely.
#[error(
"cannot CMAF-mux track {track_id} (subtitle format {format}): \
CodecConfig::Subtitle has no ISOBMFF re-mux sample entry in this crate yet"
)]
UnmuxableSubtitleTrack {
/// The offending track's [`TrackSpec::track_id`](crate::pipeline::TrackSpec::track_id).
track_id: TrackId,
/// The subtitle wire format the track carries.
format: crate::pipeline::SubtitleFormat,
},
/// A streaming reassembly buffer (e.g.
/// [`rtp_stream`](crate::rtp_stream)'s per-track access-unit buffer)
/// grew past its configured cap while waiting for a completion signal
/// that never arrived (a dropped final FU-A fragment, a marker bit that
/// never comes, or any other malformed/hostile input) — issue #663 P5.2,
/// audit-ingest #4. The partial data was dropped rather than grown
/// without bound; the buffer's owner has already reset its internal
/// state, so the caller may simply continue feeding new input (it will
/// resync at the next natural boundary) or treat this as a recoverable
/// per-connection error, at its discretion.
///
/// Also returned by
/// [`ProgressiveDemux`](crate::progressive_demux::ProgressiveDemux)'s
/// [`Stage`](broadcast_common::Stage) adapter (issue B7, media plane step
/// 2 fix wave 3) when `feed` would grow its whole-file buffer past the
/// `max_bytes` bound supplied at construction — there this buffer has no
/// partial-unit resync point to drop and continue from, so the caller
/// should treat it as fatal for that `Stage` instance rather than keep
/// feeding.
#[error("{what} buffer exceeded its {cap}-byte cap and was dropped")]
BufferCapExceeded {
/// Human-readable name of the buffer that overflowed.
what: &'static str,
/// The configured cap, in bytes.
cap: usize,
},
/// A length or count did not fit the wire field it is written to (#1129):
/// a serializer that narrowed a `usize` with `as u8`/`as u16`/`as u32` (or
/// masked it) would have silently wrapped and emitted a misframed box or
/// section; this is returned instead.
#[error(transparent)]
FieldOverflow(#[from] broadcast_common::error::FieldOverflow),
/// The input needs a feature this crate does not implement yet.
///
/// Distinct from [`Error::UnsupportedCodec`]/[`Error::UnsupportedCencScheme`]
/// (which name a specific codec/scheme this crate has no handling for at
/// all): this variant is for a *partially* supported wire feature where
/// silently ignoring the unimplemented part would produce a plausible but
/// wrong result rather than an obvious failure. E.g. CENC 'seig' sample-group
/// key rotation (ISO/IEC 23001-7 §4.1/Annex A: per-sample-group KID
/// override via `sgpd`/`sbgp`) — decrypting such content with only the
/// track's default `tenc.default_KID` produces garbage for every sample
/// whose group overrides the key, with no structural signal that anything
/// went wrong (issue #990).
#[error("unsupported feature: {0}")]
UnsupportedFeature(&'static str),
/// An `#EXT-X-KEY` attribute value (`URI`/`KEYFORMAT`/`KEYFORMATVERSIONS`,
/// [`ExtXKey::to_tag`](crate::sample_aes::ExtXKey::to_tag)) could not be
/// represented as an RFC 8216 §4.2 quoted-string (contains `"`, CR or
/// LF) — issue #1140 / audit r05-W10: a key-server URL assembled from
/// request data previously went straight into the tag unvalidated, an
/// attribute-list-injection vector.
#[error(transparent)]
HlsAttrValue(#[from] broadcast_hls::Error),
/// A trick-mode `AdaptationSet` (DASH-IF IOP §3.2.9) references a main
/// `AdaptationSet@id` that is not present in the rendered MPD Period
/// (ISO/IEC 23009-1:2022 Table 5, printed p. 41 — `@id` is an unsigned
/// integer unique within the Period).
#[error(
"trick-mode AdaptationSet {trick_adaptation_set_id} references \
unknown main AdaptationSet {main_adaptation_set_id}"
)]
TrickModeMainAdaptationSetNotFound {
/// The `@id` of the trick-mode `AdaptationSet`.
trick_adaptation_set_id: u32,
/// The `main_adaptation_set_id` supplied by the caller.
main_adaptation_set_id: u32,
},
}