Skip to main content

moqtap_codec/draft10/
data_stream.rs

1//! Draft-10 data stream header encoding and decoding.
2//!
3//! Changes from draft-08:
4//! - `extension_count` → `extension_headers_length` (byte length, not count)
5//! - Datagram (0x01): no `payload_length` or `object_status`; payload is remaining bytes
6//! - DatagramStatus (0x02): gains `extension_headers_length` field
7
8use super::types::ObjectStatus;
9use crate::error::CodecError;
10use crate::types::read_bytes;
11use crate::varint::VarInt;
12use bytes::{Buf, BufMut};
13
14/// Every type ID this draft's data plane assigns, from two separate tables.
15///
16/// The draft keeps unidirectional stream types and datagram types in different
17/// tables and different number spaces: SUBGROUP_HEADER and FETCH_HEADER name
18/// unidirectional streams, OBJECT_DATAGRAM and OBJECT_DATAGRAM_STATUS name
19/// datagrams. The numbers happen not to collide on this draft, which is what
20/// lets one enum hold all four.
21///
22/// [`StreamType::from_id`] therefore answers across both tables and cannot say
23/// which one a value came from, which makes it the wrong question to ask about
24/// a stream or a datagram in hand. `stream_type_error` and
25/// `datagram_type_error` ask it per table, and the difference is not
26/// cosmetic: a value assigned in the other table is unknown in this one, and
27/// this draft answers an unknown type by ending the session.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[repr(u64)]
30pub enum StreamType {
31    /// Datagram with payload (0x01).
32    Datagram = 0x01,
33    /// Datagram with status only, no payload (0x02).
34    DatagramStatus = 0x02,
35    /// Subgroup stream type (0x04).
36    Subgroup = 0x04,
37    /// Fetch stream type (0x05).
38    Fetch = 0x05,
39}
40
41impl StreamType {
42    /// Convert a raw stream type ID to a `StreamType`, if valid.
43    pub fn from_id(id: u64) -> Option<Self> {
44        match id {
45            0x01 => Some(StreamType::Datagram),
46            0x02 => Some(StreamType::DatagramStatus),
47            0x04 => Some(StreamType::Subgroup),
48            0x05 => Some(StreamType::Fetch),
49            _ => None,
50        }
51    }
52}
53
54/// Which failure a leading unidirectional stream type that is not the one a
55/// reader wants is.
56///
57/// Section 9: "An endpoint that receives an unknown stream or datagram type
58/// MUST close the session." One sentence, two tables. The stream table assigns
59/// SUBGROUP_HEADER and FETCH_HEADER; everything outside those two is unknown at
60/// the head of a stream, and the session ends.
61///
62/// The datagram types are among the values outside them. This draft is the
63/// first to give datagrams a table of their own, with numbering independent of
64/// the stream table, so a datagram type says nothing about a stream and is not
65/// a value the stream table assigns. Draft-07, which numbered both in one
66/// table, is the only draft where that is not so.
67///
68/// A stream announcing the *other* assigned stream type is a different matter
69/// and not that rule. The value is one this draft defines, and the disagreement
70/// is with the reader that was called rather than with the draft, so the
71/// session survives it.
72fn stream_type_error(raw: u64) -> CodecError {
73    if raw == StreamType::Subgroup as u64 || raw == StreamType::Fetch as u64 {
74        CodecError::InvalidField
75    } else {
76        CodecError::UnknownStreamType(raw)
77    }
78}
79
80/// Which failure a leading datagram type that is not one a reader wants is.
81///
82/// The datagram half of the sentence quoted on `stream_type_error`, read
83/// against the other table: OBJECT_DATAGRAM and OBJECT_DATAGRAM_STATUS are what
84/// it assigns, and everything else arriving as a datagram is unknown.
85///
86/// Both assigned values are decodable here, so the [`CodecError::InvalidField`]
87/// arm is reached only by the stream types — 0x04 and 0x05 — delivered as
88/// datagrams. Those are values this draft defines, and refusing them without
89/// closing is the same judgement the stream side makes about a datagram type.
90fn datagram_type_error(raw: u64) -> CodecError {
91    if raw == StreamType::Datagram as u64 || raw == StreamType::DatagramStatus as u64 {
92        CodecError::InvalidField
93    } else {
94        CodecError::UnknownDatagramType(raw)
95    }
96}
97
98// ── Extension helpers (length-based, not count-based) ─────────
99
100/// Read `byte_len` bytes of raw extension data from the buffer.
101fn read_extension_bytes(buf: &mut impl Buf, byte_len: u64) -> Result<Vec<u8>, CodecError> {
102    read_bytes(buf, byte_len as usize)
103}
104
105/// Encode extension bytes to the buffer (just writes the raw bytes).
106fn encode_extensions(extensions: &[u8], buf: &mut impl BufMut) {
107    buf.put_slice(extensions);
108}
109
110// ============================================================
111// Subgroup stream (type 0x04)
112// ============================================================
113
114/// Subgroup stream header (follows the stream type varint).
115#[derive(Debug, Clone, PartialEq, Eq)]
116pub struct SubgroupHeader {
117    /// Track alias identifying the subscription.
118    pub track_alias: VarInt,
119    /// Group identifier.
120    pub group_id: VarInt,
121    /// Subgroup identifier within the group.
122    pub subgroup_id: VarInt,
123    /// Publisher priority for delivery ordering.
124    pub publisher_priority: u8,
125}
126
127/// Object within a subgroup stream (draft-10).
128///
129/// Encoding: object_id(vi), extension_headers_length(vi), [extensions...],
130///   payload_length(vi),
131///   if payload_length == 0: object_status(vi)
132///   else: payload bytes
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct ObjectHeader {
135    /// Object identifier within the subgroup.
136    pub object_id: VarInt,
137    /// Total byte length of extension headers, as it arrived.
138    ///
139    /// Advisory on encode. Every encoder here writes
140    /// `VarInt::from_usize(self.extensions.len())` and never reads this field,
141    /// so a hand-built header whose stated length disagrees with the bytes
142    /// beside it goes on the wire with the derived length and is read back
143    /// consistent. Decoding always sets the two together, so a value that came
144    /// off the wire never disagrees.
145    ///
146    /// It is kept because it is what the peer stated, which is not always
147    /// recoverable from the bytes: a non-minimal varint length encodes the same
148    /// number in more bytes, and a relay that must forward the block unchanged
149    /// has to know which it saw.
150    pub extension_headers_length: VarInt,
151    /// Raw extension bytes (opaque).
152    pub extensions: Vec<u8>,
153    /// Length of the object payload in bytes.
154    pub payload_length: VarInt,
155    /// Status of this object.
156    pub object_status: ObjectStatus,
157}
158
159impl SubgroupHeader {
160    /// Encode a subgroup stream header including its leading stream-type
161    /// field, so the bytes form the start of a data stream a peer can read.
162    ///
163    /// [`Self::encode`] writes the body alone, which is what a caller wants
164    /// once the stream is already open and what a caller must not use for its
165    /// first write.
166    pub fn encode_stream(&self, buf: &mut impl BufMut) {
167        VarInt::from_usize(StreamType::Subgroup as usize).encode(buf);
168        self.encode(buf);
169    }
170
171    /// Encode the subgroup header into the buffer.
172    pub fn encode(&self, buf: &mut impl BufMut) {
173        self.track_alias.encode(buf);
174        self.group_id.encode(buf);
175        self.subgroup_id.encode(buf);
176        buf.put_u8(self.publisher_priority);
177    }
178
179    /// Decode a subgroup header from the buffer.
180    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
181        let track_alias = VarInt::decode(buf)?;
182        let group_id = VarInt::decode(buf)?;
183        let subgroup_id = VarInt::decode(buf)?;
184        if buf.remaining() < 1 {
185            return Err(CodecError::UnexpectedEnd);
186        }
187        let publisher_priority = buf.get_u8();
188        Ok(Self { track_alias, group_id, subgroup_id, publisher_priority })
189    }
190
191    /// Decode a subgroup header from the start of a data stream, consuming
192    /// the leading stream type varint.
193    ///
194    /// Errors with [`CodecError::UnknownStreamType`] when the stream type is
195    /// one the stream table does not assign, which this draft answers with a
196    /// close, and with [`CodecError::InvalidField`] when it is assigned but is
197    /// not [`StreamType::Subgroup`]. `stream_type_error` draws that line.
198    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
199        let stream_type = VarInt::decode(buf)?.into_inner();
200        if stream_type != StreamType::Subgroup as u64 {
201            return Err(stream_type_error(stream_type));
202        }
203        Self::decode(buf)
204    }
205}
206
207/// Refuse an end-of-track object that does not end at object zero.
208///
209/// Section 9.1.1.1 describes Object Status 0x5 as "end of Track. GroupID is one
210/// greater than the largest group produced in this track and the ObjectId is
211/// zero", and states the consequence: "An object with this status that has a
212/// Group ID less than or equal to any other Group ID, or an Object ID other
213/// than zero, is a protocol error, and the receiver MUST terminate the
214/// session."
215///
216/// Only the Object ID half is answerable here. The Group ID half compares
217/// against the largest group produced on the track, which no single header
218/// carries and no reader of one header can know.
219///
220/// Applied on both sides. A receiver is required to close the session over
221/// this, so writing one is not a way to send it.
222fn check_end_of_track(object_id: VarInt, status: ObjectStatus) -> Result<(), CodecError> {
223    if status == ObjectStatus::EndOfTrack && object_id.into_inner() != 0 {
224        return Err(CodecError::EndOfTrackObjectId(object_id.into_inner()));
225    }
226    Ok(())
227}
228
229impl ObjectHeader {
230    /// Encode the object header into the buffer.
231    pub fn encode(&self, buf: &mut impl BufMut) {
232        self.object_id.encode(buf);
233        VarInt::from_usize(self.extensions.len()).encode(buf);
234        encode_extensions(&self.extensions, buf);
235        self.payload_length.encode(buf);
236        if self.payload_length.into_inner() == 0 {
237            VarInt::from_usize(self.object_status as usize).encode(buf);
238        }
239    }
240
241    /// Encode the header, refusing a status the framing cannot carry.
242    ///
243    /// Section 9.4.2 puts the Object Status field on the wire only when the
244    /// Object Payload Length is zero, and Section 9.1.1.1 says "Any object
245    /// with a status code other than zero MUST have an empty payload". A
246    /// non-zero status paired with a non-zero payload length therefore has no
247    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
248    /// ordinary object, which is a different object from the one the caller
249    /// described. This refuses instead.
250    ///
251    /// The datagram types on this draft already refuse the same pairing. These
252    /// two did not, and they are the ones a publisher writes on every stream.
253    ///
254    /// # Errors
255    ///
256    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
257    /// a non-zero Object Payload Length.
258    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
259        check_end_of_track(self.object_id, self.object_status)?;
260        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
261            return Err(CodecError::InvalidField);
262        }
263        self.encode(buf);
264        Ok(())
265    }
266
267    /// Decode an object header from the buffer.
268    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
269        let object_id = VarInt::decode(buf)?;
270        let extension_headers_length = VarInt::decode(buf)?;
271        let extensions = read_extension_bytes(buf, extension_headers_length.into_inner())?;
272        let payload_length = VarInt::decode(buf)?;
273        let object_status = if payload_length.into_inner() == 0 {
274            let status_val = VarInt::decode(buf)?.into_inner();
275            ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
276        } else {
277            ObjectStatus::Normal
278        };
279        check_end_of_track(object_id, object_status)?;
280        Ok(Self { object_id, extension_headers_length, extensions, payload_length, object_status })
281    }
282}
283
284// ============================================================
285// Datagram (type 0x01)
286// ============================================================
287
288/// Datagram header with payload (draft-10, type 0x01).
289///
290/// Draft-09 dropped these two fields and draft-10 keeps them dropped: no
291/// `payload_length` and no `object_status`.
292/// Payload is the remaining bytes in the datagram.
293///
294/// Encoding (after type varint):
295///   track_alias(vi), group_id(vi), object_id(vi),
296///   publisher_priority(u8), extension_headers_length(vi), [extensions...],
297///   [remaining bytes = payload]
298#[derive(Debug, Clone, PartialEq, Eq)]
299pub struct DatagramHeader {
300    /// Track alias identifying the subscription.
301    pub track_alias: VarInt,
302    /// Group identifier.
303    pub group_id: VarInt,
304    /// Object identifier within the group.
305    pub object_id: VarInt,
306    /// Publisher priority for delivery ordering.
307    pub publisher_priority: u8,
308    /// Total byte length of extension headers, as it arrived.
309    ///
310    /// Advisory on encode. Every encoder here writes
311    /// `VarInt::from_usize(self.extensions.len())` and never reads this field,
312    /// so a hand-built header whose stated length disagrees with the bytes
313    /// beside it goes on the wire with the derived length and is read back
314    /// consistent. Decoding always sets the two together, so a value that came
315    /// off the wire never disagrees.
316    ///
317    /// It is kept because it is what the peer stated, which is not always
318    /// recoverable from the bytes: a non-minimal varint length encodes the same
319    /// number in more bytes, and a relay that must forward the block unchanged
320    /// has to know which it saw.
321    pub extension_headers_length: VarInt,
322    /// Raw extension bytes (opaque).
323    pub extensions: Vec<u8>,
324}
325
326impl DatagramHeader {
327    /// Encode the datagram header into the buffer.
328    pub fn encode(&self, buf: &mut impl BufMut) {
329        self.track_alias.encode(buf);
330        self.group_id.encode(buf);
331        self.object_id.encode(buf);
332        buf.put_u8(self.publisher_priority);
333        VarInt::from_usize(self.extensions.len()).encode(buf);
334        encode_extensions(&self.extensions, buf);
335    }
336
337    /// Encode the datagram header, refusing a status the framing cannot carry.
338    ///
339    /// Nothing here is ever refused, and that is a fact about draft-10 rather
340    /// than a check left out. This is the OBJECT_DATAGRAM of Section 9.2, whose
341    /// layout carries no Object Status field at all; a datagram that states a
342    /// status is the separate OBJECT_DATAGRAM_STATUS message of Section 9.3,
343    /// modelled here as [`DatagramStatusHeader`]. So there is no status for
344    /// [`Self::encode`] to drop, and nothing for Section 9.1.1.1's "Any object
345    /// with a status code other than zero MUST have an empty payload" to rule
346    /// on: an object framed this way has status zero by construction.
347    ///
348    /// The fallible signature is what lets one entry point span every draft.
349    /// `dispatch::AnyDatagramHeader::encode` calls this on all thirteen, and
350    /// the drafts whose payload-bearing datagram *does* carry a status field
351    /// need somewhere to say no.
352    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
353        self.encode(buf);
354        Ok(())
355    }
356
357    /// Decode a datagram header from the buffer.
358    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
359        let track_alias = VarInt::decode(buf)?;
360        let group_id = VarInt::decode(buf)?;
361        let object_id = VarInt::decode(buf)?;
362        if buf.remaining() < 1 {
363            return Err(CodecError::UnexpectedEnd);
364        }
365        let publisher_priority = buf.get_u8();
366        let extension_headers_length = VarInt::decode(buf)?;
367        let extensions = read_extension_bytes(buf, extension_headers_length.into_inner())?;
368        Ok(Self {
369            track_alias,
370            group_id,
371            object_id,
372            publisher_priority,
373            extension_headers_length,
374            extensions,
375        })
376    }
377}
378
379// ============================================================
380// Datagram Status (type 0x02)
381// ============================================================
382
383/// Datagram status header (draft-10, type 0x02).
384///
385/// Draft-09 added `extension_headers_length` here and draft-10 keeps it.
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct DatagramStatusHeader {
388    /// Track alias identifying the subscription.
389    pub track_alias: VarInt,
390    /// Group identifier.
391    pub group_id: VarInt,
392    /// Object identifier within the group.
393    pub object_id: VarInt,
394    /// Publisher priority for delivery ordering.
395    pub publisher_priority: u8,
396    /// Total byte length of extension headers, as it arrived.
397    ///
398    /// Advisory on encode. Every encoder here writes
399    /// `VarInt::from_usize(self.extensions.len())` and never reads this field,
400    /// so a hand-built header whose stated length disagrees with the bytes
401    /// beside it goes on the wire with the derived length and is read back
402    /// consistent. Decoding always sets the two together, so a value that came
403    /// off the wire never disagrees.
404    ///
405    /// It is kept because it is what the peer stated, which is not always
406    /// recoverable from the bytes: a non-minimal varint length encodes the same
407    /// number in more bytes, and a relay that must forward the block unchanged
408    /// has to know which it saw.
409    pub extension_headers_length: VarInt,
410    /// Raw extension bytes (opaque).
411    pub extensions: Vec<u8>,
412    /// Object status code.
413    pub object_status: ObjectStatus,
414}
415
416impl DatagramStatusHeader {
417    /// Encode the datagram status header into the buffer.
418    pub fn encode(&self, buf: &mut impl BufMut) {
419        self.track_alias.encode(buf);
420        self.group_id.encode(buf);
421        self.object_id.encode(buf);
422        buf.put_u8(self.publisher_priority);
423        VarInt::from_usize(self.extensions.len()).encode(buf);
424        encode_extensions(&self.extensions, buf);
425        VarInt::from_usize(self.object_status as usize).encode(buf);
426    }
427
428    /// Encode the datagram status header, refusing an end-of-track object that
429    /// does not end at object zero.
430    ///
431    /// # Errors
432    ///
433    /// [`CodecError::EndOfTrackObjectId`] if an end-of-track status is paired
434    /// with a non-zero Object ID.
435    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
436        check_end_of_track(self.object_id, self.object_status)?;
437        self.encode(buf);
438        Ok(())
439    }
440
441    /// Decode a datagram status header from the buffer.
442    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
443        let track_alias = VarInt::decode(buf)?;
444        let group_id = VarInt::decode(buf)?;
445        let object_id = VarInt::decode(buf)?;
446        if buf.remaining() < 1 {
447            return Err(CodecError::UnexpectedEnd);
448        }
449        let publisher_priority = buf.get_u8();
450        let extension_headers_length = VarInt::decode(buf)?;
451        let extensions = read_extension_bytes(buf, extension_headers_length.into_inner())?;
452        let status_val = VarInt::decode(buf)?.into_inner();
453        let object_status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
454        check_end_of_track(object_id, object_status)?;
455        Ok(Self {
456            track_alias,
457            group_id,
458            object_id,
459            publisher_priority,
460            extension_headers_length,
461            extensions,
462            object_status,
463        })
464    }
465}
466
467// ============================================================
468// Datagram framing
469// ============================================================
470
471/// One datagram, of whichever shape its type field names.
472///
473/// A MoQT datagram opens with a variable-length integer naming its type, and
474/// that integer is what says which of the layouts above follows it.
475/// Neither [`DatagramHeader`] nor [`DatagramStatusHeader`] reads or writes it, so neither can be handed
476/// the first byte of a datagram a peer sent, and neither produces bytes a peer
477/// can read. This is the entry point that does both.
478///
479/// The payload of a payload-bearing datagram runs to the end of the QUIC
480/// datagram, so it is not part of this value: [`Self::decode`] stops at the end
481/// of the header and leaves the payload in the buffer, and a caller appends the
482/// payload after [`Self::encode`].
483#[derive(Debug, Clone, PartialEq, Eq)]
484pub enum Datagram {
485    /// An object carrying a payload.
486    Payload(DatagramHeader),
487    /// An object stating a status, with no payload.
488    Status(DatagramStatusHeader),
489}
490
491impl Datagram {
492    /// Whether this datagram states an Object Status instead of carrying a
493    /// payload.
494    pub fn is_status(&self) -> bool {
495        matches!(self, Self::Status(_))
496    }
497
498    /// The type field this value writes.
499    pub fn datagram_type(&self) -> StreamType {
500        match self {
501            Self::Payload(_) => StreamType::Datagram,
502            Self::Status(_) => StreamType::DatagramStatus,
503        }
504    }
505
506    /// Decode a datagram from its first byte, type field included.
507    ///
508    /// Errors with [`CodecError::UnknownDatagramType`] when the leading type is
509    /// one the datagram table does not assign, which this draft answers with a
510    /// close, and with [`CodecError::InvalidField`] for the stream types, which
511    /// it does assign but not to a datagram. `datagram_type_error` draws that
512    /// line.
513    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
514        let raw = VarInt::decode(buf)?.into_inner();
515        match StreamType::from_id(raw) {
516            Some(StreamType::Datagram) => Ok(Self::Payload(DatagramHeader::decode(buf)?)),
517            Some(StreamType::DatagramStatus) => {
518                Ok(Self::Status(DatagramStatusHeader::decode(buf)?))
519            }
520            _ => Err(datagram_type_error(raw)),
521        }
522    }
523
524    /// Encode the datagram, type field included.
525    pub fn encode(&self, buf: &mut impl BufMut) {
526        VarInt::from_usize(self.datagram_type() as usize).encode(buf);
527        match self {
528            Self::Payload(header) => header.encode(buf),
529            Self::Status(header) => header.encode(buf),
530        }
531    }
532
533    /// Encode the datagram, refusing a header the framing it names cannot
534    /// carry.
535    ///
536    /// The body is built before anything reaches `buf`, so a refused datagram
537    /// leaves `buf` untouched rather than a type field with no body under it.
538    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
539        let mut body = Vec::with_capacity(64);
540        match self {
541            Self::Payload(header) => header.encode_checked(&mut body)?,
542            Self::Status(header) => header.encode_checked(&mut body)?,
543        }
544        VarInt::from_usize(self.datagram_type() as usize).encode(buf);
545        buf.put_slice(&body);
546        Ok(())
547    }
548}
549
550// ============================================================
551// Fetch stream (type 0x05)
552// ============================================================
553
554/// Fetch stream header (follows the stream type varint).
555#[derive(Debug, Clone, PartialEq, Eq)]
556pub struct FetchHeader {
557    /// Subscribe ID this fetch responds to.
558    pub subscribe_id: VarInt,
559}
560
561/// Object within a fetch stream (draft-10).
562///
563/// Uses `extension_headers_length` instead of `extension_count`.
564#[derive(Debug, Clone, PartialEq, Eq)]
565pub struct FetchObjectHeader {
566    /// Group identifier.
567    pub group_id: VarInt,
568    /// Subgroup identifier within the group.
569    pub subgroup_id: VarInt,
570    /// Object identifier within the subgroup.
571    pub object_id: VarInt,
572    /// Publisher priority for delivery ordering.
573    pub publisher_priority: u8,
574    /// Total byte length of extension headers, as it arrived.
575    ///
576    /// Advisory on encode. Every encoder here writes
577    /// `VarInt::from_usize(self.extensions.len())` and never reads this field,
578    /// so a hand-built header whose stated length disagrees with the bytes
579    /// beside it goes on the wire with the derived length and is read back
580    /// consistent. Decoding always sets the two together, so a value that came
581    /// off the wire never disagrees.
582    ///
583    /// It is kept because it is what the peer stated, which is not always
584    /// recoverable from the bytes: a non-minimal varint length encodes the same
585    /// number in more bytes, and a relay that must forward the block unchanged
586    /// has to know which it saw.
587    pub extension_headers_length: VarInt,
588    /// Raw extension bytes (opaque).
589    pub extensions: Vec<u8>,
590    /// Status of this object.
591    pub object_status: ObjectStatus,
592    /// Length of the object payload in bytes.
593    pub payload_length: VarInt,
594}
595
596impl FetchHeader {
597    /// Encode a fetch stream header including its leading stream-type field,
598    /// so the bytes form the start of a data stream a peer can read.
599    ///
600    /// [`Self::encode`] writes the body alone, which is what a caller wants
601    /// once the stream is already open and what a caller must not use for its
602    /// first write. The read side has had [`Self::decode_stream`] all along,
603    /// so without this the codec could not round-trip its own fetch stream
604    /// through its own reader.
605    pub fn encode_stream(&self, buf: &mut impl BufMut) {
606        VarInt::from_usize(StreamType::Fetch as usize).encode(buf);
607        self.encode(buf);
608    }
609
610    /// Encode the fetch header into the buffer.
611    pub fn encode(&self, buf: &mut impl BufMut) {
612        self.subscribe_id.encode(buf);
613    }
614
615    /// Decode a fetch header from the buffer.
616    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
617        let subscribe_id = VarInt::decode(buf)?;
618        Ok(Self { subscribe_id })
619    }
620
621    /// Decode a fetch header from the start of a data stream, consuming the
622    /// leading stream type varint.
623    ///
624    /// Errors with [`CodecError::UnknownStreamType`] when the stream type is
625    /// one the stream table does not assign, which this draft answers with a
626    /// close, and with [`CodecError::InvalidField`] when it is assigned but is
627    /// not [`StreamType::Fetch`]. `stream_type_error` draws that line.
628    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
629        let stream_type = VarInt::decode(buf)?.into_inner();
630        if stream_type != StreamType::Fetch as u64 {
631            return Err(stream_type_error(stream_type));
632        }
633        Self::decode(buf)
634    }
635}
636
637impl FetchObjectHeader {
638    /// Encode the fetch object header into the buffer.
639    pub fn encode(&self, buf: &mut impl BufMut) {
640        self.group_id.encode(buf);
641        self.subgroup_id.encode(buf);
642        self.object_id.encode(buf);
643        buf.put_u8(self.publisher_priority);
644        VarInt::from_usize(self.extensions.len()).encode(buf);
645        encode_extensions(&self.extensions, buf);
646        self.payload_length.encode(buf);
647        if self.payload_length.into_inner() == 0 {
648            VarInt::from_usize(self.object_status as usize).encode(buf);
649        }
650    }
651
652    /// Encode the header, refusing a status the framing cannot carry.
653    ///
654    /// Section 9.4.4 puts the Object Status field on the wire only when the
655    /// Object Payload Length is zero, and Section 9.1.1.1 says "Any object
656    /// with a status code other than zero MUST have an empty payload". A
657    /// non-zero status paired with a non-zero payload length therefore has no
658    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
659    /// ordinary object, which is a different object from the one the caller
660    /// described. This refuses instead.
661    ///
662    /// The datagram types on this draft already refuse the same pairing. These
663    /// two did not, and they are the ones a publisher writes on every stream.
664    ///
665    /// # Errors
666    ///
667    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
668    /// a non-zero Object Payload Length.
669    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
670        check_end_of_track(self.object_id, self.object_status)?;
671        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
672            return Err(CodecError::InvalidField);
673        }
674        self.encode(buf);
675        Ok(())
676    }
677
678    /// Decode a fetch object header from the buffer.
679    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
680        let group_id = VarInt::decode(buf)?;
681        let subgroup_id = VarInt::decode(buf)?;
682        let object_id = VarInt::decode(buf)?;
683        if buf.remaining() < 1 {
684            return Err(CodecError::UnexpectedEnd);
685        }
686        let publisher_priority = buf.get_u8();
687        let extension_headers_length = VarInt::decode(buf)?;
688        let extensions = read_extension_bytes(buf, extension_headers_length.into_inner())?;
689        let payload_length = VarInt::decode(buf)?;
690        let object_status = if payload_length.into_inner() == 0 {
691            let status_val = VarInt::decode(buf)?.into_inner();
692            ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
693        } else {
694            ObjectStatus::Normal
695        };
696        check_end_of_track(object_id, object_status)?;
697        Ok(Self {
698            group_id,
699            subgroup_id,
700            object_id,
701            publisher_priority,
702            extension_headers_length,
703            extensions,
704            object_status,
705            payload_length,
706        })
707    }
708}