moqtap_codec/draft15/error_codes.rs
1//! Error and status code registries defined by draft-15.
2//!
3//! Each of the four registries in Section 13.3 of the draft ("Error Codes") is
4//! transcribed here as one enum. The IANA tables list only names, codes and a
5//! pointer to the defining section; the per-variant prose below comes from those
6//! defining sections (3.4, 9.8, 9.15 and 10.4.3).
7//!
8//! `from_u64` returns `None` for any code the draft does not define. Peers are
9//! free to send codes from a newer draft or from a private range, so an unknown
10//! code is a normal decode outcome and never an error on its own.
11
12/// Session Termination Error Codes (draft-15 Section 13.3.1, defined in Section 3.4).
13///
14/// Sent when closing the session. Draft-15 assigns 0x0 through 0x9 and 0x10
15/// through 0x1A; it reserves no greasing range in this registry.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17#[repr(u64)]
18pub enum SessionErrorCode {
19 /// `NO_ERROR` — the session is being terminated without an error.
20 NoError = 0x0,
21 /// `INTERNAL_ERROR` — an implementation specific error occurred.
22 InternalError = 0x1,
23 /// `UNAUTHORIZED` — the client is not authorized to establish a session.
24 Unauthorized = 0x2,
25 /// `PROTOCOL_VIOLATION` — the remote endpoint performed an action that was
26 /// disallowed by the specification.
27 ProtocolViolation = 0x3,
28 /// `INVALID_REQUEST_ID` — the session was closed because the endpoint used a
29 /// Request ID that was smaller than or equal to a previously received request
30 /// ID, or the least-significant bit of the request ID was incorrect for the
31 /// endpoint.
32 InvalidRequestId = 0x4,
33 /// `DUPLICATE_TRACK_ALIAS` — the endpoint attempted to use a Track Alias that
34 /// was already in use.
35 DuplicateTrackAlias = 0x5,
36 /// `KEY_VALUE_FORMATTING_ERROR` — the key-value pair has a formatting error.
37 KeyValueFormattingError = 0x6,
38 /// `TOO_MANY_REQUESTS` — the session was closed because the endpoint used a
39 /// Request ID equal to or larger than the current Maximum Request ID.
40 TooManyRequests = 0x7,
41 /// `INVALID_PATH` — the PATH parameter was used by a server, on a WebTransport
42 /// session, or the server does not support the path.
43 InvalidPath = 0x8,
44 /// `MALFORMED_PATH` — the PATH parameter does not conform to the rules in
45 /// Section 9.3.1.2.
46 MalformedPath = 0x9,
47 /// `GOAWAY_TIMEOUT` — the session was closed because the peer took too long to
48 /// close the session in response to a GOAWAY (Section 9.4) message. See session
49 /// migration (Section 3.5).
50 GoawayTimeout = 0x10,
51 /// `CONTROL_MESSAGE_TIMEOUT` — the session was closed because the peer took too
52 /// long to respond to a control message.
53 ControlMessageTimeout = 0x11,
54 /// `DATA_STREAM_TIMEOUT` — the session was closed because the peer took too long
55 /// to send data expected on an open Data Stream (see Section 10). This includes
56 /// fields of a stream header or an object header within a data stream. If an
57 /// endpoint times out waiting for a new object header on an open subgroup
58 /// stream, it MAY send a STOP_SENDING on that stream or terminate the
59 /// subscription.
60 DataStreamTimeout = 0x12,
61 /// `AUTH_TOKEN_CACHE_OVERFLOW` — the Session limit (Section 9.3.1.4) of the size
62 /// of all registered Authorization tokens has been exceeded.
63 AuthTokenCacheOverflow = 0x13,
64 /// `DUPLICATE_AUTH_TOKEN_ALIAS` — Authorization Token attempted to register an
65 /// Alias that was in use (see Section 9.2.1.1).
66 DuplicateAuthTokenAlias = 0x14,
67 /// `VERSION_NEGOTIATION_FAILED` — the client didn't offer a version supported by
68 /// the server.
69 VersionNegotiationFailed = 0x15,
70 /// `MALFORMED_AUTH_TOKEN` — invalid Auth Token serialization during registration
71 /// (see Section 9.2.1.1).
72 MalformedAuthToken = 0x16,
73 /// `UNKNOWN_AUTH_TOKEN_ALIAS` — no registered token found for the provided Alias
74 /// (see Section 9.2.1.1).
75 UnknownAuthTokenAlias = 0x17,
76 /// `EXPIRED_AUTH_TOKEN` — authorization token has expired (Section 9.2.1.1).
77 ExpiredAuthToken = 0x18,
78 /// `INVALID_AUTHORITY` — the specified AUTHORITY does not correspond to this
79 /// server or cannot be used in this context.
80 InvalidAuthority = 0x19,
81 /// `MALFORMED_AUTHORITY` — the AUTHORITY value is syntactically invalid.
82 MalformedAuthority = 0x1A,
83}
84
85/// REQUEST_ERROR Codes (draft-15 Section 13.3.2, defined in Section 9.8).
86///
87/// Carried in the REQUEST_ERROR message, which draft-15 sends in response to any
88/// request (SUBSCRIBE, FETCH, PUBLISH, SUBSCRIBE_NAMESPACE, PUBLISH_NAMESPACE,
89/// TRACK_STATUS). Most codepoints have identical meanings across request types,
90/// but Section 9.8 lists the registry in four bands, separated by which side of
91/// the session sends the code:
92///
93/// - 0x0 through 0x5 carry the same meaning whichever request they answer.
94/// - 0x10 through 0x12 are for use by the publisher. They can appear in response
95/// to SUBSCRIBE, FETCH, TRACK_STATUS and SUBSCRIBE_NAMESPACE, unless otherwise
96/// noted.
97/// - 0x20 is for use by the subscriber. It can appear in response to PUBLISH or
98/// PUBLISH_NAMESPACE, unless otherwise noted.
99/// - 0x30 and above can only be used in response to one message type.
100///
101/// The draft assigns no greasing range in this registry, and leaves 0x31
102/// unassigned between PREFIX_OVERLAP and INVALID_JOINING_REQUEST_ID.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104#[repr(u64)]
105pub enum RequestErrorCode {
106 /// `INTERNAL_ERROR` — an implementation specific or generic error occurred.
107 InternalError = 0x0,
108 /// `UNAUTHORIZED` — the subscriber is not authorized to perform the requested
109 /// action on the given track.
110 Unauthorized = 0x1,
111 /// `TIMEOUT` — the subscription could not be completed before an implementation
112 /// specific timeout. For example, a relay could not establish an upstream
113 /// subscription within the timeout.
114 Timeout = 0x2,
115 /// `NOT_SUPPORTED` — the endpoint does not support the type of request.
116 NotSupported = 0x3,
117 /// `MALFORMED_AUTH_TOKEN` — invalid Auth Token serialization during registration
118 /// (see Section 9.2.1.1).
119 MalformedAuthToken = 0x4,
120 /// `EXPIRED_AUTH_TOKEN` — authorization token has expired (Section 9.2.1.1).
121 ExpiredAuthToken = 0x5,
122 /// `DOES_NOT_EXIST` — the track or namespace is not available at the publisher.
123 DoesNotExist = 0x10,
124 /// `INVALID_RANGE` — in response to SUBSCRIBE or FETCH, specified Filter or range
125 /// of Locations cannot be satisfied.
126 InvalidRange = 0x11,
127 /// `MALFORMED_TRACK` — in response to a FETCH, a relay publisher detected the
128 /// track was malformed (see Section 2.4.2).
129 MalformedTrack = 0x12,
130 /// `UNINTERESTED` — the subscriber is not interested in the track or namespace.
131 Uninterested = 0x20,
132 /// `PREFIX_OVERLAP` — in response to SUBSCRIBE_NAMESPACE, the namespace prefix
133 /// overlaps with another SUBSCRIBE_NAMESPACE in the same session.
134 PrefixOverlap = 0x30,
135 /// `INVALID_JOINING_REQUEST_ID` — in response to a Joining FETCH, the referenced
136 /// Request ID is not an `Established` Subscription. `Established` is the
137 /// subscription state named in Section 5.1.
138 InvalidJoiningRequestId = 0x32,
139 /// `UNKNOWN_STATUS_IN_RANGE` — in response to a FETCH, the requested range
140 /// contains an object with unknown status.
141 UnknownStatusInRange = 0x33,
142}
143
144/// PUBLISH_DONE Codes (draft-15 Section 13.3.3, defined in Section 9.15).
145///
146/// The status a publisher reports when a subscription ends. Draft-15 assigns 0x0
147/// through 0x8 contiguously and reserves no greasing range.
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149#[repr(u64)]
150pub enum PublishDoneStatusCode {
151 /// `INTERNAL_ERROR` — an implementation specific or generic error occurred.
152 InternalError = 0x0,
153 /// `UNAUTHORIZED` — the subscriber is no longer authorized to subscribe to the
154 /// given track.
155 Unauthorized = 0x1,
156 /// `TRACK_ENDED` — the track is no longer being published.
157 TrackEnded = 0x2,
158 /// `SUBSCRIPTION_ENDED` — the publisher reached the end of an associated
159 /// subscription filter.
160 SubscriptionEnded = 0x3,
161 /// `GOING_AWAY` — the subscriber or publisher issued a GOAWAY message.
162 GoingAway = 0x4,
163 /// `EXPIRED` — the publisher reached the timeout specified in SUBSCRIBE_OK.
164 Expired = 0x5,
165 /// `TOO_FAR_BEHIND` — the publisher's queue of objects to be sent to the given
166 /// subscriber exceeds its implementation defined limit.
167 TooFarBehind = 0x6,
168 /// `MALFORMED_TRACK` — a relay publisher detected the track was malformed (see
169 /// Section 2.4.2).
170 MalformedTrack = 0x7,
171 /// `UPDATE_FAILED` — SUBSCRIBE_UPDATE failed on this subscription (see
172 /// Section 9.11).
173 UpdateFailed = 0x8,
174}
175
176/// Data Stream Reset Error Codes (draft-15 Section 13.3.4, defined in Section 10.4.3).
177///
178/// Sent as the application error code in the RESET_STREAM or RESET_STREAM_AT
179/// frame when a publisher closes a data stream before delivering every object in
180/// the subgroup. Draft-15 assigns 0x0 through 0x3 and reserves no greasing range.
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182#[repr(u64)]
183pub enum DataStreamResetErrorCode {
184 /// `INTERNAL_ERROR` — an implementation specific error.
185 InternalError = 0x0,
186 /// `CANCELLED` — the subscriber requested cancellation via UNSUBSCRIBE,
187 /// FETCH_CANCEL or STOP_SENDING, or the publisher ended the subscription, in
188 /// which case PUBLISH_DONE (Section 9.15) will have a more detailed status code.
189 Cancelled = 0x1,
190 /// `DELIVERY_TIMEOUT` — the DELIVERY TIMEOUT (Section 9.2.1.2) was exceeded for
191 /// this stream.
192 DeliveryTimeout = 0x2,
193 /// `SESSION_CLOSED` — the publisher session is being closed.
194 SessionClosed = 0x3,
195}
196
197impl SessionErrorCode {
198 /// Every session termination code draft-15 assigns, in ascending wire order.
199 ///
200 /// This is the set [`Self::from_u64`] accepts, written out so that it can
201 /// be enumerated: nothing can iterate an enum's variants, so a caller that
202 /// wants the registry has to be handed it. Writing it down is also what
203 /// lets a test state its claims about the registry itself rather than about
204 /// the range some sweep happens to reach.
205 pub const ALL: &[SessionErrorCode] = &[
206 SessionErrorCode::NoError,
207 SessionErrorCode::InternalError,
208 SessionErrorCode::Unauthorized,
209 SessionErrorCode::ProtocolViolation,
210 SessionErrorCode::InvalidRequestId,
211 SessionErrorCode::DuplicateTrackAlias,
212 SessionErrorCode::KeyValueFormattingError,
213 SessionErrorCode::TooManyRequests,
214 SessionErrorCode::InvalidPath,
215 SessionErrorCode::MalformedPath,
216 SessionErrorCode::GoawayTimeout,
217 SessionErrorCode::ControlMessageTimeout,
218 SessionErrorCode::DataStreamTimeout,
219 SessionErrorCode::AuthTokenCacheOverflow,
220 SessionErrorCode::DuplicateAuthTokenAlias,
221 SessionErrorCode::VersionNegotiationFailed,
222 SessionErrorCode::MalformedAuthToken,
223 SessionErrorCode::UnknownAuthTokenAlias,
224 SessionErrorCode::ExpiredAuthToken,
225 SessionErrorCode::InvalidAuthority,
226 SessionErrorCode::MalformedAuthority,
227 ];
228
229 /// Convert a raw u64 to a `SessionErrorCode`, if draft-15 defines it.
230 pub fn from_u64(v: u64) -> Option<Self> {
231 match v {
232 0x0 => Some(SessionErrorCode::NoError),
233 0x1 => Some(SessionErrorCode::InternalError),
234 0x2 => Some(SessionErrorCode::Unauthorized),
235 0x3 => Some(SessionErrorCode::ProtocolViolation),
236 0x4 => Some(SessionErrorCode::InvalidRequestId),
237 0x5 => Some(SessionErrorCode::DuplicateTrackAlias),
238 0x6 => Some(SessionErrorCode::KeyValueFormattingError),
239 0x7 => Some(SessionErrorCode::TooManyRequests),
240 0x8 => Some(SessionErrorCode::InvalidPath),
241 0x9 => Some(SessionErrorCode::MalformedPath),
242 0x10 => Some(SessionErrorCode::GoawayTimeout),
243 0x11 => Some(SessionErrorCode::ControlMessageTimeout),
244 0x12 => Some(SessionErrorCode::DataStreamTimeout),
245 0x13 => Some(SessionErrorCode::AuthTokenCacheOverflow),
246 0x14 => Some(SessionErrorCode::DuplicateAuthTokenAlias),
247 0x15 => Some(SessionErrorCode::VersionNegotiationFailed),
248 0x16 => Some(SessionErrorCode::MalformedAuthToken),
249 0x17 => Some(SessionErrorCode::UnknownAuthTokenAlias),
250 0x18 => Some(SessionErrorCode::ExpiredAuthToken),
251 0x19 => Some(SessionErrorCode::InvalidAuthority),
252 0x1A => Some(SessionErrorCode::MalformedAuthority),
253 _ => None,
254 }
255 }
256
257 /// Return the raw u64 value of this error code.
258 pub fn as_u64(self) -> u64 {
259 self as u64
260 }
261}
262
263impl RequestErrorCode {
264 /// Every REQUEST_ERROR code draft-15 assigns, in ascending wire order.
265 ///
266 /// This is the set [`Self::from_u64`] accepts, written out so that it can
267 /// be enumerated: nothing can iterate an enum's variants, so a caller that
268 /// wants the registry has to be handed it. Writing it down is also what
269 /// lets a test state its claims about the registry itself rather than about
270 /// the range some sweep happens to reach.
271 pub const ALL: &[RequestErrorCode] = &[
272 RequestErrorCode::InternalError,
273 RequestErrorCode::Unauthorized,
274 RequestErrorCode::Timeout,
275 RequestErrorCode::NotSupported,
276 RequestErrorCode::MalformedAuthToken,
277 RequestErrorCode::ExpiredAuthToken,
278 RequestErrorCode::DoesNotExist,
279 RequestErrorCode::InvalidRange,
280 RequestErrorCode::MalformedTrack,
281 RequestErrorCode::Uninterested,
282 RequestErrorCode::PrefixOverlap,
283 RequestErrorCode::InvalidJoiningRequestId,
284 RequestErrorCode::UnknownStatusInRange,
285 ];
286
287 /// Convert a raw u64 to a `RequestErrorCode`, if draft-15 defines it.
288 pub fn from_u64(v: u64) -> Option<Self> {
289 match v {
290 0x0 => Some(RequestErrorCode::InternalError),
291 0x1 => Some(RequestErrorCode::Unauthorized),
292 0x2 => Some(RequestErrorCode::Timeout),
293 0x3 => Some(RequestErrorCode::NotSupported),
294 0x4 => Some(RequestErrorCode::MalformedAuthToken),
295 0x5 => Some(RequestErrorCode::ExpiredAuthToken),
296 0x10 => Some(RequestErrorCode::DoesNotExist),
297 0x11 => Some(RequestErrorCode::InvalidRange),
298 0x12 => Some(RequestErrorCode::MalformedTrack),
299 0x20 => Some(RequestErrorCode::Uninterested),
300 0x30 => Some(RequestErrorCode::PrefixOverlap),
301 0x32 => Some(RequestErrorCode::InvalidJoiningRequestId),
302 0x33 => Some(RequestErrorCode::UnknownStatusInRange),
303 _ => None,
304 }
305 }
306
307 /// Return the raw u64 value of this error code.
308 pub fn as_u64(self) -> u64 {
309 self as u64
310 }
311}
312
313impl PublishDoneStatusCode {
314 /// Every PUBLISH_DONE status code draft-15 assigns, in ascending wire order.
315 ///
316 /// This is the set [`Self::from_u64`] accepts, written out so that it can
317 /// be enumerated: nothing can iterate an enum's variants, so a caller that
318 /// wants the registry has to be handed it. Writing it down is also what
319 /// lets a test state its claims about the registry itself rather than about
320 /// the range some sweep happens to reach.
321 pub const ALL: &[PublishDoneStatusCode] = &[
322 PublishDoneStatusCode::InternalError,
323 PublishDoneStatusCode::Unauthorized,
324 PublishDoneStatusCode::TrackEnded,
325 PublishDoneStatusCode::SubscriptionEnded,
326 PublishDoneStatusCode::GoingAway,
327 PublishDoneStatusCode::Expired,
328 PublishDoneStatusCode::TooFarBehind,
329 PublishDoneStatusCode::MalformedTrack,
330 PublishDoneStatusCode::UpdateFailed,
331 ];
332
333 /// Convert a raw u64 to a `PublishDoneStatusCode`, if draft-15 defines it.
334 pub fn from_u64(v: u64) -> Option<Self> {
335 match v {
336 0x0 => Some(PublishDoneStatusCode::InternalError),
337 0x1 => Some(PublishDoneStatusCode::Unauthorized),
338 0x2 => Some(PublishDoneStatusCode::TrackEnded),
339 0x3 => Some(PublishDoneStatusCode::SubscriptionEnded),
340 0x4 => Some(PublishDoneStatusCode::GoingAway),
341 0x5 => Some(PublishDoneStatusCode::Expired),
342 0x6 => Some(PublishDoneStatusCode::TooFarBehind),
343 0x7 => Some(PublishDoneStatusCode::MalformedTrack),
344 0x8 => Some(PublishDoneStatusCode::UpdateFailed),
345 _ => None,
346 }
347 }
348
349 /// Return the raw u64 value of this status code.
350 pub fn as_u64(self) -> u64 {
351 self as u64
352 }
353}
354
355impl DataStreamResetErrorCode {
356 /// Every data stream reset code draft-15 assigns, in ascending wire order.
357 ///
358 /// This is the set [`Self::from_u64`] accepts, written out so that it can
359 /// be enumerated: nothing can iterate an enum's variants, so a caller that
360 /// wants the registry has to be handed it. Writing it down is also what
361 /// lets a test state its claims about the registry itself rather than about
362 /// the range some sweep happens to reach.
363 pub const ALL: &[DataStreamResetErrorCode] = &[
364 DataStreamResetErrorCode::InternalError,
365 DataStreamResetErrorCode::Cancelled,
366 DataStreamResetErrorCode::DeliveryTimeout,
367 DataStreamResetErrorCode::SessionClosed,
368 ];
369
370 /// Convert a raw u64 to a `DataStreamResetErrorCode`, if draft-15 defines it.
371 pub fn from_u64(v: u64) -> Option<Self> {
372 match v {
373 0x0 => Some(DataStreamResetErrorCode::InternalError),
374 0x1 => Some(DataStreamResetErrorCode::Cancelled),
375 0x2 => Some(DataStreamResetErrorCode::DeliveryTimeout),
376 0x3 => Some(DataStreamResetErrorCode::SessionClosed),
377 _ => None,
378 }
379 }
380
381 /// Return the raw u64 value of this error code.
382 pub fn as_u64(self) -> u64 {
383 self as u64
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use super::*;
390
391 #[test]
392 fn round_trips_known_codes() {
393 assert_eq!(SessionErrorCode::from_u64(0x1A), Some(SessionErrorCode::MalformedAuthority));
394 assert_eq!(SessionErrorCode::MalformedAuthority.as_u64(), 0x1A);
395 assert_eq!(RequestErrorCode::from_u64(0x33), Some(RequestErrorCode::UnknownStatusInRange));
396 assert_eq!(RequestErrorCode::UnknownStatusInRange.as_u64(), 0x33);
397 assert_eq!(PublishDoneStatusCode::from_u64(0x8), Some(PublishDoneStatusCode::UpdateFailed));
398 assert_eq!(PublishDoneStatusCode::UpdateFailed.as_u64(), 0x8);
399 assert_eq!(
400 DataStreamResetErrorCode::from_u64(0x3),
401 Some(DataStreamResetErrorCode::SessionClosed)
402 );
403 assert_eq!(DataStreamResetErrorCode::SessionClosed.as_u64(), 0x3);
404 }
405
406 #[test]
407 fn unknown_codes_decode_to_none() {
408 // 0xA..0xF and anything above 0x1A are unassigned in draft-15.
409 assert_eq!(SessionErrorCode::from_u64(0xA), None);
410 assert_eq!(SessionErrorCode::from_u64(0x1B), None);
411 // 0x31 sits between PREFIX_OVERLAP and INVALID_JOINING_REQUEST_ID.
412 assert_eq!(RequestErrorCode::from_u64(0x31), None);
413 assert_eq!(PublishDoneStatusCode::from_u64(0x9), None);
414 assert_eq!(DataStreamResetErrorCode::from_u64(0x4), None);
415 assert_eq!(SessionErrorCode::from_u64(u64::MAX), None);
416 }
417}