Skip to main content

moqtap_codec/draft11/
data_stream.rs

1//! Draft-11 data stream header encoding and decoding.
2//!
3//! Changes from draft-09/10:
4//! - Datagram stream type IDs: 0x00 (no ext), 0x01 (with ext), 0x02 (status, no ext),
5//!   0x03 (status, with ext)
6//! - Subgroup stream types: 0x08-0x0D (6 variants based on subgroup_id encoding and extensions)
7//! - Fetch stream type: 0x05 (request_id only in header)
8//! - Object within subgroup: object_id + [ext_headers_length + extensions] + payload_length
9//!   + [object_status if payload_length=0]
10
11use super::types::ObjectStatus;
12use crate::error::CodecError;
13use crate::types::read_bytes;
14use crate::varint::VarInt;
15use bytes::{Buf, BufMut};
16
17/// Stream type IDs for draft-11 data streams.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[repr(u64)]
20pub enum StreamType {
21    /// Object datagram, no extensions (0x00).
22    Datagram = 0x00,
23    /// Object datagram, with extensions (0x01).
24    DatagramExt = 0x01,
25    /// Object datagram status, no extensions (0x02).
26    DatagramStatus = 0x02,
27    /// Object datagram status, with extensions (0x03).
28    DatagramStatusExt = 0x03,
29    /// Fetch response stream (0x05).
30    Fetch = 0x05,
31    /// Subgroup: subgroup_id=0, no extensions (0x08).
32    SubgroupZero = 0x08,
33    /// Subgroup: subgroup_id=0, with extensions (0x09).
34    SubgroupZeroExt = 0x09,
35    /// Subgroup: subgroup_id=first object ID, no extensions (0x0A).
36    SubgroupFirstObj = 0x0A,
37    /// Subgroup: subgroup_id=first object ID, with extensions (0x0B).
38    SubgroupFirstObjExt = 0x0B,
39    /// Subgroup: explicit subgroup_id, no extensions (0x0C).
40    SubgroupExplicit = 0x0C,
41    /// Subgroup: explicit subgroup_id, with extensions (0x0D).
42    SubgroupExplicitExt = 0x0D,
43}
44
45/// Hold an object to the rule that a non-existent object carries no extensions.
46///
47/// Section 9.1.1.2: "Any Object may have extension headers except those with
48/// Object Status 'Object Does Not Exist'. If an endpoint receives a non-existent
49/// Object containing extension headers it MUST close the session with a Protocol
50/// Violation."
51///
52/// The sentence is about a receiver, and it reaches all three carriers that can
53/// announce a status: an object on a subgroup stream, an object on a fetch
54/// stream, and a status datagram. A plain datagram has no status field, so it
55/// is the only carrier that cannot break the rule.
56///
57/// Reported under [`CodecError::ExtensionsOnNonExistentObject`], which is this
58/// rule and nothing else. It was [`CodecError::InvalidField`] until now, shared
59/// with a dozen unrelated malformations the draft does not answer with a close,
60/// which left a caller unable to act on the sentence above.
61fn check_extensions_against_status(
62    status: ObjectStatus,
63    extensions: &[u8],
64) -> Result<(), CodecError> {
65    if status == ObjectStatus::ObjectDoesNotExist && !extensions.is_empty() {
66        return Err(CodecError::ExtensionsOnNonExistentObject(extensions.len()));
67    }
68    Ok(())
69}
70
71impl StreamType {
72    /// Convert a raw stream type ID to a `StreamType`, if valid.
73    pub fn from_id(id: u64) -> Option<Self> {
74        match id {
75            0x00 => Some(StreamType::Datagram),
76            0x01 => Some(StreamType::DatagramExt),
77            0x02 => Some(StreamType::DatagramStatus),
78            0x03 => Some(StreamType::DatagramStatusExt),
79            0x05 => Some(StreamType::Fetch),
80            0x08 => Some(StreamType::SubgroupZero),
81            0x09 => Some(StreamType::SubgroupZeroExt),
82            0x0A => Some(StreamType::SubgroupFirstObj),
83            0x0B => Some(StreamType::SubgroupFirstObjExt),
84            0x0C => Some(StreamType::SubgroupExplicit),
85            0x0D => Some(StreamType::SubgroupExplicitExt),
86            _ => None,
87        }
88    }
89
90    /// Whether this stream type is a subgroup variant.
91    pub fn is_subgroup(&self) -> bool {
92        matches!(
93            self,
94            StreamType::SubgroupZero
95                | StreamType::SubgroupZeroExt
96                | StreamType::SubgroupFirstObj
97                | StreamType::SubgroupFirstObjExt
98                | StreamType::SubgroupExplicit
99                | StreamType::SubgroupExplicitExt
100        )
101    }
102
103    /// Whether this stream type includes extension headers on objects.
104    pub fn has_extensions(&self) -> bool {
105        matches!(
106            self,
107            StreamType::DatagramExt
108                | StreamType::DatagramStatusExt
109                | StreamType::SubgroupZeroExt
110                | StreamType::SubgroupFirstObjExt
111                | StreamType::SubgroupExplicitExt
112        )
113    }
114
115    /// True if this subgroup stream type puts an explicit Subgroup ID on the
116    /// wire.
117    ///
118    /// The Subgroup ID Field Present column of the SUBGROUP_HEADER type table
119    /// in Section 9.4.2. The other two columns of that row say what the
120    /// Subgroup ID *is* where the field is absent — zero, or the first
121    /// Object's ID — so this is only about the field, never about the value.
122    pub fn writes_subgroup_id(&self) -> bool {
123        matches!(self, StreamType::SubgroupExplicit | StreamType::SubgroupExplicitExt)
124    }
125}
126
127/// Which failure a leading unidirectional stream type that is not the one a
128/// reader wants is.
129///
130/// Section 9: "An endpoint that receives an unknown stream or datagram type
131/// MUST close the session." One sentence, two tables. The stream table assigns
132/// 0x05 for FETCH_HEADER and the range 0x08 to 0x0D for SUBGROUP_HEADER;
133/// everything outside those is unknown at the head of a stream, and the session
134/// ends.
135///
136/// The six subgroup values are one type spread over the combinations of two
137/// choices, which is why the range has no holes in it and why a value just past
138/// its end — 0x0E — is unknown rather than a subgroup variant this decoder has
139/// not learned yet.
140///
141/// [`StreamType`] holds both tables in one enum because draft-11's numbers do
142/// not collide across them, so membership in it is not the question. A datagram
143/// type at the head of a stream is unknown there, and this is where that is
144/// decided.
145fn stream_type_error(raw: u64) -> CodecError {
146    match StreamType::from_id(raw) {
147        Some(t) if t.is_subgroup() || t == StreamType::Fetch => CodecError::InvalidField,
148        _ => CodecError::UnknownStreamType(raw),
149    }
150}
151
152/// Which failure a leading datagram type that is not one a reader wants is.
153///
154/// The datagram half of the sentence quoted on `stream_type_error`, read
155/// against the other table: 0x00 to 0x03 are what it assigns, and everything
156/// else arriving as a datagram is unknown.
157///
158/// Every assigned datagram value is decodable here, so the
159/// [`CodecError::InvalidField`] arm is reached only by the stream types
160/// delivered as datagrams — the mirror image of the case above, and refused the
161/// same way, without ending the session over a number this draft defines.
162fn datagram_type_error(raw: u64) -> CodecError {
163    if StreamType::from_id(raw).is_some() {
164        CodecError::InvalidField
165    } else {
166        CodecError::UnknownDatagramType(raw)
167    }
168}
169
170// ── Extension helpers ─────────────────────────────────────────
171
172fn read_extension_bytes(buf: &mut impl Buf, byte_len: u64) -> Result<Vec<u8>, CodecError> {
173    read_bytes(buf, byte_len as usize)
174}
175
176// ============================================================
177// Subgroup stream header
178// ============================================================
179
180/// Subgroup stream header (unified across all 6 stream type variants).
181///
182/// Decoded representation includes `stream_type` to preserve the variant.
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct SubgroupHeader {
185    /// The stream type variant used for encoding.
186    pub stream_type: StreamType,
187    /// Track alias identifying the subscription.
188    pub track_alias: VarInt,
189    /// Group identifier.
190    pub group_id: VarInt,
191    /// Subgroup identifier within the group.
192    pub subgroup_id: VarInt,
193    /// Publisher priority for delivery ordering.
194    pub publisher_priority: u8,
195}
196
197impl SubgroupHeader {
198    /// Encode a subgroup stream header including its leading stream-type
199    /// field, so the bytes form the start of a data stream a peer can read.
200    ///
201    /// [`Self::encode`] writes the body alone, which is what a caller wants
202    /// once the stream is already open and what a caller must not use for its
203    /// first write. It is also the half that cannot stand on its own here,
204    /// because the stream type is what says whether a Subgroup ID follows it
205    /// and whether the objects on the stream carry extension headers.
206    pub fn encode_stream(&self, buf: &mut impl BufMut) {
207        VarInt::from_usize(self.stream_type as usize).encode(buf);
208        self.encode(buf);
209    }
210
211    /// Encode the subgroup header (always as explicit subgroup_id format).
212    /// Encode the header body, without its leading stream-type field.
213    ///
214    /// Driven by the stream type, and silent about a `subgroup_id` it decides
215    /// not to write: on a type whose Subgroup ID Field Present column reads No
216    /// the field is dropped, and the peer reads the subgroup the *type* names -
217    /// zero, or the first Object's ID - rather than the one in hand. Nothing is
218    /// malformed about the result, which is what makes it worth refusing rather
219    /// than tolerating. [`Self::encode_checked`] refuses it.
220    pub fn encode(&self, buf: &mut impl BufMut) {
221        self.track_alias.encode(buf);
222        self.group_id.encode(buf);
223        if self.stream_type.writes_subgroup_id() {
224            self.subgroup_id.encode(buf);
225        }
226        buf.put_u8(self.publisher_priority);
227    }
228
229    /// Encode the header body, refusing a Subgroup ID this stream type has
230    /// nowhere to put.
231    ///
232    /// [`Self::decode_with_type`] leaves the field at zero for every type that
233    /// does not carry it, so a decoded header always passes: the refusal is for
234    /// a header assembled by hand, where a caller set an ID the type will
235    /// discard.
236    ///
237    /// A zero is accepted under any type. It is what the decoder produces, and
238    /// on a Subgroup ID Value column reading `0` it is also the truth, so
239    /// refusing it would refuse the ordinary case to catch nothing.
240    ///
241    /// # Errors
242    ///
243    /// [`CodecError::InvalidField`] if a non-zero Subgroup ID sits under a
244    /// stream type that writes no Subgroup ID field.
245    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
246        if !self.stream_type.writes_subgroup_id() && self.subgroup_id.into_inner() != 0 {
247            return Err(CodecError::InvalidField);
248        }
249        self.encode(buf);
250        Ok(())
251    }
252
253    /// Decode a subgroup header (assumes explicit subgroup_id format).
254    ///
255    /// For stream-type-aware decoding, use [`Self::decode_with_type`].
256    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
257        Self::decode_with_type(StreamType::SubgroupExplicit, buf)
258    }
259
260    /// Decode a subgroup header with the specific stream type variant.
261    pub fn decode_with_type(
262        stream_type: StreamType,
263        buf: &mut impl Buf,
264    ) -> Result<Self, CodecError> {
265        let track_alias = VarInt::decode(buf)?;
266        let group_id = VarInt::decode(buf)?;
267        let subgroup_id = match stream_type {
268            StreamType::SubgroupZero | StreamType::SubgroupZeroExt => VarInt::from_usize(0),
269            StreamType::SubgroupExplicit | StreamType::SubgroupExplicitExt => VarInt::decode(buf)?,
270            // For FirstObj variants, subgroup_id is the first object's ID.
271            // We read it later from the first object. Set to 0 for now;
272            // the caller should update after reading the first object.
273            StreamType::SubgroupFirstObj | StreamType::SubgroupFirstObjExt => VarInt::from_usize(0),
274            _ => return Err(CodecError::InvalidField),
275        };
276        if buf.remaining() < 1 {
277            return Err(CodecError::UnexpectedEnd);
278        }
279        let publisher_priority = buf.get_u8();
280        Ok(Self { stream_type, track_alias, group_id, subgroup_id, publisher_priority })
281    }
282
283    /// Decode a subgroup header from the start of a data stream, consuming
284    /// the leading stream type varint and using it to select the variant.
285    ///
286    /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
287    /// not assign the leading type, which this draft answers with a close, and
288    /// with [`CodecError::InvalidField`] when it does assign it but not to a
289    /// subgroup. `stream_type_error` draws that line.
290    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
291        let raw = VarInt::decode(buf)?.into_inner();
292        let stream_type = StreamType::from_id(raw).ok_or_else(|| stream_type_error(raw))?;
293        if !stream_type.is_subgroup() {
294            return Err(stream_type_error(raw));
295        }
296        Self::decode_with_type(stream_type, buf)
297    }
298}
299
300// ============================================================
301// Object header within subgroup
302// ============================================================
303
304/// Object within a subgroup stream (draft-11).
305#[derive(Debug, Clone, PartialEq, Eq)]
306pub struct ObjectHeader {
307    /// Object identifier within the subgroup.
308    pub object_id: VarInt,
309    /// Total byte length of extension headers (0 if no extensions).
310    pub extension_headers_length: VarInt,
311    /// Raw extension bytes (opaque).
312    pub extensions: Vec<u8>,
313    /// Length of the object payload in bytes.
314    pub payload_length: VarInt,
315    /// Object status (Normal unless payload_length == 0).
316    pub object_status: ObjectStatus,
317}
318
319impl ObjectHeader {
320    /// Encode the object header in the framing that carries no extension
321    /// block.
322    ///
323    /// Lossy, and lossy in a way the caller cannot see: an object holding
324    /// extension headers is written without them and without a word. Which
325    /// framing is correct is not a property of the object at all - Section
326    /// 9.4.2 gives the stream's type an Extensions Present column, and every
327    /// object on the stream follows it - so this entry point can only guess,
328    /// and it guesses "absent". Prefer [`Self::encode_with_extensions`], which
329    /// is told, or [`Self::encode_checked`], which refuses what it would
330    /// otherwise drop.
331    pub fn encode(&self, buf: &mut impl BufMut) {
332        self.encode_with_extensions(false, buf);
333    }
334
335    /// Encode the header, refusing a status the framing cannot carry.
336    ///
337    /// Section 9.4.2 puts the Object Status field on the wire only when the
338    /// Object Payload Length is zero, and Section 9.1.1.1 says "Any object
339    /// with a status code other than zero MUST have an empty payload". A
340    /// non-zero status paired with a non-zero payload length therefore has no
341    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
342    /// ordinary object, which is a different object from the one the caller
343    /// described. This refuses instead.
344    ///
345    /// The datagram types on this draft already refuse the same pairing. These
346    /// two did not, and they are the ones a publisher writes on every stream.
347    ///
348    /// Extension headers are refused here rather than dropped, for a reason
349    /// the status rule does not share: this entry point writes the framing
350    /// that has no Extension Headers Length field, so the bytes have nowhere
351    /// to go. Writing them anyway is not an option and losing them silently
352    /// puts a stream on the wire that no reader can follow - a reader on an
353    /// extensions-bearing stream takes the Object Payload Length as the
354    /// extension length and every object after it is misread. A caller that
355    /// knows the stream's framing wants
356    /// [`Self::encode_checked_with_extensions`].
357    ///
358    /// # Errors
359    ///
360    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
361    /// a non-zero Object Payload Length, or if the object carries extension
362    /// headers this framing cannot write.
363    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
364        self.encode_checked_with_extensions(false, buf)
365    }
366
367    /// Encode the object header into a stream whose type has already settled
368    /// whether objects carry an extension block, refusing what that framing
369    /// cannot express.
370    ///
371    /// `has_extensions` is the stream's answer, not the object's: Section
372    /// 9.4.2 fixes it for the whole stream from the SUBGROUP_HEADER type, so
373    /// an object with no extensions on a stream that carries them still writes
374    /// a length of zero, and that is the one direction not refused here. The
375    /// other direction has no encoding, so it is refused.
376    ///
377    /// # Errors
378    ///
379    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
380    /// a non-zero Object Payload Length, or if `has_extensions` is `false`
381    /// while the object carries extension headers.
382    pub fn encode_checked_with_extensions(
383        &self,
384        has_extensions: bool,
385        buf: &mut impl BufMut,
386    ) -> Result<(), CodecError> {
387        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
388            return Err(CodecError::InvalidField);
389        }
390        if !has_extensions && !self.extensions.is_empty() {
391            return Err(CodecError::InvalidField);
392        }
393        self.encode_with_extensions(has_extensions, buf);
394        Ok(())
395    }
396
397    /// Encode the object header, writing the extension block only when the
398    /// stream's type says objects carry one.
399    ///
400    /// Infallible, and so unable to say that a `false` here discards the
401    /// extension headers the object holds. [`Self::encode_checked_with_extensions`]
402    /// is the same write with that refusal in front of it.
403    pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
404        self.object_id.encode(buf);
405        if has_extensions {
406            VarInt::from_usize(self.extensions.len()).encode(buf);
407            buf.put_slice(&self.extensions);
408        }
409        self.payload_length.encode(buf);
410        if self.payload_length.into_inner() == 0 {
411            VarInt::from_usize(self.object_status as usize).encode(buf);
412        }
413    }
414
415    /// Decode an object header (no extensions).
416    ///
417    /// For extension-aware decoding, use [`Self::decode_with_extensions`].
418    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
419        Self::decode_with_extensions(false, buf)
420    }
421
422    /// Decode an object header with extensions control.
423    pub fn decode_with_extensions(
424        has_extensions: bool,
425        buf: &mut impl Buf,
426    ) -> Result<Self, CodecError> {
427        let object_id = VarInt::decode(buf)?;
428        let (extension_headers_length, extensions) = if has_extensions {
429            let ehl = VarInt::decode(buf)?;
430            let ext = read_extension_bytes(buf, ehl.into_inner())?;
431            (ehl, ext)
432        } else {
433            (VarInt::from_usize(0), Vec::new())
434        };
435        let payload_length = VarInt::decode(buf)?;
436        let object_status = if payload_length.into_inner() == 0 {
437            let sv = VarInt::decode(buf)?.into_inner();
438            ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?
439        } else {
440            ObjectStatus::Normal
441        };
442        check_extensions_against_status(object_status, &extensions)?;
443        Ok(Self { object_id, extension_headers_length, extensions, payload_length, object_status })
444    }
445}
446
447// ============================================================
448// Datagram (types 0x00, 0x01)
449// ============================================================
450
451/// Datagram header (draft-11, types 0x00/0x01).
452///
453/// Payload is the remaining bytes in the datagram after the header.
454#[derive(Debug, Clone, PartialEq, Eq)]
455pub struct DatagramHeader {
456    /// Track alias identifying the subscription.
457    pub track_alias: VarInt,
458    /// Group identifier.
459    pub group_id: VarInt,
460    /// Object identifier within the group.
461    pub object_id: VarInt,
462    /// Publisher priority for delivery ordering.
463    pub publisher_priority: u8,
464    /// Total byte length of extension headers (0 for type 0x00).
465    pub extension_headers_length: VarInt,
466    /// Raw extension bytes.
467    pub extensions: Vec<u8>,
468}
469
470impl DatagramHeader {
471    /// Encode the datagram header in the framing that carries no extension
472    /// block.
473    ///
474    /// Lossy in the same way the subgroup object header is: an extension block
475    /// this value holds is dropped, because the framing being written has no
476    /// field for it. The type byte decides which framing is right, and this
477    /// entry point does not write the type byte, so it cannot consult it.
478    /// [`Datagram::encode`] does both together and never disagrees with itself;
479    /// this is the piece for a caller that has already written the type.
480    pub fn encode(&self, buf: &mut impl BufMut) {
481        self.encode_with_extensions(false, buf);
482    }
483
484    /// Encode the datagram header with extensions control.
485    pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
486        self.track_alias.encode(buf);
487        self.group_id.encode(buf);
488        self.object_id.encode(buf);
489        buf.put_u8(self.publisher_priority);
490        if has_extensions {
491            VarInt::from_usize(self.extensions.len()).encode(buf);
492            buf.put_slice(&self.extensions);
493        }
494    }
495
496    /// Encode the datagram header, refusing what this framing cannot carry.
497    ///
498    /// No status is ever refused, and that is a fact about this draft rather
499    /// than a check left out. This is the OBJECT_DATAGRAM of Section 9.2,
500    /// whose layout carries no Object Status field at all; a datagram that
501    /// states a status is the separate OBJECT_DATAGRAM_STATUS message, modelled
502    /// here as [`DatagramStatusHeader`]. So there is no status for
503    /// [`Self::encode`] to drop, and nothing for Section 9.1.1.1's "Any object
504    /// with a status code other than zero MUST have an empty payload" to rule
505    /// on: an object framed this way has status zero by construction.
506    ///
507    /// The extension block is a different matter. [`Self::encode`] writes the
508    /// framing without one, so a block this value holds has nowhere to go, and
509    /// dropping it silently is what puts a datagram on the wire describing
510    /// something other than what the caller built. That is refused here.
511    ///
512    /// The fallible signature is also what lets one entry point span every
513    /// draft. `dispatch::AnyDatagramHeader::encode` calls this on all thirteen,
514    /// and the drafts whose payload-bearing datagram *does* carry a status field
515    /// need somewhere to say no.
516    ///
517    /// # Errors
518    ///
519    /// [`CodecError::InvalidField`] if the value carries extension headers,
520    /// which this framing has no field for.
521    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
522        if !self.extensions.is_empty() {
523            return Err(CodecError::InvalidField);
524        }
525        self.encode(buf);
526        Ok(())
527    }
528
529    /// Decode a datagram header (no extensions).
530    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
531        Self::decode_with_extensions(false, buf)
532    }
533
534    /// Decode a datagram header with extensions control.
535    pub fn decode_with_extensions(
536        has_extensions: bool,
537        buf: &mut impl Buf,
538    ) -> Result<Self, CodecError> {
539        let track_alias = VarInt::decode(buf)?;
540        let group_id = VarInt::decode(buf)?;
541        let object_id = VarInt::decode(buf)?;
542        if buf.remaining() < 1 {
543            return Err(CodecError::UnexpectedEnd);
544        }
545        let publisher_priority = buf.get_u8();
546        let (extension_headers_length, extensions) = if has_extensions {
547            let ehl = VarInt::decode(buf)?;
548            // A datagram whose type says extensions are present must actually carry
549            // some: receiving one with an Extension Headers Length of 0 closes the
550            // session. The opposite holds on a subgroup stream, where the type byte is
551            // fixed for the whole stream and an object with no extensions has no other
552            // way to say so, which is why this check belongs to the datagram readers
553            // alone.
554            if ehl.into_inner() == 0 {
555                return Err(CodecError::InvalidField);
556            }
557            let ext = read_extension_bytes(buf, ehl.into_inner())?;
558            (ehl, ext)
559        } else {
560            (VarInt::from_usize(0), Vec::new())
561        };
562        Ok(Self {
563            track_alias,
564            group_id,
565            object_id,
566            publisher_priority,
567            extension_headers_length,
568            extensions,
569        })
570    }
571}
572
573// ============================================================
574// Datagram Status (types 0x02, 0x03)
575// ============================================================
576
577/// Datagram status header (draft-11, types 0x02/0x03).
578#[derive(Debug, Clone, PartialEq, Eq)]
579pub struct DatagramStatusHeader {
580    /// Track alias identifying the subscription.
581    pub track_alias: VarInt,
582    /// Group identifier.
583    pub group_id: VarInt,
584    /// Object identifier within the group.
585    pub object_id: VarInt,
586    /// Publisher priority for delivery ordering.
587    pub publisher_priority: u8,
588    /// Total byte length of extension headers (0 for type 0x02).
589    pub extension_headers_length: VarInt,
590    /// Raw extension bytes.
591    pub extensions: Vec<u8>,
592    /// Object status code.
593    pub object_status: ObjectStatus,
594}
595
596impl DatagramStatusHeader {
597    /// Encode the datagram status header (no extensions).
598    pub fn encode(&self, buf: &mut impl BufMut) {
599        self.encode_with_extensions(false, buf);
600    }
601
602    /// Encode the status datagram header, refusing an extension block this
603    /// framing cannot carry.
604    ///
605    /// The same one-sided rule the payload-bearing header obeys, and the same
606    /// reason for it: [`Self::encode`] writes the framing without a block, so
607    /// bytes held here would be dropped rather than written.
608    ///
609    /// # Errors
610    ///
611    /// [`CodecError::InvalidField`] if the value carries extension headers.
612    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
613        if !self.extensions.is_empty() {
614            return Err(CodecError::InvalidField);
615        }
616        self.encode(buf);
617        Ok(())
618    }
619
620    /// Encode the datagram status header with extensions control.
621    pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
622        self.track_alias.encode(buf);
623        self.group_id.encode(buf);
624        self.object_id.encode(buf);
625        buf.put_u8(self.publisher_priority);
626        if has_extensions {
627            VarInt::from_usize(self.extensions.len()).encode(buf);
628            buf.put_slice(&self.extensions);
629        }
630        VarInt::from_usize(self.object_status as usize).encode(buf);
631    }
632
633    /// Decode a datagram status header (no extensions).
634    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
635        Self::decode_with_extensions(false, buf)
636    }
637
638    /// Decode a datagram status header with extensions control.
639    pub fn decode_with_extensions(
640        has_extensions: bool,
641        buf: &mut impl Buf,
642    ) -> Result<Self, CodecError> {
643        let track_alias = VarInt::decode(buf)?;
644        let group_id = VarInt::decode(buf)?;
645        let object_id = VarInt::decode(buf)?;
646        if buf.remaining() < 1 {
647            return Err(CodecError::UnexpectedEnd);
648        }
649        let publisher_priority = buf.get_u8();
650        let (extension_headers_length, extensions) = if has_extensions {
651            let ehl = VarInt::decode(buf)?;
652            // A datagram whose type says extensions are present must actually carry
653            // some: receiving one with an Extension Headers Length of 0 closes the
654            // session. The opposite holds on a subgroup stream, where the type byte is
655            // fixed for the whole stream and an object with no extensions has no other
656            // way to say so, which is why this check belongs to the datagram readers
657            // alone.
658            if ehl.into_inner() == 0 {
659                return Err(CodecError::InvalidField);
660            }
661            let ext = read_extension_bytes(buf, ehl.into_inner())?;
662            (ehl, ext)
663        } else {
664            (VarInt::from_usize(0), Vec::new())
665        };
666        let sv = VarInt::decode(buf)?.into_inner();
667        let object_status = ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?;
668        check_extensions_against_status(object_status, &extensions)?;
669        Ok(Self {
670            track_alias,
671            group_id,
672            object_id,
673            publisher_priority,
674            extension_headers_length,
675            extensions,
676            object_status,
677        })
678    }
679}
680
681// ============================================================
682// Datagram framing
683// ============================================================
684
685/// One datagram, of whichever shape its type field names.
686///
687/// A MoQT datagram opens with a variable-length integer naming its type, and
688/// that integer is what says which of the layouts above follows it, and whether an extension block sits inside it.
689/// Neither [`DatagramHeader`] nor [`DatagramStatusHeader`] reads or writes it, so neither can be handed
690/// the first byte of a datagram a peer sent, and neither produces bytes a peer
691/// can read. This is the entry point that does both.
692///
693/// The payload of a payload-bearing datagram runs to the end of the QUIC
694/// datagram, so it is not part of this value: [`Self::decode`] stops at the end
695/// of the header and leaves the payload in the buffer, and a caller appends the
696/// payload after [`Self::encode`].
697#[derive(Debug, Clone, PartialEq, Eq)]
698pub enum Datagram {
699    /// An object carrying a payload.
700    Payload(DatagramHeader),
701    /// An object stating a status, with no payload.
702    Status(DatagramStatusHeader),
703}
704
705impl Datagram {
706    /// Whether this datagram states an Object Status instead of carrying a
707    /// payload.
708    pub fn is_status(&self) -> bool {
709        matches!(self, Self::Status(_))
710    }
711
712    /// The type field this value writes.
713    ///
714    /// The extensions bit is taken from the extension bytes themselves rather
715    /// than from the declared length beside them, which is what keeps the type
716    /// and the body from contradicting each other: a datagram whose type
717    /// announces extensions and then declares a length of 0 closes the session
718    /// on receipt, and one that announces none has nowhere to put them.
719    pub fn datagram_type(&self) -> StreamType {
720        match self {
721            Self::Payload(header) => {
722                if header.extensions.is_empty() {
723                    StreamType::Datagram
724                } else {
725                    StreamType::DatagramExt
726                }
727            }
728            Self::Status(header) => {
729                if header.extensions.is_empty() {
730                    StreamType::DatagramStatus
731                } else {
732                    StreamType::DatagramStatusExt
733                }
734            }
735        }
736    }
737
738    /// Decode a datagram from its first byte, type field included.
739    ///
740    /// Errors with [`CodecError::UnknownDatagramType`] when the datagram table
741    /// does not assign the leading type, which this draft answers with a close,
742    /// and with [`CodecError::InvalidField`] for the stream types, which it
743    /// does assign but not to a datagram. `datagram_type_error` draws that
744    /// line.
745    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
746        let raw = VarInt::decode(buf)?.into_inner();
747        let stream_type = StreamType::from_id(raw).ok_or_else(|| datagram_type_error(raw))?;
748        let has_extensions = stream_type.has_extensions();
749        match stream_type {
750            StreamType::Datagram | StreamType::DatagramExt => {
751                Ok(Self::Payload(DatagramHeader::decode_with_extensions(has_extensions, buf)?))
752            }
753            StreamType::DatagramStatus | StreamType::DatagramStatusExt => {
754                Ok(Self::Status(DatagramStatusHeader::decode_with_extensions(has_extensions, buf)?))
755            }
756            _ => Err(datagram_type_error(raw)),
757        }
758    }
759
760    /// Encode the datagram, type field included.
761    pub fn encode(&self, buf: &mut impl BufMut) {
762        let stream_type = self.datagram_type();
763        let has_extensions = stream_type.has_extensions();
764        VarInt::from_usize(stream_type as usize).encode(buf);
765        match self {
766            Self::Payload(header) => header.encode_with_extensions(has_extensions, buf),
767            Self::Status(header) => header.encode_with_extensions(has_extensions, buf),
768        }
769    }
770
771    /// Encode the datagram, refusing a header the framing it names cannot
772    /// carry.
773    ///
774    /// The body is built before anything reaches `buf`, so a refused datagram
775    /// leaves `buf` untouched rather than a type field with no body under it.
776    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
777        let mut body = Vec::with_capacity(64);
778        let stream_type = self.datagram_type();
779        let has_extensions = stream_type.has_extensions();
780        match self {
781            Self::Payload(header) => header.encode_with_extensions(has_extensions, &mut body),
782            Self::Status(header) => {
783                check_extensions_against_status(header.object_status, &header.extensions)?;
784                header.encode_with_extensions(has_extensions, &mut body);
785            }
786        }
787        VarInt::from_usize(stream_type as usize).encode(buf);
788        buf.put_slice(&body);
789        Ok(())
790    }
791}
792
793// ============================================================
794// Fetch stream (type 0x05)
795// ============================================================
796
797/// Fetch stream header (draft-11, type 0x05).
798///
799/// Contains only the request_id. Objects follow inline.
800#[derive(Debug, Clone, PartialEq, Eq)]
801pub struct FetchHeader {
802    /// Request ID this fetch responds to.
803    pub request_id: VarInt,
804}
805
806/// Object within a fetch stream (draft-11).
807#[derive(Debug, Clone, PartialEq, Eq)]
808pub struct FetchObjectHeader {
809    /// Group identifier.
810    pub group_id: VarInt,
811    /// Subgroup identifier within the group.
812    pub subgroup_id: VarInt,
813    /// Object identifier within the subgroup.
814    pub object_id: VarInt,
815    /// Publisher priority for delivery ordering.
816    pub publisher_priority: u8,
817    /// Total byte length of extension headers, as it arrived.
818    ///
819    /// Advisory on encode. Every encoder here writes
820    /// `VarInt::from_usize(self.extensions.len())` and never reads this field,
821    /// so a hand-built header whose stated length disagrees with the bytes
822    /// beside it goes on the wire with the derived length and is read back
823    /// consistent. Decoding always sets the two together, so a value that came
824    /// off the wire never disagrees.
825    ///
826    /// It is kept because it is what the peer stated, which is not always
827    /// recoverable from the bytes: a non-minimal varint length encodes the same
828    /// number in more bytes, and a relay that must forward the block unchanged
829    /// has to know which it saw.
830    pub extension_headers_length: VarInt,
831    /// Raw extension bytes.
832    pub extensions: Vec<u8>,
833    /// Length of the object payload in bytes.
834    pub payload_length: VarInt,
835    /// Object status (Normal unless payload_length == 0).
836    pub object_status: ObjectStatus,
837}
838
839impl FetchHeader {
840    /// Encode a fetch stream header including its leading stream-type field,
841    /// so the bytes form the start of a data stream a peer can read.
842    ///
843    /// [`Self::encode`] writes the body alone, which is what a caller wants
844    /// once the stream is already open and what a caller must not use for its
845    /// first write. The read side has had [`Self::decode_stream`] all along,
846    /// so without this the codec could not round-trip its own fetch stream
847    /// through its own reader.
848    pub fn encode_stream(&self, buf: &mut impl BufMut) {
849        VarInt::from_usize(StreamType::Fetch as usize).encode(buf);
850        self.encode(buf);
851    }
852
853    /// Encode the fetch header.
854    pub fn encode(&self, buf: &mut impl BufMut) {
855        self.request_id.encode(buf);
856    }
857
858    /// Decode a fetch header.
859    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
860        let request_id = VarInt::decode(buf)?;
861        Ok(Self { request_id })
862    }
863
864    /// Decode a fetch header from the start of a data stream, consuming the
865    /// leading stream type varint.
866    ///
867    /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
868    /// not assign the leading type, which this draft answers with a close, and
869    /// with [`CodecError::InvalidField`] when it is assigned but is not
870    /// [`StreamType::Fetch`]. `stream_type_error` draws that line.
871    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
872        let stream_type = VarInt::decode(buf)?.into_inner();
873        if stream_type != StreamType::Fetch as u64 {
874            return Err(stream_type_error(stream_type));
875        }
876        Self::decode(buf)
877    }
878}
879
880impl FetchObjectHeader {
881    /// Encode the fetch object header.
882    pub fn encode(&self, buf: &mut impl BufMut) {
883        self.group_id.encode(buf);
884        self.subgroup_id.encode(buf);
885        self.object_id.encode(buf);
886        buf.put_u8(self.publisher_priority);
887        VarInt::from_usize(self.extensions.len()).encode(buf);
888        buf.put_slice(&self.extensions);
889        self.payload_length.encode(buf);
890        if self.payload_length.into_inner() == 0 {
891            VarInt::from_usize(self.object_status as usize).encode(buf);
892        }
893    }
894
895    /// Encode the header, refusing a status the framing cannot carry.
896    ///
897    /// Section 9.4.4 puts the Object Status field on the wire only when the
898    /// Object Payload Length is zero, and Section 9.1.1.1 says "Any object
899    /// with a status code other than zero MUST have an empty payload". A
900    /// non-zero status paired with a non-zero payload length therefore has no
901    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
902    /// ordinary object, which is a different object from the one the caller
903    /// described. This refuses instead.
904    ///
905    /// The datagram types on this draft already refuse the same pairing. These
906    /// two did not, and they are the ones a publisher writes on every stream.
907    ///
908    /// # Errors
909    ///
910    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
911    /// a non-zero Object Payload Length.
912    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
913        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
914            return Err(CodecError::InvalidField);
915        }
916        self.encode(buf);
917        Ok(())
918    }
919
920    /// Decode a fetch object header.
921    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
922        let group_id = VarInt::decode(buf)?;
923        let subgroup_id = VarInt::decode(buf)?;
924        let object_id = VarInt::decode(buf)?;
925        if buf.remaining() < 1 {
926            return Err(CodecError::UnexpectedEnd);
927        }
928        let publisher_priority = buf.get_u8();
929        let extension_headers_length = VarInt::decode(buf)?;
930        let extensions = read_extension_bytes(buf, extension_headers_length.into_inner())?;
931        let payload_length = VarInt::decode(buf)?;
932        let object_status = if payload_length.into_inner() == 0 {
933            let sv = VarInt::decode(buf)?.into_inner();
934            ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?
935        } else {
936            ObjectStatus::Normal
937        };
938        check_extensions_against_status(object_status, &extensions)?;
939        Ok(Self {
940            group_id,
941            subgroup_id,
942            object_id,
943            publisher_priority,
944            extension_headers_length,
945            extensions,
946            payload_length,
947            object_status,
948        })
949    }
950}