Skip to main content

moqtap_codec/draft21/
data_stream.rs

1//! Draft-21 data stream header encoding and decoding.
2//!
3//! Byte-for-byte identical to draft-19. The field list, the field order, the
4//! widths and the set of valid Type values are all unchanged; what draft-20
5//! changed is the receive path.
6//!
7//! # `Type` became `Type Flags`, and the rules left the figure
8//!
9//! Draft-19 enumerated the legal leading values inside the figure —
10//! `Type (vi64) = 0x10..0x15 / 0x18..0x1D / …` for a subgroup header — and
11//! draft-21 declares the field a set of flags and states the invalid
12//! combinations as prose after it. Computing draft-21's rules gives back
13//! draft-19's enumeration byte for byte, on both carriers, so no valid frame
14//! changed hands. A decoder that validated by range membership was already
15//! right; one that validated loosely, by masking off bits it did not know,
16//! becomes wrong.
17//!
18//! **The two carriers do not share a rule set, and neither can be derived from
19//! the other.** Section 11.3.1 gives SUBGROUP_HEADER three conditions and
20//! Section 11.2.1 gives OBJECT_DATAGRAM three of its own, and they disagree at
21//! two points:
22//!
23//! * bit 4 (`0x10`) **MUST be 1** on a subgroup header and **MUST be 0** on a
24//!   datagram, where it is reserved — the same bit, opposite requirements, and
25//!   it is what tells the two carriers apart in the stream-type registry;
26//! * the datagram has an "a bit set whose meaning is not specified" condition
27//!   and the subgroup header has none, because bits 0 through 6 are all
28//!   specified for a subgroup. The subgroup gets an explicit "values of 128 or
29//!   greater" condition instead, which the datagram does not have — there the
30//!   unspecified-bit rule catches the same values, since 128 sets bit 7.
31//!
32//! # Non-minimal Type Flags are accepted on receive and never emitted
33//!
34//! Section 8.1 permits a value to be encoded in more bytes than it needs, and
35//! Section 11.3.1 words its third condition as "values of 128 or greater (i.e.,
36//! any value that requires more than a one-byte variable-length integer
37//! encoding)". Those two clauses are not equivalent under that allowance: the
38//! value `0x14` written as the two-byte `0x8014` is a legal varint whose value
39//! is below 128 and whose encoding is two bytes. **This codec reads the rule as
40//! a bound on the value**, so it decodes such a header, and it always emits the
41//! minimal one-byte form. The draft does not settle which clause governs;
42//! rejecting a legal-but-non-minimal encoding risks failing a conformant peer
43//! and emitting one risks tripping a stricter peer, so the asymmetry is the
44//! safe default. The draft-19 decoder refuses every wide spelling outright;
45//! this one reads the whole field.
46//!
47//! # The payload rule
48//!
49//! Unchanged from draft-19, and worth restating because it is the one place
50//! where the encoder refuses an object draft-18 would have reinterpreted.
51//! Draft-18 said every Object with a status other than Normal has an empty
52//! payload; draft-21 Section 11.1.2 says instead that an Object has an empty
53//! payload unless its status is registered as permitting one, and
54//! Section 16.9 puts that permission in the Object Status registry. Both
55//! encodings here keep their draft-18 framing — a subgroup object carries a
56//! status exactly when its Object Payload Length is zero, and a datagram
57//! carries one exactly when its type sets the STATUS bit, both stated that way
58//! by the draft — so no frame these decoders can read is able to state a
59//! status and a payload at once. The registry rule therefore bites where a
60//! caller can hold both: `SubgroupObjectReader::write_object` consults the
61//! status's payload permission and refuses an object whose status forbids the
62//! payload handed with it, rather than dropping the status and writing the
63//! bytes as a Normal object. On the way back out, `SubgroupObject` and
64//! `DatagramHeader` answer the same question from the registry, so a reader
65//! never has to recover it from a length.
66//!
67//! Subgroup Type Flags: form 0b0XX1XXXX, so bit 4 is set and bit 7 is
68//! clear; the ranges are 0x10..0x1F, 0x30..0x3F, 0x50..0x5F, 0x70..0x7F.
69//!   - bit 0 (0x01): PROPERTIES
70//!   - bits 1-2 (0x06): SUBGROUP_ID_MODE (0=zero, 1=first_obj, 2=explicit, 3=reserved)
71//!   - bit 3 (0x08): END_OF_GROUP
72//!   - bit 5 (0x20): DEFAULT_PRIORITY (no priority byte)
73//!   - bit 6 (0x40): FIRST_OBJECT
74//!
75//! Datagram Type Flags: 0b00X0XXXX, so bits 4, 6 and 7 are clear; the ranges
76//! are 0x00..0x0F and 0x20..0x2F.
77//!   - bit 0 (0x01): PROPERTIES
78//!   - bit 1 (0x02): END_OF_GROUP
79//!   - bit 2 (0x04): ZERO_OBJECT_ID (object_id=0, field omitted)
80//!   - bit 3 (0x08): DEFAULT_PRIORITY (no priority byte)
81//!   - bit 5 (0x20): STATUS (status byte replaces payload)
82//!
83//! Neither range is fully assigned, and draft-21 spells out which values in
84//! them an endpoint must refuse rather than decode, closing the session with a
85//! PROTOCOL_VIOLATION. Section 11.3.1 excludes the subgroup Types whose
86//! SUBGROUP_ID_MODE is the reserved 0b11 — 0x16, 0x17, 0x1E, 0x1F and the same
87//! four offsets in each higher range — because that mode does not say whether
88//! a Subgroup ID field follows the Group ID, so a decoder would have to guess
89//! and a wrong guess shifts every later field by the width of that varint.
90//! Section 11.2.1 excludes the datagram Types setting both STATUS (0x20) and
91//! END_OF_GROUP (0x02) — 0x22, 0x23, 0x26, 0x27, 0x2A, 0x2B, 0x2E and 0x2F —
92//! because an object status message cannot signal end of group.
93//! [`SubgroupHeader::decode`] and [`DatagramHeader::decode`] refuse both
94//! lists, and [`SubgroupHeader::encode_checked`] and
95//! [`DatagramHeader::encode_checked`] refuse to write them.
96//!
97//! Fetch header: stream type 0x05 + request_id, then per-object records whose
98//! leading Serialization Flags varint says which of the object's fields are on
99//! the wire at all; [`FetchObjectHeader`] decodes and encodes one such record.
100//! Draft-20 adds a third End of Range marker to Table 7,
101//! [`FetchEndOfRange::TimedOut`] (`0x20C`), for the Objects a relay abandoned
102//! when its `FILL_TIMEOUT` budget ran out — what draft-19 reported as an
103//! Unknown range.
104
105use bytes::{Buf, BufMut};
106
107use super::types::{ObjectStatus, PayloadPermission};
108use crate::error::CodecError;
109use crate::object_properties::{ObjectProperty, PropertyValue};
110use crate::varint::{Moqt18 as Wire, VarInt};
111
112/// Advance `buf` past `len` bytes without copying them.
113fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
114    let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
115    if buf.remaining() < len {
116        return Err(CodecError::UnexpectedEnd);
117    }
118    buf.advance(len);
119    Ok(())
120}
121
122/// Turn a wire Object Status code into an [`ObjectStatus`], refusing one
123/// draft-21 does not assign.
124///
125/// Draft-21 Section 11.1.2 lists the codes an object may carry — the three
126/// rows of the Object Status registry it establishes in Section 16.9 — and
127/// says any other value SHOULD be treated as a protocol error and the session
128/// closed with a PROTOCOL_VIOLATION. Every place this module reads a status
129/// converts it here, so a decoded [`SubgroupObject::object_status`] or
130/// [`DatagramHeader::object_status`] is always a status the draft assigns, and
131/// [`SubgroupObjectMeta::status`] — which stays a raw code because a relay may
132/// carry it to a draft that numbers the set differently — holds one because it
133/// comes from the same conversion.
134fn decoded_status(code: u64) -> Result<ObjectStatus, CodecError> {
135    ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)
136}
137
138// ── Stream and datagram types ─────────────────────────────────
139
140/// Unidirectional stream type for padding, draft-21 Section 11.5.1: "An
141/// endpoint MAY open a unidirectional stream with a stream type of 0x132B3E28
142/// to send padding data. The stream begins with the stream type, followed by
143/// zero or more bytes that MUST all be set to zero."
144///
145/// Named here because the value is what tells a padding stream from a data
146/// stream, and nothing else in this module would otherwise say so. Under the
147/// draft-21 variable-length integer encoding the value takes five bytes, the
148/// first of which is 0xF0 — an octet with bit 7 set, which the subgroup form
149/// leaves clear. A reader judging the Type by that first byte alone would find
150/// the form broken and call the stream unknown, which Section 6.4.1 answers by
151/// ending the session. Table 2 assigns the value, so that would be a close over
152/// traffic this draft permits.
153pub const PADDING_STREAM_TYPE: u64 = 0x132B_3E28;
154
155/// Datagram type for padding, draft-21 Section 11.5.2: "An endpoint MAY send a
156/// datagram with a type of 0x132B3E29 to send padding data. The datagram
157/// contains the type followed by zero or more bytes that MUST all be set to
158/// zero."
159///
160/// One more than [`PADDING_STREAM_TYPE`] and encoded the same width, and
161/// assigned the same way, so the same reasoning applies to a datagram reader.
162pub const PADDING_DATAGRAM_TYPE: u64 = 0x132B_3E29;
163
164/// The unidirectional stream Type draft-21 Section 9.1 gives the control
165/// stream.
166const SETUP_STREAM_TYPE: u64 = 0x2F00;
167
168// ── Subgroup ──────────────────────────────────────────────────
169
170const SUBGROUP_PROPERTIES_BIT: u8 = 0x01;
171const SUBGROUP_ID_MODE_MASK: u8 = 0x06;
172const SUBGROUP_END_OF_GROUP_BIT: u8 = 0x08;
173const SUBGROUP_BASE_BIT: u8 = 0x10;
174const SUBGROUP_DEFAULT_PRIORITY_BIT: u8 = 0x20;
175const SUBGROUP_FIRST_OBJECT_BIT: u8 = 0x40;
176/// The SUBGROUP_ID_MODE value draft-21 reserves, once the mask is applied and
177/// the field shifted down.
178const SUBGROUP_ID_MODE_RESERVED: u8 = 0b11;
179
180/// Refuse a subgroup header `Type Flags` value draft-21 Section 11.3.1 lists
181/// as invalid.
182///
183/// The section gives **three** conditions and says of all of them that an
184/// endpoint receiving a stream header with such a value MUST close the session
185/// with a PROTOCOL_VIOLATION:
186///
187///   1. "Values with SUBGROUP_ID_MODE set to 0b11. This mode is reserved for
188///      future use." That is 0x16, 0x17, 0x1E, 0x1F and the same four offsets
189///      in each higher range.
190///   2. "Values where bit 4 is not set. Bit 4 MUST be 1 for SUBGROUP_HEADER."
191///   3. "Values of 128 or greater (i.e., any value that requires more than a
192///      one-byte variable-length integer encoding)."
193///
194/// Computing the three gives 0x10-0x15, 0x18-0x1D, 0x30-0x35, 0x38-0x3D,
195/// 0x50-0x55, 0x58-0x5D, 0x70-0x75 and 0x78-0x7D — byte for byte the
196/// enumeration draft-19 wrote into its figure.
197///
198/// **These are not the datagram's rules.** Section 11.2.1 reserves bit 4 and
199/// requires it to be *zero*, and adds an unspecified-bit condition that this
200/// list does not have, because bits 0 through 6 are all specified here. The two
201/// sets are read from their own sections and neither is derived from the other.
202///
203/// The reserved mode is worth separating from a mere unassigned code point,
204/// because it is not decodable rather than merely unknown. The other three
205/// modes each say whether a Subgroup ID field follows the Group ID; 0b11 says
206/// nothing, so a decoder has to guess, and a wrong guess shifts every
207/// subsequent field by the width of that varint.
208fn validate_subgroup_type(raw: u64) -> Result<(), CodecError> {
209    if subgroup_type_is_valid(raw) {
210        Ok(())
211    } else {
212        Err(stream_type_error(raw))
213    }
214}
215
216/// Whether `raw` is a subgroup `Type Flags` value draft-21 admits: below 128,
217/// bit 4 set, and not the reserved SUBGROUP_ID_MODE.
218fn subgroup_type_is_valid(raw: u64) -> bool {
219    // `raw < 0x80` is Section 11.3.1's third condition, and it subsumes the
220    // form's bit-7-clear requirement.
221    raw < 0x80 && {
222        let t = raw as u8;
223        t & SUBGROUP_BASE_BIT != 0 && (t & SUBGROUP_ID_MODE_MASK) >> 1 != SUBGROUP_ID_MODE_RESERVED
224    }
225}
226
227/// Which failure a leading unidirectional stream Type that is not the one a
228/// reader wants is.
229///
230/// Draft-21 states two rules about such a Type and answers both with a close,
231/// and telling them apart is the whole job of this function.
232///
233/// Section 6.4.1 is about the table: "An endpoint that receives an unknown stream
234/// type MUST close the session." A Type Table 2 does not assign is
235/// [`CodecError::UnknownStreamType`].
236///
237/// Section 11.3.1 is about the subgroup form specifically, and gives three
238/// conditions on a `Type Flags` value *inside* it. Those values are not unknown
239/// — the registry pattern 0b0XX1XXXX assigns the space and the draft rules
240/// particular values out within it — but they are unreadable, and they are
241/// [`CodecError::InvalidStreamTypeValue`], one arm per condition so a log names which.
242///
243/// Table 2 assigns four things, and two of them carry no Objects at all:
244/// FETCH_HEADER, the subgroup form, SETUP and PADDING. A subgroup reader handed
245/// any of them refuses it as [`CodecError::InvalidField`] — the value is one
246/// this draft defines, the disagreement is with the reader that was called, and
247/// the session survives it. See [`PADDING_STREAM_TYPE`] for why that case in
248/// particular is worth the trouble.
249///
250/// # Where the boundary between the two rules sits
251///
252/// Section 11.3.1's conditions are about a one-byte flags field, so they are
253/// applied to values that fit one — `raw <= 0xFF`. Above that the value is out
254/// of the space the section describes and the question is Table 2's instead:
255/// an assigned non-subgroup stream type is refused as the wrong reader, and
256/// anything else is unknown. That split is what keeps a datagram's
257/// `Type Flags` of 0x20 reported as an invalid subgroup value — which is what
258/// Section 11.3.1's bit-4 condition calls it — while a stray 0x5000 stays an
259/// unknown stream type.
260fn stream_type_error(raw: u64) -> CodecError {
261    if raw == FETCH_STREAM_TYPE
262        || raw == SETUP_STREAM_TYPE
263        || raw == PADDING_STREAM_TYPE
264        || subgroup_type_is_valid(raw)
265    {
266        return CodecError::InvalidField;
267    }
268    if raw > 0xFF {
269        return CodecError::UnknownStreamType(raw);
270    }
271    let t = raw as u8;
272    // Third condition first: it is the coarsest, and 128 or more can also set
273    // the other two bits in ways that would misreport it.
274    if raw >= 0x80 {
275        CodecError::InvalidStreamTypeValue {
276            raw,
277            detail: "SUBGROUP_HEADER Type Flags of 128 or greater are invalid",
278        }
279    } else if t & SUBGROUP_BASE_BIT == 0 {
280        CodecError::InvalidStreamTypeValue { raw, detail: "bit 4 must be 1 for SUBGROUP_HEADER" }
281    } else {
282        CodecError::InvalidStreamTypeValue {
283            raw,
284            detail: "its SUBGROUP_ID_MODE is 0b11, which this draft reserves",
285        }
286    }
287}
288
289#[derive(Debug, Clone)]
290pub struct SubgroupHeader {
291    pub header_type: u8,
292    pub track_alias: VarInt,
293    pub group_id: VarInt,
294    pub subgroup_id: VarInt,
295    pub publisher_priority: Option<u8>,
296}
297
298impl SubgroupHeader {
299    /// Decode a subgroup header, `Type Flags` field included.
300    ///
301    /// The field is read as a whole varint of whatever width the sender used,
302    /// and the value it decodes to is what the three Section 11.3.1 conditions
303    /// are applied to. **A non-minimal spelling of a valid value is
304    /// accepted**: `0x8010` decodes to 16, which is a legal `Type Flags`, and
305    /// this reads it. See the module documentation for why the draft leaves
306    /// that open and why permissive-on-receive is the choice here; the encoder
307    /// is the strict half and always writes the one-byte form.
308    ///
309    /// The draft-19 module refuses every wide spelling before looking at the
310    /// value; this one reads the whole field, which is also what
311    /// lets `stream_type_error` tell a padding or SETUP stream — both assigned,
312    /// both several bytes wide — from a Type Table 2 does not assign.
313    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
314        let raw = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
315        validate_subgroup_type(raw)?;
316        // Narrowing is safe: `validate_subgroup_type` has already refused
317        // everything at 128 or above.
318        let header_type = raw as u8;
319
320        let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
321        let group_id = VarInt::decode_moqt::<Wire>(buf)?;
322
323        let subgroup_id_mode = (header_type & SUBGROUP_ID_MODE_MASK) >> 1;
324        let subgroup_id = match subgroup_id_mode {
325            0 => VarInt::from_u64_moqt(0),
326            2 => VarInt::decode_moqt::<Wire>(buf)?,
327            // Mode 1 puts no Subgroup ID on the wire either: it is the first
328            // object's ID, which this header cannot see. Store 0 until a
329            // caller resolves it. Mode 3 never reaches here — the type check
330            // above refuses it.
331            _ => VarInt::from_u64_moqt(0),
332        };
333
334        let publisher_priority = if header_type & SUBGROUP_DEFAULT_PRIORITY_BIT == 0 {
335            if buf.remaining() < 1 {
336                return Err(CodecError::UnexpectedEnd);
337            }
338            Some(buf.get_u8())
339        } else {
340            None
341        };
342
343        Ok(SubgroupHeader { header_type, track_alias, group_id, subgroup_id, publisher_priority })
344    }
345
346    /// Serialize the header exactly as its Type byte describes it.
347    ///
348    /// Infallible, and so willing to write a Type draft-21 Section 11.3.1
349    /// tells an endpoint to reject — including a reserved-mode Type this
350    /// module's own [`Self::decode`] refuses to read back. Prefer
351    /// [`Self::encode_checked`], which refuses those Types instead.
352    pub fn encode(&self, buf: &mut impl BufMut) {
353        buf.put_u8(self.header_type);
354        self.track_alias.encode_moqt::<Wire>(buf);
355        self.group_id.encode_moqt::<Wire>(buf);
356
357        let subgroup_id_mode = (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1;
358        if subgroup_id_mode == 2 {
359            self.subgroup_id.encode_moqt::<Wire>(buf);
360        }
361
362        if self.header_type & SUBGROUP_DEFAULT_PRIORITY_BIT == 0 {
363            buf.put_u8(self.publisher_priority.unwrap_or(128));
364        }
365    }
366
367    /// Serialize the header, refusing a Type value draft-21 forbids.
368    ///
369    /// Errors with [`CodecError::InvalidField`] for exactly the Types draft-21
370    /// Section 11.3.1 lists as invalid (the same set [`Self::decode`] refuses),
371    /// before any byte is written, so a refused header leaves `buf` untouched.
372    /// Everything else is written by [`Self::encode`].
373    ///
374    /// The check belongs on the encode side as well as the decode side because
375    /// the two halves would otherwise disagree about which streams exist: a
376    /// reserved-mode header written by [`Self::encode`] cannot be read back by
377    /// [`Self::decode`], and a codec used to rewrite captured traffic would
378    /// emit a stream it could not then parse.
379    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
380        validate_subgroup_type(self.header_type as u64)?;
381        self.encode(buf);
382        Ok(())
383    }
384
385    pub fn has_properties(&self) -> bool {
386        self.header_type & SUBGROUP_PROPERTIES_BIT != 0
387    }
388
389    /// The subgroup-ID mode: `(header_type & 0x06) >> 1`.
390    ///
391    /// `0` = no subgroup ID on the wire and it is zero; `1` = the subgroup ID
392    /// is the first object's ID; `2` = an explicit ID follows the Group ID;
393    /// `3` = reserved. Exposed because the mask is module-private and
394    /// `dispatch::AnySubgroupHeader::subgroup_id_mode` cannot read it.
395    ///
396    /// `3` never comes back from [`Self::decode`], which refuses the Types
397    /// carrying it. `header_type` is a public field, so a hand-built header
398    /// can still report it; [`Self::encode_checked`] is what refuses to put
399    /// one on the wire.
400    pub fn subgroup_id_mode(&self) -> u8 {
401        (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1
402    }
403
404    pub fn is_end_of_group(&self) -> bool {
405        self.header_type & SUBGROUP_END_OF_GROUP_BIT != 0
406    }
407
408    /// `true` when the FIRST_OBJECT bit (0x40) is set, signaling the first
409    /// object on this stream is the original publisher's first object in the
410    /// subgroup. Added in draft-18.
411    pub fn is_first_object(&self) -> bool {
412        self.header_type & SUBGROUP_FIRST_OBJECT_BIT != 0
413    }
414}
415
416// ── Subgroup objects (stateful) ───────────────────────────────
417
418/// One object within a draft-21 subgroup stream. Object IDs are
419/// delta-encoded; whether a per-object "properties" block (the draft-21
420/// equivalent of extension headers) is present depends on the PROPERTIES
421/// bit on the enclosing [`SubgroupHeader`]. Use [`SubgroupObjectReader`]
422/// to encode/decode.
423#[derive(Debug, Clone)]
424pub struct SubgroupObject {
425    pub object_id: VarInt,
426    /// Raw properties bytes, excluding the byte-length prefix that precedes
427    /// them on the wire. Empty unless the subgroup header sets the
428    /// PROPERTIES bit, or when the block is present but zero-length.
429    /// Opaque: [`SubgroupObjectReader::write_object`] re-emits the prefix
430    /// and these bytes verbatim.
431    pub extension_headers: Vec<u8>,
432    pub payload_length: VarInt,
433    /// The object's status, carried on the wire only when `payload_length` is
434    /// zero. `None` with a zero `payload_length` is written as
435    /// [`ObjectStatus::Normal`].
436    ///
437    /// `None` is not "no status": every Object has one. It means the status is
438    /// the one the encoding elides — [`ObjectStatus::Normal`], the only row of
439    /// the Object Status registry (draft-21 Section 16.9) that permits the
440    /// payload such an object carries. Decoding a payload-bearing object leaves
441    /// this `None` for that reason; [`Self::status`] resolves it either way.
442    ///
443    /// Typed rather than a raw code. The wire field is a varint with room for
444    /// any value, and draft-21 assigns three of them; the decoder refuses the
445    /// rest, and this type is that same refusal on the encode side — 0x1 and
446    /// 0x2 cannot be named here, so [`SubgroupObjectReader::write_object`]
447    /// cannot emit a status this module's own decoder would reject.
448    ///
449    /// A status and a payload can be held here together, which the wire has no
450    /// way to express. That combination is what draft-21's registry rules on:
451    /// [`SubgroupObjectReader::write_object`] accepts it when the status is
452    /// registered as permitting a payload and refuses it otherwise.
453    pub object_status: Option<ObjectStatus>,
454    pub payload: Vec<u8>,
455}
456
457impl SubgroupObject {
458    /// The object's status, with the one draft-21's encoding elides filled in.
459    ///
460    /// A subgroup object states its status only when its Object Payload Length
461    /// is zero. An object that carries bytes therefore has no status field, and
462    /// its status is [`ObjectStatus::Normal`] — the sole row of the Object
463    /// Status registry (draft-21 Section 16.9) permitting a payload, so the
464    /// only status such an object could have had.
465    pub fn status(&self) -> ObjectStatus {
466        self.object_status.unwrap_or(ObjectStatus::Normal)
467    }
468
469    /// Whether the Object Status registry permits this object a non-empty
470    /// payload, per draft-21 Section 16.9.
471    ///
472    /// Answered from the status alone. `payload` and `payload_length` are not
473    /// consulted: on a status that permits a payload they say only whether this
474    /// particular object took the offer, and on one that forbids a payload a
475    /// non-empty payload is the malformation this reports, not evidence about
476    /// the rule.
477    pub fn permits_payload(&self) -> bool {
478        self.status().permits_payload()
479    }
480
481    /// Whether this object's status is allowed to carry the properties it has.
482    ///
483    /// Draft-21 Section 11.1.3: "Any Object with status Normal can have
484    /// properties (Section 8.4). If an endpoint receives properties on an
485    /// Object with status that is not Normal, it MUST close the session with a
486    /// PROTOCOL_VIOLATION."
487    ///
488    /// So this is `false` for exactly one shape: a non-empty properties block
489    /// on an object whose status is not [`ObjectStatus::Normal`]. An object
490    /// with no properties is fine at any status, and an object at Normal may
491    /// carry any properties.
492    ///
493    /// [`SubgroupObjectReader::read_object`] and
494    /// [`SubgroupObjectReader::write_object`] refuse an object this answers
495    /// `false` for; [`SubgroupObjectReader::read_object_meta`] frames it.
496    pub fn properties_permitted(&self) -> bool {
497        self.extension_headers.is_empty() || self.status() == ObjectStatus::Normal
498    }
499}
500
501/// The framing of one draft-21 subgroup object, without its payload.
502///
503/// Produced by [`SubgroupObjectReader::read_object_meta`] for callers that
504/// forward an object's bytes verbatim and never inspect the payload.
505#[derive(Debug, Clone, Copy, PartialEq, Eq)]
506pub struct SubgroupObjectMeta {
507    /// Resolved absolute Object ID.
508    pub object_id: u64,
509    /// Byte length of the properties block's contents, excluding its length
510    /// prefix.
511    pub extension_headers_len: u64,
512    /// Declared payload length. Zero when `status` is `Some`.
513    pub payload_length: u64,
514    /// Object status wire code, present only when the payload is empty.
515    pub status: Option<u64>,
516    /// Total bytes this object occupies on the wire, prefix fields included.
517    pub wire_len: u64,
518}
519
520impl SubgroupObjectMeta {
521    /// What the Object Status registry says about this object's payload, or
522    /// `None` if the registry has no row for its status.
523    ///
524    /// Draft-21 Section 16.9, Table 16 gives the registry a "Payload" column,
525    /// and Section 11.1.2 makes it the rule: an Object has an empty payload
526    /// unless its status is registered as permitting one. Table 16 fills the
527    /// column in for the three statuses draft-21 assigns — 0x0 Normal is
528    /// "Yes", 0x3 End of Group and 0x4 End of Track are "No" — and requires
529    /// every future registration to fill it in too.
530    ///
531    /// `status` is `None` for an object whose payload length is non-zero,
532    /// because the encoding puts a status field only where the payload does
533    /// not go. Such an object's status is Normal, the one row permitting the
534    /// payload it is carrying, so this answers
535    /// [`PayloadPermission::Permitted`] rather than `None`.
536    ///
537    /// The `None` this does return means something else entirely: a status
538    /// code with no row in the registry, for which the draft supplies no
539    /// answer and this must not invent one. [`SubgroupObjectReader`] never
540    /// produces such a meta — it refuses an unassigned code while decoding —
541    /// but every field here is public, so a caller that assembled a meta by
542    /// hand, or carried a status across from a draft numbering the set
543    /// differently, can hold one.
544    ///
545    /// Answered from the status alone. `payload_length` is not consulted: on a
546    /// status that permits a payload it says only whether this particular
547    /// object took the offer, and on one that forbids a payload a non-zero
548    /// length is the malformation a caller uses this to detect, not evidence
549    /// about the rule.
550    pub fn payload_permission(&self) -> Option<PayloadPermission> {
551        match self.status {
552            None => Some(ObjectStatus::Normal.payload_permission()),
553            Some(code) => ObjectStatus::from_u64(code).map(ObjectStatus::payload_permission),
554        }
555    }
556}
557
558/// Prior Group ID Gap, Object Property Type 0x3C (Section 10.8).
559pub const PRIOR_GROUP_ID_GAP: u64 = 0x3C;
560/// Prior Object ID Gap, Object Property Type 0x3E (Section 10.9).
561pub const PRIOR_OBJECT_ID_GAP: u64 = 0x3E;
562
563/// The Prior Group ID Gap and Prior Object ID Gap one Object carries.
564#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
565pub struct PriorGaps {
566    /// Prior Group ID Gap, if present.
567    pub group: Option<u64>,
568    /// Prior Object ID Gap, if present.
569    pub object: Option<u64>,
570}
571
572impl PriorGaps {
573    /// Read the two gaps from an Object's parsed properties, Immutable
574    /// Properties included, refusing the conditions one Object shows on its
575    /// own.
576    ///
577    /// Section 10.8 makes the Track malformed when "An Object contains more
578    /// than one instance of Prior Group ID Gap" and when "An Object has a Prior
579    /// Group ID Gap larger than the Group ID". Section 10.9 says the same two
580    /// for Prior Object ID Gap against the Object ID. Each is
581    /// [`CodecError::MalformedTrack`].
582    pub fn from_properties(
583        properties: &[ObjectProperty<'_>],
584        group_id: u64,
585        object_id: u64,
586    ) -> Result<Self, CodecError> {
587        let mut gaps = Self::default();
588        collect_gaps(properties, &mut gaps)?;
589        if let Some(gap) = gaps.group.filter(|&gap| gap > group_id) {
590            return Err(CodecError::MalformedTrack {
591                detail: format!("Prior Group ID Gap {gap} is larger than Group ID {group_id}"),
592            });
593        }
594        if let Some(gap) = gaps.object.filter(|&gap| gap > object_id) {
595            return Err(CodecError::MalformedTrack {
596                detail: format!("Prior Object ID Gap {gap} is larger than Object ID {object_id}"),
597            });
598        }
599        Ok(gaps)
600    }
601}
602
603fn collect_gaps(properties: &[ObjectProperty<'_>], gaps: &mut PriorGaps) -> Result<(), CodecError> {
604    for property in properties {
605        let slot = match (&property.value, property.property_type) {
606            (PropertyValue::Immutable { properties, .. }, _) => {
607                collect_gaps(properties, gaps)?;
608                continue;
609            }
610            (PropertyValue::Varint(value), PRIOR_GROUP_ID_GAP) => {
611                (&mut gaps.group, *value, "Group")
612            }
613            (PropertyValue::Varint(value), PRIOR_OBJECT_ID_GAP) => {
614                (&mut gaps.object, *value, "Object")
615            }
616            _ => continue,
617        };
618        let (field, value, which) = slot;
619        if field.replace(value).is_some() {
620            return Err(CodecError::MalformedTrack {
621                detail: format!("an Object contains more than one Prior {which} ID Gap"),
622            });
623        }
624    }
625    Ok(())
626}
627
628/// The Prior Group ID Gap and Prior Object ID Gap conditions of
629/// draft-21 Section 10.8 and draft-21 Section 10.9 that one fetch stream
630/// shows.
631///
632/// [`PriorGaps::from_properties`] refuses what one Object shows. Across the
633/// stream this refuses "A Group contains more than one Object with different
634/// values for Prior Group ID Gap" for the Objects of one Group. It does not
635/// refuse a gap covering an Object received earlier, or an Object within a gap
636/// communicated earlier, though this draft lists both: draft-21 Section 2.1.1
637/// says of an Object known to exist, "From this state, it can transition to not
638/// existing, but not vice versa", and of an Object received after it was
639/// recorded as not existing, "This is not a protocol error and the Track is not
640/// malformed." So whether either makes the Track malformed is ambiguous.
641/// draft-21 Section 9.11: "Within each group, objects are sent in Object ID
642/// order; subgroup ID is not used for ordering."
643///
644/// [`FetchObjectReader::read_object_header`] applies it to each Object it reads.
645///
646/// End of Range indicators are not Objects; the caller passes Objects only.
647#[derive(Debug, Clone, PartialEq, Eq)]
648// Boxed so that the fetch readers holding one stay small.
649pub struct FetchGaps(Box<crate::prior_gaps::FetchGapTracker>);
650
651impl Default for FetchGaps {
652    fn default() -> Self {
653        Self::new()
654    }
655}
656
657impl FetchGaps {
658    /// Track the Objects of one fetch stream.
659    pub fn new() -> Self {
660        Self(Box::new(crate::prior_gaps::FetchGapTracker::new(
661            crate::prior_gaps::GapRules::Draft18To22,
662        )))
663    }
664
665    /// Check the next Object on the stream, from its resolved Group ID and
666    /// Object ID and its property block, refusing a gap condition with
667    /// [`CodecError::MalformedTrack`] and a block that does not parse as
668    /// [`crate::object_properties::parse_object_properties`] does. Records the
669    /// Object only when it passes.
670    pub fn check(
671        &mut self,
672        group_id: u64,
673        object_id: u64,
674        property_block: &[u8],
675    ) -> Result<PriorGaps, CodecError> {
676        let properties = crate::object_properties::parse_object_properties(
677            crate::version::DraftVersion::Draft21,
678            property_block,
679        )?;
680        let gaps = PriorGaps::from_properties(&properties, group_id, object_id)?;
681        self.0.check(group_id, object_id, gaps.group.as_slice(), gaps.object)?;
682        Ok(gaps)
683    }
684}
685
686#[derive(Debug, Clone)]
687pub struct SubgroupObjectReader {
688    extensions_present: bool,
689    prev_object_id: Option<u64>,
690    group_id: u64,
691    group_gap: Option<u64>,
692}
693
694impl SubgroupObjectReader {
695    pub fn new(header: &SubgroupHeader) -> Self {
696        Self {
697            extensions_present: header.has_properties(),
698            prev_object_id: None,
699            group_id: header.group_id.into_inner(),
700            group_gap: None,
701        }
702    }
703
704    /// Decode the next object whole, payload included.
705    ///
706    /// Refuses, with [`CodecError::InvalidField`], properties on an object
707    /// whose status is not Normal: Section 11.1.3, "If an endpoint receives
708    /// properties on an Object with status that is not Normal, it MUST close
709    /// the session with a PROTOCOL_VIOLATION." [`Self::read_object_meta`]
710    /// frames such an object without judging it, as
711    /// [`DatagramHeader::decode`] does a datagram.
712    ///
713    /// Refuses, with [`CodecError::MalformedTrack`], the Prior Group ID Gap and
714    /// Prior Object ID Gap conditions of Section 10.8 and Section 10.9 this stream
715    /// shows: those [`PriorGaps::from_properties`] refuses, and "A Group contains
716    /// more than one Object with different values for Prior Group ID Gap". It does
717    /// not refuse "An endpoint receives an Object with a Prior Object ID Gap
718    /// covering an Object it previously received": draft-21 Section 2.1.1 says of
719    /// an Object known to exist, "From this state, it can transition to not
720    /// existing, but not vice versa", so whether such a gap makes the Track
721    /// malformed is ambiguous. [`Self::read_object_meta`] does not read properties
722    /// and applies none of them.
723    pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<SubgroupObject, CodecError> {
724        let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
725        let object_id_val = match self.prev_object_id {
726            None => delta,
727            Some(prev) => prev
728                .checked_add(1)
729                .and_then(|v| v.checked_add(delta))
730                .ok_or(CodecError::ObjectIdOverflow(prev, delta))?,
731        };
732        self.prev_object_id = Some(object_id_val);
733        let object_id = VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?;
734
735        // The properties block is a byte-length-prefixed opaque blob. We
736        // copy the blob verbatim; callers that want structured properties
737        // can parse the returned bytes.
738        let extension_headers = if self.extensions_present {
739            let ext_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
740            crate::types::read_bytes(buf, ext_len)?
741        } else {
742            Vec::new()
743        };
744
745        let payload_length_vi = VarInt::decode_moqt::<Wire>(buf)?;
746        let payload_length_val = payload_length_vi.into_inner() as usize;
747        let (object_status, payload) = if payload_length_val == 0 {
748            let status = VarInt::decode_moqt::<Wire>(buf)?;
749            (Some(decoded_status(status.into_inner())?), Vec::new())
750        } else {
751            let payload = crate::types::read_bytes(buf, payload_length_val)?;
752            (None, payload)
753        };
754
755        let properties = crate::object_properties::parse_object_properties(
756            crate::version::DraftVersion::Draft21,
757            &extension_headers,
758        )?;
759        let gaps = PriorGaps::from_properties(&properties, self.group_id, object_id_val)?;
760        self.check_stream_gaps(gaps)?;
761        let object = SubgroupObject {
762            object_id,
763            extension_headers,
764            payload_length: payload_length_vi,
765            object_status,
766            payload,
767        };
768        if !object.properties_permitted() {
769            return Err(CodecError::InvalidField);
770        }
771        Ok(object)
772    }
773
774    fn check_stream_gaps(&mut self, gaps: PriorGaps) -> Result<(), CodecError> {
775        if let Some(gap) = gaps.group {
776            match self.group_gap {
777                Some(seen) if seen != gap => {
778                    return Err(CodecError::MalformedTrack {
779                        detail: format!(
780                            "two Objects in Group {} carry different Prior Group ID Gap values",
781                            self.group_id
782                        ),
783                    });
784                }
785                _ => self.group_gap = Some(gap),
786            }
787        }
788        Ok(())
789    }
790
791    /// Decode the next object's framing without copying its payload.
792    ///
793    /// Consumes exactly the bytes [`Self::read_object`] consumes and leaves
794    /// the same delta state behind, so the two are interchangeable on a
795    /// given stream.
796    pub fn read_object_meta(
797        &mut self,
798        buf: &mut impl Buf,
799    ) -> Result<SubgroupObjectMeta, CodecError> {
800        let start = buf.remaining();
801        let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
802        let object_id_val = match self.prev_object_id {
803            None => delta,
804            Some(prev) => prev
805                .checked_add(1)
806                .and_then(|v| v.checked_add(delta))
807                .ok_or(CodecError::ObjectIdOverflow(prev, delta))?,
808        };
809        self.prev_object_id = Some(object_id_val);
810        let object_id =
811            VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?.into_inner();
812
813        let extension_headers_len = if self.extensions_present {
814            let ext_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
815            skip(buf, ext_len)?;
816            ext_len
817        } else {
818            0
819        };
820
821        let payload_length = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
822        let status = if payload_length == 0 {
823            let code = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
824            Some(decoded_status(code)?.as_u64())
825        } else {
826            skip(buf, payload_length)?;
827            None
828        };
829
830        Ok(SubgroupObjectMeta {
831            object_id,
832            extension_headers_len,
833            payload_length,
834            status,
835            wire_len: (start - buf.remaining()) as u64,
836        })
837    }
838
839    /// Serialize an object, producing the correct delta encoding.
840    ///
841    /// A zero `payload_length` writes the object's status, with
842    /// [`SubgroupObject::status`] filling in [`ObjectStatus::Normal`] when the
843    /// `object_status` field is `None`. The status is typed, so every value
844    /// that can reach this method is one draft-21 assigns and one
845    /// [`Self::read_object`] accepts; no unassigned code can be written.
846    ///
847    /// Errors with [`CodecError::InvalidField`] when the Object Status registry
848    /// (draft-21 Section 16.9) does not permit the object's status to carry a
849    /// payload and a payload was handed over anyway. Draft-21 Section 11.1.2
850    /// makes that object malformed, and the encoding has no way to state it:
851    /// the status field appears only where the payload does not. Writing it
852    /// would mean silently discarding one of the two — emitting the bytes as a
853    /// Normal object and losing an End of Group marker, say — so it is refused
854    /// instead. A status the registry does permit a payload is written as an
855    /// ordinary payload object, its status implicit, since that is how the
856    /// encoding spells it.
857    ///
858    /// Errors with [`CodecError::InvalidField`] when `object.object_id` is
859    /// not strictly greater than the previously written object's ID, since
860    /// no valid delta exists for that case.
861    ///
862    /// Errors with [`CodecError::InvalidField`] when `payload_length` is not
863    /// exactly `payload.len()`. The declared length is written ahead of the
864    /// payload, so a mismatch is a frame [`Self::read_object`] cannot parse
865    /// and one no caller could fix by appending bytes.
866    pub fn write_object(
867        &mut self,
868        object: &SubgroupObject,
869        buf: &mut impl BufMut,
870    ) -> Result<(), CodecError> {
871        // A declared length that disagrees with the payload framed under it
872        // produces bytes no reader can parse and no caller can repair: the
873        // length is already on the wire ahead of the payload. Checked before
874        // anything is written, so a refused object leaves `buf` untouched
875        // rather than half an object the next read would run into.
876        //
877        // A zero declared length is also what puts a status code where the
878        // payload would go, so an object carrying bytes under it is asking for
879        // two framings at once.
880        let declared = object.payload_length.into_inner();
881        if declared != object.payload.len() as u64 {
882            return Err(CodecError::InvalidField);
883        }
884
885        // The registry decides whether these two fields may be filled in at
886        // once, rather than the length deciding on its own which of them gets
887        // written. A status marked "Payload: No" is refused a payload; a status
888        // marked "Yes" keeps it, and gets no status field on the wire because
889        // the encoding elides the status of any object that carries bytes.
890        // Checked alongside the length above, before any byte is emitted.
891        if declared != 0 && !object.permits_payload() {
892            return Err(CodecError::PayloadNotPermitted {
893                status: object.status().as_u64(),
894                len: object.payload.len(),
895                detail: "its status is registered as forbidding one",
896            });
897        }
898
899        // Properties on a non-Normal status are a frame `read_object` refuses
900        // (Section 11.1.3), so they are not written either.
901        if !object.properties_permitted() {
902            return Err(CodecError::InvalidField);
903        }
904
905        let oid = object.object_id.into_inner();
906        let delta = match self.prev_object_id {
907            None => oid,
908            Some(prev) => oid
909                .checked_sub(prev)
910                .and_then(|v| v.checked_sub(1))
911                .ok_or(CodecError::InvalidField)?,
912        };
913        VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?.encode_moqt::<Wire>(buf);
914        if self.extensions_present {
915            VarInt::from_u64(object.extension_headers.len() as u64)
916                .map_err(|_| CodecError::InvalidField)?
917                .encode_moqt::<Wire>(buf);
918            buf.put_slice(&object.extension_headers);
919        }
920        object.payload_length.encode_moqt::<Wire>(buf);
921        if declared == 0 {
922            VarInt::from_u64_moqt(object.status().as_u64()).encode_moqt::<Wire>(buf);
923        } else {
924            buf.put_slice(&object.payload);
925        }
926        self.prev_object_id = Some(oid);
927        Ok(())
928    }
929}
930
931// ── Datagram ──────────────────────────────────────────────────
932
933const DATAGRAM_PROPERTIES_BIT: u8 = 0x01;
934const DATAGRAM_END_OF_GROUP_BIT: u8 = 0x02;
935const DATAGRAM_ZERO_OBJECT_ID_BIT: u8 = 0x04;
936const DATAGRAM_DEFAULT_PRIORITY_BIT: u8 = 0x08;
937const DATAGRAM_STATUS_BIT: u8 = 0x20;
938/// Bit 4. Draft-21 Section 11.2.1: "This bit is reserved and MUST be zero."
939/// The opposite of Section 11.3.1, where the same bit MUST be 1.
940const DATAGRAM_RESERVED_BIT: u8 = 0x10;
941
942/// Refuse a datagram `Type Flags` value draft-21 Section 11.2.1 lists as
943/// invalid.
944///
945/// The section states the rule twice, once in the paragraph introducing the
946/// field — "If a received value has bit 4 set, or has a bit set whose meaning
947/// is not specified, the endpoint MUST close the session with a
948/// PROTOCOL_VIOLATION" — and once as a list of three conditions, each of which
949/// MUST close the session with a PROTOCOL_VIOLATION:
950///
951///   1. "Values with both the STATUS bit (0x20) and END_OF_GROUP bit (0x02)
952///      set": 0x22, 0x23, 0x26, 0x27, 0x2A, 0x2B, 0x2E and 0x2F. The reason is
953///      that an object status message cannot signal end of group — the two bits
954///      ask for one datagram to be both a status and an end-of-group marker for
955///      an object it does not carry.
956///   2. "Values with bit 4 (0x10) set. This bit is reserved and MUST be zero."
957///   3. "Values with a bit set whose meaning is not specified." Bits 0, 1, 2, 3
958///      and 5 are specified; bit 6 and everything above it are not, so this
959///      catches 0x40 and, with it, every value of 128 or greater.
960///
961/// The valid set is 0x00..0x0F and 0x20..0x2F, which is draft-19's enumeration
962/// unchanged.
963///
964/// **These are not the subgroup header's rules.** Section 11.3.1 requires bit 4
965/// to be *set*, has no unspecified-bit condition, and states an explicit
966/// "values of 128 or greater" condition that this list does not need. The two
967/// sets are read from their own sections and neither is derived from the other.
968fn validate_datagram_type(raw: u64) -> Result<(), CodecError> {
969    if datagram_type_is_valid(raw) {
970        Ok(())
971    } else {
972        Err(datagram_type_error(raw))
973    }
974}
975
976/// Every bit draft-21 Section 11.2.1 gives a datagram `Type Flags` value a
977/// meaning for: PROPERTIES, END_OF_GROUP, ZERO_OBJECT_ID, DEFAULT_PRIORITY and
978/// STATUS. Bit 4 is reserved and bits 6 and up are unspecified, so both fail
979/// the mask.
980const DATAGRAM_SPECIFIED_BITS: u64 = (DATAGRAM_PROPERTIES_BIT
981    | DATAGRAM_END_OF_GROUP_BIT
982    | DATAGRAM_ZERO_OBJECT_ID_BIT
983    | DATAGRAM_DEFAULT_PRIORITY_BIT
984    | DATAGRAM_STATUS_BIT) as u64;
985
986/// Whether `raw` is a datagram `Type Flags` value draft-21 admits.
987fn datagram_type_is_valid(raw: u64) -> bool {
988    // Every bit outside the specified set fails, which covers bit 4 (reserved),
989    // bit 6 (unspecified) and everything at 128 or above in one test.
990    raw & !DATAGRAM_SPECIFIED_BITS == 0
991        && !(raw & DATAGRAM_STATUS_BIT as u64 != 0 && raw & DATAGRAM_END_OF_GROUP_BIT as u64 != 0)
992}
993
994/// Which failure a leading datagram `Type Flags` value that is not one a reader
995/// wants is.
996///
997/// The same split as `stream_type_error`, read against the datagram table.
998/// Section 11.2.1's three conditions each get an arm, so a log names which one
999/// a value fell into rather than reporting all three as one complaint; a value
1000/// too wide to be a one-byte flags field at all is Section 6.4.1's
1001/// [`CodecError::UnknownDatagramType`] instead.
1002///
1003/// The padding datagram is why the [`CodecError::InvalidField`] arm exists.
1004/// [`PADDING_DATAGRAM_TYPE`] is assigned, so a datagram carrying it is not
1005/// unknown; it simply carries no Object, and refusing it must not end the
1006/// session.
1007fn datagram_type_error(raw: u64) -> CodecError {
1008    if raw == PADDING_DATAGRAM_TYPE || datagram_type_is_valid(raw) {
1009        return CodecError::InvalidField;
1010    }
1011    if raw > 0xFF {
1012        return CodecError::UnknownDatagramType(raw);
1013    }
1014    let t = raw as u8;
1015    if t & DATAGRAM_RESERVED_BIT != 0 {
1016        CodecError::InvalidDatagramTypeValue {
1017            raw,
1018            detail: "bit 4 (0x10) is reserved for a datagram and must be zero",
1019        }
1020    } else if t & DATAGRAM_STATUS_BIT != 0 && t & DATAGRAM_END_OF_GROUP_BIT != 0 {
1021        CodecError::InvalidDatagramTypeValue {
1022            raw,
1023            detail: "it sets both the STATUS bit and the END_OF_GROUP bit",
1024        }
1025    } else {
1026        CodecError::InvalidDatagramTypeValue {
1027            raw,
1028            detail: "it sets a bit whose meaning is not specified for a datagram",
1029        }
1030    }
1031}
1032
1033#[derive(Debug, Clone)]
1034pub struct DatagramHeader {
1035    pub datagram_type: u8,
1036    pub track_alias: VarInt,
1037    pub group_id: VarInt,
1038    pub object_id: VarInt,
1039    pub publisher_priority: Option<u8>,
1040    /// Raw properties bytes, excluding the byte-length prefix that precedes
1041    /// them on the wire. Present only when `datagram_type` sets the PROPERTIES
1042    /// bit (0x01), and empty otherwise — the bit is what puts the block on the
1043    /// wire, so contents held here with the bit clear are not written.
1044    ///
1045    /// Opaque: [`Self::encode`] re-emits the prefix and these bytes verbatim,
1046    /// and [`Self::decode`] copies them out the same way, so a datagram can be
1047    /// decoded and re-encoded without understanding what its properties mean.
1048    /// The block sits between the publisher priority and the status field, so
1049    /// leaving it out of the struct would put the status where the decoder
1050    /// looks for the properties length.
1051    pub properties: Vec<u8>,
1052    /// The object's status, carried on the wire only when `datagram_type` sets
1053    /// the STATUS bit (0x20): such a datagram holds a one-byte status code in
1054    /// place of a payload. `None` with the bit set is written as
1055    /// [`ObjectStatus::Normal`]; a status with the bit clear is not written at
1056    /// all, because the bit is what puts the field on the wire.
1057    ///
1058    /// `None` is not "no status": a datagram whose type leaves the STATUS bit
1059    /// clear carries a payload, and the status of an Object that carries a
1060    /// payload is [`ObjectStatus::Normal`], the only row of the Object Status
1061    /// registry (draft-21 Section 16.9) permitting one. [`Self::status`]
1062    /// resolves the field either way.
1063    ///
1064    /// Typed rather than a bare byte. The wire field is one octet with 256
1065    /// values, and draft-21 Section 11.1.2 assigns three of them; the
1066    /// decoder refuses the other 253, and this type is that same refusal on
1067    /// the encode side — [`Self::encode`] is infallible precisely because a
1068    /// status it could not legally write cannot be built.
1069    pub object_status: Option<ObjectStatus>,
1070}
1071
1072impl DatagramHeader {
1073    /// Decode a datagram header, `Type Flags` field included.
1074    ///
1075    /// The field is read as a whole varint of whatever width the sender used,
1076    /// and a non-minimal spelling of a valid value is accepted, for the reason
1077    /// given on [`SubgroupHeader::decode`] and in the module documentation.
1078    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1079        let raw = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1080        validate_datagram_type(raw)?;
1081        // Narrowing is safe: `validate_datagram_type` has already refused every
1082        // value with a bit set above the specified five.
1083        let datagram_type = raw as u8;
1084
1085        let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
1086        let group_id = VarInt::decode_moqt::<Wire>(buf)?;
1087
1088        let object_id = if datagram_type & DATAGRAM_ZERO_OBJECT_ID_BIT != 0 {
1089            VarInt::from_usize(0)
1090        } else {
1091            VarInt::decode_moqt::<Wire>(buf)?
1092        };
1093
1094        let publisher_priority = if datagram_type & DATAGRAM_DEFAULT_PRIORITY_BIT == 0 {
1095            if buf.remaining() < 1 {
1096                return Err(CodecError::UnexpectedEnd);
1097            }
1098            Some(buf.get_u8())
1099        } else {
1100            None
1101        };
1102
1103        let properties = if datagram_type & DATAGRAM_PROPERTIES_BIT != 0 {
1104            let props_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1105            crate::types::read_bytes(buf, props_len)?
1106        } else {
1107            Vec::new()
1108        };
1109
1110        let object_status = if datagram_type & DATAGRAM_STATUS_BIT != 0 {
1111            if buf.remaining() < 1 {
1112                return Err(CodecError::UnexpectedEnd);
1113            }
1114            let status = buf.get_u8();
1115            Some(decoded_status(status as u64)?)
1116        } else {
1117            None
1118        };
1119
1120        // Two rules of draft-21 Section 11.2.1 reach the properties block just
1121        // read, and neither is applied here — [`Self::properties_permitted`]
1122        // and [`Self::properties_block_well_formed`] report them instead, and
1123        // [`Self::encode_checked`] refuses to write either shape:
1124        //
1125        //   - "If an endpoint receives a datagram with the PROPERTIES bit set
1126        //     and an Properties Length of 0, it MUST close the session with a
1127        //     PROTOCOL_VIOLATION."
1128        //   - "If an Object Datagram includes both the STATUS bit and
1129        //     PROPERTIES bit, and the Object Status is not Normal (0x0), the
1130        //     endpoint MUST close the session with a PROTOCOL_VIOLATION,
1131        //     because only Normal Objects can have Properties."
1132        //
1133        // Both describe a datagram that is well framed and non-conforming: the
1134        // fields are all where the layout puts them and every one of them
1135        // parses, so a decoder can read the datagram back exactly as it
1136        // arrived. Refusing here would leave this module unable to reproduce a
1137        // capture containing one, and both rules address an endpoint receiving
1138        // such a datagram, so the endpoint is where they are enforced — the
1139        // same division [`SubgroupObject::properties_permitted`] explains.
1140        //
1141        // The Type rules above are the contrast, and the contrast is what
1142        // decides it: an invalid Type names no layout at all, so reading on
1143        // invents the fields behind it rather than reporting them.
1144
1145        Ok(DatagramHeader {
1146            datagram_type,
1147            track_alias,
1148            group_id,
1149            object_id,
1150            publisher_priority,
1151            properties,
1152            object_status,
1153        })
1154    }
1155
1156    /// Decode one whole datagram: the header, then the payload that runs to the
1157    /// end of `buf`.
1158    ///
1159    /// `buf` must hold exactly one transport datagram and nothing else, since
1160    /// that boundary is the only thing that delimits the payload — draft-21
1161    /// Section 11.2.1: "There is no explicit length field for the Object
1162    /// Payload; the entirety of the transport datagram following the Object
1163    /// header contains the payload."
1164    ///
1165    /// Which is why the refusal lives here and not in [`Self::decode`]. A
1166    /// datagram whose type sets the STATUS bit has no payload at all — the same
1167    /// section: "When set to 1, the Object Status field is present and there is
1168    /// no Object Payload" — so trailing bytes after its status are not a short
1169    /// payload or an odd one, they are bytes the frame does not define. A
1170    /// decoder that stops at the header cannot see them, and a caller that
1171    /// treats whatever is left as the payload hands the application content the
1172    /// publisher never framed as content. That is the case this refuses, and it
1173    /// bites hardest on a status datagram carrying the Normal code 0x0, whose
1174    /// status alone would report a payload as permitted.
1175    ///
1176    /// The same refusal covers a status the registry forbids a payload to, per
1177    /// Section 11.1.2 and the "Payload" column of Section 16.9.
1178    ///
1179    /// Errors with [`CodecError::PayloadNotPermitted`] when bytes remain and
1180    /// the header forbids them, naming which of the two rules refused them.
1181    ///
1182    /// # The two Properties rules are enforced here too
1183    ///
1184    /// Section 11.2.1 states them of a receiving endpoint, and this is the
1185    /// endpoint's read:
1186    ///
1187    /// * "If an endpoint receives a datagram with the PROPERTIES bit set and an
1188    ///   Properties Length of 0, it MUST close the session with a
1189    ///   PROTOCOL_VIOLATION." The bit and a zero length are two ways to spell
1190    ///   *no properties* and a datagram may use only the first, because a
1191    ///   datagram with none has a Type Flags value that says so and the block
1192    ///   costs bytes the flags already saved. A subgroup stream says the
1193    ///   opposite in Section 11.3.1 — there the PROPERTIES bit is fixed for the
1194    ///   whole stream, so an object with no properties has nowhere else to say
1195    ///   so and a zero-length block is the required spelling.
1196    /// * "If an Object Datagram includes both the STATUS bit and PROPERTIES
1197    ///   bit, and the Object Status is not Normal (0x0), the endpoint MUST close
1198    ///   the session with a PROTOCOL_VIOLATION, because only Normal Objects can
1199    ///   have Properties."
1200    ///
1201    /// Both errors are [`CodecError::InvalidField`].
1202    ///
1203    /// [`Self::decode`] does **not** apply them, and the split is deliberate.
1204    /// Both describe a datagram that is well framed and non-conforming: every
1205    /// field is where the layout puts it and every one of them parses, so the
1206    /// header reads back exactly as it arrived and a tool reproducing a capture
1207    /// can re-emit it. What it may not do is hand such a datagram to an
1208    /// application as an ordinary Object, which is what this entry point would
1209    /// be doing. [`Self::properties_block_well_formed`] and
1210    /// [`Self::properties_permitted`] report the two for a caller that wants
1211    /// the header without the judgement, and [`Self::encode_checked`] refuses
1212    /// to write either shape.
1213    ///
1214    /// A property block that does not parse is refused by
1215    /// [`crate::object_properties::parse_object_properties`], and the Prior
1216    /// Group ID Gap and Prior Object ID Gap conditions one Object shows by
1217    /// [`PriorGaps::from_properties`].
1218    pub fn decode_object(buf: &mut impl Buf) -> Result<(Self, Vec<u8>), CodecError> {
1219        let header = Self::decode(buf)?;
1220        if !header.properties_block_well_formed() || !header.properties_permitted() {
1221            return Err(CodecError::InvalidField);
1222        }
1223        let properties = crate::object_properties::parse_object_properties(
1224            crate::version::DraftVersion::Draft21,
1225            &header.properties,
1226        )?;
1227        PriorGaps::from_properties(
1228            &properties,
1229            header.group_id.into_inner(),
1230            header.object_id.into_inner(),
1231        )?;
1232        let payload = crate::types::read_bytes(buf, buf.remaining())?;
1233        if !payload.is_empty() && !header.permits_payload() {
1234            return Err(CodecError::PayloadNotPermitted {
1235                status: header.status().as_u64(),
1236                len: payload.len(),
1237                detail: if header.has_status() {
1238                    "its type states a status in place of a payload"
1239                } else {
1240                    "its status is registered as forbidding one"
1241                },
1242            });
1243        }
1244        Ok((header, payload))
1245    }
1246
1247    /// Serialize the header, refusing a status the framing cannot carry.
1248    ///
1249    /// A datagram states a status only when its type byte sets the STATUS bit
1250    /// (0x20). With the bit clear there is no status field on the wire, so an
1251    /// `object_status` of anything but [`ObjectStatus::Normal`] has nowhere to
1252    /// go: [`Self::encode`] drops it, and the datagram parses back as an
1253    /// ordinary payload object. An End of Group marker written that way does
1254    /// not arrive late or malformed — it does not arrive at all, and the
1255    /// receiver sees a normal object in its place.
1256    ///
1257    /// [`ObjectStatus::Normal`] with the bit clear is not that case and is
1258    /// accepted. It is the status the encoding elides for every object that
1259    /// carries a payload, so stating it asks for exactly the bytes leaving it
1260    /// out asks for, and nothing is lost.
1261    ///
1262    /// Errors with [`CodecError::InvalidField`] on the lossy combination,
1263    /// before any byte is written, so a refused header leaves `buf` untouched.
1264    /// This is the datagram half of the rule
1265    /// [`SubgroupObjectReader::write_object`] applies on a subgroup stream.
1266    ///
1267    /// Also errors with [`CodecError::InvalidField`] for a Type value draft-21
1268    /// Section 11.2.1 lists as invalid, and for the same reason: a datagram
1269    /// written with one could not be read back by [`Self::decode`], and a
1270    /// codec whose two halves disagree about which datagrams exist cannot be
1271    /// used to rewrite captured traffic.
1272    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1273        validate_datagram_type(self.datagram_type as u64)?;
1274        if !self.has_status() && matches!(self.object_status, Some(s) if s != ObjectStatus::Normal)
1275        {
1276            return Err(CodecError::InvalidField);
1277        }
1278        // The two properties rules of Section 11.2.1. [`Self::decode`] reports
1279        // both rather than refusing them, because the datagrams they describe
1280        // are well framed and a codec that could not read one could not
1281        // reproduce a capture containing it. Writing one is the other
1282        // direction and has no such excuse: a conforming peer answers either
1283        // with a PROTOCOL_VIOLATION, so emitting one costs the session and not
1284        // merely the datagram.
1285        if !self.properties_block_well_formed() || !self.properties_permitted() {
1286            return Err(CodecError::InvalidField);
1287        }
1288        self.encode(buf);
1289        Ok(())
1290    }
1291
1292    /// Serialize the header exactly as its type byte describes it.
1293    ///
1294    /// Every field the type byte announces is written, in the order
1295    /// [`Self::decode`] reads them, so the bytes this produces always parse
1296    /// back. The properties block in particular has to be written here: it
1297    /// sits ahead of the status field, and a datagram that skipped it would
1298    /// offer the status byte where the decoder reads the block's length.
1299    ///
1300    /// The type byte is taken as the authority on framing, which is what makes
1301    /// this infallible — and what makes it lossy when the struct disagrees with
1302    /// itself. An `object_status` set while the type byte leaves the STATUS bit
1303    /// clear is discarded here without a word, and a Type value draft-21
1304    /// forbids is written out as readily as one it assigns, including Types
1305    /// this module's own [`Self::decode`] refuses. Prefer
1306    /// [`Self::encode_checked`], which refuses both instead of resolving them.
1307    pub fn encode(&self, buf: &mut impl BufMut) {
1308        buf.put_u8(self.datagram_type);
1309        self.track_alias.encode_moqt::<Wire>(buf);
1310        self.group_id.encode_moqt::<Wire>(buf);
1311
1312        if self.datagram_type & DATAGRAM_ZERO_OBJECT_ID_BIT == 0 {
1313            self.object_id.encode_moqt::<Wire>(buf);
1314        }
1315
1316        if self.datagram_type & DATAGRAM_DEFAULT_PRIORITY_BIT == 0 {
1317            buf.put_u8(self.publisher_priority.unwrap_or(128));
1318        }
1319
1320        if self.datagram_type & DATAGRAM_PROPERTIES_BIT != 0 {
1321            VarInt::from_usize(self.properties.len()).encode_moqt::<Wire>(buf);
1322            buf.put_slice(&self.properties);
1323        }
1324
1325        if self.datagram_type & DATAGRAM_STATUS_BIT != 0 {
1326            buf.put_u8(self.object_status.unwrap_or(ObjectStatus::Normal).as_u8());
1327        }
1328    }
1329
1330    pub fn is_end_of_group(&self) -> bool {
1331        self.datagram_type & DATAGRAM_END_OF_GROUP_BIT != 0
1332    }
1333
1334    pub fn has_status(&self) -> bool {
1335        self.datagram_type & DATAGRAM_STATUS_BIT != 0
1336    }
1337
1338    /// `true` when the type byte sets the PROPERTIES bit (0x01), which is what
1339    /// puts the properties block on the wire.
1340    ///
1341    /// Reports the framing, not the contents. A decoded datagram with this set
1342    /// always has a non-empty [`Self::properties`], because [`Self::decode`]
1343    /// refuses a zero-length block; a header built by hand can hold the two
1344    /// apart, and [`Self::encode_checked`] is what refuses that.
1345    pub fn has_properties(&self) -> bool {
1346        self.datagram_type & DATAGRAM_PROPERTIES_BIT != 0
1347    }
1348
1349    /// The datagram's object status, with the one the encoding elides filled
1350    /// in.
1351    ///
1352    /// A datagram states a status only when its type sets the STATUS bit, and
1353    /// such a datagram has no payload. One without the bit is all payload, and
1354    /// its status is [`ObjectStatus::Normal`] — the sole row of the Object
1355    /// Status registry (draft-21 Section 16.9) permitting a payload, so the
1356    /// only status it could have had.
1357    pub fn status(&self) -> ObjectStatus {
1358        self.object_status.unwrap_or(ObjectStatus::Normal)
1359    }
1360
1361    /// Whether the bytes after this datagram's header are allowed to exist.
1362    ///
1363    /// Two independent rules forbid them, and this reports both:
1364    ///
1365    /// - The framing. Draft-21 Section 11.2.1: "The STATUS bit (0x20)
1366    ///   indicates whether the datagram contains an Object Status or Object
1367    ///   Payload. When set to 1, the Object Status field is present and there
1368    ///   is no Object Payload." A datagram that states a status has no payload
1369    ///   field at all, whichever status it states — so a STATUS datagram
1370    ///   carrying the Normal code 0x0 has no more room for bytes than one
1371    ///   carrying End of Group.
1372    /// - The status. Section 11.1.2 and the Object Status registry's
1373    ///   "Payload" column, Section 16.9: an Object has an empty payload unless
1374    ///   its status is registered as permitting one. This half reaches a
1375    ///   datagram whose type byte leaves the STATUS bit clear while the value
1376    ///   claims a status that forbids a payload — a disagreement
1377    ///   [`Self::encode_checked`] refuses to write, and one a decoded header
1378    ///   never shows.
1379    ///
1380    /// The first is the rule a decoded datagram can actually trip, and reading
1381    /// the registry alone misses it: `Some(ObjectStatus::Normal)` under a type
1382    /// byte with the STATUS bit set is exactly the case where the payload the
1383    /// draft says does not exist would otherwise be handed to the application
1384    /// as the object's content, because Normal is the one status the registry
1385    /// marks as permitting a payload.
1386    ///
1387    /// Distinct from [`Self::has_status`], which reports how the datagram is
1388    /// framed rather than whether a payload may follow. A caller holding the
1389    /// bytes after the header wants this one; [`Self::decode_object`] applies
1390    /// it for a caller who would rather the decode simply fail.
1391    pub fn permits_payload(&self) -> bool {
1392        if self.has_status() {
1393            return false;
1394        }
1395        self.status().permits_payload()
1396    }
1397
1398    /// Whether this datagram's status is allowed to carry the properties it
1399    /// has.
1400    ///
1401    /// The same rule the subgroup form obeys. Draft-21 Section 11.2.1 builds
1402    /// the datagram's Properties field out of "the Object Properties structure
1403    /// defined in Section 11.1.3", and that section is where the rule sits:
1404    /// "If an endpoint receives properties on an Object with status that is not
1405    /// Normal, it MUST close the session with a PROTOCOL_VIOLATION."
1406    ///
1407    /// [`Self::decode`] reports this and [`Self::decode_object`] refuses it, as
1408    /// the subgroup readers split it.
1409    pub fn properties_permitted(&self) -> bool {
1410        self.properties.is_empty() || self.status() == ObjectStatus::Normal
1411    }
1412
1413    /// Whether the properties block is framed the way a datagram may frame it.
1414    ///
1415    /// Draft-21 Section 11.2.1: "If an endpoint receives a datagram with the
1416    /// PROPERTIES bit set and an Properties Length of 0, it MUST close the
1417    /// session with a PROTOCOL_VIOLATION."
1418    ///
1419    /// The bit and a zero length are two ways to spell "no properties", and on
1420    /// a datagram they are not interchangeable: a datagram with none has a type
1421    /// byte that says so, and the block costs bytes the type byte already
1422    /// saved. This rule is the datagram's alone. A subgroup stream says the
1423    /// opposite in Section 11.3.1 — "Objects with no properties set Properties
1424    /// Length to 0" — because there the PROPERTIES bit is fixed for the whole
1425    /// stream, so an object with no properties has nowhere else to say so and a
1426    /// zero-length block is the required spelling rather than a violation.
1427    ///
1428    /// The mirror case is not a wire state but is a state this struct can hold:
1429    /// properties with the bit clear. [`Self::encode`] drops them without a
1430    /// word, so this reports that too, and [`Self::encode_checked`] refuses
1431    /// both.
1432    pub fn properties_block_well_formed(&self) -> bool {
1433        self.has_properties() != self.properties.is_empty()
1434    }
1435}
1436
1437// ── Fetch Header ──────────────────────────────────────────────
1438
1439const FETCH_STREAM_TYPE: u64 = 0x05;
1440
1441/// The head of a fetch stream: the stream type `0x05` and a Request ID.
1442///
1443/// Byte-identical to draft-19. What draft-20 widened is what the Request ID may
1444/// name. Section 11.4.1: "all objects on the stream belong to the track
1445/// requested in the message identified by Request ID", where draft-19 said "the
1446/// track requested in the Fetch message". The widening is deliberate, because a
1447/// fetch stream can
1448/// now be a *fill fetch stream* whose Request ID names the `SUBSCRIBE` or the
1449/// `REQUEST_UPDATE` that carried `FILL_PARAMETERS` (Section 3.4), not only a
1450/// `FETCH`. Nothing on the stream says which, so a reader that keys a fetch
1451/// stream by looking up a FETCH will fail to find one.
1452///
1453/// # Two things a fill fetch stream does not have, which the draft leaves open
1454///
1455/// * **No `FETCH_OK`, and therefore no `End Location` and no `End Of Track`.**
1456///   Section 3.4 says the fill is "delivered as a FETCH response" and
1457///   Section 3.4.1 says there is no `REQUEST_ERROR` for it, so there is no OK
1458///   either. How a subscriber learns the fill's actual end, beyond the stream
1459///   FINing, is unspecified; this codec treats the FIN as the only end signal
1460///   and does not synthesize a `FETCH_OK`. Section 9.11's rule that gaps
1461///   between the last Object and the FETCH_OK's Largest imply non-existence
1462///   cannot be applied here, because there is no FETCH_OK to read it from.
1463/// * **Failure is a stream reset, not an error message.** Section 3.4.1: if
1464///   the publisher must fail a fill it opens the stream and resets it
1465///   immediately after the `FETCH_HEADER`. Resetting or cancelling a fill fetch
1466///   stream does not affect the subscription, which keeps delivering.
1467///
1468/// Both are the caller's to act on: neither is visible in a frame.
1469#[derive(Debug, Clone)]
1470pub struct FetchHeader {
1471    pub request_id: VarInt,
1472}
1473
1474impl FetchHeader {
1475    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1476        let stream_type = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1477        if stream_type != FETCH_STREAM_TYPE {
1478            return Err(stream_type_error(stream_type));
1479        }
1480        let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1481        Ok(FetchHeader { request_id })
1482    }
1483
1484    pub fn encode(&self, buf: &mut impl BufMut) {
1485        VarInt::from_usize(FETCH_STREAM_TYPE as usize).encode_moqt::<Wire>(buf);
1486        self.request_id.encode_moqt::<Wire>(buf);
1487    }
1488}
1489
1490// ── Fetch objects ─────────────────────────────────────────────
1491
1492/// Serialization Flags bits 0-1, the Subgroup ID encoding
1493/// (draft-21 Section 11.4.1.1, Table 8).
1494const FETCH_SUBGROUP_ID_MODE_MASK: u64 = 0x03;
1495/// Subgroup ID mode 0b11: an explicit Subgroup ID field is on the wire.
1496const FETCH_SUBGROUP_ID_EXPLICIT: u64 = 0b11;
1497/// Table 9 flag: an Object ID Delta field is present.
1498const FETCH_OBJECT_ID_DELTA_BIT: u64 = 0x04;
1499/// Table 9 flag: a Group ID Delta field is present.
1500const FETCH_GROUP_ID_DELTA_BIT: u64 = 0x08;
1501/// Table 9 flag: a Publisher Priority field is present.
1502const FETCH_PRIORITY_BIT: u64 = 0x10;
1503/// Table 9 flag: a Properties field is present.
1504const FETCH_PROPERTIES_BIT: u64 = 0x20;
1505/// Table 9 flag: the Object's Forwarding Preference is Datagram, so it has no
1506/// Subgroup ID and the two low bits are to be ignored.
1507const FETCH_DATAGRAM_BIT: u64 = 0x40;
1508/// The largest Serialization Flags value whose bits are flags. Draft-21
1509/// Section 11.4.1: "When less than 128, the bits represent flags".
1510const FETCH_FLAGS_MAX: u64 = 0x7F;
1511/// Table 7: End of Non-Existent Range.
1512const FETCH_END_OF_NON_EXISTENT_RANGE: u64 = 0x8C;
1513/// Table 7: End of Unknown Range.
1514const FETCH_END_OF_UNKNOWN_RANGE: u64 = 0x10C;
1515/// Table 7: End of Timed-Out Range. New in draft-20.
1516///
1517/// The minimal encoding is the two-byte `82 0C`; it is not a single byte, and
1518/// cannot be, because Section 11.4.1 reserves values below 128 for the bit-flag
1519/// reading. Its low bits are `0x0C` — Group ID Delta present, Object ID Delta
1520/// present — exactly as the other two markers' are, so the two fields that
1521/// follow occupy the ordinary delta slots of Figure 28.
1522const FETCH_END_OF_TIMED_OUT_RANGE: u64 = 0x20C;
1523
1524/// What an End of Range indicator on a fetch stream asserts about the
1525/// Locations it covers, from draft-21 Section 11.4.1.2.
1526///
1527/// All three say that every Object with a Location between the previous
1528/// serialized Object and this one, inclusive, was not serialized. They differ
1529/// in why, and the three between them partition non-delivery: the publisher
1530/// knows the Objects are not there, it does not know either way, or it gave up
1531/// waiting. A subscriber can cache the first as a definitive gap and must not
1532/// cache the other two, so they cannot be collapsed.
1533#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1534pub enum FetchEndOfRange {
1535    /// Serialization Flags 0x8C. The Objects in the range do not exist.
1536    ///
1537    /// Section 11.4.1.2: "A publisher SHOULD NOT use End of Non-Existent Range
1538    /// in a FETCH response except to split a range of Objects that will not be
1539    /// serialized into those that are known not to exist and those with unknown
1540    /// or timed out status." Draft-19's wording of the same sentence did not
1541    /// have the "or timed out" half, because it had no marker for it.
1542    NonExistent,
1543    /// Serialization Flags 0x10C. The Objects in the range have unknown
1544    /// status.
1545    Unknown,
1546    /// Serialization Flags 0x20C. The Objects in the range timed out: the relay
1547    /// abandoned them because its `FILL_TIMEOUT` budget (Section 9.20.6) ran
1548    /// out. **New in draft-20.**
1549    ///
1550    /// This is where a fill timeout's output goes, and it is a behavioural
1551    /// change as much as a new value: draft-19 classified the same Objects as
1552    /// Unknown gaps, so a draft-19 receiver and a draft-21 receiver reading the
1553    /// same relay see different markers for the same event. `FILL_TIMEOUT = 0`
1554    /// means the subscriber wants only immediately-available Objects and the
1555    /// relay MUST report everything else this way; with the parameter absent,
1556    /// the relay waits an implementation-specific duration first.
1557    TimedOut,
1558}
1559
1560/// Which optional fields a Serialization Flags value puts on the wire.
1561///
1562/// Derived once by [`FetchObjectHeader::layout`] and then used by both
1563/// [`FetchObjectHeader::decode`] and [`FetchObjectHeader::encode`], so the two
1564/// cannot drift into disagreeing about a shape.
1565#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1566struct FetchObjectLayout {
1567    group_id_delta: bool,
1568    subgroup_id: bool,
1569    object_id_delta: bool,
1570    publisher_priority: bool,
1571    properties: bool,
1572}
1573
1574/// One Object on a draft-21 fetch stream, up to but not including its payload.
1575///
1576/// Draft-21 Section 11.4.1 rebuilt the fetch object. Earlier drafts wrote a
1577/// fixed set of fields on every Object; draft-21 writes a Serialization Flags
1578/// varint first, and the flags say which fields follow:
1579///
1580/// ```text
1581/// {
1582///   Serialization Flags (vi64),
1583///   [Group ID Delta (vi64),]
1584///   [Subgroup ID (vi64),]
1585///   [Object ID Delta (vi64),]
1586///   [Publisher Priority (8),]
1587///   [Properties (..),]
1588///   Object Payload Length (vi64),
1589///   [Object Payload (..),]
1590/// }
1591/// ```
1592///
1593/// Every field is optional except the flags and the payload length, and an
1594/// absent field means *the same as the previous Object's*, not "zero" — the
1595/// whole point of the layout is that a run of Objects in one group at one
1596/// priority costs one byte of framing each. This type therefore holds what is
1597/// on the wire and nothing more: the deltas, not the Group and Object IDs they
1598/// resolve to. Resolving them needs the previous Object on the same stream and
1599/// the FETCH's Group Order, neither of which a single Object header knows,
1600/// and Section 11.4.1 spells out the arithmetic a caller must apply:
1601///
1602///   - The first Object MUST carry both deltas, and they are the absolute
1603///     Group ID and Object ID.
1604///   - Later on, a Group ID Delta moves the group by `delta + 1` — forwards
1605///     under Ascending Group Order and backwards under Descending — and
1606///     restarts the Object ID from the Object ID Delta. With no Group ID
1607///     Delta, the group is unchanged and the Object ID Delta is added to the
1608///     previous Object's ID; with no Object ID Delta either, the Object ID is
1609///     the previous one plus one.
1610///
1611/// There is no Object Status field. Draft-21 Section 11.1.2 states that
1612/// Object Status is present only on Objects delivered via a subscription and
1613/// absent from Objects delivered via a FETCH, which is why this type has no
1614/// counterpart to [`SubgroupObject::object_status`] and why a zero
1615/// `payload_length` here is simply an Object with no payload.
1616///
1617/// Two Serialization Flags values name an End of Range indicator rather than
1618/// an Object; [`Self::end_of_range`] reports which, and Section 11.4.1.2 gives
1619/// the rules such a frame follows.
1620#[derive(Debug, Clone, PartialEq, Eq)]
1621pub struct FetchObjectHeader {
1622    /// The raw Serialization Flags value, kept whole rather than split into
1623    /// booleans because it is also the field that names an End of Range
1624    /// indicator, and because re-encoding must reproduce the value the
1625    /// publisher chose.
1626    pub serialization_flags: VarInt,
1627    /// Group ID Delta, present when the flags set 0x08. Its meaning depends on
1628    /// the Object's position in the stream and on the Group Order; see the
1629    /// type's own documentation.
1630    pub group_id_delta: Option<VarInt>,
1631    /// An explicit Subgroup ID, present only when the two low flag bits are
1632    /// 0b11 and the Datagram bit is clear. The other three modes derive the
1633    /// Subgroup ID from the previous Object and put nothing on the wire.
1634    pub subgroup_id: Option<VarInt>,
1635    /// Object ID Delta, present when the flags set 0x04. Absent means the
1636    /// previous Object's ID plus one.
1637    pub object_id_delta: Option<VarInt>,
1638    /// Publisher Priority, present when the flags set 0x10. Absent means the
1639    /// previous Object's priority.
1640    pub publisher_priority: Option<u8>,
1641    /// Raw properties bytes, excluding the byte-length prefix that precedes
1642    /// them on the wire, and `None` when the flags leave 0x20 clear.
1643    ///
1644    /// `Some(vec![])` and `None` are different frames: the first writes a zero
1645    /// length prefix, the second writes nothing at all. Opaque, like the
1646    /// property blocks on the subgroup and datagram forms — draft-21
1647    /// Section 11.4.1 defines the field as the Object Properties structure of
1648    /// Section 11.1.3, and these bytes are re-emitted verbatim.
1649    pub properties: Option<Vec<u8>>,
1650    /// Object Payload Length. Always on the wire; the payload itself follows
1651    /// this header and is not held here.
1652    pub payload_length: VarInt,
1653}
1654
1655impl FetchObjectHeader {
1656    /// The Serialization Flags as a plain integer.
1657    pub fn flags(&self) -> u64 {
1658        self.serialization_flags.into_inner()
1659    }
1660
1661    /// Which End of Range indicator this is, or `None` for an ordinary Object.
1662    ///
1663    /// Draft-21 Section 11.4.1, Table 7 gives the three indicators their own
1664    /// Serialization Flags values rather than a flag bit, so this is an
1665    /// equality test on the whole field and not a mask.
1666    ///
1667    /// An indicator uses the same two positions on the wire an ordinary Object
1668    /// uses for its deltas, so they are reached through [`Self::group_id_delta`]
1669    /// and [`Self::object_id_delta`], but they hold an absolute Group ID and
1670    /// Object ID. draft-21 Section 11.4.1.2: "the Group ID and Object ID fields are
1671    /// present". The delta rules of draft-21 Section 11.4.1.1 cover "an Object other
1672    /// than the first", and draft-21 Section 11.4.1 reads the flag bits only "When less
1673    /// than 128". draft-23 made the fields deltas ("Delta encode FETCH End of
1674    /// Range indicators").
1675    pub fn end_of_range(&self) -> Option<FetchEndOfRange> {
1676        match self.flags() {
1677            FETCH_END_OF_NON_EXISTENT_RANGE => Some(FetchEndOfRange::NonExistent),
1678            FETCH_END_OF_UNKNOWN_RANGE => Some(FetchEndOfRange::Unknown),
1679            FETCH_END_OF_TIMED_OUT_RANGE => Some(FetchEndOfRange::TimedOut),
1680            _ => None,
1681        }
1682    }
1683
1684    /// The two-bit Subgroup ID mode, `flags & 0x03`.
1685    ///
1686    /// `0b00` = the Subgroup ID is zero; `0b01` = the previous Object's
1687    /// Subgroup ID; `0b10` = the previous Object's Subgroup ID plus one;
1688    /// `0b11` = an explicit field is present. Draft-21 Section 11.4.1.1
1689    /// assigns all four, unlike the subgroup stream header's reserved
1690    /// `0b11`.
1691    ///
1692    /// Meaningless when [`Self::is_datagram`] is true: such an Object has no
1693    /// Subgroup ID and the section says the subscriber MUST ignore these bits.
1694    pub fn subgroup_id_mode(&self) -> u8 {
1695        (self.flags() & FETCH_SUBGROUP_ID_MODE_MASK) as u8
1696    }
1697
1698    /// `true` when the flags set 0x40, marking an Object whose Forwarding
1699    /// Preference is Datagram. Such an Object has no Subgroup ID at all, so
1700    /// the Subgroup ID mode bits carry no meaning and no Subgroup ID field is
1701    /// on the wire whatever they say.
1702    pub fn is_datagram(&self) -> bool {
1703        self.flags() & FETCH_DATAGRAM_BIT != 0
1704    }
1705
1706    /// Which optional fields `flags` puts on the wire, or
1707    /// [`CodecError::InvalidField`] if draft-21 does not define that
1708    /// Serialization Flags value.
1709    ///
1710    /// Section 11.4.1 defines the field in two pieces: values below 128 are a
1711    /// set of flags, and Table 7 adds exactly three values above that — 0x8C,
1712    /// 0x10C and draft-20's new 0x20C. "Any other value is a
1713    /// PROTOCOL_VIOLATION", which is what the error covers — every value at 128
1714    /// or above that is not one of the three.
1715    ///
1716    /// The three indicators get their layout from Section 11.4.1.2 rather than
1717    /// from their bits: "the Group ID and Object ID fields are present.
1718    /// Subgroup ID, Priority and Properties are not present." Their low bits
1719    /// happen to spell exactly that (all three are `0x0C` in the low seven
1720    /// bits: Group ID Delta and Object ID Delta set, Subgroup ID mode 0b00, no
1721    /// priority, no properties), but that is a property of the values the draft
1722    /// chose and not a rule, so the layout is taken from the section that
1723    /// states it.
1724    ///
1725    /// **`Object Payload Length` is present on a marker, encoded as 0.**
1726    /// Section 11.4.1.2 lists what is absent and does not name it, and Figure
1727    /// 28 marks it mandatory; the draft does not settle the case. This codec
1728    /// keeps the field, because omitting one a figure requires is what
1729    /// desynchronises a fetch stream, and a marker has nothing to put in it
1730    /// anyway. The layout below therefore differs from an ordinary object only
1731    /// in the optional fields.
1732    fn layout(flags: u64) -> Result<FetchObjectLayout, CodecError> {
1733        if flags == FETCH_END_OF_NON_EXISTENT_RANGE
1734            || flags == FETCH_END_OF_UNKNOWN_RANGE
1735            || flags == FETCH_END_OF_TIMED_OUT_RANGE
1736        {
1737            return Ok(FetchObjectLayout {
1738                group_id_delta: true,
1739                subgroup_id: false,
1740                object_id_delta: true,
1741                publisher_priority: false,
1742                properties: false,
1743            });
1744        }
1745        if flags > FETCH_FLAGS_MAX {
1746            return Err(CodecError::InvalidField);
1747        }
1748        Ok(FetchObjectLayout {
1749            group_id_delta: flags & FETCH_GROUP_ID_DELTA_BIT != 0,
1750            // An Object with the Datagram bit set has no Subgroup ID to write,
1751            // whatever the mode bits hold, so the field is absent.
1752            subgroup_id: flags & FETCH_DATAGRAM_BIT == 0
1753                && flags & FETCH_SUBGROUP_ID_MODE_MASK == FETCH_SUBGROUP_ID_EXPLICIT,
1754            object_id_delta: flags & FETCH_OBJECT_ID_DELTA_BIT != 0,
1755            publisher_priority: flags & FETCH_PRIORITY_BIT != 0,
1756            properties: flags & FETCH_PROPERTIES_BIT != 0,
1757        })
1758    }
1759
1760    /// Decode one fetch object header, leaving the payload in `buf`.
1761    ///
1762    /// Errors with [`CodecError::InvalidField`] for a Serialization Flags
1763    /// value draft-21 Section 11.4.1 does not define, and with
1764    /// [`CodecError::UnexpectedEnd`] or a varint error when the buffer runs
1765    /// out mid-field.
1766    ///
1767    /// The flags are validated before any field is read, because they are what
1768    /// says where the fields are: decoding an undefined value would mean
1769    /// picking a layout the draft never described and then consuming a
1770    /// plausible number of bytes under it.
1771    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1772        let serialization_flags = VarInt::decode_moqt::<Wire>(buf)?;
1773        let layout = Self::layout(serialization_flags.into_inner())?;
1774
1775        let group_id_delta =
1776            layout.group_id_delta.then(|| VarInt::decode_moqt::<Wire>(buf)).transpose()?;
1777        let subgroup_id =
1778            layout.subgroup_id.then(|| VarInt::decode_moqt::<Wire>(buf)).transpose()?;
1779        let object_id_delta =
1780            layout.object_id_delta.then(|| VarInt::decode_moqt::<Wire>(buf)).transpose()?;
1781
1782        let publisher_priority = if layout.publisher_priority {
1783            if buf.remaining() < 1 {
1784                return Err(CodecError::UnexpectedEnd);
1785            }
1786            Some(buf.get_u8())
1787        } else {
1788            None
1789        };
1790
1791        let properties = if layout.properties {
1792            let len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1793            Some(crate::types::read_bytes(buf, len)?)
1794        } else {
1795            None
1796        };
1797
1798        let payload_length = VarInt::decode_moqt::<Wire>(buf)?;
1799
1800        Ok(FetchObjectHeader {
1801            serialization_flags,
1802            group_id_delta,
1803            subgroup_id,
1804            object_id_delta,
1805            publisher_priority,
1806            properties,
1807            payload_length,
1808        })
1809    }
1810
1811    /// Serialize the header, refusing one whose fields disagree with its own
1812    /// Serialization Flags.
1813    ///
1814    /// Errors with [`CodecError::InvalidField`] when the flags are a value
1815    /// draft-21 does not define, and when any optional field is present while
1816    /// its flag is clear or absent while its flag is set. Checked before any
1817    /// byte is written, so a refused header leaves `buf` untouched.
1818    ///
1819    /// Fallible for the same reason [`DatagramHeader::encode_checked`] is: the
1820    /// flags decide the framing, so writing them as the authority and dropping
1821    /// whatever they do not cover is silent data loss. A Group ID Delta held
1822    /// with the 0x08 bit clear is not written, the reader takes the Object as
1823    /// belonging to the previous Object's group, and nothing about the
1824    /// resulting stream looks wrong. The mirror case is worse: a flag set with
1825    /// no value behind it would have to invent one, and an invented Object ID
1826    /// Delta of zero is a real Object ID.
1827    ///
1828    /// Nothing here checks the deltas against the previous Object — that no
1829    /// Object other than the first may reference a prior Object that does not
1830    /// exist, for one. A single header has no way to see that, and this type
1831    /// deliberately does not carry stream state.
1832    pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1833        let layout = Self::layout(self.flags())?;
1834        if layout.group_id_delta != self.group_id_delta.is_some()
1835            || layout.subgroup_id != self.subgroup_id.is_some()
1836            || layout.object_id_delta != self.object_id_delta.is_some()
1837            || layout.publisher_priority != self.publisher_priority.is_some()
1838            || layout.properties != self.properties.is_some()
1839        {
1840            return Err(CodecError::InvalidField);
1841        }
1842
1843        self.serialization_flags.encode_moqt::<Wire>(buf);
1844        // Wire order, from Figure 28: Group ID Delta, then Subgroup ID, then
1845        // Object ID Delta. The `layout` check above has already established
1846        // that exactly the fields the flags call for are present, so whichever
1847        // of the three are `Some` are the ones that belong here.
1848        for field in
1849            [self.group_id_delta, self.subgroup_id, self.object_id_delta].into_iter().flatten()
1850        {
1851            field.encode_moqt::<Wire>(buf);
1852        }
1853        if let Some(priority) = self.publisher_priority {
1854            buf.put_u8(priority);
1855        }
1856        if let Some(properties) = &self.properties {
1857            VarInt::from_usize(properties.len()).encode_moqt::<Wire>(buf);
1858            buf.put_slice(properties);
1859        }
1860        self.payload_length.encode_moqt::<Wire>(buf);
1861        Ok(())
1862    }
1863}
1864
1865/// The order a FETCH response's Groups arrive in, which decides how a Group ID
1866/// Delta is applied.
1867///
1868/// Draft-21 Section 11.4.1.1: "If the Group Order is Ascending, the Group ID is
1869/// the prior Object's Group ID plus the Group ID Delta + 1. If the Group Order
1870/// is Descending, the Group ID is the prior Object's Group ID minus the (Group
1871/// ID Delta + 1)."
1872///
1873/// The order is not on the data stream — it is settled by the control exchange
1874/// that opened the FETCH, whose GROUP_ORDER parameter (Section 9.20.9) spells
1875/// Ascending 0x1 and Descending 0x2 — so [`FetchObjectReader`] has to be told
1876/// which one it is reading. Getting it wrong does not fail to parse: it decodes
1877/// every Object under a Group ID that walks the wrong way.
1878#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1879pub enum GroupOrder {
1880    /// Group IDs increase: a delta adds to the prior Group ID.
1881    Ascending,
1882    /// Group IDs decrease: a delta subtracts from the prior Group ID.
1883    Descending,
1884}
1885
1886/// One frame from a FETCH stream with its delta-encoded fields resolved.
1887///
1888/// The header is kept alongside the resolved values so that a caller can
1889/// forward the frame's bytes unchanged while acting on what they mean.
1890#[derive(Debug, Clone, PartialEq, Eq)]
1891pub struct FetchObject {
1892    /// The frame as it appeared on the wire.
1893    pub header: FetchObjectHeader,
1894    /// Resolved absolute Group ID. On an End of Range marker, the Group ID of
1895    /// the Location the marker names.
1896    pub group_id: u64,
1897    /// Resolved Subgroup ID. `None` for an End of Range marker, which has
1898    /// none, and for an Object whose forwarding preference is Datagram.
1899    pub subgroup_id: Option<u64>,
1900    /// Resolved absolute Object ID. On an End of Range marker, the Object ID of
1901    /// the Location the marker names.
1902    pub object_id: u64,
1903    /// The Publisher Priority in force for this frame, whether this frame wrote
1904    /// it or an earlier one did, and `None` while no frame has written one.
1905    ///
1906    /// An End of Range marker carries no Priority field of its own, so what it
1907    /// reports is the one still in force from the last Object before it —
1908    /// Section 11.4.1.2: "Prior Priority: The Priority from the last actual
1909    /// Object before the End of Range indicator."
1910    ///
1911    /// The draft-21 fallback for a subscription that never stated a priority is
1912    /// left to the caller rather than substituted here, so that "no frame has
1913    /// said" stays distinguishable from "a frame said 128".
1914    pub publisher_priority: Option<u8>,
1915}
1916
1917/// Resolves the delta-encoded fields of the frames on one FETCH stream.
1918///
1919/// Draft-21 Section 11.4.1.1 defines nearly every field of a fetch frame
1920/// against "the prior Object", so no frame after the first can be understood on
1921/// its own. This holds what the frames so far established, in the two parts the
1922/// draft keeps separate: Section 11.4.1.2 says that after an End of Range
1923/// marker the prior Group ID and Object ID are the marker's, while the prior
1924/// Subgroup ID and Priority are still "from the last actual Object before the
1925/// End of Range indicator".
1926///
1927/// Every rule the section answers with a PROTOCOL_VIOLATION is refused here
1928/// with [`CodecError::InvalidField`]: a first Object that references fields no
1929/// prior Object established, a Subgroup ID or Priority inherited when there is
1930/// none to inherit, and an arithmetic result outside the 64-bit range.
1931#[derive(Debug, Clone)]
1932pub struct FetchObjectReader {
1933    group_order: GroupOrder,
1934    /// Group ID and Object ID of the last frame, marker or Object.
1935    prior_location: Option<(u64, u64)>,
1936    /// Subgroup ID of the last actual Object that had one.
1937    prior_subgroup_id: Option<u64>,
1938    /// Publisher Priority of the last actual Object.
1939    prior_publisher_priority: Option<u8>,
1940    /// The Prior gap conditions the stream's Objects show.
1941    gaps: FetchGaps,
1942}
1943
1944impl FetchObjectReader {
1945    /// A reader for a stream whose Groups arrive in `group_order`.
1946    pub fn new(group_order: GroupOrder) -> Self {
1947        Self {
1948            group_order,
1949            prior_location: None,
1950            prior_subgroup_id: None,
1951            prior_publisher_priority: None,
1952            gaps: FetchGaps::new(),
1953        }
1954    }
1955
1956    /// Decode the next frame's header and resolve its fields.
1957    ///
1958    /// Consumes the header only. The Object Payload is
1959    /// `header.payload_length` bytes and stays in `buf`, so a caller that
1960    /// forwards payloads never copies them and one that ignores them can skip.
1961    ///
1962    /// Errors with [`CodecError::InvalidField`] on every rule
1963    /// Section 11.4.1.1 states:
1964    ///
1965    /// - "The first Object MUST include a Group ID Delta and Object ID Delta,
1966    ///   and these values are the absolute Group ID and Object ID. If the first
1967    ///   Object in the FETCH response uses a flag that references fields in the
1968    ///   prior Object, the Subscriber MUST close the session with a
1969    ///   PROTOCOL_VIOLATION." Each such flag is refused where it is read, so
1970    ///   the reason survives: a missing delta, an inherited Priority and an
1971    ///   inherited Subgroup ID are three different frames, all of them
1972    ///   referencing an Object that does not exist.
1973    /// - "If the computed Group ID would be less than 0 or greater than
1974    ///   2^64-1, the Subscriber MUST close the Session with error
1975    ///   'PROTOCOL_VIOLATION'" — the descending and ascending ends of the same
1976    ///   rule.
1977    /// - "If the computed Object ID would be greater than 2^64-1, the
1978    ///   Subscriber MUST close the Session with error 'PROTOCOL_VIOLATION'."
1979    ///
1980    /// Refuses, with [`CodecError::MalformedTrack`], the Prior gap conditions an
1981    /// Object shows alone or after the Objects before it on the stream, as
1982    /// [`FetchGaps`] does, and a property block that does not parse as
1983    /// [`crate::object_properties::parse_object_properties`] does.
1984    pub fn read_object_header(&mut self, buf: &mut impl Buf) -> Result<FetchObject, CodecError> {
1985        let object = self.read_frame_header(buf)?;
1986        if object.header.end_of_range().is_none() {
1987            self.gaps.check(
1988                object.group_id,
1989                object.object_id,
1990                object.header.properties.as_deref().unwrap_or_default(),
1991            )?;
1992        }
1993        Ok(object)
1994    }
1995
1996    /// [`Self::read_object_header`] without the Prior gap conditions and without reading
1997    /// inside the property block, for the facade's frame and meta reads, which
1998    /// a forwarder uses.
1999    pub(crate) fn read_frame_header(
2000        &mut self,
2001        buf: &mut impl Buf,
2002    ) -> Result<FetchObject, CodecError> {
2003        let header = FetchObjectHeader::decode(buf)?;
2004
2005        // An End of Range marker carries no Subgroup ID and no Priority, so
2006        // those two are resolved here rather than by the rules below: Section
2007        // 11.4.1.2 says the prior Subgroup ID and prior Priority for whatever
2008        // follows come "from the last actual Object before the End of Range
2009        // indicator", which means a marker neither states them nor disturbs
2010        // them, and a marker arriving before any Object has none to report.
2011        //
2012        // Its two present fields are the absolute Location; see
2013        // [`FetchObjectHeader::end_of_range`].
2014        if header.end_of_range().is_some() {
2015            let group_id = header.group_id_delta.ok_or(CodecError::InvalidField)?.into_inner();
2016            let object_id = header.object_id_delta.ok_or(CodecError::InvalidField)?.into_inner();
2017            self.prior_location = Some((group_id, object_id));
2018            return Ok(FetchObject {
2019                header,
2020                group_id,
2021                subgroup_id: None,
2022                object_id,
2023                publisher_priority: self.prior_publisher_priority,
2024            });
2025        }
2026
2027        let group_id = match (self.prior_location, header.group_id_delta) {
2028            // The first object's delta is its absolute Group ID.
2029            (None, Some(delta)) => delta.into_inner(),
2030            (None, None) => return Err(CodecError::InvalidField),
2031            (Some((prior_group, _)), None) => prior_group,
2032            (Some((prior_group, _)), Some(delta)) => {
2033                let delta = delta.into_inner();
2034                match self.group_order {
2035                    GroupOrder::Ascending => prior_group
2036                        .checked_add(delta)
2037                        .and_then(|v| v.checked_add(1))
2038                        .ok_or(CodecError::InvalidField)?,
2039                    GroupOrder::Descending => prior_group
2040                        .checked_sub(delta)
2041                        .and_then(|v| v.checked_sub(1))
2042                        .ok_or(CodecError::InvalidField)?,
2043                }
2044            }
2045        };
2046
2047        let object_id =
2048            match (self.prior_location, header.group_id_delta.is_some(), header.object_id_delta) {
2049                // A Group ID Delta restarts the Object ID from its own delta,
2050                // which is why a new group does not continue the previous
2051                // group's numbering.
2052                (_, true, Some(delta)) => delta.into_inner(),
2053                (Some((_, prior_object)), false, Some(delta)) => {
2054                    prior_object.checked_add(delta.into_inner()).ok_or(CodecError::InvalidField)?
2055                }
2056                (Some((_, prior_object)), _, None) => {
2057                    prior_object.checked_add(1).ok_or(CodecError::InvalidField)?
2058                }
2059                (None, _, _) => return Err(CodecError::InvalidField),
2060            };
2061
2062        let subgroup_id = if header.is_datagram() {
2063            None
2064        } else {
2065            Some(match header.subgroup_id_mode() {
2066                0x00 => 0,
2067                0x01 => self.prior_subgroup_id.ok_or(CodecError::InvalidField)?,
2068                0x02 => self
2069                    .prior_subgroup_id
2070                    .ok_or(CodecError::InvalidField)?
2071                    .checked_add(1)
2072                    .ok_or(CodecError::InvalidField)?,
2073                // Mode 0b11, the only value left: the field is on the wire.
2074                _ => header.subgroup_id.ok_or(CodecError::InvalidField)?.into_inner(),
2075            })
2076        };
2077
2078        let publisher_priority = match header.publisher_priority {
2079            Some(p) => p,
2080            None => self.prior_publisher_priority.ok_or(CodecError::InvalidField)?,
2081        };
2082
2083        self.prior_location = Some((group_id, object_id));
2084        // A Datagram-forwarded object has no Subgroup ID to leave behind, so it
2085        // does not clear the running one: the object after it inherits from the
2086        // last object that had one.
2087        if let Some(subgroup_id) = subgroup_id {
2088            self.prior_subgroup_id = Some(subgroup_id);
2089        }
2090        self.prior_publisher_priority = Some(publisher_priority);
2091
2092        Ok(FetchObject {
2093            header,
2094            group_id,
2095            subgroup_id,
2096            object_id,
2097            publisher_priority: Some(publisher_priority),
2098        })
2099    }
2100}
2101
2102/// Re-encodes resolved fetch frames onto one FETCH stream.
2103///
2104/// The exact inverse of [`FetchObjectReader`], and it exists for one caller:
2105/// something that has read a stream and is writing a different stream from the
2106/// same frames. Removing a frame changes what the frames after it are encoded
2107/// *against*, and draft-21 Section 11.4.1.1 defines nearly every field against
2108/// "the prior Object", so the survivor that follows a removed run cannot keep
2109/// its original bytes. What has to change is not one field: an Object that
2110/// carried no Group ID Delta because it shared its predecessor's group needs
2111/// one once that predecessor is gone, so a field appears and a flag bit with
2112/// it.
2113///
2114/// # Why this is not a general encoder
2115///
2116/// Every frame it writes came off a stream, so the caller already holds the
2117/// frame's own [`FetchObjectHeader`] alongside the resolved values. That header
2118/// is used as the preference: wherever the original shape still encodes the
2119/// same meaning against the new predecessor, it is kept, so a stream with
2120/// nothing removed from it is reproduced byte for byte. Only where the original
2121/// shape would now decode to something else is a different one chosen. An
2122/// encoder built from the resolved values alone could not do that — it would
2123/// have to invent a canonical form and would rewrite every frame on a stream
2124/// that needed no rewriting at all.
2125///
2126/// # What it refuses
2127///
2128/// [`CodecError::InvalidField`] where no encoding exists rather than picking
2129/// one: a Group ID that moves against the FETCH's Group Order, an Object ID
2130/// that does not advance, an Object with neither a Subgroup ID nor the Datagram
2131/// bit, and the arithmetic overflows. Each of these is a frame this writer was
2132/// handed that no draft-21 stream could carry, and inventing a value for it
2133/// would put a different Object on the wire than the one it was given.
2134#[derive(Debug, Clone)]
2135pub struct FetchObjectWriter {
2136    group_order: GroupOrder,
2137    /// Group ID and Object ID of the last frame written, marker or Object.
2138    prior_location: Option<(u64, u64)>,
2139    /// Subgroup ID of the last actual Object written that had one.
2140    prior_subgroup_id: Option<u64>,
2141    /// Publisher Priority of the last actual Object written.
2142    prior_publisher_priority: Option<u8>,
2143}
2144
2145impl FetchObjectWriter {
2146    /// A writer for a stream whose Groups are being written in `group_order`.
2147    ///
2148    /// The order has to match the one the FETCH was opened with, for the same
2149    /// reason [`FetchObjectReader::new`] takes it: it decides whether a Group
2150    /// ID Delta adds or subtracts, and it is not on the data stream.
2151    pub fn new(group_order: GroupOrder) -> Self {
2152        Self {
2153            group_order,
2154            prior_location: None,
2155            prior_subgroup_id: None,
2156            prior_publisher_priority: None,
2157        }
2158    }
2159
2160    /// The header that encodes `frame` against everything written so far.
2161    ///
2162    /// Does not advance the writer — [`Self::write_object_header`] is the call
2163    /// that does both. Separated so that a caller can measure the bytes a
2164    /// re-encode would take before committing to it.
2165    ///
2166    /// # Errors
2167    ///
2168    /// [`CodecError::InvalidField`] for a frame that cannot be encoded against
2169    /// the current predecessor; see the type's own documentation for the list.
2170    pub fn header_for(&self, frame: &FetchObject) -> Result<FetchObjectHeader, CodecError> {
2171        let original = &frame.header;
2172
2173        // An End of Range marker states its Location outright and inherits
2174        // nothing, so its two fields are the same whatever precedes it; see
2175        // [`FetchObjectHeader::end_of_range`].
2176        if original.end_of_range().is_some() {
2177            return Ok(FetchObjectHeader {
2178                serialization_flags: original.serialization_flags,
2179                group_id_delta: Some(VarInt::from_u64(frame.group_id)?),
2180                subgroup_id: None,
2181                object_id_delta: Some(VarInt::from_u64(frame.object_id)?),
2182                publisher_priority: None,
2183                properties: None,
2184                payload_length: original.payload_length,
2185            });
2186        }
2187
2188        let (group_id_delta, object_id_delta) = self.identity_fields(frame, original)?;
2189        let (subgroup_mode, subgroup_id) = self.subgroup_field(frame, original)?;
2190        let publisher_priority = self.priority_field(frame, original)?;
2191
2192        let mut flags = subgroup_mode;
2193        if original.is_datagram() {
2194            flags |= FETCH_DATAGRAM_BIT;
2195        }
2196        if group_id_delta.is_some() {
2197            flags |= FETCH_GROUP_ID_DELTA_BIT;
2198        }
2199        if object_id_delta.is_some() {
2200            flags |= FETCH_OBJECT_ID_DELTA_BIT;
2201        }
2202        if publisher_priority.is_some() {
2203            flags |= FETCH_PRIORITY_BIT;
2204        }
2205        if original.properties.is_some() {
2206            flags |= FETCH_PROPERTIES_BIT;
2207        }
2208
2209        Ok(FetchObjectHeader {
2210            serialization_flags: VarInt::from_u64(flags)?,
2211            group_id_delta,
2212            subgroup_id,
2213            object_id_delta,
2214            publisher_priority,
2215            properties: original.properties.clone(),
2216            payload_length: original.payload_length,
2217        })
2218    }
2219
2220    /// The Group ID Delta and Object ID Delta fields, as this predecessor needs
2221    /// them.
2222    ///
2223    /// Presence is forced by the frame rather than chosen: a group that differs
2224    /// from the predecessor's has to be stated, and one that matches has to be
2225    /// left off, since a delta of zero means the next group along and not this
2226    /// one. Only the Object ID Delta has a choice to make, and it is made in
2227    /// favour of the shape the frame arrived in.
2228    fn identity_fields(
2229        &self,
2230        frame: &FetchObject,
2231        original: &FetchObjectHeader,
2232    ) -> Result<(Option<VarInt>, Option<VarInt>), CodecError> {
2233        let Some((prior_group, prior_object)) = self.prior_location else {
2234            // Section 11.4.1.1: "The first Object MUST include a Group ID Delta
2235            // and Object ID Delta, and these values are the absolute Group ID
2236            // and Object ID."
2237            return Ok((
2238                Some(VarInt::from_u64(frame.group_id)?),
2239                Some(VarInt::from_u64(frame.object_id)?),
2240            ));
2241        };
2242
2243        if frame.group_id != prior_group {
2244            // A Group ID Delta moves the group by delta + 1, forwards under
2245            // Ascending and backwards under Descending, and when an Object ID
2246            // Delta accompanies it the Object ID is that delta outright rather
2247            // than an advance on the predecessor.
2248            let step = match self.group_order {
2249                GroupOrder::Ascending => frame.group_id.checked_sub(prior_group),
2250                GroupOrder::Descending => prior_group.checked_sub(frame.group_id),
2251            };
2252            let delta = step.and_then(|s| s.checked_sub(1)).ok_or(CodecError::InvalidField)?;
2253
2254            // Omitting the Object ID Delta across a group boundary is legal and
2255            // is a byte shorter. Section 11.4.1.1: "If Object ID Delta is not
2256            // present, the Object ID is the prior Object's ID plus one,
2257            // REGARDLESS OF WHICH GROUP IT BELONGS TO." So an Object that
2258            // continues the numbering into a new group encodes without one —
2259            // the Object ID does not restart at the group boundary unless a
2260            // delta says so.
2261            //
2262            // Gated on the frame's own framing, like the same-group case below,
2263            // so re-emitting a stream reproduces the publisher's bytes instead
2264            // of silently rewriting the shorter form into the longer one.
2265            if original.object_id_delta.is_none()
2266                && frame.object_id == prior_object.wrapping_add(1)
2267                && prior_object != u64::MAX
2268            {
2269                return Ok((Some(VarInt::from_u64(delta)?), None));
2270            }
2271
2272            return Ok((Some(VarInt::from_u64(delta)?), Some(VarInt::from_u64(frame.object_id)?)));
2273        }
2274
2275        // Same group. The Object ID is the predecessor's plus the delta, or
2276        // plus one when no delta is written, so an Object that does not advance
2277        // has no encoding at all.
2278        let advance = frame.object_id.checked_sub(prior_object).ok_or(CodecError::InvalidField)?;
2279        if advance == 0 {
2280            return Err(CodecError::InvalidField);
2281        }
2282        if advance == 1 && original.object_id_delta.is_none() {
2283            return Ok((None, None));
2284        }
2285        Ok((None, Some(VarInt::from_u64(advance)?)))
2286    }
2287
2288    /// The Subgroup ID mode bits and the explicit field, if one is needed.
2289    ///
2290    /// The frame's own mode is tried first, so a run of Objects that inherited
2291    /// their Subgroup ID keeps inheriting it and its bytes do not move. Only
2292    /// when the predecessor changed under it does a different mode get chosen,
2293    /// and then the cheapest one that says the right number.
2294    fn subgroup_field(
2295        &self,
2296        frame: &FetchObject,
2297        original: &FetchObjectHeader,
2298    ) -> Result<(u64, Option<VarInt>), CodecError> {
2299        // Section 11.4.1.1 has the subscriber ignore these bits on a
2300        // Datagram-forwarded Object, and no field is on the wire whatever they
2301        // say, so the frame's own bits are carried across untouched.
2302        if original.is_datagram() {
2303            return Ok((original.flags() & FETCH_SUBGROUP_ID_MODE_MASK, None));
2304        }
2305
2306        let subgroup_id = frame.subgroup_id.ok_or(CodecError::InvalidField)?;
2307        let inherits = self.prior_subgroup_id == Some(subgroup_id);
2308        let successor =
2309            self.prior_subgroup_id.is_some_and(|p| p.checked_add(1) == Some(subgroup_id));
2310
2311        // The frame's own mode, kept when it still names this number.
2312        let kept = match original.flags() & FETCH_SUBGROUP_ID_MODE_MASK {
2313            0x00 if subgroup_id == 0 => Some((0x00, None)),
2314            0x01 if inherits => Some((0x01, None)),
2315            0x02 if successor => Some((0x02, None)),
2316            FETCH_SUBGROUP_ID_EXPLICIT => Some((FETCH_SUBGROUP_ID_EXPLICIT, Some(subgroup_id))),
2317            _ => None,
2318        };
2319        let (mode, explicit) = match kept {
2320            Some(pair) => pair,
2321            None if subgroup_id == 0 => (0x00, None),
2322            None if inherits => (0x01, None),
2323            None if successor => (0x02, None),
2324            None => (FETCH_SUBGROUP_ID_EXPLICIT, Some(subgroup_id)),
2325        };
2326        Ok((mode, explicit.map(VarInt::from_u64).transpose()?))
2327    }
2328
2329    /// The Publisher Priority field, or `None` when the predecessor already
2330    /// carries it.
2331    ///
2332    /// Written whenever the frame wrote one, so a publisher that stated a
2333    /// priority on every Object keeps its bytes, and written anyway when the
2334    /// predecessor's differs or when there is no predecessor to inherit from.
2335    fn priority_field(
2336        &self,
2337        frame: &FetchObject,
2338        original: &FetchObjectHeader,
2339    ) -> Result<Option<u8>, CodecError> {
2340        let priority = frame.publisher_priority.ok_or(CodecError::InvalidField)?;
2341        if original.publisher_priority.is_some() || self.prior_publisher_priority != Some(priority)
2342        {
2343            return Ok(Some(priority));
2344        }
2345        Ok(None)
2346    }
2347
2348    /// Encode `frame` against everything written so far and advance.
2349    ///
2350    /// Writes the header only. The payload is `frame.header.payload_length`
2351    /// bytes and is the caller's to copy, unchanged — nothing about it depends
2352    /// on what preceded the Object.
2353    ///
2354    /// # Errors
2355    ///
2356    /// [`CodecError::InvalidField`] for a frame with no encoding against the
2357    /// current predecessor. The writer is left untouched when this happens, so
2358    /// a caller that gives up on one frame and carries on with the next is
2359    /// writing against the same predecessor it thought it was.
2360    pub fn write_object_header(
2361        &mut self,
2362        frame: &FetchObject,
2363        out: &mut impl BufMut,
2364    ) -> Result<FetchObjectHeader, CodecError> {
2365        let header = self.header_for(frame)?;
2366        header.encode(out)?;
2367        self.advance(frame);
2368        Ok(header)
2369    }
2370
2371    /// Record `frame` as the predecessor of whatever is written next.
2372    ///
2373    /// Split from the write so that a caller re-emitting bytes it already holds
2374    /// can advance without producing a header twice — which is what happens
2375    /// whenever the framing a frame arrived in still encodes the same meaning
2376    /// against the frame before it, and is why this is public.
2377    pub fn advance(&mut self, frame: &FetchObject) {
2378        self.prior_location = Some((frame.group_id, frame.object_id));
2379        // Mirrors the reader: a Datagram-forwarded Object leaves no Subgroup ID
2380        // behind, so the running one survives it.
2381        if let Some(subgroup_id) = frame.subgroup_id {
2382            self.prior_subgroup_id = Some(subgroup_id);
2383        }
2384        if frame.header.end_of_range().is_none() {
2385            if let Some(priority) = frame.publisher_priority {
2386                self.prior_publisher_priority = Some(priority);
2387            }
2388        }
2389    }
2390}
2391
2392#[cfg(test)]
2393mod tests {
2394    use super::*;
2395
2396    /// Canonically encoded subgroup stream vectors from
2397    /// `test-vectors/transport/draft21/codec/data-streams/subgroup.json`.
2398    /// `subgroup-explicit-subgroup-id` is omitted: it encodes group_id 100 as a
2399    /// two-byte varint, which does not survive a minimal-width re-encode.
2400    const VECTORS: &[&str] = &[
2401        // subgroup-single-object
2402        "100100800004deadbeef",
2403        // subgroup-two-objects
2404        "100100800004deadbeef0002cafe",
2405        // subgroup-no-priority
2406        "3001000004deadbeef",
2407        // subgroup-with-extensions
2408        "11010080000004deadbeef",
2409        // subgroup-end-of-group
2410        "180105800004deadbeef",
2411        // subgroup-id-mode-01
2412        "120100800504deadbeef",
2413        // subgroup-with-object-properties
2414        "1101058001043c02020104deadbeef",
2415        // subgroup-object-status-end-of-group
2416        "100100800004deadbeef000003",
2417        // subgroup-object-status-end-of-track
2418        "10010080000004",
2419        // subgroup-properties-two-objects-empty
2420        "11010080000004deadbeef000002cafe",
2421        // subgroup-properties-two-objects-nonempty
2422        "1101058000023c0204deadbeef00023c0202cafe",
2423        // subgroup-first-object-bit
2424        "500100800004deadbeef",
2425        // subgroup-first-object-and-end-of-group
2426        "580102800002cafe",
2427    ];
2428
2429    fn vi(v: u64) -> VarInt {
2430        VarInt::from_u64_moqt(v)
2431    }
2432
2433    fn hex(s: &str) -> Vec<u8> {
2434        (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
2435    }
2436
2437    /// Decode a whole subgroup stream: the header, then every object up to
2438    /// the end of the buffer.
2439    fn decode_all(bytes: &[u8]) -> (SubgroupHeader, Vec<SubgroupObject>) {
2440        let mut cursor = bytes;
2441        let header = SubgroupHeader::decode(&mut cursor)
2442            .unwrap_or_else(|e| panic!("header decode failed: {e:?}"));
2443        let mut reader = SubgroupObjectReader::new(&header);
2444        let mut objects = Vec::new();
2445        while cursor.has_remaining() {
2446            objects.push(
2447                reader
2448                    .read_object(&mut cursor)
2449                    .unwrap_or_else(|e| panic!("object {} decode failed: {e:?}", objects.len())),
2450            );
2451        }
2452        (header, objects)
2453    }
2454
2455    fn encode_all(header: &SubgroupHeader, objects: &[SubgroupObject]) -> Vec<u8> {
2456        let mut buf = Vec::new();
2457        header.encode(&mut buf);
2458        let mut writer = SubgroupObjectReader::new(header);
2459        for o in objects {
2460            writer.write_object(o, &mut buf).unwrap_or_else(|e| panic!("write failed: {e:?}"));
2461        }
2462        buf
2463    }
2464
2465    fn object(id: u64, extensions: Vec<u8>, payload: Vec<u8>) -> SubgroupObject {
2466        SubgroupObject {
2467            object_id: vi(id),
2468            extension_headers: extensions,
2469            payload_length: vi(payload.len() as u64),
2470            object_status: None,
2471            payload,
2472        }
2473    }
2474
2475    // ── Object ID deltas ────────────────────────────────────
2476
2477    #[test]
2478    fn two_objects_with_properties_have_distinct_ids() {
2479        // Vector `subgroup-properties-two-objects-empty`: two objects, each
2480        // carrying an empty properties block and a delta of 0. The delta is
2481        // biased by one whether or not the properties bit is set, so the IDs
2482        // are 0 and 1 — not 0 and 0.
2483        let bytes = hex("11010080000004deadbeef000002cafe");
2484        let (header, objects) = decode_all(&bytes);
2485        assert!(header.has_properties());
2486        assert_eq!(objects.len(), 2);
2487        assert_eq!(objects[0].object_id.into_inner(), 0);
2488        assert_eq!(objects[1].object_id.into_inner(), 1);
2489        assert_eq!(objects[0].payload, hex("deadbeef"));
2490        assert_eq!(objects[1].payload, hex("cafe"));
2491        assert!(objects.iter().all(|o| o.extension_headers.is_empty()));
2492    }
2493
2494    #[test]
2495    fn deltas_resolve_sparse_ids() {
2496        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2497        let objects: Vec<_> =
2498            [3u64, 4, 40].iter().map(|&id| object(id, vec![], vec![0xAA, id as u8])).collect();
2499        let (_, decoded) = decode_all(&encode_all(&header, &objects));
2500        let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
2501        assert_eq!(ids, vec![3, 4, 40]);
2502    }
2503
2504    #[test]
2505    fn write_rejects_non_increasing_ids() {
2506        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2507        let mut writer = SubgroupObjectReader::new(&header);
2508        let mut buf = Vec::new();
2509        writer.write_object(&object(7, vec![], vec![0x01]), &mut buf).unwrap();
2510        for id in [7u64, 6, 0] {
2511            let err = writer.write_object(&object(id, vec![], vec![0x01]), &mut buf).unwrap_err();
2512            assert!(matches!(err, CodecError::InvalidField), "id {id} gave {err:?}");
2513        }
2514    }
2515
2516    #[test]
2517    fn eliding_an_object_renumbers_its_successor() {
2518        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2519        let all: Vec<_> = (0..5u64).map(|id| object(id, vec![], vec![id as u8])).collect();
2520        for elided in 0..5u64 {
2521            let kept: Vec<_> =
2522                all.iter().filter(|o| o.object_id.into_inner() != elided).cloned().collect();
2523            let (_, decoded) = decode_all(&encode_all(&header, &kept));
2524            let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
2525            let expected: Vec<u64> = (0..5u64).filter(|&i| i != elided).collect();
2526            assert_eq!(ids, expected, "eliding object {elided}");
2527        }
2528    }
2529
2530    // ── Properties blocks ──────────────────────────────
2531
2532    #[test]
2533    fn properties_blob_excludes_its_length_prefix() {
2534        // Vector `subgroup-properties-two-objects-nonempty`: each
2535        // object carries a two-byte block, so the blob is those two bytes
2536        // with the `02` length prefix stripped.
2537        let bytes = hex("1101058000023c0204deadbeef00023c0202cafe");
2538        let (_, objects) = decode_all(&bytes);
2539        assert_eq!(objects.len(), 2);
2540        assert_eq!(objects[0].object_id.into_inner(), 0);
2541        assert_eq!(objects[1].object_id.into_inner(), 1);
2542        assert_eq!(objects[0].extension_headers, hex("3c02"));
2543        assert_eq!(objects[1].extension_headers, hex("3c02"));
2544        assert_eq!(objects[0].payload, hex("deadbeef"));
2545        assert_eq!(objects[1].payload, hex("cafe"));
2546    }
2547
2548    /// Read every object on a subgroup stream, returning the first error.
2549    fn first_error(stream: &str) -> Option<CodecError> {
2550        let bytes = hex(stream);
2551        let mut cursor = &bytes[..];
2552        let header = SubgroupHeader::decode(&mut cursor).unwrap();
2553        let mut reader = SubgroupObjectReader::new(&header);
2554        while cursor.has_remaining() {
2555            if let Err(e) = reader.read_object(&mut cursor) {
2556                return Some(e);
2557            }
2558        }
2559        None
2560    }
2561
2562    #[test]
2563    fn prior_gaps_are_checked_per_object_and_along_the_stream() {
2564        let malformed =
2565            |stream| matches!(first_error(stream), Some(CodecError::MalformedTrack { .. }));
2566        // Section 10.8: "An Object contains more than one instance of Prior
2567        // Group ID Gap", at the top level and inside Immutable Properties.
2568        assert!(malformed("1101058000043c01000104deadbeef"));
2569        assert!(malformed("1101058000060b023c01310104deadbeef"));
2570        // Section 10.9: Object 2's gap of 2 covers Object 0, received earlier,
2571        // which draft-21 Section 2.1.1 allows: an Object known to exist "can
2572        // transition to not existing".
2573        assert_eq!(first_error("11010580000004deadbeef01023e0202cafe"), None);
2574        // Object 3's gap of 2 covers Objects 1 and 2, neither received.
2575        assert_eq!(first_error("11010580000004deadbeef02023e0202cafe"), None);
2576        // Equal Prior Group ID Gap values in one Group are fine.
2577        assert_eq!(first_error("1101058000023c0204deadbeef00023c0202cafe"), None);
2578    }
2579
2580    #[test]
2581    fn status_object_carries_its_properties_block() {
2582        // Section 11.1.3 forbids properties on a non-Normal status, so the
2583        // whole-object reader refuses the frame and the meta reader frames it.
2584        let bytes = hex("1101058000023c010003");
2585        let mut cursor = &bytes[..];
2586        let header = SubgroupHeader::decode(&mut cursor).unwrap();
2587        let mut reader = SubgroupObjectReader::new(&header);
2588        assert!(matches!(reader.read_object(&mut &cursor[..]), Err(CodecError::InvalidField)));
2589        let meta = SubgroupObjectReader::new(&header).read_object_meta(&mut &cursor[..]).unwrap();
2590        assert_eq!(meta.extension_headers_len, 2);
2591        assert_eq!(meta.status, Some(3));
2592    }
2593
2594    // ── Re-encoding ─────────────────────────────────────────
2595
2596    #[test]
2597    fn vectors_re_encode_byte_identically() {
2598        for vector in VECTORS {
2599            let bytes = hex(vector);
2600            let (header, objects) = decode_all(&bytes);
2601            assert_eq!(encode_all(&header, &objects), bytes, "[{vector}] re-encode");
2602        }
2603    }
2604
2605    // ── Payload-free framing ────────────────────────────────
2606
2607    #[test]
2608    fn meta_matches_read_object() {
2609        for vector in VECTORS {
2610            let bytes = hex(vector);
2611            let mut cursor = &bytes[..];
2612            let header = SubgroupHeader::decode(&mut cursor).unwrap();
2613            let mut full_reader = SubgroupObjectReader::new(&header);
2614            let mut meta_reader = SubgroupObjectReader::new(&header);
2615            let mut full_cursor = cursor;
2616            let mut meta_cursor = cursor;
2617            while meta_cursor.has_remaining() {
2618                let before = meta_cursor.remaining();
2619                let object = full_reader.read_object(&mut full_cursor).unwrap();
2620                let meta = meta_reader.read_object_meta(&mut meta_cursor).unwrap();
2621                assert_eq!(meta.object_id, object.object_id.into_inner(), "[{vector}]");
2622                assert_eq!(
2623                    meta.extension_headers_len,
2624                    object.extension_headers.len() as u64,
2625                    "[{vector}]"
2626                );
2627                assert_eq!(meta.payload_length, object.payload_length.into_inner(), "[{vector}]");
2628                assert_eq!(
2629                    meta.status,
2630                    object.object_status.map(ObjectStatus::as_u64),
2631                    "[{vector}]"
2632                );
2633                assert_eq!(meta.wire_len, (before - meta_cursor.remaining()) as u64, "[{vector}]");
2634                assert_eq!(full_cursor.remaining(), meta_cursor.remaining(), "[{vector}]");
2635            }
2636        }
2637    }
2638
2639    #[test]
2640    fn short_buffers_report_unexpected_end() {
2641        let bytes = hex("1101058000023c0204deadbeef00023c0202cafe");
2642        let mut cursor = &bytes[..];
2643        let header = SubgroupHeader::decode(&mut cursor).unwrap();
2644        let objects_start = bytes.len() - cursor.len();
2645        for cut in objects_start..bytes.len() {
2646            let mut reader = SubgroupObjectReader::new(&header);
2647            let mut meta_reader = SubgroupObjectReader::new(&header);
2648            let mut cursor = &bytes[objects_start..cut];
2649            let mut meta_cursor = cursor;
2650            while cursor.has_remaining() {
2651                if let Err(err) = reader.read_object(&mut cursor) {
2652                    assert!(
2653                        matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2654                        "cut {cut} gave {err:?}"
2655                    );
2656                    break;
2657                }
2658            }
2659            while meta_cursor.has_remaining() {
2660                if let Err(err) = meta_reader.read_object_meta(&mut meta_cursor) {
2661                    assert!(
2662                        matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2663                        "cut {cut} gave {err:?}"
2664                    );
2665                    break;
2666                }
2667            }
2668        }
2669    }
2670
2671    // ── Object status ───────────────────────────────────────
2672
2673    /// A one-object subgroup stream whose object carries `status` in place of
2674    /// a payload: header type 0x10 (no properties, subgroup-ID mode 0, no
2675    /// FIRST_OBJECT bit), track alias 1, group 0, publisher priority 128; then
2676    /// an Object ID delta of 0, a payload length of 0, and the status code.
2677    fn subgroup_status_stream(status: u64) -> Vec<u8> {
2678        vec![0x10, 0x01, 0x00, 0x80, 0x00, 0x00, status as u8]
2679    }
2680
2681    /// A status datagram carrying `status`: type 0x20 (STATUS bit set,
2682    /// explicit Object ID, explicit priority), track alias 1, group 0, object
2683    /// 0, priority 128, then the status byte.
2684    fn status_datagram(status: u64) -> Vec<u8> {
2685        vec![0x20, 0x01, 0x00, 0x00, 0x80, status as u8]
2686    }
2687
2688    /// The object [`subgroup_status_stream`] describes, as a value.
2689    fn status_object(status: Option<ObjectStatus>) -> SubgroupObject {
2690        SubgroupObject {
2691            object_id: vi(0),
2692            extension_headers: Vec::new(),
2693            payload_length: vi(0),
2694            object_status: status,
2695            payload: Vec::new(),
2696        }
2697    }
2698
2699    /// An object carrying both `payload` and, in the caller's hands, `status`.
2700    /// The wire has no room for both, which is what the registry rules on.
2701    fn payload_object(status: Option<ObjectStatus>, payload: Vec<u8>) -> SubgroupObject {
2702        SubgroupObject {
2703            object_id: vi(0),
2704            extension_headers: Vec::new(),
2705            payload_length: vi(payload.len() as u64),
2706            object_status: status,
2707            payload,
2708        }
2709    }
2710
2711    /// The datagram [`status_datagram`] describes, as a value.
2712    fn status_datagram_header(status: Option<ObjectStatus>) -> DatagramHeader {
2713        DatagramHeader {
2714            datagram_type: 0x20,
2715            track_alias: vi(1),
2716            group_id: vi(0),
2717            object_id: vi(0),
2718            publisher_priority: Some(128),
2719            properties: Vec::new(),
2720            object_status: status,
2721        }
2722    }
2723
2724    /// Every status draft-21 assigns can be written and read back as the same
2725    /// status, on both a subgroup stream and a status datagram.
2726    ///
2727    /// The set is read from `ObjectStatus::ALL` — the three rows of the Object
2728    /// Status registry — rather than restated here, so this moves with the
2729    /// draft if a code is ever reassigned. It is the gate on typing the two
2730    /// `object_status` fields: a typed field that silently narrowed or
2731    /// renumbered the set would fail here even though it still compiled.
2732    ///
2733    /// Writing `ObjectStatus::Normal` when a zero-length object's status is
2734    /// `None` is checked too — without it the encoder emits an object whose
2735    /// declared payload length promises a status field that never arrives.
2736    ///
2737    /// Fails if `write_object` or `DatagramHeader::encode` writes a constant
2738    /// status instead of the object's own.
2739    #[test]
2740    fn every_assigned_status_survives_a_round_trip() {
2741        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2742        for &status in ObjectStatus::ALL {
2743            let mut buf = Vec::new();
2744            SubgroupObjectReader::new(&header)
2745                .write_object(&status_object(Some(status)), &mut buf)
2746                .unwrap_or_else(|e| panic!("write_object refused {status:?}: {e:?}"));
2747
2748            let mut cursor = &buf[..];
2749            let object =
2750                SubgroupObjectReader::new(&header).read_object(&mut cursor).unwrap_or_else(|e| {
2751                    panic!("read_object refused the bytes written for {status:?}: {e:?}")
2752                });
2753            assert_eq!(object.object_status, Some(status), "subgroup object status");
2754            assert!(!cursor.has_remaining(), "{status:?}: bytes left over after read_object");
2755
2756            let meta =
2757                SubgroupObjectReader::new(&header).read_object_meta(&mut &buf[..]).unwrap_or_else(
2758                    |e| panic!("read_object_meta refused the bytes written for {status:?}: {e:?}"),
2759                );
2760            assert_eq!(meta.status, Some(status.as_u64()), "subgroup meta status");
2761
2762            let mut datagram = Vec::new();
2763            status_datagram_header(Some(status)).encode(&mut datagram);
2764            let decoded = DatagramHeader::decode(&mut &datagram[..]).unwrap_or_else(|e| {
2765                panic!("datagram decode refused the bytes written for {status:?}: {e:?}")
2766            });
2767            assert_eq!(decoded.object_status, Some(status), "datagram object status");
2768        }
2769
2770        let mut buf = Vec::new();
2771        SubgroupObjectReader::new(&header).write_object(&status_object(None), &mut buf).unwrap();
2772        let object = SubgroupObjectReader::new(&header)
2773            .read_object(&mut &buf[..])
2774            .expect("a zero-length object with no status must still decode");
2775        assert_eq!(object.object_status, Some(ObjectStatus::Normal));
2776
2777        let mut datagram = Vec::new();
2778        status_datagram_header(None).encode(&mut datagram);
2779        let decoded = DatagramHeader::decode(&mut &datagram[..])
2780            .expect("a status datagram with no status must still decode");
2781        assert_eq!(decoded.object_status, Some(ObjectStatus::Normal));
2782    }
2783
2784    /// The encoder writes exactly the frames the decoder accepts.
2785    ///
2786    /// Sweeps every status code `0x00..=0x3f` — one wire byte under both the
2787    /// varint on a subgroup stream and the bare byte on a datagram, and wide
2788    /// enough to contain the gap at `0x2` and the `0x1` draft-16 dropped. For
2789    /// a code the draft assigns, the hand-built frame must decode *and* the
2790    /// encoder handed that status must reproduce those exact bytes. For a code
2791    /// it does not assign, the same frame must be refused at all three decode
2792    /// sites — and no `ObjectStatus` exists to hand the encoder, so the frame
2793    /// has no way to be produced in the first place.
2794    ///
2795    /// This is a gate on the status code points only. Which of those statuses
2796    /// may carry a payload is the separate question
2797    /// [`the_registry_decides_which_statuses_may_carry_a_payload`] gates.
2798    ///
2799    /// Fails if `write_object` or `DatagramHeader::encode` writes a constant
2800    /// status instead of the object's own, or if `ObjectStatus::from_u64`
2801    /// decodes a code the draft does not assign.
2802    ///
2803    /// # The encode-side refusal is a type, not an assertion
2804    ///
2805    /// Once `object_status` is typed there is no runtime path that offers the
2806    /// encoder the `0x2` that this module's decoder is documented as refusing,
2807    /// so no test here can watch one be refused. The guarantee is the type: an
2808    /// `Option<u8>` status field does not compile against this test.
2809    #[test]
2810    fn the_encoder_writes_exactly_the_frames_the_decoder_accepts() {
2811        for code in 0x00u64..=0x3f {
2812            let assigned = ObjectStatus::ALL.iter().copied().find(|s| s.as_u64() == code);
2813
2814            let stream = subgroup_status_stream(code);
2815            let mut cursor: &[u8] = &stream;
2816            let header = SubgroupHeader::decode(&mut cursor).unwrap();
2817            let objects = cursor;
2818            let read = SubgroupObjectReader::new(&header).read_object(&mut { objects });
2819            let meta = SubgroupObjectReader::new(&header).read_object_meta(&mut { objects });
2820
2821            let datagram = status_datagram(code);
2822            let decoded = DatagramHeader::decode(&mut &datagram[..]);
2823
2824            match assigned {
2825                Some(status) => {
2826                    let object = read.unwrap_or_else(|e| {
2827                        panic!(
2828                            "read_object refused status {code:#x}, which the draft assigns: {e:?}"
2829                        )
2830                    });
2831                    assert_eq!(object.object_status, Some(status));
2832                    assert_eq!(meta.unwrap().status, Some(code));
2833                    assert_eq!(decoded.unwrap().object_status, Some(status));
2834
2835                    let mut written = Vec::new();
2836                    header.encode(&mut written);
2837                    SubgroupObjectReader::new(&header)
2838                        .write_object(&status_object(Some(status)), &mut written)
2839                        .unwrap();
2840                    assert_eq!(
2841                        written, stream,
2842                        "the encoder must produce the frame the decoder accepted for status {code:#x}"
2843                    );
2844
2845                    let mut written = Vec::new();
2846                    status_datagram_header(Some(status)).encode(&mut written);
2847                    assert_eq!(
2848                        written, datagram,
2849                        "the encoder must produce the datagram the decoder accepted for status {code:#x}"
2850                    );
2851                }
2852                None => {
2853                    for (site, result) in [
2854                        ("subgroup read_object", read.map(|_| ())),
2855                        ("subgroup read_object_meta", meta.map(|_| ())),
2856                        ("status datagram", decoded.map(|_| ())),
2857                    ] {
2858                        match result {
2859                            Ok(()) => panic!(
2860                                "{site} accepted status {code:#x}, which the draft does not assign"
2861                            ),
2862                            Err(error) => assert!(
2863                                matches!(error, CodecError::InvalidField),
2864                                "{site} refused status {code:#x} with {error:?}, not InvalidField"
2865                            ),
2866                        }
2867                    }
2868                }
2869            }
2870        }
2871    }
2872
2873    // ── The registry's payload column ───────────────────────
2874
2875    /// Draft-21 Section 16.9, Table 16, "Payload" column: Normal "Yes", End of
2876    /// Group "No", End of Track "No".
2877    ///
2878    /// Restated here rather than read from [`ObjectStatus::payload_permission`]
2879    /// so the gate holds its own copy of the registry. A codec that changed a
2880    /// row would still agree with itself; only a second copy notices.
2881    const PAYLOAD_COLUMN: &[(ObjectStatus, bool)] = &[
2882        (ObjectStatus::Normal, true),
2883        (ObjectStatus::EndOfGroup, false),
2884        (ObjectStatus::EndOfTrack, false),
2885    ];
2886
2887    /// Which statuses may carry a payload is decided by the registry, not by a
2888    /// payload length.
2889    ///
2890    /// Draft-18 states one blanket rule — an Object with any status other than
2891    /// Normal has an empty payload — so under it the answer can be read off the
2892    /// status number, or equivalently off a zero payload length. Draft-21
2893    /// Section 11.1.2 replaces the blanket rule with "an Object MUST have an
2894    /// empty payload unless its Object Status value is registered as permitting
2895    /// a payload", the permission being a column of the Object Status registry
2896    /// in Section 16.9. The three rows assigned today give the same answers
2897    /// draft-18's rule gives; what this gate observes is that the answers come
2898    /// from the rows.
2899    ///
2900    /// Each row is driven both ways. A zero-length object with that status must
2901    /// encode and read back — Normal included, since it permits a payload
2902    /// without requiring one and so has to stay expressible with none. An
2903    /// object handed a payload under that status must be accepted exactly when
2904    /// the row permits one, and otherwise refused with nothing written.
2905    ///
2906    /// Fails if `write_object` lets the payload length decide without the
2907    /// registry, if End of Group moves into the permitting column, or if the
2908    /// status field is written for the statuses that forbid a payload instead
2909    /// of for the objects whose payload length is zero; the draft frames the
2910    /// status on payload length alone, so a zero-length Normal object needs it.
2911    #[test]
2912    fn the_registry_decides_which_statuses_may_carry_a_payload() {
2913        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2914        assert_eq!(
2915            PAYLOAD_COLUMN.len(),
2916            ObjectStatus::ALL.len(),
2917            "every assigned status needs a row in the payload column"
2918        );
2919
2920        for &(status, permitted) in PAYLOAD_COLUMN {
2921            assert!(ObjectStatus::ALL.contains(&status), "{status:?} is not an assigned status");
2922
2923            // A zero-length object is legal under every row, and is the only
2924            // framing that states a status on a subgroup stream.
2925            let mut empty = Vec::new();
2926            SubgroupObjectReader::new(&header)
2927                .write_object(&status_object(Some(status)), &mut empty)
2928                .unwrap_or_else(|e| panic!("write_object refused a zero-length {status:?}: {e:?}"));
2929            let object = SubgroupObjectReader::new(&header)
2930                .read_object(&mut &empty[..])
2931                .unwrap_or_else(|e| panic!("read_object refused a zero-length {status:?}: {e:?}"));
2932            assert_eq!(object.status(), status, "zero-length {status:?} lost its status");
2933            assert!(object.payload.is_empty(), "zero-length {status:?} gained a payload");
2934            assert_eq!(
2935                object.permits_payload(),
2936                permitted,
2937                "decoded {status:?} reports the wrong payload permission"
2938            );
2939
2940            // A status datagram is the one carrier where the registry is not
2941            // the last word. Section 11.2.1 puts the status field in the
2942            // payload's place — "When set to 1, the Object Status field is
2943            // present and there is no Object Payload" — so no status makes
2944            // trailing bytes part of such a datagram, Normal included, and
2945            // `permitted` is not the expected answer here.
2946            let datagram = DatagramHeader::decode(&mut &status_datagram(status.as_u64())[..])
2947                .unwrap_or_else(|e| panic!("datagram decode refused {status:?}: {e:?}"));
2948            assert!(
2949                !datagram.permits_payload(),
2950                "a status datagram has no Object Payload field, so {status:?} permits no bytes"
2951            );
2952            assert_eq!(
2953                datagram.status().permits_payload(),
2954                permitted,
2955                "decoded {status:?} datagram reports the wrong registry row"
2956            );
2957            let mut whole = status_datagram(status.as_u64());
2958            whole.extend_from_slice(&hex("deadbeef"));
2959            let trailing = DatagramHeader::decode_object(&mut &whole[..]);
2960            assert!(
2961                matches!(trailing, Err(CodecError::PayloadNotPermitted { .. })),
2962                "trailing bytes on a {status:?} status datagram must be refused; got {trailing:?}"
2963            );
2964
2965            // The same status, handed a payload the wire cannot frame beside it.
2966            let mut written = Vec::new();
2967            let result = SubgroupObjectReader::new(&header)
2968                .write_object(&payload_object(Some(status), hex("deadbeef")), &mut written);
2969
2970            if permitted {
2971                result.unwrap_or_else(|e| {
2972                    panic!(
2973                        "write_object refused a payload under {status:?}, \
2974                         which the registry permits: {e:?}"
2975                    )
2976                });
2977                let object = SubgroupObjectReader::new(&header)
2978                    .read_object(&mut &written[..])
2979                    .unwrap_or_else(|e| {
2980                        panic!("read_object refused its own output for {status:?}: {e:?}")
2981                    });
2982                assert_eq!(object.payload, hex("deadbeef"), "{status:?} lost its payload");
2983                assert_eq!(object.status(), status, "{status:?} came back as another status");
2984            } else {
2985                assert!(
2986                    matches!(result, Err(CodecError::PayloadNotPermitted { .. })),
2987                    "write_object must refuse a payload under {status:?}, \
2988                     which the registry forbids one; got {result:?}"
2989                );
2990                assert!(written.is_empty(), "a refused {status:?} object still wrote {written:?}");
2991            }
2992        }
2993
2994        // A datagram without the STATUS bit is all payload, and the status its
2995        // framing leaves out is the one row that permits a payload.
2996        let plain = DatagramHeader::decode(&mut &[0x00u8, 0x01, 0x00, 0x00, 0x80][..])
2997            .expect("a datagram with no status field must decode");
2998        assert_eq!(plain.status(), ObjectStatus::Normal);
2999        assert!(plain.permits_payload(), "a payload-carrying datagram must be permitted one");
3000    }
3001}