Skip to main content

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}