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