Skip to main content

moqtap_codec/draft14/
data_stream.rs

1//! Draft-14 data streams: subgroup streams, fetch streams, datagrams.
2//!
3//! Draft-14 carries object data in three wire shapes:
4//!
5//! * **Subgroup stream**: starts with a Type byte `0x10..=0x1D`
6//!   whose bit-flags determine whether a Subgroup ID field is present,
7//!   whether the subgroup ID is zero or the first Object ID, whether
8//!   extension headers are present, and whether the stream ends at a
9//!   group boundary. Object IDs are delta-encoded relative to the
10//!   previous Object ID in the same stream.
11//!
12//! * **Fetch stream**: Type `0x05`, Request ID, then a sequence
13//!   of self-describing objects until FIN.
14//!
15//! * **Datagram**: Type byte `0x00..=0x07` or `0x20..=0x21`
16//!   with bit-flags for End of Group, Extensions Present, Object ID
17//!   Present, and Status vs Payload.
18
19use bytes::{Buf, BufMut};
20
21use super::types::ObjectStatus;
22use crate::error::CodecError;
23use crate::varint::VarInt;
24
25/// Advance `buf` past `len` bytes without copying them.
26fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
27    let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
28    if buf.remaining() < len {
29        return Err(CodecError::UnexpectedEnd);
30    }
31    buf.advance(len);
32    Ok(())
33}
34
35/// Hold an object to the rule binding extension headers to Object Status.
36///
37/// Draft-14 Section 10.2.1.2: "Any Object may have extension headers except
38/// those with Object Status 'Object Does Not Exist'. If an endpoint receives a
39/// non-existent Object containing extension headers it MUST close the session
40/// with a PROTOCOL_VIOLATION."
41///
42/// The rule names one status and no others, so extensions beside End of Group
43/// or End of Track stay legal and are left alone here. The reasoning behind the
44/// exception is that an object nobody has cannot carry metadata about itself:
45/// a relay that forwards the extensions of a non-existent object is inventing
46/// provenance for something that was never published.
47///
48/// All three carriers reach this, in both directions — subgroup streams, fetch
49/// streams and status datagrams each pair a status with an extension block.
50///
51/// Reported under [`CodecError::ExtensionsOnNonExistentObject`], which is this
52/// rule and nothing else. It was [`CodecError::InvalidField`] until now, shared
53/// with a dozen unrelated malformations the draft does not answer with a close,
54/// which left a caller unable to act on the sentence above.
55fn check_extensions_against_status(
56    status: Option<u64>,
57    extension_headers_len: u64,
58) -> Result<(), CodecError> {
59    if status == Some(ObjectStatus::ObjectDoesNotExist.as_u64()) && extension_headers_len != 0 {
60        // The length is reported as it appeared on the wire; saturating rather
61        // than truncating means a declared length above `usize::MAX` — which
62        // cannot have been read, but can have been declared — is never reported
63        // as some smaller, plausible number.
64        return Err(CodecError::ExtensionsOnNonExistentObject(
65            usize::try_from(extension_headers_len).unwrap_or(usize::MAX),
66        ));
67    }
68    Ok(())
69}
70
71// ============================================================
72// Subgroup stream (Type 0x10..=0x1D)
73// ============================================================
74
75/// Subgroup stream type byte.
76///
77/// The 12 defined types encode four independent boolean fields in the
78/// low nibble:
79///
80/// * bit 0 (`0x01`) — Extensions Present
81/// * bit 1 (`0x02`) — Subgroup ID derives from first Object ID
82///   (only meaningful when bit 2 is clear)
83/// * bit 2 (`0x04`) — Subgroup ID Field Present (explicit Subgroup ID varint)
84/// * bit 3 (`0x08`) — Contains End of Group
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct SubgroupStreamType(u8);
87
88impl SubgroupStreamType {
89    /// The raw wire byte.
90    pub fn as_u8(self) -> u8 {
91        self.0
92    }
93
94    /// Create a [`SubgroupStreamType`] from its raw byte, validating
95    /// that it is one of the 12 defined values. `0x16` and `0x17` fall
96    /// inside the `0x10..=0x1D` range but are not defined, so a range
97    /// check alone is not enough.
98    pub fn from_u8(v: u8) -> Option<Self> {
99        if (0x10..=0x15).contains(&v) || (0x18..=0x1D).contains(&v) {
100            Some(SubgroupStreamType(v))
101        } else {
102            None
103        }
104    }
105
106    /// Build a subgroup stream type from its component flags.
107    ///
108    /// `subgroup_id_is_first_object` and `subgroup_id_field_present` are
109    /// mutually exclusive — if both are set, the resulting type has the
110    /// "Subgroup ID Field Present" bit set (bit 2 wins).
111    pub fn from_flags(
112        subgroup_id_field_present: bool,
113        subgroup_id_is_first_object: bool,
114        extensions_present: bool,
115        end_of_group: bool,
116    ) -> Self {
117        let mut v: u8 = 0x10;
118        if extensions_present {
119            v |= 0x01;
120        }
121        if subgroup_id_field_present {
122            v |= 0x04;
123        } else if subgroup_id_is_first_object {
124            v |= 0x02;
125        }
126        if end_of_group {
127            v |= 0x08;
128        }
129        SubgroupStreamType(v)
130    }
131
132    /// True if the header carries an explicit Subgroup ID varint.
133    pub fn has_subgroup_id_field(self) -> bool {
134        self.0 & 0x04 != 0
135    }
136
137    /// True if the subgroup ID is defined to equal the first Object ID
138    /// in the stream (applies only when [`Self::has_subgroup_id_field`]
139    /// is false).
140    pub fn subgroup_id_is_first_object(self) -> bool {
141        !self.has_subgroup_id_field() && (self.0 & 0x02 != 0)
142    }
143
144    /// True if every object in the stream carries extension headers.
145    pub fn extensions_present(self) -> bool {
146        self.0 & 0x01 != 0
147    }
148
149    /// True if the last object on the stream (prior to FIN) is the end
150    /// of its group.
151    pub fn contains_end_of_group(self) -> bool {
152        self.0 & 0x08 != 0
153    }
154}
155
156/// Subgroup stream header.
157///
158/// Wire order: type byte, Track Alias, Group ID, Subgroup ID (only for
159/// stream types that carry one), Publisher Priority.
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct SubgroupHeader {
162    /// Type byte identifying the flag set for this stream.
163    pub stream_type: SubgroupStreamType,
164    /// Track alias.
165    pub track_alias: VarInt,
166    /// Group ID.
167    pub group_id: VarInt,
168    /// Explicit Subgroup ID — present only when the stream type sets
169    /// `Subgroup ID Field Present = Yes`. For types where the subgroup
170    /// ID is implicit (0 or the first Object ID), the effective
171    /// subgroup ID is resolved on the receive side by the reader.
172    pub subgroup_id: Option<VarInt>,
173    /// Publisher priority.
174    pub publisher_priority: u8,
175}
176
177impl SubgroupHeader {
178    /// Encode the header including the leading stream type byte.
179    ///
180    /// Driven by `stream_type`, and silent about a `subgroup_id` that
181    /// disagrees with it in either direction: a `None` under a type whose
182    /// Subgroup ID Field Present column reads Yes is written as zero, which
183    /// names a different subgroup rather than no subgroup, and a `Some` under
184    /// a type whose column reads No is dropped. Both write a well-formed
185    /// stream describing something other than what the caller built.
186    /// [`Self::encode_checked`] refuses that shape instead.
187    pub fn encode(&self, buf: &mut impl BufMut) {
188        VarInt::from_u64(self.stream_type.as_u8() as u64).unwrap().encode(buf);
189        self.track_alias.encode(buf);
190        self.group_id.encode(buf);
191        if self.stream_type.has_subgroup_id_field() {
192            let sg = self.subgroup_id.unwrap_or_else(|| VarInt::from_u64(0).unwrap());
193            sg.encode(buf);
194        }
195        buf.put_u8(self.publisher_priority);
196    }
197
198    /// Encode, refusing a header whose Subgroup ID disagrees with its own type
199    /// byte.
200    ///
201    /// Section 10.4.2 gives the SUBGROUP_HEADER type table a Subgroup ID Field
202    /// Present column, and that column - not the value in hand - decides
203    /// whether the field is on the wire. [`Self::decode`] therefore only ever
204    /// produces a header where the two agree, so this refuses exactly the
205    /// shapes a caller assembled by hand: an absent ID under a type that
206    /// carries one, and a present ID under a type that does not.
207    ///
208    /// Nothing about the *value* is refused. Zero is a legal Subgroup ID, so a
209    /// `Some(0)` under a type that carries the field is written as the caller
210    /// asked.
211    ///
212    /// # Errors
213    ///
214    /// [`CodecError::InvalidField`] if the presence of `subgroup_id` does not
215    /// match what `stream_type` puts on the wire.
216    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
217        if self.stream_type.has_subgroup_id_field() != self.subgroup_id.is_some() {
218            return Err(CodecError::InvalidField);
219        }
220        self.encode(buf);
221        Ok(())
222    }
223
224    /// Decode a subgroup header (leading type byte + remaining fields).
225    ///
226    /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
227    /// not assign the leading type, which this draft answers with a close, and
228    /// with [`CodecError::InvalidField`] for `0x05`, which it assigns to a
229    /// fetch stream. `stream_type_error` draws that line.
230    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
231        let type_val = VarInt::decode(buf)?.into_inner();
232        if type_val > 0xFF {
233            return Err(stream_type_error(type_val));
234        }
235        let stream_type = SubgroupStreamType::from_u8(type_val as u8)
236            .ok_or_else(|| stream_type_error(type_val))?;
237        let track_alias = VarInt::decode(buf)?;
238        let group_id = VarInt::decode(buf)?;
239        let subgroup_id =
240            if stream_type.has_subgroup_id_field() { Some(VarInt::decode(buf)?) } else { None };
241        if buf.remaining() < 1 {
242            return Err(CodecError::UnexpectedEnd);
243        }
244        let publisher_priority = buf.get_u8();
245        Ok(SubgroupHeader { stream_type, track_alias, group_id, subgroup_id, publisher_priority })
246    }
247}
248
249/// One object within a subgroup stream, with the Object ID already
250/// resolved from its delta encoding.
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct SubgroupObject {
253    /// Resolved Object ID (delta decoded to an absolute value).
254    pub object_id: VarInt,
255    /// Raw extension-header bytes. Empty when the stream type has
256    /// `Extensions Present = No`, or when present but the length was 0.
257    /// The content is a sequence of Key-Value-Pairs but is
258    /// left opaque here — relays and subscribers that do not understand
259    /// specific extensions must forward or ignore the bytes unchanged.
260    pub extension_headers: Vec<u8>,
261    /// Object Status when `payload.is_empty()` and the object was sent
262    /// with an explicit status code; `None` when a non-empty payload
263    /// follows (status is implicitly [`ObjectStatus::Normal`]).
264    pub status: Option<ObjectStatus>,
265    /// Object payload. Empty when `status` is `Some(..)`.
266    pub payload: Vec<u8>,
267}
268
269/// The framing of one subgroup object, without its payload.
270///
271/// Produced by [`SubgroupObjectReader::read_object_meta`] for callers that
272/// forward an object's bytes verbatim and never inspect the payload.
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub struct SubgroupObjectMeta {
275    /// Resolved Object ID (delta decoded to an absolute value).
276    pub object_id: u64,
277    /// Byte length of the extension-header block's contents, excluding its
278    /// length prefix.
279    pub extension_headers_len: u64,
280    /// Declared payload length. Zero when `status` is `Some`.
281    pub payload_length: u64,
282    /// Object Status wire code, present only when the payload is empty.
283    pub status: Option<u64>,
284    /// Total bytes this object occupies on the wire, prefix fields included.
285    pub wire_len: u64,
286}
287
288/// Whether an object carrying a given status may hold a non-empty payload.
289///
290/// Section 10.2.1.1 states the rule in one sentence — "Any object with a
291/// status code other than zero MUST have an empty payload" — so on this draft
292/// the answer falls out of the code being zero or not, and every status but
293/// [`ObjectStatus::Normal`] forbids a payload.
294///
295/// It is worth a type all the same, because that arithmetic is not something a
296/// consumer can safely perform on a raw wire code. A code this draft does not
297/// assign is not *non-zero, and therefore forbidden*: it is a code with no
298/// meaning at all, and no payload rule attaches to it. Handing back a
299/// `PayloadPermission` keeps the two apart, and lets a caller ask the question
300/// without restating the rule — or, worse, restating it slightly differently.
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum PayloadPermission {
303    /// The status permits a payload but does not require one: a zero-length
304    /// object with such a status is well formed, and this draft's encodings
305    /// have a way to spell it.
306    Permitted,
307    /// An object with such a status has an empty payload, and one carrying
308    /// bytes is malformed.
309    Forbidden,
310}
311
312impl PayloadPermission {
313    /// The permission this draft gives objects carrying `status`.
314    ///
315    /// Written as a match over every assigned status rather than as a test for
316    /// zero, so that a status added to [`ObjectStatus`] later cannot quietly
317    /// inherit *not Normal, therefore forbidden* — it stops the crate
318    /// compiling until its own answer is written down.
319    pub fn for_status(status: ObjectStatus) -> Self {
320        match status {
321            ObjectStatus::Normal => PayloadPermission::Permitted,
322            ObjectStatus::ObjectDoesNotExist => PayloadPermission::Forbidden,
323            ObjectStatus::EndOfGroup => PayloadPermission::Forbidden,
324            ObjectStatus::EndOfTrack => PayloadPermission::Forbidden,
325        }
326    }
327
328    /// `true` for [`PayloadPermission::Permitted`].
329    ///
330    /// The permission answers on its own, with no payload length in hand,
331    /// which is the point of asking the status rather than the framing.
332    pub fn permits(self) -> bool {
333        matches!(self, PayloadPermission::Permitted)
334    }
335}
336
337impl SubgroupObject {
338    /// The status this object resolves to.
339    ///
340    /// The wire carries a status field only on an empty object, so an object
341    /// holding bytes is [`ObjectStatus::Normal`] whatever `status` says.
342    pub fn status(&self) -> ObjectStatus {
343        if self.payload.is_empty() {
344            self.status.unwrap_or(ObjectStatus::Normal)
345        } else {
346            ObjectStatus::Normal
347        }
348    }
349
350    /// Whether this object's status permits it a non-empty payload.
351    ///
352    /// Answered from the status alone. `payload` is not consulted: on a status
353    /// that permits a payload it says only whether this particular object took
354    /// the offer, and on one that forbids a payload a non-empty payload is the
355    /// malformation this reports, not evidence about the rule.
356    pub fn permits_payload(&self) -> bool {
357        PayloadPermission::for_status(self.status()).permits()
358    }
359}
360
361impl SubgroupObjectMeta {
362    /// Whether this object's status permits it a non-empty payload.
363    ///
364    /// The same question [`SubgroupObject::permits_payload`] answers, from the
365    /// declared length and the wire code rather than from the bytes. A relay
366    /// that forwards an object verbatim reads it through
367    /// [`SubgroupObjectReader::read_object_meta`] and never copies the payload,
368    /// so asking this must not require having it.
369    ///
370    /// An absent status answers [`PayloadPermission::Permitted`] rather than
371    /// `None`. A meta has no status only when its payload length is non-zero,
372    /// so the object has a status — the encoding just does not spell it.
373    ///
374    /// `None` means the code is one this draft leaves unassigned, and so one it
375    /// gives no payload rule for. That is not reachable through
376    /// [`SubgroupObjectReader::read_object_meta`], which refuses such a code
377    /// before it can reach the field, but the fields here are public and a meta
378    /// assembled by hand — by a relay carrying a status across from a draft
379    /// that numbers them differently, say — can hold anything a varint can. The
380    /// answer there is that this draft has none, not that the payload is
381    /// forbidden.
382    pub fn payload_permission(&self) -> Option<PayloadPermission> {
383        match self.status {
384            None => Some(PayloadPermission::Permitted),
385            Some(code) => ObjectStatus::from_u64(code).map(PayloadPermission::for_status),
386        }
387    }
388}
389
390/// Stateful reader for the object fields on a subgroup stream.
391///
392/// Object IDs on a subgroup stream are delta-encoded against the
393/// previous Object ID, and whether extension headers are present is
394/// fixed by the enclosing [`SubgroupHeader`]'s stream type. This reader
395/// carries that context across successive `read_object` calls.
396#[derive(Debug, Clone)]
397pub struct SubgroupObjectReader {
398    extensions_present: bool,
399    prev_object_id: Option<u64>,
400}
401
402impl SubgroupObjectReader {
403    /// Create a reader from a parsed subgroup header.
404    pub fn new(header: &SubgroupHeader) -> Self {
405        Self { extensions_present: header.stream_type.extensions_present(), prev_object_id: None }
406    }
407
408    /// Decode the next object from `buf`. Caller is responsible for
409    /// ensuring the buffer contains a complete object (draft-14 objects
410    /// are length-delimited by the payload-length field, so the buffer
411    /// boundary is known once the header portion has been consumed).
412    pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<SubgroupObject, CodecError> {
413        let delta = VarInt::decode(buf)?.into_inner();
414        let object_id_val = match self.prev_object_id {
415            None => delta,
416            Some(prev) => prev
417                .checked_add(1)
418                .and_then(|v| v.checked_add(delta))
419                .ok_or(CodecError::InvalidField)?,
420        };
421        self.prev_object_id = Some(object_id_val);
422        let object_id = VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?;
423
424        let extension_headers = if self.extensions_present {
425            let ext_len = VarInt::decode(buf)?.into_inner() as usize;
426            crate::types::read_bytes(buf, ext_len)?
427        } else {
428            Vec::new()
429        };
430
431        let payload_length = VarInt::decode(buf)?.into_inner() as usize;
432        let (status, payload) = if payload_length == 0 {
433            let status_val = VarInt::decode(buf)?.into_inner();
434            let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
435            (Some(status), Vec::new())
436        } else {
437            let payload = crate::types::read_bytes(buf, payload_length)?;
438            (None, payload)
439        };
440        check_extensions_against_status(
441            status.map(|s| s.as_u64()),
442            extension_headers.len() as u64,
443        )?;
444
445        Ok(SubgroupObject { object_id, extension_headers, status, payload })
446    }
447
448    /// Decode the next object's framing without copying its payload.
449    ///
450    /// Consumes exactly the bytes [`Self::read_object`] consumes and leaves
451    /// the same delta state behind, so the two are interchangeable on a
452    /// given stream.
453    pub fn read_object_meta(
454        &mut self,
455        buf: &mut impl Buf,
456    ) -> Result<SubgroupObjectMeta, CodecError> {
457        let start = buf.remaining();
458        let delta = VarInt::decode(buf)?.into_inner();
459        let object_id_val = match self.prev_object_id {
460            None => delta,
461            Some(prev) => prev
462                .checked_add(1)
463                .and_then(|v| v.checked_add(delta))
464                .ok_or(CodecError::InvalidField)?,
465        };
466        self.prev_object_id = Some(object_id_val);
467        let object_id =
468            VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?.into_inner();
469
470        let extension_headers_len = if self.extensions_present {
471            let ext_len = VarInt::decode(buf)?.into_inner();
472            skip(buf, ext_len)?;
473            ext_len
474        } else {
475            0
476        };
477
478        let payload_length = VarInt::decode(buf)?.into_inner();
479        let status = if payload_length == 0 {
480            let status_val = VarInt::decode(buf)?.into_inner();
481            Some(ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?.as_u64())
482        } else {
483            skip(buf, payload_length)?;
484            None
485        };
486        check_extensions_against_status(status, extension_headers_len)?;
487
488        Ok(SubgroupObjectMeta {
489            object_id,
490            extension_headers_len,
491            payload_length,
492            status,
493            wire_len: (start - buf.remaining()) as u64,
494        })
495    }
496
497    /// Serialize a subgroup object using the reader's delta state. Intended
498    /// for senders that want to build a stream incrementally — tracks
499    /// `prev_object_id` so successive calls produce correct deltas.
500    ///
501    /// Returns an error if `object.object_id <= prev_object_id`, which
502    /// would produce an invalid delta.
503    ///
504    /// It also refuses the three shapes the stream cannot carry, rather than
505    /// writing whichever half fits and dropping the rest:
506    ///
507    /// * A non-empty payload beside a status other than Normal. Section
508    ///   10.2.1.1: "Any object with a status code other than zero MUST have an
509    ///   empty payload." A truncated payload is worse than a refusal — the
510    ///   receiver has no way to tell that anything was there.
511    /// * Extension headers on an object whose stream type says the subgroup has
512    ///   none. The type byte is fixed for the whole stream by the header, so
513    ///   this object cannot opt in, and its extensions would simply vanish.
514    /// * Extension headers on an Object Does Not Exist status, per Section
515    ///   10.2.1.2.
516    ///
517    /// Normal beside a non-empty payload is not one of those and is written as
518    /// an ordinary payload-bearing object: it is the status such an object
519    /// already has, so naming it asks for the bytes leaving it out asks for.
520    /// Normal beside an empty payload keeps the explicit status form, which is
521    /// the only way to send a zero-length object at all.
522    pub fn write_object(
523        &mut self,
524        object: &SubgroupObject,
525        buf: &mut impl BufMut,
526    ) -> Result<(), CodecError> {
527        let explicit_status = match object.status {
528            Some(status) if status != ObjectStatus::Normal => {
529                if !object.payload.is_empty() {
530                    return Err(CodecError::InvalidField);
531                }
532                Some(status)
533            }
534            Some(ObjectStatus::Normal) if object.payload.is_empty() => Some(ObjectStatus::Normal),
535            _ => None,
536        };
537        if !self.extensions_present && !object.extension_headers.is_empty() {
538            return Err(CodecError::InvalidField);
539        }
540        check_extensions_against_status(
541            explicit_status.map(|s| s.as_u64()),
542            object.extension_headers.len() as u64,
543        )?;
544
545        let oid = object.object_id.into_inner();
546        let delta = match self.prev_object_id {
547            None => oid,
548            Some(prev) => oid
549                .checked_sub(prev)
550                .and_then(|v| v.checked_sub(1))
551                .ok_or(CodecError::InvalidField)?,
552        };
553        VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?.encode(buf);
554        if self.extensions_present {
555            VarInt::from_u64(object.extension_headers.len() as u64)
556                .map_err(|_| CodecError::InvalidField)?
557                .encode(buf);
558            buf.put_slice(&object.extension_headers);
559        }
560        if let Some(status) = explicit_status {
561            VarInt::from_u64(0).unwrap().encode(buf);
562            VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
563        } else {
564            VarInt::from_u64(object.payload.len() as u64)
565                .map_err(|_| CodecError::InvalidField)?
566                .encode(buf);
567            buf.put_slice(&object.payload);
568        }
569        self.prev_object_id = Some(oid);
570        Ok(())
571    }
572}
573
574// ============================================================
575// Fetch stream (Type 0x05)
576// ============================================================
577
578/// Which failure a leading unidirectional stream type that is not the one a
579/// reader wants is.
580///
581/// Section 10: "An endpoint that receives an unknown stream or datagram type
582/// MUST close the session." One sentence, two tables. The stream table assigns
583/// 0x05 for FETCH_HEADER and the range 0x10 to 0x1D for SUBGROUP_HEADER;
584/// everything outside them is unknown at the head of a stream, and the session
585/// ends.
586///
587/// The two assigned kinds are what the [`CodecError::InvalidField`] arm is for:
588/// a fetch stream reaching the subgroup reader, or a subgroup stream reaching
589/// the fetch reader, is a value this draft defines, and the disagreement is
590/// with the reader that was called rather than with the draft. Reporting it as
591/// unknown would end sessions over streams draft-14 permits.
592///
593/// Values above 0xFF land here too. None of them is assigned — this draft
594/// carries its control messages on a bidirectional stream and so has no
595/// multi-byte unidirectional type the way drafts 17 and later do.
596fn stream_type_error(raw: u64) -> CodecError {
597    let assigned = raw == FETCH_STREAM_TYPE as u64
598        || (raw <= 0xFF && SubgroupStreamType::from_u8(raw as u8).is_some());
599    if assigned {
600        CodecError::InvalidField
601    } else {
602        CodecError::UnknownStreamType(raw)
603    }
604}
605
606/// Draft-14 fetch stream type byte.
607pub const FETCH_STREAM_TYPE: u8 = 0x05;
608
609/// Fetch stream header: the type byte `0x05` followed by the Request ID.
610#[derive(Debug, Clone, PartialEq, Eq)]
611pub struct FetchHeader {
612    /// Request ID from the originating FETCH control message.
613    pub request_id: VarInt,
614}
615
616impl FetchHeader {
617    /// Encode the header including the leading type byte.
618    pub fn encode(&self, buf: &mut impl BufMut) {
619        VarInt::from_u64(FETCH_STREAM_TYPE as u64).unwrap().encode(buf);
620        self.request_id.encode(buf);
621    }
622
623    /// Decode the header.
624    ///
625    /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
626    /// not assign the leading type, which this draft answers with a close, and
627    /// with [`CodecError::InvalidField`] for the subgroup types, which it does
628    /// assign. `stream_type_error` draws that line.
629    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
630        let type_val = VarInt::decode(buf)?.into_inner();
631        if type_val != FETCH_STREAM_TYPE as u64 {
632            return Err(stream_type_error(type_val));
633        }
634        let request_id = VarInt::decode(buf)?;
635        Ok(FetchHeader { request_id })
636    }
637}
638
639/// One object carried on a fetch stream.
640///
641/// Every object on a fetch stream is self-describing — unlike subgroup
642/// streams, there is no delta encoding and extension headers are always
643/// length-prefixed (the length is zero when absent).
644#[derive(Debug, Clone, PartialEq, Eq)]
645pub struct FetchObject {
646    /// Group ID.
647    pub group_id: VarInt,
648    /// Subgroup ID. For objects whose Forwarding Preference is Datagram,
649    /// this is set to the Object ID.
650    pub subgroup_id: VarInt,
651    /// Object ID.
652    pub object_id: VarInt,
653    /// Publisher priority.
654    pub publisher_priority: u8,
655    /// Raw extension-header bytes (opaque sequence of Key-Value-Pairs).
656    pub extension_headers: Vec<u8>,
657    /// Object status when `payload.is_empty()`, otherwise `None`.
658    pub status: Option<ObjectStatus>,
659    /// Object payload.
660    pub payload: Vec<u8>,
661}
662
663/// The framing of one fetch object, without its payload.
664///
665/// Produced by [`FetchObject::decode_meta`] for callers that forward an
666/// object's bytes verbatim and never inspect the payload.
667#[derive(Debug, Clone, Copy, PartialEq, Eq)]
668pub struct FetchObjectMeta {
669    /// Group ID.
670    pub group_id: u64,
671    /// Subgroup ID.
672    pub subgroup_id: u64,
673    /// Object ID.
674    pub object_id: u64,
675    /// Publisher priority.
676    pub publisher_priority: u8,
677    /// Byte length of the extension-header block's contents, excluding its
678    /// length prefix.
679    pub extension_headers_len: u64,
680    /// Declared payload length. Zero when `status` is `Some`.
681    pub payload_length: u64,
682    /// Object Status wire code, present only when the payload is empty.
683    pub status: Option<u64>,
684    /// Total bytes this object occupies on the wire, prefix fields included.
685    pub wire_len: u64,
686}
687
688impl FetchObjectMeta {
689    /// Whether this object's status permits it a non-empty payload.
690    ///
691    /// The fetch-stream twin of [`SubgroupObjectMeta::payload_permission`], and
692    /// it answers on the same terms: `None` for a code this draft does not
693    /// assign, and [`PayloadPermission::Permitted`] for an absent status, which
694    /// a meta has only when its payload length is non-zero.
695    pub fn payload_permission(&self) -> Option<PayloadPermission> {
696        match self.status {
697            None => Some(PayloadPermission::Permitted),
698            Some(code) => ObjectStatus::from_u64(code).map(PayloadPermission::for_status),
699        }
700    }
701}
702
703impl FetchObject {
704    /// The status this object resolves to.
705    ///
706    /// The wire carries a status field only on an empty object, so an object
707    /// holding bytes is [`ObjectStatus::Normal`] whatever `status` says.
708    pub fn status(&self) -> ObjectStatus {
709        if self.payload.is_empty() {
710            self.status.unwrap_or(ObjectStatus::Normal)
711        } else {
712            ObjectStatus::Normal
713        }
714    }
715
716    /// Whether this object's status permits it a non-empty payload.
717    ///
718    /// Answered from the status alone, for the reason
719    /// [`SubgroupObject::permits_payload`] gives. Note that
720    /// [`Self::encode_checked`] refuses the pairing this reports on, so a
721    /// `false` here is a value that will not be written rather than one
722    /// already on the wire.
723    pub fn permits_payload(&self) -> bool {
724        PayloadPermission::for_status(self.status()).permits()
725    }
726
727    /// Encode one fetch object, refusing a value the wire shape cannot carry.
728    ///
729    /// A fetch object holds a status and a payload in the same value, and the
730    /// wire form has room for only one: the Object Status field is written only
731    /// when Object Payload Length is zero. [`Self::encode`] settles that by
732    /// following the status and dropping the payload, which loses the payload
733    /// without saying so. This refuses instead, before any byte is written, so
734    /// a rejected object leaves `buf` untouched.
735    ///
736    /// Section 10.2.1.1: "Any object with a status code other than zero MUST
737    /// have an empty payload." Normal beside a payload is therefore not a
738    /// disagreement — it is the status a payload-bearing object already has —
739    /// and it is written as an ordinary payload-bearing object.
740    ///
741    /// Extension headers on an Object Does Not Exist status are refused for the
742    /// separate reason given in Section 10.2.1.2.
743    ///
744    /// Unlike a subgroup stream, a fetch object always carries its Extension
745    /// Headers Length field, so a length of zero is an ordinary object with no
746    /// extensions and is written as such.
747    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
748        if matches!(self.status, Some(status) if status != ObjectStatus::Normal)
749            && !self.payload.is_empty()
750        {
751            return Err(CodecError::InvalidField);
752        }
753        check_extensions_against_status(
754            self.status.map(|s| s.as_u64()),
755            self.extension_headers.len() as u64,
756        )?;
757        self.encode(buf);
758        Ok(())
759    }
760
761    /// Encode one fetch object.
762    ///
763    /// Infallible because the status is taken as the authority on framing, and
764    /// lossy for the same reason: a payload set beside a status is discarded
765    /// here without a word. Prefer [`Self::encode_checked`], which refuses that
766    /// combination rather than resolving it.
767    pub fn encode(&self, buf: &mut impl BufMut) {
768        self.group_id.encode(buf);
769        self.subgroup_id.encode(buf);
770        self.object_id.encode(buf);
771        buf.put_u8(self.publisher_priority);
772        VarInt::from_u64(self.extension_headers.len() as u64).unwrap().encode(buf);
773        buf.put_slice(&self.extension_headers);
774        if let Some(status) = self.status {
775            VarInt::from_u64(0).unwrap().encode(buf);
776            VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
777        } else {
778            VarInt::from_u64(self.payload.len() as u64).unwrap().encode(buf);
779            buf.put_slice(&self.payload);
780        }
781    }
782
783    /// Decode one fetch object.
784    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
785        let group_id = VarInt::decode(buf)?;
786        let subgroup_id = VarInt::decode(buf)?;
787        let object_id = VarInt::decode(buf)?;
788        if buf.remaining() < 1 {
789            return Err(CodecError::UnexpectedEnd);
790        }
791        let publisher_priority = buf.get_u8();
792        let ext_len = VarInt::decode(buf)?.into_inner() as usize;
793        let extension_headers = crate::types::read_bytes(buf, ext_len)?;
794        let payload_length = VarInt::decode(buf)?.into_inner() as usize;
795        let (status, payload) = if payload_length == 0 {
796            let status_val = VarInt::decode(buf)?.into_inner();
797            let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
798            (Some(status), Vec::new())
799        } else {
800            (None, crate::types::read_bytes(buf, payload_length)?)
801        };
802        check_extensions_against_status(
803            status.map(|s| s.as_u64()),
804            extension_headers.len() as u64,
805        )?;
806        Ok(FetchObject {
807            group_id,
808            subgroup_id,
809            object_id,
810            publisher_priority,
811            extension_headers,
812            status,
813            payload,
814        })
815    }
816
817    /// Decode one fetch object's framing without copying its payload.
818    ///
819    /// Consumes exactly the bytes [`Self::decode`] consumes.
820    pub fn decode_meta(buf: &mut impl Buf) -> Result<FetchObjectMeta, CodecError> {
821        let start = buf.remaining();
822        let group_id = VarInt::decode(buf)?.into_inner();
823        let subgroup_id = VarInt::decode(buf)?.into_inner();
824        let object_id = VarInt::decode(buf)?.into_inner();
825        if buf.remaining() < 1 {
826            return Err(CodecError::UnexpectedEnd);
827        }
828        let publisher_priority = buf.get_u8();
829        let extension_headers_len = VarInt::decode(buf)?.into_inner();
830        skip(buf, extension_headers_len)?;
831        let payload_length = VarInt::decode(buf)?.into_inner();
832        let status = if payload_length == 0 {
833            let status_val = VarInt::decode(buf)?.into_inner();
834            Some(ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?.as_u64())
835        } else {
836            skip(buf, payload_length)?;
837            None
838        };
839        check_extensions_against_status(status, extension_headers_len)?;
840        Ok(FetchObjectMeta {
841            group_id,
842            subgroup_id,
843            object_id,
844            publisher_priority,
845            extension_headers_len,
846            payload_length,
847            status,
848            wire_len: (start - buf.remaining()) as u64,
849        })
850    }
851}
852
853// ============================================================
854// Datagram (Type 0x00..=0x07, 0x20..=0x21)
855// ============================================================
856
857/// Datagram type byte.
858///
859/// Bit layout (low nibble):
860///
861/// * bit 0 (`0x01`) — Extensions Present
862/// * bit 1 (`0x02`) — End of Group
863/// * bit 2 (`0x04`) — Object ID **absent** (when set, Object ID = 0)
864///
865/// Status variants use the high nibble (`0x20..=0x21`). Only types
866/// `0x00..=0x07`, `0x20`, `0x21` are defined.
867#[derive(Debug, Clone, Copy, PartialEq, Eq)]
868pub struct DatagramType(u8);
869
870impl DatagramType {
871    /// Raw wire byte.
872    pub fn as_u8(self) -> u8 {
873        self.0
874    }
875
876    /// Validate and wrap a raw wire byte.
877    pub fn from_u8(v: u8) -> Option<Self> {
878        if (0x00..=0x07).contains(&v) || v == 0x20 || v == 0x21 {
879            Some(DatagramType(v))
880        } else {
881            None
882        }
883    }
884
885    /// Build a payload-bearing datagram type (`0x00..=0x07`).
886    pub fn payload(object_id_present: bool, extensions_present: bool, end_of_group: bool) -> Self {
887        let mut v: u8 = 0x00;
888        if extensions_present {
889            v |= 0x01;
890        }
891        if end_of_group {
892            v |= 0x02;
893        }
894        if !object_id_present {
895            v |= 0x04;
896        }
897        DatagramType(v)
898    }
899
900    /// Build a status-only datagram type (`0x20` or `0x21`).
901    pub fn status(extensions_present: bool) -> Self {
902        if extensions_present {
903            DatagramType(0x21)
904        } else {
905            DatagramType(0x20)
906        }
907    }
908
909    /// True when the datagram carries an Object Status instead of a
910    /// payload (types `0x20` / `0x21`).
911    pub fn is_status(self) -> bool {
912        self.0 >= 0x20
913    }
914
915    /// True when the datagram carries an explicit Object ID field.
916    pub fn object_id_present(self) -> bool {
917        // Bit 2 is only meaningful in the 0x00..=0x07 range; status
918        // variants (0x20/0x21) always carry an Object ID.
919        if self.is_status() {
920            true
921        } else {
922            self.0 & 0x04 == 0
923        }
924    }
925
926    /// True if the last object of the group is conveyed.
927    pub fn end_of_group(self) -> bool {
928        !self.is_status() && (self.0 & 0x02 != 0)
929    }
930
931    /// True if extension headers are present in this datagram.
932    pub fn extensions_present(self) -> bool {
933        self.0 & 0x01 != 0
934    }
935}
936
937/// Datagram carrying a single object.
938#[derive(Debug, Clone, PartialEq, Eq)]
939pub struct DatagramObject {
940    /// Datagram type byte.
941    pub datagram_type: DatagramType,
942    /// Track alias.
943    pub track_alias: VarInt,
944    /// Group ID.
945    pub group_id: VarInt,
946    /// Object ID. Defaults to 0 when
947    /// [`DatagramType::object_id_present`] is false.
948    pub object_id: VarInt,
949    /// Publisher priority.
950    pub publisher_priority: u8,
951    /// Raw extension-header bytes (empty unless
952    /// [`DatagramType::extensions_present`] is true).
953    pub extension_headers: Vec<u8>,
954    /// Object status (only present for status-type datagrams).
955    pub status: Option<ObjectStatus>,
956    /// Object payload (empty for status-type datagrams).
957    pub payload: Vec<u8>,
958}
959
960impl DatagramObject {
961    /// Encode the datagram in full, refusing a field the type byte cannot
962    /// carry.
963    ///
964    /// Draft-14 Section 10.3.1 states the framing rule outright: "The Object
965    /// Status field and Object Payload are mutually exclusive." Types 0x00
966    /// through 0x07 carry a payload and omit the status field; types 0x20 and
967    /// 0x21 carry a status field and have no payload. This value can hold both
968    /// at once, and [`Self::encode`] resolves the disagreement by writing
969    /// whichever one the type byte announces and discarding the other without a
970    /// word. An End of Group marker written under a payload type does not
971    /// arrive late or malformed — it does not arrive at all, and the receiver
972    /// sees an ordinary object in its place; a payload written under a status
973    /// type vanishes the same way.
974    ///
975    /// [`ObjectStatus::Normal`] under a payload type is not that case and is
976    /// accepted. Section 10.2.1.1 says "Any object with a status code other
977    /// than zero MUST have an empty payload", so Normal is the status a
978    /// payload-bearing object already has, and stating it asks for exactly the
979    /// bytes leaving it out asks for.
980    ///
981    /// Errors with [`CodecError::InvalidField`] on either lossy combination,
982    /// before any byte is written, so a refused datagram leaves `buf`
983    /// untouched.
984    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
985        if self.datagram_type.is_status() {
986            if !self.payload.is_empty() {
987                return Err(CodecError::InvalidField);
988            }
989        } else if matches!(self.status, Some(status) if status != ObjectStatus::Normal) {
990            return Err(CodecError::InvalidField);
991        }
992        // The extension block is the other field the type byte governs, and it
993        // is governed in both directions. A type that announces extensions must
994        // carry some, because Section 10.3.1 makes a declared length of 0 a
995        // session-closing offence on receipt; a type that announces none cannot
996        // carry any, and `encode` would drop them in silence.
997        if self.datagram_type.extensions_present() {
998            if self.extension_headers.is_empty() {
999                return Err(CodecError::InvalidField);
1000            }
1001        } else if !self.extension_headers.is_empty() {
1002            return Err(CodecError::InvalidField);
1003        }
1004        check_extensions_against_status(
1005            self.status.map(|s| s.as_u64()),
1006            self.extension_headers.len() as u64,
1007        )?;
1008        self.encode(buf);
1009        Ok(())
1010    }
1011
1012    /// Encode the datagram in full.
1013    ///
1014    /// The type byte is taken as the authority on framing, which is what makes
1015    /// this infallible — and what makes it lossy when the value disagrees with
1016    /// itself. A `status` set under a payload type, or a `payload` set under a
1017    /// status type, is discarded here without a word. Prefer
1018    /// [`Self::encode_checked`], which refuses those combinations instead of
1019    /// resolving them.
1020    pub fn encode(&self, buf: &mut impl BufMut) {
1021        VarInt::from_u64(self.datagram_type.as_u8() as u64).unwrap().encode(buf);
1022        self.track_alias.encode(buf);
1023        self.group_id.encode(buf);
1024        if self.datagram_type.object_id_present() {
1025            self.object_id.encode(buf);
1026        }
1027        buf.put_u8(self.publisher_priority);
1028        if self.datagram_type.extensions_present() {
1029            VarInt::from_u64(self.extension_headers.len() as u64).unwrap().encode(buf);
1030            buf.put_slice(&self.extension_headers);
1031        }
1032        if self.datagram_type.is_status() {
1033            let status = self.status.unwrap_or(ObjectStatus::Normal);
1034            VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
1035        } else {
1036            buf.put_slice(&self.payload);
1037        }
1038    }
1039
1040    /// Decode a datagram. The buffer must contain the full datagram —
1041    /// payload-bearing types extend to the end of the QUIC datagram,
1042    /// which the caller is responsible for delimiting.
1043    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1044        let type_val = VarInt::decode(buf)?.into_inner();
1045        if type_val > 0xFF {
1046            return Err(CodecError::UnknownDatagramType(type_val));
1047        }
1048        let datagram_type = DatagramType::from_u8(type_val as u8)
1049            .ok_or(CodecError::UnknownDatagramType(type_val))?;
1050        let track_alias = VarInt::decode(buf)?;
1051        let group_id = VarInt::decode(buf)?;
1052        let object_id = if datagram_type.object_id_present() {
1053            VarInt::decode(buf)?
1054        } else {
1055            VarInt::from_u64(0).unwrap()
1056        };
1057        if buf.remaining() < 1 {
1058            return Err(CodecError::UnexpectedEnd);
1059        }
1060        let publisher_priority = buf.get_u8();
1061        let extension_headers = if datagram_type.extensions_present() {
1062            let ext_len = VarInt::decode(buf)?.into_inner() as usize;
1063            // Section 10.3.1: "If an endpoint receives a datagram with
1064            // Extensions Present as 'Yes' and a Extension Headers Length of 0,
1065            // it MUST close the session with PROTOCOL_VIOLATION." A datagram
1066            // with no extensions has a type byte that says so, and the two
1067            // spellings of "no extensions" are not interchangeable here.
1068            //
1069            // Subgroup streams say the opposite in Section 10.4.2 — there the
1070            // type byte is fixed for the whole stream, so an object with no
1071            // extensions has nowhere to say it but a length of 0. Only the
1072            // datagram carries this rule.
1073            if ext_len == 0 {
1074                return Err(CodecError::InvalidField);
1075            }
1076            crate::types::read_bytes(buf, ext_len)?
1077        } else {
1078            Vec::new()
1079        };
1080        let (status, payload) = if datagram_type.is_status() {
1081            let status_val = VarInt::decode(buf)?.into_inner();
1082            let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
1083            (Some(status), Vec::new())
1084        } else {
1085            let remaining = buf.remaining();
1086            (None, crate::types::read_bytes(buf, remaining)?)
1087        };
1088        check_extensions_against_status(
1089            status.map(|s| s.as_u64()),
1090            extension_headers.len() as u64,
1091        )?;
1092        Ok(DatagramObject {
1093            datagram_type,
1094            track_alias,
1095            group_id,
1096            object_id,
1097            publisher_priority,
1098            extension_headers,
1099            status,
1100            payload,
1101        })
1102    }
1103}
1104
1105#[cfg(test)]
1106mod tests {
1107    use super::*;
1108
1109    fn vi(v: u64) -> VarInt {
1110        VarInt::from_u64(v).unwrap()
1111    }
1112
1113    // ── SubgroupStreamType flag helpers ─────────────────────
1114
1115    #[test]
1116    fn subgroup_type_0x10_all_off() {
1117        let t = SubgroupStreamType::from_u8(0x10).unwrap();
1118        assert!(!t.has_subgroup_id_field());
1119        assert!(!t.subgroup_id_is_first_object());
1120        assert!(!t.extensions_present());
1121        assert!(!t.contains_end_of_group());
1122    }
1123
1124    #[test]
1125    fn subgroup_type_0x15_explicit_with_ext() {
1126        let t = SubgroupStreamType::from_u8(0x15).unwrap();
1127        assert!(t.has_subgroup_id_field());
1128        assert!(!t.subgroup_id_is_first_object());
1129        assert!(t.extensions_present());
1130        assert!(!t.contains_end_of_group());
1131    }
1132
1133    #[test]
1134    fn subgroup_type_0x1d_all_on() {
1135        let t = SubgroupStreamType::from_u8(0x1D).unwrap();
1136        assert!(t.has_subgroup_id_field());
1137        assert!(t.extensions_present());
1138        assert!(t.contains_end_of_group());
1139    }
1140
1141    #[test]
1142    fn subgroup_type_0x12_first_object() {
1143        let t = SubgroupStreamType::from_u8(0x12).unwrap();
1144        assert!(!t.has_subgroup_id_field());
1145        assert!(t.subgroup_id_is_first_object());
1146        assert!(!t.extensions_present());
1147    }
1148
1149    #[test]
1150    fn subgroup_type_rejects_undefined() {
1151        for bad in [0x00u8, 0x0F, 0x16, 0x17, 0x1E, 0x1F, 0x20] {
1152            assert!(SubgroupStreamType::from_u8(bad).is_none(), "0x{bad:02x} should be rejected");
1153        }
1154    }
1155
1156    #[test]
1157    fn subgroup_type_from_flags_roundtrip() {
1158        for &f_sg in &[false, true] {
1159            for &f_first in &[false, true] {
1160                for &f_ext in &[false, true] {
1161                    for &f_eog in &[false, true] {
1162                        let t = SubgroupStreamType::from_flags(f_sg, f_first, f_ext, f_eog);
1163                        assert_eq!(t.has_subgroup_id_field(), f_sg);
1164                        // subgroup_id_is_first_object only meaningful when
1165                        // explicit field is absent
1166                        if !f_sg {
1167                            assert_eq!(t.subgroup_id_is_first_object(), f_first);
1168                        }
1169                        assert_eq!(t.extensions_present(), f_ext);
1170                        assert_eq!(t.contains_end_of_group(), f_eog);
1171                    }
1172                }
1173            }
1174        }
1175    }
1176
1177    // ── SubgroupHeader round-trip ───────────────────────────
1178
1179    #[test]
1180    fn subgroup_header_roundtrip_0x10() {
1181        let h = SubgroupHeader {
1182            stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1183            track_alias: vi(1),
1184            group_id: vi(0),
1185            subgroup_id: None,
1186            publisher_priority: 128,
1187        };
1188        let mut buf = Vec::new();
1189        h.encode(&mut buf);
1190        assert_eq!(buf[0], 0x10);
1191        let decoded = SubgroupHeader::decode(&mut &buf[..]).unwrap();
1192        assert_eq!(decoded, h);
1193    }
1194
1195    #[test]
1196    fn subgroup_header_roundtrip_explicit_subgroup() {
1197        let h = SubgroupHeader {
1198            stream_type: SubgroupStreamType::from_u8(0x14).unwrap(),
1199            track_alias: vi(5),
1200            group_id: vi(10),
1201            subgroup_id: Some(vi(2)),
1202            publisher_priority: 64,
1203        };
1204        let mut buf = Vec::new();
1205        h.encode(&mut buf);
1206        let decoded = SubgroupHeader::decode(&mut &buf[..]).unwrap();
1207        assert_eq!(decoded, h);
1208    }
1209
1210    #[test]
1211    fn subgroup_header_decode_rejects_bad_type() {
1212        // 0x16 falls in the gap between the two ranges Table 4 assigns,
1213        // 0x10-0x15 and 0x18-0x1D, so no table names it. Draft-14 states no
1214        // list of invalid Types the way drafts 16 and later do — the value is
1215        // simply one it does not have, which Section 10 answers by ending the
1216        // session.
1217        let buf = [0x16u8, 0x01, 0x00, 0x80];
1218        let err = SubgroupHeader::decode(&mut &buf[..]).unwrap_err();
1219        assert!(
1220            matches!(err, CodecError::UnknownStreamType(0x16)),
1221            "an unassigned Type must be named as one, got {err:?}"
1222        );
1223    }
1224
1225    #[test]
1226    fn subgroup_header_decode_does_not_call_the_fetch_type_unknown() {
1227        // 0x05 is FETCH_HEADER, which Table 4 assigns. A subgroup reader
1228        // refuses it, but the disagreement is with the caller rather than with
1229        // the draft, so it must not reach for the rule that ends the session.
1230        let buf = [0x05u8, 0x01, 0x00, 0x80];
1231        let err = SubgroupHeader::decode(&mut &buf[..]).unwrap_err();
1232        assert!(
1233            matches!(err, CodecError::InvalidField),
1234            "a fetch stream at the subgroup reader must be refused without naming the \
1235             unknown-stream-type rule, got {err:?}"
1236        );
1237    }
1238
1239    // ── Subgroup object reader (delta + extensions) ─────────
1240
1241    #[test]
1242    fn subgroup_reader_delta_sequential_ids() {
1243        // Type 0x10: no subgroup field, no extensions, no eog
1244        let header = SubgroupHeader {
1245            stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1246            track_alias: vi(1),
1247            group_id: vi(0),
1248            subgroup_id: None,
1249            publisher_priority: 0,
1250        };
1251
1252        let mut write = SubgroupObjectReader::new(&header);
1253        let mut buf = Vec::new();
1254        for i in 0..3u64 {
1255            let obj = SubgroupObject {
1256                object_id: vi(i),
1257                extension_headers: vec![],
1258                status: None,
1259                payload: vec![0xAA + i as u8; 4],
1260            };
1261            write.write_object(&obj, &mut buf).unwrap();
1262        }
1263
1264        let mut read = SubgroupObjectReader::new(&header);
1265        let mut cursor = &buf[..];
1266        let o0 = read.read_object(&mut cursor).unwrap();
1267        assert_eq!(o0.object_id.into_inner(), 0);
1268        assert_eq!(o0.payload, vec![0xAA; 4]);
1269        let o1 = read.read_object(&mut cursor).unwrap();
1270        assert_eq!(o1.object_id.into_inner(), 1);
1271        let o2 = read.read_object(&mut cursor).unwrap();
1272        assert_eq!(o2.object_id.into_inner(), 2);
1273    }
1274
1275    #[test]
1276    fn subgroup_reader_delta_sparse_ids() {
1277        // Object IDs 5, 10, 11 — deltas are 5, 4, 0
1278        let header = SubgroupHeader {
1279            stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1280            track_alias: vi(1),
1281            group_id: vi(0),
1282            subgroup_id: None,
1283            publisher_priority: 0,
1284        };
1285        let mut write = SubgroupObjectReader::new(&header);
1286        let mut buf = Vec::new();
1287        for &id in &[5u64, 10, 11] {
1288            write
1289                .write_object(
1290                    &SubgroupObject {
1291                        object_id: vi(id),
1292                        extension_headers: vec![],
1293                        status: None,
1294                        payload: vec![1, 2, 3],
1295                    },
1296                    &mut buf,
1297                )
1298                .unwrap();
1299        }
1300        let mut read = SubgroupObjectReader::new(&header);
1301        let mut cursor = &buf[..];
1302        assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 5);
1303        assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 10);
1304        assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 11);
1305    }
1306
1307    #[test]
1308    fn subgroup_reader_with_extensions() {
1309        // Type 0x11: extensions present
1310        let header = SubgroupHeader {
1311            stream_type: SubgroupStreamType::from_u8(0x11).unwrap(),
1312            track_alias: vi(1),
1313            group_id: vi(0),
1314            subgroup_id: None,
1315            publisher_priority: 0,
1316        };
1317        let mut write = SubgroupObjectReader::new(&header);
1318        let mut buf = Vec::new();
1319        write
1320            .write_object(
1321                &SubgroupObject {
1322                    object_id: vi(0),
1323                    extension_headers: vec![0x01, 0x02, 0x03],
1324                    status: None,
1325                    payload: vec![0xFF],
1326                },
1327                &mut buf,
1328            )
1329            .unwrap();
1330        let mut read = SubgroupObjectReader::new(&header);
1331        let o = read.read_object(&mut &buf[..]).unwrap();
1332        assert_eq!(o.extension_headers, vec![0x01, 0x02, 0x03]);
1333        assert_eq!(o.payload, vec![0xFF]);
1334    }
1335
1336    #[test]
1337    fn subgroup_reader_status_object() {
1338        let header = SubgroupHeader {
1339            stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1340            track_alias: vi(1),
1341            group_id: vi(0),
1342            subgroup_id: None,
1343            publisher_priority: 0,
1344        };
1345        let mut write = SubgroupObjectReader::new(&header);
1346        let mut buf = Vec::new();
1347        write
1348            .write_object(
1349                &SubgroupObject {
1350                    object_id: vi(7),
1351                    extension_headers: vec![],
1352                    status: Some(ObjectStatus::EndOfGroup),
1353                    payload: vec![],
1354                },
1355                &mut buf,
1356            )
1357            .unwrap();
1358        let mut read = SubgroupObjectReader::new(&header);
1359        let o = read.read_object(&mut &buf[..]).unwrap();
1360        assert_eq!(o.object_id.into_inner(), 7);
1361        assert_eq!(o.status, Some(ObjectStatus::EndOfGroup));
1362        assert!(o.payload.is_empty());
1363    }
1364
1365    #[test]
1366    fn subgroup_reader_meta_matches_read_object() {
1367        // Type 0x11: extensions present, so every field is exercised.
1368        let header = SubgroupHeader {
1369            stream_type: SubgroupStreamType::from_u8(0x11).unwrap(),
1370            track_alias: vi(1),
1371            group_id: vi(0),
1372            subgroup_id: None,
1373            publisher_priority: 0,
1374        };
1375        let mut write = SubgroupObjectReader::new(&header);
1376        let mut buf = Vec::new();
1377        for (id, status) in
1378            [(0u64, None), (4, Some(ObjectStatus::EndOfGroup)), (9, None)].into_iter()
1379        {
1380            write
1381                .write_object(
1382                    &SubgroupObject {
1383                        object_id: vi(id),
1384                        extension_headers: vec![0x0A, 0x0B],
1385                        status,
1386                        payload: if status.is_some() { vec![] } else { vec![0xEE; 3] },
1387                    },
1388                    &mut buf,
1389                )
1390                .unwrap();
1391        }
1392
1393        let mut full = SubgroupObjectReader::new(&header);
1394        let mut meta = SubgroupObjectReader::new(&header);
1395        let mut full_cursor = &buf[..];
1396        let mut meta_cursor = &buf[..];
1397        for _ in 0..3 {
1398            let before = meta_cursor.remaining();
1399            let o = full.read_object(&mut full_cursor).unwrap();
1400            let m = meta.read_object_meta(&mut meta_cursor).unwrap();
1401            assert_eq!(m.object_id, o.object_id.into_inner());
1402            assert_eq!(m.extension_headers_len, o.extension_headers.len() as u64);
1403            assert_eq!(m.payload_length, o.payload.len() as u64);
1404            assert_eq!(m.status, o.status.map(|s| s.as_u64()));
1405            assert_eq!(m.wire_len, (before - meta_cursor.remaining()) as u64);
1406            assert_eq!(full_cursor.remaining(), meta_cursor.remaining());
1407        }
1408        assert!(meta_cursor.is_empty());
1409    }
1410
1411    #[test]
1412    fn subgroup_reader_meta_short_buffer_is_unexpected_end() {
1413        let header = SubgroupHeader {
1414            stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1415            track_alias: vi(1),
1416            group_id: vi(0),
1417            subgroup_id: None,
1418            publisher_priority: 0,
1419        };
1420        let mut write = SubgroupObjectReader::new(&header);
1421        let mut buf = Vec::new();
1422        write
1423            .write_object(
1424                &SubgroupObject {
1425                    object_id: vi(0),
1426                    extension_headers: vec![],
1427                    status: None,
1428                    payload: vec![1, 2, 3, 4],
1429                },
1430                &mut buf,
1431            )
1432            .unwrap();
1433
1434        for cut in 1..buf.len() {
1435            let mut read = SubgroupObjectReader::new(&header);
1436            let err = read.read_object_meta(&mut &buf[..cut]).unwrap_err();
1437            assert!(
1438                matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
1439                "cut {cut} gave {err:?}"
1440            );
1441        }
1442    }
1443
1444    // ── FetchHeader + FetchObject ───────────────────────────
1445
1446    #[test]
1447    fn fetch_header_roundtrip() {
1448        let h = FetchHeader { request_id: vi(99) };
1449        let mut buf = Vec::new();
1450        h.encode(&mut buf);
1451        assert_eq!(buf[0], 0x05);
1452        assert_eq!(FetchHeader::decode(&mut &buf[..]).unwrap(), h);
1453    }
1454
1455    #[test]
1456    fn fetch_header_rejects_wrong_type() {
1457        let buf = [0x10u8, 0x05];
1458        assert!(FetchHeader::decode(&mut &buf[..]).is_err());
1459    }
1460
1461    #[test]
1462    fn fetch_object_roundtrip_with_payload() {
1463        let obj = FetchObject {
1464            group_id: vi(3),
1465            subgroup_id: vi(1),
1466            object_id: vi(7),
1467            publisher_priority: 200,
1468            extension_headers: vec![0xAA, 0xBB],
1469            status: None,
1470            payload: vec![1, 2, 3, 4],
1471        };
1472        let mut buf = Vec::new();
1473        obj.encode(&mut buf);
1474        assert_eq!(FetchObject::decode(&mut &buf[..]).unwrap(), obj);
1475    }
1476
1477    #[test]
1478    fn fetch_object_roundtrip_status() {
1479        let obj = FetchObject {
1480            group_id: vi(3),
1481            subgroup_id: vi(1),
1482            object_id: vi(8),
1483            publisher_priority: 200,
1484            extension_headers: vec![],
1485            status: Some(ObjectStatus::ObjectDoesNotExist),
1486            payload: vec![],
1487        };
1488        let mut buf = Vec::new();
1489        obj.encode(&mut buf);
1490        assert_eq!(FetchObject::decode(&mut &buf[..]).unwrap(), obj);
1491    }
1492
1493    #[test]
1494    fn fetch_object_meta_matches_decode() {
1495        for obj in [
1496            FetchObject {
1497                group_id: vi(3),
1498                subgroup_id: vi(1),
1499                object_id: vi(7),
1500                publisher_priority: 200,
1501                extension_headers: vec![0xAA, 0xBB],
1502                status: None,
1503                payload: vec![1, 2, 3, 4],
1504            },
1505            FetchObject {
1506                group_id: vi(4),
1507                subgroup_id: vi(0),
1508                object_id: vi(8),
1509                publisher_priority: 1,
1510                extension_headers: vec![],
1511                status: Some(ObjectStatus::EndOfTrack),
1512                payload: vec![],
1513            },
1514        ] {
1515            let mut buf = Vec::new();
1516            obj.encode(&mut buf);
1517            let mut decode_cursor = &buf[..];
1518            let mut meta_cursor = &buf[..];
1519            let decoded = FetchObject::decode(&mut decode_cursor).unwrap();
1520            let meta = FetchObject::decode_meta(&mut meta_cursor).unwrap();
1521            assert_eq!(meta.group_id, decoded.group_id.into_inner());
1522            assert_eq!(meta.subgroup_id, decoded.subgroup_id.into_inner());
1523            assert_eq!(meta.object_id, decoded.object_id.into_inner());
1524            assert_eq!(meta.publisher_priority, decoded.publisher_priority);
1525            assert_eq!(meta.extension_headers_len, decoded.extension_headers.len() as u64);
1526            assert_eq!(meta.payload_length, decoded.payload.len() as u64);
1527            assert_eq!(meta.status, decoded.status.map(|s| s.as_u64()));
1528            assert_eq!(meta.wire_len, buf.len() as u64);
1529            assert!(meta_cursor.is_empty());
1530            assert_eq!(decode_cursor.remaining(), meta_cursor.remaining());
1531        }
1532    }
1533
1534    // ── DatagramType ────────────────────────────────────────
1535
1536    #[test]
1537    fn datagram_type_variants() {
1538        let t0 = DatagramType::from_u8(0x00).unwrap();
1539        assert!(t0.object_id_present());
1540        assert!(!t0.extensions_present());
1541        assert!(!t0.end_of_group());
1542        assert!(!t0.is_status());
1543
1544        let t7 = DatagramType::from_u8(0x07).unwrap();
1545        assert!(!t7.object_id_present()); // bit 2 set
1546        assert!(t7.extensions_present());
1547        assert!(t7.end_of_group());
1548        assert!(!t7.is_status());
1549
1550        let t20 = DatagramType::from_u8(0x20).unwrap();
1551        assert!(t20.is_status());
1552        assert!(!t20.extensions_present());
1553        // Status datagrams always carry Object ID
1554        assert!(t20.object_id_present());
1555
1556        let t21 = DatagramType::from_u8(0x21).unwrap();
1557        assert!(t21.is_status());
1558        assert!(t21.extensions_present());
1559    }
1560
1561    #[test]
1562    fn datagram_type_rejects_undefined() {
1563        for bad in [0x08u8, 0x10, 0x1F, 0x22, 0x80] {
1564            assert!(DatagramType::from_u8(bad).is_none(), "0x{bad:02x}");
1565        }
1566    }
1567
1568    // ── DatagramObject round-trip ───────────────────────────
1569
1570    #[test]
1571    fn datagram_object_0x00_roundtrip() {
1572        let d = DatagramObject {
1573            datagram_type: DatagramType::from_u8(0x00).unwrap(),
1574            track_alias: vi(1),
1575            group_id: vi(2),
1576            object_id: vi(3),
1577            publisher_priority: 100,
1578            extension_headers: vec![],
1579            status: None,
1580            payload: vec![0xDE, 0xAD, 0xBE, 0xEF],
1581        };
1582        let mut buf = Vec::new();
1583        d.encode(&mut buf);
1584        assert_eq!(DatagramObject::decode(&mut &buf[..]).unwrap(), d);
1585    }
1586
1587    #[test]
1588    fn datagram_object_0x04_no_object_id() {
1589        // 0x04: no object id field, implicit 0
1590        let d = DatagramObject {
1591            datagram_type: DatagramType::from_u8(0x04).unwrap(),
1592            track_alias: vi(1),
1593            group_id: vi(2),
1594            object_id: vi(0),
1595            publisher_priority: 100,
1596            extension_headers: vec![],
1597            status: None,
1598            payload: vec![0xAA],
1599        };
1600        let mut buf = Vec::new();
1601        d.encode(&mut buf);
1602        let decoded = DatagramObject::decode(&mut &buf[..]).unwrap();
1603        assert_eq!(decoded, d);
1604    }
1605
1606    #[test]
1607    fn datagram_object_0x21_status_with_extensions() {
1608        let d = DatagramObject {
1609            datagram_type: DatagramType::from_u8(0x21).unwrap(),
1610            track_alias: vi(9),
1611            group_id: vi(4),
1612            object_id: vi(11),
1613            publisher_priority: 50,
1614            extension_headers: vec![0xCA, 0xFE],
1615            status: Some(ObjectStatus::EndOfTrack),
1616            payload: vec![],
1617        };
1618        let mut buf = Vec::new();
1619        d.encode(&mut buf);
1620        assert_eq!(DatagramObject::decode(&mut &buf[..]).unwrap(), d);
1621    }
1622
1623    /// A datagram under `type_byte` holding both `status` and `payload`.
1624    fn datagram(type_byte: u8, status: Option<ObjectStatus>, payload: Vec<u8>) -> DatagramObject {
1625        DatagramObject {
1626            datagram_type: DatagramType::from_u8(type_byte).unwrap(),
1627            track_alias: vi(1),
1628            group_id: vi(0),
1629            object_id: vi(0),
1630            publisher_priority: 128,
1631            extension_headers: vec![],
1632            status,
1633            payload,
1634        }
1635    }
1636
1637    /// Neither of the two fields the type byte cannot carry is dropped in
1638    /// silence; both are refused.
1639    ///
1640    /// Draft-14 Section 10.3.1: "The Object Status field and Object Payload are
1641    /// mutually exclusive." Types 0x00 through 0x07 write a payload and no
1642    /// status; types 0x20 and 0x21 write a status and no payload. A
1643    /// [`DatagramObject`] can hold both at once, and [`DatagramObject::encode`]
1644    /// resolves that by writing whichever the type byte announces and
1645    /// discarding the other — the loss this gate exists for. The middle of each
1646    /// half observes the discard directly, so the gate states the old behaviour
1647    /// as well as the new.
1648    ///
1649    /// Normal beside a payload is exempt and checked at the end. Section
1650    /// 10.2.1.1 says "Any object with a status code other than zero MUST have
1651    /// an empty payload", so Normal is the status a payload-bearing object
1652    /// already has, and naming it asks for the same bytes as leaving it out.
1653    ///
1654    /// # What this catches, observed by making each change and running it
1655    ///
1656    /// Dropping the status half of the check, leaving the type byte to decide
1657    /// as it did before:
1658    ///
1659    /// ```text
1660    /// encode_checked must refuse ObjectDoesNotExist under a payload type; got Ok(())
1661    /// ```
1662    ///
1663    /// Dropping the payload half instead:
1664    ///
1665    /// ```text
1666    /// encode_checked must refuse a payload under a status type; got Ok(())
1667    /// ```
1668    ///
1669    /// Widening the status half to refuse Normal beside a payload as well:
1670    ///
1671    /// ```text
1672    /// encode_checked refused a Normal status under a payload type: InvalidField
1673    /// ```
1674    #[test]
1675    fn encode_checked_refuses_the_field_the_type_byte_cannot_carry() {
1676        for &status in ObjectStatus::ALL {
1677            if status == ObjectStatus::Normal {
1678                continue;
1679            }
1680
1681            // A status under a payload type: the status is what would go.
1682            let object = datagram(0x00, Some(status), vec![0xDE, 0xAD]);
1683            let mut refused = Vec::new();
1684            let result = object.encode_checked(&mut refused);
1685            assert!(
1686                matches!(result, Err(CodecError::InvalidField)),
1687                "encode_checked must refuse {status:?} under a payload type; got {result:?}"
1688            );
1689            assert!(refused.is_empty(), "a refused {status:?} datagram still wrote {refused:?}");
1690
1691            let mut dropped = Vec::new();
1692            object.encode(&mut dropped);
1693            let decoded = DatagramObject::decode(&mut &dropped[..])
1694                .unwrap_or_else(|e| panic!("the lossy encoding of {status:?} must parse: {e:?}"));
1695            assert_eq!(decoded.status, None, "{status:?} is exactly what `encode` loses here");
1696            assert_eq!(decoded.payload, vec![0xDE, 0xAD]);
1697
1698            // The same status under a status type is representable, so it is
1699            // written and read back unchanged.
1700            let mut carried = Vec::new();
1701            datagram(0x20, Some(status), vec![]).encode_checked(&mut carried).unwrap_or_else(|e| {
1702                panic!("encode_checked refused a status-type {status:?}: {e:?}")
1703            });
1704            let decoded = DatagramObject::decode(&mut &carried[..]).unwrap();
1705            assert_eq!(decoded.status, Some(status), "{status:?} lost its status");
1706        }
1707
1708        // A payload under a status type: now the payload is what would go.
1709        let object = datagram(0x20, Some(ObjectStatus::EndOfGroup), vec![0xDE, 0xAD]);
1710        let mut refused = Vec::new();
1711        let result = object.encode_checked(&mut refused);
1712        assert!(
1713            matches!(result, Err(CodecError::InvalidField)),
1714            "encode_checked must refuse a payload under a status type; got {result:?}"
1715        );
1716        assert!(refused.is_empty(), "a refused datagram still wrote {refused:?}");
1717
1718        let mut dropped = Vec::new();
1719        object.encode(&mut dropped);
1720        let decoded = DatagramObject::decode(&mut &dropped[..]).unwrap();
1721        assert!(decoded.payload.is_empty(), "the payload is exactly what `encode` loses here");
1722        assert_eq!(decoded.status, Some(ObjectStatus::EndOfGroup));
1723
1724        // Normal beside a payload asks for the bytes a payload datagram
1725        // already writes, so it is accepted and writes exactly those.
1726        let mut named = Vec::new();
1727        datagram(0x00, Some(ObjectStatus::Normal), vec![0xDE, 0xAD])
1728            .encode_checked(&mut named)
1729            .unwrap_or_else(|e| {
1730                panic!("encode_checked refused a Normal status under a payload type: {e:?}")
1731            });
1732        let mut unnamed = Vec::new();
1733        datagram(0x00, None, vec![0xDE, 0xAD]).encode_checked(&mut unnamed).unwrap();
1734        assert_eq!(named, unnamed, "naming Normal must ask for the bytes leaving it out asks for");
1735    }
1736}