Skip to main content

moqtap_codec/draft11/
data_stream.rs

1//! Draft-11 data stream header encoding and decoding.
2//!
3//! Changes from draft-09/10:
4//! - Datagram stream type IDs: 0x00 (no ext), 0x01 (with ext), 0x02 (status, no ext),
5//!   0x03 (status, with ext)
6//! - Subgroup stream types: 0x08-0x0D (6 variants based on subgroup_id encoding and extensions)
7//! - Fetch stream type: 0x05 (request_id only in header)
8//! - Object within subgroup: object_id + [ext_headers_length + extensions] + payload_length
9//!   + [object_status if payload_length=0]
10
11use super::types::ObjectStatus;
12use crate::error::CodecError;
13use crate::object_properties::{parse_object_properties, ObjectProperty, PropertyValue};
14use crate::types::read_bytes;
15use crate::varint::VarInt;
16use crate::version::DraftVersion;
17use bytes::{Buf, BufMut};
18
19/// Stream type IDs for draft-11 data streams.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[repr(u64)]
22pub enum StreamType {
23    /// Object datagram, no extensions (0x00).
24    Datagram = 0x00,
25    /// Object datagram, with extensions (0x01).
26    DatagramExt = 0x01,
27    /// Object datagram status, no extensions (0x02).
28    DatagramStatus = 0x02,
29    /// Object datagram status, with extensions (0x03).
30    DatagramStatusExt = 0x03,
31    /// Fetch response stream (0x05).
32    Fetch = 0x05,
33    /// Subgroup: subgroup_id=0, no extensions (0x08).
34    SubgroupZero = 0x08,
35    /// Subgroup: subgroup_id=0, with extensions (0x09).
36    SubgroupZeroExt = 0x09,
37    /// Subgroup: subgroup_id=first object ID, no extensions (0x0A).
38    SubgroupFirstObj = 0x0A,
39    /// Subgroup: subgroup_id=first object ID, with extensions (0x0B).
40    SubgroupFirstObjExt = 0x0B,
41    /// Subgroup: explicit subgroup_id, no extensions (0x0C).
42    SubgroupExplicit = 0x0C,
43    /// Subgroup: explicit subgroup_id, with extensions (0x0D).
44    SubgroupExplicitExt = 0x0D,
45}
46
47/// Hold an object to the rule that a non-existent object carries no extensions.
48///
49/// Section 9.1.1.2: "Any Object may have extension headers except those with
50/// Object Status 'Object Does Not Exist'. If an endpoint receives a non-existent
51/// Object containing extension headers it MUST close the session with a Protocol
52/// Violation."
53///
54/// The sentence is about a receiver, and it reaches all three carriers that can
55/// announce a status: an object on a subgroup stream, an object on a fetch
56/// stream, and a status datagram. A plain datagram has no status field, so it
57/// is the only carrier that cannot break the rule.
58///
59/// Reported under [`CodecError::ExtensionsOnNonExistentObject`], which is this
60/// rule and nothing else. [`CodecError::InvalidField`] is too coarse for it:
61/// shared with a dozen unrelated malformations the draft does not answer with a
62/// close, it leaves a caller unable to act on the sentence above.
63fn check_extensions_against_status(
64    status: ObjectStatus,
65    extensions: &[u8],
66) -> Result<(), CodecError> {
67    if status == ObjectStatus::ObjectDoesNotExist && !extensions.is_empty() {
68        return Err(CodecError::ExtensionsOnNonExistentObject(extensions.len()));
69    }
70    Ok(())
71}
72
73impl StreamType {
74    /// Convert a raw stream type ID to a `StreamType`, if valid.
75    pub fn from_id(id: u64) -> Option<Self> {
76        match id {
77            0x00 => Some(StreamType::Datagram),
78            0x01 => Some(StreamType::DatagramExt),
79            0x02 => Some(StreamType::DatagramStatus),
80            0x03 => Some(StreamType::DatagramStatusExt),
81            0x05 => Some(StreamType::Fetch),
82            0x08 => Some(StreamType::SubgroupZero),
83            0x09 => Some(StreamType::SubgroupZeroExt),
84            0x0A => Some(StreamType::SubgroupFirstObj),
85            0x0B => Some(StreamType::SubgroupFirstObjExt),
86            0x0C => Some(StreamType::SubgroupExplicit),
87            0x0D => Some(StreamType::SubgroupExplicitExt),
88            _ => None,
89        }
90    }
91
92    /// Whether this stream type is a subgroup variant.
93    pub fn is_subgroup(&self) -> bool {
94        matches!(
95            self,
96            StreamType::SubgroupZero
97                | StreamType::SubgroupZeroExt
98                | StreamType::SubgroupFirstObj
99                | StreamType::SubgroupFirstObjExt
100                | StreamType::SubgroupExplicit
101                | StreamType::SubgroupExplicitExt
102        )
103    }
104
105    /// Whether this stream type includes extension headers on objects.
106    pub fn has_extensions(&self) -> bool {
107        matches!(
108            self,
109            StreamType::DatagramExt
110                | StreamType::DatagramStatusExt
111                | StreamType::SubgroupZeroExt
112                | StreamType::SubgroupFirstObjExt
113                | StreamType::SubgroupExplicitExt
114        )
115    }
116
117    /// True if this subgroup stream type puts an explicit Subgroup ID on the
118    /// wire.
119    ///
120    /// The Subgroup ID Field Present column of the SUBGROUP_HEADER type table
121    /// in Section 9.4.2. The other two columns of that row say what the
122    /// Subgroup ID *is* where the field is absent — zero, or the first
123    /// Object's ID — so this is only about the field, never about the value.
124    pub fn writes_subgroup_id(&self) -> bool {
125        matches!(self, StreamType::SubgroupExplicit | StreamType::SubgroupExplicitExt)
126    }
127}
128
129/// Which failure a leading unidirectional stream type that is not the one a
130/// reader wants is.
131///
132/// Section 9: "An endpoint that receives an unknown stream or datagram type
133/// MUST close the session." One sentence, two tables. The stream table assigns
134/// 0x05 for FETCH_HEADER and the range 0x08 to 0x0D for SUBGROUP_HEADER;
135/// everything outside those is unknown at the head of a stream, and the session
136/// ends.
137///
138/// The six subgroup values are one type spread over the combinations of two
139/// choices, which is why the range has no holes in it and why a value just past
140/// its end — 0x0E — is unknown rather than a subgroup variant this decoder has
141/// not learned yet.
142///
143/// [`StreamType`] holds both tables in one enum because draft-11's numbers do
144/// not collide across them, so membership in it is not the question. A datagram
145/// type at the head of a stream is unknown there, and this is where that is
146/// decided.
147fn stream_type_error(raw: u64) -> CodecError {
148    match StreamType::from_id(raw) {
149        Some(t) if t.is_subgroup() || t == StreamType::Fetch => CodecError::InvalidField,
150        _ => CodecError::UnknownStreamType(raw),
151    }
152}
153
154/// Which failure a leading datagram type that is not one a reader wants is.
155///
156/// The datagram half of the sentence quoted on `stream_type_error`, read
157/// against the other table: 0x00 to 0x03 are what it assigns, and everything
158/// else arriving as a datagram is unknown.
159///
160/// Every assigned datagram value is decodable here, so the
161/// [`CodecError::InvalidField`] arm is reached only by the stream types
162/// delivered as datagrams — the mirror image of the case above, and refused the
163/// same way, without ending the session over a number this draft defines.
164fn datagram_type_error(raw: u64) -> CodecError {
165    if StreamType::from_id(raw).is_some() {
166        CodecError::InvalidField
167    } else {
168        CodecError::UnknownDatagramType(raw)
169    }
170}
171
172// ── Extension helpers ─────────────────────────────────────────
173
174fn read_extension_bytes(buf: &mut impl Buf, byte_len: u64) -> Result<Vec<u8>, CodecError> {
175    read_bytes(buf, byte_len as usize)
176}
177
178// ============================================================
179// Subgroup stream header
180// ============================================================
181
182/// Subgroup stream header (unified across all 6 stream type variants).
183///
184/// Decoded representation includes `stream_type` to preserve the variant.
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct SubgroupHeader {
187    /// The stream type variant used for encoding.
188    pub stream_type: StreamType,
189    /// Track alias identifying the subscription.
190    pub track_alias: VarInt,
191    /// Group identifier.
192    pub group_id: VarInt,
193    /// Subgroup identifier within the group.
194    pub subgroup_id: VarInt,
195    /// Publisher priority for delivery ordering.
196    pub publisher_priority: u8,
197}
198
199impl SubgroupHeader {
200    /// Encode a subgroup stream header including its leading stream-type
201    /// field, so the bytes form the start of a data stream a peer can read.
202    ///
203    /// [`Self::encode`] writes the body alone, which is what a caller wants
204    /// once the stream is already open and what a caller must not use for its
205    /// first write. It is also the half that cannot stand on its own here,
206    /// because the stream type is what says whether a Subgroup ID follows it
207    /// and whether the objects on the stream carry extension headers.
208    pub fn encode_stream(&self, buf: &mut impl BufMut) {
209        VarInt::from_usize(self.stream_type as usize).encode(buf);
210        self.encode(buf);
211    }
212
213    /// Encode the subgroup header (always as explicit subgroup_id format).
214    /// Encode the header body, without its leading stream-type field.
215    ///
216    /// Driven by the stream type, and silent about a `subgroup_id` it decides
217    /// not to write: on a type whose Subgroup ID Field Present column reads No
218    /// the field is dropped, and the peer reads the subgroup the *type* names -
219    /// zero, or the first Object's ID - rather than the one in hand. Nothing is
220    /// malformed about the result, which is what makes it worth refusing rather
221    /// than tolerating. [`Self::encode_checked`] refuses it.
222    pub fn encode(&self, buf: &mut impl BufMut) {
223        self.track_alias.encode(buf);
224        self.group_id.encode(buf);
225        if self.stream_type.writes_subgroup_id() {
226            self.subgroup_id.encode(buf);
227        }
228        buf.put_u8(self.publisher_priority);
229    }
230
231    /// Encode the header body, refusing a Subgroup ID this stream type has
232    /// nowhere to put.
233    ///
234    /// [`Self::decode_with_type`] leaves the field at zero for every type that
235    /// does not carry it, so a decoded header always passes: the refusal is for
236    /// a header assembled by hand, where a caller set an ID the type will
237    /// discard.
238    ///
239    /// A zero is accepted under any type. It is what the decoder produces, and
240    /// on a Subgroup ID Value column reading `0` it is also the truth, so
241    /// refusing it would refuse the ordinary case to catch nothing.
242    ///
243    /// # Errors
244    ///
245    /// [`CodecError::InvalidField`] if a non-zero Subgroup ID sits under a
246    /// stream type that writes no Subgroup ID field.
247    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
248        if !self.stream_type.writes_subgroup_id() && self.subgroup_id.into_inner() != 0 {
249            return Err(CodecError::InvalidField);
250        }
251        self.encode(buf);
252        Ok(())
253    }
254
255    /// Decode a subgroup header (assumes explicit subgroup_id format).
256    ///
257    /// For stream-type-aware decoding, use [`Self::decode_with_type`].
258    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
259        Self::decode_with_type(StreamType::SubgroupExplicit, buf)
260    }
261
262    /// Decode a subgroup header with the specific stream type variant.
263    pub fn decode_with_type(
264        stream_type: StreamType,
265        buf: &mut impl Buf,
266    ) -> Result<Self, CodecError> {
267        let track_alias = VarInt::decode(buf)?;
268        let group_id = VarInt::decode(buf)?;
269        let subgroup_id = match stream_type {
270            StreamType::SubgroupZero | StreamType::SubgroupZeroExt => VarInt::from_usize(0),
271            StreamType::SubgroupExplicit | StreamType::SubgroupExplicitExt => VarInt::decode(buf)?,
272            // For FirstObj variants, subgroup_id is the first object's ID.
273            // We read it later from the first object. Set to 0 for now;
274            // the caller should update after reading the first object.
275            StreamType::SubgroupFirstObj | StreamType::SubgroupFirstObjExt => VarInt::from_usize(0),
276            _ => return Err(CodecError::InvalidField),
277        };
278        if buf.remaining() < 1 {
279            return Err(CodecError::UnexpectedEnd);
280        }
281        let publisher_priority = buf.get_u8();
282        Ok(Self { stream_type, track_alias, group_id, subgroup_id, publisher_priority })
283    }
284
285    /// Decode a subgroup header from the start of a data stream, consuming
286    /// the leading stream type varint and using it to select the variant.
287    ///
288    /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
289    /// not assign the leading type, which this draft answers with a close, and
290    /// with [`CodecError::InvalidField`] when it does assign it but not to a
291    /// subgroup. `stream_type_error` draws that line.
292    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
293        let raw = VarInt::decode(buf)?.into_inner();
294        let stream_type = StreamType::from_id(raw).ok_or_else(|| stream_type_error(raw))?;
295        if !stream_type.is_subgroup() {
296            return Err(stream_type_error(raw));
297        }
298        Self::decode_with_type(stream_type, buf)
299    }
300}
301
302// ============================================================
303// Object header within subgroup
304// ============================================================
305
306/// Object within a subgroup stream (draft-11).
307#[derive(Debug, Clone, PartialEq, Eq)]
308pub struct ObjectHeader {
309    /// Object identifier within the subgroup.
310    pub object_id: VarInt,
311    /// Total byte length of extension headers (0 if no extensions).
312    pub extension_headers_length: VarInt,
313    /// Raw extension bytes (opaque).
314    pub extensions: Vec<u8>,
315    /// Length of the object payload in bytes.
316    pub payload_length: VarInt,
317    /// Object status (Normal unless payload_length == 0).
318    pub object_status: ObjectStatus,
319}
320
321impl ObjectHeader {
322    /// Encode the object header in the framing that carries no extension
323    /// block.
324    ///
325    /// Lossy, and lossy in a way the caller cannot see: an object holding
326    /// extension headers is written without them and without a word. Which
327    /// framing is correct is not a property of the object at all - Section
328    /// 9.4.2 gives the stream's type an Extensions Present column, and every
329    /// object on the stream follows it - so this entry point can only guess,
330    /// and it guesses "absent". Prefer [`Self::encode_with_extensions`], which
331    /// is told, or [`Self::encode_checked`], which refuses what it would
332    /// otherwise drop.
333    pub fn encode(&self, buf: &mut impl BufMut) {
334        self.encode_with_extensions(false, buf);
335    }
336
337    /// Encode the header, refusing a status the framing cannot carry.
338    ///
339    /// Section 9.4.2 puts the Object Status field on the wire only when the
340    /// Object Payload Length is zero, and Section 9.1.1.1 says "Any object
341    /// with a status code other than zero MUST have an empty payload". A
342    /// non-zero status paired with a non-zero payload length therefore has no
343    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
344    /// ordinary object, which is a different object from the one the caller
345    /// described. This refuses instead.
346    ///
347    /// The datagram types on this draft already refuse the same pairing. These
348    /// two did not, and they are the ones a publisher writes on every stream.
349    ///
350    /// Extension headers are refused here rather than dropped, for a reason
351    /// the status rule does not share: this entry point writes the framing
352    /// that has no Extension Headers Length field, so the bytes have nowhere
353    /// to go. Writing them anyway is not an option and losing them silently
354    /// puts a stream on the wire that no reader can follow - a reader on an
355    /// extensions-bearing stream takes the Object Payload Length as the
356    /// extension length and every object after it is misread. A caller that
357    /// knows the stream's framing wants
358    /// [`Self::encode_checked_with_extensions`].
359    ///
360    /// # Errors
361    ///
362    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
363    /// a non-zero Object Payload Length, or if the object carries extension
364    /// headers this framing cannot write.
365    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
366        self.encode_checked_with_extensions(false, buf)
367    }
368
369    /// Encode the object header into a stream whose type has already settled
370    /// whether objects carry an extension block, refusing what that framing
371    /// cannot express.
372    ///
373    /// `has_extensions` is the stream's answer, not the object's: Section
374    /// 9.4.2 fixes it for the whole stream from the SUBGROUP_HEADER type, so
375    /// an object with no extensions on a stream that carries them still writes
376    /// a length of zero, and that is the one direction not refused here. The
377    /// other direction has no encoding, so it is refused.
378    ///
379    /// # Errors
380    ///
381    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
382    /// a non-zero Object Payload Length, or if `has_extensions` is `false`
383    /// while the object carries extension headers.
384    pub fn encode_checked_with_extensions(
385        &self,
386        has_extensions: bool,
387        buf: &mut impl BufMut,
388    ) -> Result<(), CodecError> {
389        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
390            return Err(CodecError::InvalidField);
391        }
392        if !has_extensions && !self.extensions.is_empty() {
393            return Err(CodecError::InvalidField);
394        }
395        self.encode_with_extensions(has_extensions, buf);
396        Ok(())
397    }
398
399    /// Encode the object header, writing the extension block only when the
400    /// stream's type says objects carry one.
401    ///
402    /// Infallible, and so unable to say that a `false` here discards the
403    /// extension headers the object holds. [`Self::encode_checked_with_extensions`]
404    /// is the same write with that refusal in front of it.
405    pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
406        self.object_id.encode(buf);
407        if has_extensions {
408            VarInt::from_usize(self.extensions.len()).encode(buf);
409            buf.put_slice(&self.extensions);
410        }
411        self.payload_length.encode(buf);
412        if self.payload_length.into_inner() == 0 {
413            VarInt::from_usize(self.object_status as usize).encode(buf);
414        }
415    }
416
417    /// Decode an object header (no extensions).
418    ///
419    /// For extension-aware decoding, use [`Self::decode_with_extensions`].
420    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
421        Self::decode_with_extensions(false, buf)
422    }
423
424    /// Decode an object header with extensions control.
425    pub fn decode_with_extensions(
426        has_extensions: bool,
427        buf: &mut impl Buf,
428    ) -> Result<Self, CodecError> {
429        let object_id = VarInt::decode(buf)?;
430        let (extension_headers_length, extensions) = if has_extensions {
431            let ehl = VarInt::decode(buf)?;
432            let ext = read_extension_bytes(buf, ehl.into_inner())?;
433            (ehl, ext)
434        } else {
435            (VarInt::from_usize(0), Vec::new())
436        };
437        let payload_length = VarInt::decode(buf)?;
438        let object_status = if payload_length.into_inner() == 0 {
439            let sv = VarInt::decode(buf)?.into_inner();
440            ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?
441        } else {
442            ObjectStatus::Normal
443        };
444        check_extensions_against_status(object_status, &extensions)?;
445        Ok(Self { object_id, extension_headers_length, extensions, payload_length, object_status })
446    }
447}
448
449// ============================================================
450// Prior Group ID Gap
451// ============================================================
452
453/// Prior Group ID Gap, Extension Header Type 0x40 (draft-11 Section 10.1).
454pub const PRIOR_GROUP_ID_GAP: u64 = 0x40;
455
456/// The Prior Group ID Gap values one Object carries.
457///
458/// draft-11 Section 10.1: "A track with a Group that contains more than one
459/// Object with different values for Prior Group ID Gap or has a Prior Group ID
460/// Gap larger than the Group ID is considered malformed." This draft does not
461/// limit an Object to one instance, so every value is kept. The least is
462/// held to the Group ID, for the reason [`SubgroupGaps`] gives.
463#[derive(Debug, Clone, Default, PartialEq, Eq)]
464pub struct PriorGaps {
465    /// Each Prior Group ID Gap, in wire order.
466    pub group: Vec<u64>,
467}
468
469impl PriorGaps {
470    /// Read the gaps from an Object's parsed extension headers, refusing them
471    /// with [`CodecError::MalformedTrack`] when every one is larger than
472    /// `group_id`.
473    pub fn from_properties(
474        properties: &[ObjectProperty<'_>],
475        group_id: u64,
476    ) -> Result<Self, CodecError> {
477        let group: Vec<u64> = properties
478            .iter()
479            .filter_map(|property| match (property.property_type, &property.value) {
480                (PRIOR_GROUP_ID_GAP, PropertyValue::Varint(value)) => Some(*value),
481                _ => None,
482            })
483            .collect();
484        if let Some(gap) = group.iter().copied().min().filter(|&gap| gap > group_id) {
485            return Err(CodecError::MalformedTrack {
486                detail: format!("Prior Group ID Gap {gap} is larger than Group ID {group_id}"),
487            });
488        }
489        Ok(Self { group })
490    }
491
492    /// [`Self::from_properties`] on an extension block, refusing a block that
493    /// does not parse as [`crate::object_properties::parse_object_properties`]
494    /// does.
495    pub fn from_extensions(extensions: &[u8], group_id: u64) -> Result<Self, CodecError> {
496        let properties = parse_object_properties(DraftVersion::Draft11, extensions)?;
497        Self::from_properties(&properties, group_id)
498    }
499}
500
501/// The Prior Group ID Gap values the Objects of one subgroup stream carried.
502///
503/// A subgroup stream carries one Group, so its Objects are where "a Group that
504/// contains more than one Object with different values for Prior Group ID Gap"
505/// (draft-11 Section 10.1) shows on one stream.
506///
507/// This draft does not say whether an Object may carry 0x40 more than once, or
508/// which value is that Object's when it does. draft-11 Section 9.1.1.2 says
509/// only "Object Extension Headers are serialized as Key-Value-Pairs", and the
510/// no-repeat rule of draft-11 Section 8.2, "Senders MUST NOT repeat the same
511/// parameter type in a message", covers message Parameters. So the Objects
512/// carry different values only when no value is carried by every one of them:
513/// then, whichever of its values is taken as each Object's, two of them differ.
514/// While a value is common to all, taking it as each Object's makes them equal,
515/// and they are not refused. The values one Object carries are not compared
516/// with each other, since the condition needs "more than one Object".
517///
518/// "has a Prior Group ID Gap larger than the Group ID" (draft-11 Section 10.1)
519/// also depends on which value is the Object's. An Object carrying a value
520/// larger than the Group ID and one that is not has a gap larger than the Group
521/// ID under one reading and not under another, so it is refused only when every
522/// value it carries is larger.
523#[derive(Debug, Clone, PartialEq, Eq)]
524pub struct SubgroupGaps {
525    group_id: u64,
526    /// The values every earlier Object that carried a gap carried, or `None`
527    /// before the first such Object.
528    common: Option<Vec<u64>>,
529}
530
531impl SubgroupGaps {
532    /// Track the Objects of the stream `header` opens.
533    pub fn new(header: &SubgroupHeader) -> Self {
534        Self { group_id: header.group_id.into_inner(), common: None }
535    }
536
537    /// Check the next Object on this stream, refusing with
538    /// [`CodecError::MalformedTrack`] a gap larger than the Group ID, or
539    /// values that leave no value common to every Object on the stream that
540    /// carried a gap. Records the Object only when it passes.
541    pub fn check(&mut self, object: &ObjectHeader) -> Result<PriorGaps, CodecError> {
542        let gaps = PriorGaps::from_extensions(&object.extensions, self.group_id)?;
543        if gaps.group.is_empty() {
544            return Ok(gaps);
545        }
546        let common: Vec<u64> = match &self.common {
547            Some(common) => common.iter().copied().filter(|v| gaps.group.contains(v)).collect(),
548            None => {
549                let mut values = gaps.group.clone();
550                values.sort_unstable();
551                values.dedup();
552                values
553            }
554        };
555        if common.is_empty() {
556            return Err(CodecError::MalformedTrack {
557                detail: format!(
558                    "the Objects in Group {} carry no Prior Group ID Gap value in common",
559                    self.group_id
560                ),
561            });
562        }
563        self.common = Some(common);
564        Ok(gaps)
565    }
566}
567
568/// The Prior Group ID Gap conditions of draft-11 Section 10.1 that one
569/// fetch stream shows.
570///
571/// [`PriorGaps::from_extensions`] refuses a gap larger than the Group ID. Across
572/// the stream this refuses Objects of one Group with no value in common, read
573/// as [`SubgroupGaps`] reads them, and "an Object with a Group ID within a
574/// previously communicated gap" for the Group before on the stream. An Object
575/// whose values differ has communicated at least the least of them, whichever
576/// is that Object's. draft-11 Section 8.13: "Within each group,
577/// objects are sent in Object ID order; subgroup ID is not used for ordering."
578///
579/// [`FetchObjectHeader::decode`] reads one Object and applies none of this: a
580/// reader of a fetch stream checks each Object with one `FetchGaps`.
581#[derive(Debug, Clone, PartialEq, Eq)]
582// Boxed so that the fetch readers holding one stay small.
583pub struct FetchGaps(Box<crate::prior_gaps::FetchGapTracker>);
584
585impl Default for FetchGaps {
586    fn default() -> Self {
587        Self::new()
588    }
589}
590
591impl FetchGaps {
592    /// Track the Objects of one fetch stream.
593    pub fn new() -> Self {
594        Self(Box::new(crate::prior_gaps::FetchGapTracker::new(
595            crate::prior_gaps::GapRules::Draft11To13,
596        )))
597    }
598
599    /// Check the next Object on the stream, from its Group ID, Object ID and
600    /// extension block, refusing a gap condition with
601    /// [`CodecError::MalformedTrack`] and a block that does not parse as
602    /// [`parse_object_properties`] does. Records the Object only when it
603    /// passes.
604    pub fn check(
605        &mut self,
606        group_id: u64,
607        object_id: u64,
608        extensions: &[u8],
609    ) -> Result<PriorGaps, CodecError> {
610        let gaps = PriorGaps::from_extensions(extensions, group_id)?;
611        self.0.check(group_id, object_id, &gaps.group, None)?;
612        Ok(gaps)
613    }
614}
615
616// ============================================================
617// Datagram (types 0x00, 0x01)
618// ============================================================
619
620/// Datagram header (draft-11, types 0x00/0x01).
621///
622/// Payload is the remaining bytes in the datagram after the header.
623#[derive(Debug, Clone, PartialEq, Eq)]
624pub struct DatagramHeader {
625    /// Track alias identifying the subscription.
626    pub track_alias: VarInt,
627    /// Group identifier.
628    pub group_id: VarInt,
629    /// Object identifier within the group.
630    pub object_id: VarInt,
631    /// Publisher priority for delivery ordering.
632    pub publisher_priority: u8,
633    /// Total byte length of extension headers (0 for type 0x00).
634    pub extension_headers_length: VarInt,
635    /// Raw extension bytes.
636    pub extensions: Vec<u8>,
637}
638
639impl DatagramHeader {
640    /// Encode the datagram header in the framing that carries no extension
641    /// block.
642    ///
643    /// Lossy in the same way the subgroup object header is: an extension block
644    /// this value holds is dropped, because the framing being written has no
645    /// field for it. The type byte decides which framing is right, and this
646    /// entry point does not write the type byte, so it cannot consult it.
647    /// [`Datagram::encode`] does both together and never disagrees with itself;
648    /// this is the piece for a caller that has already written the type.
649    pub fn encode(&self, buf: &mut impl BufMut) {
650        self.encode_with_extensions(false, buf);
651    }
652
653    /// Encode the datagram header with extensions control.
654    pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
655        self.track_alias.encode(buf);
656        self.group_id.encode(buf);
657        self.object_id.encode(buf);
658        buf.put_u8(self.publisher_priority);
659        if has_extensions {
660            VarInt::from_usize(self.extensions.len()).encode(buf);
661            buf.put_slice(&self.extensions);
662        }
663    }
664
665    /// Encode the datagram header, refusing what this framing cannot carry.
666    ///
667    /// No status is ever refused, and that is a fact about this draft rather
668    /// than a check left out. This is the OBJECT_DATAGRAM of Section 9.2,
669    /// whose layout carries no Object Status field at all; a datagram that
670    /// states a status is the separate OBJECT_DATAGRAM_STATUS message, modelled
671    /// here as [`DatagramStatusHeader`]. So there is no status for
672    /// [`Self::encode`] to drop, and nothing for Section 9.1.1.1's "Any object
673    /// with a status code other than zero MUST have an empty payload" to rule
674    /// on: an object framed this way has status zero by construction.
675    ///
676    /// The extension block is a different matter. [`Self::encode`] writes the
677    /// framing without one, so a block this value holds has nowhere to go, and
678    /// dropping it silently is what puts a datagram on the wire describing
679    /// something other than what the caller built. That is refused here.
680    ///
681    /// The fallible signature is also what lets one entry point span every
682    /// draft. `dispatch::AnyDatagramHeader::encode` calls this on all thirteen,
683    /// and the drafts whose payload-bearing datagram *does* carry a status field
684    /// need somewhere to say no.
685    ///
686    /// # Errors
687    ///
688    /// [`CodecError::InvalidField`] if the value carries extension headers,
689    /// which this framing has no field for.
690    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
691        if !self.extensions.is_empty() {
692            return Err(CodecError::InvalidField);
693        }
694        self.encode(buf);
695        Ok(())
696    }
697
698    /// Decode a datagram header (no extensions).
699    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
700        Self::decode_with_extensions(false, buf)
701    }
702
703    /// Decode a datagram header with extensions control.
704    pub fn decode_with_extensions(
705        has_extensions: bool,
706        buf: &mut impl Buf,
707    ) -> Result<Self, CodecError> {
708        let track_alias = VarInt::decode(buf)?;
709        let group_id = VarInt::decode(buf)?;
710        let object_id = VarInt::decode(buf)?;
711        if buf.remaining() < 1 {
712            return Err(CodecError::UnexpectedEnd);
713        }
714        let publisher_priority = buf.get_u8();
715        let (extension_headers_length, extensions) = if has_extensions {
716            let ehl = VarInt::decode(buf)?;
717            // A datagram whose type says extensions are present must actually carry
718            // some: receiving one with an Extension Headers Length of 0 closes the
719            // session. The opposite holds on a subgroup stream, where the type byte is
720            // fixed for the whole stream and an object with no extensions has no other
721            // way to say so, which is why this check belongs to the datagram readers
722            // alone.
723            if ehl.into_inner() == 0 {
724                return Err(CodecError::InvalidField);
725            }
726            let ext = read_extension_bytes(buf, ehl.into_inner())?;
727            (ehl, ext)
728        } else {
729            (VarInt::from_usize(0), Vec::new())
730        };
731        Ok(Self {
732            track_alias,
733            group_id,
734            object_id,
735            publisher_priority,
736            extension_headers_length,
737            extensions,
738        })
739    }
740}
741
742// ============================================================
743// Datagram Status (types 0x02, 0x03)
744// ============================================================
745
746/// Datagram status header (draft-11, types 0x02/0x03).
747#[derive(Debug, Clone, PartialEq, Eq)]
748pub struct DatagramStatusHeader {
749    /// Track alias identifying the subscription.
750    pub track_alias: VarInt,
751    /// Group identifier.
752    pub group_id: VarInt,
753    /// Object identifier within the group.
754    pub object_id: VarInt,
755    /// Publisher priority for delivery ordering.
756    pub publisher_priority: u8,
757    /// Total byte length of extension headers (0 for type 0x02).
758    pub extension_headers_length: VarInt,
759    /// Raw extension bytes.
760    pub extensions: Vec<u8>,
761    /// Object status code.
762    pub object_status: ObjectStatus,
763}
764
765impl DatagramStatusHeader {
766    /// Encode the datagram status header (no extensions).
767    pub fn encode(&self, buf: &mut impl BufMut) {
768        self.encode_with_extensions(false, buf);
769    }
770
771    /// Encode the status datagram header, refusing an extension block this
772    /// framing cannot carry.
773    ///
774    /// The same one-sided rule the payload-bearing header obeys, and the same
775    /// reason for it: [`Self::encode`] writes the framing without a block, so
776    /// bytes held here would be dropped rather than written.
777    ///
778    /// # Errors
779    ///
780    /// [`CodecError::InvalidField`] if the value carries extension headers.
781    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
782        if !self.extensions.is_empty() {
783            return Err(CodecError::InvalidField);
784        }
785        self.encode(buf);
786        Ok(())
787    }
788
789    /// Encode the datagram status header with extensions control.
790    pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
791        self.track_alias.encode(buf);
792        self.group_id.encode(buf);
793        self.object_id.encode(buf);
794        buf.put_u8(self.publisher_priority);
795        if has_extensions {
796            VarInt::from_usize(self.extensions.len()).encode(buf);
797            buf.put_slice(&self.extensions);
798        }
799        VarInt::from_usize(self.object_status as usize).encode(buf);
800    }
801
802    /// Decode a datagram status header (no extensions).
803    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
804        Self::decode_with_extensions(false, buf)
805    }
806
807    /// Decode a datagram status header with extensions control.
808    pub fn decode_with_extensions(
809        has_extensions: bool,
810        buf: &mut impl Buf,
811    ) -> Result<Self, CodecError> {
812        let track_alias = VarInt::decode(buf)?;
813        let group_id = VarInt::decode(buf)?;
814        let object_id = VarInt::decode(buf)?;
815        if buf.remaining() < 1 {
816            return Err(CodecError::UnexpectedEnd);
817        }
818        let publisher_priority = buf.get_u8();
819        let (extension_headers_length, extensions) = if has_extensions {
820            let ehl = VarInt::decode(buf)?;
821            // A datagram whose type says extensions are present must actually carry
822            // some: receiving one with an Extension Headers Length of 0 closes the
823            // session. The opposite holds on a subgroup stream, where the type byte is
824            // fixed for the whole stream and an object with no extensions has no other
825            // way to say so, which is why this check belongs to the datagram readers
826            // alone.
827            if ehl.into_inner() == 0 {
828                return Err(CodecError::InvalidField);
829            }
830            let ext = read_extension_bytes(buf, ehl.into_inner())?;
831            (ehl, ext)
832        } else {
833            (VarInt::from_usize(0), Vec::new())
834        };
835        let sv = VarInt::decode(buf)?.into_inner();
836        let object_status = ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?;
837        check_extensions_against_status(object_status, &extensions)?;
838        Ok(Self {
839            track_alias,
840            group_id,
841            object_id,
842            publisher_priority,
843            extension_headers_length,
844            extensions,
845            object_status,
846        })
847    }
848}
849
850// ============================================================
851// Datagram framing
852// ============================================================
853
854/// One datagram, of whichever shape its type field names.
855///
856/// A MoQT datagram opens with a variable-length integer naming its type, and
857/// that integer is what says which of the layouts above follows it, and whether an extension block sits inside it.
858/// Neither [`DatagramHeader`] nor [`DatagramStatusHeader`] reads or writes it, so neither can be handed
859/// the first byte of a datagram a peer sent, and neither produces bytes a peer
860/// can read. This is the entry point that does both.
861///
862/// The payload of a payload-bearing datagram runs to the end of the QUIC
863/// datagram, so it is not part of this value: [`Self::decode`] stops at the end
864/// of the header and leaves the payload in the buffer, and a caller appends the
865/// payload after [`Self::encode`].
866#[derive(Debug, Clone, PartialEq, Eq)]
867pub enum Datagram {
868    /// An object carrying a payload.
869    Payload(DatagramHeader),
870    /// An object stating a status, with no payload.
871    Status(DatagramStatusHeader),
872}
873
874impl Datagram {
875    /// Whether this datagram states an Object Status instead of carrying a
876    /// payload.
877    pub fn is_status(&self) -> bool {
878        matches!(self, Self::Status(_))
879    }
880
881    /// The type field this value writes.
882    ///
883    /// The extensions bit is taken from the extension bytes themselves rather
884    /// than from the declared length beside them, which is what keeps the type
885    /// and the body from contradicting each other: a datagram whose type
886    /// announces extensions and then declares a length of 0 closes the session
887    /// on receipt, and one that announces none has nowhere to put them.
888    pub fn datagram_type(&self) -> StreamType {
889        match self {
890            Self::Payload(header) => {
891                if header.extensions.is_empty() {
892                    StreamType::Datagram
893                } else {
894                    StreamType::DatagramExt
895                }
896            }
897            Self::Status(header) => {
898                if header.extensions.is_empty() {
899                    StreamType::DatagramStatus
900                } else {
901                    StreamType::DatagramStatusExt
902                }
903            }
904        }
905    }
906
907    /// Decode a datagram from its first byte, type field included.
908    ///
909    /// Errors with [`CodecError::UnknownDatagramType`] when the datagram table
910    /// does not assign the leading type, which this draft answers with a close,
911    /// and with [`CodecError::InvalidField`] for the stream types, which it
912    /// does assign but not to a datagram. `datagram_type_error` draws that
913    /// line.
914    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
915        let raw = VarInt::decode(buf)?.into_inner();
916        let stream_type = StreamType::from_id(raw).ok_or_else(|| datagram_type_error(raw))?;
917        let has_extensions = stream_type.has_extensions();
918        match stream_type {
919            StreamType::Datagram | StreamType::DatagramExt => {
920                Ok(Self::Payload(DatagramHeader::decode_with_extensions(has_extensions, buf)?))
921            }
922            StreamType::DatagramStatus | StreamType::DatagramStatusExt => {
923                Ok(Self::Status(DatagramStatusHeader::decode_with_extensions(has_extensions, buf)?))
924            }
925            _ => Err(datagram_type_error(raw)),
926        }
927    }
928
929    /// Decode one whole datagram: [`Self::decode`], then the payload that runs
930    /// to the end of `buf`, which must hold exactly one transport datagram.
931    ///
932    /// Unlike [`Self::decode`], this refuses an extension block that does not
933    /// parse, as [`crate::object_properties::parse_object_properties`] does,
934    /// and, with [`CodecError::MalformedTrack`], a Prior Group ID Gap larger
935    /// than the datagram's Group ID, through [`PriorGaps::from_extensions`].
936    /// draft-11 Section 9.2 and draft-11 Section 9.3 carry the Group ID and the
937    /// extension headers in one datagram, so this half of the
938    /// draft-11 Section 10.1 rule shows in it.
939    ///
940    /// It also refuses bytes after a status datagram's Object Status, with
941    /// [`CodecError::PayloadNotPermitted`]. draft-11 Section 9.3: "An
942    /// OBJECT_DATAGRAM_STATUS is similar to OBJECT_DATAGRAM except it conveys
943    /// an Object Status and has no payload."
944    pub fn decode_object(buf: &mut impl Buf) -> Result<(Self, Vec<u8>), CodecError> {
945        let datagram = Self::decode(buf)?;
946        let (group_id, extensions) = match &datagram {
947            Self::Payload(header) => (header.group_id.into_inner(), &header.extensions),
948            Self::Status(header) => (header.group_id.into_inner(), &header.extensions),
949        };
950        PriorGaps::from_extensions(extensions, group_id)?;
951        let payload = read_bytes(buf, buf.remaining())?;
952        if let Self::Status(header) = &datagram {
953            if !payload.is_empty() {
954                return Err(CodecError::PayloadNotPermitted {
955                    status: header.object_status.as_u64(),
956                    len: payload.len(),
957                    detail: "its type states a status in place of a payload",
958                });
959            }
960        }
961        Ok((datagram, payload))
962    }
963
964    /// Encode the datagram, type field included.
965    pub fn encode(&self, buf: &mut impl BufMut) {
966        let stream_type = self.datagram_type();
967        let has_extensions = stream_type.has_extensions();
968        VarInt::from_usize(stream_type as usize).encode(buf);
969        match self {
970            Self::Payload(header) => header.encode_with_extensions(has_extensions, buf),
971            Self::Status(header) => header.encode_with_extensions(has_extensions, buf),
972        }
973    }
974
975    /// Encode the datagram, refusing a header the framing it names cannot
976    /// carry.
977    ///
978    /// The body is built before anything reaches `buf`, so a refused datagram
979    /// leaves `buf` untouched rather than a type field with no body under it.
980    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
981        let mut body = Vec::with_capacity(64);
982        let stream_type = self.datagram_type();
983        let has_extensions = stream_type.has_extensions();
984        match self {
985            Self::Payload(header) => header.encode_with_extensions(has_extensions, &mut body),
986            Self::Status(header) => {
987                check_extensions_against_status(header.object_status, &header.extensions)?;
988                header.encode_with_extensions(has_extensions, &mut body);
989            }
990        }
991        VarInt::from_usize(stream_type as usize).encode(buf);
992        buf.put_slice(&body);
993        Ok(())
994    }
995}
996
997// ============================================================
998// Fetch stream (type 0x05)
999// ============================================================
1000
1001/// Fetch stream header (draft-11, type 0x05).
1002///
1003/// Contains only the request_id. Objects follow inline.
1004#[derive(Debug, Clone, PartialEq, Eq)]
1005pub struct FetchHeader {
1006    /// Request ID this fetch responds to.
1007    pub request_id: VarInt,
1008}
1009
1010/// Object within a fetch stream (draft-11).
1011#[derive(Debug, Clone, PartialEq, Eq)]
1012pub struct FetchObjectHeader {
1013    /// Group identifier.
1014    pub group_id: VarInt,
1015    /// Subgroup identifier within the group.
1016    pub subgroup_id: VarInt,
1017    /// Object identifier within the subgroup.
1018    pub object_id: VarInt,
1019    /// Publisher priority for delivery ordering.
1020    pub publisher_priority: u8,
1021    /// Total byte length of extension headers, as it arrived.
1022    ///
1023    /// Advisory on encode. Every encoder here writes
1024    /// `VarInt::from_usize(self.extensions.len())` and never reads this field,
1025    /// so a hand-built header whose stated length disagrees with the bytes
1026    /// beside it goes on the wire with the derived length and is read back
1027    /// consistent. Decoding always sets the two together, so a value that came
1028    /// off the wire never disagrees.
1029    ///
1030    /// It is kept because it is what the peer stated, which is not always
1031    /// recoverable from the bytes: a non-minimal varint length encodes the same
1032    /// number in more bytes, and a relay that must forward the block unchanged
1033    /// has to know which it saw.
1034    pub extension_headers_length: VarInt,
1035    /// Raw extension bytes.
1036    pub extensions: Vec<u8>,
1037    /// Length of the object payload in bytes.
1038    pub payload_length: VarInt,
1039    /// Object status (Normal unless payload_length == 0).
1040    pub object_status: ObjectStatus,
1041}
1042
1043impl FetchHeader {
1044    /// Encode a fetch stream header including its leading stream-type field,
1045    /// so the bytes form the start of a data stream a peer can read.
1046    ///
1047    /// [`Self::encode`] writes the body alone, which is what a caller wants
1048    /// once the stream is already open and what a caller must not use for its
1049    /// first write. The read side has had [`Self::decode_stream`] all along,
1050    /// so without this the codec could not round-trip its own fetch stream
1051    /// through its own reader.
1052    pub fn encode_stream(&self, buf: &mut impl BufMut) {
1053        VarInt::from_usize(StreamType::Fetch as usize).encode(buf);
1054        self.encode(buf);
1055    }
1056
1057    /// Encode the fetch header.
1058    pub fn encode(&self, buf: &mut impl BufMut) {
1059        self.request_id.encode(buf);
1060    }
1061
1062    /// Decode a fetch header.
1063    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1064        let request_id = VarInt::decode(buf)?;
1065        Ok(Self { request_id })
1066    }
1067
1068    /// Decode a fetch header from the start of a data stream, consuming the
1069    /// leading stream type varint.
1070    ///
1071    /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
1072    /// not assign the leading type, which this draft answers with a close, and
1073    /// with [`CodecError::InvalidField`] when it is assigned but is not
1074    /// [`StreamType::Fetch`]. `stream_type_error` draws that line.
1075    pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
1076        let stream_type = VarInt::decode(buf)?.into_inner();
1077        if stream_type != StreamType::Fetch as u64 {
1078            return Err(stream_type_error(stream_type));
1079        }
1080        Self::decode(buf)
1081    }
1082}
1083
1084impl FetchObjectHeader {
1085    /// Encode the fetch object header.
1086    pub fn encode(&self, buf: &mut impl BufMut) {
1087        self.group_id.encode(buf);
1088        self.subgroup_id.encode(buf);
1089        self.object_id.encode(buf);
1090        buf.put_u8(self.publisher_priority);
1091        VarInt::from_usize(self.extensions.len()).encode(buf);
1092        buf.put_slice(&self.extensions);
1093        self.payload_length.encode(buf);
1094        if self.payload_length.into_inner() == 0 {
1095            VarInt::from_usize(self.object_status as usize).encode(buf);
1096        }
1097    }
1098
1099    /// Encode the header, refusing a status the framing cannot carry.
1100    ///
1101    /// Section 9.4.4 puts the Object Status field on the wire only when the
1102    /// Object Payload Length is zero, and Section 9.1.1.1 says "Any object
1103    /// with a status code other than zero MUST have an empty payload". A
1104    /// non-zero status paired with a non-zero payload length therefore has no
1105    /// encoding at all: [`Self::encode`] drops the status and the peer reads an
1106    /// ordinary object, which is a different object from the one the caller
1107    /// described. This refuses instead.
1108    ///
1109    /// The datagram types on this draft already refuse the same pairing. These
1110    /// two did not, and they are the ones a publisher writes on every stream.
1111    ///
1112    /// # Errors
1113    ///
1114    /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
1115    /// a non-zero Object Payload Length.
1116    pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1117        if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
1118            return Err(CodecError::InvalidField);
1119        }
1120        self.encode(buf);
1121        Ok(())
1122    }
1123
1124    /// Decode a fetch object header.
1125    pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1126        let group_id = VarInt::decode(buf)?;
1127        let subgroup_id = VarInt::decode(buf)?;
1128        let object_id = VarInt::decode(buf)?;
1129        if buf.remaining() < 1 {
1130            return Err(CodecError::UnexpectedEnd);
1131        }
1132        let publisher_priority = buf.get_u8();
1133        let extension_headers_length = VarInt::decode(buf)?;
1134        let extensions = read_extension_bytes(buf, extension_headers_length.into_inner())?;
1135        let payload_length = VarInt::decode(buf)?;
1136        let object_status = if payload_length.into_inner() == 0 {
1137            let sv = VarInt::decode(buf)?.into_inner();
1138            ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?
1139        } else {
1140            ObjectStatus::Normal
1141        };
1142        check_extensions_against_status(object_status, &extensions)?;
1143        Ok(Self {
1144            group_id,
1145            subgroup_id,
1146            object_id,
1147            publisher_priority,
1148            extension_headers_length,
1149            extensions,
1150            payload_length,
1151            object_status,
1152        })
1153    }
1154}
1155
1156#[cfg(test)]
1157mod prior_gap_tests {
1158    use super::*;
1159
1160    fn header(group: usize) -> SubgroupHeader {
1161        SubgroupHeader {
1162            stream_type: StreamType::SubgroupZeroExt,
1163            track_alias: VarInt::from_usize(1),
1164            group_id: VarInt::from_usize(group),
1165            subgroup_id: VarInt::from_usize(0),
1166            publisher_priority: 0x80,
1167        }
1168    }
1169
1170    fn gaps_block(gaps: &[usize]) -> Vec<u8> {
1171        let mut block = Vec::new();
1172        for &gap in gaps {
1173            VarInt::from_usize(PRIOR_GROUP_ID_GAP as usize).encode(&mut block);
1174            VarInt::from_usize(gap).encode(&mut block);
1175        }
1176        block
1177    }
1178
1179    fn object(id: usize, gaps: &[usize]) -> ObjectHeader {
1180        let extensions = gaps_block(gaps);
1181        ObjectHeader {
1182            object_id: VarInt::from_usize(id),
1183            extension_headers_length: VarInt::from_usize(extensions.len()),
1184            extensions,
1185            payload_length: VarInt::from_usize(1),
1186            object_status: ObjectStatus::Normal,
1187        }
1188    }
1189
1190    fn is_malformed_track<T>(result: Result<T, CodecError>) -> bool {
1191        matches!(result, Err(CodecError::MalformedTrack { .. }))
1192    }
1193
1194    #[test]
1195    fn gap_larger_than_the_group_id_is_refused() {
1196        assert!(is_malformed_track(SubgroupGaps::new(&header(1)).check(&object(0, &[2]))));
1197        let gaps = SubgroupGaps::new(&header(2)).check(&object(0, &[2])).unwrap();
1198        assert_eq!(gaps.group, vec![2]);
1199    }
1200
1201    #[test]
1202    fn objects_with_different_gaps_are_refused() {
1203        let mut gaps = SubgroupGaps::new(&header(5));
1204        gaps.check(&object(0, &[2])).unwrap();
1205        gaps.check(&object(1, &[])).unwrap();
1206        gaps.check(&object(2, &[2])).unwrap();
1207        assert!(is_malformed_track(gaps.check(&object(3, &[3]))));
1208    }
1209
1210    #[test]
1211    fn values_within_one_object_are_not_compared() {
1212        let mut gaps = SubgroupGaps::new(&header(5));
1213        assert_eq!(gaps.check(&object(0, &[2, 3])).unwrap().group, vec![2, 3]);
1214        assert_eq!(gaps.check(&object(1, &[3, 2, 3])).unwrap().group, vec![3, 2, 3]);
1215    }
1216
1217    #[test]
1218    fn objects_with_a_common_value_are_accepted() {
1219        let mut gaps = SubgroupGaps::new(&header(5));
1220        gaps.check(&object(0, &[2, 3])).unwrap();
1221        gaps.check(&object(1, &[3, 4])).unwrap();
1222        gaps.check(&object(2, &[])).unwrap();
1223        gaps.check(&object(3, &[3])).unwrap();
1224    }
1225
1226    #[test]
1227    fn objects_that_share_no_value_are_refused() {
1228        let mut gaps = SubgroupGaps::new(&header(5));
1229        gaps.check(&object(0, &[2, 3])).unwrap();
1230        assert!(is_malformed_track(gaps.check(&object(1, &[4, 5]))));
1231        gaps.check(&object(2, &[3, 4])).unwrap();
1232        assert!(is_malformed_track(gaps.check(&object(3, &[4]))));
1233    }
1234
1235    #[test]
1236    fn objects_with_no_value_common_to_all_are_refused() {
1237        let mut gaps = SubgroupGaps::new(&header(5));
1238        gaps.check(&object(0, &[1, 2])).unwrap();
1239        gaps.check(&object(1, &[2, 3])).unwrap();
1240        assert!(is_malformed_track(gaps.check(&object(2, &[1, 3]))));
1241    }
1242
1243    #[test]
1244    fn a_gap_larger_than_the_group_id_under_every_reading_is_refused() {
1245        assert!(is_malformed_track(SubgroupGaps::new(&header(5)).check(&object(0, &[7, 6]))));
1246        SubgroupGaps::new(&header(5)).check(&object(0, &[2, 6])).unwrap();
1247    }
1248
1249    #[test]
1250    fn a_refused_object_is_not_recorded() {
1251        let mut gaps = SubgroupGaps::new(&header(5));
1252        assert!(is_malformed_track(gaps.check(&object(0, &[6]))));
1253        gaps.check(&object(1, &[3])).unwrap();
1254    }
1255
1256    fn datagram(group: usize, gaps: &[usize]) -> Vec<u8> {
1257        let extensions = gaps_block(gaps);
1258        let mut bytes = Vec::new();
1259        Datagram::Payload(DatagramHeader {
1260            track_alias: VarInt::from_usize(1),
1261            group_id: VarInt::from_usize(group),
1262            object_id: VarInt::from_usize(0),
1263            publisher_priority: 0x80,
1264            extension_headers_length: VarInt::from_usize(extensions.len()),
1265            extensions,
1266        })
1267        .encode(&mut bytes);
1268        bytes.push(0xAA);
1269        bytes
1270    }
1271
1272    #[test]
1273    fn decode_object_refuses_a_gap_larger_than_the_datagram_group_id() {
1274        let wire = datagram(1, &[2]);
1275        assert!(is_malformed_track(Datagram::decode_object(&mut &wire[..])));
1276        assert!(Datagram::decode(&mut &wire[..]).is_ok());
1277        let wire = datagram(2, &[2]);
1278        let (decoded, payload) = Datagram::decode_object(&mut &wire[..]).unwrap();
1279        assert_eq!(decoded.datagram_type(), StreamType::DatagramExt);
1280        assert_eq!(payload, vec![0xAA]);
1281    }
1282
1283    #[test]
1284    fn decode_object_refuses_a_payload_after_a_status_datagram() {
1285        let mut wire = Vec::new();
1286        Datagram::Status(DatagramStatusHeader {
1287            track_alias: VarInt::from_usize(1),
1288            group_id: VarInt::from_usize(0),
1289            object_id: VarInt::from_usize(0),
1290            publisher_priority: 0x80,
1291            extension_headers_length: VarInt::from_usize(0),
1292            extensions: Vec::new(),
1293            object_status: ObjectStatus::Normal,
1294        })
1295        .encode(&mut wire);
1296        let (_, payload) = Datagram::decode_object(&mut &wire[..]).unwrap();
1297        assert!(payload.is_empty());
1298        wire.push(0xAA);
1299        assert!(matches!(
1300            Datagram::decode_object(&mut &wire[..]),
1301            Err(CodecError::PayloadNotPermitted { len: 1, .. })
1302        ));
1303    }
1304}