Skip to main content

moqtap_codec/draft13/
data_stream.rs

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