Skip to main content

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}