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