Skip to main content

moqtap_codec/draft20/
types.rs

1//! Draft-20 object status values.
2//!
3//! - 0x0 = Normal, may carry a payload
4//! - 0x3 = End of Group, may not
5//! - 0x4 = End of Track, may not
6//!
7//! Unchanged from draft-19 in both the code points and the rule behind them.
8//! Draft-18 and earlier stated flatly that an Object with a status other than
9//! Normal has an empty payload, so the payload rule could be read off the
10//! status number; draft-19 gave the Object Status registry a "Payload" column
11//! and made the rule registry data, and draft-20 Section 15.9 keeps it that
12//! way. It is carried here as a `PayloadPermission` on the status itself.
13//!
14//! Object Status is present only on subscription-delivered Objects. Draft-20
15//! Section 11.2.1.1 is unchanged on this: a fetch stream's objects carry no
16//! status field at all.
17
18/// Whether an Object carrying a given status is permitted a non-empty payload:
19/// the "Payload" column of the Object Status registry, MoQ Transport draft-20
20/// Section 15.9, Table 16.
21///
22/// Draft-20 Section 11.2.1.1 phrases the rule as "An Object MUST have an empty
23/// payload unless its Object Status value is registered as permitting a
24/// payload", and Section 15.9 adds that each new registration "MUST indicate
25/// whether the status permits a payload". Modelling the column as a value keeps
26/// that obligation visible: a status cannot be added to [`ObjectStatus`]
27/// without [`ObjectStatus::payload_permission`] refusing to compile until its
28/// column is filled in.
29///
30/// Drafts up to 18 had no such column — they derived the same answer
31/// arithmetically, from the status being non-zero — so this type belongs to
32/// draft-19 and later rather than being shared across the range.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum PayloadPermission {
35    /// Registry column "Yes". The status permits a payload but does not
36    /// require one: a zero-length Object with such a status is well formed.
37    Permitted,
38    /// Registry column "No". An Object with such a status has an empty
39    /// payload, and one carrying bytes is malformed.
40    Forbidden,
41}
42
43impl PayloadPermission {
44    /// `true` for [`PayloadPermission::Permitted`].
45    ///
46    /// The permission answers on its own, without a payload length in hand,
47    /// which is the point of moving the rule onto the status.
48    pub fn permits(self) -> bool {
49        matches!(self, PayloadPermission::Permitted)
50    }
51}
52
53/// Object status values, from MoQ Transport draft-20 Section 11.2.1.1
54/// "Object Status", with the same three code points listed in the IANA Object
55/// Status registry at Section 15.9, Table 16.
56///
57/// The draft assigns 0x0, 0x3 and 0x4. Of every other value the section says:
58/// "Any other value SHOULD be treated as a protocol error and the session
59/// SHOULD be closed with a PROTOCOL_VIOLATION". [`ObjectStatus::from_u64`]
60/// answers `None` for everything the draft leaves unassigned, 0x1 and 0x2
61/// included. The section also states plainly that there is no status meaning
62/// end of Subgroup: a subgroup ends when its stream is closed with a FIN.
63///
64/// Each status carries the registry's payload rule with it, as
65/// [`ObjectStatus::payload_permission`].
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67#[repr(u8)]
68pub enum ObjectStatus {
69    /// Normal object. The one status Table 16 marks "Payload: Yes", and the
70    /// status of every Object that carries bytes — the encodings elide it and
71    /// spell it out only when the payload is empty.
72    Normal = 0x0,
73    /// End of Group. No object with the given Group ID and an Object ID greater
74    /// than or equal to the one specified exists in that group. Table 16 marks
75    /// it "Payload: No".
76    EndOfGroup = 0x3,
77    /// End of Track. No object at a location equal to or greater than the one
78    /// specified exists. Table 16 marks it "Payload: No".
79    EndOfTrack = 0x4,
80}
81
82impl ObjectStatus {
83    /// Every status draft-20 assigns, in ascending wire order.
84    ///
85    /// This is exactly the set [`ObjectStatus::from_u64`] accepts, and exactly
86    /// the three rows of the draft's Object Status registry. Any other value is
87    /// one the draft does not assign.
88    pub const ALL: &[ObjectStatus] =
89        &[ObjectStatus::Normal, ObjectStatus::EndOfGroup, ObjectStatus::EndOfTrack];
90
91    /// Convert a raw u64 to an `ObjectStatus`, or `None` if draft-20 does not
92    /// assign that value.
93    pub fn from_u64(v: u64) -> Option<Self> {
94        match v {
95            0x0 => Some(ObjectStatus::Normal),
96            0x3 => Some(ObjectStatus::EndOfGroup),
97            0x4 => Some(ObjectStatus::EndOfTrack),
98            _ => None,
99        }
100    }
101
102    /// The registry's "Payload" column for this status, from draft-20
103    /// Section 15.9, Table 16: Normal is "Yes", End of Group and End of Track
104    /// are "No".
105    ///
106    /// This is the whole of the rule draft-20 Section 11.2.1.1 states — an
107    /// Object has an empty payload unless its status is registered as
108    /// permitting one — so no caller has to restate it, and none has to reach
109    /// for a payload length to guess at it. The three rows currently agree with
110    /// the blanket *any status other than Normal means an empty payload* that
111    /// drafts up to 18 used; they agree by coincidence of the current
112    /// assignments, not by construction, and a status registered later with
113    /// "Payload: Yes" would part them.
114    pub fn payload_permission(self) -> PayloadPermission {
115        match self {
116            ObjectStatus::Normal => PayloadPermission::Permitted,
117            ObjectStatus::EndOfGroup => PayloadPermission::Forbidden,
118            ObjectStatus::EndOfTrack => PayloadPermission::Forbidden,
119        }
120    }
121
122    /// `true` when the registry permits an Object with this status to carry a
123    /// non-empty payload. Shorthand for
124    /// `self.payload_permission().permits()`.
125    ///
126    /// Permitting is not requiring: a Normal Object with no payload is well
127    /// formed, and draft-20's encodings have a way to spell it.
128    pub fn permits_payload(self) -> bool {
129        self.payload_permission().permits()
130    }
131
132    /// Return the wire value.
133    pub fn as_u64(self) -> u64 {
134        self as u64
135    }
136
137    /// Return the wire value as a single byte.
138    ///
139    /// A draft-20 status datagram carries its status as one bare byte rather
140    /// than a varint, so the datagram encoder needs the code in that width;
141    /// every assigned code is well under 0xff, so this is the same number
142    /// [`ObjectStatus::as_u64`] returns.
143    pub fn as_u8(self) -> u8 {
144        self as u8
145    }
146}