Skip to main content

moqtap_codec/draft08/
data_stream.rs

1//! Draft-08 data stream header encoding and decoding.
2//!
3//! Differences from draft-07:
4//! - Object headers include `extension_count` (varint) + raw extension bytes
5//! - Separate DatagramStatus type (0x02) for status-only datagrams
6//! - Datagram (0x01) includes extension_count + payload
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 8: "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 ───────────────────────────────────────
99
100/// Skip over extensions in the buffer, reading extension_count varints.
101///
102/// Extension encoding: for each extension, read type (varint).
103/// - Even type: value is a single varint
104/// - Odd type: value is length-prefixed bytes (varint length + bytes)
105fn skip_extensions(buf: &mut impl Buf, count: u64) -> Result<Vec<u8>, CodecError> {
106    let mut raw = Vec::new();
107    for _ in 0..count {
108        let ext_type = VarInt::decode(buf)?;
109        ext_type.encode(&mut raw);
110        if ext_type.into_inner().is_multiple_of(2) {
111            let val = VarInt::decode(buf)?;
112            val.encode(&mut raw);
113        } else {
114            let len = VarInt::decode(buf)?.into_inner() as usize;
115            VarInt::from_usize(len).encode(&mut raw);
116            let bytes = read_bytes(buf, len)?;
117            raw.extend_from_slice(&bytes);
118        }
119    }
120    Ok(raw)
121}
122
123/// Count the extension headers in a raw block, or `None` if the bytes do not
124/// tile into whole headers.
125///
126/// The block is stored opaque, so the number of headers in it is not something
127/// the value carries - it is a property of the bytes, recovered by walking them
128/// with the same rule [`skip_extensions`] reads them by. This is the only draft
129/// that needs it: draft-08 states the field as "Extension Count", a number of
130/// headers, and every later draft states a byte length that
131/// `extensions.len()` supplies directly.
132fn count_extensions(raw: &[u8]) -> Option<u64> {
133    let mut buf = raw;
134    let mut count: u64 = 0;
135    while buf.has_remaining() {
136        let ext_type = VarInt::decode(&mut buf).ok()?;
137        if ext_type.into_inner().is_multiple_of(2) {
138            VarInt::decode(&mut buf).ok()?;
139        } else {
140            let len = VarInt::decode(&mut buf).ok()?.into_inner() as usize;
141            if buf.remaining() < len {
142                return None;
143            }
144            buf.advance(len);
145        }
146        count = count.checked_add(1)?;
147    }
148    Some(count)
149}
150
151/// Refuse an Extension Count that disagrees with the extension bytes beside it.
152///
153/// The count and the bytes are two fields with nothing tying them together, and
154/// [`skip_extensions`] reads exactly `count` headers on the way back in. A
155/// header that states two and carries three leaves the third where the peer
156/// expects the Object Payload Length, so every field after it - and every
157/// object after that on the same stream - is read at the wrong offset. There is
158/// no recovery downstream, which is why this is refused rather than corrected.
159fn check_extension_count(stated: VarInt, raw: &[u8]) -> Result<(), CodecError> {
160    match count_extensions(raw) {
161        Some(actual) if actual == stated.into_inner() => Ok(()),
162        _ => Err(CodecError::InvalidField),
163    }
164}
165
166/// Encode extension bytes back to the buffer.
167fn encode_extensions(extensions: &[u8], buf: &mut impl BufMut) {
168    buf.put_slice(extensions);
169}
170
171// ============================================================
172// Subgroup stream (type 0x04)
173// ============================================================
174
175/// Subgroup stream header (follows the stream type varint).
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct SubgroupHeader {
178    /// Track alias identifying the subscription.
179    pub track_alias: VarInt,
180    /// Group identifier.
181    pub group_id: VarInt,
182    /// Subgroup identifier within the group.
183    pub subgroup_id: VarInt,
184    /// Publisher priority for delivery ordering.
185    pub publisher_priority: u8,
186}
187
188/// Object within a subgroup stream (draft-08).
189///
190/// Encoding: object_id(vi), extension_count(vi), [extensions...],
191///   payload_length(vi),
192///   if payload_length == 0: object_status(vi)
193///   else: payload bytes
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct ObjectHeader {
196    /// Object identifier within the subgroup.
197    pub object_id: VarInt,
198    /// Number of extensions.
199    pub extension_count: VarInt,
200    /// Raw extension bytes (opaque).
201    pub extensions: Vec<u8>,
202    /// Length of the object payload in bytes.
203    pub payload_length: VarInt,
204    /// Status of this object.
205    pub object_status: ObjectStatus,
206}
207
208impl SubgroupHeader {
209    /// Encode a subgroup stream header including its leading stream-type
210    /// field, so the bytes form the start of a data stream a peer can read.
211    ///
212    /// [`Self::encode`] writes the body alone, which is what a caller wants
213    /// once the stream is already open and what a caller must not use for its
214    /// first write.
215    pub fn encode_stream(&self, buf: &mut impl BufMut) {
216        VarInt::from_usize(StreamType::Subgroup as usize).encode(buf);
217        self.encode(buf);
218    }
219
220    /// Encode the subgroup header into the buffer.
221    pub fn encode(&self, buf: &mut impl BufMut) {
222        self.track_alias.encode(buf);
223        self.group_id.encode(buf);
224        self.subgroup_id.encode(buf);
225        buf.put_u8(self.publisher_priority);
226    }
227
228    /// Decode a subgroup header from the buffer.
229    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
230        let track_alias = VarInt::decode(buf)?;
231        let group_id = VarInt::decode(buf)?;
232        let subgroup_id = VarInt::decode(buf)?;
233        if buf.remaining() < 1 {
234            return Err(CodecError::UnexpectedEnd);
235        }
236        let publisher_priority = buf.get_u8();
237        Ok(Self { track_alias, group_id, subgroup_id, publisher_priority })
238    }
239
240    /// Decode a subgroup header from the start of a data stream, consuming
241    /// the leading stream type varint.
242    ///
243    /// Errors with [`CodecError::UnknownStreamType`] when the stream type is
244    /// one the stream table does not assign, which this draft answers with a
245    /// close, and with [`CodecError::InvalidField`] when it is assigned but is
246    /// not [`StreamType::Subgroup`]. `stream_type_error` draws that line.
247    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
248        let stream_type = VarInt::decode(buf)?.into_inner();
249        if stream_type != StreamType::Subgroup as u64 {
250            return Err(stream_type_error(stream_type));
251        }
252        Self::decode(buf)
253    }
254}
255
256/// Refuse an end-of-track object that does not end at object zero.
257///
258/// Section 8.1.1.1 describes Object Status 0x5 as "end of Track. GroupID is one
259/// greater than the largest group produced in this track and the ObjectId is
260/// zero", and states the consequence: "An object with this status that has a
261/// Group ID less than or equal to any other Group ID, or an Object ID other
262/// than zero, is a protocol error, and the receiver MUST terminate the
263/// session."
264///
265/// Only the Object ID half is answerable here. The Group ID half compares
266/// against the largest group produced on the track, which no single header
267/// carries and no reader of one header can know.
268///
269/// Applied on both sides. A receiver is required to close the session over
270/// this, so writing one is not a way to send it.
271fn check_end_of_track(object_id: VarInt, status: ObjectStatus) -> Result<(), CodecError> {
272    if status == ObjectStatus::EndOfTrack && object_id.into_inner() != 0 {
273        return Err(CodecError::EndOfTrackObjectId(object_id.into_inner()));
274    }
275    Ok(())
276}
277
278impl ObjectHeader {
279    /// Encode the object header into the buffer.
280    pub fn encode(&self, buf: &mut impl BufMut) {
281        self.object_id.encode(buf);
282        self.extension_count.encode(buf);
283        encode_extensions(&self.extensions, buf);
284        self.payload_length.encode(buf);
285        if self.payload_length.into_inner() == 0 {
286            VarInt::from_usize(self.object_status as usize).encode(buf);
287        }
288    }
289
290    /// Encode the header, refusing a status the framing cannot carry.
291    ///
292    /// Section 8.4.1 puts the Object Status field on the wire only when the
293    /// Object Payload Length is zero, and Section 8.1.1.1 says "Any object
294    /// with a status code other than zero MUST have an empty payload". A
295    /// non-zero status paired with a non-zero payload length therefore has no
296    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
297    /// ordinary object, which is a different object from the one the caller
298    /// described. This refuses instead.
299    ///
300    /// The datagram types on this draft already refuse the same pairing. These
301    /// two did not, and they are the ones a publisher writes on every stream.
302    ///
303    /// # Errors
304    ///
305    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
306    /// a non-zero Object Payload Length.
307    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
308        check_end_of_track(self.object_id, self.object_status)?;
309        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
310            return Err(CodecError::InvalidField);
311        }
312        check_extension_count(self.extension_count, &self.extensions)?;
313        self.encode(buf);
314        Ok(())
315    }
316
317    /// Decode an object header from the buffer.
318    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
319        let object_id = VarInt::decode(buf)?;
320        let extension_count = VarInt::decode(buf)?;
321        let extensions = skip_extensions(buf, extension_count.into_inner())?;
322        let payload_length = VarInt::decode(buf)?;
323        let object_status = if payload_length.into_inner() == 0 {
324            let status_val = VarInt::decode(buf)?.into_inner();
325            ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
326        } else {
327            ObjectStatus::Normal
328        };
329        check_end_of_track(object_id, object_status)?;
330        Ok(Self { object_id, extension_count, extensions, payload_length, object_status })
331    }
332}
333
334// ============================================================
335// Datagram (type 0x01)
336// ============================================================
337
338/// Datagram header with payload (draft-08, type 0x01).
339///
340/// Encoding (after type varint):
341///   track_alias(vi), group_id(vi), object_id(vi),
342///   publisher_priority(u8), extension_count(vi), [extensions...],
343///   payload_length(vi),
344///   if payload_length == 0: object_status(vi),
345///   payload bytes
346#[derive(Debug, Clone, PartialEq, Eq)]
347pub struct DatagramHeader {
348    /// Track alias identifying the subscription.
349    pub track_alias: VarInt,
350    /// Group identifier.
351    pub group_id: VarInt,
352    /// Object identifier within the group.
353    pub object_id: VarInt,
354    /// Publisher priority for delivery ordering.
355    pub publisher_priority: u8,
356    /// Number of extensions.
357    pub extension_count: VarInt,
358    /// Raw extension bytes (opaque).
359    pub extensions: Vec<u8>,
360    /// Status of this object.
361    pub object_status: ObjectStatus,
362    /// Length of the object payload in bytes.
363    pub payload_length: VarInt,
364}
365
366impl DatagramHeader {
367    /// Encode the datagram header into the buffer.
368    ///
369    /// The declared payload length is taken as the authority on framing: the
370    /// status field is written exactly when that length is zero, because that
371    /// is the condition under which the OBJECT_DATAGRAM layout in draft-08
372    /// Section 8.2 carries one. That is what makes this infallible — and what
373    /// makes it lossy when the struct disagrees with itself. An
374    /// `object_status` set alongside a non-zero `payload_length` is discarded
375    /// here without a word. Prefer [`Self::encode_checked`], which refuses that
376    /// combination instead of resolving it.
377    pub fn encode(&self, buf: &mut impl BufMut) {
378        self.track_alias.encode(buf);
379        self.group_id.encode(buf);
380        self.object_id.encode(buf);
381        buf.put_u8(self.publisher_priority);
382        self.extension_count.encode(buf);
383        encode_extensions(&self.extensions, buf);
384        self.payload_length.encode(buf);
385        if self.payload_length.into_inner() == 0 {
386            VarInt::from_usize(self.object_status as usize).encode(buf);
387        }
388    }
389
390    /// Encode the datagram header, refusing a status the framing cannot carry.
391    ///
392    /// A datagram states its Object Status only when its Object Payload Length
393    /// is zero. With a non-zero length there is no status field on the wire, so
394    /// an `object_status` of anything but [`ObjectStatus::Normal`] has nowhere
395    /// to go: [`Self::encode`] drops it and the datagram parses back as an
396    /// ordinary payload object. An End of Group marker written that way does
397    /// not arrive late or malformed — it does not arrive at all, and the
398    /// receiver sees a normal object in its place.
399    ///
400    /// That pair is also the frame draft-08 Section 8.1.1.1 forbids outright:
401    /// "Any object with a status code other than zero MUST have an empty
402    /// payload." So the refusal here is not merely about what this encoder can
403    /// express; there is no conforming datagram to express.
404    ///
405    /// [`ObjectStatus::Normal`] beside a payload is not that case and is
406    /// accepted. It is the status every payload-bearing object has under the
407    /// rule above, and the one the encoding elides, so stating it asks for
408    /// exactly the bytes leaving it out asks for and nothing is lost.
409    ///
410    /// Errors with [`CodecError::InvalidField`] on the lossy combination,
411    /// before any byte is written, so a refused header leaves `buf` untouched.
412    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
413        check_end_of_track(self.object_id, self.object_status)?;
414        if self.payload_length.into_inner() != 0 && self.object_status != ObjectStatus::Normal {
415            return Err(CodecError::InvalidField);
416        }
417        check_extension_count(self.extension_count, &self.extensions)?;
418        self.encode(buf);
419        Ok(())
420    }
421
422    /// Decode a datagram header from the buffer.
423    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
424        let track_alias = VarInt::decode(buf)?;
425        let group_id = VarInt::decode(buf)?;
426        let object_id = VarInt::decode(buf)?;
427        if buf.remaining() < 1 {
428            return Err(CodecError::UnexpectedEnd);
429        }
430        let publisher_priority = buf.get_u8();
431        let extension_count = VarInt::decode(buf)?;
432        let extensions = skip_extensions(buf, extension_count.into_inner())?;
433        let payload_length = VarInt::decode(buf)?;
434        let object_status = if payload_length.into_inner() == 0 {
435            let status_val = VarInt::decode(buf)?.into_inner();
436            ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
437        } else {
438            ObjectStatus::Normal
439        };
440        check_end_of_track(object_id, object_status)?;
441        Ok(Self {
442            track_alias,
443            group_id,
444            object_id,
445            publisher_priority,
446            extension_count,
447            extensions,
448            object_status,
449            payload_length,
450        })
451    }
452}
453
454// ============================================================
455// Datagram Status (type 0x02)
456// ============================================================
457
458/// Datagram status header (draft-08, type 0x02).
459///
460/// Status-only datagram with no payload or extensions.
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct DatagramStatusHeader {
463    /// Track alias identifying the subscription.
464    pub track_alias: VarInt,
465    /// Group identifier.
466    pub group_id: VarInt,
467    /// Object identifier within the group.
468    pub object_id: VarInt,
469    /// Publisher priority for delivery ordering.
470    pub publisher_priority: u8,
471    /// Object status code.
472    pub object_status: ObjectStatus,
473}
474
475impl DatagramStatusHeader {
476    /// Encode the datagram status header into the buffer.
477    pub fn encode(&self, buf: &mut impl BufMut) {
478        self.track_alias.encode(buf);
479        self.group_id.encode(buf);
480        self.object_id.encode(buf);
481        buf.put_u8(self.publisher_priority);
482        VarInt::from_usize(self.object_status as usize).encode(buf);
483    }
484
485    /// Encode the datagram status header, refusing an end-of-track object that
486    /// does not end at object zero.
487    ///
488    /// # Errors
489    ///
490    /// [`CodecError::EndOfTrackObjectId`] if an end-of-track status is paired
491    /// with a non-zero Object ID.
492    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
493        check_end_of_track(self.object_id, self.object_status)?;
494        self.encode(buf);
495        Ok(())
496    }
497
498    /// Decode a datagram status header from the buffer.
499    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
500        let track_alias = VarInt::decode(buf)?;
501        let group_id = VarInt::decode(buf)?;
502        let object_id = VarInt::decode(buf)?;
503        if buf.remaining() < 1 {
504            return Err(CodecError::UnexpectedEnd);
505        }
506        let publisher_priority = buf.get_u8();
507        let status_val = VarInt::decode(buf)?.into_inner();
508        let object_status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
509        check_end_of_track(object_id, object_status)?;
510        Ok(Self { track_alias, group_id, object_id, publisher_priority, object_status })
511    }
512}
513
514// ============================================================
515// Datagram framing
516// ============================================================
517
518/// One datagram, of whichever shape its type field names.
519///
520/// A MoQT datagram opens with a variable-length integer naming its type, and
521/// that integer is what says which of the layouts above follows it.
522/// Neither [`DatagramHeader`] nor [`DatagramStatusHeader`] reads or writes it, so neither can be handed
523/// the first byte of a datagram a peer sent, and neither produces bytes a peer
524/// can read. This is the entry point that does both.
525///
526/// The payload of a payload-bearing datagram runs to the end of the QUIC
527/// datagram, so it is not part of this value: [`Self::decode`] stops at the end
528/// of the header and leaves the payload in the buffer, and a caller appends the
529/// payload after [`Self::encode`].
530#[derive(Debug, Clone, PartialEq, Eq)]
531pub enum Datagram {
532    /// An object carrying a payload.
533    Payload(DatagramHeader),
534    /// An object stating a status, with no payload.
535    Status(DatagramStatusHeader),
536}
537
538impl Datagram {
539    /// Whether this datagram states an Object Status instead of carrying a
540    /// payload.
541    ///
542    /// Draft-08 can say it two ways: on the dedicated status message, and on
543    /// the payload message with a declared payload length of zero, which
544    /// draft-09 removed.
545    pub fn is_status(&self) -> bool {
546        match self {
547            Self::Payload(header) => header.payload_length.into_inner() == 0,
548            Self::Status(_) => true,
549        }
550    }
551
552    /// The type field this value writes.
553    pub fn datagram_type(&self) -> StreamType {
554        match self {
555            Self::Payload(_) => StreamType::Datagram,
556            Self::Status(_) => StreamType::DatagramStatus,
557        }
558    }
559
560    /// Decode a datagram from its first byte, type field included.
561    ///
562    /// Errors with [`CodecError::UnknownDatagramType`] when the leading type is
563    /// one the datagram table does not assign, which this draft answers with a
564    /// close, and with [`CodecError::InvalidField`] for the stream types, which
565    /// it does assign but not to a datagram. `datagram_type_error` draws that
566    /// line.
567    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
568        let raw = VarInt::decode(buf)?.into_inner();
569        match StreamType::from_id(raw) {
570            Some(StreamType::Datagram) => Ok(Self::Payload(DatagramHeader::decode(buf)?)),
571            Some(StreamType::DatagramStatus) => {
572                Ok(Self::Status(DatagramStatusHeader::decode(buf)?))
573            }
574            _ => Err(datagram_type_error(raw)),
575        }
576    }
577
578    /// Encode the datagram, type field included.
579    pub fn encode(&self, buf: &mut impl BufMut) {
580        VarInt::from_usize(self.datagram_type() as usize).encode(buf);
581        match self {
582            Self::Payload(header) => header.encode(buf),
583            Self::Status(header) => header.encode(buf),
584        }
585    }
586
587    /// Encode the datagram, refusing a header the framing it names cannot
588    /// carry.
589    ///
590    /// The body is built before anything reaches `buf`, so a refused datagram
591    /// leaves `buf` untouched rather than a type field with no body under it.
592    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
593        let mut body = Vec::with_capacity(64);
594        match self {
595            Self::Payload(header) => header.encode_checked(&mut body)?,
596            Self::Status(header) => header.encode_checked(&mut body)?,
597        }
598        VarInt::from_usize(self.datagram_type() as usize).encode(buf);
599        buf.put_slice(&body);
600        Ok(())
601    }
602}
603
604// ============================================================
605// Fetch stream (type 0x05)
606// ============================================================
607
608/// Fetch stream header (follows the stream type varint).
609#[derive(Debug, Clone, PartialEq, Eq)]
610pub struct FetchHeader {
611    /// Subscribe ID this fetch responds to.
612    pub subscribe_id: VarInt,
613}
614
615/// Object within a fetch stream (draft-08).
616///
617/// Encoding: group_id(vi), subgroup_id(vi), object_id(vi),
618///   publisher_priority(u8), extension_count(vi), [extensions...],
619///   payload_length(vi),
620///   [object_status(vi) if payload_length==0],
621///   payload bytes
622#[derive(Debug, Clone, PartialEq, Eq)]
623pub struct FetchObjectHeader {
624    /// Group identifier.
625    pub group_id: VarInt,
626    /// Subgroup identifier within the group.
627    pub subgroup_id: VarInt,
628    /// Object identifier within the subgroup.
629    pub object_id: VarInt,
630    /// Publisher priority for delivery ordering.
631    pub publisher_priority: u8,
632    /// Number of extensions.
633    pub extension_count: VarInt,
634    /// Raw extension bytes (opaque).
635    pub extensions: Vec<u8>,
636    /// Status of this object.
637    pub object_status: ObjectStatus,
638    /// Length of the object payload in bytes.
639    pub payload_length: VarInt,
640}
641
642impl FetchHeader {
643    /// Encode a fetch stream header including its leading stream-type field,
644    /// so the bytes form the start of a data stream a peer can read.
645    ///
646    /// [`Self::encode`] writes the body alone, which is what a caller wants
647    /// once the stream is already open and what a caller must not use for its
648    /// first write. The read side has had [`Self::decode_stream`] all along,
649    /// so without this the codec could not round-trip its own fetch stream
650    /// through its own reader.
651    pub fn encode_stream(&self, buf: &mut impl BufMut) {
652        VarInt::from_usize(StreamType::Fetch as usize).encode(buf);
653        self.encode(buf);
654    }
655
656    /// Encode the fetch header into the buffer.
657    pub fn encode(&self, buf: &mut impl BufMut) {
658        self.subscribe_id.encode(buf);
659    }
660
661    /// Decode a fetch header from the buffer.
662    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
663        let subscribe_id = VarInt::decode(buf)?;
664        Ok(Self { subscribe_id })
665    }
666
667    /// Decode a fetch header from the start of a data stream, consuming the
668    /// leading stream type varint.
669    ///
670    /// Errors with [`CodecError::UnknownStreamType`] when the stream type is
671    /// one the stream table does not assign, which this draft answers with a
672    /// close, and with [`CodecError::InvalidField`] when it is assigned but is
673    /// not [`StreamType::Fetch`]. `stream_type_error` draws that line.
674    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
675        let stream_type = VarInt::decode(buf)?.into_inner();
676        if stream_type != StreamType::Fetch as u64 {
677            return Err(stream_type_error(stream_type));
678        }
679        Self::decode(buf)
680    }
681}
682
683impl FetchObjectHeader {
684    /// Encode the fetch object header into the buffer.
685    pub fn encode(&self, buf: &mut impl BufMut) {
686        self.group_id.encode(buf);
687        self.subgroup_id.encode(buf);
688        self.object_id.encode(buf);
689        buf.put_u8(self.publisher_priority);
690        self.extension_count.encode(buf);
691        encode_extensions(&self.extensions, buf);
692        self.payload_length.encode(buf);
693        if self.payload_length.into_inner() == 0 {
694            VarInt::from_usize(self.object_status as usize).encode(buf);
695        }
696    }
697
698    /// Encode the header, refusing a status the framing cannot carry.
699    ///
700    /// Section 8.4.3 puts the Object Status field on the wire only when the
701    /// Object Payload Length is zero, and Section 8.1.1.1 says "Any object
702    /// with a status code other than zero MUST have an empty payload". A
703    /// non-zero status paired with a non-zero payload length therefore has no
704    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
705    /// ordinary object, which is a different object from the one the caller
706    /// described. This refuses instead.
707    ///
708    /// The datagram types on this draft already refuse the same pairing. These
709    /// two did not, and they are the ones a publisher writes on every stream.
710    ///
711    /// # Errors
712    ///
713    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
714    /// a non-zero Object Payload Length.
715    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
716        check_end_of_track(self.object_id, self.object_status)?;
717        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
718            return Err(CodecError::InvalidField);
719        }
720        check_extension_count(self.extension_count, &self.extensions)?;
721        self.encode(buf);
722        Ok(())
723    }
724
725    /// Decode a fetch object header from the buffer.
726    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
727        let group_id = VarInt::decode(buf)?;
728        let subgroup_id = VarInt::decode(buf)?;
729        let object_id = VarInt::decode(buf)?;
730        if buf.remaining() < 1 {
731            return Err(CodecError::UnexpectedEnd);
732        }
733        let publisher_priority = buf.get_u8();
734        let extension_count = VarInt::decode(buf)?;
735        let extensions = skip_extensions(buf, extension_count.into_inner())?;
736        let payload_length = VarInt::decode(buf)?;
737        let object_status = if payload_length.into_inner() == 0 {
738            let status_val = VarInt::decode(buf)?.into_inner();
739            ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
740        } else {
741            ObjectStatus::Normal
742        };
743        check_end_of_track(object_id, object_status)?;
744        Ok(Self {
745            group_id,
746            subgroup_id,
747            object_id,
748            publisher_priority,
749            extension_count,
750            extensions,
751            object_status,
752            payload_length,
753        })
754    }
755}