Skip to main content

moqtap_codec/draft19/
types.rs

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