hls_runtime/client/error.rs
1//! Error type returned by [`crate::client::HlsClient`].
2
3use alloc::string::String;
4use thiserror::Error;
5
6/// Crate-wide result alias.
7pub type Result<T> = core::result::Result<T, Error>;
8
9/// Error variants [`crate::client::HlsClient`] can return.
10#[derive(Debug, Error)]
11#[non_exhaustive]
12pub enum Error {
13 /// The playlist bytes fed to [`crate::client::HlsClient::on_playlist`]
14 /// are not valid UTF-8 (RFC 8216 §4.1 playlists are UTF-8 text).
15 #[error("playlist is not valid UTF-8: {0}")]
16 PlaylistNotUtf8(#[from] core::str::Utf8Error),
17
18 /// `broadcast_hls::MediaPlaylist::parse` rejected the playlist text (a
19 /// known tag with a missing/unparsable required attribute).
20 #[error("playlist parse: {0}")]
21 PlaylistParse(#[from] broadcast_hls::Error),
22
23 /// [`crate::client::HlsClient::on_resource`] was fed bytes for a
24 /// [`crate::client::ResourceId`] the client never requested (a
25 /// caller/driver bug, or a stale/duplicate delivery after the client
26 /// already moved past it).
27 #[error("resource delivered for an id the client did not request: {id:?}")]
28 UnrequestedResource {
29 /// The unexpected id.
30 id: super::action::ResourceId,
31 },
32
33 /// Defensive-only: a Part/Segment reached the demux step with no init
34 /// segment cached. In normal operation this cannot happen —
35 /// [`crate::client::HlsClient::on_resource`] buffers any Part/Segment
36 /// that arrives before the init segment and replays it once the init is
37 /// delivered, so callers may complete fetches in any order.
38 #[error("resource {id:?} delivered before the init segment was available")]
39 InitNotYetAvailable {
40 /// The id that could not be demuxed yet.
41 id: super::action::ResourceId,
42 },
43
44 /// Demuxing the concatenation of the init segment + a fetched Part/Segment
45 /// resource (via `transmux::Fmp4Demux`) failed.
46 #[error("demux of resource {id:?} failed: {source}")]
47 Demux {
48 /// The id whose bytes failed to demux.
49 id: super::action::ResourceId,
50 /// The underlying transmux error.
51 #[source]
52 source: transmux::Error,
53 },
54
55 /// A playlist URI (segment/part/map/rendition-report) could not be
56 /// resolved against the playlist's own URL.
57 #[error("could not resolve URI {uri:?} against base {base:?}: {reason}")]
58 UriResolve {
59 /// The (possibly relative) URI from the playlist.
60 uri: String,
61 /// The base URL it was resolved against.
62 base: String,
63 /// Human-readable explanation.
64 reason: &'static str,
65 },
66
67 /// An `EXT-X-BYTERANGE` (or `EXT-X-PRELOAD-HINT` byte range,
68 /// RFC 8216bis §4.4.4.9/§4.4.5.3) `offset + length` overflowed `u64`.
69 /// Both values come straight from the remote origin's playlist text —
70 /// `broadcast_hls::MediaPlaylist::parse` places no upper bound on
71 /// either (confirmed: it is a bare `str::parse::<u64>()`) — so a
72 /// malicious or corrupt origin can advertise e.g.
73 /// `BYTERANGE:18446744073709551615@0`, or a chain of omitted-offset
74 /// ranges whose per-URL running cursor
75 /// ([`crate::client::HlsClient`]'s `byte_range_cursor`) accumulates past
76 /// `u64::MAX`. Rejected outright rather than saturating: a saturated
77 /// range would silently become a *different, wrong* byte range, and the
78 /// client would demux whatever bytes the origin happened to return for
79 /// it — a worse outcome than simply failing this one fetch.
80 #[error("byte range for {url:?} overflows u64: offset {offset} + length {length}")]
81 ByteRangeOverflow {
82 /// The resolved resource URL the range applies to.
83 url: String,
84 /// The range's starting offset (explicit, or the carried-forward
85 /// cursor for this URL).
86 offset: u64,
87 /// The range's length, as advertised by the playlist.
88 length: u64,
89 },
90}