Skip to main content

moqtap_codec/draft09/
error_codes.rs

1//! The error, status and termination code registries that MoQ Transport
2//! draft-09 publishes as `Code`/`Reason` tables.
3//!
4//! Draft-09 predates the IANA registries introduced in later drafts. Every
5//! registry here is an inline two-column table with a `Code` column and a
6//! `Reason` column, and the draft assigns no ALLCAPS symbolic names to the code
7//! points. Each variant's doc comment therefore quotes the draft's `Reason`
8//! text verbatim so the mapping from the table to the Rust name is checkable by
9//! eye; where the draft explains a code in prose, that explanation follows.
10//!
11//! # Scope
12//!
13//! Draft-09 has six such tables and all six are transcribed here: Section 3.5
14//! (Termination), Section 7.10 (ANNOUNCE_ERROR), Section 7.16
15//! (SUBSCRIBE_ERROR), Section 7.18 (FETCH_ERROR), Section 7.19 (SUBSCRIBE_DONE)
16//! and Section 7.26 (SUBSCRIBE_ANNOUNCES_ERROR). Draft-09 assigns code points
17//! in two further places, neither of which is a `Code`/`Reason` table and
18//! neither of which belongs here:
19//!
20//! - Section 7.24 assigns TRACK_STATUS Status Codes 0x00 through 0x04 in a
21//!   prose list. That registry is closed — the draft says the field "MUST hold
22//!   one of the following values. Any other value is a malformed message" —
23//!   which is the opposite of the open registries below, so folding it in would
24//!   misrepresent it. [`super::message::TrackStatus`] carries the field as a
25//!   raw `VarInt`.
26//! - Section 8.1.1.1 assigns Object Status 0x0, 0x1, 0x3, 0x4 and 0x5 (0x2 is
27//!   not assigned). That registry is [`super::types::ObjectStatus`], next to
28//!   the data-stream code that reads it.
29//!
30//! ANNOUNCE_CANCEL carries an Error Code field with no table of its own, and
31//! Section 7.11 says why: "ANNOUNCE_CANCEL uses the same error codes as
32//! ANNOUNCE_ERROR". So its codes are [`AnnounceErrorCode`], and there is no
33//! separate registry to transcribe.
34//!
35//! # Unrecognised codes
36//!
37//! All six registries here are open: the draft says only that an application
38//! "SHOULD use a relevant error code", so a peer may send a code this draft
39//! does not define. `from_u64` returns `None` for an unrecognised value rather
40//! than failing or widening the enum.
41//!
42//! # These are six separate number spaces
43//!
44//! The registries are deliberately six distinct types because the same number
45//! means different things in each, and the differences are not intuitive:
46//!
47//! - The termination registry is offset by one against the other five.
48//!   `Internal Error` is 0x1 in Section 3.5 but 0x0 in all five message-scoped
49//!   registries, and `Unauthorized` is 0x2 in Section 3.5 but 0x1 in all five.
50//!   Reusing a session code as a message code therefore shifts its meaning by a
51//!   whole row rather than producing an obviously wrong value.
52//! - 0x2 is `Timeout` in ANNOUNCE_ERROR, SUBSCRIBE_ERROR, FETCH_ERROR and
53//!   SUBSCRIBE_ANNOUNCES_ERROR, but `Track Ended` in SUBSCRIBE_DONE and
54//!   `Unauthorized` in the termination registry.
55//! - 0x4 carries five distinct meanings across the six registries:
56//!   `Duplicate Track Alias`, `Uninterested`, `Track Does Not Exist` (in both
57//!   SUBSCRIBE_ERROR and FETCH_ERROR), `Going Away` and
58//!   `Namespace Prefix Unknown`.
59//!
60//! `Unauthorized` also differs in meaning as well as in number: in
61//! [`SessionErrorCode`] it reports a breached agreement, which the
62//! message-scoped registries do not say.
63
64/// Session termination codes, from draft-09 Section 3.5 (Termination).
65///
66/// The draft introduces the table with "The application MAY use any error
67/// message and SHOULD use a relevant code, as defined below". These codes
68/// travel in the QUIC `CONNECTION_CLOSE` frame or the WebTransport
69/// `CLOSE_WEBTRANSPORT_SESSION` capsule.
70///
71/// Section 3.5 assigns 0x0 through 0x6 and then jumps to 0x10 through 0x12 for
72/// the three timeouts; 0x7 through 0xF are unassigned. The gap is the draft's
73/// own, so `GOAWAY Timeout` is 0x10 and not 0x7.
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75#[repr(u64)]
76pub enum SessionErrorCode {
77    /// `No Error` — The session is being terminated without an error.
78    NoError = 0x0,
79    /// `Internal Error` — An implementation specific error occurred.
80    InternalError = 0x1,
81    /// `Unauthorized` — The endpoint breached an agreement, which MAY have been
82    /// pre-negotiated by the application.
83    Unauthorized = 0x2,
84    /// `Protocol Violation` — The remote endpoint performed an action that was
85    /// disallowed by the specification.
86    ProtocolViolation = 0x3,
87    /// `Duplicate Track Alias` — The endpoint attempted to use a Track Alias
88    /// that was already in use.
89    DuplicateTrackAlias = 0x4,
90    /// `Parameter Length Mismatch` — the Section 3.5 table assigns this code but
91    /// the list of descriptions that follows the table skips it. Section 7.1
92    /// supplies the meaning: if a receiver understands a parameter type, and the
93    /// parameter length implied by that type does not match the Parameter Length
94    /// field, the receiver MUST terminate the session with this code.
95    ParameterLengthMismatch = 0x5,
96    /// `Too Many Subscribes` — The session was closed because the subscriber
97    /// used a Subscribe ID equal or larger than the current Maximum Subscribe
98    /// ID.
99    TooManySubscribes = 0x6,
100    /// `GOAWAY Timeout` — The session was closed because the peer took too long
101    /// to close the session in response to a GOAWAY (Section 7.3) message. See
102    /// session migration (Section 3.6).
103    GoawayTimeout = 0x10,
104    /// `Control Message Timeout` — The session was closed because the peer took
105    /// too long to respond to a control message.
106    ControlMessageTimeout = 0x11,
107    /// `Data Stream Timeout` — The session was closed because the peer took too
108    /// long to send data expected on an open Data Stream (Section 8). This
109    /// includes fields of a stream header or an object header within a data
110    /// stream. If an endpoint times out waiting for a new object header on an
111    /// open subgroup stream, it MAY send a STOP_SENDING on that stream,
112    /// terminate the subscription, or close the session with an error.
113    DataStreamTimeout = 0x12,
114}
115
116/// ANNOUNCE_ERROR codes, from draft-09 Section 7.10 (ANNOUNCE_ERROR).
117///
118/// The draft introduces the table with "The application SHOULD use a relevant
119/// error code in ANNOUNCE_ERROR, as defined below" and gives no prose beyond the
120/// `Reason` column for any of these codes.
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122#[repr(u64)]
123pub enum AnnounceErrorCode {
124    /// `Internal Error`.
125    InternalError = 0x0,
126    /// `Unauthorized`.
127    Unauthorized = 0x1,
128    /// `Timeout`.
129    Timeout = 0x2,
130    /// `Not Supported`.
131    NotSupported = 0x3,
132    /// `Uninterested`.
133    Uninterested = 0x4,
134}
135
136/// SUBSCRIBE_ERROR codes, from draft-09 Section 7.16 (SUBSCRIBE_ERROR).
137///
138/// The draft introduces the table with "The application SHOULD use a relevant
139/// error code in SUBSCRIBE_ERROR, as defined below".
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141#[repr(u64)]
142pub enum SubscribeErrorCode {
143    /// `Internal Error`.
144    InternalError = 0x0,
145    /// `Unauthorized`.
146    Unauthorized = 0x1,
147    /// `Timeout`.
148    Timeout = 0x2,
149    /// `Not Supported`.
150    NotSupported = 0x3,
151    /// `Track Does Not Exist`.
152    TrackDoesNotExist = 0x4,
153    /// `Invalid Range` — Section 7.4 adds that if a publisher cannot satisfy
154    /// the requested start or end, or if the end has already been published, it
155    /// SHOULD send a SUBSCRIBE_ERROR with this code.
156    InvalidRange = 0x5,
157    /// `Retry Track Alias` — Section 7.16 adds that the subscriber SHOULD
158    /// re-issue the SUBSCRIBE with the Track Alias carried in the
159    /// SUBSCRIBE_ERROR message instead. If that Track Alias is already in use,
160    /// the subscriber MUST close the connection with a Duplicate Track Alias
161    /// error (Section 3.5).
162    RetryTrackAlias = 0x6,
163}
164
165/// FETCH_ERROR codes, from draft-09 Section 7.18 (FETCH_ERROR).
166///
167/// The draft introduces the table with "The application SHOULD use a relevant
168/// error code in FETCH_ERROR, as defined below" and gives no prose beyond the
169/// `Reason` column for any of these codes. The table stops at `Invalid Range`:
170/// unlike SUBSCRIBE_ERROR, FETCH_ERROR has no `Retry Track Alias` code in this
171/// draft.
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173#[repr(u64)]
174pub enum FetchErrorCode {
175    /// `Internal Error`.
176    InternalError = 0x0,
177    /// `Unauthorized`.
178    Unauthorized = 0x1,
179    /// `Timeout`.
180    Timeout = 0x2,
181    /// `Not Supported`.
182    NotSupported = 0x3,
183    /// `Track Does Not Exist`.
184    TrackDoesNotExist = 0x4,
185    /// `Invalid Range`.
186    InvalidRange = 0x5,
187}
188
189/// SUBSCRIBE_DONE status codes, from draft-09 Section 7.19 (SUBSCRIBE_DONE).
190///
191/// The draft introduces the table with "The application SHOULD use a relevant
192/// status code in SUBSCRIBE_DONE, as defined below"; the table's second column
193/// is still headed `Reason`. The Status Code indicates why the subscription
194/// ended, and whether it was an error.
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196#[repr(u64)]
197pub enum SubscribeDoneStatusCode {
198    /// `Internal Error`.
199    InternalError = 0x0,
200    /// `Unauthorized`.
201    Unauthorized = 0x1,
202    /// `Track Ended`.
203    TrackEnded = 0x2,
204    /// `Subscription Ended`.
205    SubscriptionEnded = 0x3,
206    /// `Going Away`.
207    GoingAway = 0x4,
208    /// `Expired`.
209    Expired = 0x5,
210    /// `Too Far Behind` — Section 7.1.1.2 adds that if a subscriber exceeds the
211    /// publisher's resource limits by failing to consume objects at a sufficient
212    /// rate, the publisher MAY terminate the subscription with this code.
213    TooFarBehind = 0x6,
214}
215
216/// SUBSCRIBE_ANNOUNCES_ERROR codes, from draft-09 Section 7.26
217/// (SUBSCRIBE_ANNOUNCES_ERROR).
218///
219/// The draft introduces the table with "The application SHOULD use a relevant
220/// error code in SUBSCRIBE_ANNOUNCES_ERROR, as defined below" and gives no prose
221/// beyond the `Reason` column for any of these codes.
222#[derive(Debug, Clone, Copy, PartialEq, Eq)]
223#[repr(u64)]
224pub enum SubscribeAnnouncesErrorCode {
225    /// `Internal Error`.
226    InternalError = 0x0,
227    /// `Unauthorized`.
228    Unauthorized = 0x1,
229    /// `Timeout`.
230    Timeout = 0x2,
231    /// `Not Supported`.
232    NotSupported = 0x3,
233    /// `Namespace Prefix Unknown`.
234    NamespacePrefixUnknown = 0x4,
235}
236
237impl SessionErrorCode {
238    /// Every session termination code draft-09 assigns, in ascending wire order.
239    ///
240    /// This is the set [`Self::from_u64`] accepts, written out so that it can
241    /// be enumerated: nothing can iterate an enum's variants, so a caller that
242    /// wants the registry has to be handed it. Writing it down is also what
243    /// lets a test state its claims about the registry itself rather than about
244    /// the range some sweep happens to reach.
245    pub const ALL: &[SessionErrorCode] = &[
246        SessionErrorCode::NoError,
247        SessionErrorCode::InternalError,
248        SessionErrorCode::Unauthorized,
249        SessionErrorCode::ProtocolViolation,
250        SessionErrorCode::DuplicateTrackAlias,
251        SessionErrorCode::ParameterLengthMismatch,
252        SessionErrorCode::TooManySubscribes,
253        SessionErrorCode::GoawayTimeout,
254        SessionErrorCode::ControlMessageTimeout,
255        SessionErrorCode::DataStreamTimeout,
256    ];
257
258    /// Convert a raw u64 to a `SessionErrorCode`, if draft-09 defines it.
259    pub fn from_u64(v: u64) -> Option<Self> {
260        match v {
261            0x0 => Some(SessionErrorCode::NoError),
262            0x1 => Some(SessionErrorCode::InternalError),
263            0x2 => Some(SessionErrorCode::Unauthorized),
264            0x3 => Some(SessionErrorCode::ProtocolViolation),
265            0x4 => Some(SessionErrorCode::DuplicateTrackAlias),
266            0x5 => Some(SessionErrorCode::ParameterLengthMismatch),
267            0x6 => Some(SessionErrorCode::TooManySubscribes),
268            0x10 => Some(SessionErrorCode::GoawayTimeout),
269            0x11 => Some(SessionErrorCode::ControlMessageTimeout),
270            0x12 => Some(SessionErrorCode::DataStreamTimeout),
271            _ => None,
272        }
273    }
274
275    /// Return the raw u64 value of this error code.
276    pub fn as_u64(self) -> u64 {
277        self as u64
278    }
279}
280
281impl AnnounceErrorCode {
282    /// Every ANNOUNCE_ERROR code draft-09 assigns, in ascending wire order.
283    ///
284    /// This is the set [`Self::from_u64`] accepts, written out so that it can
285    /// be enumerated: nothing can iterate an enum's variants, so a caller that
286    /// wants the registry has to be handed it. Writing it down is also what
287    /// lets a test state its claims about the registry itself rather than about
288    /// the range some sweep happens to reach.
289    pub const ALL: &[AnnounceErrorCode] = &[
290        AnnounceErrorCode::InternalError,
291        AnnounceErrorCode::Unauthorized,
292        AnnounceErrorCode::Timeout,
293        AnnounceErrorCode::NotSupported,
294        AnnounceErrorCode::Uninterested,
295    ];
296
297    /// Convert a raw u64 to an `AnnounceErrorCode`, if draft-09 defines it.
298    pub fn from_u64(v: u64) -> Option<Self> {
299        match v {
300            0x0 => Some(AnnounceErrorCode::InternalError),
301            0x1 => Some(AnnounceErrorCode::Unauthorized),
302            0x2 => Some(AnnounceErrorCode::Timeout),
303            0x3 => Some(AnnounceErrorCode::NotSupported),
304            0x4 => Some(AnnounceErrorCode::Uninterested),
305            _ => None,
306        }
307    }
308
309    /// Return the raw u64 value of this error code.
310    pub fn as_u64(self) -> u64 {
311        self as u64
312    }
313}
314
315impl SubscribeErrorCode {
316    /// Every SUBSCRIBE_ERROR code draft-09 assigns, in ascending wire order.
317    ///
318    /// This is the set [`Self::from_u64`] accepts, written out so that it can
319    /// be enumerated: nothing can iterate an enum's variants, so a caller that
320    /// wants the registry has to be handed it. Writing it down is also what
321    /// lets a test state its claims about the registry itself rather than about
322    /// the range some sweep happens to reach.
323    pub const ALL: &[SubscribeErrorCode] = &[
324        SubscribeErrorCode::InternalError,
325        SubscribeErrorCode::Unauthorized,
326        SubscribeErrorCode::Timeout,
327        SubscribeErrorCode::NotSupported,
328        SubscribeErrorCode::TrackDoesNotExist,
329        SubscribeErrorCode::InvalidRange,
330        SubscribeErrorCode::RetryTrackAlias,
331    ];
332
333    /// Convert a raw u64 to a `SubscribeErrorCode`, if draft-09 defines it.
334    pub fn from_u64(v: u64) -> Option<Self> {
335        match v {
336            0x0 => Some(SubscribeErrorCode::InternalError),
337            0x1 => Some(SubscribeErrorCode::Unauthorized),
338            0x2 => Some(SubscribeErrorCode::Timeout),
339            0x3 => Some(SubscribeErrorCode::NotSupported),
340            0x4 => Some(SubscribeErrorCode::TrackDoesNotExist),
341            0x5 => Some(SubscribeErrorCode::InvalidRange),
342            0x6 => Some(SubscribeErrorCode::RetryTrackAlias),
343            _ => None,
344        }
345    }
346
347    /// Return the raw u64 value of this error code.
348    pub fn as_u64(self) -> u64 {
349        self as u64
350    }
351}
352
353impl FetchErrorCode {
354    /// Every FETCH_ERROR code draft-09 assigns, in ascending wire order.
355    ///
356    /// This is the set [`Self::from_u64`] accepts, written out so that it can
357    /// be enumerated: nothing can iterate an enum's variants, so a caller that
358    /// wants the registry has to be handed it. Writing it down is also what
359    /// lets a test state its claims about the registry itself rather than about
360    /// the range some sweep happens to reach.
361    pub const ALL: &[FetchErrorCode] = &[
362        FetchErrorCode::InternalError,
363        FetchErrorCode::Unauthorized,
364        FetchErrorCode::Timeout,
365        FetchErrorCode::NotSupported,
366        FetchErrorCode::TrackDoesNotExist,
367        FetchErrorCode::InvalidRange,
368    ];
369
370    /// Convert a raw u64 to a `FetchErrorCode`, if draft-09 defines it.
371    pub fn from_u64(v: u64) -> Option<Self> {
372        match v {
373            0x0 => Some(FetchErrorCode::InternalError),
374            0x1 => Some(FetchErrorCode::Unauthorized),
375            0x2 => Some(FetchErrorCode::Timeout),
376            0x3 => Some(FetchErrorCode::NotSupported),
377            0x4 => Some(FetchErrorCode::TrackDoesNotExist),
378            0x5 => Some(FetchErrorCode::InvalidRange),
379            _ => None,
380        }
381    }
382
383    /// Return the raw u64 value of this error code.
384    pub fn as_u64(self) -> u64 {
385        self as u64
386    }
387}
388
389impl SubscribeDoneStatusCode {
390    /// Every SUBSCRIBE_DONE status code draft-09 assigns, in ascending wire order.
391    ///
392    /// This is the set [`Self::from_u64`] accepts, written out so that it can
393    /// be enumerated: nothing can iterate an enum's variants, so a caller that
394    /// wants the registry has to be handed it. Writing it down is also what
395    /// lets a test state its claims about the registry itself rather than about
396    /// the range some sweep happens to reach.
397    pub const ALL: &[SubscribeDoneStatusCode] = &[
398        SubscribeDoneStatusCode::InternalError,
399        SubscribeDoneStatusCode::Unauthorized,
400        SubscribeDoneStatusCode::TrackEnded,
401        SubscribeDoneStatusCode::SubscriptionEnded,
402        SubscribeDoneStatusCode::GoingAway,
403        SubscribeDoneStatusCode::Expired,
404        SubscribeDoneStatusCode::TooFarBehind,
405    ];
406
407    /// Convert a raw u64 to a `SubscribeDoneStatusCode`, if draft-09 defines it.
408    pub fn from_u64(v: u64) -> Option<Self> {
409        match v {
410            0x0 => Some(SubscribeDoneStatusCode::InternalError),
411            0x1 => Some(SubscribeDoneStatusCode::Unauthorized),
412            0x2 => Some(SubscribeDoneStatusCode::TrackEnded),
413            0x3 => Some(SubscribeDoneStatusCode::SubscriptionEnded),
414            0x4 => Some(SubscribeDoneStatusCode::GoingAway),
415            0x5 => Some(SubscribeDoneStatusCode::Expired),
416            0x6 => Some(SubscribeDoneStatusCode::TooFarBehind),
417            _ => None,
418        }
419    }
420
421    /// Return the raw u64 value of this status code.
422    pub fn as_u64(self) -> u64 {
423        self as u64
424    }
425}
426
427impl SubscribeAnnouncesErrorCode {
428    /// Every SUBSCRIBE_ANNOUNCES_ERROR code draft-09 assigns, in ascending wire order.
429    ///
430    /// This is the set [`Self::from_u64`] accepts, written out so that it can
431    /// be enumerated: nothing can iterate an enum's variants, so a caller that
432    /// wants the registry has to be handed it. Writing it down is also what
433    /// lets a test state its claims about the registry itself rather than about
434    /// the range some sweep happens to reach.
435    pub const ALL: &[SubscribeAnnouncesErrorCode] = &[
436        SubscribeAnnouncesErrorCode::InternalError,
437        SubscribeAnnouncesErrorCode::Unauthorized,
438        SubscribeAnnouncesErrorCode::Timeout,
439        SubscribeAnnouncesErrorCode::NotSupported,
440        SubscribeAnnouncesErrorCode::NamespacePrefixUnknown,
441    ];
442
443    /// Convert a raw u64 to a `SubscribeAnnouncesErrorCode`, if draft-09 defines
444    /// it.
445    pub fn from_u64(v: u64) -> Option<Self> {
446        match v {
447            0x0 => Some(SubscribeAnnouncesErrorCode::InternalError),
448            0x1 => Some(SubscribeAnnouncesErrorCode::Unauthorized),
449            0x2 => Some(SubscribeAnnouncesErrorCode::Timeout),
450            0x3 => Some(SubscribeAnnouncesErrorCode::NotSupported),
451            0x4 => Some(SubscribeAnnouncesErrorCode::NamespacePrefixUnknown),
452            _ => None,
453        }
454    }
455
456    /// Return the raw u64 value of this error code.
457    pub fn as_u64(self) -> u64 {
458        self as u64
459    }
460}
461
462#[cfg(test)]
463mod tests {
464    use super::*;
465
466    #[test]
467    fn session_error_code_roundtrip() {
468        for code in [
469            SessionErrorCode::NoError,
470            SessionErrorCode::InternalError,
471            SessionErrorCode::Unauthorized,
472            SessionErrorCode::ProtocolViolation,
473            SessionErrorCode::DuplicateTrackAlias,
474            SessionErrorCode::ParameterLengthMismatch,
475            SessionErrorCode::TooManySubscribes,
476            SessionErrorCode::GoawayTimeout,
477            SessionErrorCode::ControlMessageTimeout,
478            SessionErrorCode::DataStreamTimeout,
479        ] {
480            assert_eq!(SessionErrorCode::from_u64(code.as_u64()), Some(code));
481        }
482    }
483
484    #[test]
485    fn unknown_codes_are_none() {
486        // 0x7..=0xF are unassigned in draft-09's termination table, and the
487        // table stops at 0x12. Assert over the whole gap rather than a sample.
488        for code in 0x7..=0xF {
489            assert_eq!(SessionErrorCode::from_u64(code), None, "0x{code:x}");
490        }
491        assert_eq!(SessionErrorCode::from_u64(0x13), None);
492        assert_eq!(AnnounceErrorCode::from_u64(0x5), None);
493        assert_eq!(SubscribeErrorCode::from_u64(0x7), None);
494        assert_eq!(FetchErrorCode::from_u64(0x6), None);
495        assert_eq!(SubscribeDoneStatusCode::from_u64(0x7), None);
496        assert_eq!(SubscribeAnnouncesErrorCode::from_u64(0x5), None);
497        assert_eq!(SessionErrorCode::from_u64(u64::MAX), None);
498    }
499
500    #[test]
501    fn fetch_error_has_no_retry_track_alias() {
502        // draft-09 gives SUBSCRIBE_ERROR 0x6 but stops FETCH_ERROR at 0x5.
503        assert_eq!(SubscribeErrorCode::from_u64(0x6), Some(SubscribeErrorCode::RetryTrackAlias));
504        assert_eq!(FetchErrorCode::from_u64(0x6), None);
505    }
506}
507
508/// TRACK_STATUS Status Code values (draft-09, Section 7.24).
509///
510/// The draft defines these as a prose list rather than as a `Code`/`Reason`
511/// table, so they are named from the prose. It is stricter about this field
512/// than about the error registries above: the Status Code "MUST hold one of the
513/// following values" and "Any other value in the Status Code field is a
514/// malformed message", so [`TrackStatusCode::from_u64`] answering `None` is a
515/// decode failure rather than a merely unrecognised code.
516#[derive(Debug, Clone, Copy, PartialEq, Eq)]
517#[repr(u64)]
518pub enum TrackStatusCode {
519    /// The track is in progress, and subsequent fields contain the highest
520    /// group and object ID for that track.
521    InProgress = 0x00,
522    /// The track does not exist. Subsequent fields MUST be zero, and any other
523    /// value is a malformed message.
524    TrackDoesNotExist = 0x01,
525    /// The track has not yet begun. Subsequent fields MUST be zero, and any
526    /// other value is a malformed message.
527    NotYetBegun = 0x02,
528    /// The track has finished, so there is no live edge. Subsequent fields
529    /// contain the highest group and object ID known.
530    Finished = 0x03,
531    /// The publisher is a relay that cannot obtain the current track status
532    /// from upstream. Subsequent fields contain the largest group and object
533    /// ID known.
534    RelayStatusUnavailable = 0x04,
535}
536
537impl TrackStatusCode {
538    /// Convert a raw u64 to a `TrackStatusCode`, if this draft assigns it.
539    pub fn from_u64(v: u64) -> Option<Self> {
540        match v {
541            0x00 => Some(TrackStatusCode::InProgress),
542            0x01 => Some(TrackStatusCode::TrackDoesNotExist),
543            0x02 => Some(TrackStatusCode::NotYetBegun),
544            0x03 => Some(TrackStatusCode::Finished),
545            0x04 => Some(TrackStatusCode::RelayStatusUnavailable),
546            _ => None,
547        }
548    }
549
550    /// Whether this code requires the fields after it to be zero.
551    ///
552    /// Section 7.24 says of 0x01 "Subsequent fields MUST be zero, and any other
553    /// value is a malformed message", and the same of 0x02. The other three
554    /// codes describe those fields as carrying a real location, so they place
555    /// no requirement on them.
556    pub fn requires_zero_location(self) -> bool {
557        matches!(self, TrackStatusCode::TrackDoesNotExist | TrackStatusCode::NotYetBegun)
558    }
559
560    /// Return the raw u64 value of this status code.
561    pub fn as_u64(self) -> u64 {
562        self as u64
563    }
564}