Skip to main content

moqtap_codec/draft07/
error_codes.rs

1//! Error, status and termination code registries defined by MoQ Transport draft-07.
2//!
3//! Draft-07 predates the IANA registries introduced in draft-14. Its code points
4//! live in inline two-column tables headed `Code | Reason`, and the draft assigns
5//! no ALLCAPS symbolic names — the Reason cell is the only label a code has.
6//! Variant names below are derived mechanically from that Reason text, and every
7//! variant doc quotes the Reason text in backticks so the mapping from draft row
8//! to Rust variant stays checkable by eye.
9//!
10//! Where the draft says more about a code than the Reason cell, the variant doc
11//! carries that text and names the section it came from. How much exists differs
12//! sharply by table: section 3.5 continues past its table with a list defining
13//! seven of its eight codes, sections 6.1, 6.4, 6.16 and 6.20 state the rule for
14//! four more, and the SUBSCRIBE_DONE table has no supporting prose anywhere —
15//! each of its Reason strings occurs exactly once in the whole document, in the
16//! table itself.
17//!
18//! `from_u64` returns `None` for any value the draft does not assign. A peer may
19//! legitimately send a code from a later draft or a private extension, and an
20//! unrecognized code must not be a decode failure at this layer.
21//!
22//! Draft-07 defines no reserved-for-greasing ranges; every row in all three tables
23//! is a single assigned code point.
24
25/// Session termination codes, from draft-07 section 3.5 "Termination" (Table 1).
26///
27/// The draft introduces the table with: "The application MAY use any error message
28/// and SHOULD use a relevant code, as defined below". These are the codes carried
29/// in the QUIC CONNECTION_CLOSE frame or the WebTransport
30/// CLOSE_WEBTRANSPORT_SESSION capsule.
31///
32/// Section 3.5 continues past the table with a list defining the codes. That list
33/// has seven entries for eight rows: it skips `Parameter Length Mismatch`, whose
34/// rule appears in section 6.1 instead. Each variant below gives the Reason text
35/// followed by that definition.
36///
37/// The jump from 0x6 to 0x10 is the draft's own; 0x7 through 0xF are unassigned.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[repr(u64)]
40pub enum SessionErrorCode {
41    /// `No Error` — the session is being terminated without an error.
42    NoError = 0x0,
43    /// `Internal Error` — an implementation specific error occurred.
44    InternalError = 0x1,
45    /// `Unauthorized` — the endpoint breached an agreement, which may have been
46    /// pre-negotiated by the application.
47    Unauthorized = 0x2,
48    /// `Protocol Violation` — the remote endpoint performed an action that was
49    /// disallowed by the specification.
50    ProtocolViolation = 0x3,
51    /// `Duplicate Track Alias` — the endpoint attempted to use a Track Alias that
52    /// was already in use.
53    DuplicateTrackAlias = 0x4,
54    /// `Parameter Length Mismatch` — the one code section 3.5 lists in its table
55    /// but omits from the definitions that follow it. Section 6.1 supplies the
56    /// rule: if a receiver understands a parameter type and the parameter length
57    /// implied by that type does not match the Parameter Length field, the
58    /// receiver must terminate the session with this code.
59    ParameterLengthMismatch = 0x5,
60    /// `Too Many Subscribes` — the session was closed because the subscriber used
61    /// a Subscribe ID equal or larger than the current Maximum Subscribe ID.
62    /// Section 6.20 states the same rule from the publisher's side, for any
63    /// message carrying such a Subscribe ID rather than SUBSCRIBE alone.
64    TooManySubscribes = 0x6,
65    /// `GOAWAY Timeout` — the session was closed because the client took too long
66    /// to close the session in response to a GOAWAY message (section 6.3). See
67    /// session migration, section 3.6.
68    GoawayTimeout = 0x10,
69}
70
71/// SUBSCRIBE_ERROR codes, from draft-07 section 5.1 "Subscriber Interactions" (Table 2).
72///
73/// The draft introduces the table with: "The application SHOULD use a relevant
74/// error code in SUBSCRIBE_ERROR, as defined below". Draft-07 places this table
75/// under a narrative section rather than under the SUBSCRIBE_ERROR message
76/// definition in section 6.16.
77///
78/// No list of definitions follows this table. Two of the six codes have a stated
79/// rule elsewhere in the draft and carry it below; for the other four the Reason
80/// text is all there is.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82#[repr(u64)]
83pub enum SubscribeErrorCode {
84    /// `Internal Error`
85    InternalError = 0x0,
86    /// `Invalid Range` — section 6.4 requires this code when the requested range
87    /// cannot be served: for the AbsoluteStart and AbsoluteRange filters when
88    /// StartGroup is prior to the current group, and when the publisher cannot
89    /// satisfy the requested start or end, or the end has already been published.
90    InvalidRange = 0x1,
91    /// `Retry Track Alias` — section 6.16: the SUBSCRIBE_ERROR carries a Track
92    /// Alias field, and the subscriber should re-issue the SUBSCRIBE with that
93    /// alias instead. If that alias is itself already in use, the subscriber must
94    /// close the session with `Duplicate Track Alias`
95    /// ([`SessionErrorCode::DuplicateTrackAlias`]).
96    RetryTrackAlias = 0x2,
97    /// `Track Does Not Exist`
98    TrackDoesNotExist = 0x3,
99    /// `Unauthorized`
100    Unauthorized = 0x4,
101    /// `Timeout`
102    Timeout = 0x5,
103}
104
105/// SUBSCRIBE_DONE status codes, from draft-07 section 5.1 "Subscriber Interactions" (Table 3).
106///
107/// The draft introduces the table with: "The application SHOULD use a relevant
108/// status code in SUBSCRIBE_DONE, as defined below". These codes report why a
109/// publisher ended a subscription; they are not all failures. Section 6.19
110/// describes the field only as "an integer status code indicating why the
111/// subscription ended".
112///
113/// The Reason text is the whole of what draft-07 says about these seven codes:
114/// each of the strings below occurs exactly once in the document, in the table.
115/// Reason-only docs here are the complete record, not a truncation.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117#[repr(u64)]
118pub enum SubscribeDoneStatusCode {
119    /// `Unsubscribed`
120    Unsubscribed = 0x0,
121    /// `Internal Error`
122    InternalError = 0x1,
123    /// `Unauthorized`
124    Unauthorized = 0x2,
125    /// `Track Ended`
126    TrackEnded = 0x3,
127    /// `Subscription Ended`
128    SubscriptionEnded = 0x4,
129    /// `Going Away`
130    GoingAway = 0x5,
131    /// `Expired`
132    Expired = 0x6,
133}
134
135impl SessionErrorCode {
136    /// Every session termination code draft-07 assigns, in ascending wire order.
137    ///
138    /// This is the set [`Self::from_u64`] accepts, written out so that it can
139    /// be enumerated: nothing can iterate an enum's variants, so a caller that
140    /// wants the registry has to be handed it. Writing it down is also what
141    /// lets a test state its claims about the registry itself rather than about
142    /// the range some sweep happens to reach.
143    pub const ALL: &[SessionErrorCode] = &[
144        SessionErrorCode::NoError,
145        SessionErrorCode::InternalError,
146        SessionErrorCode::Unauthorized,
147        SessionErrorCode::ProtocolViolation,
148        SessionErrorCode::DuplicateTrackAlias,
149        SessionErrorCode::ParameterLengthMismatch,
150        SessionErrorCode::TooManySubscribes,
151        SessionErrorCode::GoawayTimeout,
152    ];
153
154    /// Convert a raw u64 to a `SessionErrorCode`, if draft-07 defines it.
155    ///
156    /// Returns `None` for any code the draft does not assign; peers are free to
157    /// send codes from later drafts or from private extensions.
158    pub fn from_u64(v: u64) -> Option<Self> {
159        match v {
160            0x0 => Some(SessionErrorCode::NoError),
161            0x1 => Some(SessionErrorCode::InternalError),
162            0x2 => Some(SessionErrorCode::Unauthorized),
163            0x3 => Some(SessionErrorCode::ProtocolViolation),
164            0x4 => Some(SessionErrorCode::DuplicateTrackAlias),
165            0x5 => Some(SessionErrorCode::ParameterLengthMismatch),
166            0x6 => Some(SessionErrorCode::TooManySubscribes),
167            0x10 => Some(SessionErrorCode::GoawayTimeout),
168            _ => None,
169        }
170    }
171
172    /// Return the raw u64 value of this error code.
173    pub fn as_u64(self) -> u64 {
174        self as u64
175    }
176}
177
178impl SubscribeErrorCode {
179    /// Every SUBSCRIBE_ERROR code draft-07 assigns, in ascending wire order.
180    ///
181    /// This is the set [`Self::from_u64`] accepts, written out so that it can
182    /// be enumerated: nothing can iterate an enum's variants, so a caller that
183    /// wants the registry has to be handed it. Writing it down is also what
184    /// lets a test state its claims about the registry itself rather than about
185    /// the range some sweep happens to reach.
186    pub const ALL: &[SubscribeErrorCode] = &[
187        SubscribeErrorCode::InternalError,
188        SubscribeErrorCode::InvalidRange,
189        SubscribeErrorCode::RetryTrackAlias,
190        SubscribeErrorCode::TrackDoesNotExist,
191        SubscribeErrorCode::Unauthorized,
192        SubscribeErrorCode::Timeout,
193    ];
194
195    /// Convert a raw u64 to a `SubscribeErrorCode`, if draft-07 defines it.
196    ///
197    /// Returns `None` for any code the draft does not assign; peers are free to
198    /// send codes from later drafts or from private extensions.
199    pub fn from_u64(v: u64) -> Option<Self> {
200        match v {
201            0x0 => Some(SubscribeErrorCode::InternalError),
202            0x1 => Some(SubscribeErrorCode::InvalidRange),
203            0x2 => Some(SubscribeErrorCode::RetryTrackAlias),
204            0x3 => Some(SubscribeErrorCode::TrackDoesNotExist),
205            0x4 => Some(SubscribeErrorCode::Unauthorized),
206            0x5 => Some(SubscribeErrorCode::Timeout),
207            _ => None,
208        }
209    }
210
211    /// Return the raw u64 value of this error code.
212    pub fn as_u64(self) -> u64 {
213        self as u64
214    }
215}
216
217impl SubscribeDoneStatusCode {
218    /// Every SUBSCRIBE_DONE status code draft-07 assigns, in ascending wire order.
219    ///
220    /// This is the set [`Self::from_u64`] accepts, written out so that it can
221    /// be enumerated: nothing can iterate an enum's variants, so a caller that
222    /// wants the registry has to be handed it. Writing it down is also what
223    /// lets a test state its claims about the registry itself rather than about
224    /// the range some sweep happens to reach.
225    pub const ALL: &[SubscribeDoneStatusCode] = &[
226        SubscribeDoneStatusCode::Unsubscribed,
227        SubscribeDoneStatusCode::InternalError,
228        SubscribeDoneStatusCode::Unauthorized,
229        SubscribeDoneStatusCode::TrackEnded,
230        SubscribeDoneStatusCode::SubscriptionEnded,
231        SubscribeDoneStatusCode::GoingAway,
232        SubscribeDoneStatusCode::Expired,
233    ];
234
235    /// Convert a raw u64 to a `SubscribeDoneStatusCode`, if draft-07 defines it.
236    ///
237    /// Returns `None` for any code the draft does not assign; peers are free to
238    /// send codes from later drafts or from private extensions.
239    pub fn from_u64(v: u64) -> Option<Self> {
240        match v {
241            0x0 => Some(SubscribeDoneStatusCode::Unsubscribed),
242            0x1 => Some(SubscribeDoneStatusCode::InternalError),
243            0x2 => Some(SubscribeDoneStatusCode::Unauthorized),
244            0x3 => Some(SubscribeDoneStatusCode::TrackEnded),
245            0x4 => Some(SubscribeDoneStatusCode::SubscriptionEnded),
246            0x5 => Some(SubscribeDoneStatusCode::GoingAway),
247            0x6 => Some(SubscribeDoneStatusCode::Expired),
248            _ => None,
249        }
250    }
251
252    /// Return the raw u64 value of this status code.
253    pub fn as_u64(self) -> u64 {
254        self as u64
255    }
256}
257
258/// TRACK_STATUS Status Code values (draft-07, Section 6.23).
259///
260/// The draft defines these as a prose list rather than as a `Code`/`Reason`
261/// table, so they are named from the prose. It is stricter about this field
262/// than about the error registries above: the Status Code "MUST hold one of the
263/// following values" and "Any other value in the Status Code field is a
264/// malformed message", so [`TrackStatusCode::from_u64`] answering `None` is a
265/// decode failure rather than a merely unrecognised code.
266#[derive(Debug, Clone, Copy, PartialEq, Eq)]
267#[repr(u64)]
268pub enum TrackStatusCode {
269    /// The track is in progress, and subsequent fields contain the highest
270    /// group and object ID for that track.
271    InProgress = 0x00,
272    /// The track does not exist. Subsequent fields MUST be zero, and any other
273    /// value is a malformed message.
274    TrackDoesNotExist = 0x01,
275    /// The track has not yet begun. Subsequent fields MUST be zero, and any
276    /// other value is a malformed message.
277    NotYetBegun = 0x02,
278    /// The track has finished, so there is no live edge. Subsequent fields
279    /// contain the highest group and object ID known.
280    Finished = 0x03,
281    /// The publisher is a relay that cannot obtain the current track status
282    /// from upstream. Subsequent fields contain the largest group and object
283    /// ID known.
284    RelayStatusUnavailable = 0x04,
285}
286
287impl TrackStatusCode {
288    /// Convert a raw u64 to a `TrackStatusCode`, if this draft assigns it.
289    pub fn from_u64(v: u64) -> Option<Self> {
290        match v {
291            0x00 => Some(TrackStatusCode::InProgress),
292            0x01 => Some(TrackStatusCode::TrackDoesNotExist),
293            0x02 => Some(TrackStatusCode::NotYetBegun),
294            0x03 => Some(TrackStatusCode::Finished),
295            0x04 => Some(TrackStatusCode::RelayStatusUnavailable),
296            _ => None,
297        }
298    }
299
300    /// Whether this code requires the fields after it to be zero.
301    ///
302    /// Section 6.23 says of 0x01 "Subsequent fields MUST be zero, and any other
303    /// value is a malformed message", and the same of 0x02. The other three
304    /// codes describe those fields as carrying a real location, so they place
305    /// no requirement on them.
306    pub fn requires_zero_location(self) -> bool {
307        matches!(self, TrackStatusCode::TrackDoesNotExist | TrackStatusCode::NotYetBegun)
308    }
309
310    /// Return the raw u64 value of this status code.
311    pub fn as_u64(self) -> u64 {
312        self as u64
313    }
314}