moqtap_codec/draft21/data_stream.rs
1//! Draft-21 data stream header encoding and decoding.
2//!
3//! Byte-for-byte identical to draft-19. The field list, the field order, the
4//! widths and the set of valid Type values are all unchanged; what draft-20
5//! changed is the receive path.
6//!
7//! # `Type` became `Type Flags`, and the rules left the figure
8//!
9//! Draft-19 enumerated the legal leading values inside the figure —
10//! `Type (vi64) = 0x10..0x15 / 0x18..0x1D / …` for a subgroup header — and
11//! draft-21 declares the field a set of flags and states the invalid
12//! combinations as prose after it. Computing draft-21's rules gives back
13//! draft-19's enumeration byte for byte, on both carriers, so no valid frame
14//! changed hands. A decoder that validated by range membership was already
15//! right; one that validated loosely, by masking off bits it did not know,
16//! becomes wrong.
17//!
18//! **The two carriers do not share a rule set, and neither can be derived from
19//! the other.** Section 11.3.1 gives SUBGROUP_HEADER three conditions and
20//! Section 11.2.1 gives OBJECT_DATAGRAM three of its own, and they disagree at
21//! two points:
22//!
23//! * bit 4 (`0x10`) **MUST be 1** on a subgroup header and **MUST be 0** on a
24//! datagram, where it is reserved — the same bit, opposite requirements, and
25//! it is what tells the two carriers apart in the stream-type registry;
26//! * the datagram has an "a bit set whose meaning is not specified" condition
27//! and the subgroup header has none, because bits 0 through 6 are all
28//! specified for a subgroup. The subgroup gets an explicit "values of 128 or
29//! greater" condition instead, which the datagram does not have — there the
30//! unspecified-bit rule catches the same values, since 128 sets bit 7.
31//!
32//! # Non-minimal Type Flags are accepted on receive and never emitted
33//!
34//! Section 8.1 permits a value to be encoded in more bytes than it needs, and
35//! Section 11.3.1 words its third condition as "values of 128 or greater (i.e.,
36//! any value that requires more than a one-byte variable-length integer
37//! encoding)". Those two clauses are not equivalent under that allowance: the
38//! value `0x14` written as the two-byte `0x8014` is a legal varint whose value
39//! is below 128 and whose encoding is two bytes. **This codec reads the rule as
40//! a bound on the value**, so it decodes such a header, and it always emits the
41//! minimal one-byte form. The draft does not settle which clause governs;
42//! rejecting a legal-but-non-minimal encoding risks failing a conformant peer
43//! and emitting one risks tripping a stricter peer, so the asymmetry is the
44//! safe default. The draft-19 decoder refuses every wide spelling outright;
45//! this one reads the whole field.
46//!
47//! # The payload rule
48//!
49//! Unchanged from draft-19, and worth restating because it is the one place
50//! where the encoder refuses an object draft-18 would have reinterpreted.
51//! Draft-18 said every Object with a status other than Normal has an empty
52//! payload; draft-21 Section 11.1.2 says instead that an Object has an empty
53//! payload unless its status is registered as permitting one, and
54//! Section 16.9 puts that permission in the Object Status registry. Both
55//! encodings here keep their draft-18 framing — a subgroup object carries a
56//! status exactly when its Object Payload Length is zero, and a datagram
57//! carries one exactly when its type sets the STATUS bit, both stated that way
58//! by the draft — so no frame these decoders can read is able to state a
59//! status and a payload at once. The registry rule therefore bites where a
60//! caller can hold both: `SubgroupObjectReader::write_object` consults the
61//! status's payload permission and refuses an object whose status forbids the
62//! payload handed with it, rather than dropping the status and writing the
63//! bytes as a Normal object. On the way back out, `SubgroupObject` and
64//! `DatagramHeader` answer the same question from the registry, so a reader
65//! never has to recover it from a length.
66//!
67//! Subgroup Type Flags: form 0b0XX1XXXX, so bit 4 is set and bit 7 is
68//! clear; the ranges are 0x10..0x1F, 0x30..0x3F, 0x50..0x5F, 0x70..0x7F.
69//! - bit 0 (0x01): PROPERTIES
70//! - bits 1-2 (0x06): SUBGROUP_ID_MODE (0=zero, 1=first_obj, 2=explicit, 3=reserved)
71//! - bit 3 (0x08): END_OF_GROUP
72//! - bit 5 (0x20): DEFAULT_PRIORITY (no priority byte)
73//! - bit 6 (0x40): FIRST_OBJECT
74//!
75//! Datagram Type Flags: 0b00X0XXXX, so bits 4, 6 and 7 are clear; the ranges
76//! are 0x00..0x0F and 0x20..0x2F.
77//! - bit 0 (0x01): PROPERTIES
78//! - bit 1 (0x02): END_OF_GROUP
79//! - bit 2 (0x04): ZERO_OBJECT_ID (object_id=0, field omitted)
80//! - bit 3 (0x08): DEFAULT_PRIORITY (no priority byte)
81//! - bit 5 (0x20): STATUS (status byte replaces payload)
82//!
83//! Neither range is fully assigned, and draft-21 spells out which values in
84//! them an endpoint must refuse rather than decode, closing the session with a
85//! PROTOCOL_VIOLATION. Section 11.3.1 excludes the subgroup Types whose
86//! SUBGROUP_ID_MODE is the reserved 0b11 — 0x16, 0x17, 0x1E, 0x1F and the same
87//! four offsets in each higher range — because that mode does not say whether
88//! a Subgroup ID field follows the Group ID, so a decoder would have to guess
89//! and a wrong guess shifts every later field by the width of that varint.
90//! Section 11.2.1 excludes the datagram Types setting both STATUS (0x20) and
91//! END_OF_GROUP (0x02) — 0x22, 0x23, 0x26, 0x27, 0x2A, 0x2B, 0x2E and 0x2F —
92//! because an object status message cannot signal end of group.
93//! [`SubgroupHeader::decode`] and [`DatagramHeader::decode`] refuse both
94//! lists, and [`SubgroupHeader::encode_checked`] and
95//! [`DatagramHeader::encode_checked`] refuse to write them.
96//!
97//! Fetch header: stream type 0x05 + request_id, then per-object records whose
98//! leading Serialization Flags varint says which of the object's fields are on
99//! the wire at all; [`FetchObjectHeader`] decodes and encodes one such record.
100//! Draft-20 adds a third End of Range marker to Table 7,
101//! [`FetchEndOfRange::TimedOut`] (`0x20C`), for the Objects a relay abandoned
102//! when its `FILL_TIMEOUT` budget ran out — what draft-19 reported as an
103//! Unknown range.
104
105use bytes::{Buf, BufMut};
106
107use super::types::{ObjectStatus, PayloadPermission};
108use crate::error::CodecError;
109use crate::object_properties::{ObjectProperty, PropertyValue};
110use crate::varint::{Moqt18 as Wire, VarInt};
111
112/// Advance `buf` past `len` bytes without copying them.
113fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
114 let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
115 if buf.remaining() < len {
116 return Err(CodecError::UnexpectedEnd);
117 }
118 buf.advance(len);
119 Ok(())
120}
121
122/// Turn a wire Object Status code into an [`ObjectStatus`], refusing one
123/// draft-21 does not assign.
124///
125/// Draft-21 Section 11.1.2 lists the codes an object may carry — the three
126/// rows of the Object Status registry it establishes in Section 16.9 — and
127/// says any other value SHOULD be treated as a protocol error and the session
128/// closed with a PROTOCOL_VIOLATION. Every place this module reads a status
129/// converts it here, so a decoded [`SubgroupObject::object_status`] or
130/// [`DatagramHeader::object_status`] is always a status the draft assigns, and
131/// [`SubgroupObjectMeta::status`] — which stays a raw code because a relay may
132/// carry it to a draft that numbers the set differently — holds one because it
133/// comes from the same conversion.
134fn decoded_status(code: u64) -> Result<ObjectStatus, CodecError> {
135 ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)
136}
137
138// ── Stream and datagram types ─────────────────────────────────
139
140/// Unidirectional stream type for padding, draft-21 Section 11.5.1: "An
141/// endpoint MAY open a unidirectional stream with a stream type of 0x132B3E28
142/// to send padding data. The stream begins with the stream type, followed by
143/// zero or more bytes that MUST all be set to zero."
144///
145/// Named here because the value is what tells a padding stream from a data
146/// stream, and nothing else in this module would otherwise say so. Under the
147/// draft-21 variable-length integer encoding the value takes five bytes, the
148/// first of which is 0xF0 — an octet with bit 7 set, which the subgroup form
149/// leaves clear. A reader judging the Type by that first byte alone would find
150/// the form broken and call the stream unknown, which Section 6.4.1 answers by
151/// ending the session. Table 2 assigns the value, so that would be a close over
152/// traffic this draft permits.
153pub const PADDING_STREAM_TYPE: u64 = 0x132B_3E28;
154
155/// Datagram type for padding, draft-21 Section 11.5.2: "An endpoint MAY send a
156/// datagram with a type of 0x132B3E29 to send padding data. The datagram
157/// contains the type followed by zero or more bytes that MUST all be set to
158/// zero."
159///
160/// One more than [`PADDING_STREAM_TYPE`] and encoded the same width, and
161/// assigned the same way, so the same reasoning applies to a datagram reader.
162pub const PADDING_DATAGRAM_TYPE: u64 = 0x132B_3E29;
163
164/// The unidirectional stream Type draft-21 Section 9.1 gives the control
165/// stream.
166const SETUP_STREAM_TYPE: u64 = 0x2F00;
167
168// ── Subgroup ──────────────────────────────────────────────────
169
170const SUBGROUP_PROPERTIES_BIT: u8 = 0x01;
171const SUBGROUP_ID_MODE_MASK: u8 = 0x06;
172const SUBGROUP_END_OF_GROUP_BIT: u8 = 0x08;
173const SUBGROUP_BASE_BIT: u8 = 0x10;
174const SUBGROUP_DEFAULT_PRIORITY_BIT: u8 = 0x20;
175const SUBGROUP_FIRST_OBJECT_BIT: u8 = 0x40;
176/// The SUBGROUP_ID_MODE value draft-21 reserves, once the mask is applied and
177/// the field shifted down.
178const SUBGROUP_ID_MODE_RESERVED: u8 = 0b11;
179
180/// Refuse a subgroup header `Type Flags` value draft-21 Section 11.3.1 lists
181/// as invalid.
182///
183/// The section gives **three** conditions and says of all of them that an
184/// endpoint receiving a stream header with such a value MUST close the session
185/// with a PROTOCOL_VIOLATION:
186///
187/// 1. "Values with SUBGROUP_ID_MODE set to 0b11. This mode is reserved for
188/// future use." That is 0x16, 0x17, 0x1E, 0x1F and the same four offsets
189/// in each higher range.
190/// 2. "Values where bit 4 is not set. Bit 4 MUST be 1 for SUBGROUP_HEADER."
191/// 3. "Values of 128 or greater (i.e., any value that requires more than a
192/// one-byte variable-length integer encoding)."
193///
194/// Computing the three gives 0x10-0x15, 0x18-0x1D, 0x30-0x35, 0x38-0x3D,
195/// 0x50-0x55, 0x58-0x5D, 0x70-0x75 and 0x78-0x7D — byte for byte the
196/// enumeration draft-19 wrote into its figure.
197///
198/// **These are not the datagram's rules.** Section 11.2.1 reserves bit 4 and
199/// requires it to be *zero*, and adds an unspecified-bit condition that this
200/// list does not have, because bits 0 through 6 are all specified here. The two
201/// sets are read from their own sections and neither is derived from the other.
202///
203/// The reserved mode is worth separating from a mere unassigned code point,
204/// because it is not decodable rather than merely unknown. The other three
205/// modes each say whether a Subgroup ID field follows the Group ID; 0b11 says
206/// nothing, so a decoder has to guess, and a wrong guess shifts every
207/// subsequent field by the width of that varint.
208fn validate_subgroup_type(raw: u64) -> Result<(), CodecError> {
209 if subgroup_type_is_valid(raw) {
210 Ok(())
211 } else {
212 Err(stream_type_error(raw))
213 }
214}
215
216/// Whether `raw` is a subgroup `Type Flags` value draft-21 admits: below 128,
217/// bit 4 set, and not the reserved SUBGROUP_ID_MODE.
218fn subgroup_type_is_valid(raw: u64) -> bool {
219 // `raw < 0x80` is Section 11.3.1's third condition, and it subsumes the
220 // form's bit-7-clear requirement.
221 raw < 0x80 && {
222 let t = raw as u8;
223 t & SUBGROUP_BASE_BIT != 0 && (t & SUBGROUP_ID_MODE_MASK) >> 1 != SUBGROUP_ID_MODE_RESERVED
224 }
225}
226
227/// Which failure a leading unidirectional stream Type that is not the one a
228/// reader wants is.
229///
230/// Draft-21 states two rules about such a Type and answers both with a close,
231/// and telling them apart is the whole job of this function.
232///
233/// Section 6.4.1 is about the table: "An endpoint that receives an unknown stream
234/// type MUST close the session." A Type Table 2 does not assign is
235/// [`CodecError::UnknownStreamType`].
236///
237/// Section 11.3.1 is about the subgroup form specifically, and gives three
238/// conditions on a `Type Flags` value *inside* it. Those values are not unknown
239/// — the registry pattern 0b0XX1XXXX assigns the space and the draft rules
240/// particular values out within it — but they are unreadable, and they are
241/// [`CodecError::InvalidStreamTypeValue`], one arm per condition so a log names which.
242///
243/// Table 2 assigns four things, and two of them carry no Objects at all:
244/// FETCH_HEADER, the subgroup form, SETUP and PADDING. A subgroup reader handed
245/// any of them refuses it as [`CodecError::InvalidField`] — the value is one
246/// this draft defines, the disagreement is with the reader that was called, and
247/// the session survives it. See [`PADDING_STREAM_TYPE`] for why that case in
248/// particular is worth the trouble.
249///
250/// # Where the boundary between the two rules sits
251///
252/// Section 11.3.1's conditions are about a one-byte flags field, so they are
253/// applied to values that fit one — `raw <= 0xFF`. Above that the value is out
254/// of the space the section describes and the question is Table 2's instead:
255/// an assigned non-subgroup stream type is refused as the wrong reader, and
256/// anything else is unknown. That split is what keeps a datagram's
257/// `Type Flags` of 0x20 reported as an invalid subgroup value — which is what
258/// Section 11.3.1's bit-4 condition calls it — while a stray 0x5000 stays an
259/// unknown stream type.
260fn stream_type_error(raw: u64) -> CodecError {
261 if raw == FETCH_STREAM_TYPE
262 || raw == SETUP_STREAM_TYPE
263 || raw == PADDING_STREAM_TYPE
264 || subgroup_type_is_valid(raw)
265 {
266 return CodecError::InvalidField;
267 }
268 if raw > 0xFF {
269 return CodecError::UnknownStreamType(raw);
270 }
271 let t = raw as u8;
272 // Third condition first: it is the coarsest, and 128 or more can also set
273 // the other two bits in ways that would misreport it.
274 if raw >= 0x80 {
275 CodecError::InvalidStreamTypeValue {
276 raw,
277 detail: "SUBGROUP_HEADER Type Flags of 128 or greater are invalid",
278 }
279 } else if t & SUBGROUP_BASE_BIT == 0 {
280 CodecError::InvalidStreamTypeValue { raw, detail: "bit 4 must be 1 for SUBGROUP_HEADER" }
281 } else {
282 CodecError::InvalidStreamTypeValue {
283 raw,
284 detail: "its SUBGROUP_ID_MODE is 0b11, which this draft reserves",
285 }
286 }
287}
288
289#[derive(Debug, Clone)]
290pub struct SubgroupHeader {
291 pub header_type: u8,
292 pub track_alias: VarInt,
293 pub group_id: VarInt,
294 pub subgroup_id: VarInt,
295 pub publisher_priority: Option<u8>,
296}
297
298impl SubgroupHeader {
299 /// Decode a subgroup header, `Type Flags` field included.
300 ///
301 /// The field is read as a whole varint of whatever width the sender used,
302 /// and the value it decodes to is what the three Section 11.3.1 conditions
303 /// are applied to. **A non-minimal spelling of a valid value is
304 /// accepted**: `0x8010` decodes to 16, which is a legal `Type Flags`, and
305 /// this reads it. See the module documentation for why the draft leaves
306 /// that open and why permissive-on-receive is the choice here; the encoder
307 /// is the strict half and always writes the one-byte form.
308 ///
309 /// The draft-19 module refuses every wide spelling before looking at the
310 /// value; this one reads the whole field, which is also what
311 /// lets `stream_type_error` tell a padding or SETUP stream — both assigned,
312 /// both several bytes wide — from a Type Table 2 does not assign.
313 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
314 let raw = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
315 validate_subgroup_type(raw)?;
316 // Narrowing is safe: `validate_subgroup_type` has already refused
317 // everything at 128 or above.
318 let header_type = raw as u8;
319
320 let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
321 let group_id = VarInt::decode_moqt::<Wire>(buf)?;
322
323 let subgroup_id_mode = (header_type & SUBGROUP_ID_MODE_MASK) >> 1;
324 let subgroup_id = match subgroup_id_mode {
325 0 => VarInt::from_u64_moqt(0),
326 2 => VarInt::decode_moqt::<Wire>(buf)?,
327 // Mode 1 puts no Subgroup ID on the wire either: it is the first
328 // object's ID, which this header cannot see. Store 0 until a
329 // caller resolves it. Mode 3 never reaches here — the type check
330 // above refuses it.
331 _ => VarInt::from_u64_moqt(0),
332 };
333
334 let publisher_priority = if header_type & SUBGROUP_DEFAULT_PRIORITY_BIT == 0 {
335 if buf.remaining() < 1 {
336 return Err(CodecError::UnexpectedEnd);
337 }
338 Some(buf.get_u8())
339 } else {
340 None
341 };
342
343 Ok(SubgroupHeader { header_type, track_alias, group_id, subgroup_id, publisher_priority })
344 }
345
346 /// Serialize the header exactly as its Type byte describes it.
347 ///
348 /// Infallible, and so willing to write a Type draft-21 Section 11.3.1
349 /// tells an endpoint to reject — including a reserved-mode Type this
350 /// module's own [`Self::decode`] refuses to read back. Prefer
351 /// [`Self::encode_checked`], which refuses those Types instead.
352 pub fn encode(&self, buf: &mut impl BufMut) {
353 buf.put_u8(self.header_type);
354 self.track_alias.encode_moqt::<Wire>(buf);
355 self.group_id.encode_moqt::<Wire>(buf);
356
357 let subgroup_id_mode = (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1;
358 if subgroup_id_mode == 2 {
359 self.subgroup_id.encode_moqt::<Wire>(buf);
360 }
361
362 if self.header_type & SUBGROUP_DEFAULT_PRIORITY_BIT == 0 {
363 buf.put_u8(self.publisher_priority.unwrap_or(128));
364 }
365 }
366
367 /// Serialize the header, refusing a Type value draft-21 forbids.
368 ///
369 /// Errors with [`CodecError::InvalidField`] for exactly the Types draft-21
370 /// Section 11.3.1 lists as invalid (the same set [`Self::decode`] refuses),
371 /// before any byte is written, so a refused header leaves `buf` untouched.
372 /// Everything else is written by [`Self::encode`].
373 ///
374 /// The check belongs on the encode side as well as the decode side because
375 /// the two halves would otherwise disagree about which streams exist: a
376 /// reserved-mode header written by [`Self::encode`] cannot be read back by
377 /// [`Self::decode`], and a codec used to rewrite captured traffic would
378 /// emit a stream it could not then parse.
379 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
380 validate_subgroup_type(self.header_type as u64)?;
381 self.encode(buf);
382 Ok(())
383 }
384
385 pub fn has_properties(&self) -> bool {
386 self.header_type & SUBGROUP_PROPERTIES_BIT != 0
387 }
388
389 /// The subgroup-ID mode: `(header_type & 0x06) >> 1`.
390 ///
391 /// `0` = no subgroup ID on the wire and it is zero; `1` = the subgroup ID
392 /// is the first object's ID; `2` = an explicit ID follows the Group ID;
393 /// `3` = reserved. Exposed because the mask is module-private and
394 /// `dispatch::AnySubgroupHeader::subgroup_id_mode` cannot read it.
395 ///
396 /// `3` never comes back from [`Self::decode`], which refuses the Types
397 /// carrying it. `header_type` is a public field, so a hand-built header
398 /// can still report it; [`Self::encode_checked`] is what refuses to put
399 /// one on the wire.
400 pub fn subgroup_id_mode(&self) -> u8 {
401 (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1
402 }
403
404 pub fn is_end_of_group(&self) -> bool {
405 self.header_type & SUBGROUP_END_OF_GROUP_BIT != 0
406 }
407
408 /// `true` when the FIRST_OBJECT bit (0x40) is set, signaling the first
409 /// object on this stream is the original publisher's first object in the
410 /// subgroup. Added in draft-18.
411 pub fn is_first_object(&self) -> bool {
412 self.header_type & SUBGROUP_FIRST_OBJECT_BIT != 0
413 }
414}
415
416// ── Subgroup objects (stateful) ───────────────────────────────
417
418/// One object within a draft-21 subgroup stream. Object IDs are
419/// delta-encoded; whether a per-object "properties" block (the draft-21
420/// equivalent of extension headers) is present depends on the PROPERTIES
421/// bit on the enclosing [`SubgroupHeader`]. Use [`SubgroupObjectReader`]
422/// to encode/decode.
423#[derive(Debug, Clone)]
424pub struct SubgroupObject {
425 pub object_id: VarInt,
426 /// Raw properties bytes, excluding the byte-length prefix that precedes
427 /// them on the wire. Empty unless the subgroup header sets the
428 /// PROPERTIES bit, or when the block is present but zero-length.
429 /// Opaque: [`SubgroupObjectReader::write_object`] re-emits the prefix
430 /// and these bytes verbatim.
431 pub extension_headers: Vec<u8>,
432 pub payload_length: VarInt,
433 /// The object's status, carried on the wire only when `payload_length` is
434 /// zero. `None` with a zero `payload_length` is written as
435 /// [`ObjectStatus::Normal`].
436 ///
437 /// `None` is not "no status": every Object has one. It means the status is
438 /// the one the encoding elides — [`ObjectStatus::Normal`], the only row of
439 /// the Object Status registry (draft-21 Section 16.9) that permits the
440 /// payload such an object carries. Decoding a payload-bearing object leaves
441 /// this `None` for that reason; [`Self::status`] resolves it either way.
442 ///
443 /// Typed rather than a raw code. The wire field is a varint with room for
444 /// any value, and draft-21 assigns three of them; the decoder refuses the
445 /// rest, and this type is that same refusal on the encode side — 0x1 and
446 /// 0x2 cannot be named here, so [`SubgroupObjectReader::write_object`]
447 /// cannot emit a status this module's own decoder would reject.
448 ///
449 /// A status and a payload can be held here together, which the wire has no
450 /// way to express. That combination is what draft-21's registry rules on:
451 /// [`SubgroupObjectReader::write_object`] accepts it when the status is
452 /// registered as permitting a payload and refuses it otherwise.
453 pub object_status: Option<ObjectStatus>,
454 pub payload: Vec<u8>,
455}
456
457impl SubgroupObject {
458 /// The object's status, with the one draft-21's encoding elides filled in.
459 ///
460 /// A subgroup object states its status only when its Object Payload Length
461 /// is zero. An object that carries bytes therefore has no status field, and
462 /// its status is [`ObjectStatus::Normal`] — the sole row of the Object
463 /// Status registry (draft-21 Section 16.9) permitting a payload, so the
464 /// only status such an object could have had.
465 pub fn status(&self) -> ObjectStatus {
466 self.object_status.unwrap_or(ObjectStatus::Normal)
467 }
468
469 /// Whether the Object Status registry permits this object a non-empty
470 /// payload, per draft-21 Section 16.9.
471 ///
472 /// Answered from the status alone. `payload` and `payload_length` are not
473 /// consulted: on a status that permits a payload they say only whether this
474 /// particular object took the offer, and on one that forbids a payload a
475 /// non-empty payload is the malformation this reports, not evidence about
476 /// the rule.
477 pub fn permits_payload(&self) -> bool {
478 self.status().permits_payload()
479 }
480
481 /// Whether this object's status is allowed to carry the properties it has.
482 ///
483 /// Draft-21 Section 11.1.3: "Any Object with status Normal can have
484 /// properties (Section 8.4). If an endpoint receives properties on an
485 /// Object with status that is not Normal, it MUST close the session with a
486 /// PROTOCOL_VIOLATION."
487 ///
488 /// So this is `false` for exactly one shape: a non-empty properties block
489 /// on an object whose status is not [`ObjectStatus::Normal`]. An object
490 /// with no properties is fine at any status, and an object at Normal may
491 /// carry any properties.
492 ///
493 /// [`SubgroupObjectReader::read_object`] and
494 /// [`SubgroupObjectReader::write_object`] refuse an object this answers
495 /// `false` for; [`SubgroupObjectReader::read_object_meta`] frames it.
496 pub fn properties_permitted(&self) -> bool {
497 self.extension_headers.is_empty() || self.status() == ObjectStatus::Normal
498 }
499}
500
501/// The framing of one draft-21 subgroup object, without its payload.
502///
503/// Produced by [`SubgroupObjectReader::read_object_meta`] for callers that
504/// forward an object's bytes verbatim and never inspect the payload.
505#[derive(Debug, Clone, Copy, PartialEq, Eq)]
506pub struct SubgroupObjectMeta {
507 /// Resolved absolute Object ID.
508 pub object_id: u64,
509 /// Byte length of the properties block's contents, excluding its length
510 /// prefix.
511 pub extension_headers_len: u64,
512 /// Declared payload length. Zero when `status` is `Some`.
513 pub payload_length: u64,
514 /// Object status wire code, present only when the payload is empty.
515 pub status: Option<u64>,
516 /// Total bytes this object occupies on the wire, prefix fields included.
517 pub wire_len: u64,
518}
519
520impl SubgroupObjectMeta {
521 /// What the Object Status registry says about this object's payload, or
522 /// `None` if the registry has no row for its status.
523 ///
524 /// Draft-21 Section 16.9, Table 16 gives the registry a "Payload" column,
525 /// and Section 11.1.2 makes it the rule: an Object has an empty payload
526 /// unless its status is registered as permitting one. Table 16 fills the
527 /// column in for the three statuses draft-21 assigns — 0x0 Normal is
528 /// "Yes", 0x3 End of Group and 0x4 End of Track are "No" — and requires
529 /// every future registration to fill it in too.
530 ///
531 /// `status` is `None` for an object whose payload length is non-zero,
532 /// because the encoding puts a status field only where the payload does
533 /// not go. Such an object's status is Normal, the one row permitting the
534 /// payload it is carrying, so this answers
535 /// [`PayloadPermission::Permitted`] rather than `None`.
536 ///
537 /// The `None` this does return means something else entirely: a status
538 /// code with no row in the registry, for which the draft supplies no
539 /// answer and this must not invent one. [`SubgroupObjectReader`] never
540 /// produces such a meta — it refuses an unassigned code while decoding —
541 /// but every field here is public, so a caller that assembled a meta by
542 /// hand, or carried a status across from a draft numbering the set
543 /// differently, can hold one.
544 ///
545 /// Answered from the status alone. `payload_length` is not consulted: on a
546 /// status that permits a payload it says only whether this particular
547 /// object took the offer, and on one that forbids a payload a non-zero
548 /// length is the malformation a caller uses this to detect, not evidence
549 /// about the rule.
550 pub fn payload_permission(&self) -> Option<PayloadPermission> {
551 match self.status {
552 None => Some(ObjectStatus::Normal.payload_permission()),
553 Some(code) => ObjectStatus::from_u64(code).map(ObjectStatus::payload_permission),
554 }
555 }
556}
557
558/// Prior Group ID Gap, Object Property Type 0x3C (Section 10.8).
559pub const PRIOR_GROUP_ID_GAP: u64 = 0x3C;
560/// Prior Object ID Gap, Object Property Type 0x3E (Section 10.9).
561pub const PRIOR_OBJECT_ID_GAP: u64 = 0x3E;
562
563/// The Prior Group ID Gap and Prior Object ID Gap one Object carries.
564#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
565pub struct PriorGaps {
566 /// Prior Group ID Gap, if present.
567 pub group: Option<u64>,
568 /// Prior Object ID Gap, if present.
569 pub object: Option<u64>,
570}
571
572impl PriorGaps {
573 /// Read the two gaps from an Object's parsed properties, Immutable
574 /// Properties included, refusing the conditions one Object shows on its
575 /// own.
576 ///
577 /// Section 10.8 makes the Track malformed when "An Object contains more
578 /// than one instance of Prior Group ID Gap" and when "An Object has a Prior
579 /// Group ID Gap larger than the Group ID". Section 10.9 says the same two
580 /// for Prior Object ID Gap against the Object ID. Each is
581 /// [`CodecError::MalformedTrack`].
582 pub fn from_properties(
583 properties: &[ObjectProperty<'_>],
584 group_id: u64,
585 object_id: u64,
586 ) -> Result<Self, CodecError> {
587 let mut gaps = Self::default();
588 collect_gaps(properties, &mut gaps)?;
589 if let Some(gap) = gaps.group.filter(|&gap| gap > group_id) {
590 return Err(CodecError::MalformedTrack {
591 detail: format!("Prior Group ID Gap {gap} is larger than Group ID {group_id}"),
592 });
593 }
594 if let Some(gap) = gaps.object.filter(|&gap| gap > object_id) {
595 return Err(CodecError::MalformedTrack {
596 detail: format!("Prior Object ID Gap {gap} is larger than Object ID {object_id}"),
597 });
598 }
599 Ok(gaps)
600 }
601}
602
603fn collect_gaps(properties: &[ObjectProperty<'_>], gaps: &mut PriorGaps) -> Result<(), CodecError> {
604 for property in properties {
605 let slot = match (&property.value, property.property_type) {
606 (PropertyValue::Immutable { properties, .. }, _) => {
607 collect_gaps(properties, gaps)?;
608 continue;
609 }
610 (PropertyValue::Varint(value), PRIOR_GROUP_ID_GAP) => {
611 (&mut gaps.group, *value, "Group")
612 }
613 (PropertyValue::Varint(value), PRIOR_OBJECT_ID_GAP) => {
614 (&mut gaps.object, *value, "Object")
615 }
616 _ => continue,
617 };
618 let (field, value, which) = slot;
619 if field.replace(value).is_some() {
620 return Err(CodecError::MalformedTrack {
621 detail: format!("an Object contains more than one Prior {which} ID Gap"),
622 });
623 }
624 }
625 Ok(())
626}
627
628/// The Prior Group ID Gap and Prior Object ID Gap conditions of
629/// draft-21 Section 10.8 and draft-21 Section 10.9 that one fetch stream
630/// shows.
631///
632/// [`PriorGaps::from_properties`] refuses what one Object shows. Across the
633/// stream this refuses "A Group contains more than one Object with different
634/// values for Prior Group ID Gap" for the Objects of one Group. It does not
635/// refuse a gap covering an Object received earlier, or an Object within a gap
636/// communicated earlier, though this draft lists both: draft-21 Section 2.1.1
637/// says of an Object known to exist, "From this state, it can transition to not
638/// existing, but not vice versa", and of an Object received after it was
639/// recorded as not existing, "This is not a protocol error and the Track is not
640/// malformed." So whether either makes the Track malformed is ambiguous.
641/// draft-21 Section 9.11: "Within each group, objects are sent in Object ID
642/// order; subgroup ID is not used for ordering."
643///
644/// [`FetchObjectReader::read_object_header`] applies it to each Object it reads.
645///
646/// End of Range indicators are not Objects; the caller passes Objects only.
647#[derive(Debug, Clone, PartialEq, Eq)]
648// Boxed so that the fetch readers holding one stay small.
649pub struct FetchGaps(Box<crate::prior_gaps::FetchGapTracker>);
650
651impl Default for FetchGaps {
652 fn default() -> Self {
653 Self::new()
654 }
655}
656
657impl FetchGaps {
658 /// Track the Objects of one fetch stream.
659 pub fn new() -> Self {
660 Self(Box::new(crate::prior_gaps::FetchGapTracker::new(
661 crate::prior_gaps::GapRules::Draft18To22,
662 )))
663 }
664
665 /// Check the next Object on the stream, from its resolved Group ID and
666 /// Object ID and its property block, refusing a gap condition with
667 /// [`CodecError::MalformedTrack`] and a block that does not parse as
668 /// [`crate::object_properties::parse_object_properties`] does. Records the
669 /// Object only when it passes.
670 pub fn check(
671 &mut self,
672 group_id: u64,
673 object_id: u64,
674 property_block: &[u8],
675 ) -> Result<PriorGaps, CodecError> {
676 let properties = crate::object_properties::parse_object_properties(
677 crate::version::DraftVersion::Draft21,
678 property_block,
679 )?;
680 let gaps = PriorGaps::from_properties(&properties, group_id, object_id)?;
681 self.0.check(group_id, object_id, gaps.group.as_slice(), gaps.object)?;
682 Ok(gaps)
683 }
684}
685
686#[derive(Debug, Clone)]
687pub struct SubgroupObjectReader {
688 extensions_present: bool,
689 prev_object_id: Option<u64>,
690 group_id: u64,
691 group_gap: Option<u64>,
692}
693
694impl SubgroupObjectReader {
695 pub fn new(header: &SubgroupHeader) -> Self {
696 Self {
697 extensions_present: header.has_properties(),
698 prev_object_id: None,
699 group_id: header.group_id.into_inner(),
700 group_gap: None,
701 }
702 }
703
704 /// Decode the next object whole, payload included.
705 ///
706 /// Refuses, with [`CodecError::InvalidField`], properties on an object
707 /// whose status is not Normal: Section 11.1.3, "If an endpoint receives
708 /// properties on an Object with status that is not Normal, it MUST close
709 /// the session with a PROTOCOL_VIOLATION." [`Self::read_object_meta`]
710 /// frames such an object without judging it, as
711 /// [`DatagramHeader::decode`] does a datagram.
712 ///
713 /// Refuses, with [`CodecError::MalformedTrack`], the Prior Group ID Gap and
714 /// Prior Object ID Gap conditions of Section 10.8 and Section 10.9 this stream
715 /// shows: those [`PriorGaps::from_properties`] refuses, and "A Group contains
716 /// more than one Object with different values for Prior Group ID Gap". It does
717 /// not refuse "An endpoint receives an Object with a Prior Object ID Gap
718 /// covering an Object it previously received": draft-21 Section 2.1.1 says of
719 /// an Object known to exist, "From this state, it can transition to not
720 /// existing, but not vice versa", so whether such a gap makes the Track
721 /// malformed is ambiguous. [`Self::read_object_meta`] does not read properties
722 /// and applies none of them.
723 pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<SubgroupObject, CodecError> {
724 let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
725 let object_id_val = match self.prev_object_id {
726 None => delta,
727 Some(prev) => prev
728 .checked_add(1)
729 .and_then(|v| v.checked_add(delta))
730 .ok_or(CodecError::ObjectIdOverflow(prev, delta))?,
731 };
732 self.prev_object_id = Some(object_id_val);
733 let object_id = VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?;
734
735 // The properties block is a byte-length-prefixed opaque blob. We
736 // copy the blob verbatim; callers that want structured properties
737 // can parse the returned bytes.
738 let extension_headers = if self.extensions_present {
739 let ext_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
740 crate::types::read_bytes(buf, ext_len)?
741 } else {
742 Vec::new()
743 };
744
745 let payload_length_vi = VarInt::decode_moqt::<Wire>(buf)?;
746 let payload_length_val = payload_length_vi.into_inner() as usize;
747 let (object_status, payload) = if payload_length_val == 0 {
748 let status = VarInt::decode_moqt::<Wire>(buf)?;
749 (Some(decoded_status(status.into_inner())?), Vec::new())
750 } else {
751 let payload = crate::types::read_bytes(buf, payload_length_val)?;
752 (None, payload)
753 };
754
755 let properties = crate::object_properties::parse_object_properties(
756 crate::version::DraftVersion::Draft21,
757 &extension_headers,
758 )?;
759 let gaps = PriorGaps::from_properties(&properties, self.group_id, object_id_val)?;
760 self.check_stream_gaps(gaps)?;
761 let object = SubgroupObject {
762 object_id,
763 extension_headers,
764 payload_length: payload_length_vi,
765 object_status,
766 payload,
767 };
768 if !object.properties_permitted() {
769 return Err(CodecError::InvalidField);
770 }
771 Ok(object)
772 }
773
774 fn check_stream_gaps(&mut self, gaps: PriorGaps) -> Result<(), CodecError> {
775 if let Some(gap) = gaps.group {
776 match self.group_gap {
777 Some(seen) if seen != gap => {
778 return Err(CodecError::MalformedTrack {
779 detail: format!(
780 "two Objects in Group {} carry different Prior Group ID Gap values",
781 self.group_id
782 ),
783 });
784 }
785 _ => self.group_gap = Some(gap),
786 }
787 }
788 Ok(())
789 }
790
791 /// Decode the next object's framing without copying its payload.
792 ///
793 /// Consumes exactly the bytes [`Self::read_object`] consumes and leaves
794 /// the same delta state behind, so the two are interchangeable on a
795 /// given stream.
796 pub fn read_object_meta(
797 &mut self,
798 buf: &mut impl Buf,
799 ) -> Result<SubgroupObjectMeta, CodecError> {
800 let start = buf.remaining();
801 let delta = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
802 let object_id_val = match self.prev_object_id {
803 None => delta,
804 Some(prev) => prev
805 .checked_add(1)
806 .and_then(|v| v.checked_add(delta))
807 .ok_or(CodecError::ObjectIdOverflow(prev, delta))?,
808 };
809 self.prev_object_id = Some(object_id_val);
810 let object_id =
811 VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?.into_inner();
812
813 let extension_headers_len = if self.extensions_present {
814 let ext_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
815 skip(buf, ext_len)?;
816 ext_len
817 } else {
818 0
819 };
820
821 let payload_length = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
822 let status = if payload_length == 0 {
823 let code = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
824 Some(decoded_status(code)?.as_u64())
825 } else {
826 skip(buf, payload_length)?;
827 None
828 };
829
830 Ok(SubgroupObjectMeta {
831 object_id,
832 extension_headers_len,
833 payload_length,
834 status,
835 wire_len: (start - buf.remaining()) as u64,
836 })
837 }
838
839 /// Serialize an object, producing the correct delta encoding.
840 ///
841 /// A zero `payload_length` writes the object's status, with
842 /// [`SubgroupObject::status`] filling in [`ObjectStatus::Normal`] when the
843 /// `object_status` field is `None`. The status is typed, so every value
844 /// that can reach this method is one draft-21 assigns and one
845 /// [`Self::read_object`] accepts; no unassigned code can be written.
846 ///
847 /// Errors with [`CodecError::InvalidField`] when the Object Status registry
848 /// (draft-21 Section 16.9) does not permit the object's status to carry a
849 /// payload and a payload was handed over anyway. Draft-21 Section 11.1.2
850 /// makes that object malformed, and the encoding has no way to state it:
851 /// the status field appears only where the payload does not. Writing it
852 /// would mean silently discarding one of the two — emitting the bytes as a
853 /// Normal object and losing an End of Group marker, say — so it is refused
854 /// instead. A status the registry does permit a payload is written as an
855 /// ordinary payload object, its status implicit, since that is how the
856 /// encoding spells it.
857 ///
858 /// Errors with [`CodecError::InvalidField`] when `object.object_id` is
859 /// not strictly greater than the previously written object's ID, since
860 /// no valid delta exists for that case.
861 ///
862 /// Errors with [`CodecError::InvalidField`] when `payload_length` is not
863 /// exactly `payload.len()`. The declared length is written ahead of the
864 /// payload, so a mismatch is a frame [`Self::read_object`] cannot parse
865 /// and one no caller could fix by appending bytes.
866 pub fn write_object(
867 &mut self,
868 object: &SubgroupObject,
869 buf: &mut impl BufMut,
870 ) -> Result<(), CodecError> {
871 // A declared length that disagrees with the payload framed under it
872 // produces bytes no reader can parse and no caller can repair: the
873 // length is already on the wire ahead of the payload. Checked before
874 // anything is written, so a refused object leaves `buf` untouched
875 // rather than half an object the next read would run into.
876 //
877 // A zero declared length is also what puts a status code where the
878 // payload would go, so an object carrying bytes under it is asking for
879 // two framings at once.
880 let declared = object.payload_length.into_inner();
881 if declared != object.payload.len() as u64 {
882 return Err(CodecError::InvalidField);
883 }
884
885 // The registry decides whether these two fields may be filled in at
886 // once, rather than the length deciding on its own which of them gets
887 // written. A status marked "Payload: No" is refused a payload; a status
888 // marked "Yes" keeps it, and gets no status field on the wire because
889 // the encoding elides the status of any object that carries bytes.
890 // Checked alongside the length above, before any byte is emitted.
891 if declared != 0 && !object.permits_payload() {
892 return Err(CodecError::PayloadNotPermitted {
893 status: object.status().as_u64(),
894 len: object.payload.len(),
895 detail: "its status is registered as forbidding one",
896 });
897 }
898
899 // Properties on a non-Normal status are a frame `read_object` refuses
900 // (Section 11.1.3), so they are not written either.
901 if !object.properties_permitted() {
902 return Err(CodecError::InvalidField);
903 }
904
905 let oid = object.object_id.into_inner();
906 let delta = match self.prev_object_id {
907 None => oid,
908 Some(prev) => oid
909 .checked_sub(prev)
910 .and_then(|v| v.checked_sub(1))
911 .ok_or(CodecError::InvalidField)?,
912 };
913 VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?.encode_moqt::<Wire>(buf);
914 if self.extensions_present {
915 VarInt::from_u64(object.extension_headers.len() as u64)
916 .map_err(|_| CodecError::InvalidField)?
917 .encode_moqt::<Wire>(buf);
918 buf.put_slice(&object.extension_headers);
919 }
920 object.payload_length.encode_moqt::<Wire>(buf);
921 if declared == 0 {
922 VarInt::from_u64_moqt(object.status().as_u64()).encode_moqt::<Wire>(buf);
923 } else {
924 buf.put_slice(&object.payload);
925 }
926 self.prev_object_id = Some(oid);
927 Ok(())
928 }
929}
930
931// ── Datagram ──────────────────────────────────────────────────
932
933const DATAGRAM_PROPERTIES_BIT: u8 = 0x01;
934const DATAGRAM_END_OF_GROUP_BIT: u8 = 0x02;
935const DATAGRAM_ZERO_OBJECT_ID_BIT: u8 = 0x04;
936const DATAGRAM_DEFAULT_PRIORITY_BIT: u8 = 0x08;
937const DATAGRAM_STATUS_BIT: u8 = 0x20;
938/// Bit 4. Draft-21 Section 11.2.1: "This bit is reserved and MUST be zero."
939/// The opposite of Section 11.3.1, where the same bit MUST be 1.
940const DATAGRAM_RESERVED_BIT: u8 = 0x10;
941
942/// Refuse a datagram `Type Flags` value draft-21 Section 11.2.1 lists as
943/// invalid.
944///
945/// The section states the rule twice, once in the paragraph introducing the
946/// field — "If a received value has bit 4 set, or has a bit set whose meaning
947/// is not specified, the endpoint MUST close the session with a
948/// PROTOCOL_VIOLATION" — and once as a list of three conditions, each of which
949/// MUST close the session with a PROTOCOL_VIOLATION:
950///
951/// 1. "Values with both the STATUS bit (0x20) and END_OF_GROUP bit (0x02)
952/// set": 0x22, 0x23, 0x26, 0x27, 0x2A, 0x2B, 0x2E and 0x2F. The reason is
953/// that an object status message cannot signal end of group — the two bits
954/// ask for one datagram to be both a status and an end-of-group marker for
955/// an object it does not carry.
956/// 2. "Values with bit 4 (0x10) set. This bit is reserved and MUST be zero."
957/// 3. "Values with a bit set whose meaning is not specified." Bits 0, 1, 2, 3
958/// and 5 are specified; bit 6 and everything above it are not, so this
959/// catches 0x40 and, with it, every value of 128 or greater.
960///
961/// The valid set is 0x00..0x0F and 0x20..0x2F, which is draft-19's enumeration
962/// unchanged.
963///
964/// **These are not the subgroup header's rules.** Section 11.3.1 requires bit 4
965/// to be *set*, has no unspecified-bit condition, and states an explicit
966/// "values of 128 or greater" condition that this list does not need. The two
967/// sets are read from their own sections and neither is derived from the other.
968fn validate_datagram_type(raw: u64) -> Result<(), CodecError> {
969 if datagram_type_is_valid(raw) {
970 Ok(())
971 } else {
972 Err(datagram_type_error(raw))
973 }
974}
975
976/// Every bit draft-21 Section 11.2.1 gives a datagram `Type Flags` value a
977/// meaning for: PROPERTIES, END_OF_GROUP, ZERO_OBJECT_ID, DEFAULT_PRIORITY and
978/// STATUS. Bit 4 is reserved and bits 6 and up are unspecified, so both fail
979/// the mask.
980const DATAGRAM_SPECIFIED_BITS: u64 = (DATAGRAM_PROPERTIES_BIT
981 | DATAGRAM_END_OF_GROUP_BIT
982 | DATAGRAM_ZERO_OBJECT_ID_BIT
983 | DATAGRAM_DEFAULT_PRIORITY_BIT
984 | DATAGRAM_STATUS_BIT) as u64;
985
986/// Whether `raw` is a datagram `Type Flags` value draft-21 admits.
987fn datagram_type_is_valid(raw: u64) -> bool {
988 // Every bit outside the specified set fails, which covers bit 4 (reserved),
989 // bit 6 (unspecified) and everything at 128 or above in one test.
990 raw & !DATAGRAM_SPECIFIED_BITS == 0
991 && !(raw & DATAGRAM_STATUS_BIT as u64 != 0 && raw & DATAGRAM_END_OF_GROUP_BIT as u64 != 0)
992}
993
994/// Which failure a leading datagram `Type Flags` value that is not one a reader
995/// wants is.
996///
997/// The same split as `stream_type_error`, read against the datagram table.
998/// Section 11.2.1's three conditions each get an arm, so a log names which one
999/// a value fell into rather than reporting all three as one complaint; a value
1000/// too wide to be a one-byte flags field at all is Section 6.4.1's
1001/// [`CodecError::UnknownDatagramType`] instead.
1002///
1003/// The padding datagram is why the [`CodecError::InvalidField`] arm exists.
1004/// [`PADDING_DATAGRAM_TYPE`] is assigned, so a datagram carrying it is not
1005/// unknown; it simply carries no Object, and refusing it must not end the
1006/// session.
1007fn datagram_type_error(raw: u64) -> CodecError {
1008 if raw == PADDING_DATAGRAM_TYPE || datagram_type_is_valid(raw) {
1009 return CodecError::InvalidField;
1010 }
1011 if raw > 0xFF {
1012 return CodecError::UnknownDatagramType(raw);
1013 }
1014 let t = raw as u8;
1015 if t & DATAGRAM_RESERVED_BIT != 0 {
1016 CodecError::InvalidDatagramTypeValue {
1017 raw,
1018 detail: "bit 4 (0x10) is reserved for a datagram and must be zero",
1019 }
1020 } else if t & DATAGRAM_STATUS_BIT != 0 && t & DATAGRAM_END_OF_GROUP_BIT != 0 {
1021 CodecError::InvalidDatagramTypeValue {
1022 raw,
1023 detail: "it sets both the STATUS bit and the END_OF_GROUP bit",
1024 }
1025 } else {
1026 CodecError::InvalidDatagramTypeValue {
1027 raw,
1028 detail: "it sets a bit whose meaning is not specified for a datagram",
1029 }
1030 }
1031}
1032
1033#[derive(Debug, Clone)]
1034pub struct DatagramHeader {
1035 pub datagram_type: u8,
1036 pub track_alias: VarInt,
1037 pub group_id: VarInt,
1038 pub object_id: VarInt,
1039 pub publisher_priority: Option<u8>,
1040 /// Raw properties bytes, excluding the byte-length prefix that precedes
1041 /// them on the wire. Present only when `datagram_type` sets the PROPERTIES
1042 /// bit (0x01), and empty otherwise — the bit is what puts the block on the
1043 /// wire, so contents held here with the bit clear are not written.
1044 ///
1045 /// Opaque: [`Self::encode`] re-emits the prefix and these bytes verbatim,
1046 /// and [`Self::decode`] copies them out the same way, so a datagram can be
1047 /// decoded and re-encoded without understanding what its properties mean.
1048 /// The block sits between the publisher priority and the status field, so
1049 /// leaving it out of the struct would put the status where the decoder
1050 /// looks for the properties length.
1051 pub properties: Vec<u8>,
1052 /// The object's status, carried on the wire only when `datagram_type` sets
1053 /// the STATUS bit (0x20): such a datagram holds a one-byte status code in
1054 /// place of a payload. `None` with the bit set is written as
1055 /// [`ObjectStatus::Normal`]; a status with the bit clear is not written at
1056 /// all, because the bit is what puts the field on the wire.
1057 ///
1058 /// `None` is not "no status": a datagram whose type leaves the STATUS bit
1059 /// clear carries a payload, and the status of an Object that carries a
1060 /// payload is [`ObjectStatus::Normal`], the only row of the Object Status
1061 /// registry (draft-21 Section 16.9) permitting one. [`Self::status`]
1062 /// resolves the field either way.
1063 ///
1064 /// Typed rather than a bare byte. The wire field is one octet with 256
1065 /// values, and draft-21 Section 11.1.2 assigns three of them; the
1066 /// decoder refuses the other 253, and this type is that same refusal on
1067 /// the encode side — [`Self::encode`] is infallible precisely because a
1068 /// status it could not legally write cannot be built.
1069 pub object_status: Option<ObjectStatus>,
1070}
1071
1072impl DatagramHeader {
1073 /// Decode a datagram header, `Type Flags` field included.
1074 ///
1075 /// The field is read as a whole varint of whatever width the sender used,
1076 /// and a non-minimal spelling of a valid value is accepted, for the reason
1077 /// given on [`SubgroupHeader::decode`] and in the module documentation.
1078 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1079 let raw = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1080 validate_datagram_type(raw)?;
1081 // Narrowing is safe: `validate_datagram_type` has already refused every
1082 // value with a bit set above the specified five.
1083 let datagram_type = raw as u8;
1084
1085 let track_alias = VarInt::decode_moqt::<Wire>(buf)?;
1086 let group_id = VarInt::decode_moqt::<Wire>(buf)?;
1087
1088 let object_id = if datagram_type & DATAGRAM_ZERO_OBJECT_ID_BIT != 0 {
1089 VarInt::from_usize(0)
1090 } else {
1091 VarInt::decode_moqt::<Wire>(buf)?
1092 };
1093
1094 let publisher_priority = if datagram_type & DATAGRAM_DEFAULT_PRIORITY_BIT == 0 {
1095 if buf.remaining() < 1 {
1096 return Err(CodecError::UnexpectedEnd);
1097 }
1098 Some(buf.get_u8())
1099 } else {
1100 None
1101 };
1102
1103 let properties = if datagram_type & DATAGRAM_PROPERTIES_BIT != 0 {
1104 let props_len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1105 crate::types::read_bytes(buf, props_len)?
1106 } else {
1107 Vec::new()
1108 };
1109
1110 let object_status = if datagram_type & DATAGRAM_STATUS_BIT != 0 {
1111 if buf.remaining() < 1 {
1112 return Err(CodecError::UnexpectedEnd);
1113 }
1114 let status = buf.get_u8();
1115 Some(decoded_status(status as u64)?)
1116 } else {
1117 None
1118 };
1119
1120 // Two rules of draft-21 Section 11.2.1 reach the properties block just
1121 // read, and neither is applied here — [`Self::properties_permitted`]
1122 // and [`Self::properties_block_well_formed`] report them instead, and
1123 // [`Self::encode_checked`] refuses to write either shape:
1124 //
1125 // - "If an endpoint receives a datagram with the PROPERTIES bit set
1126 // and an Properties Length of 0, it MUST close the session with a
1127 // PROTOCOL_VIOLATION."
1128 // - "If an Object Datagram includes both the STATUS bit and
1129 // PROPERTIES bit, and the Object Status is not Normal (0x0), the
1130 // endpoint MUST close the session with a PROTOCOL_VIOLATION,
1131 // because only Normal Objects can have Properties."
1132 //
1133 // Both describe a datagram that is well framed and non-conforming: the
1134 // fields are all where the layout puts them and every one of them
1135 // parses, so a decoder can read the datagram back exactly as it
1136 // arrived. Refusing here would leave this module unable to reproduce a
1137 // capture containing one, and both rules address an endpoint receiving
1138 // such a datagram, so the endpoint is where they are enforced — the
1139 // same division [`SubgroupObject::properties_permitted`] explains.
1140 //
1141 // The Type rules above are the contrast, and the contrast is what
1142 // decides it: an invalid Type names no layout at all, so reading on
1143 // invents the fields behind it rather than reporting them.
1144
1145 Ok(DatagramHeader {
1146 datagram_type,
1147 track_alias,
1148 group_id,
1149 object_id,
1150 publisher_priority,
1151 properties,
1152 object_status,
1153 })
1154 }
1155
1156 /// Decode one whole datagram: the header, then the payload that runs to the
1157 /// end of `buf`.
1158 ///
1159 /// `buf` must hold exactly one transport datagram and nothing else, since
1160 /// that boundary is the only thing that delimits the payload — draft-21
1161 /// Section 11.2.1: "There is no explicit length field for the Object
1162 /// Payload; the entirety of the transport datagram following the Object
1163 /// header contains the payload."
1164 ///
1165 /// Which is why the refusal lives here and not in [`Self::decode`]. A
1166 /// datagram whose type sets the STATUS bit has no payload at all — the same
1167 /// section: "When set to 1, the Object Status field is present and there is
1168 /// no Object Payload" — so trailing bytes after its status are not a short
1169 /// payload or an odd one, they are bytes the frame does not define. A
1170 /// decoder that stops at the header cannot see them, and a caller that
1171 /// treats whatever is left as the payload hands the application content the
1172 /// publisher never framed as content. That is the case this refuses, and it
1173 /// bites hardest on a status datagram carrying the Normal code 0x0, whose
1174 /// status alone would report a payload as permitted.
1175 ///
1176 /// The same refusal covers a status the registry forbids a payload to, per
1177 /// Section 11.1.2 and the "Payload" column of Section 16.9.
1178 ///
1179 /// Errors with [`CodecError::PayloadNotPermitted`] when bytes remain and
1180 /// the header forbids them, naming which of the two rules refused them.
1181 ///
1182 /// # The two Properties rules are enforced here too
1183 ///
1184 /// Section 11.2.1 states them of a receiving endpoint, and this is the
1185 /// endpoint's read:
1186 ///
1187 /// * "If an endpoint receives a datagram with the PROPERTIES bit set and an
1188 /// Properties Length of 0, it MUST close the session with a
1189 /// PROTOCOL_VIOLATION." The bit and a zero length are two ways to spell
1190 /// *no properties* and a datagram may use only the first, because a
1191 /// datagram with none has a Type Flags value that says so and the block
1192 /// costs bytes the flags already saved. A subgroup stream says the
1193 /// opposite in Section 11.3.1 — there the PROPERTIES bit is fixed for the
1194 /// whole stream, so an object with no properties has nowhere else to say
1195 /// so and a zero-length block is the required spelling.
1196 /// * "If an Object Datagram includes both the STATUS bit and PROPERTIES
1197 /// bit, and the Object Status is not Normal (0x0), the endpoint MUST close
1198 /// the session with a PROTOCOL_VIOLATION, because only Normal Objects can
1199 /// have Properties."
1200 ///
1201 /// Both errors are [`CodecError::InvalidField`].
1202 ///
1203 /// [`Self::decode`] does **not** apply them, and the split is deliberate.
1204 /// Both describe a datagram that is well framed and non-conforming: every
1205 /// field is where the layout puts it and every one of them parses, so the
1206 /// header reads back exactly as it arrived and a tool reproducing a capture
1207 /// can re-emit it. What it may not do is hand such a datagram to an
1208 /// application as an ordinary Object, which is what this entry point would
1209 /// be doing. [`Self::properties_block_well_formed`] and
1210 /// [`Self::properties_permitted`] report the two for a caller that wants
1211 /// the header without the judgement, and [`Self::encode_checked`] refuses
1212 /// to write either shape.
1213 ///
1214 /// A property block that does not parse is refused by
1215 /// [`crate::object_properties::parse_object_properties`], and the Prior
1216 /// Group ID Gap and Prior Object ID Gap conditions one Object shows by
1217 /// [`PriorGaps::from_properties`].
1218 pub fn decode_object(buf: &mut impl Buf) -> Result<(Self, Vec<u8>), CodecError> {
1219 let header = Self::decode(buf)?;
1220 if !header.properties_block_well_formed() || !header.properties_permitted() {
1221 return Err(CodecError::InvalidField);
1222 }
1223 let properties = crate::object_properties::parse_object_properties(
1224 crate::version::DraftVersion::Draft21,
1225 &header.properties,
1226 )?;
1227 PriorGaps::from_properties(
1228 &properties,
1229 header.group_id.into_inner(),
1230 header.object_id.into_inner(),
1231 )?;
1232 let payload = crate::types::read_bytes(buf, buf.remaining())?;
1233 if !payload.is_empty() && !header.permits_payload() {
1234 return Err(CodecError::PayloadNotPermitted {
1235 status: header.status().as_u64(),
1236 len: payload.len(),
1237 detail: if header.has_status() {
1238 "its type states a status in place of a payload"
1239 } else {
1240 "its status is registered as forbidding one"
1241 },
1242 });
1243 }
1244 Ok((header, payload))
1245 }
1246
1247 /// Serialize the header, refusing a status the framing cannot carry.
1248 ///
1249 /// A datagram states a status only when its type byte sets the STATUS bit
1250 /// (0x20). With the bit clear there is no status field on the wire, so an
1251 /// `object_status` of anything but [`ObjectStatus::Normal`] has nowhere to
1252 /// go: [`Self::encode`] drops it, and the datagram parses back as an
1253 /// ordinary payload object. An End of Group marker written that way does
1254 /// not arrive late or malformed — it does not arrive at all, and the
1255 /// receiver sees a normal object in its place.
1256 ///
1257 /// [`ObjectStatus::Normal`] with the bit clear is not that case and is
1258 /// accepted. It is the status the encoding elides for every object that
1259 /// carries a payload, so stating it asks for exactly the bytes leaving it
1260 /// out asks for, and nothing is lost.
1261 ///
1262 /// Errors with [`CodecError::InvalidField`] on the lossy combination,
1263 /// before any byte is written, so a refused header leaves `buf` untouched.
1264 /// This is the datagram half of the rule
1265 /// [`SubgroupObjectReader::write_object`] applies on a subgroup stream.
1266 ///
1267 /// Also errors with [`CodecError::InvalidField`] for a Type value draft-21
1268 /// Section 11.2.1 lists as invalid, and for the same reason: a datagram
1269 /// written with one could not be read back by [`Self::decode`], and a
1270 /// codec whose two halves disagree about which datagrams exist cannot be
1271 /// used to rewrite captured traffic.
1272 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1273 validate_datagram_type(self.datagram_type as u64)?;
1274 if !self.has_status() && matches!(self.object_status, Some(s) if s != ObjectStatus::Normal)
1275 {
1276 return Err(CodecError::InvalidField);
1277 }
1278 // The two properties rules of Section 11.2.1. [`Self::decode`] reports
1279 // both rather than refusing them, because the datagrams they describe
1280 // are well framed and a codec that could not read one could not
1281 // reproduce a capture containing it. Writing one is the other
1282 // direction and has no such excuse: a conforming peer answers either
1283 // with a PROTOCOL_VIOLATION, so emitting one costs the session and not
1284 // merely the datagram.
1285 if !self.properties_block_well_formed() || !self.properties_permitted() {
1286 return Err(CodecError::InvalidField);
1287 }
1288 self.encode(buf);
1289 Ok(())
1290 }
1291
1292 /// Serialize the header exactly as its type byte describes it.
1293 ///
1294 /// Every field the type byte announces is written, in the order
1295 /// [`Self::decode`] reads them, so the bytes this produces always parse
1296 /// back. The properties block in particular has to be written here: it
1297 /// sits ahead of the status field, and a datagram that skipped it would
1298 /// offer the status byte where the decoder reads the block's length.
1299 ///
1300 /// The type byte is taken as the authority on framing, which is what makes
1301 /// this infallible — and what makes it lossy when the struct disagrees with
1302 /// itself. An `object_status` set while the type byte leaves the STATUS bit
1303 /// clear is discarded here without a word, and a Type value draft-21
1304 /// forbids is written out as readily as one it assigns, including Types
1305 /// this module's own [`Self::decode`] refuses. Prefer
1306 /// [`Self::encode_checked`], which refuses both instead of resolving them.
1307 pub fn encode(&self, buf: &mut impl BufMut) {
1308 buf.put_u8(self.datagram_type);
1309 self.track_alias.encode_moqt::<Wire>(buf);
1310 self.group_id.encode_moqt::<Wire>(buf);
1311
1312 if self.datagram_type & DATAGRAM_ZERO_OBJECT_ID_BIT == 0 {
1313 self.object_id.encode_moqt::<Wire>(buf);
1314 }
1315
1316 if self.datagram_type & DATAGRAM_DEFAULT_PRIORITY_BIT == 0 {
1317 buf.put_u8(self.publisher_priority.unwrap_or(128));
1318 }
1319
1320 if self.datagram_type & DATAGRAM_PROPERTIES_BIT != 0 {
1321 VarInt::from_usize(self.properties.len()).encode_moqt::<Wire>(buf);
1322 buf.put_slice(&self.properties);
1323 }
1324
1325 if self.datagram_type & DATAGRAM_STATUS_BIT != 0 {
1326 buf.put_u8(self.object_status.unwrap_or(ObjectStatus::Normal).as_u8());
1327 }
1328 }
1329
1330 pub fn is_end_of_group(&self) -> bool {
1331 self.datagram_type & DATAGRAM_END_OF_GROUP_BIT != 0
1332 }
1333
1334 pub fn has_status(&self) -> bool {
1335 self.datagram_type & DATAGRAM_STATUS_BIT != 0
1336 }
1337
1338 /// `true` when the type byte sets the PROPERTIES bit (0x01), which is what
1339 /// puts the properties block on the wire.
1340 ///
1341 /// Reports the framing, not the contents. A decoded datagram with this set
1342 /// always has a non-empty [`Self::properties`], because [`Self::decode`]
1343 /// refuses a zero-length block; a header built by hand can hold the two
1344 /// apart, and [`Self::encode_checked`] is what refuses that.
1345 pub fn has_properties(&self) -> bool {
1346 self.datagram_type & DATAGRAM_PROPERTIES_BIT != 0
1347 }
1348
1349 /// The datagram's object status, with the one the encoding elides filled
1350 /// in.
1351 ///
1352 /// A datagram states a status only when its type sets the STATUS bit, and
1353 /// such a datagram has no payload. One without the bit is all payload, and
1354 /// its status is [`ObjectStatus::Normal`] — the sole row of the Object
1355 /// Status registry (draft-21 Section 16.9) permitting a payload, so the
1356 /// only status it could have had.
1357 pub fn status(&self) -> ObjectStatus {
1358 self.object_status.unwrap_or(ObjectStatus::Normal)
1359 }
1360
1361 /// Whether the bytes after this datagram's header are allowed to exist.
1362 ///
1363 /// Two independent rules forbid them, and this reports both:
1364 ///
1365 /// - The framing. Draft-21 Section 11.2.1: "The STATUS bit (0x20)
1366 /// indicates whether the datagram contains an Object Status or Object
1367 /// Payload. When set to 1, the Object Status field is present and there
1368 /// is no Object Payload." A datagram that states a status has no payload
1369 /// field at all, whichever status it states — so a STATUS datagram
1370 /// carrying the Normal code 0x0 has no more room for bytes than one
1371 /// carrying End of Group.
1372 /// - The status. Section 11.1.2 and the Object Status registry's
1373 /// "Payload" column, Section 16.9: an Object has an empty payload unless
1374 /// its status is registered as permitting one. This half reaches a
1375 /// datagram whose type byte leaves the STATUS bit clear while the value
1376 /// claims a status that forbids a payload — a disagreement
1377 /// [`Self::encode_checked`] refuses to write, and one a decoded header
1378 /// never shows.
1379 ///
1380 /// The first is the rule a decoded datagram can actually trip, and reading
1381 /// the registry alone misses it: `Some(ObjectStatus::Normal)` under a type
1382 /// byte with the STATUS bit set is exactly the case where the payload the
1383 /// draft says does not exist would otherwise be handed to the application
1384 /// as the object's content, because Normal is the one status the registry
1385 /// marks as permitting a payload.
1386 ///
1387 /// Distinct from [`Self::has_status`], which reports how the datagram is
1388 /// framed rather than whether a payload may follow. A caller holding the
1389 /// bytes after the header wants this one; [`Self::decode_object`] applies
1390 /// it for a caller who would rather the decode simply fail.
1391 pub fn permits_payload(&self) -> bool {
1392 if self.has_status() {
1393 return false;
1394 }
1395 self.status().permits_payload()
1396 }
1397
1398 /// Whether this datagram's status is allowed to carry the properties it
1399 /// has.
1400 ///
1401 /// The same rule the subgroup form obeys. Draft-21 Section 11.2.1 builds
1402 /// the datagram's Properties field out of "the Object Properties structure
1403 /// defined in Section 11.1.3", and that section is where the rule sits:
1404 /// "If an endpoint receives properties on an Object with status that is not
1405 /// Normal, it MUST close the session with a PROTOCOL_VIOLATION."
1406 ///
1407 /// [`Self::decode`] reports this and [`Self::decode_object`] refuses it, as
1408 /// the subgroup readers split it.
1409 pub fn properties_permitted(&self) -> bool {
1410 self.properties.is_empty() || self.status() == ObjectStatus::Normal
1411 }
1412
1413 /// Whether the properties block is framed the way a datagram may frame it.
1414 ///
1415 /// Draft-21 Section 11.2.1: "If an endpoint receives a datagram with the
1416 /// PROPERTIES bit set and an Properties Length of 0, it MUST close the
1417 /// session with a PROTOCOL_VIOLATION."
1418 ///
1419 /// The bit and a zero length are two ways to spell "no properties", and on
1420 /// a datagram they are not interchangeable: a datagram with none has a type
1421 /// byte that says so, and the block costs bytes the type byte already
1422 /// saved. This rule is the datagram's alone. A subgroup stream says the
1423 /// opposite in Section 11.3.1 — "Objects with no properties set Properties
1424 /// Length to 0" — because there the PROPERTIES bit is fixed for the whole
1425 /// stream, so an object with no properties has nowhere else to say so and a
1426 /// zero-length block is the required spelling rather than a violation.
1427 ///
1428 /// The mirror case is not a wire state but is a state this struct can hold:
1429 /// properties with the bit clear. [`Self::encode`] drops them without a
1430 /// word, so this reports that too, and [`Self::encode_checked`] refuses
1431 /// both.
1432 pub fn properties_block_well_formed(&self) -> bool {
1433 self.has_properties() != self.properties.is_empty()
1434 }
1435}
1436
1437// ── Fetch Header ──────────────────────────────────────────────
1438
1439const FETCH_STREAM_TYPE: u64 = 0x05;
1440
1441/// The head of a fetch stream: the stream type `0x05` and a Request ID.
1442///
1443/// Byte-identical to draft-19. What draft-20 widened is what the Request ID may
1444/// name. Section 11.4.1: "all objects on the stream belong to the track
1445/// requested in the message identified by Request ID", where draft-19 said "the
1446/// track requested in the Fetch message". The widening is deliberate, because a
1447/// fetch stream can
1448/// now be a *fill fetch stream* whose Request ID names the `SUBSCRIBE` or the
1449/// `REQUEST_UPDATE` that carried `FILL_PARAMETERS` (Section 3.4), not only a
1450/// `FETCH`. Nothing on the stream says which, so a reader that keys a fetch
1451/// stream by looking up a FETCH will fail to find one.
1452///
1453/// # Two things a fill fetch stream does not have, which the draft leaves open
1454///
1455/// * **No `FETCH_OK`, and therefore no `End Location` and no `End Of Track`.**
1456/// Section 3.4 says the fill is "delivered as a FETCH response" and
1457/// Section 3.4.1 says there is no `REQUEST_ERROR` for it, so there is no OK
1458/// either. How a subscriber learns the fill's actual end, beyond the stream
1459/// FINing, is unspecified; this codec treats the FIN as the only end signal
1460/// and does not synthesize a `FETCH_OK`. Section 9.11's rule that gaps
1461/// between the last Object and the FETCH_OK's Largest imply non-existence
1462/// cannot be applied here, because there is no FETCH_OK to read it from.
1463/// * **Failure is a stream reset, not an error message.** Section 3.4.1: if
1464/// the publisher must fail a fill it opens the stream and resets it
1465/// immediately after the `FETCH_HEADER`. Resetting or cancelling a fill fetch
1466/// stream does not affect the subscription, which keeps delivering.
1467///
1468/// Both are the caller's to act on: neither is visible in a frame.
1469#[derive(Debug, Clone)]
1470pub struct FetchHeader {
1471 pub request_id: VarInt,
1472}
1473
1474impl FetchHeader {
1475 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1476 let stream_type = VarInt::decode_moqt::<Wire>(buf)?.into_inner();
1477 if stream_type != FETCH_STREAM_TYPE {
1478 return Err(stream_type_error(stream_type));
1479 }
1480 let request_id = VarInt::decode_moqt::<Wire>(buf)?;
1481 Ok(FetchHeader { request_id })
1482 }
1483
1484 pub fn encode(&self, buf: &mut impl BufMut) {
1485 VarInt::from_usize(FETCH_STREAM_TYPE as usize).encode_moqt::<Wire>(buf);
1486 self.request_id.encode_moqt::<Wire>(buf);
1487 }
1488}
1489
1490// ── Fetch objects ─────────────────────────────────────────────
1491
1492/// Serialization Flags bits 0-1, the Subgroup ID encoding
1493/// (draft-21 Section 11.4.1.1, Table 8).
1494const FETCH_SUBGROUP_ID_MODE_MASK: u64 = 0x03;
1495/// Subgroup ID mode 0b11: an explicit Subgroup ID field is on the wire.
1496const FETCH_SUBGROUP_ID_EXPLICIT: u64 = 0b11;
1497/// Table 9 flag: an Object ID Delta field is present.
1498const FETCH_OBJECT_ID_DELTA_BIT: u64 = 0x04;
1499/// Table 9 flag: a Group ID Delta field is present.
1500const FETCH_GROUP_ID_DELTA_BIT: u64 = 0x08;
1501/// Table 9 flag: a Publisher Priority field is present.
1502const FETCH_PRIORITY_BIT: u64 = 0x10;
1503/// Table 9 flag: a Properties field is present.
1504const FETCH_PROPERTIES_BIT: u64 = 0x20;
1505/// Table 9 flag: the Object's Forwarding Preference is Datagram, so it has no
1506/// Subgroup ID and the two low bits are to be ignored.
1507const FETCH_DATAGRAM_BIT: u64 = 0x40;
1508/// The largest Serialization Flags value whose bits are flags. Draft-21
1509/// Section 11.4.1: "When less than 128, the bits represent flags".
1510const FETCH_FLAGS_MAX: u64 = 0x7F;
1511/// Table 7: End of Non-Existent Range.
1512const FETCH_END_OF_NON_EXISTENT_RANGE: u64 = 0x8C;
1513/// Table 7: End of Unknown Range.
1514const FETCH_END_OF_UNKNOWN_RANGE: u64 = 0x10C;
1515/// Table 7: End of Timed-Out Range. New in draft-20.
1516///
1517/// The minimal encoding is the two-byte `82 0C`; it is not a single byte, and
1518/// cannot be, because Section 11.4.1 reserves values below 128 for the bit-flag
1519/// reading. Its low bits are `0x0C` — Group ID Delta present, Object ID Delta
1520/// present — exactly as the other two markers' are, so the two fields that
1521/// follow occupy the ordinary delta slots of Figure 28.
1522const FETCH_END_OF_TIMED_OUT_RANGE: u64 = 0x20C;
1523
1524/// What an End of Range indicator on a fetch stream asserts about the
1525/// Locations it covers, from draft-21 Section 11.4.1.2.
1526///
1527/// All three say that every Object with a Location between the previous
1528/// serialized Object and this one, inclusive, was not serialized. They differ
1529/// in why, and the three between them partition non-delivery: the publisher
1530/// knows the Objects are not there, it does not know either way, or it gave up
1531/// waiting. A subscriber can cache the first as a definitive gap and must not
1532/// cache the other two, so they cannot be collapsed.
1533#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1534pub enum FetchEndOfRange {
1535 /// Serialization Flags 0x8C. The Objects in the range do not exist.
1536 ///
1537 /// Section 11.4.1.2: "A publisher SHOULD NOT use End of Non-Existent Range
1538 /// in a FETCH response except to split a range of Objects that will not be
1539 /// serialized into those that are known not to exist and those with unknown
1540 /// or timed out status." Draft-19's wording of the same sentence did not
1541 /// have the "or timed out" half, because it had no marker for it.
1542 NonExistent,
1543 /// Serialization Flags 0x10C. The Objects in the range have unknown
1544 /// status.
1545 Unknown,
1546 /// Serialization Flags 0x20C. The Objects in the range timed out: the relay
1547 /// abandoned them because its `FILL_TIMEOUT` budget (Section 9.20.6) ran
1548 /// out. **New in draft-20.**
1549 ///
1550 /// This is where a fill timeout's output goes, and it is a behavioural
1551 /// change as much as a new value: draft-19 classified the same Objects as
1552 /// Unknown gaps, so a draft-19 receiver and a draft-21 receiver reading the
1553 /// same relay see different markers for the same event. `FILL_TIMEOUT = 0`
1554 /// means the subscriber wants only immediately-available Objects and the
1555 /// relay MUST report everything else this way; with the parameter absent,
1556 /// the relay waits an implementation-specific duration first.
1557 TimedOut,
1558}
1559
1560/// Which optional fields a Serialization Flags value puts on the wire.
1561///
1562/// Derived once by [`FetchObjectHeader::layout`] and then used by both
1563/// [`FetchObjectHeader::decode`] and [`FetchObjectHeader::encode`], so the two
1564/// cannot drift into disagreeing about a shape.
1565#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1566struct FetchObjectLayout {
1567 group_id_delta: bool,
1568 subgroup_id: bool,
1569 object_id_delta: bool,
1570 publisher_priority: bool,
1571 properties: bool,
1572}
1573
1574/// One Object on a draft-21 fetch stream, up to but not including its payload.
1575///
1576/// Draft-21 Section 11.4.1 rebuilt the fetch object. Earlier drafts wrote a
1577/// fixed set of fields on every Object; draft-21 writes a Serialization Flags
1578/// varint first, and the flags say which fields follow:
1579///
1580/// ```text
1581/// {
1582/// Serialization Flags (vi64),
1583/// [Group ID Delta (vi64),]
1584/// [Subgroup ID (vi64),]
1585/// [Object ID Delta (vi64),]
1586/// [Publisher Priority (8),]
1587/// [Properties (..),]
1588/// Object Payload Length (vi64),
1589/// [Object Payload (..),]
1590/// }
1591/// ```
1592///
1593/// Every field is optional except the flags and the payload length, and an
1594/// absent field means *the same as the previous Object's*, not "zero" — the
1595/// whole point of the layout is that a run of Objects in one group at one
1596/// priority costs one byte of framing each. This type therefore holds what is
1597/// on the wire and nothing more: the deltas, not the Group and Object IDs they
1598/// resolve to. Resolving them needs the previous Object on the same stream and
1599/// the FETCH's Group Order, neither of which a single Object header knows,
1600/// and Section 11.4.1 spells out the arithmetic a caller must apply:
1601///
1602/// - The first Object MUST carry both deltas, and they are the absolute
1603/// Group ID and Object ID.
1604/// - Later on, a Group ID Delta moves the group by `delta + 1` — forwards
1605/// under Ascending Group Order and backwards under Descending — and
1606/// restarts the Object ID from the Object ID Delta. With no Group ID
1607/// Delta, the group is unchanged and the Object ID Delta is added to the
1608/// previous Object's ID; with no Object ID Delta either, the Object ID is
1609/// the previous one plus one.
1610///
1611/// There is no Object Status field. Draft-21 Section 11.1.2 states that
1612/// Object Status is present only on Objects delivered via a subscription and
1613/// absent from Objects delivered via a FETCH, which is why this type has no
1614/// counterpart to [`SubgroupObject::object_status`] and why a zero
1615/// `payload_length` here is simply an Object with no payload.
1616///
1617/// Two Serialization Flags values name an End of Range indicator rather than
1618/// an Object; [`Self::end_of_range`] reports which, and Section 11.4.1.2 gives
1619/// the rules such a frame follows.
1620#[derive(Debug, Clone, PartialEq, Eq)]
1621pub struct FetchObjectHeader {
1622 /// The raw Serialization Flags value, kept whole rather than split into
1623 /// booleans because it is also the field that names an End of Range
1624 /// indicator, and because re-encoding must reproduce the value the
1625 /// publisher chose.
1626 pub serialization_flags: VarInt,
1627 /// Group ID Delta, present when the flags set 0x08. Its meaning depends on
1628 /// the Object's position in the stream and on the Group Order; see the
1629 /// type's own documentation.
1630 pub group_id_delta: Option<VarInt>,
1631 /// An explicit Subgroup ID, present only when the two low flag bits are
1632 /// 0b11 and the Datagram bit is clear. The other three modes derive the
1633 /// Subgroup ID from the previous Object and put nothing on the wire.
1634 pub subgroup_id: Option<VarInt>,
1635 /// Object ID Delta, present when the flags set 0x04. Absent means the
1636 /// previous Object's ID plus one.
1637 pub object_id_delta: Option<VarInt>,
1638 /// Publisher Priority, present when the flags set 0x10. Absent means the
1639 /// previous Object's priority.
1640 pub publisher_priority: Option<u8>,
1641 /// Raw properties bytes, excluding the byte-length prefix that precedes
1642 /// them on the wire, and `None` when the flags leave 0x20 clear.
1643 ///
1644 /// `Some(vec![])` and `None` are different frames: the first writes a zero
1645 /// length prefix, the second writes nothing at all. Opaque, like the
1646 /// property blocks on the subgroup and datagram forms — draft-21
1647 /// Section 11.4.1 defines the field as the Object Properties structure of
1648 /// Section 11.1.3, and these bytes are re-emitted verbatim.
1649 pub properties: Option<Vec<u8>>,
1650 /// Object Payload Length. Always on the wire; the payload itself follows
1651 /// this header and is not held here.
1652 pub payload_length: VarInt,
1653}
1654
1655impl FetchObjectHeader {
1656 /// The Serialization Flags as a plain integer.
1657 pub fn flags(&self) -> u64 {
1658 self.serialization_flags.into_inner()
1659 }
1660
1661 /// Which End of Range indicator this is, or `None` for an ordinary Object.
1662 ///
1663 /// Draft-21 Section 11.4.1, Table 7 gives the three indicators their own
1664 /// Serialization Flags values rather than a flag bit, so this is an
1665 /// equality test on the whole field and not a mask.
1666 ///
1667 /// An indicator uses the same two positions on the wire an ordinary Object
1668 /// uses for its deltas, so they are reached through [`Self::group_id_delta`]
1669 /// and [`Self::object_id_delta`], but they hold an absolute Group ID and
1670 /// Object ID. draft-21 Section 11.4.1.2: "the Group ID and Object ID fields are
1671 /// present". The delta rules of draft-21 Section 11.4.1.1 cover "an Object other
1672 /// than the first", and draft-21 Section 11.4.1 reads the flag bits only "When less
1673 /// than 128". draft-23 made the fields deltas ("Delta encode FETCH End of
1674 /// Range indicators").
1675 pub fn end_of_range(&self) -> Option<FetchEndOfRange> {
1676 match self.flags() {
1677 FETCH_END_OF_NON_EXISTENT_RANGE => Some(FetchEndOfRange::NonExistent),
1678 FETCH_END_OF_UNKNOWN_RANGE => Some(FetchEndOfRange::Unknown),
1679 FETCH_END_OF_TIMED_OUT_RANGE => Some(FetchEndOfRange::TimedOut),
1680 _ => None,
1681 }
1682 }
1683
1684 /// The two-bit Subgroup ID mode, `flags & 0x03`.
1685 ///
1686 /// `0b00` = the Subgroup ID is zero; `0b01` = the previous Object's
1687 /// Subgroup ID; `0b10` = the previous Object's Subgroup ID plus one;
1688 /// `0b11` = an explicit field is present. Draft-21 Section 11.4.1.1
1689 /// assigns all four, unlike the subgroup stream header's reserved
1690 /// `0b11`.
1691 ///
1692 /// Meaningless when [`Self::is_datagram`] is true: such an Object has no
1693 /// Subgroup ID and the section says the subscriber MUST ignore these bits.
1694 pub fn subgroup_id_mode(&self) -> u8 {
1695 (self.flags() & FETCH_SUBGROUP_ID_MODE_MASK) as u8
1696 }
1697
1698 /// `true` when the flags set 0x40, marking an Object whose Forwarding
1699 /// Preference is Datagram. Such an Object has no Subgroup ID at all, so
1700 /// the Subgroup ID mode bits carry no meaning and no Subgroup ID field is
1701 /// on the wire whatever they say.
1702 pub fn is_datagram(&self) -> bool {
1703 self.flags() & FETCH_DATAGRAM_BIT != 0
1704 }
1705
1706 /// Which optional fields `flags` puts on the wire, or
1707 /// [`CodecError::InvalidField`] if draft-21 does not define that
1708 /// Serialization Flags value.
1709 ///
1710 /// Section 11.4.1 defines the field in two pieces: values below 128 are a
1711 /// set of flags, and Table 7 adds exactly three values above that — 0x8C,
1712 /// 0x10C and draft-20's new 0x20C. "Any other value is a
1713 /// PROTOCOL_VIOLATION", which is what the error covers — every value at 128
1714 /// or above that is not one of the three.
1715 ///
1716 /// The three indicators get their layout from Section 11.4.1.2 rather than
1717 /// from their bits: "the Group ID and Object ID fields are present.
1718 /// Subgroup ID, Priority and Properties are not present." Their low bits
1719 /// happen to spell exactly that (all three are `0x0C` in the low seven
1720 /// bits: Group ID Delta and Object ID Delta set, Subgroup ID mode 0b00, no
1721 /// priority, no properties), but that is a property of the values the draft
1722 /// chose and not a rule, so the layout is taken from the section that
1723 /// states it.
1724 ///
1725 /// **`Object Payload Length` is present on a marker, encoded as 0.**
1726 /// Section 11.4.1.2 lists what is absent and does not name it, and Figure
1727 /// 28 marks it mandatory; the draft does not settle the case. This codec
1728 /// keeps the field, because omitting one a figure requires is what
1729 /// desynchronises a fetch stream, and a marker has nothing to put in it
1730 /// anyway. The layout below therefore differs from an ordinary object only
1731 /// in the optional fields.
1732 fn layout(flags: u64) -> Result<FetchObjectLayout, CodecError> {
1733 if flags == FETCH_END_OF_NON_EXISTENT_RANGE
1734 || flags == FETCH_END_OF_UNKNOWN_RANGE
1735 || flags == FETCH_END_OF_TIMED_OUT_RANGE
1736 {
1737 return Ok(FetchObjectLayout {
1738 group_id_delta: true,
1739 subgroup_id: false,
1740 object_id_delta: true,
1741 publisher_priority: false,
1742 properties: false,
1743 });
1744 }
1745 if flags > FETCH_FLAGS_MAX {
1746 return Err(CodecError::InvalidField);
1747 }
1748 Ok(FetchObjectLayout {
1749 group_id_delta: flags & FETCH_GROUP_ID_DELTA_BIT != 0,
1750 // An Object with the Datagram bit set has no Subgroup ID to write,
1751 // whatever the mode bits hold, so the field is absent.
1752 subgroup_id: flags & FETCH_DATAGRAM_BIT == 0
1753 && flags & FETCH_SUBGROUP_ID_MODE_MASK == FETCH_SUBGROUP_ID_EXPLICIT,
1754 object_id_delta: flags & FETCH_OBJECT_ID_DELTA_BIT != 0,
1755 publisher_priority: flags & FETCH_PRIORITY_BIT != 0,
1756 properties: flags & FETCH_PROPERTIES_BIT != 0,
1757 })
1758 }
1759
1760 /// Decode one fetch object header, leaving the payload in `buf`.
1761 ///
1762 /// Errors with [`CodecError::InvalidField`] for a Serialization Flags
1763 /// value draft-21 Section 11.4.1 does not define, and with
1764 /// [`CodecError::UnexpectedEnd`] or a varint error when the buffer runs
1765 /// out mid-field.
1766 ///
1767 /// The flags are validated before any field is read, because they are what
1768 /// says where the fields are: decoding an undefined value would mean
1769 /// picking a layout the draft never described and then consuming a
1770 /// plausible number of bytes under it.
1771 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1772 let serialization_flags = VarInt::decode_moqt::<Wire>(buf)?;
1773 let layout = Self::layout(serialization_flags.into_inner())?;
1774
1775 let group_id_delta =
1776 layout.group_id_delta.then(|| VarInt::decode_moqt::<Wire>(buf)).transpose()?;
1777 let subgroup_id =
1778 layout.subgroup_id.then(|| VarInt::decode_moqt::<Wire>(buf)).transpose()?;
1779 let object_id_delta =
1780 layout.object_id_delta.then(|| VarInt::decode_moqt::<Wire>(buf)).transpose()?;
1781
1782 let publisher_priority = if layout.publisher_priority {
1783 if buf.remaining() < 1 {
1784 return Err(CodecError::UnexpectedEnd);
1785 }
1786 Some(buf.get_u8())
1787 } else {
1788 None
1789 };
1790
1791 let properties = if layout.properties {
1792 let len = VarInt::decode_moqt::<Wire>(buf)?.into_inner() as usize;
1793 Some(crate::types::read_bytes(buf, len)?)
1794 } else {
1795 None
1796 };
1797
1798 let payload_length = VarInt::decode_moqt::<Wire>(buf)?;
1799
1800 Ok(FetchObjectHeader {
1801 serialization_flags,
1802 group_id_delta,
1803 subgroup_id,
1804 object_id_delta,
1805 publisher_priority,
1806 properties,
1807 payload_length,
1808 })
1809 }
1810
1811 /// Serialize the header, refusing one whose fields disagree with its own
1812 /// Serialization Flags.
1813 ///
1814 /// Errors with [`CodecError::InvalidField`] when the flags are a value
1815 /// draft-21 does not define, and when any optional field is present while
1816 /// its flag is clear or absent while its flag is set. Checked before any
1817 /// byte is written, so a refused header leaves `buf` untouched.
1818 ///
1819 /// Fallible for the same reason [`DatagramHeader::encode_checked`] is: the
1820 /// flags decide the framing, so writing them as the authority and dropping
1821 /// whatever they do not cover is silent data loss. A Group ID Delta held
1822 /// with the 0x08 bit clear is not written, the reader takes the Object as
1823 /// belonging to the previous Object's group, and nothing about the
1824 /// resulting stream looks wrong. The mirror case is worse: a flag set with
1825 /// no value behind it would have to invent one, and an invented Object ID
1826 /// Delta of zero is a real Object ID.
1827 ///
1828 /// Nothing here checks the deltas against the previous Object — that no
1829 /// Object other than the first may reference a prior Object that does not
1830 /// exist, for one. A single header has no way to see that, and this type
1831 /// deliberately does not carry stream state.
1832 pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1833 let layout = Self::layout(self.flags())?;
1834 if layout.group_id_delta != self.group_id_delta.is_some()
1835 || layout.subgroup_id != self.subgroup_id.is_some()
1836 || layout.object_id_delta != self.object_id_delta.is_some()
1837 || layout.publisher_priority != self.publisher_priority.is_some()
1838 || layout.properties != self.properties.is_some()
1839 {
1840 return Err(CodecError::InvalidField);
1841 }
1842
1843 self.serialization_flags.encode_moqt::<Wire>(buf);
1844 // Wire order, from Figure 28: Group ID Delta, then Subgroup ID, then
1845 // Object ID Delta. The `layout` check above has already established
1846 // that exactly the fields the flags call for are present, so whichever
1847 // of the three are `Some` are the ones that belong here.
1848 for field in
1849 [self.group_id_delta, self.subgroup_id, self.object_id_delta].into_iter().flatten()
1850 {
1851 field.encode_moqt::<Wire>(buf);
1852 }
1853 if let Some(priority) = self.publisher_priority {
1854 buf.put_u8(priority);
1855 }
1856 if let Some(properties) = &self.properties {
1857 VarInt::from_usize(properties.len()).encode_moqt::<Wire>(buf);
1858 buf.put_slice(properties);
1859 }
1860 self.payload_length.encode_moqt::<Wire>(buf);
1861 Ok(())
1862 }
1863}
1864
1865/// The order a FETCH response's Groups arrive in, which decides how a Group ID
1866/// Delta is applied.
1867///
1868/// Draft-21 Section 11.4.1.1: "If the Group Order is Ascending, the Group ID is
1869/// the prior Object's Group ID plus the Group ID Delta + 1. If the Group Order
1870/// is Descending, the Group ID is the prior Object's Group ID minus the (Group
1871/// ID Delta + 1)."
1872///
1873/// The order is not on the data stream — it is settled by the control exchange
1874/// that opened the FETCH, whose GROUP_ORDER parameter (Section 9.20.9) spells
1875/// Ascending 0x1 and Descending 0x2 — so [`FetchObjectReader`] has to be told
1876/// which one it is reading. Getting it wrong does not fail to parse: it decodes
1877/// every Object under a Group ID that walks the wrong way.
1878#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1879pub enum GroupOrder {
1880 /// Group IDs increase: a delta adds to the prior Group ID.
1881 Ascending,
1882 /// Group IDs decrease: a delta subtracts from the prior Group ID.
1883 Descending,
1884}
1885
1886/// One frame from a FETCH stream with its delta-encoded fields resolved.
1887///
1888/// The header is kept alongside the resolved values so that a caller can
1889/// forward the frame's bytes unchanged while acting on what they mean.
1890#[derive(Debug, Clone, PartialEq, Eq)]
1891pub struct FetchObject {
1892 /// The frame as it appeared on the wire.
1893 pub header: FetchObjectHeader,
1894 /// Resolved absolute Group ID. On an End of Range marker, the Group ID of
1895 /// the Location the marker names.
1896 pub group_id: u64,
1897 /// Resolved Subgroup ID. `None` for an End of Range marker, which has
1898 /// none, and for an Object whose forwarding preference is Datagram.
1899 pub subgroup_id: Option<u64>,
1900 /// Resolved absolute Object ID. On an End of Range marker, the Object ID of
1901 /// the Location the marker names.
1902 pub object_id: u64,
1903 /// The Publisher Priority in force for this frame, whether this frame wrote
1904 /// it or an earlier one did, and `None` while no frame has written one.
1905 ///
1906 /// An End of Range marker carries no Priority field of its own, so what it
1907 /// reports is the one still in force from the last Object before it —
1908 /// Section 11.4.1.2: "Prior Priority: The Priority from the last actual
1909 /// Object before the End of Range indicator."
1910 ///
1911 /// The draft-21 fallback for a subscription that never stated a priority is
1912 /// left to the caller rather than substituted here, so that "no frame has
1913 /// said" stays distinguishable from "a frame said 128".
1914 pub publisher_priority: Option<u8>,
1915}
1916
1917/// Resolves the delta-encoded fields of the frames on one FETCH stream.
1918///
1919/// Draft-21 Section 11.4.1.1 defines nearly every field of a fetch frame
1920/// against "the prior Object", so no frame after the first can be understood on
1921/// its own. This holds what the frames so far established, in the two parts the
1922/// draft keeps separate: Section 11.4.1.2 says that after an End of Range
1923/// marker the prior Group ID and Object ID are the marker's, while the prior
1924/// Subgroup ID and Priority are still "from the last actual Object before the
1925/// End of Range indicator".
1926///
1927/// Every rule the section answers with a PROTOCOL_VIOLATION is refused here
1928/// with [`CodecError::InvalidField`]: a first Object that references fields no
1929/// prior Object established, a Subgroup ID or Priority inherited when there is
1930/// none to inherit, and an arithmetic result outside the 64-bit range.
1931#[derive(Debug, Clone)]
1932pub struct FetchObjectReader {
1933 group_order: GroupOrder,
1934 /// Group ID and Object ID of the last frame, marker or Object.
1935 prior_location: Option<(u64, u64)>,
1936 /// Subgroup ID of the last actual Object that had one.
1937 prior_subgroup_id: Option<u64>,
1938 /// Publisher Priority of the last actual Object.
1939 prior_publisher_priority: Option<u8>,
1940 /// The Prior gap conditions the stream's Objects show.
1941 gaps: FetchGaps,
1942}
1943
1944impl FetchObjectReader {
1945 /// A reader for a stream whose Groups arrive in `group_order`.
1946 pub fn new(group_order: GroupOrder) -> Self {
1947 Self {
1948 group_order,
1949 prior_location: None,
1950 prior_subgroup_id: None,
1951 prior_publisher_priority: None,
1952 gaps: FetchGaps::new(),
1953 }
1954 }
1955
1956 /// Decode the next frame's header and resolve its fields.
1957 ///
1958 /// Consumes the header only. The Object Payload is
1959 /// `header.payload_length` bytes and stays in `buf`, so a caller that
1960 /// forwards payloads never copies them and one that ignores them can skip.
1961 ///
1962 /// Errors with [`CodecError::InvalidField`] on every rule
1963 /// Section 11.4.1.1 states:
1964 ///
1965 /// - "The first Object MUST include a Group ID Delta and Object ID Delta,
1966 /// and these values are the absolute Group ID and Object ID. If the first
1967 /// Object in the FETCH response uses a flag that references fields in the
1968 /// prior Object, the Subscriber MUST close the session with a
1969 /// PROTOCOL_VIOLATION." Each such flag is refused where it is read, so
1970 /// the reason survives: a missing delta, an inherited Priority and an
1971 /// inherited Subgroup ID are three different frames, all of them
1972 /// referencing an Object that does not exist.
1973 /// - "If the computed Group ID would be less than 0 or greater than
1974 /// 2^64-1, the Subscriber MUST close the Session with error
1975 /// 'PROTOCOL_VIOLATION'" — the descending and ascending ends of the same
1976 /// rule.
1977 /// - "If the computed Object ID would be greater than 2^64-1, the
1978 /// Subscriber MUST close the Session with error 'PROTOCOL_VIOLATION'."
1979 ///
1980 /// Refuses, with [`CodecError::MalformedTrack`], the Prior gap conditions an
1981 /// Object shows alone or after the Objects before it on the stream, as
1982 /// [`FetchGaps`] does, and a property block that does not parse as
1983 /// [`crate::object_properties::parse_object_properties`] does.
1984 pub fn read_object_header(&mut self, buf: &mut impl Buf) -> Result<FetchObject, CodecError> {
1985 let object = self.read_frame_header(buf)?;
1986 if object.header.end_of_range().is_none() {
1987 self.gaps.check(
1988 object.group_id,
1989 object.object_id,
1990 object.header.properties.as_deref().unwrap_or_default(),
1991 )?;
1992 }
1993 Ok(object)
1994 }
1995
1996 /// [`Self::read_object_header`] without the Prior gap conditions and without reading
1997 /// inside the property block, for the facade's frame and meta reads, which
1998 /// a forwarder uses.
1999 pub(crate) fn read_frame_header(
2000 &mut self,
2001 buf: &mut impl Buf,
2002 ) -> Result<FetchObject, CodecError> {
2003 let header = FetchObjectHeader::decode(buf)?;
2004
2005 // An End of Range marker carries no Subgroup ID and no Priority, so
2006 // those two are resolved here rather than by the rules below: Section
2007 // 11.4.1.2 says the prior Subgroup ID and prior Priority for whatever
2008 // follows come "from the last actual Object before the End of Range
2009 // indicator", which means a marker neither states them nor disturbs
2010 // them, and a marker arriving before any Object has none to report.
2011 //
2012 // Its two present fields are the absolute Location; see
2013 // [`FetchObjectHeader::end_of_range`].
2014 if header.end_of_range().is_some() {
2015 let group_id = header.group_id_delta.ok_or(CodecError::InvalidField)?.into_inner();
2016 let object_id = header.object_id_delta.ok_or(CodecError::InvalidField)?.into_inner();
2017 self.prior_location = Some((group_id, object_id));
2018 return Ok(FetchObject {
2019 header,
2020 group_id,
2021 subgroup_id: None,
2022 object_id,
2023 publisher_priority: self.prior_publisher_priority,
2024 });
2025 }
2026
2027 let group_id = match (self.prior_location, header.group_id_delta) {
2028 // The first object's delta is its absolute Group ID.
2029 (None, Some(delta)) => delta.into_inner(),
2030 (None, None) => return Err(CodecError::InvalidField),
2031 (Some((prior_group, _)), None) => prior_group,
2032 (Some((prior_group, _)), Some(delta)) => {
2033 let delta = delta.into_inner();
2034 match self.group_order {
2035 GroupOrder::Ascending => prior_group
2036 .checked_add(delta)
2037 .and_then(|v| v.checked_add(1))
2038 .ok_or(CodecError::InvalidField)?,
2039 GroupOrder::Descending => prior_group
2040 .checked_sub(delta)
2041 .and_then(|v| v.checked_sub(1))
2042 .ok_or(CodecError::InvalidField)?,
2043 }
2044 }
2045 };
2046
2047 let object_id =
2048 match (self.prior_location, header.group_id_delta.is_some(), header.object_id_delta) {
2049 // A Group ID Delta restarts the Object ID from its own delta,
2050 // which is why a new group does not continue the previous
2051 // group's numbering.
2052 (_, true, Some(delta)) => delta.into_inner(),
2053 (Some((_, prior_object)), false, Some(delta)) => {
2054 prior_object.checked_add(delta.into_inner()).ok_or(CodecError::InvalidField)?
2055 }
2056 (Some((_, prior_object)), _, None) => {
2057 prior_object.checked_add(1).ok_or(CodecError::InvalidField)?
2058 }
2059 (None, _, _) => return Err(CodecError::InvalidField),
2060 };
2061
2062 let subgroup_id = if header.is_datagram() {
2063 None
2064 } else {
2065 Some(match header.subgroup_id_mode() {
2066 0x00 => 0,
2067 0x01 => self.prior_subgroup_id.ok_or(CodecError::InvalidField)?,
2068 0x02 => self
2069 .prior_subgroup_id
2070 .ok_or(CodecError::InvalidField)?
2071 .checked_add(1)
2072 .ok_or(CodecError::InvalidField)?,
2073 // Mode 0b11, the only value left: the field is on the wire.
2074 _ => header.subgroup_id.ok_or(CodecError::InvalidField)?.into_inner(),
2075 })
2076 };
2077
2078 let publisher_priority = match header.publisher_priority {
2079 Some(p) => p,
2080 None => self.prior_publisher_priority.ok_or(CodecError::InvalidField)?,
2081 };
2082
2083 self.prior_location = Some((group_id, object_id));
2084 // A Datagram-forwarded object has no Subgroup ID to leave behind, so it
2085 // does not clear the running one: the object after it inherits from the
2086 // last object that had one.
2087 if let Some(subgroup_id) = subgroup_id {
2088 self.prior_subgroup_id = Some(subgroup_id);
2089 }
2090 self.prior_publisher_priority = Some(publisher_priority);
2091
2092 Ok(FetchObject {
2093 header,
2094 group_id,
2095 subgroup_id,
2096 object_id,
2097 publisher_priority: Some(publisher_priority),
2098 })
2099 }
2100}
2101
2102/// Re-encodes resolved fetch frames onto one FETCH stream.
2103///
2104/// The exact inverse of [`FetchObjectReader`], and it exists for one caller:
2105/// something that has read a stream and is writing a different stream from the
2106/// same frames. Removing a frame changes what the frames after it are encoded
2107/// *against*, and draft-21 Section 11.4.1.1 defines nearly every field against
2108/// "the prior Object", so the survivor that follows a removed run cannot keep
2109/// its original bytes. What has to change is not one field: an Object that
2110/// carried no Group ID Delta because it shared its predecessor's group needs
2111/// one once that predecessor is gone, so a field appears and a flag bit with
2112/// it.
2113///
2114/// # Why this is not a general encoder
2115///
2116/// Every frame it writes came off a stream, so the caller already holds the
2117/// frame's own [`FetchObjectHeader`] alongside the resolved values. That header
2118/// is used as the preference: wherever the original shape still encodes the
2119/// same meaning against the new predecessor, it is kept, so a stream with
2120/// nothing removed from it is reproduced byte for byte. Only where the original
2121/// shape would now decode to something else is a different one chosen. An
2122/// encoder built from the resolved values alone could not do that — it would
2123/// have to invent a canonical form and would rewrite every frame on a stream
2124/// that needed no rewriting at all.
2125///
2126/// # What it refuses
2127///
2128/// [`CodecError::InvalidField`] where no encoding exists rather than picking
2129/// one: a Group ID that moves against the FETCH's Group Order, an Object ID
2130/// that does not advance, an Object with neither a Subgroup ID nor the Datagram
2131/// bit, and the arithmetic overflows. Each of these is a frame this writer was
2132/// handed that no draft-21 stream could carry, and inventing a value for it
2133/// would put a different Object on the wire than the one it was given.
2134#[derive(Debug, Clone)]
2135pub struct FetchObjectWriter {
2136 group_order: GroupOrder,
2137 /// Group ID and Object ID of the last frame written, marker or Object.
2138 prior_location: Option<(u64, u64)>,
2139 /// Subgroup ID of the last actual Object written that had one.
2140 prior_subgroup_id: Option<u64>,
2141 /// Publisher Priority of the last actual Object written.
2142 prior_publisher_priority: Option<u8>,
2143}
2144
2145impl FetchObjectWriter {
2146 /// A writer for a stream whose Groups are being written in `group_order`.
2147 ///
2148 /// The order has to match the one the FETCH was opened with, for the same
2149 /// reason [`FetchObjectReader::new`] takes it: it decides whether a Group
2150 /// ID Delta adds or subtracts, and it is not on the data stream.
2151 pub fn new(group_order: GroupOrder) -> Self {
2152 Self {
2153 group_order,
2154 prior_location: None,
2155 prior_subgroup_id: None,
2156 prior_publisher_priority: None,
2157 }
2158 }
2159
2160 /// The header that encodes `frame` against everything written so far.
2161 ///
2162 /// Does not advance the writer — [`Self::write_object_header`] is the call
2163 /// that does both. Separated so that a caller can measure the bytes a
2164 /// re-encode would take before committing to it.
2165 ///
2166 /// # Errors
2167 ///
2168 /// [`CodecError::InvalidField`] for a frame that cannot be encoded against
2169 /// the current predecessor; see the type's own documentation for the list.
2170 pub fn header_for(&self, frame: &FetchObject) -> Result<FetchObjectHeader, CodecError> {
2171 let original = &frame.header;
2172
2173 // An End of Range marker states its Location outright and inherits
2174 // nothing, so its two fields are the same whatever precedes it; see
2175 // [`FetchObjectHeader::end_of_range`].
2176 if original.end_of_range().is_some() {
2177 return Ok(FetchObjectHeader {
2178 serialization_flags: original.serialization_flags,
2179 group_id_delta: Some(VarInt::from_u64(frame.group_id)?),
2180 subgroup_id: None,
2181 object_id_delta: Some(VarInt::from_u64(frame.object_id)?),
2182 publisher_priority: None,
2183 properties: None,
2184 payload_length: original.payload_length,
2185 });
2186 }
2187
2188 let (group_id_delta, object_id_delta) = self.identity_fields(frame, original)?;
2189 let (subgroup_mode, subgroup_id) = self.subgroup_field(frame, original)?;
2190 let publisher_priority = self.priority_field(frame, original)?;
2191
2192 let mut flags = subgroup_mode;
2193 if original.is_datagram() {
2194 flags |= FETCH_DATAGRAM_BIT;
2195 }
2196 if group_id_delta.is_some() {
2197 flags |= FETCH_GROUP_ID_DELTA_BIT;
2198 }
2199 if object_id_delta.is_some() {
2200 flags |= FETCH_OBJECT_ID_DELTA_BIT;
2201 }
2202 if publisher_priority.is_some() {
2203 flags |= FETCH_PRIORITY_BIT;
2204 }
2205 if original.properties.is_some() {
2206 flags |= FETCH_PROPERTIES_BIT;
2207 }
2208
2209 Ok(FetchObjectHeader {
2210 serialization_flags: VarInt::from_u64(flags)?,
2211 group_id_delta,
2212 subgroup_id,
2213 object_id_delta,
2214 publisher_priority,
2215 properties: original.properties.clone(),
2216 payload_length: original.payload_length,
2217 })
2218 }
2219
2220 /// The Group ID Delta and Object ID Delta fields, as this predecessor needs
2221 /// them.
2222 ///
2223 /// Presence is forced by the frame rather than chosen: a group that differs
2224 /// from the predecessor's has to be stated, and one that matches has to be
2225 /// left off, since a delta of zero means the next group along and not this
2226 /// one. Only the Object ID Delta has a choice to make, and it is made in
2227 /// favour of the shape the frame arrived in.
2228 fn identity_fields(
2229 &self,
2230 frame: &FetchObject,
2231 original: &FetchObjectHeader,
2232 ) -> Result<(Option<VarInt>, Option<VarInt>), CodecError> {
2233 let Some((prior_group, prior_object)) = self.prior_location else {
2234 // Section 11.4.1.1: "The first Object MUST include a Group ID Delta
2235 // and Object ID Delta, and these values are the absolute Group ID
2236 // and Object ID."
2237 return Ok((
2238 Some(VarInt::from_u64(frame.group_id)?),
2239 Some(VarInt::from_u64(frame.object_id)?),
2240 ));
2241 };
2242
2243 if frame.group_id != prior_group {
2244 // A Group ID Delta moves the group by delta + 1, forwards under
2245 // Ascending and backwards under Descending, and when an Object ID
2246 // Delta accompanies it the Object ID is that delta outright rather
2247 // than an advance on the predecessor.
2248 let step = match self.group_order {
2249 GroupOrder::Ascending => frame.group_id.checked_sub(prior_group),
2250 GroupOrder::Descending => prior_group.checked_sub(frame.group_id),
2251 };
2252 let delta = step.and_then(|s| s.checked_sub(1)).ok_or(CodecError::InvalidField)?;
2253
2254 // Omitting the Object ID Delta across a group boundary is legal and
2255 // is a byte shorter. Section 11.4.1.1: "If Object ID Delta is not
2256 // present, the Object ID is the prior Object's ID plus one,
2257 // REGARDLESS OF WHICH GROUP IT BELONGS TO." So an Object that
2258 // continues the numbering into a new group encodes without one —
2259 // the Object ID does not restart at the group boundary unless a
2260 // delta says so.
2261 //
2262 // Gated on the frame's own framing, like the same-group case below,
2263 // so re-emitting a stream reproduces the publisher's bytes instead
2264 // of silently rewriting the shorter form into the longer one.
2265 if original.object_id_delta.is_none()
2266 && frame.object_id == prior_object.wrapping_add(1)
2267 && prior_object != u64::MAX
2268 {
2269 return Ok((Some(VarInt::from_u64(delta)?), None));
2270 }
2271
2272 return Ok((Some(VarInt::from_u64(delta)?), Some(VarInt::from_u64(frame.object_id)?)));
2273 }
2274
2275 // Same group. The Object ID is the predecessor's plus the delta, or
2276 // plus one when no delta is written, so an Object that does not advance
2277 // has no encoding at all.
2278 let advance = frame.object_id.checked_sub(prior_object).ok_or(CodecError::InvalidField)?;
2279 if advance == 0 {
2280 return Err(CodecError::InvalidField);
2281 }
2282 if advance == 1 && original.object_id_delta.is_none() {
2283 return Ok((None, None));
2284 }
2285 Ok((None, Some(VarInt::from_u64(advance)?)))
2286 }
2287
2288 /// The Subgroup ID mode bits and the explicit field, if one is needed.
2289 ///
2290 /// The frame's own mode is tried first, so a run of Objects that inherited
2291 /// their Subgroup ID keeps inheriting it and its bytes do not move. Only
2292 /// when the predecessor changed under it does a different mode get chosen,
2293 /// and then the cheapest one that says the right number.
2294 fn subgroup_field(
2295 &self,
2296 frame: &FetchObject,
2297 original: &FetchObjectHeader,
2298 ) -> Result<(u64, Option<VarInt>), CodecError> {
2299 // Section 11.4.1.1 has the subscriber ignore these bits on a
2300 // Datagram-forwarded Object, and no field is on the wire whatever they
2301 // say, so the frame's own bits are carried across untouched.
2302 if original.is_datagram() {
2303 return Ok((original.flags() & FETCH_SUBGROUP_ID_MODE_MASK, None));
2304 }
2305
2306 let subgroup_id = frame.subgroup_id.ok_or(CodecError::InvalidField)?;
2307 let inherits = self.prior_subgroup_id == Some(subgroup_id);
2308 let successor =
2309 self.prior_subgroup_id.is_some_and(|p| p.checked_add(1) == Some(subgroup_id));
2310
2311 // The frame's own mode, kept when it still names this number.
2312 let kept = match original.flags() & FETCH_SUBGROUP_ID_MODE_MASK {
2313 0x00 if subgroup_id == 0 => Some((0x00, None)),
2314 0x01 if inherits => Some((0x01, None)),
2315 0x02 if successor => Some((0x02, None)),
2316 FETCH_SUBGROUP_ID_EXPLICIT => Some((FETCH_SUBGROUP_ID_EXPLICIT, Some(subgroup_id))),
2317 _ => None,
2318 };
2319 let (mode, explicit) = match kept {
2320 Some(pair) => pair,
2321 None if subgroup_id == 0 => (0x00, None),
2322 None if inherits => (0x01, None),
2323 None if successor => (0x02, None),
2324 None => (FETCH_SUBGROUP_ID_EXPLICIT, Some(subgroup_id)),
2325 };
2326 Ok((mode, explicit.map(VarInt::from_u64).transpose()?))
2327 }
2328
2329 /// The Publisher Priority field, or `None` when the predecessor already
2330 /// carries it.
2331 ///
2332 /// Written whenever the frame wrote one, so a publisher that stated a
2333 /// priority on every Object keeps its bytes, and written anyway when the
2334 /// predecessor's differs or when there is no predecessor to inherit from.
2335 fn priority_field(
2336 &self,
2337 frame: &FetchObject,
2338 original: &FetchObjectHeader,
2339 ) -> Result<Option<u8>, CodecError> {
2340 let priority = frame.publisher_priority.ok_or(CodecError::InvalidField)?;
2341 if original.publisher_priority.is_some() || self.prior_publisher_priority != Some(priority)
2342 {
2343 return Ok(Some(priority));
2344 }
2345 Ok(None)
2346 }
2347
2348 /// Encode `frame` against everything written so far and advance.
2349 ///
2350 /// Writes the header only. The payload is `frame.header.payload_length`
2351 /// bytes and is the caller's to copy, unchanged — nothing about it depends
2352 /// on what preceded the Object.
2353 ///
2354 /// # Errors
2355 ///
2356 /// [`CodecError::InvalidField`] for a frame with no encoding against the
2357 /// current predecessor. The writer is left untouched when this happens, so
2358 /// a caller that gives up on one frame and carries on with the next is
2359 /// writing against the same predecessor it thought it was.
2360 pub fn write_object_header(
2361 &mut self,
2362 frame: &FetchObject,
2363 out: &mut impl BufMut,
2364 ) -> Result<FetchObjectHeader, CodecError> {
2365 let header = self.header_for(frame)?;
2366 header.encode(out)?;
2367 self.advance(frame);
2368 Ok(header)
2369 }
2370
2371 /// Record `frame` as the predecessor of whatever is written next.
2372 ///
2373 /// Split from the write so that a caller re-emitting bytes it already holds
2374 /// can advance without producing a header twice — which is what happens
2375 /// whenever the framing a frame arrived in still encodes the same meaning
2376 /// against the frame before it, and is why this is public.
2377 pub fn advance(&mut self, frame: &FetchObject) {
2378 self.prior_location = Some((frame.group_id, frame.object_id));
2379 // Mirrors the reader: a Datagram-forwarded Object leaves no Subgroup ID
2380 // behind, so the running one survives it.
2381 if let Some(subgroup_id) = frame.subgroup_id {
2382 self.prior_subgroup_id = Some(subgroup_id);
2383 }
2384 if frame.header.end_of_range().is_none() {
2385 if let Some(priority) = frame.publisher_priority {
2386 self.prior_publisher_priority = Some(priority);
2387 }
2388 }
2389 }
2390}
2391
2392#[cfg(test)]
2393mod tests {
2394 use super::*;
2395
2396 /// Canonically encoded subgroup stream vectors from
2397 /// `test-vectors/transport/draft21/codec/data-streams/subgroup.json`.
2398 /// `subgroup-explicit-subgroup-id` is omitted: it encodes group_id 100 as a
2399 /// two-byte varint, which does not survive a minimal-width re-encode.
2400 const VECTORS: &[&str] = &[
2401 // subgroup-single-object
2402 "100100800004deadbeef",
2403 // subgroup-two-objects
2404 "100100800004deadbeef0002cafe",
2405 // subgroup-no-priority
2406 "3001000004deadbeef",
2407 // subgroup-with-extensions
2408 "11010080000004deadbeef",
2409 // subgroup-end-of-group
2410 "180105800004deadbeef",
2411 // subgroup-id-mode-01
2412 "120100800504deadbeef",
2413 // subgroup-with-object-properties
2414 "1101058001043c02020104deadbeef",
2415 // subgroup-object-status-end-of-group
2416 "100100800004deadbeef000003",
2417 // subgroup-object-status-end-of-track
2418 "10010080000004",
2419 // subgroup-properties-two-objects-empty
2420 "11010080000004deadbeef000002cafe",
2421 // subgroup-properties-two-objects-nonempty
2422 "1101058000023c0204deadbeef00023c0202cafe",
2423 // subgroup-first-object-bit
2424 "500100800004deadbeef",
2425 // subgroup-first-object-and-end-of-group
2426 "580102800002cafe",
2427 ];
2428
2429 fn vi(v: u64) -> VarInt {
2430 VarInt::from_u64_moqt(v)
2431 }
2432
2433 fn hex(s: &str) -> Vec<u8> {
2434 (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
2435 }
2436
2437 /// Decode a whole subgroup stream: the header, then every object up to
2438 /// the end of the buffer.
2439 fn decode_all(bytes: &[u8]) -> (SubgroupHeader, Vec<SubgroupObject>) {
2440 let mut cursor = bytes;
2441 let header = SubgroupHeader::decode(&mut cursor)
2442 .unwrap_or_else(|e| panic!("header decode failed: {e:?}"));
2443 let mut reader = SubgroupObjectReader::new(&header);
2444 let mut objects = Vec::new();
2445 while cursor.has_remaining() {
2446 objects.push(
2447 reader
2448 .read_object(&mut cursor)
2449 .unwrap_or_else(|e| panic!("object {} decode failed: {e:?}", objects.len())),
2450 );
2451 }
2452 (header, objects)
2453 }
2454
2455 fn encode_all(header: &SubgroupHeader, objects: &[SubgroupObject]) -> Vec<u8> {
2456 let mut buf = Vec::new();
2457 header.encode(&mut buf);
2458 let mut writer = SubgroupObjectReader::new(header);
2459 for o in objects {
2460 writer.write_object(o, &mut buf).unwrap_or_else(|e| panic!("write failed: {e:?}"));
2461 }
2462 buf
2463 }
2464
2465 fn object(id: u64, extensions: Vec<u8>, payload: Vec<u8>) -> SubgroupObject {
2466 SubgroupObject {
2467 object_id: vi(id),
2468 extension_headers: extensions,
2469 payload_length: vi(payload.len() as u64),
2470 object_status: None,
2471 payload,
2472 }
2473 }
2474
2475 // ── Object ID deltas ────────────────────────────────────
2476
2477 #[test]
2478 fn two_objects_with_properties_have_distinct_ids() {
2479 // Vector `subgroup-properties-two-objects-empty`: two objects, each
2480 // carrying an empty properties block and a delta of 0. The delta is
2481 // biased by one whether or not the properties bit is set, so the IDs
2482 // are 0 and 1 — not 0 and 0.
2483 let bytes = hex("11010080000004deadbeef000002cafe");
2484 let (header, objects) = decode_all(&bytes);
2485 assert!(header.has_properties());
2486 assert_eq!(objects.len(), 2);
2487 assert_eq!(objects[0].object_id.into_inner(), 0);
2488 assert_eq!(objects[1].object_id.into_inner(), 1);
2489 assert_eq!(objects[0].payload, hex("deadbeef"));
2490 assert_eq!(objects[1].payload, hex("cafe"));
2491 assert!(objects.iter().all(|o| o.extension_headers.is_empty()));
2492 }
2493
2494 #[test]
2495 fn deltas_resolve_sparse_ids() {
2496 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2497 let objects: Vec<_> =
2498 [3u64, 4, 40].iter().map(|&id| object(id, vec![], vec![0xAA, id as u8])).collect();
2499 let (_, decoded) = decode_all(&encode_all(&header, &objects));
2500 let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
2501 assert_eq!(ids, vec![3, 4, 40]);
2502 }
2503
2504 #[test]
2505 fn write_rejects_non_increasing_ids() {
2506 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2507 let mut writer = SubgroupObjectReader::new(&header);
2508 let mut buf = Vec::new();
2509 writer.write_object(&object(7, vec![], vec![0x01]), &mut buf).unwrap();
2510 for id in [7u64, 6, 0] {
2511 let err = writer.write_object(&object(id, vec![], vec![0x01]), &mut buf).unwrap_err();
2512 assert!(matches!(err, CodecError::InvalidField), "id {id} gave {err:?}");
2513 }
2514 }
2515
2516 #[test]
2517 fn eliding_an_object_renumbers_its_successor() {
2518 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2519 let all: Vec<_> = (0..5u64).map(|id| object(id, vec![], vec![id as u8])).collect();
2520 for elided in 0..5u64 {
2521 let kept: Vec<_> =
2522 all.iter().filter(|o| o.object_id.into_inner() != elided).cloned().collect();
2523 let (_, decoded) = decode_all(&encode_all(&header, &kept));
2524 let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
2525 let expected: Vec<u64> = (0..5u64).filter(|&i| i != elided).collect();
2526 assert_eq!(ids, expected, "eliding object {elided}");
2527 }
2528 }
2529
2530 // ── Properties blocks ──────────────────────────────
2531
2532 #[test]
2533 fn properties_blob_excludes_its_length_prefix() {
2534 // Vector `subgroup-properties-two-objects-nonempty`: each
2535 // object carries a two-byte block, so the blob is those two bytes
2536 // with the `02` length prefix stripped.
2537 let bytes = hex("1101058000023c0204deadbeef00023c0202cafe");
2538 let (_, objects) = decode_all(&bytes);
2539 assert_eq!(objects.len(), 2);
2540 assert_eq!(objects[0].object_id.into_inner(), 0);
2541 assert_eq!(objects[1].object_id.into_inner(), 1);
2542 assert_eq!(objects[0].extension_headers, hex("3c02"));
2543 assert_eq!(objects[1].extension_headers, hex("3c02"));
2544 assert_eq!(objects[0].payload, hex("deadbeef"));
2545 assert_eq!(objects[1].payload, hex("cafe"));
2546 }
2547
2548 /// Read every object on a subgroup stream, returning the first error.
2549 fn first_error(stream: &str) -> Option<CodecError> {
2550 let bytes = hex(stream);
2551 let mut cursor = &bytes[..];
2552 let header = SubgroupHeader::decode(&mut cursor).unwrap();
2553 let mut reader = SubgroupObjectReader::new(&header);
2554 while cursor.has_remaining() {
2555 if let Err(e) = reader.read_object(&mut cursor) {
2556 return Some(e);
2557 }
2558 }
2559 None
2560 }
2561
2562 #[test]
2563 fn prior_gaps_are_checked_per_object_and_along_the_stream() {
2564 let malformed =
2565 |stream| matches!(first_error(stream), Some(CodecError::MalformedTrack { .. }));
2566 // Section 10.8: "An Object contains more than one instance of Prior
2567 // Group ID Gap", at the top level and inside Immutable Properties.
2568 assert!(malformed("1101058000043c01000104deadbeef"));
2569 assert!(malformed("1101058000060b023c01310104deadbeef"));
2570 // Section 10.9: Object 2's gap of 2 covers Object 0, received earlier,
2571 // which draft-21 Section 2.1.1 allows: an Object known to exist "can
2572 // transition to not existing".
2573 assert_eq!(first_error("11010580000004deadbeef01023e0202cafe"), None);
2574 // Object 3's gap of 2 covers Objects 1 and 2, neither received.
2575 assert_eq!(first_error("11010580000004deadbeef02023e0202cafe"), None);
2576 // Equal Prior Group ID Gap values in one Group are fine.
2577 assert_eq!(first_error("1101058000023c0204deadbeef00023c0202cafe"), None);
2578 }
2579
2580 #[test]
2581 fn status_object_carries_its_properties_block() {
2582 // Section 11.1.3 forbids properties on a non-Normal status, so the
2583 // whole-object reader refuses the frame and the meta reader frames it.
2584 let bytes = hex("1101058000023c010003");
2585 let mut cursor = &bytes[..];
2586 let header = SubgroupHeader::decode(&mut cursor).unwrap();
2587 let mut reader = SubgroupObjectReader::new(&header);
2588 assert!(matches!(reader.read_object(&mut &cursor[..]), Err(CodecError::InvalidField)));
2589 let meta = SubgroupObjectReader::new(&header).read_object_meta(&mut &cursor[..]).unwrap();
2590 assert_eq!(meta.extension_headers_len, 2);
2591 assert_eq!(meta.status, Some(3));
2592 }
2593
2594 // ── Re-encoding ─────────────────────────────────────────
2595
2596 #[test]
2597 fn vectors_re_encode_byte_identically() {
2598 for vector in VECTORS {
2599 let bytes = hex(vector);
2600 let (header, objects) = decode_all(&bytes);
2601 assert_eq!(encode_all(&header, &objects), bytes, "[{vector}] re-encode");
2602 }
2603 }
2604
2605 // ── Payload-free framing ────────────────────────────────
2606
2607 #[test]
2608 fn meta_matches_read_object() {
2609 for vector in VECTORS {
2610 let bytes = hex(vector);
2611 let mut cursor = &bytes[..];
2612 let header = SubgroupHeader::decode(&mut cursor).unwrap();
2613 let mut full_reader = SubgroupObjectReader::new(&header);
2614 let mut meta_reader = SubgroupObjectReader::new(&header);
2615 let mut full_cursor = cursor;
2616 let mut meta_cursor = cursor;
2617 while meta_cursor.has_remaining() {
2618 let before = meta_cursor.remaining();
2619 let object = full_reader.read_object(&mut full_cursor).unwrap();
2620 let meta = meta_reader.read_object_meta(&mut meta_cursor).unwrap();
2621 assert_eq!(meta.object_id, object.object_id.into_inner(), "[{vector}]");
2622 assert_eq!(
2623 meta.extension_headers_len,
2624 object.extension_headers.len() as u64,
2625 "[{vector}]"
2626 );
2627 assert_eq!(meta.payload_length, object.payload_length.into_inner(), "[{vector}]");
2628 assert_eq!(
2629 meta.status,
2630 object.object_status.map(ObjectStatus::as_u64),
2631 "[{vector}]"
2632 );
2633 assert_eq!(meta.wire_len, (before - meta_cursor.remaining()) as u64, "[{vector}]");
2634 assert_eq!(full_cursor.remaining(), meta_cursor.remaining(), "[{vector}]");
2635 }
2636 }
2637 }
2638
2639 #[test]
2640 fn short_buffers_report_unexpected_end() {
2641 let bytes = hex("1101058000023c0204deadbeef00023c0202cafe");
2642 let mut cursor = &bytes[..];
2643 let header = SubgroupHeader::decode(&mut cursor).unwrap();
2644 let objects_start = bytes.len() - cursor.len();
2645 for cut in objects_start..bytes.len() {
2646 let mut reader = SubgroupObjectReader::new(&header);
2647 let mut meta_reader = SubgroupObjectReader::new(&header);
2648 let mut cursor = &bytes[objects_start..cut];
2649 let mut meta_cursor = cursor;
2650 while cursor.has_remaining() {
2651 if let Err(err) = reader.read_object(&mut cursor) {
2652 assert!(
2653 matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2654 "cut {cut} gave {err:?}"
2655 );
2656 break;
2657 }
2658 }
2659 while meta_cursor.has_remaining() {
2660 if let Err(err) = meta_reader.read_object_meta(&mut meta_cursor) {
2661 assert!(
2662 matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2663 "cut {cut} gave {err:?}"
2664 );
2665 break;
2666 }
2667 }
2668 }
2669 }
2670
2671 // ── Object status ───────────────────────────────────────
2672
2673 /// A one-object subgroup stream whose object carries `status` in place of
2674 /// a payload: header type 0x10 (no properties, subgroup-ID mode 0, no
2675 /// FIRST_OBJECT bit), track alias 1, group 0, publisher priority 128; then
2676 /// an Object ID delta of 0, a payload length of 0, and the status code.
2677 fn subgroup_status_stream(status: u64) -> Vec<u8> {
2678 vec![0x10, 0x01, 0x00, 0x80, 0x00, 0x00, status as u8]
2679 }
2680
2681 /// A status datagram carrying `status`: type 0x20 (STATUS bit set,
2682 /// explicit Object ID, explicit priority), track alias 1, group 0, object
2683 /// 0, priority 128, then the status byte.
2684 fn status_datagram(status: u64) -> Vec<u8> {
2685 vec![0x20, 0x01, 0x00, 0x00, 0x80, status as u8]
2686 }
2687
2688 /// The object [`subgroup_status_stream`] describes, as a value.
2689 fn status_object(status: Option<ObjectStatus>) -> SubgroupObject {
2690 SubgroupObject {
2691 object_id: vi(0),
2692 extension_headers: Vec::new(),
2693 payload_length: vi(0),
2694 object_status: status,
2695 payload: Vec::new(),
2696 }
2697 }
2698
2699 /// An object carrying both `payload` and, in the caller's hands, `status`.
2700 /// The wire has no room for both, which is what the registry rules on.
2701 fn payload_object(status: Option<ObjectStatus>, payload: Vec<u8>) -> SubgroupObject {
2702 SubgroupObject {
2703 object_id: vi(0),
2704 extension_headers: Vec::new(),
2705 payload_length: vi(payload.len() as u64),
2706 object_status: status,
2707 payload,
2708 }
2709 }
2710
2711 /// The datagram [`status_datagram`] describes, as a value.
2712 fn status_datagram_header(status: Option<ObjectStatus>) -> DatagramHeader {
2713 DatagramHeader {
2714 datagram_type: 0x20,
2715 track_alias: vi(1),
2716 group_id: vi(0),
2717 object_id: vi(0),
2718 publisher_priority: Some(128),
2719 properties: Vec::new(),
2720 object_status: status,
2721 }
2722 }
2723
2724 /// Every status draft-21 assigns can be written and read back as the same
2725 /// status, on both a subgroup stream and a status datagram.
2726 ///
2727 /// The set is read from `ObjectStatus::ALL` — the three rows of the Object
2728 /// Status registry — rather than restated here, so this moves with the
2729 /// draft if a code is ever reassigned. It is the gate on typing the two
2730 /// `object_status` fields: a typed field that silently narrowed or
2731 /// renumbered the set would fail here even though it still compiled.
2732 ///
2733 /// Writing `ObjectStatus::Normal` when a zero-length object's status is
2734 /// `None` is checked too — without it the encoder emits an object whose
2735 /// declared payload length promises a status field that never arrives.
2736 ///
2737 /// Fails if `write_object` or `DatagramHeader::encode` writes a constant
2738 /// status instead of the object's own.
2739 #[test]
2740 fn every_assigned_status_survives_a_round_trip() {
2741 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2742 for &status in ObjectStatus::ALL {
2743 let mut buf = Vec::new();
2744 SubgroupObjectReader::new(&header)
2745 .write_object(&status_object(Some(status)), &mut buf)
2746 .unwrap_or_else(|e| panic!("write_object refused {status:?}: {e:?}"));
2747
2748 let mut cursor = &buf[..];
2749 let object =
2750 SubgroupObjectReader::new(&header).read_object(&mut cursor).unwrap_or_else(|e| {
2751 panic!("read_object refused the bytes written for {status:?}: {e:?}")
2752 });
2753 assert_eq!(object.object_status, Some(status), "subgroup object status");
2754 assert!(!cursor.has_remaining(), "{status:?}: bytes left over after read_object");
2755
2756 let meta =
2757 SubgroupObjectReader::new(&header).read_object_meta(&mut &buf[..]).unwrap_or_else(
2758 |e| panic!("read_object_meta refused the bytes written for {status:?}: {e:?}"),
2759 );
2760 assert_eq!(meta.status, Some(status.as_u64()), "subgroup meta status");
2761
2762 let mut datagram = Vec::new();
2763 status_datagram_header(Some(status)).encode(&mut datagram);
2764 let decoded = DatagramHeader::decode(&mut &datagram[..]).unwrap_or_else(|e| {
2765 panic!("datagram decode refused the bytes written for {status:?}: {e:?}")
2766 });
2767 assert_eq!(decoded.object_status, Some(status), "datagram object status");
2768 }
2769
2770 let mut buf = Vec::new();
2771 SubgroupObjectReader::new(&header).write_object(&status_object(None), &mut buf).unwrap();
2772 let object = SubgroupObjectReader::new(&header)
2773 .read_object(&mut &buf[..])
2774 .expect("a zero-length object with no status must still decode");
2775 assert_eq!(object.object_status, Some(ObjectStatus::Normal));
2776
2777 let mut datagram = Vec::new();
2778 status_datagram_header(None).encode(&mut datagram);
2779 let decoded = DatagramHeader::decode(&mut &datagram[..])
2780 .expect("a status datagram with no status must still decode");
2781 assert_eq!(decoded.object_status, Some(ObjectStatus::Normal));
2782 }
2783
2784 /// The encoder writes exactly the frames the decoder accepts.
2785 ///
2786 /// Sweeps every status code `0x00..=0x3f` — one wire byte under both the
2787 /// varint on a subgroup stream and the bare byte on a datagram, and wide
2788 /// enough to contain the gap at `0x2` and the `0x1` draft-16 dropped. For
2789 /// a code the draft assigns, the hand-built frame must decode *and* the
2790 /// encoder handed that status must reproduce those exact bytes. For a code
2791 /// it does not assign, the same frame must be refused at all three decode
2792 /// sites — and no `ObjectStatus` exists to hand the encoder, so the frame
2793 /// has no way to be produced in the first place.
2794 ///
2795 /// This is a gate on the status code points only. Which of those statuses
2796 /// may carry a payload is the separate question
2797 /// [`the_registry_decides_which_statuses_may_carry_a_payload`] gates.
2798 ///
2799 /// Fails if `write_object` or `DatagramHeader::encode` writes a constant
2800 /// status instead of the object's own, or if `ObjectStatus::from_u64`
2801 /// decodes a code the draft does not assign.
2802 ///
2803 /// # The encode-side refusal is a type, not an assertion
2804 ///
2805 /// Once `object_status` is typed there is no runtime path that offers the
2806 /// encoder the `0x2` that this module's decoder is documented as refusing,
2807 /// so no test here can watch one be refused. The guarantee is the type: an
2808 /// `Option<u8>` status field does not compile against this test.
2809 #[test]
2810 fn the_encoder_writes_exactly_the_frames_the_decoder_accepts() {
2811 for code in 0x00u64..=0x3f {
2812 let assigned = ObjectStatus::ALL.iter().copied().find(|s| s.as_u64() == code);
2813
2814 let stream = subgroup_status_stream(code);
2815 let mut cursor: &[u8] = &stream;
2816 let header = SubgroupHeader::decode(&mut cursor).unwrap();
2817 let objects = cursor;
2818 let read = SubgroupObjectReader::new(&header).read_object(&mut { objects });
2819 let meta = SubgroupObjectReader::new(&header).read_object_meta(&mut { objects });
2820
2821 let datagram = status_datagram(code);
2822 let decoded = DatagramHeader::decode(&mut &datagram[..]);
2823
2824 match assigned {
2825 Some(status) => {
2826 let object = read.unwrap_or_else(|e| {
2827 panic!(
2828 "read_object refused status {code:#x}, which the draft assigns: {e:?}"
2829 )
2830 });
2831 assert_eq!(object.object_status, Some(status));
2832 assert_eq!(meta.unwrap().status, Some(code));
2833 assert_eq!(decoded.unwrap().object_status, Some(status));
2834
2835 let mut written = Vec::new();
2836 header.encode(&mut written);
2837 SubgroupObjectReader::new(&header)
2838 .write_object(&status_object(Some(status)), &mut written)
2839 .unwrap();
2840 assert_eq!(
2841 written, stream,
2842 "the encoder must produce the frame the decoder accepted for status {code:#x}"
2843 );
2844
2845 let mut written = Vec::new();
2846 status_datagram_header(Some(status)).encode(&mut written);
2847 assert_eq!(
2848 written, datagram,
2849 "the encoder must produce the datagram the decoder accepted for status {code:#x}"
2850 );
2851 }
2852 None => {
2853 for (site, result) in [
2854 ("subgroup read_object", read.map(|_| ())),
2855 ("subgroup read_object_meta", meta.map(|_| ())),
2856 ("status datagram", decoded.map(|_| ())),
2857 ] {
2858 match result {
2859 Ok(()) => panic!(
2860 "{site} accepted status {code:#x}, which the draft does not assign"
2861 ),
2862 Err(error) => assert!(
2863 matches!(error, CodecError::InvalidField),
2864 "{site} refused status {code:#x} with {error:?}, not InvalidField"
2865 ),
2866 }
2867 }
2868 }
2869 }
2870 }
2871 }
2872
2873 // ── The registry's payload column ───────────────────────
2874
2875 /// Draft-21 Section 16.9, Table 16, "Payload" column: Normal "Yes", End of
2876 /// Group "No", End of Track "No".
2877 ///
2878 /// Restated here rather than read from [`ObjectStatus::payload_permission`]
2879 /// so the gate holds its own copy of the registry. A codec that changed a
2880 /// row would still agree with itself; only a second copy notices.
2881 const PAYLOAD_COLUMN: &[(ObjectStatus, bool)] = &[
2882 (ObjectStatus::Normal, true),
2883 (ObjectStatus::EndOfGroup, false),
2884 (ObjectStatus::EndOfTrack, false),
2885 ];
2886
2887 /// Which statuses may carry a payload is decided by the registry, not by a
2888 /// payload length.
2889 ///
2890 /// Draft-18 states one blanket rule — an Object with any status other than
2891 /// Normal has an empty payload — so under it the answer can be read off the
2892 /// status number, or equivalently off a zero payload length. Draft-21
2893 /// Section 11.1.2 replaces the blanket rule with "an Object MUST have an
2894 /// empty payload unless its Object Status value is registered as permitting
2895 /// a payload", the permission being a column of the Object Status registry
2896 /// in Section 16.9. The three rows assigned today give the same answers
2897 /// draft-18's rule gives; what this gate observes is that the answers come
2898 /// from the rows.
2899 ///
2900 /// Each row is driven both ways. A zero-length object with that status must
2901 /// encode and read back — Normal included, since it permits a payload
2902 /// without requiring one and so has to stay expressible with none. An
2903 /// object handed a payload under that status must be accepted exactly when
2904 /// the row permits one, and otherwise refused with nothing written.
2905 ///
2906 /// Fails if `write_object` lets the payload length decide without the
2907 /// registry, if End of Group moves into the permitting column, or if the
2908 /// status field is written for the statuses that forbid a payload instead
2909 /// of for the objects whose payload length is zero; the draft frames the
2910 /// status on payload length alone, so a zero-length Normal object needs it.
2911 #[test]
2912 fn the_registry_decides_which_statuses_may_carry_a_payload() {
2913 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
2914 assert_eq!(
2915 PAYLOAD_COLUMN.len(),
2916 ObjectStatus::ALL.len(),
2917 "every assigned status needs a row in the payload column"
2918 );
2919
2920 for &(status, permitted) in PAYLOAD_COLUMN {
2921 assert!(ObjectStatus::ALL.contains(&status), "{status:?} is not an assigned status");
2922
2923 // A zero-length object is legal under every row, and is the only
2924 // framing that states a status on a subgroup stream.
2925 let mut empty = Vec::new();
2926 SubgroupObjectReader::new(&header)
2927 .write_object(&status_object(Some(status)), &mut empty)
2928 .unwrap_or_else(|e| panic!("write_object refused a zero-length {status:?}: {e:?}"));
2929 let object = SubgroupObjectReader::new(&header)
2930 .read_object(&mut &empty[..])
2931 .unwrap_or_else(|e| panic!("read_object refused a zero-length {status:?}: {e:?}"));
2932 assert_eq!(object.status(), status, "zero-length {status:?} lost its status");
2933 assert!(object.payload.is_empty(), "zero-length {status:?} gained a payload");
2934 assert_eq!(
2935 object.permits_payload(),
2936 permitted,
2937 "decoded {status:?} reports the wrong payload permission"
2938 );
2939
2940 // A status datagram is the one carrier where the registry is not
2941 // the last word. Section 11.2.1 puts the status field in the
2942 // payload's place — "When set to 1, the Object Status field is
2943 // present and there is no Object Payload" — so no status makes
2944 // trailing bytes part of such a datagram, Normal included, and
2945 // `permitted` is not the expected answer here.
2946 let datagram = DatagramHeader::decode(&mut &status_datagram(status.as_u64())[..])
2947 .unwrap_or_else(|e| panic!("datagram decode refused {status:?}: {e:?}"));
2948 assert!(
2949 !datagram.permits_payload(),
2950 "a status datagram has no Object Payload field, so {status:?} permits no bytes"
2951 );
2952 assert_eq!(
2953 datagram.status().permits_payload(),
2954 permitted,
2955 "decoded {status:?} datagram reports the wrong registry row"
2956 );
2957 let mut whole = status_datagram(status.as_u64());
2958 whole.extend_from_slice(&hex("deadbeef"));
2959 let trailing = DatagramHeader::decode_object(&mut &whole[..]);
2960 assert!(
2961 matches!(trailing, Err(CodecError::PayloadNotPermitted { .. })),
2962 "trailing bytes on a {status:?} status datagram must be refused; got {trailing:?}"
2963 );
2964
2965 // The same status, handed a payload the wire cannot frame beside it.
2966 let mut written = Vec::new();
2967 let result = SubgroupObjectReader::new(&header)
2968 .write_object(&payload_object(Some(status), hex("deadbeef")), &mut written);
2969
2970 if permitted {
2971 result.unwrap_or_else(|e| {
2972 panic!(
2973 "write_object refused a payload under {status:?}, \
2974 which the registry permits: {e:?}"
2975 )
2976 });
2977 let object = SubgroupObjectReader::new(&header)
2978 .read_object(&mut &written[..])
2979 .unwrap_or_else(|e| {
2980 panic!("read_object refused its own output for {status:?}: {e:?}")
2981 });
2982 assert_eq!(object.payload, hex("deadbeef"), "{status:?} lost its payload");
2983 assert_eq!(object.status(), status, "{status:?} came back as another status");
2984 } else {
2985 assert!(
2986 matches!(result, Err(CodecError::PayloadNotPermitted { .. })),
2987 "write_object must refuse a payload under {status:?}, \
2988 which the registry forbids one; got {result:?}"
2989 );
2990 assert!(written.is_empty(), "a refused {status:?} object still wrote {written:?}");
2991 }
2992 }
2993
2994 // A datagram without the STATUS bit is all payload, and the status its
2995 // framing leaves out is the one row that permits a payload.
2996 let plain = DatagramHeader::decode(&mut &[0x00u8, 0x01, 0x00, 0x00, 0x80][..])
2997 .expect("a datagram with no status field must decode");
2998 assert_eq!(plain.status(), ObjectStatus::Normal);
2999 assert!(plain.permits_payload(), "a payload-carrying datagram must be permitted one");
3000 }
3001}