Skip to main content

moqtap_codec/draft16/
data_stream.rs

1//! Draft-16 data stream header encoding and decoding.
2//!
3//! Draft-16 subgroup type-byte flag layout:
4//! - `& 0x01`: extensions present on objects
5//! - `& 0x06`: SUBGROUP_ID_MODE. Section 10.4.2: "The SUBGROUP_ID_MODE field
6//!   (bits 1-2, mask 0x06) is a two-bit field that determines the encoding of
7//!   the Subgroup ID. To extract this value, perform a bitwise AND with mask
8//!   0x06 and right-shift by 1 bit". Mode 0 puts no Subgroup ID on the wire
9//!   and it is zero, mode 1 takes it from the first object, mode 2 reads it as
10//!   a field after the Group ID, and mode 3 is reserved
11//! - `& 0x08`: end-of-group marker
12//! - `& 0x20`: no publisher_priority (0x30+ types)
13//!
14//! Because the mode is one field and not two independent bits, the three
15//! carriers are mutually exclusive: a Type setting both `0x02` and `0x04` names
16//! the reserved mode, not two carriers at once. This module described those bits
17//! separately and read them separately, which let a single Type answer to two
18//! carriers.
19//!
20//! Draft-15 spells the same three carriers out of the same two bits, as a pair
21//! of table columns rather than a named field, and the two drafts end up
22//! admitting the same twenty-four Types — draft-16 by excluding the reserved
23//! mode from the ranges `0x10`-`0x1F` and `0x30`-`0x3F`, draft-15 by listing
24//! them.
25//!
26//! Draft-16 datagram type-byte flag layout:
27//! - `0x01`: extensions present (byte-length-prefixed blob)
28//! - `0x02`: end-of-group
29//! - `0x04`: no object_id (object_id = 0 implied)
30//! - `0x08`: default priority (priority omitted, inherited)
31//! - `0x20`: status datagram (carries object_status instead of payload)
32//!
33//! Draft-16 fetch objects are framed differently again: a per-object
34//! Serialization Flags varint decides which of Group ID, Subgroup ID, Object ID,
35//! Priority and Extensions are on the wire, and a field left off is taken from
36//! the object before it. See [`FetchObjectHeader`] for the flag layout and
37//! [`FetchObjectReader`] for the inheritance. A fetch object carries no Object
38//! Status at all — draft-16 Section 10.2.1.1 puts that field on subscription
39//! deliveries only.
40//!
41//! Extension headers in draft-16 are byte-length-prefixed opaque blobs
42//! (not count-prefixed as in draft-14).
43
44use super::types::ObjectStatus;
45use crate::error::CodecError;
46use crate::varint::VarInt;
47use bytes::{Buf, BufMut};
48
49/// Advance `buf` past `len` bytes without copying them.
50fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
51    let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
52    if buf.remaining() < len {
53        return Err(CodecError::UnexpectedEnd);
54    }
55    buf.advance(len);
56    Ok(())
57}
58
59/// Turn a wire Object Status code into the status draft-16 gives it, refusing
60/// any code the draft does not assign.
61///
62/// Draft-16 Section 10.2.1.1 lists the codes an object may carry and says any
63/// other value SHOULD be treated as a protocol error and the session closed
64/// with a PROTOCOL_VIOLATION. Every place this module reads a status runs the
65/// wire code through here. Draft-16 is where the set narrowed to three: 0x1,
66/// which drafts 07-15 assign to Object Does Not Exist, is refused here.
67///
68/// [`SubgroupObject::object_status`] and [`DatagramHeader::object_status`]
69/// then store the [`ObjectStatus`] this returns rather than the raw code, so
70/// the refusal is not something a future decode site can forget: those fields
71/// cannot hold an unassigned value at all, in either direction, and the encode
72/// paths need no check of their own.
73/// [`SubgroupObjectMeta::status`] deliberately keeps the raw code — it is a
74/// decode-only view that never feeds an encoder — but it is filtered through
75/// here too, so the two readers agree byte for byte on what parses.
76fn decoded_status(code: u64) -> Result<ObjectStatus, CodecError> {
77    ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)
78}
79
80/// Whether an Object at `status` is allowed to carry `extensions_len` bytes of
81/// extension headers.
82///
83/// Draft-16 Section 10.2.1.2: "Any Object with status Normal can have extension
84/// headers", with a reference to Section 2.5 inside the sentence, and "If an
85/// endpoint receives extension headers on Objects with status that is not
86/// Normal, it MUST close the session with a PROTOCOL_VIOLATION."
87///
88/// Draft-16 is the draft where the status set narrowed to Normal, End of Group
89/// and End of Track, so the rule reaches two codes rather than the single
90/// "Object Does Not Exist" earlier drafts name, draft-15 Section 10.2.1.1 among
91/// them. It reaches every carrier that can announce a status: an Object on a
92/// subgroup stream and a status datagram. An Object on a fetch stream carries
93/// no status field on this draft — Section 10.2.1.1 puts the field on
94/// subscription deliveries only — so it is the one carrier that cannot break
95/// the rule.
96///
97/// So this is `false` for exactly one shape: a non-empty extension block on an
98/// Object whose status is not Normal. An Object with no extensions is fine at
99/// any status, and an Object at Normal may carry any extensions.
100///
101/// `status` is taken as a raw code so the two subgroup readers can share this:
102/// one resolves the status into [`ObjectStatus`] and the other keeps the wire
103/// code. `None` means the Object carried a payload, which is Normal by
104/// definition and always permitted.
105///
106/// # Why the readers do not apply this themselves
107///
108/// A deliberate contrast with the payload rule beside it. A status next to a
109/// payload has no encoding — the status field and the payload occupy the same
110/// position on the wire — so a writer refuses that pair as unrepresentable.
111/// Extensions next to a status encode perfectly well: the block sits between
112/// the extensions length and the status field and reads back byte for byte. The
113/// frame is well formed and merely non-conforming, which is a judgement about
114/// what a peer may send, not about what the bytes mean.
115///
116/// A decoder that refused it could not report the violation, and a writer that
117/// refused it could not reproduce a capture containing one — including the
118/// committed `subgroup-extensions-status-object` vector, which is exactly this
119/// frame. The rule addresses an endpoint *receiving* such an Object, so the
120/// endpoint is where it is enforced. This predicate is what it asks.
121fn extensions_permitted_at(status: Option<u64>, extensions_len: u64) -> bool {
122    match status {
123        None => true,
124        Some(code) => extensions_len == 0 || code == ObjectStatus::Normal.as_u64(),
125    }
126}
127
128// ── Subgroup and datagram Type validation ─────────────────────
129//
130// Both Types are read as a single byte rather than as a varint. Every Type
131// draft-16 assigns to either is below 0x40, which is exactly the one-byte range
132// of the varint encoding this draft uses, and both forms independently forbid
133// bits 6 and 7 — so a first byte that begins a longer varint is a Type the
134// draft rules out anyway.
135//
136// Reading a full varint and narrowing it to `u8` was the defect: a Type of
137// 0x110, spelled as the two-byte varint 0x41 0x10, truncated to 0x10 and was
138// accepted as a valid subgroup header. An out-of-range Type aliased onto a
139// valid one instead of being refused, and the stream behind it was parsed under
140// framing its sender never asked for.
141
142/// Bit 4, which every subgroup header Type sets.
143const SUBGROUP_BASE_BIT: u8 = 0x10;
144/// Bits 6 and 7, which the subgroup header's 0b00X1XXXX form leaves clear.
145///
146/// Narrower than the equivalent on draft-19, whose form is 0b0XX1XXXX and which
147/// therefore admits 0x50..0x5F and 0x70..0x7F as well. Draft-16 Section 10.4.2
148/// names only "the ranges 0x10..0x1F and 0x30..0x3F".
149const SUBGROUP_FORM_FORBIDDEN_BITS: u8 = 0xC0;
150/// Bits 1-2, the SUBGROUP_ID_MODE field.
151const SUBGROUP_ID_MODE_MASK: u8 = 0x06;
152/// The SUBGROUP_ID_MODE value draft-16 reserves, once the mask is applied and
153/// the field shifted down.
154const SUBGROUP_ID_MODE_RESERVED: u8 = 0b11;
155
156/// Refuse a subgroup header Type value draft-16 Section 10.4.2 lists as invalid.
157///
158/// The section gives two lists and says of both that an endpoint receiving a
159/// stream header with such a Type MUST close the session with a
160/// PROTOCOL_VIOLATION:
161///
162///   - "Type values with SUBGROUP_ID_MODE set to 0b11: 0x16, 0x17, 0x1E, 0x1F,
163///     0x36, 0x37, 0x3E, 0x3F. This mode is reserved for future use."
164///   - "Type values that do not match the form 0b00X1XXXX (i.e., Type values
165///     outside the ranges 0x10..0x1F and 0x30..0x3F, or values where bit 4 is
166///     not set)."
167///
168/// The reserved mode is worth separating from a merely unassigned code point,
169/// because it is not decodable rather than merely unknown. The other three modes
170/// each say whether a Subgroup ID field follows the Group ID; 0b11 says nothing,
171/// so a decoder has to guess, and a wrong guess shifts every later field by the
172/// width of that varint. This module read those eight Types as though an
173/// explicit Subgroup ID were present, which turned a header the draft says to
174/// reject into objects with plausible, wrong contents.
175///
176/// Which of the two lists a Type failed is what `stream_type_error` reports,
177/// and the answer is not the same error: one is a Type no table assigns and the
178/// other is a Type this draft assigns a form to and then forbids.
179fn validate_subgroup_type(raw: u64) -> Result<(), CodecError> {
180    if subgroup_type_is_valid(raw) {
181        Ok(())
182    } else {
183        Err(stream_type_error(raw))
184    }
185}
186
187/// The unidirectional stream Type draft-16 Section 10.4.4 gives a fetch stream.
188const FETCH_STREAM_TYPE: u64 = 0x05;
189
190/// Refuse a Type field spelled in more than one byte, before anything narrows
191/// it to a byte.
192///
193/// Returns `Ok(None)` when the next Type is a single byte and the caller should
194/// read it itself, `Ok(Some(err))` when it is wider and `refusal` has named the
195/// failure, and `Err` only when the buffer does not hold the whole field yet.
196///
197/// Every Type draft-16 assigns is below 0x40 and so occupies one byte under
198/// this draft's variable-length integer encoding. A wider spelling is therefore
199/// one of two things, and neither may be read as a header: a Type this draft
200/// does not assign, or a non-minimal spelling of one it does. The second is the
201/// dangerous one — narrowing a two-byte 0x4001 to its low octet turns it into
202/// the assigned Type 0x01, so a peer could name any Type it liked and have it
203/// parsed as another.
204///
205/// The full varint is decoded before `refusal` sees it, so the refusal reports
206/// the number the field actually carried rather than its first byte.
207fn wide_type_refusal(
208    buf: &mut impl Buf,
209    refusal: fn(u64) -> CodecError,
210) -> Result<Option<CodecError>, CodecError> {
211    if !buf.has_remaining() {
212        return Err(CodecError::UnexpectedEnd);
213    }
214    // Under the draft-16 encoding the top two bits of the first byte give the
215    // field's length, so a first byte below 0x40 is the whole of it.
216    if buf.chunk()[0] < 0x40 {
217        return Ok(None);
218    }
219    let raw = VarInt::decode(buf)?.into_inner();
220    Ok(Some(refusal(raw)))
221}
222
223/// The refusal a datagram Type deserves, as a plain function so
224/// `wide_type_refusal` can take it.
225fn datagram_type_refusal(raw: u64) -> CodecError {
226    match validate_datagram_type(raw) {
227        Ok(()) => CodecError::InvalidField,
228        Err(e) => e,
229    }
230}
231
232/// Whether `raw` is a subgroup Type draft-16 admits: inside the form, and not
233/// the reserved SUBGROUP_ID_MODE.
234fn subgroup_type_is_valid(raw: u64) -> bool {
235    raw <= 0xFF && {
236        let t = raw as u8;
237        t & SUBGROUP_FORM_FORBIDDEN_BITS == 0
238            && t & SUBGROUP_BASE_BIT != 0
239            && (t & SUBGROUP_ID_MODE_MASK) >> 1 != SUBGROUP_ID_MODE_RESERVED
240    }
241}
242
243/// Whether `raw` sits inside the subgroup form but names the reserved
244/// SUBGROUP_ID_MODE — the first of Section 10.4.2's two lists.
245fn subgroup_type_is_reserved_mode(raw: u64) -> bool {
246    raw <= 0xFF && {
247        let t = raw as u8;
248        t & SUBGROUP_FORM_FORBIDDEN_BITS == 0
249            && t & SUBGROUP_BASE_BIT != 0
250            && (t & SUBGROUP_ID_MODE_MASK) >> 1 == SUBGROUP_ID_MODE_RESERVED
251    }
252}
253
254/// Which failure a leading unidirectional stream Type that is not the one a
255/// reader wants is.
256///
257/// Draft-16 states two rules about such a Type and answers both with a close,
258/// and telling them apart is the whole job of this function.
259///
260/// Section 10 is about the table: "An endpoint that receives an unknown stream
261/// or datagram type MUST close the session." A Type the table does not assign
262/// is [`CodecError::UnknownStreamType`]. The table has two entries — 0x05 for
263/// FETCH_HEADER and the subgroup form — because draft-16 carries its control
264/// messages on a bidirectional stream and defines no padding stream, so
265/// FETCH_HEADER is the only assigned value a subgroup reader can be handed.
266///
267/// Section 10.4.2 is about the subgroup form specifically, and the eight Types
268/// inside it that name the reserved SUBGROUP_ID_MODE. Those are not unknown —
269/// the form is assigned and the draft lists the values outright — but they are
270/// unreadable, and they are [`CodecError::InvalidStreamTypeValue`].
271///
272/// A fetch stream at the subgroup reader is neither. The value is one this
273/// draft defines, the disagreement is with the reader that was called, and the
274/// session survives it.
275fn stream_type_error(raw: u64) -> CodecError {
276    if raw == FETCH_STREAM_TYPE || subgroup_type_is_valid(raw) {
277        CodecError::InvalidField
278    } else if subgroup_type_is_reserved_mode(raw) {
279        CodecError::InvalidStreamTypeValue {
280            raw,
281            detail: "its SUBGROUP_ID_MODE is 0b11, which this draft reserves",
282        }
283    } else {
284        CodecError::UnknownStreamType(raw)
285    }
286}
287
288/// The END_OF_GROUP bit of a datagram Type.
289const DATAGRAM_END_OF_GROUP_BIT: u8 = 0x02;
290/// The STATUS bit of a datagram Type.
291const DATAGRAM_STATUS_BIT: u8 = 0x20;
292/// Bits 4, 6 and 7, which the datagram's 0b00X0XXXX form leaves clear.
293const DATAGRAM_FORM_FORBIDDEN_BITS: u8 = 0xD0;
294
295/// Refuse a datagram Type value draft-16 Section 10.3.1 lists as invalid.
296///
297/// The section gives two lists and says of both that an endpoint receiving a
298/// datagram with such a Type MUST close the session with a PROTOCOL_VIOLATION:
299///
300///   - "Type values with both the STATUS bit (0x20) and END_OF_GROUP bit (0x02)
301///     set: 0x22, 0x23, 0x26, 0x27, 0x2A, 0x2B, 0x2E, 0x2F. An object status
302///     message cannot signal end of group."
303///   - "Type values that do not match the form 0b00X0XXXX (i.e., Type values
304///     outside the ranges 0x00..0x0F and 0x20..0x2F)."
305///
306/// That leaves 24 of the 256 byte values valid. Without this check every other
307/// one is accepted, and bit 4 is the bit that separates a datagram Type from a
308/// subgroup stream header Type — so an unchecked datagram Type could name a
309/// stream header and be parsed as a datagram anyway.
310///
311/// The two lists are not the same failure. A Type outside the form is one no
312/// table assigns, and so is [`CodecError::UnknownDatagramType`]; a Type inside
313/// the form setting STATUS and END_OF_GROUP together is one this draft names
314/// and forbids, and so is [`CodecError::InvalidDatagramTypeValue`].
315fn validate_datagram_type(raw: u64) -> Result<(), CodecError> {
316    if datagram_type_is_valid(raw) {
317        Ok(())
318    } else if datagram_type_is_status_end_of_group(raw) {
319        Err(CodecError::InvalidDatagramTypeValue {
320            raw,
321            detail: "it sets both the STATUS bit and the END_OF_GROUP bit",
322        })
323    } else {
324        Err(CodecError::UnknownDatagramType(raw))
325    }
326}
327
328/// Whether `raw` is a datagram Type draft-16 admits.
329fn datagram_type_is_valid(raw: u64) -> bool {
330    raw <= 0xFF && {
331        let t = raw as u8;
332        t & DATAGRAM_FORM_FORBIDDEN_BITS == 0
333            && !(t & DATAGRAM_STATUS_BIT != 0 && t & DATAGRAM_END_OF_GROUP_BIT != 0)
334    }
335}
336
337/// Whether `raw` sits inside the datagram form but sets STATUS and END_OF_GROUP
338/// together — the first of Section 10.3.1's two lists.
339fn datagram_type_is_status_end_of_group(raw: u64) -> bool {
340    raw <= 0xFF && {
341        let t = raw as u8;
342        t & DATAGRAM_FORM_FORBIDDEN_BITS == 0
343            && t & DATAGRAM_STATUS_BIT != 0
344            && t & DATAGRAM_END_OF_GROUP_BIT != 0
345    }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct SubgroupHeader {
350    pub header_type: u8,
351    pub track_alias: VarInt,
352    pub group_id: VarInt,
353    pub subgroup_id: VarInt,
354    pub publisher_priority: Option<u8>,
355}
356
357impl SubgroupHeader {
358    pub fn has_extensions(&self) -> bool {
359        self.header_type & 0x01 != 0
360    }
361
362    /// Whether SUBGROUP_ID_MODE is 1: the Subgroup ID is the first object's
363    /// Object ID and is not transmitted on the wire.
364    ///
365    /// Reads the two bits as the one field Section 10.4.2 defines, rather than
366    /// as the single bit `0x02`. The single-bit reading answered `true` for a
367    /// Type naming the reserved mode 3, where `0x02` and `0x04` are both set —
368    /// so this predicate and [`Self::has_explicit_subgroup_id`] answered `true`
369    /// together, for a state one two-bit field cannot be in.
370    pub fn subgroup_id_from_first_object(&self) -> bool {
371        (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1 == 1
372    }
373
374    /// Whether SUBGROUP_ID_MODE is 2: an explicit Subgroup ID field follows the
375    /// Group ID.
376    ///
377    /// [`Self::decode`] refuses the reserved mode before reading any field, so
378    /// on a decoded header this agrees with the single-bit `0x04` reading it
379    /// replaces. The two differ only on a header built by hand, which is the
380    /// only way to hold a reserved-mode Type at all.
381    pub fn has_explicit_subgroup_id(&self) -> bool {
382        (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1 == 2
383    }
384
385    pub fn has_end_of_group(&self) -> bool {
386        self.header_type & 0x08 != 0
387    }
388
389    pub fn has_priority(&self) -> bool {
390        self.header_type & 0x20 == 0
391    }
392
393    /// Serialize the header exactly as its Type byte describes it.
394    ///
395    /// Infallible, and so willing to write a Type draft-16 Section 10.4.2 tells
396    /// an endpoint to reject — including a reserved-mode Type this module's own
397    /// [`Self::decode`] refuses to read back. Prefer [`Self::encode_checked`],
398    /// which refuses those Types instead.
399    ///
400    /// A reserved-mode Type gets no Subgroup ID field, because mode 3 defines
401    /// none: the draft says what the other three modes put on the wire and
402    /// nothing about this one. Writing the field there was an artifact of
403    /// reading `0x04` on its own, and it disagreed with every neighbouring
404    /// draft — 15, 17, 18 and 19 all leave it off.
405    ///
406    /// Every field is driven by the Type byte, because that is the only thing
407    /// [`Self::decode`] and the peer have to go on — the Priority byte
408    /// included. Driving that one off `publisher_priority` being `Some`
409    /// instead desyncs the stream: a Type with DEFAULT_PRIORITY (`0x20`) set
410    /// beside a `Some` writes a stray byte the reader takes for the first
411    /// Object's Object ID Delta, and a Type with the bit clear beside a `None`
412    /// leaves the reader taking that Delta for the priority. Either way every
413    /// later field shifts — which is precisely the disagreement
414    /// [`Self::encode_checked`]'s doc comment says this pair must not have.
415    ///
416    /// A `None` under a Type whose bit is clear therefore writes 128 rather
417    /// than dropping the byte. Draft-16 Section 11.1.1.1: "A subscription has
418    /// Publisher Priorty 128 if this extension is omitted", so 128 is this
419    /// draft's own name for an unstated priority and not an invented filler.
420    /// Drafts 17-21 write the same value in the same place. Use
421    /// [`Self::encode_checked`] to be told about the disagreement rather than
422    /// having it resolved silently.
423    pub fn encode(&self, buf: &mut impl BufMut) {
424        VarInt::from_usize(self.header_type as usize).encode(buf);
425        self.track_alias.encode(buf);
426        self.group_id.encode(buf);
427        if self.has_explicit_subgroup_id() {
428            self.subgroup_id.encode(buf);
429        }
430        if self.has_priority() {
431            buf.put_u8(self.publisher_priority.unwrap_or(128));
432        }
433    }
434
435    /// Serialize the header, refusing a Type value draft-16 forbids.
436    ///
437    /// Errors with [`CodecError::InvalidField`] for exactly the Types Section
438    /// 10.4.2 lists as invalid — the same set [`Self::decode`] refuses — before
439    /// any byte is written, so a refused header leaves `buf` untouched.
440    ///
441    /// The check belongs on this side as well because the two halves would
442    /// otherwise disagree about which streams exist: a reserved-mode header
443    /// written by [`Self::encode`] cannot be read back by [`Self::decode`], and
444    /// a codec used to rewrite captured traffic would emit a stream it could not
445    /// then parse.
446    ///
447    /// That argument covers the Priority byte too. `publisher_priority`
448    /// disagreeing with the Type byte's DEFAULT_PRIORITY bit (`0x20`) is the
449    /// same failure one field along: a header that does not mean what it says,
450    /// whose bytes the peer reads under framing its writer never asked for.
451    /// [`Self::encode`] does not desync the stream over it — the byte follows
452    /// the Type byte — but resolving the disagreement is not the same as being
453    /// told about it, and the caller who set the wrong half is the only one who
454    /// can fix it. Same check, same reason, as draft-15 Section 10.4.2's
455    /// encoder.
456    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
457        validate_subgroup_type(self.header_type as u64)?;
458        if self.has_priority() != self.publisher_priority.is_some() {
459            return Err(CodecError::InvalidField);
460        }
461        self.encode(buf);
462        Ok(())
463    }
464
465    /// Decode a subgroup header, Type field included.
466    ///
467    /// A Type spelled in more than one byte is refused before it is narrowed,
468    /// by `wide_type_refusal`. Every Type draft-16 assigns fits a single
469    /// byte, so a wider spelling is either a Type this draft does not have or a
470    /// non-minimal spelling of one it does, and both have to be refused rather
471    /// than truncated into a Type that happens to be valid.
472    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
473        if let Some(err) = wide_type_refusal(buf, stream_type_error)? {
474            return Err(err);
475        }
476        let header_type = buf.get_u8();
477        validate_subgroup_type(header_type as u64)?;
478        let track_alias = VarInt::decode(buf)?;
479        let group_id = VarInt::decode(buf)?;
480        // `validate_subgroup_type` has already refused the reserved mode, so
481        // the field is read for mode 2 and for nothing else. Reading the mask
482        // rather than the `0x04` bit makes that independent of the check above
483        // instead of contingent on it.
484        let subgroup_id = if (header_type & SUBGROUP_ID_MODE_MASK) >> 1 == 2 {
485            VarInt::decode(buf)?
486        } else {
487            VarInt::from_usize(0)
488        };
489        let publisher_priority = if header_type & 0x20 == 0 {
490            if buf.remaining() < 1 {
491                return Err(CodecError::UnexpectedEnd);
492            }
493            Some(buf.get_u8())
494        } else {
495            None
496        };
497        Ok(Self { header_type, track_alias, group_id, subgroup_id, publisher_priority })
498    }
499}
500
501/// One object within a draft-16 subgroup stream with its Object ID
502/// already resolved from the delta encoding. See
503/// [`SubgroupObjectReader`] for stateful encode/decode.
504#[derive(Debug, Clone, PartialEq, Eq)]
505pub struct SubgroupObject {
506    pub object_id: VarInt,
507    /// Raw extension-header bytes, excluding the byte-length prefix that
508    /// precedes them on the wire. Empty when the stream header does not
509    /// set the extensions-present bit, or when the block is present but
510    /// zero-length. Opaque: [`SubgroupObjectReader::write_object`] re-emits
511    /// the prefix and these bytes verbatim.
512    pub extension_headers: Vec<u8>,
513    pub payload_length: VarInt,
514    /// Object status; `Some` when `payload_length == 0`.
515    ///
516    /// The wire field is a varint, so it can carry any value up to 2^62-1;
517    /// draft-16 Section 10.2.1.1 assigns three of them and says a peer SHOULD
518    /// treat the rest as a protocol error. This field is typed to the assigned
519    /// set, so it refuses to hold the codes the draft leaves unassigned —
520    /// including 0x1, Object Does Not Exist, which drafts 07-15 assign and
521    /// draft-16 dropped. That makes the refusal a property of the struct
522    /// rather than of any one code path: an encoder cannot be handed a status
523    /// the draft does not define, and does not have to check.
524    ///
525    /// `None` on a zero-length object means the same thing as
526    /// [`ObjectStatus::Normal`] and encodes as it; the wire field is not
527    /// optional once `payload_length` is zero.
528    pub object_status: Option<ObjectStatus>,
529    pub payload: Vec<u8>,
530}
531
532impl SubgroupObject {
533    /// The status this Object resolves to.
534    ///
535    /// The wire carries a status field only on a zero-length Object, so an
536    /// Object holding bytes is [`ObjectStatus::Normal`] whatever
537    /// [`Self::object_status`] says — draft-16 Section 10.2.1.1: "This status is
538    /// implicit for any non-zero length object."
539    pub fn status(&self) -> ObjectStatus {
540        if self.payload_length.into_inner() == 0 {
541            self.object_status.unwrap_or(ObjectStatus::Normal)
542        } else {
543            ObjectStatus::Normal
544        }
545    }
546
547    /// Whether this Object's status is allowed to carry the extension headers
548    /// it has.
549    ///
550    /// `false` for exactly one shape: a non-empty extension block on an Object
551    /// whose status is not [`ObjectStatus::Normal`]. See
552    /// `extensions_permitted_at` for the rule and for why neither
553    /// [`SubgroupObjectReader::read_object`] nor
554    /// [`SubgroupObjectReader::write_object`] applies it — this is the codec's
555    /// way of reporting the violation to the endpoint that must act on it,
556    /// rather than refusing bytes that are well formed.
557    pub fn extensions_permitted(&self) -> bool {
558        extensions_permitted_at(
559            self.object_status.map(|s| s.as_u64()),
560            self.extension_headers.len() as u64,
561        )
562    }
563}
564
565/// Whether an Object carrying a given status is allowed a non-empty payload.
566///
567/// Draft-16 Section 10.2.1.1 states the rule in one sentence — "Any object with
568/// a status code other than zero MUST have an empty payload" — and the same
569/// section makes Normal (0x0) "implicit for any non-zero length object". So the
570/// permission follows from the status code alone, with no payload length in
571/// hand, which is what makes it worth naming: a caller that has only an
572/// object's framing can ask whether the bytes it is about to forward are
573/// allowed there at all.
574#[derive(Debug, Clone, Copy, PartialEq, Eq)]
575pub enum PayloadPermission {
576    /// The status permits a payload but does not require one: a zero-length
577    /// object with such a status is well formed, and draft-16's encodings can
578    /// spell it.
579    Permitted,
580    /// An object with such a status has an empty payload, and one carrying
581    /// bytes is malformed.
582    Forbidden,
583}
584
585impl PayloadPermission {
586    /// `true` for [`PayloadPermission::Permitted`].
587    pub fn permits(self) -> bool {
588        matches!(self, PayloadPermission::Permitted)
589    }
590}
591
592/// The framing of one draft-16 subgroup object, without its payload.
593///
594/// Produced by [`SubgroupObjectReader::read_object_meta`] for callers that
595/// forward an object's bytes verbatim and never inspect the payload.
596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597pub struct SubgroupObjectMeta {
598    /// Resolved absolute Object ID.
599    pub object_id: u64,
600    /// Byte length of the extension-header block's contents, excluding its
601    /// length prefix.
602    pub extension_headers_len: u64,
603    /// Declared payload length. Zero when `status` is `Some`.
604    pub payload_length: u64,
605    /// Object status wire code, present only when the payload is empty.
606    ///
607    /// Kept as the raw code, unlike [`SubgroupObject::object_status`]: a meta
608    /// is produced by [`SubgroupObjectReader::read_object_meta`] and is never
609    /// an encode input, and a relay that reads a status on one draft may hand
610    /// it to a draft that numbers the same value differently. The code is
611    /// still one draft-16 assigns — `read_object_meta` refuses the others.
612    pub status: Option<u64>,
613    /// Total bytes this object occupies on the wire, prefix fields included.
614    pub wire_len: u64,
615}
616
617impl SubgroupObjectMeta {
618    /// Whether draft-16 allows this object a non-empty payload, or `None` when
619    /// the answer does not exist.
620    ///
621    /// Draft-16 Section 10.2.1.1 gives the rule for the three codes it assigns:
622    /// Normal (0x0) permits a payload, and "any object with a status code other
623    /// than zero MUST have an empty payload", which covers End of Group (0x3)
624    /// and End of Track (0x4). No status at all means the object carried bytes
625    /// — the status field is on the wire only when `payload_length` is zero —
626    /// and the same section makes Normal "implicit for any non-zero length
627    /// object", so that case answers [`PayloadPermission::Permitted`] as well.
628    ///
629    /// `None` is reserved for a `status` the draft does not assign. A meta read
630    /// off the wire cannot hold one — [`SubgroupObjectReader::read_object_meta`]
631    /// refuses those bytes — but the field is public and holds the raw code
632    /// precisely so a value carried over from another draft's numbering can sit
633    /// in it. Draft-16 states no payload rule for a code it never assigned, and
634    /// there is no safe direction to guess in: answering `Permitted` would wave
635    /// through a payload the peer may have to reject, and `Forbidden` would
636    /// discard one the peer may accept.
637    pub fn payload_permission(&self) -> Option<PayloadPermission> {
638        match self.status {
639            None => Some(PayloadPermission::Permitted),
640            Some(code) => match ObjectStatus::from_u64(code)? {
641                ObjectStatus::Normal => Some(PayloadPermission::Permitted),
642                ObjectStatus::EndOfGroup | ObjectStatus::EndOfTrack => {
643                    Some(PayloadPermission::Forbidden)
644                }
645            },
646        }
647    }
648
649    /// Whether this Object's status is allowed to carry the extension block it
650    /// declares.
651    ///
652    /// The meta form of [`SubgroupObject::extensions_permitted`], answering
653    /// from the declared block length rather than its contents, so a relay that
654    /// forwards bytes verbatim can report the violation without copying the
655    /// block.
656    pub fn extensions_permitted(&self) -> bool {
657        extensions_permitted_at(self.status, self.extension_headers_len)
658    }
659}
660
661/// Stateful reader/writer for draft-16 subgroup objects. Mirrors the
662/// draft-15 semantics (delta-encoded object IDs and header-typed
663/// extension presence).
664#[derive(Debug, Clone)]
665pub struct SubgroupObjectReader {
666    extensions_present: bool,
667    prev_object_id: Option<u64>,
668}
669
670impl SubgroupObjectReader {
671    pub fn new(header: &SubgroupHeader) -> Self {
672        Self { extensions_present: header.has_extensions(), prev_object_id: None }
673    }
674
675    pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<SubgroupObject, CodecError> {
676        let delta = VarInt::decode(buf)?.into_inner();
677        // The first object's field is its absolute Object ID; every later
678        // object encodes the gap to its predecessor, biased by one because
679        // two objects on a subgroup stream cannot share an ID.
680        let object_id_val = match self.prev_object_id {
681            None => delta,
682            Some(prev) => prev
683                .checked_add(1)
684                .and_then(|v| v.checked_add(delta))
685                .ok_or(CodecError::InvalidField)?,
686        };
687        self.prev_object_id = Some(object_id_val);
688        let object_id = VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?;
689
690        // Draft-16: extensions are a byte-length-prefixed opaque blob.
691        let extension_headers = if self.extensions_present {
692            let ext_len = VarInt::decode(buf)?.into_inner() as usize;
693            crate::types::read_bytes(buf, ext_len)?
694        } else {
695            Vec::new()
696        };
697
698        let payload_length_vi = VarInt::decode(buf)?;
699        let payload_length_val = payload_length_vi.into_inner() as usize;
700        let (object_status, payload) = if payload_length_val == 0 {
701            let status = decoded_status(VarInt::decode(buf)?.into_inner())?;
702            (Some(status), Vec::new())
703        } else {
704            let payload = crate::types::read_bytes(buf, payload_length_val)?;
705            (None, payload)
706        };
707        Ok(SubgroupObject {
708            object_id,
709            extension_headers,
710            payload_length: payload_length_vi,
711            object_status,
712            payload,
713        })
714    }
715
716    /// Decode the next object's framing without copying its payload.
717    ///
718    /// Consumes exactly the bytes [`Self::read_object`] consumes and leaves
719    /// the same delta state behind, so the two are interchangeable on a
720    /// given stream.
721    pub fn read_object_meta(
722        &mut self,
723        buf: &mut impl Buf,
724    ) -> Result<SubgroupObjectMeta, CodecError> {
725        let start = buf.remaining();
726        let delta = VarInt::decode(buf)?.into_inner();
727        let object_id_val = match self.prev_object_id {
728            None => delta,
729            Some(prev) => prev
730                .checked_add(1)
731                .and_then(|v| v.checked_add(delta))
732                .ok_or(CodecError::InvalidField)?,
733        };
734        self.prev_object_id = Some(object_id_val);
735        let object_id =
736            VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?.into_inner();
737
738        let extension_headers_len = if self.extensions_present {
739            let ext_len = VarInt::decode(buf)?.into_inner();
740            skip(buf, ext_len)?;
741            ext_len
742        } else {
743            0
744        };
745
746        let payload_length = VarInt::decode(buf)?.into_inner();
747        let status = if payload_length == 0 {
748            let code = VarInt::decode(buf)?.into_inner();
749            decoded_status(code)?;
750            Some(code)
751        } else {
752            skip(buf, payload_length)?;
753            None
754        };
755        Ok(SubgroupObjectMeta {
756            object_id,
757            extension_headers_len,
758            payload_length,
759            status,
760            wire_len: (start - buf.remaining()) as u64,
761        })
762    }
763
764    /// Serialize an object, producing the correct delta encoding.
765    ///
766    /// Errors with [`CodecError::InvalidField`] when `object.object_id` is
767    /// not strictly greater than the previously written object's ID, since
768    /// no valid delta exists for that case.
769    ///
770    /// A zero `payload_length` makes this a status object, and the status
771    /// field is then mandatory on the wire: an absent
772    /// [`SubgroupObject::object_status`] is written as
773    /// [`ObjectStatus::Normal`]. The code written is always one draft-16
774    /// assigns, because the field cannot hold any other, so the bytes this
775    /// produces are always bytes [`SubgroupObjectReader::read_object`] accepts.
776    ///
777    /// Errors with [`CodecError::InvalidField`] when `payload_length` is not
778    /// exactly `payload.len()`. The declared length is written ahead of the
779    /// payload, so a mismatch is a frame [`Self::read_object`] cannot parse
780    /// and one no caller could fix by appending bytes.
781    pub fn write_object(
782        &mut self,
783        object: &SubgroupObject,
784        buf: &mut impl BufMut,
785    ) -> Result<(), CodecError> {
786        // A declared length that disagrees with the payload framed under it
787        // produces bytes no reader can parse and no caller can repair: the
788        // length is already on the wire ahead of the payload. Checked before
789        // anything is written, so a refused object leaves `buf` untouched
790        // rather than half an object the next read would run into.
791        //
792        // Zero is not "an empty payload" here; it is the marker that puts a
793        // status code where the payload would go, so an object carrying bytes
794        // under it is asking for two framings at once.
795        let declared = object.payload_length.into_inner();
796        if declared != object.payload.len() as u64 {
797            return Err(CodecError::InvalidField);
798        }
799
800        // Extensions on a non-Normal status are NOT refused here, though
801        // Section 10.2.1.2 forbids them. The two rules differ in kind. A status
802        // beside a payload has no encoding at all — the status field and the
803        // payload occupy the same position — so writing one is impossible
804        // rather than merely wrong. Extensions beside a status encode perfectly
805        // well; the frame is well formed and non-conforming, which is a
806        // judgement about what a peer may send, not about what these bytes
807        // mean.
808        //
809        // Refusing it here would also make this writer unable to reproduce a
810        // frame the decoder must be able to read, including the committed
811        // `subgroup-extensions-status-object` vector.
812        // [`SubgroupObject::extensions_permitted`] reports the violation
813        // instead, and the endpoint acts on it.
814
815        let oid = object.object_id.into_inner();
816        let delta = match self.prev_object_id {
817            None => oid,
818            Some(prev) => oid
819                .checked_sub(prev)
820                .and_then(|v| v.checked_sub(1))
821                .ok_or(CodecError::InvalidField)?,
822        };
823        VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?.encode(buf);
824        if self.extensions_present {
825            let ext_len = object.extension_headers.len();
826            VarInt::from_usize(ext_len).encode(buf);
827            buf.put_slice(&object.extension_headers);
828        }
829        object.payload_length.encode(buf);
830        if object.payload_length.into_inner() == 0 {
831            // Zero-length means a status object, and the status is not
832            // optional on the wire; an unset one is Normal.
833            let status = object.object_status.unwrap_or(ObjectStatus::Normal);
834            VarInt::from_usize(status.as_u64() as usize).encode(buf);
835        } else {
836            buf.put_slice(&object.payload);
837        }
838        self.prev_object_id = Some(oid);
839        Ok(())
840    }
841}
842
843#[derive(Debug, Clone, PartialEq, Eq)]
844pub struct DatagramHeader {
845    pub datagram_type: u8,
846    pub track_alias: VarInt,
847    pub group_id: VarInt,
848    pub object_id: VarInt,
849    /// Publisher priority — `None` when the DEFAULT_PRIORITY flag is set and
850    /// the priority is inherited from the subscription's control message.
851    pub publisher_priority: Option<u8>,
852    /// Opaque extension-headers blob (only when flag 0x01 is set).
853    pub extension_headers: Vec<u8>,
854    /// Object status (only when the `0x20` status flag is set).
855    ///
856    /// The wire field is a varint and can carry any value up to 2^62-1;
857    /// draft-16 Section 10.2.1.1 assigns three of them and says a peer SHOULD
858    /// treat the rest as a protocol error. This field is typed to the assigned
859    /// set, so it cannot hold 0x1, 0x2 or anything from 0x5 up —
860    /// [`Self::encode`] therefore needs no check and cannot emit a datagram
861    /// that [`Self::decode`] would reject.
862    ///
863    /// `None` while the status flag is set encodes as
864    /// [`ObjectStatus::Normal`]: once the flag is set the field is present on
865    /// the wire, so there is nothing for `None` to mean but the default.
866    pub object_status: Option<ObjectStatus>,
867}
868
869impl DatagramHeader {
870    pub fn has_extensions(&self) -> bool {
871        self.datagram_type & 0x01 != 0
872    }
873
874    pub fn is_end_of_group(&self) -> bool {
875        self.datagram_type & 0x02 != 0
876    }
877
878    pub fn has_object_id(&self) -> bool {
879        self.datagram_type & 0x04 == 0
880    }
881
882    /// When set, publisher_priority is omitted on the wire and inherited
883    /// from the subscription / control-message context.
884    pub fn has_default_priority(&self) -> bool {
885        self.datagram_type & 0x08 != 0
886    }
887
888    pub fn is_status(&self) -> bool {
889        self.datagram_type & 0x20 != 0
890    }
891
892    /// Whether this datagram's status is allowed to carry the extension headers
893    /// it has.
894    ///
895    /// The same rule the subgroup form obeys — draft-16 Section 10.3.1 builds
896    /// the datagram's Extensions field out of the structure Section 10.2.1.2
897    /// defines, and that section is where the rule sits. See
898    /// [`SubgroupObject::extensions_permitted`] for why [`Self::decode`]
899    /// reports this instead of refusing it.
900    ///
901    /// The block is on the wire only when the type byte sets the EXTENSIONS
902    /// bit, so contents held here with the bit clear are not written and do not
903    /// count against the rule.
904    pub fn extensions_permitted(&self) -> bool {
905        extensions_permitted_at(
906            self.object_status.map(|s| s.as_u64()),
907            if self.has_extensions() { self.extension_headers.len() as u64 } else { 0 },
908        )
909    }
910
911    /// Encode the datagram header, refusing a status the framing cannot carry.
912    ///
913    /// A datagram states a status only when its type byte sets the STATUS bit
914    /// (0x20). With the bit clear there is no status field on the wire, so an
915    /// `object_status` of anything but [`ObjectStatus::Normal`] has nowhere to
916    /// go: [`Self::encode`] drops it, and the datagram parses back as an
917    /// ordinary payload object. An End of Group marker written that way does
918    /// not arrive late or malformed — it does not arrive at all, and the
919    /// receiver sees a normal object in its place.
920    ///
921    /// Draft-16 Section 10.3.1 puts the framing side plainly — "The STATUS bit
922    /// (0x20) indicates whether the datagram contains an Object Status or
923    /// Object Payload" — and Section 10.2.1.1 the conformance side: "Any object
924    /// with a status code other than zero MUST have an empty payload." Between
925    /// them there is no datagram that carries a non-zero status and a payload,
926    /// so the pair being refused here is not one this encoder merely declines
927    /// to spell.
928    ///
929    /// [`ObjectStatus::Normal`] with the bit clear is not that case and is
930    /// accepted. It is the status the encoding elides for every datagram that
931    /// carries a payload, so stating it asks for exactly the bytes leaving it
932    /// out asks for, and nothing is lost.
933    ///
934    /// Errors with [`CodecError::InvalidField`] on the lossy combination,
935    /// before any byte is written, so a refused header leaves `buf` untouched.
936    ///
937    /// Three further refusals, each of them a datagram this codec would
938    /// otherwise emit and then decline to read back:
939    ///
940    /// * A Type value Section 10.3.1 lists as invalid. See
941    ///   `validate_datagram_type`.
942    /// * An EXTENSIONS bit set over an empty extension block. Section 10.3.1:
943    ///   "If an endpoint receives a datagram with the EXTENSIONS bit set and an
944    ///   Extension Headers Length of 0, it MUST close the session with a
945    ///   PROTOCOL_VIOLATION." [`Self::encode`] writes the length prefix from the
946    ///   block's own length, so it spells exactly the datagram the peer must
947    ///   close the session over. This is a datagram rule only: on a subgroup
948    ///   stream the same section says "Objects with no extensions set Extension
949    ///   Headers Length to 0", because the Type byte is fixed for the whole
950    ///   stream and an Object with no extensions has no other way to say so.
951    /// * Extensions on an Object whose status is not Normal (Section 10.2.1.2).
952    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
953        validate_datagram_type(self.datagram_type as u64)?;
954        if !self.is_status() && matches!(self.object_status, Some(s) if s != ObjectStatus::Normal) {
955            return Err(CodecError::InvalidField);
956        }
957        if self.has_extensions() && self.extension_headers.is_empty() {
958            return Err(CodecError::InvalidField);
959        }
960        // Section 10.2.1.2. [`Self::decode`] reports this rather than refusing
961        // it, because the datagram it describes is well framed and a codec that
962        // could not read one could not reproduce a capture containing it.
963        // Writing one is the other direction and has no such excuse: a
964        // conforming peer answers with a PROTOCOL_VIOLATION, so emitting one
965        // costs the session and not merely the datagram.
966        if !self.extensions_permitted() {
967            return Err(CodecError::InvalidField);
968        }
969        self.encode(buf);
970        Ok(())
971    }
972
973    /// Encode the datagram header to `buf`.
974    ///
975    /// When the type byte sets the status flag the status field is written
976    /// unconditionally, defaulting to [`ObjectStatus::Normal`]. Omitting it
977    /// would truncate the datagram: [`Self::decode`] reads a status whenever
978    /// the flag is set, and answers [`CodecError::UnexpectedEnd`] when the
979    /// bytes stop first.
980    ///
981    /// The type byte is taken as the authority on framing, which is what makes
982    /// this infallible — and what makes it lossy when the struct disagrees with
983    /// itself. An `object_status` set while the type byte leaves the STATUS bit
984    /// clear is discarded here without a word. Prefer [`Self::encode_checked`],
985    /// which refuses that combination instead of resolving it.
986    pub fn encode(&self, buf: &mut impl BufMut) {
987        VarInt::from_usize(self.datagram_type as usize).encode(buf);
988        self.track_alias.encode(buf);
989        self.group_id.encode(buf);
990        if self.has_object_id() {
991            self.object_id.encode(buf);
992        }
993        if !self.has_default_priority() {
994            buf.put_u8(self.publisher_priority.unwrap_or(128));
995        }
996        if self.has_extensions() {
997            VarInt::from_usize(self.extension_headers.len()).encode(buf);
998            buf.put_slice(&self.extension_headers);
999        }
1000        if self.is_status() {
1001            let status = self.object_status.unwrap_or(ObjectStatus::Normal);
1002            VarInt::from_usize(status.as_u64() as usize).encode(buf);
1003        }
1004    }
1005
1006    /// Decode a datagram header, Type field included.
1007    ///
1008    /// A Type spelled in more than one byte is refused first, for the reason
1009    /// given on [`SubgroupHeader::decode`].
1010    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1011        if let Some(err) = wide_type_refusal(buf, datagram_type_refusal)? {
1012            return Err(err);
1013        }
1014        let datagram_type = buf.get_u8();
1015        validate_datagram_type(datagram_type as u64)?;
1016        let track_alias = VarInt::decode(buf)?;
1017        let group_id = VarInt::decode(buf)?;
1018        let object_id =
1019            if datagram_type & 0x04 == 0 { VarInt::decode(buf)? } else { VarInt::from_usize(0) };
1020        let publisher_priority = if datagram_type & 0x08 != 0 {
1021            None
1022        } else {
1023            if buf.remaining() < 1 {
1024                return Err(CodecError::UnexpectedEnd);
1025            }
1026            Some(buf.get_u8())
1027        };
1028        let extension_headers = if datagram_type & 0x01 != 0 {
1029            let ext_len = VarInt::decode(buf)?.into_inner() as usize;
1030            // A datagram whose type says extensions are present must actually carry
1031            // some: receiving one with an Extension Headers Length of 0 closes the
1032            // session. The opposite holds on a subgroup stream, where the type byte is
1033            // fixed for the whole stream and an object with no extensions has no other
1034            // way to say so, which is why this check belongs to the datagram readers
1035            // alone.
1036            if ext_len == 0 {
1037                return Err(CodecError::InvalidField);
1038            }
1039            crate::types::read_bytes(buf, ext_len)?
1040        } else {
1041            Vec::new()
1042        };
1043        let object_status = if datagram_type & 0x20 != 0 {
1044            Some(decoded_status(VarInt::decode(buf)?.into_inner())?)
1045        } else {
1046            None
1047        };
1048        Ok(Self {
1049            datagram_type,
1050            track_alias,
1051            group_id,
1052            object_id,
1053            publisher_priority,
1054            extension_headers,
1055            object_status,
1056        })
1057    }
1058}
1059
1060#[derive(Debug, Clone, PartialEq, Eq)]
1061pub struct FetchHeader {
1062    pub request_id: VarInt,
1063}
1064
1065impl FetchHeader {
1066    pub fn encode(&self, buf: &mut impl BufMut) {
1067        VarInt::from_usize(FETCH_STREAM_TYPE as usize).encode(buf);
1068        self.request_id.encode(buf);
1069    }
1070
1071    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1072        let stream_type = VarInt::decode(buf)?.into_inner();
1073        if stream_type != FETCH_STREAM_TYPE {
1074            return Err(stream_type_error(stream_type));
1075        }
1076        let request_id = VarInt::decode(buf)?;
1077        Ok(Self { request_id })
1078    }
1079}
1080
1081/// The two Serialization Flags values draft-16 gives a meaning of their own
1082/// instead of reading as a bit field, from Section 10.4.4 Table 4.
1083///
1084/// The bits of Serialization Flags are flags only "when less than 128"; Table 4
1085/// assigns two larger values, and of everything else at or above 128 the
1086/// section says "Any other value is a PROTOCOL_VIOLATION". That is why
1087/// [`FetchObjectHeader::decode`] refuses an unassigned large value outright
1088/// rather than carrying it through as an unknown flag word — there is no
1089/// framing to parse behind it.
1090///
1091/// Both markers stand in for a span of Objects rather than one: Section 10.4.4.2
1092/// reads them as covering every Location between the previous serialized
1093/// Object, if any, and this one, inclusive.
1094#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1095#[repr(u64)]
1096pub enum FetchEndOfRange {
1097    /// End of Non-Existent Range (0x8C): every Object in the covered span is
1098    /// known not to exist. Section 10.4.4.2 adds that a publisher SHOULD NOT
1099    /// use this except to split a span it will not serialize into the part
1100    /// known absent and the part whose status is unknown.
1101    NonExistent = 0x8c,
1102    /// End of Unknown Range (0x10C): the status of every Object in the covered
1103    /// span is unknown.
1104    Unknown = 0x10c,
1105}
1106
1107impl FetchEndOfRange {
1108    /// Both values Table 4 assigns, in ascending wire order.
1109    ///
1110    /// This is exactly the set [`FetchEndOfRange::from_u64`] accepts above the
1111    /// flag range.
1112    pub const ALL: &[FetchEndOfRange] = &[FetchEndOfRange::NonExistent, FetchEndOfRange::Unknown];
1113
1114    /// The Table 4 meaning of `v`, or `None` when `v` is a flag word (anything
1115    /// below 128) or a large value the table leaves unassigned.
1116    ///
1117    /// The two answers `None` covers are not the same thing, and callers must
1118    /// not merge them: a flag word is ordinary framing, an unassigned large
1119    /// value is a protocol violation. [`FetchObjectHeader::decode`] separates
1120    /// them by testing the 128 boundary itself.
1121    pub fn from_u64(v: u64) -> Option<Self> {
1122        match v {
1123            0x8c => Some(FetchEndOfRange::NonExistent),
1124            0x10c => Some(FetchEndOfRange::Unknown),
1125            _ => None,
1126        }
1127    }
1128
1129    /// Return the wire value.
1130    pub fn as_u64(self) -> u64 {
1131        self as u64
1132    }
1133}
1134
1135/// How an Object on a draft-16 fetch stream states its Subgroup ID.
1136///
1137/// The two least significant bits of Serialization Flags form this field;
1138/// draft-16 Section 10.4.4.1 Table 5 lists the four readings. Three of them put
1139/// no Subgroup ID on the wire.
1140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1141pub enum FetchSubgroupMode {
1142    /// 0x00 — the Subgroup ID is zero.
1143    Zero,
1144    /// 0x01 — the Subgroup ID is the prior Object's Subgroup ID.
1145    SameAsPrior,
1146    /// 0x02 — the Subgroup ID is the prior Object's Subgroup ID plus one.
1147    PriorPlusOne,
1148    /// 0x03 — the Subgroup ID field is present on the wire.
1149    Present,
1150}
1151
1152/// One Object as it is framed on a draft-16 fetch stream, before any field is
1153/// resolved against the Object before it.
1154///
1155/// Draft-16 Section 10.4.4 Figure 31 gives the layout: a Serialization Flags
1156/// varint, then Group ID, Subgroup ID, Object ID, Publisher Priority and
1157/// Extensions, each present only when the flags say so, then an Object Payload
1158/// Length and the payload itself. Every optional field here is `Some` exactly
1159/// when its bytes were on the wire, so [`Self::encode`] can put back the same
1160/// bytes [`Self::decode`] took off — including the difference between an absent
1161/// extensions block and a present, zero-length one.
1162///
1163/// The payload is not part of this struct: `payload_length` bytes follow it on
1164/// the stream. That mirrors [`SubgroupObjectMeta`], and it is what lets a relay
1165/// that forwards bytes verbatim read the framing without copying the payload.
1166///
1167/// Two things separate this from the fetch object of drafts 07-13. There is no
1168/// Object Status field — Section 10.2.1.1 says the status "is only present in
1169/// objects that are delivered via a SUBSCRIPTION, and is absent in Objects
1170/// delivered via a FETCH" — so a zero `payload_length` is simply an empty
1171/// object, with no status varint behind it. And the fields that are present are
1172/// absolute: the flags decide presence, and Table 5 and Table 6 decide what an
1173/// absent field inherits, but a field that is on the wire carries its own value
1174/// rather than a delta. Draft-16 spells "Object ID Delta" out by name where it
1175/// means one, on subgroup streams; Figure 31 says "Object ID".
1176///
1177/// Resolving the absent fields needs the Object before this one, which a single
1178/// header does not have. [`FetchObjectReader`] carries that state.
1179#[derive(Debug, Clone, PartialEq, Eq)]
1180pub struct FetchObjectHeader {
1181    /// Serialization Flags exactly as they appeared on the wire: a flag word
1182    /// below 128, or one of the [`FetchEndOfRange`] markers.
1183    pub serialization_flags: VarInt,
1184    /// Group ID, when the flags put it on the wire.
1185    pub group_id: Option<VarInt>,
1186    /// Subgroup ID, present only under [`FetchSubgroupMode::Present`].
1187    pub subgroup_id: Option<VarInt>,
1188    /// Object ID, when the flags put it on the wire.
1189    pub object_id: Option<VarInt>,
1190    /// Publisher Priority, when the flags put it on the wire.
1191    pub publisher_priority: Option<u8>,
1192    /// Raw extension-header bytes, excluding the byte-length prefix that
1193    /// precedes them on the wire, and `None` when the flags carry no extensions
1194    /// block at all. Opaque: [`Self::encode`] re-emits the prefix and these
1195    /// bytes verbatim. The block's own shape is the one Section 10.2.1.2
1196    /// defines for every draft-16 object.
1197    pub extensions: Option<Vec<u8>>,
1198    /// Declared Object Payload Length. The payload follows on the stream and is
1199    /// not held here.
1200    pub payload_length: VarInt,
1201}
1202
1203impl FetchObjectHeader {
1204    /// Serialization Flags as a plain integer.
1205    fn flags(&self) -> u64 {
1206        self.serialization_flags.into_inner()
1207    }
1208
1209    /// The Table 4 marker this object is, or `None` when its Serialization
1210    /// Flags are an ordinary flag word.
1211    pub fn end_of_range(&self) -> Option<FetchEndOfRange> {
1212        FetchEndOfRange::from_u64(self.flags())
1213    }
1214
1215    /// Bit 0x40: the Object's Forwarding Preference is Datagram, so it has no
1216    /// Subgroup ID.
1217    ///
1218    /// Draft-16 Section 10.4.4.1 requires the publisher to set this bit for such
1219    /// an Object, says it SHOULD then zero the two least significant bits, and
1220    /// requires the subscriber to ignore them — so a Subgroup ID is not read
1221    /// even when those bits spell [`FetchSubgroupMode::Present`]. Ignoring them
1222    /// is a framing decision, not a cosmetic one: reading a Subgroup ID there
1223    /// would consume a varint that belongs to the next field.
1224    ///
1225    /// Never true for a [`FetchEndOfRange`] marker, whose value is not a flag
1226    /// word.
1227    pub fn is_datagram(&self) -> bool {
1228        self.end_of_range().is_none() && self.flags() & 0x40 != 0
1229    }
1230
1231    /// The Table 5 reading of the two least significant bits.
1232    ///
1233    /// Answers [`FetchSubgroupMode::Zero`] for a [`FetchEndOfRange`] marker and
1234    /// for a Datagram-forwarded Object, neither of which has a Subgroup ID on
1235    /// the wire: Section 10.4.4.2 lists Subgroup ID among the fields an End of
1236    /// Range does not carry, and Section 10.4.4.1 says a Datagram Object has
1237    /// none at all.
1238    pub fn subgroup_mode(&self) -> FetchSubgroupMode {
1239        if self.end_of_range().is_some() || self.is_datagram() {
1240            return FetchSubgroupMode::Zero;
1241        }
1242        match self.flags() & 0x03 {
1243            0x00 => FetchSubgroupMode::Zero,
1244            0x01 => FetchSubgroupMode::SameAsPrior,
1245            0x02 => FetchSubgroupMode::PriorPlusOne,
1246            _ => FetchSubgroupMode::Present,
1247        }
1248    }
1249
1250    /// Whether a Group ID field is on the wire (bit 0x08).
1251    ///
1252    /// Always true for a [`FetchEndOfRange`] marker: Section 10.4.4.2 says both
1253    /// the Group ID and the Object ID fields are present.
1254    pub fn has_group_id(&self) -> bool {
1255        self.end_of_range().is_some() || self.flags() & 0x08 != 0
1256    }
1257
1258    /// Whether a Subgroup ID field is on the wire, which is
1259    /// [`FetchSubgroupMode::Present`] and nothing else.
1260    pub fn has_subgroup_id(&self) -> bool {
1261        matches!(self.subgroup_mode(), FetchSubgroupMode::Present)
1262    }
1263
1264    /// Whether an Object ID field is on the wire (bit 0x04).
1265    ///
1266    /// Always true for a [`FetchEndOfRange`] marker, per Section 10.4.4.2.
1267    pub fn has_object_id(&self) -> bool {
1268        self.end_of_range().is_some() || self.flags() & 0x04 != 0
1269    }
1270
1271    /// Whether a Publisher Priority byte is on the wire (bit 0x10).
1272    ///
1273    /// Never true for a [`FetchEndOfRange`] marker: Section 10.4.4.2 lists
1274    /// Priority among the fields it does not carry.
1275    pub fn has_priority(&self) -> bool {
1276        self.end_of_range().is_none() && self.flags() & 0x10 != 0
1277    }
1278
1279    /// Whether an Extensions block is on the wire (bit 0x20).
1280    ///
1281    /// Never true for a [`FetchEndOfRange`] marker, per Section 10.4.4.2.
1282    pub fn has_extensions(&self) -> bool {
1283        self.end_of_range().is_none() && self.flags() & 0x20 != 0
1284    }
1285
1286    /// Whether these flags read any field off the Object before this one.
1287    ///
1288    /// Draft-16 Section 10.4.4.1 closes the section on flags with: "If the first
1289    /// Object in the FETCH response uses a flag that references fields in the
1290    /// prior Object, the Subscriber MUST close the session with a
1291    /// PROTOCOL_VIOLATION." Four of the readings do that — an absent Group ID,
1292    /// Object ID or Priority each names the prior Object in Table 5 or Table 6,
1293    /// as do the two middle Subgroup ID modes.
1294    ///
1295    /// [`FetchSubgroupMode::Zero`] does not: it states a value outright. Nor
1296    /// does an absent Extensions block, which means the Object has none rather
1297    /// than the ones before it. A [`FetchEndOfRange`] marker references nothing
1298    /// either — its Group ID and Object ID are always present, and Section
1299    /// 10.4.4.2 gives it no Priority or Extensions to inherit.
1300    ///
1301    /// [`FetchObjectReader::resolve`] refuses the first Object of a stream when
1302    /// this is true of a field it would have to produce a value for. It is
1303    /// exposed separately because the draft's rule is wider than that: it also
1304    /// covers an absent Priority, for which Section 11.1.1.1 supplies a default
1305    /// that makes resolution possible anyway.
1306    pub fn references_prior_object(&self) -> bool {
1307        if self.end_of_range().is_some() {
1308            return false;
1309        }
1310        !self.has_group_id()
1311            || !self.has_object_id()
1312            || !self.has_priority()
1313            || matches!(
1314                self.subgroup_mode(),
1315                FetchSubgroupMode::SameAsPrior | FetchSubgroupMode::PriorPlusOne
1316            )
1317    }
1318
1319    /// Encode the object's framing, refusing a struct that disagrees with its
1320    /// own Serialization Flags.
1321    ///
1322    /// The flags are the authority on which fields are on the wire, so a field
1323    /// that is `Some` while its flag is clear has nowhere to go, and one that is
1324    /// `None` while its flag is set leaves a hole the reader would fill from the
1325    /// bytes of the next field. Either way the result is a stream
1326    /// [`Self::decode`] cannot read back as what was handed in, so both are
1327    /// refused here.
1328    ///
1329    /// Errors with [`CodecError::InvalidField`] on any such disagreement, and on
1330    /// a Serialization Flags value at or above 128 that Table 4 does not assign,
1331    /// before a single byte is written — a refused object leaves `buf` untouched
1332    /// rather than half an object the next read would run into.
1333    ///
1334    /// The payload is not written: `payload_length` bytes of it belong on the
1335    /// stream immediately after these.
1336    pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1337        let flags = self.flags();
1338        if flags >= 128 && FetchEndOfRange::from_u64(flags).is_none() {
1339            return Err(CodecError::InvalidField);
1340        }
1341        if self.group_id.is_some() != self.has_group_id()
1342            || self.subgroup_id.is_some() != self.has_subgroup_id()
1343            || self.object_id.is_some() != self.has_object_id()
1344            || self.publisher_priority.is_some() != self.has_priority()
1345            || self.extensions.is_some() != self.has_extensions()
1346        {
1347            return Err(CodecError::InvalidField);
1348        }
1349
1350        self.serialization_flags.encode(buf);
1351        if let Some(group_id) = self.group_id {
1352            group_id.encode(buf);
1353        }
1354        if let Some(subgroup_id) = self.subgroup_id {
1355            subgroup_id.encode(buf);
1356        }
1357        if let Some(object_id) = self.object_id {
1358            object_id.encode(buf);
1359        }
1360        if let Some(priority) = self.publisher_priority {
1361            buf.put_u8(priority);
1362        }
1363        if let Some(extensions) = &self.extensions {
1364            VarInt::from_usize(extensions.len()).encode(buf);
1365            buf.put_slice(extensions);
1366        }
1367        self.payload_length.encode(buf);
1368        Ok(())
1369    }
1370
1371    /// Decode one object's framing, leaving `buf` positioned at its payload.
1372    ///
1373    /// Errors with [`CodecError::InvalidField`] when Serialization Flags is at
1374    /// or above 128 and is not one of the two values Table 4 assigns — draft-16
1375    /// Section 10.4.4 makes any other such value a PROTOCOL_VIOLATION, and there
1376    /// is no way to guess which fields follow it.
1377    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1378        let serialization_flags = VarInt::decode(buf)?;
1379        let flags = serialization_flags.into_inner();
1380        if flags >= 128 && FetchEndOfRange::from_u64(flags).is_none() {
1381            return Err(CodecError::InvalidField);
1382        }
1383        // Presence is settled before any field is read, so the field order of
1384        // Figure 31 is followed exactly once: Group ID, Subgroup ID, Object ID,
1385        // Priority, Extensions, Object Payload Length.
1386        let probe = Self {
1387            serialization_flags,
1388            group_id: None,
1389            subgroup_id: None,
1390            object_id: None,
1391            publisher_priority: None,
1392            extensions: None,
1393            payload_length: VarInt::from_usize(0),
1394        };
1395
1396        let group_id = if probe.has_group_id() { Some(VarInt::decode(buf)?) } else { None };
1397        let subgroup_id = if probe.has_subgroup_id() { Some(VarInt::decode(buf)?) } else { None };
1398        let object_id = if probe.has_object_id() { Some(VarInt::decode(buf)?) } else { None };
1399        let publisher_priority = if probe.has_priority() {
1400            if buf.remaining() < 1 {
1401                return Err(CodecError::UnexpectedEnd);
1402            }
1403            Some(buf.get_u8())
1404        } else {
1405            None
1406        };
1407        let extensions = if probe.has_extensions() {
1408            let ext_len = VarInt::decode(buf)?.into_inner() as usize;
1409            Some(crate::types::read_bytes(buf, ext_len)?)
1410        } else {
1411            None
1412        };
1413        let payload_length = VarInt::decode(buf)?;
1414
1415        Ok(Self {
1416            serialization_flags,
1417            group_id,
1418            subgroup_id,
1419            object_id,
1420            publisher_priority,
1421            extensions,
1422            payload_length,
1423        })
1424    }
1425}
1426
1427/// One fetch object's Location and priority, with every field the flags left
1428/// off the wire filled in from the Object before it.
1429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1430pub struct FetchObjectLocation {
1431    /// Resolved absolute Group ID.
1432    pub group_id: u64,
1433    /// Resolved absolute Subgroup ID, and `None` for an Object whose Forwarding
1434    /// Preference is Datagram — draft-16 Section 10.2.1 omits the Subgroup ID
1435    /// for those, and Section 10.4.4.1 marks them with bit 0x40.
1436    pub subgroup_id: Option<u64>,
1437    /// Resolved absolute Object ID.
1438    pub object_id: u64,
1439    /// Publisher Priority, and `None` when no Object on the stream so far has
1440    /// stated one.
1441    ///
1442    /// That happens only after a [`FetchEndOfRange`] marker, which carries no
1443    /// Priority of its own and leaves the running value alone; any other first
1444    /// Object with no Priority is refused by [`FetchObjectReader::resolve`].
1445    /// Draft-16 Section 11.1.1.1 supplies the fallback for the gap — a
1446    /// subscription has Publisher Priority 128 when DEFAULT PUBLISHER PRIORITY
1447    /// is omitted — and it is left to the caller rather than substituted here,
1448    /// so that *the stream never said* stays distinguishable from "the stream
1449    /// said 128".
1450    pub publisher_priority: Option<u8>,
1451    /// The Table 4 marker this Object is, or `None` for an ordinary Object.
1452    ///
1453    /// When set, this Location is the far end of a span: Section 10.4.4.2 reads
1454    /// every Location between the previously serialized Object, if any, and
1455    /// this one — inclusive — as non-existent or unknown.
1456    pub end_of_range: Option<FetchEndOfRange>,
1457}
1458
1459/// Stateful resolver for the Objects on one draft-16 fetch stream.
1460///
1461/// Draft-16 Section 10.4.4.1 lets an Object leave its Group ID, Subgroup ID,
1462/// Object ID or Priority off the wire and take the value from the Object before
1463/// it, so the fields of a fetch object are only meaningful in stream order.
1464/// This carries that running state; [`FetchObjectHeader`] carries only what the
1465/// bytes said.
1466///
1467/// Unlike a subgroup stream, nothing here is a delta: a field that is on the
1468/// wire replaces the running value outright, and a field that is absent either
1469/// repeats it or steps it by one, as Table 5 and Table 6 say.
1470#[derive(Debug, Clone, Default)]
1471pub struct FetchObjectReader {
1472    group_id: Option<u64>,
1473    subgroup_id: Option<u64>,
1474    object_id: Option<u64>,
1475    publisher_priority: Option<u8>,
1476}
1477
1478impl FetchObjectReader {
1479    /// A reader positioned before the first Object of a fetch stream, with no
1480    /// prior Object to inherit from.
1481    pub fn new() -> Self {
1482        Self::default()
1483    }
1484
1485    /// Resolve one Object's Location against the Object before it, advancing
1486    /// the running state.
1487    ///
1488    /// Errors with [`CodecError::InvalidField`] when the first Object of the
1489    /// stream leaves out a Group ID or an Object ID, or names the prior
1490    /// Object's Subgroup ID: draft-16 Section 10.4.4.1 makes a first Object
1491    /// that "uses a flag that references fields in the prior Object" a
1492    /// PROTOCOL_VIOLATION, and for these three fields there is no value to
1493    /// produce even if it were not. An absent Priority is the same violation by
1494    /// the draft's letter and is reported by
1495    /// [`FetchObjectHeader::references_prior_object`], but it is resolvable —
1496    /// the Location simply carries no priority — so it is not refused here.
1497    ///
1498    /// Errors with [`CodecError::InvalidField`] when a Subgroup ID one greater
1499    /// than the prior Object's would not fit a varint.
1500    ///
1501    /// A [`FetchEndOfRange`] marker takes part in the running state like any
1502    /// other Object: Section 10.4.4.2 gives it a Location, and it is a
1503    /// "serialized Object" in the same section's words, so the Object after it
1504    /// inherits its Group ID and steps from its Object ID.
1505    pub fn resolve(
1506        &mut self,
1507        header: &FetchObjectHeader,
1508    ) -> Result<FetchObjectLocation, CodecError> {
1509        let group_id = match header.group_id {
1510            Some(v) => v.into_inner(),
1511            None => self.group_id.ok_or(CodecError::InvalidField)?,
1512        };
1513
1514        let subgroup_id = if header.is_datagram() {
1515            None
1516        } else {
1517            match header.subgroup_mode() {
1518                FetchSubgroupMode::Zero => Some(0),
1519                FetchSubgroupMode::SameAsPrior => {
1520                    Some(self.subgroup_id.ok_or(CodecError::InvalidField)?)
1521                }
1522                FetchSubgroupMode::PriorPlusOne => {
1523                    let prior = self.subgroup_id.ok_or(CodecError::InvalidField)?;
1524                    let next = prior.checked_add(1).ok_or(CodecError::InvalidField)?;
1525                    VarInt::from_u64(next).map_err(|_| CodecError::InvalidField)?;
1526                    Some(next)
1527                }
1528                FetchSubgroupMode::Present => {
1529                    Some(header.subgroup_id.ok_or(CodecError::InvalidField)?.into_inner())
1530                }
1531            }
1532        };
1533
1534        let object_id = match header.object_id {
1535            Some(v) => v.into_inner(),
1536            None => {
1537                let prior = self.object_id.ok_or(CodecError::InvalidField)?;
1538                let next = prior.checked_add(1).ok_or(CodecError::InvalidField)?;
1539                VarInt::from_u64(next).map_err(|_| CodecError::InvalidField)?;
1540                next
1541            }
1542        };
1543
1544        // An Object that states no Priority leaves the running one alone, which
1545        // is what "Priority is the prior Object's Priority" asks for and also
1546        // what an End of Range marker — which has no Priority of its own —
1547        // needs.
1548        if let Some(priority) = header.publisher_priority {
1549            self.publisher_priority = Some(priority);
1550        }
1551        self.group_id = Some(group_id);
1552        // A Datagram-forwarded Object has no Subgroup ID at all, so it leaves no
1553        // "prior Object's Subgroup ID" behind: the next Object naming one is
1554        // refused rather than reaching past it to the Object before.
1555        self.subgroup_id = subgroup_id;
1556        self.object_id = Some(object_id);
1557
1558        Ok(FetchObjectLocation {
1559            group_id,
1560            subgroup_id,
1561            object_id,
1562            publisher_priority: self.publisher_priority,
1563            end_of_range: header.end_of_range(),
1564        })
1565    }
1566}
1567
1568/// Re-encodes resolved fetch Objects onto one FETCH stream.
1569///
1570/// The exact inverse of [`FetchObjectReader`], and it exists for one caller:
1571/// something that has read a stream and is writing a different stream from the
1572/// same Objects. Draft-16 Section 10.4.4.1 lets an Object leave out its Group
1573/// ID, Object ID, Subgroup ID and Priority and take the prior Object's, so
1574/// removing an Object changes what the Objects after it are read against: a
1575/// field the survivor left off has to appear, and a flag bit with it.
1576///
1577/// Every field draft-16 puts on the wire here is the absolute value rather than
1578/// a difference — the deltas arrive at draft-18. What is stateful is the
1579/// *omission*, and that is enough to make removal a re-encode.
1580///
1581/// # Why this is not a general encoder
1582///
1583/// Every Object it writes came off a stream, so the caller holds the Object's
1584/// own [`FetchObjectHeader`] beside its resolved
1585/// [`FetchObjectLocation`]. The header is used as the preference: wherever the
1586/// original shape still says the same thing against the new predecessor it is
1587/// kept, so a stream with nothing removed is reproduced byte for byte.
1588#[derive(Debug, Clone, Default)]
1589pub struct FetchObjectWriter {
1590    group_id: Option<u64>,
1591    subgroup_id: Option<u64>,
1592    object_id: Option<u64>,
1593    publisher_priority: Option<u8>,
1594}
1595
1596impl FetchObjectWriter {
1597    /// A writer positioned before the first Object of a fetch stream, with no
1598    /// prior Object for anything to be written against.
1599    pub fn new() -> Self {
1600        Self::default()
1601    }
1602
1603    /// The header that encodes `location` against everything written so far,
1604    /// starting from the shape `original` arrived in.
1605    ///
1606    /// Does not advance the writer — [`Self::write_object_header`] is the call
1607    /// that does both.
1608    ///
1609    /// # Errors
1610    ///
1611    /// [`CodecError::InvalidField`] for an Object with neither a Subgroup ID
1612    /// nor the Datagram bit, and where a value does not fit a variable-length
1613    /// integer.
1614    pub fn header_for(
1615        &self,
1616        original: &FetchObjectHeader,
1617        location: &FetchObjectLocation,
1618    ) -> Result<FetchObjectHeader, CodecError> {
1619        // A marker's Group ID and Object ID are on the wire by definition —
1620        // `has_group_id` and `has_object_id` are true for both marker values —
1621        // and it carries no Subgroup ID, Priority or Extensions.
1622        if original.end_of_range().is_some() {
1623            return Ok(FetchObjectHeader {
1624                serialization_flags: original.serialization_flags,
1625                group_id: Some(VarInt::from_u64(location.group_id)?),
1626                subgroup_id: None,
1627                object_id: Some(VarInt::from_u64(location.object_id)?),
1628                publisher_priority: None,
1629                extensions: None,
1630                payload_length: original.payload_length,
1631            });
1632        }
1633
1634        let group_id = if !original.has_group_id() && self.group_id == Some(location.group_id) {
1635            None
1636        } else {
1637            Some(VarInt::from_u64(location.group_id)?)
1638        };
1639        let object_id = if !original.has_object_id()
1640            && self.object_id.and_then(|p| p.checked_add(1)) == Some(location.object_id)
1641        {
1642            None
1643        } else {
1644            Some(VarInt::from_u64(location.object_id)?)
1645        };
1646        let (subgroup_mode, subgroup_id) = self.subgroup_field(original, location)?;
1647        let publisher_priority = self.priority_field(original, location);
1648
1649        let flags = original.flags();
1650        let mut new_flags = subgroup_mode;
1651        if flags & 0x40 != 0 {
1652            new_flags |= 0x40;
1653        }
1654        if group_id.is_some() {
1655            new_flags |= 0x08;
1656        }
1657        if object_id.is_some() {
1658            new_flags |= 0x04;
1659        }
1660        if publisher_priority.is_some() {
1661            new_flags |= 0x10;
1662        }
1663        if original.has_extensions() {
1664            new_flags |= 0x20;
1665        }
1666
1667        Ok(FetchObjectHeader {
1668            serialization_flags: VarInt::from_u64(new_flags)?,
1669            group_id,
1670            subgroup_id,
1671            object_id,
1672            publisher_priority,
1673            extensions: original.extensions.clone(),
1674            payload_length: original.payload_length,
1675        })
1676    }
1677
1678    /// The Subgroup ID mode bits and the explicit field, if one is needed.
1679    ///
1680    /// The Object's own mode is tried first, so a run that inherited its
1681    /// Subgroup ID keeps inheriting it and its bytes do not move.
1682    fn subgroup_field(
1683        &self,
1684        original: &FetchObjectHeader,
1685        location: &FetchObjectLocation,
1686    ) -> Result<(u64, Option<VarInt>), CodecError> {
1687        // With the Datagram bit set the two low bits say nothing and no field
1688        // is on the wire, so the Object's own bits are carried across.
1689        if original.is_datagram() {
1690            return Ok((original.flags() & 0x03, None));
1691        }
1692
1693        let subgroup_id = location.subgroup_id.ok_or(CodecError::InvalidField)?;
1694        let inherits = self.subgroup_id == Some(subgroup_id);
1695        let successor = self.subgroup_id.is_some_and(|p| p.checked_add(1) == Some(subgroup_id));
1696
1697        let kept = match original.subgroup_mode() {
1698            FetchSubgroupMode::Zero if subgroup_id == 0 => Some((0x00, None)),
1699            FetchSubgroupMode::SameAsPrior if inherits => Some((0x01, None)),
1700            FetchSubgroupMode::PriorPlusOne if successor => Some((0x02, None)),
1701            FetchSubgroupMode::Present => Some((0x03, Some(subgroup_id))),
1702            _ => None,
1703        };
1704        let (mode, explicit) = match kept {
1705            Some(pair) => pair,
1706            None if subgroup_id == 0 => (0x00, None),
1707            None if inherits => (0x01, None),
1708            None if successor => (0x02, None),
1709            None => (0x03, Some(subgroup_id)),
1710        };
1711        Ok((mode, explicit.map(VarInt::from_u64).transpose()?))
1712    }
1713
1714    /// The Publisher Priority field, or `None` when the running one already
1715    /// says it.
1716    ///
1717    /// `location.publisher_priority` is the Priority in force rather than one
1718    /// this Object stated, so the comparison is against the Priority in force
1719    /// on the stream being written. Where the Object that stated it was the one
1720    /// removed, the two differ and this Object states it instead.
1721    fn priority_field(
1722        &self,
1723        original: &FetchObjectHeader,
1724        location: &FetchObjectLocation,
1725    ) -> Option<u8> {
1726        if original.has_priority() || self.publisher_priority != location.publisher_priority {
1727            return location.publisher_priority;
1728        }
1729        None
1730    }
1731
1732    /// Encode `location` against everything written so far and advance.
1733    ///
1734    /// Writes the header only. The payload is `original.payload_length` bytes
1735    /// and is the caller's to copy, unchanged.
1736    ///
1737    /// # Errors
1738    ///
1739    /// [`CodecError::InvalidField`] for an Object with no encoding; the writer
1740    /// is left untouched when this happens.
1741    pub fn write_object_header(
1742        &mut self,
1743        original: &FetchObjectHeader,
1744        location: &FetchObjectLocation,
1745        out: &mut impl BufMut,
1746    ) -> Result<FetchObjectHeader, CodecError> {
1747        let header = self.header_for(original, location)?;
1748        header.encode(out)?;
1749        self.advance(&header, location);
1750        Ok(header)
1751    }
1752
1753    /// Record what was written as the predecessor of whatever comes next.
1754    ///
1755    /// Mirrors [`FetchObjectReader::resolve`] exactly, including the two places
1756    /// draft-16 differs from the drafts after it: a Datagram-forwarded Object
1757    /// clears the running Subgroup ID rather than leaving it standing, and an
1758    /// Object that states no Priority leaves the running one alone.
1759    ///
1760    /// Public because a re-emitting caller has a second way of putting a frame
1761    /// on the wire: when the framing it arrived in still encodes the same
1762    /// meaning against the frame before it, its own bytes are forwarded
1763    /// untouched — no header is produced and nothing is copied. The writer
1764    /// still has to move, or the frame after it is encoded against a
1765    /// predecessor one frame stale. `written` is then the Object's own header, which is
1766    /// what was put on the wire.
1767    pub fn advance(&mut self, written: &FetchObjectHeader, location: &FetchObjectLocation) {
1768        if let Some(priority) = written.publisher_priority {
1769            self.publisher_priority = Some(priority);
1770        }
1771        self.group_id = Some(location.group_id);
1772        self.subgroup_id = location.subgroup_id;
1773        self.object_id = Some(location.object_id);
1774    }
1775}
1776
1777#[cfg(test)]
1778mod tests {
1779    use super::*;
1780
1781    /// Canonically encoded subgroup stream vectors from
1782    /// `test-vectors/transport/draft16/codec/data-streams/subgroup.json`.
1783    /// `subgroup-explicit-subgroup-id` is omitted: it encodes group_id 100 as a
1784    /// two-byte varint, which does not survive a minimal-width re-encode.
1785    const VECTORS: &[&str] = &[
1786        // subgroup-single-object
1787        "100100800004deadbeef",
1788        // subgroup-two-objects
1789        "100100800004deadbeef0002cafe",
1790        // subgroup-no-priority
1791        "3001000004deadbeef",
1792        // subgroup-with-extensions
1793        "11010080000004deadbeef",
1794        // subgroup-nonempty-extensions
1795        "1101008000023c0104deadbeef",
1796        // subgroup-status-end-of-group
1797        "100105800004deadbeef000003",
1798        // subgroup-status-end-of-track
1799        "10010a800004deadbeef000004",
1800        // subgroup-extensions-two-objects-empty
1801        "11010080000004deadbeef000002cafe",
1802        // subgroup-extensions-two-objects-nonempty
1803        "1101008000023c0204deadbeef00023c0302cafe",
1804        // subgroup-extensions-status-object
1805        "1101008000023c010003",
1806        // subgroup-end-of-group
1807        "180105800004deadbeef",
1808        // subgroup-id-first-object
1809        "120103800504deadbeef",
1810    ];
1811
1812    fn vi(v: u64) -> VarInt {
1813        VarInt::from_u64(v).unwrap()
1814    }
1815
1816    fn hex(s: &str) -> Vec<u8> {
1817        (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
1818    }
1819
1820    /// Decode a whole subgroup stream: the header, then every object up to
1821    /// the end of the buffer.
1822    fn decode_all(bytes: &[u8]) -> (SubgroupHeader, Vec<SubgroupObject>) {
1823        let mut cursor = bytes;
1824        let header = SubgroupHeader::decode(&mut cursor)
1825            .unwrap_or_else(|e| panic!("header decode failed: {e:?}"));
1826        let mut reader = SubgroupObjectReader::new(&header);
1827        let mut objects = Vec::new();
1828        while cursor.has_remaining() {
1829            objects.push(
1830                reader
1831                    .read_object(&mut cursor)
1832                    .unwrap_or_else(|e| panic!("object {} decode failed: {e:?}", objects.len())),
1833            );
1834        }
1835        (header, objects)
1836    }
1837
1838    fn encode_all(header: &SubgroupHeader, objects: &[SubgroupObject]) -> Vec<u8> {
1839        let mut buf = Vec::new();
1840        header.encode(&mut buf);
1841        let mut writer = SubgroupObjectReader::new(header);
1842        for o in objects {
1843            writer.write_object(o, &mut buf).unwrap_or_else(|e| panic!("write failed: {e:?}"));
1844        }
1845        buf
1846    }
1847
1848    fn object(id: u64, extensions: Vec<u8>, payload: Vec<u8>) -> SubgroupObject {
1849        SubgroupObject {
1850            object_id: vi(id),
1851            extension_headers: extensions,
1852            payload_length: vi(payload.len() as u64),
1853            object_status: None,
1854            payload,
1855        }
1856    }
1857
1858    // ── Object ID deltas ────────────────────────────────────
1859
1860    #[test]
1861    fn two_objects_with_extensions_have_distinct_ids() {
1862        // Vector `subgroup-extensions-two-objects-empty`: two objects, each
1863        // carrying an empty extensions block and a delta of 0. The delta is
1864        // biased by one whether or not the extensions bit is set, so the IDs
1865        // are 0 and 1 — not 0 and 0.
1866        let bytes = hex("11010080000004deadbeef000002cafe");
1867        let (header, objects) = decode_all(&bytes);
1868        assert!(header.has_extensions());
1869        assert_eq!(objects.len(), 2);
1870        assert_eq!(objects[0].object_id.into_inner(), 0);
1871        assert_eq!(objects[1].object_id.into_inner(), 1);
1872        assert_eq!(objects[0].payload, hex("deadbeef"));
1873        assert_eq!(objects[1].payload, hex("cafe"));
1874        assert!(objects.iter().all(|o| o.extension_headers.is_empty()));
1875    }
1876
1877    #[test]
1878    fn deltas_resolve_sparse_ids() {
1879        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
1880        let objects: Vec<_> =
1881            [3u64, 4, 40].iter().map(|&id| object(id, vec![], vec![0xAA, id as u8])).collect();
1882        let (_, decoded) = decode_all(&encode_all(&header, &objects));
1883        let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
1884        assert_eq!(ids, vec![3, 4, 40]);
1885    }
1886
1887    #[test]
1888    fn write_rejects_non_increasing_ids() {
1889        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
1890        let mut writer = SubgroupObjectReader::new(&header);
1891        let mut buf = Vec::new();
1892        writer.write_object(&object(7, vec![], vec![0x01]), &mut buf).unwrap();
1893        for id in [7u64, 6, 0] {
1894            let err = writer.write_object(&object(id, vec![], vec![0x01]), &mut buf).unwrap_err();
1895            assert!(matches!(err, CodecError::InvalidField), "id {id} gave {err:?}");
1896        }
1897    }
1898
1899    #[test]
1900    fn eliding_an_object_renumbers_its_successor() {
1901        let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
1902        let all: Vec<_> = (0..5u64).map(|id| object(id, vec![], vec![id as u8])).collect();
1903        for elided in 0..5u64 {
1904            let kept: Vec<_> =
1905                all.iter().filter(|o| o.object_id.into_inner() != elided).cloned().collect();
1906            let (_, decoded) = decode_all(&encode_all(&header, &kept));
1907            let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
1908            let expected: Vec<u64> = (0..5u64).filter(|&i| i != elided).collect();
1909            assert_eq!(ids, expected, "eliding object {elided}");
1910        }
1911    }
1912
1913    // ── Extension blocks ──────────────────────────────
1914
1915    #[test]
1916    fn extensions_blob_excludes_its_length_prefix() {
1917        // Vector `subgroup-extensions-two-objects-nonempty`: each
1918        // object carries a two-byte block, so the blob is those two bytes
1919        // with the `02` length prefix stripped.
1920        let bytes = hex("1101008000023c0204deadbeef00023c0302cafe");
1921        let (_, objects) = decode_all(&bytes);
1922        assert_eq!(objects.len(), 2);
1923        assert_eq!(objects[0].object_id.into_inner(), 0);
1924        assert_eq!(objects[1].object_id.into_inner(), 1);
1925        assert_eq!(objects[0].extension_headers, hex("3c02"));
1926        assert_eq!(objects[1].extension_headers, hex("3c03"));
1927        assert_eq!(objects[0].payload, hex("deadbeef"));
1928        assert_eq!(objects[1].payload, hex("cafe"));
1929    }
1930
1931    #[test]
1932    fn status_object_carries_its_extensions_block() {
1933        let (_, objects) = decode_all(&hex("1101008000023c010003"));
1934        assert_eq!(objects.len(), 1);
1935        assert_eq!(objects[0].extension_headers, hex("3c01"));
1936        assert_eq!(objects[0].payload_length.into_inner(), 0);
1937        assert_eq!(objects[0].object_status.map(ObjectStatus::as_u64), Some(3));
1938        assert!(objects[0].payload.is_empty());
1939    }
1940
1941    // ── Object status ───────────────────────────────────────
1942
1943    /// A subgroup header with neither extensions nor an explicit subgroup ID,
1944    /// so an object on this stream is just `delta, payload_length, [status]`.
1945    fn plain_header() -> SubgroupHeader {
1946        SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap()
1947    }
1948
1949    /// A status object on a [`plain_header`] stream: object 0, empty payload,
1950    /// `code` as the status. Every code used here is one wire byte.
1951    fn subgroup_status_body(code: u64) -> Vec<u8> {
1952        let mut buf = vec![0x00, 0x00];
1953        VarInt::from_u64(code).unwrap().encode(&mut buf);
1954        buf
1955    }
1956
1957    fn status_object(status: Option<ObjectStatus>) -> SubgroupObject {
1958        SubgroupObject {
1959            object_id: vi(0),
1960            extension_headers: vec![],
1961            payload_length: vi(0),
1962            object_status: status,
1963            payload: vec![],
1964        }
1965    }
1966
1967    /// A status datagram carrying `code`. Type 0x20 sets the status flag and
1968    /// leaves the object-id (0x04), default-priority (0x08) and extensions
1969    /// (0x01) flags clear, so the layout is `type, track_alias, group_id,
1970    /// object_id, priority, status`.
1971    fn datagram_status_bytes(code: u64) -> Vec<u8> {
1972        let mut buf = vec![0x20, 0x01, 0x02, 0x03, 0x80];
1973        VarInt::from_u64(code).unwrap().encode(&mut buf);
1974        buf
1975    }
1976
1977    fn status_datagram(status: Option<ObjectStatus>) -> DatagramHeader {
1978        DatagramHeader {
1979            datagram_type: 0x20,
1980            track_alias: vi(1),
1981            group_id: vi(2),
1982            object_id: vi(3),
1983            publisher_priority: Some(0x80),
1984            extension_headers: vec![],
1985            object_status: status,
1986        }
1987    }
1988
1989    /// Every status the type can hold reaches the wire as its own code and
1990    /// comes back unchanged, through both subgroup readers and the datagram.
1991    ///
1992    /// Observed by making `write_object` encode `ObjectStatus::Normal` instead
1993    /// of the object's own status, which fails this with:
1994    ///
1995    /// ```text
1996    /// assertion `left == right` failed: EndOfGroup on the subgroup wire
1997    ///   left: [0, 0, 0]
1998    ///  right: [0, 0, 3]
1999    /// ```
2000    #[test]
2001    fn assigned_statuses_round_trip() {
2002        let header = plain_header();
2003        for &status in ObjectStatus::ALL {
2004            let mut bytes = Vec::new();
2005            SubgroupObjectReader::new(&header)
2006                .write_object(&status_object(Some(status)), &mut bytes)
2007                .unwrap();
2008            assert_eq!(
2009                bytes,
2010                subgroup_status_body(status.as_u64()),
2011                "{status:?} on the subgroup wire"
2012            );
2013
2014            let decoded = SubgroupObjectReader::new(&header)
2015                .read_object(&mut &bytes[..])
2016                .unwrap_or_else(|e| panic!("{status:?} was written and then refused: {e:?}"));
2017            assert_eq!(decoded.object_status, Some(status), "{status:?} through read_object");
2018
2019            let meta = SubgroupObjectReader::new(&header)
2020                .read_object_meta(&mut &bytes[..])
2021                .unwrap_or_else(|e| panic!("{status:?} was written and then refused: {e:?}"));
2022            assert_eq!(meta.status, Some(status.as_u64()), "{status:?} through read_object_meta");
2023
2024            let datagram = status_datagram(Some(status));
2025            let mut bytes = Vec::new();
2026            datagram.encode(&mut bytes);
2027            assert_eq!(
2028                bytes,
2029                datagram_status_bytes(status.as_u64()),
2030                "{status:?} on the datagram wire"
2031            );
2032            let decoded = DatagramHeader::decode(&mut &bytes[..]).unwrap_or_else(|e| {
2033                panic!("{status:?} datagram was written and then refused: {e:?}")
2034            });
2035            assert_eq!(decoded, datagram, "{status:?} datagram round trip");
2036        }
2037    }
2038
2039    /// A datagram whose type byte sets the status flag always carries a status
2040    /// field, because the flag is what puts the field on the wire — an unset
2041    /// `object_status` writes Normal rather than nothing.
2042    ///
2043    /// Observed by putting back the `if let Some(s) = &self.object_status`
2044    /// with no `else`, which emits a datagram that stops before its status
2045    /// field and fails this with:
2046    ///
2047    /// ```text
2048    /// assertion `left == right` failed
2049    ///   left: [32, 1, 2, 3, 128]
2050    ///  right: [32, 1, 2, 3, 128, 0]
2051    /// ```
2052    ///
2053    /// and, with the byte comparison removed, fails the decode of its own
2054    /// output with `own output refused: VarInt(UnexpectedEnd)`.
2055    #[test]
2056    fn a_status_datagram_without_a_status_encodes_normal() {
2057        let mut bytes = Vec::new();
2058        status_datagram(None).encode(&mut bytes);
2059        assert_eq!(bytes, datagram_status_bytes(ObjectStatus::Normal.as_u64()));
2060        let decoded = DatagramHeader::decode(&mut &bytes[..])
2061            .unwrap_or_else(|e| panic!("own output refused: {e:?}"));
2062        assert_eq!(decoded.object_status, Some(ObjectStatus::Normal));
2063    }
2064
2065    /// The codes this draft's decoders accept are exactly the codes its
2066    /// encoders can emit.
2067    ///
2068    /// The sweep covers `0x00..=0x3f`, the whole one-byte varint range, so it
2069    /// contains every code draft-16 assigns, the gap inside that range (0x2)
2070    /// and the code draft-16 dropped (0x1, Object Does Not Exist). The
2071    /// expected set is read from [`ObjectStatus::ALL`] rather than written out
2072    /// here, so reassigning a code moves both halves of the test at once.
2073    ///
2074    /// Observed by teaching `ObjectStatus::from_u64` to answer `Some` for 0x1
2075    /// again — the code draft-15 assigned to Object Does Not Exist and
2076    /// draft-16 dropped, mapped onto a surviving variant since draft-16 has no
2077    /// variant of its own for it — which fails this with:
2078    ///
2079    /// ```text
2080    /// assertion `left == right` failed: subgroup read_object on status 0x1: Ok(SubgroupObject { object_id: VarInt(0), extension_headers: [], payload_length: VarInt(0), object_status: Some(Normal), payload: [] })
2081    ///   left: true
2082    ///  right: false
2083    /// ```
2084    #[test]
2085    fn the_wire_accepts_exactly_what_the_type_can_hold() {
2086        let header = plain_header();
2087        for code in 0x00u64..=0x3f {
2088            let assigned = ObjectStatus::ALL.iter().any(|s| s.as_u64() == code);
2089            let body = subgroup_status_body(code);
2090
2091            let read = SubgroupObjectReader::new(&header).read_object(&mut &body[..]);
2092            assert_eq!(
2093                read.is_ok(),
2094                assigned,
2095                "subgroup read_object on status {code:#x}: {read:?}"
2096            );
2097
2098            let meta = SubgroupObjectReader::new(&header).read_object_meta(&mut &body[..]);
2099            assert_eq!(
2100                meta.is_ok(),
2101                assigned,
2102                "subgroup read_object_meta on status {code:#x}: {meta:?}"
2103            );
2104
2105            let datagram = DatagramHeader::decode(&mut &datagram_status_bytes(code)[..]);
2106            assert_eq!(
2107                datagram.is_ok(),
2108                assigned,
2109                "status datagram on status {code:#x}: {datagram:?}"
2110            );
2111
2112            // The other direction: an accepted code is one the encoders can
2113            // reach, and they reach it with exactly these bytes.
2114            if assigned {
2115                let status = ObjectStatus::from_u64(code).unwrap();
2116                let mut bytes = Vec::new();
2117                SubgroupObjectReader::new(&header)
2118                    .write_object(&status_object(Some(status)), &mut bytes)
2119                    .unwrap();
2120                assert_eq!(bytes, body, "write_object on status {code:#x}");
2121                let mut bytes = Vec::new();
2122                status_datagram(Some(status)).encode(&mut bytes);
2123                assert_eq!(
2124                    bytes,
2125                    datagram_status_bytes(code),
2126                    "datagram encode on status {code:#x}"
2127                );
2128            }
2129        }
2130    }
2131
2132    // ── Re-encoding ─────────────────────────────────────────
2133
2134    #[test]
2135    fn vectors_re_encode_byte_identically() {
2136        for vector in VECTORS {
2137            let bytes = hex(vector);
2138            let (header, objects) = decode_all(&bytes);
2139            assert_eq!(encode_all(&header, &objects), bytes, "[{vector}] re-encode");
2140        }
2141    }
2142
2143    // ── Payload-free framing ────────────────────────────────
2144
2145    #[test]
2146    fn meta_matches_read_object() {
2147        for vector in VECTORS {
2148            let bytes = hex(vector);
2149            let mut cursor = &bytes[..];
2150            let header = SubgroupHeader::decode(&mut cursor).unwrap();
2151            let mut full_reader = SubgroupObjectReader::new(&header);
2152            let mut meta_reader = SubgroupObjectReader::new(&header);
2153            let mut full_cursor = cursor;
2154            let mut meta_cursor = cursor;
2155            while meta_cursor.has_remaining() {
2156                let before = meta_cursor.remaining();
2157                let object = full_reader.read_object(&mut full_cursor).unwrap();
2158                let meta = meta_reader.read_object_meta(&mut meta_cursor).unwrap();
2159                assert_eq!(meta.object_id, object.object_id.into_inner(), "[{vector}]");
2160                assert_eq!(
2161                    meta.extension_headers_len,
2162                    object.extension_headers.len() as u64,
2163                    "[{vector}]"
2164                );
2165                assert_eq!(meta.payload_length, object.payload_length.into_inner(), "[{vector}]");
2166                assert_eq!(
2167                    meta.status,
2168                    object.object_status.map(ObjectStatus::as_u64),
2169                    "[{vector}]"
2170                );
2171                assert_eq!(meta.wire_len, (before - meta_cursor.remaining()) as u64, "[{vector}]");
2172                assert_eq!(full_cursor.remaining(), meta_cursor.remaining(), "[{vector}]");
2173            }
2174        }
2175    }
2176
2177    #[test]
2178    fn short_buffers_report_unexpected_end() {
2179        let bytes = hex("1101008000023c0204deadbeef00023c0302cafe");
2180        let mut cursor = &bytes[..];
2181        let header = SubgroupHeader::decode(&mut cursor).unwrap();
2182        let objects_start = bytes.len() - cursor.len();
2183        for cut in objects_start..bytes.len() {
2184            let mut reader = SubgroupObjectReader::new(&header);
2185            let mut meta_reader = SubgroupObjectReader::new(&header);
2186            let mut cursor = &bytes[objects_start..cut];
2187            let mut meta_cursor = cursor;
2188            while cursor.has_remaining() {
2189                if let Err(err) = reader.read_object(&mut cursor) {
2190                    assert!(
2191                        matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2192                        "cut {cut} gave {err:?}"
2193                    );
2194                    break;
2195                }
2196            }
2197            while meta_cursor.has_remaining() {
2198                if let Err(err) = meta_reader.read_object_meta(&mut meta_cursor) {
2199                    assert!(
2200                        matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2201                        "cut {cut} gave {err:?}"
2202                    );
2203                    break;
2204                }
2205            }
2206        }
2207    }
2208}