Skip to main content

moq/
error.rs

1use std::sync::Arc;
2
3use crate::ffi;
4
5// Keep these public constants as the single source for both Rust's return mapping and the
6// generated C header. -1, -11, -12, and -39 are retired and remain reserved.
7pub const MOQ_ERROR_MOQ: i32 = -2;
8pub const MOQ_ERROR_URL: i32 = -3;
9pub const MOQ_ERROR_UTF8: i32 = -4;
10pub const MOQ_ERROR_CONNECT: i32 = -5;
11pub const MOQ_ERROR_INVALID_POINTER: i32 = -6;
12pub const MOQ_ERROR_INVALID_ID: i32 = -7;
13pub const MOQ_ERROR_NOT_FOUND: i32 = -8;
14pub const MOQ_ERROR_UNKNOWN_FORMAT: i32 = -9;
15pub const MOQ_ERROR_INIT_FAILED: i32 = -10;
16pub const MOQ_ERROR_TIMESTAMP_OVERFLOW: i32 = -13;
17pub const MOQ_ERROR_LEVEL: i32 = -14;
18pub const MOQ_ERROR_INVALID_CODE: i32 = -15;
19pub const MOQ_ERROR_PANIC: i32 = -16;
20pub const MOQ_ERROR_OFFLINE: i32 = -17;
21pub const MOQ_ERROR_HANG: i32 = -18;
22pub const MOQ_ERROR_NO_INDEX: i32 = -19;
23pub const MOQ_ERROR_NUL: i32 = -20;
24pub const MOQ_ERROR_SESSION_NOT_FOUND: i32 = -21;
25pub const MOQ_ERROR_ORIGIN_NOT_FOUND: i32 = -22;
26pub const MOQ_ERROR_ANNOUNCEMENT_NOT_FOUND: i32 = -23;
27pub const MOQ_ERROR_BROADCAST_NOT_FOUND: i32 = -24;
28pub const MOQ_ERROR_CATALOG_NOT_FOUND: i32 = -25;
29pub const MOQ_ERROR_MEDIA_NOT_FOUND: i32 = -26;
30pub const MOQ_ERROR_TRACK_NOT_FOUND: i32 = -27;
31pub const MOQ_ERROR_FRAME_NOT_FOUND: i32 = -28;
32pub const MOQ_ERROR_MUX: i32 = -29;
33pub const MOQ_ERROR_AUDIO: i32 = -30;
34pub const MOQ_ERROR_BUFFER_NOT_CONSUMED: i32 = -31;
35pub const MOQ_ERROR_GROUP_NOT_FOUND: i32 = -32;
36pub const MOQ_ERROR_NATIVE: i32 = -33;
37pub const MOQ_ERROR_UNAUTHORIZED: i32 = -34;
38pub const MOQ_ERROR_FORBIDDEN: i32 = -35;
39pub const MOQ_ERROR_VIDEO: i32 = -36;
40pub const MOQ_ERROR_JSON: i32 = -37;
41pub const MOQ_ERROR_JSON_TRACK: i32 = -38;
42pub const MOQ_ERROR_INVALID_CONFIG: i32 = -40;
43pub const MOQ_ERROR_UNRESOLVABLE_BROADCAST: i32 = -41;
44
45/// Whether a protocol code is from the session or stream registry.
46#[repr(C)]
47#[allow(non_camel_case_types)]
48#[derive(Clone, Copy, Debug)]
49pub enum moq_error_scope {
50	/// A session close code.
51	MOQ_ERROR_SCOPE_SESSION = 0,
52	/// A stream reset or stop code.
53	MOQ_ERROR_SCOPE_STREAM = 1,
54}
55
56/// A recognized protocol kind. Pair with [`moq_error_scope`]: `CANCEL` is 0 on a session
57/// and 1 on a stream. `APP` and `UNKNOWN` keep the numeric code in [`moq_protocol_error`].
58#[repr(C)]
59#[allow(non_camel_case_types)]
60#[derive(Clone, Copy, Debug)]
61pub enum moq_protocol_kind {
62	/// Cancel.
63	MOQ_PROTOCOL_KIND_CANCEL = 0,
64	/// Internal.
65	MOQ_PROTOCOL_KIND_INTERNAL = 1,
66	/// Unauthorized.
67	MOQ_PROTOCOL_KIND_UNAUTHORIZED = 2,
68	/// Protocol violation.
69	MOQ_PROTOCOL_KIND_PROTOCOL_VIOLATION = 3,
70	/// Key value formatting.
71	MOQ_PROTOCOL_KIND_KEY_VALUE_FORMATTING = 4,
72	/// Goaway timeout.
73	MOQ_PROTOCOL_KIND_GOAWAY_TIMEOUT = 5,
74	/// Timeout.
75	MOQ_PROTOCOL_KIND_TIMEOUT = 6,
76	/// Version.
77	MOQ_PROTOCOL_KIND_VERSION = 7,
78	// 8 through 10 are unassigned; the values that follow stay put for compiled consumers.
79	/// Delivery timeout.
80	MOQ_PROTOCOL_KIND_DELIVERY_TIMEOUT = 11,
81	/// Session closed.
82	MOQ_PROTOCOL_KIND_SESSION_CLOSED = 12,
83	/// Going away.
84	MOQ_PROTOCOL_KIND_GOING_AWAY = 13,
85	/// Too far behind.
86	MOQ_PROTOCOL_KIND_TOO_FAR_BEHIND = 14,
87	/// Malformed track.
88	MOQ_PROTOCOL_KIND_MALFORMED_TRACK = 15,
89	/// Not found.
90	MOQ_PROTOCOL_KIND_NOT_FOUND = 16,
91	/// Unroutable.
92	MOQ_PROTOCOL_KIND_UNROUTABLE = 17,
93	/// Old.
94	MOQ_PROTOCOL_KIND_OLD = 18,
95	/// Evicted.
96	MOQ_PROTOCOL_KIND_EVICTED = 19,
97	/// Wrong size.
98	MOQ_PROTOCOL_KIND_WRONG_SIZE = 20,
99	/// Frame too large.
100	MOQ_PROTOCOL_KIND_FRAME_TOO_LARGE = 21,
101	/// Timestamp mismatch.
102	MOQ_PROTOCOL_KIND_TIMESTAMP_MISMATCH = 22,
103	/// App.
104	MOQ_PROTOCOL_KIND_APP = 23,
105	/// Unknown.
106	MOQ_PROTOCOL_KIND_UNKNOWN = 24,
107}
108
109/// A protocol failure a peer sent: scope, verbatim wire code, and recognized kind.
110///
111/// Filled by [`crate::moq_error_protocol`] after a call returned a negative code. Do not parse
112/// [`crate::moq_error`] for this; that string is diagnostics only.
113#[repr(C)]
114#[allow(non_camel_case_types)]
115#[derive(Clone, Copy, Debug)]
116pub struct moq_protocol_error {
117	/// [`moq_error_scope`] discriminant.
118	pub scope: u32,
119	/// The integer on the wire, kept verbatim.
120	pub code: u32,
121	/// [`moq_protocol_kind`] discriminant.
122	pub kind: u32,
123}
124
125/// Status code returned by FFI functions.
126///
127/// Negative values indicate errors, zero indicates success,
128/// and positive values are valid resource handles.
129pub type Status = i32;
130
131/// Error types that can occur in the FFI layer.
132///
133/// Each error variant maps to a specific negative error code
134/// returned to C callers.
135#[derive(Debug, thiserror::Error, Clone)]
136#[non_exhaustive]
137pub enum Error {
138	/// Error from the underlying MoQ protocol layer.
139	#[error("moq error: {0}")]
140	Moq(#[from] moq_net::Error),
141
142	/// Error from the native helper layer (moq-tokio).
143	#[error("native error: {0}")]
144	Native(#[from] moq_tokio::Error),
145
146	/// URL parsing error.
147	#[error("url error: {0}")]
148	Url(String),
149
150	/// UTF-8 string validation error.
151	#[error("utf8 error: {0}")]
152	Utf8(#[from] std::str::Utf8Error),
153
154	/// Connection establishment error.
155	#[error("connect error: {0}")]
156	Connect(Arc<anyhow::Error>),
157
158	/// Null or invalid pointer passed from C.
159	#[error("invalid pointer")]
160	InvalidPointer,
161
162	/// Invalid resource ID.
163	#[error("invalid id")]
164	InvalidId,
165
166	/// Resource not found.
167	#[error("not found")]
168	NotFound,
169
170	/// Session task not found.
171	#[error("session not found")]
172	SessionNotFound,
173
174	/// Origin producer not found.
175	#[error("origin not found")]
176	OriginNotFound,
177
178	/// Announcement not found.
179	#[error("announcement not found")]
180	AnnouncementNotFound,
181
182	/// Broadcast not found.
183	#[error("broadcast not found")]
184	BroadcastNotFound,
185
186	/// Catalog not found.
187	#[error("catalog not found")]
188	CatalogNotFound,
189
190	/// Media decoder not found.
191	#[error("media not found")]
192	MediaNotFound,
193
194	/// Track task not found.
195	#[error("track not found")]
196	TrackNotFound,
197
198	/// Group producer not found.
199	#[error("group not found")]
200	GroupNotFound,
201
202	/// Frame not found.
203	#[error("frame not found")]
204	FrameNotFound,
205
206	/// Unknown media format specified.
207	#[error("unknown format: {0}")]
208	UnknownFormat(String),
209
210	/// Initialization failed (e.g. logging setup).
211	#[error("init failed: {0}")]
212	InitFailed(Arc<anyhow::Error>),
213
214	/// Buffer was not fully consumed.
215	#[error("buffer was not fully consumed")]
216	BufferNotConsumed,
217
218	/// Timestamp value overflow.
219	#[error("timestamp overflow")]
220	TimestampOverflow(#[from] moq_net::TimeOverflow),
221
222	/// Log level parsing error.
223	#[error("level error: {0}")]
224	Level(String),
225
226	/// Invalid error code conversion.
227	#[error("invalid code")]
228	InvalidCode,
229
230	/// Panic occurred in Rust code.
231	#[error("panic")]
232	Panic,
233
234	/// Session is offline.
235	#[error("offline")]
236	Offline,
237
238	/// Connection was rejected as unauthorized by the server.
239	#[error("unauthorized")]
240	Unauthorized,
241
242	/// Connection was forbidden by the server.
243	#[error("forbidden")]
244	Forbidden,
245
246	/// Error from the hang media layer.
247	#[error("hang error: {0}")]
248	Hang(#[from] hang::Error),
249
250	/// Error from the moq-mux consumer layer.
251	#[error("mux error: {0}")]
252	Mux(#[from] moq_mux::Error),
253
254	/// Index out of bounds.
255	#[error("no index")]
256	NoIndex,
257
258	/// Null byte found in C string.
259	#[error("nul error")]
260	NulError(#[from] std::ffi::NulError),
261
262	/// Error from the moq-audio codec layer.
263	#[error("audio error: {0}")]
264	Audio(Arc<moq_audio::Error>),
265
266	/// Error from the moq-video codec layer.
267	#[error("video error: {0}")]
268	Video(Arc<moq_video::Error>),
269
270	/// Invalid JSON passed for a catalog section.
271	#[error("json error: {0}")]
272	Json(String),
273
274	/// Error from the moq-json snapshot/stream layer.
275	#[error("json track error: {0}")]
276	JsonTrack(Arc<moq_json::Error>),
277
278	/// A client configuration value could not be parsed or initialized.
279	#[error("invalid config: {0}")]
280	InvalidConfig(String),
281
282	/// A catalog rendition named another broadcast, but the broadcast it came from was not
283	/// resolved through an origin, so there is nothing to resolve the reference against.
284	#[error("unresolvable broadcast reference: {0}")]
285	UnresolvableBroadcast(String),
286}
287
288impl From<moq_json::Error> for Error {
289	fn from(err: moq_json::Error) -> Self {
290		match err {
291			moq_json::Error::Net(e) => Error::Moq(e),
292			e => Error::JsonTrack(Arc::new(e)),
293		}
294	}
295}
296
297// Dependency errors are flattened to their message so their crates stay out of this crate's
298// public API.
299impl From<serde_json::Error> for Error {
300	fn from(err: serde_json::Error) -> Self {
301		Error::Json(err.to_string())
302	}
303}
304
305impl From<moq_net::InvalidPattern> for Error {
306	fn from(err: moq_net::InvalidPattern) -> Self {
307		Error::InvalidConfig(err.to_string())
308	}
309}
310
311impl From<url::ParseError> for Error {
312	fn from(err: url::ParseError) -> Self {
313		Error::Url(err.to_string())
314	}
315}
316
317impl From<moq_audio::Error> for Error {
318	fn from(err: moq_audio::Error) -> Self {
319		Error::Audio(Arc::new(err))
320	}
321}
322
323impl From<moq_video::Error> for Error {
324	fn from(err: moq_video::Error) -> Self {
325		Error::Video(Arc::new(err))
326	}
327}
328
329impl From<tracing::metadata::ParseLevelError> for Error {
330	fn from(err: tracing::metadata::ParseLevelError) -> Self {
331		Error::Level(err.to_string())
332	}
333}
334
335impl Error {
336	/// Structured protocol details when this is a session or stream code, not a local failure.
337	pub(crate) fn protocol(&self) -> Option<moq_protocol_error> {
338		std::iter::successors(Some(self as &(dyn std::error::Error + 'static)), |err| err.source())
339			.find_map(|err| err.downcast_ref::<moq_net::Error>().and_then(protocol_of_net))
340	}
341}
342
343fn protocol_of_net(err: &moq_net::Error) -> Option<moq_protocol_error> {
344	match err {
345		moq_net::Error::Transport(_) => None,
346		moq_net::Error::Session(err) => Some(from_session(err)),
347		moq_net::Error::Stream(err) => Some(from_stream(err)),
348		moq_net::Error::App(app) => Some(from_stream(&moq_net::StreamError::App(*app))),
349		_ => None,
350	}
351}
352
353fn from_session(err: &moq_net::SessionError) -> moq_protocol_error {
354	moq_protocol_error {
355		scope: moq_error_scope::MOQ_ERROR_SCOPE_SESSION as u32,
356		code: err.to_code(),
357		kind: session_kind(err) as u32,
358	}
359}
360
361fn from_stream(err: &moq_net::StreamError) -> moq_protocol_error {
362	moq_protocol_error {
363		scope: moq_error_scope::MOQ_ERROR_SCOPE_STREAM as u32,
364		code: err.to_code(),
365		kind: stream_kind(err) as u32,
366	}
367}
368
369fn session_kind(err: &moq_net::SessionError) -> moq_protocol_kind {
370	use moq_protocol_kind::*;
371	match err {
372		moq_net::SessionError::Cancel => MOQ_PROTOCOL_KIND_CANCEL,
373		moq_net::SessionError::Internal => MOQ_PROTOCOL_KIND_INTERNAL,
374		moq_net::SessionError::Unauthorized => MOQ_PROTOCOL_KIND_UNAUTHORIZED,
375		moq_net::SessionError::ProtocolViolation => MOQ_PROTOCOL_KIND_PROTOCOL_VIOLATION,
376		moq_net::SessionError::KeyValueFormatting => MOQ_PROTOCOL_KIND_KEY_VALUE_FORMATTING,
377		moq_net::SessionError::GoawayTimeout => MOQ_PROTOCOL_KIND_GOAWAY_TIMEOUT,
378		moq_net::SessionError::Timeout => MOQ_PROTOCOL_KIND_TIMEOUT,
379		moq_net::SessionError::Version => MOQ_PROTOCOL_KIND_VERSION,
380		moq_net::SessionError::App(_) => MOQ_PROTOCOL_KIND_APP,
381		moq_net::SessionError::Unknown(_) => MOQ_PROTOCOL_KIND_UNKNOWN,
382		_ => MOQ_PROTOCOL_KIND_UNKNOWN,
383	}
384}
385
386fn stream_kind(err: &moq_net::StreamError) -> moq_protocol_kind {
387	use moq_protocol_kind::*;
388	match err {
389		moq_net::StreamError::Session(_) => MOQ_PROTOCOL_KIND_SESSION_CLOSED,
390		moq_net::StreamError::Internal => MOQ_PROTOCOL_KIND_INTERNAL,
391		moq_net::StreamError::Cancel => MOQ_PROTOCOL_KIND_CANCEL,
392		moq_net::StreamError::DeliveryTimeout => MOQ_PROTOCOL_KIND_DELIVERY_TIMEOUT,
393		moq_net::StreamError::GoingAway => MOQ_PROTOCOL_KIND_GOING_AWAY,
394		moq_net::StreamError::TooFarBehind => MOQ_PROTOCOL_KIND_TOO_FAR_BEHIND,
395		moq_net::StreamError::MalformedTrack => MOQ_PROTOCOL_KIND_MALFORMED_TRACK,
396		moq_net::StreamError::NotFound => MOQ_PROTOCOL_KIND_NOT_FOUND,
397		moq_net::StreamError::Unroutable => MOQ_PROTOCOL_KIND_UNROUTABLE,
398		moq_net::StreamError::Old => MOQ_PROTOCOL_KIND_OLD,
399		moq_net::StreamError::Evicted => MOQ_PROTOCOL_KIND_EVICTED,
400		moq_net::StreamError::WrongSize => MOQ_PROTOCOL_KIND_WRONG_SIZE,
401		moq_net::StreamError::FrameTooLarge => MOQ_PROTOCOL_KIND_FRAME_TOO_LARGE,
402		moq_net::StreamError::TimestampMismatch => MOQ_PROTOCOL_KIND_TIMESTAMP_MISMATCH,
403		moq_net::StreamError::App(_) => MOQ_PROTOCOL_KIND_APP,
404		moq_net::StreamError::Unknown(_) => MOQ_PROTOCOL_KIND_UNKNOWN,
405		_ => MOQ_PROTOCOL_KIND_UNKNOWN,
406	}
407}
408
409impl ffi::ReturnCode for Error {
410	fn error(&self) -> Option<&Error> {
411		Some(self)
412	}
413
414	fn code(&self) -> i32 {
415		match self {
416			Error::Moq(_) => MOQ_ERROR_MOQ,
417			Error::Url(_) => MOQ_ERROR_URL,
418			Error::Utf8(_) => MOQ_ERROR_UTF8,
419			Error::Connect(_) => MOQ_ERROR_CONNECT,
420			Error::InvalidPointer => MOQ_ERROR_INVALID_POINTER,
421			Error::InvalidId => MOQ_ERROR_INVALID_ID,
422			Error::NotFound => MOQ_ERROR_NOT_FOUND,
423			Error::UnknownFormat(_) => MOQ_ERROR_UNKNOWN_FORMAT,
424			Error::InitFailed(_) => MOQ_ERROR_INIT_FAILED,
425			Error::TimestampOverflow(_) => MOQ_ERROR_TIMESTAMP_OVERFLOW,
426			Error::Level(_) => MOQ_ERROR_LEVEL,
427			Error::InvalidCode => MOQ_ERROR_INVALID_CODE,
428			Error::Panic => MOQ_ERROR_PANIC,
429			Error::Offline => MOQ_ERROR_OFFLINE,
430			Error::Hang(_) => MOQ_ERROR_HANG,
431			Error::NoIndex => MOQ_ERROR_NO_INDEX,
432			Error::NulError(_) => MOQ_ERROR_NUL,
433			Error::SessionNotFound => MOQ_ERROR_SESSION_NOT_FOUND,
434			Error::OriginNotFound => MOQ_ERROR_ORIGIN_NOT_FOUND,
435			Error::AnnouncementNotFound => MOQ_ERROR_ANNOUNCEMENT_NOT_FOUND,
436			Error::BroadcastNotFound => MOQ_ERROR_BROADCAST_NOT_FOUND,
437			Error::CatalogNotFound => MOQ_ERROR_CATALOG_NOT_FOUND,
438			Error::MediaNotFound => MOQ_ERROR_MEDIA_NOT_FOUND,
439			Error::TrackNotFound => MOQ_ERROR_TRACK_NOT_FOUND,
440			Error::FrameNotFound => MOQ_ERROR_FRAME_NOT_FOUND,
441			Error::Mux(_) => MOQ_ERROR_MUX,
442			Error::Audio(_) => MOQ_ERROR_AUDIO,
443			Error::BufferNotConsumed => MOQ_ERROR_BUFFER_NOT_CONSUMED,
444			Error::GroupNotFound => MOQ_ERROR_GROUP_NOT_FOUND,
445			Error::Native(_) => MOQ_ERROR_NATIVE,
446			Error::Unauthorized => MOQ_ERROR_UNAUTHORIZED,
447			Error::Forbidden => MOQ_ERROR_FORBIDDEN,
448			Error::Video(_) => MOQ_ERROR_VIDEO,
449			Error::Json(_) => MOQ_ERROR_JSON,
450			Error::JsonTrack(_) => MOQ_ERROR_JSON_TRACK,
451			Error::InvalidConfig(_) => MOQ_ERROR_INVALID_CONFIG,
452			Error::UnresolvableBroadcast(_) => MOQ_ERROR_UNRESOLVABLE_BROADCAST,
453		}
454	}
455}