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}