moqtap_codec/draft14/data_stream.rs
1//! Draft-14 data streams: subgroup streams, fetch streams, datagrams.
2//!
3//! Draft-14 carries object data in three wire shapes:
4//!
5//! * **Subgroup stream**: starts with a Type byte `0x10..=0x1D`
6//! whose bit-flags determine whether a Subgroup ID field is present,
7//! whether the subgroup ID is zero or the first Object ID, whether
8//! extension headers are present, and whether the stream ends at a
9//! group boundary. Object IDs are delta-encoded relative to the
10//! previous Object ID in the same stream.
11//!
12//! * **Fetch stream**: Type `0x05`, Request ID, then a sequence
13//! of self-describing objects until FIN.
14//!
15//! * **Datagram**: Type byte `0x00..=0x07` or `0x20..=0x21`
16//! with bit-flags for End of Group, Extensions Present, Object ID
17//! Present, and Status vs Payload.
18
19use bytes::{Buf, BufMut};
20
21use super::types::ObjectStatus;
22use crate::error::CodecError;
23use crate::varint::VarInt;
24
25/// Advance `buf` past `len` bytes without copying them.
26fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
27 let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
28 if buf.remaining() < len {
29 return Err(CodecError::UnexpectedEnd);
30 }
31 buf.advance(len);
32 Ok(())
33}
34
35/// Hold an object to the rule binding extension headers to Object Status.
36///
37/// Draft-14 Section 10.2.1.2: "Any Object may have extension headers except
38/// those with Object Status 'Object Does Not Exist'. If an endpoint receives a
39/// non-existent Object containing extension headers it MUST close the session
40/// with a PROTOCOL_VIOLATION."
41///
42/// The rule names one status and no others, so extensions beside End of Group
43/// or End of Track stay legal and are left alone here. The reasoning behind the
44/// exception is that an object nobody has cannot carry metadata about itself:
45/// a relay that forwards the extensions of a non-existent object is inventing
46/// provenance for something that was never published.
47///
48/// All three carriers reach this, in both directions — subgroup streams, fetch
49/// streams and status datagrams each pair a status with an extension block.
50///
51/// Reported under [`CodecError::ExtensionsOnNonExistentObject`], which is this
52/// rule and nothing else. It was [`CodecError::InvalidField`] until now, shared
53/// with a dozen unrelated malformations the draft does not answer with a close,
54/// which left a caller unable to act on the sentence above.
55fn check_extensions_against_status(
56 status: Option<u64>,
57 extension_headers_len: u64,
58) -> Result<(), CodecError> {
59 if status == Some(ObjectStatus::ObjectDoesNotExist.as_u64()) && extension_headers_len != 0 {
60 // The length is reported as it appeared on the wire; saturating rather
61 // than truncating means a declared length above `usize::MAX` — which
62 // cannot have been read, but can have been declared — is never reported
63 // as some smaller, plausible number.
64 return Err(CodecError::ExtensionsOnNonExistentObject(
65 usize::try_from(extension_headers_len).unwrap_or(usize::MAX),
66 ));
67 }
68 Ok(())
69}
70
71// ============================================================
72// Subgroup stream (Type 0x10..=0x1D)
73// ============================================================
74
75/// Subgroup stream type byte.
76///
77/// The 12 defined types encode four independent boolean fields in the
78/// low nibble:
79///
80/// * bit 0 (`0x01`) — Extensions Present
81/// * bit 1 (`0x02`) — Subgroup ID derives from first Object ID
82/// (only meaningful when bit 2 is clear)
83/// * bit 2 (`0x04`) — Subgroup ID Field Present (explicit Subgroup ID varint)
84/// * bit 3 (`0x08`) — Contains End of Group
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct SubgroupStreamType(u8);
87
88impl SubgroupStreamType {
89 /// The raw wire byte.
90 pub fn as_u8(self) -> u8 {
91 self.0
92 }
93
94 /// Create a [`SubgroupStreamType`] from its raw byte, validating
95 /// that it is one of the 12 defined values. `0x16` and `0x17` fall
96 /// inside the `0x10..=0x1D` range but are not defined, so a range
97 /// check alone is not enough.
98 pub fn from_u8(v: u8) -> Option<Self> {
99 if (0x10..=0x15).contains(&v) || (0x18..=0x1D).contains(&v) {
100 Some(SubgroupStreamType(v))
101 } else {
102 None
103 }
104 }
105
106 /// Build a subgroup stream type from its component flags.
107 ///
108 /// `subgroup_id_is_first_object` and `subgroup_id_field_present` are
109 /// mutually exclusive — if both are set, the resulting type has the
110 /// "Subgroup ID Field Present" bit set (bit 2 wins).
111 pub fn from_flags(
112 subgroup_id_field_present: bool,
113 subgroup_id_is_first_object: bool,
114 extensions_present: bool,
115 end_of_group: bool,
116 ) -> Self {
117 let mut v: u8 = 0x10;
118 if extensions_present {
119 v |= 0x01;
120 }
121 if subgroup_id_field_present {
122 v |= 0x04;
123 } else if subgroup_id_is_first_object {
124 v |= 0x02;
125 }
126 if end_of_group {
127 v |= 0x08;
128 }
129 SubgroupStreamType(v)
130 }
131
132 /// True if the header carries an explicit Subgroup ID varint.
133 pub fn has_subgroup_id_field(self) -> bool {
134 self.0 & 0x04 != 0
135 }
136
137 /// True if the subgroup ID is defined to equal the first Object ID
138 /// in the stream (applies only when [`Self::has_subgroup_id_field`]
139 /// is false).
140 pub fn subgroup_id_is_first_object(self) -> bool {
141 !self.has_subgroup_id_field() && (self.0 & 0x02 != 0)
142 }
143
144 /// True if every object in the stream carries extension headers.
145 pub fn extensions_present(self) -> bool {
146 self.0 & 0x01 != 0
147 }
148
149 /// True if the last object on the stream (prior to FIN) is the end
150 /// of its group.
151 pub fn contains_end_of_group(self) -> bool {
152 self.0 & 0x08 != 0
153 }
154}
155
156/// Subgroup stream header.
157///
158/// Wire order: type byte, Track Alias, Group ID, Subgroup ID (only for
159/// stream types that carry one), Publisher Priority.
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct SubgroupHeader {
162 /// Type byte identifying the flag set for this stream.
163 pub stream_type: SubgroupStreamType,
164 /// Track alias.
165 pub track_alias: VarInt,
166 /// Group ID.
167 pub group_id: VarInt,
168 /// Explicit Subgroup ID — present only when the stream type sets
169 /// `Subgroup ID Field Present = Yes`. For types where the subgroup
170 /// ID is implicit (0 or the first Object ID), the effective
171 /// subgroup ID is resolved on the receive side by the reader.
172 pub subgroup_id: Option<VarInt>,
173 /// Publisher priority.
174 pub publisher_priority: u8,
175}
176
177impl SubgroupHeader {
178 /// Encode the header including the leading stream type byte.
179 ///
180 /// Driven by `stream_type`, and silent about a `subgroup_id` that
181 /// disagrees with it in either direction: a `None` under a type whose
182 /// Subgroup ID Field Present column reads Yes is written as zero, which
183 /// names a different subgroup rather than no subgroup, and a `Some` under
184 /// a type whose column reads No is dropped. Both write a well-formed
185 /// stream describing something other than what the caller built.
186 /// [`Self::encode_checked`] refuses that shape instead.
187 pub fn encode(&self, buf: &mut impl BufMut) {
188 VarInt::from_u64(self.stream_type.as_u8() as u64).unwrap().encode(buf);
189 self.track_alias.encode(buf);
190 self.group_id.encode(buf);
191 if self.stream_type.has_subgroup_id_field() {
192 let sg = self.subgroup_id.unwrap_or_else(|| VarInt::from_u64(0).unwrap());
193 sg.encode(buf);
194 }
195 buf.put_u8(self.publisher_priority);
196 }
197
198 /// Encode, refusing a header whose Subgroup ID disagrees with its own type
199 /// byte.
200 ///
201 /// Section 10.4.2 gives the SUBGROUP_HEADER type table a Subgroup ID Field
202 /// Present column, and that column - not the value in hand - decides
203 /// whether the field is on the wire. [`Self::decode`] therefore only ever
204 /// produces a header where the two agree, so this refuses exactly the
205 /// shapes a caller assembled by hand: an absent ID under a type that
206 /// carries one, and a present ID under a type that does not.
207 ///
208 /// Nothing about the *value* is refused. Zero is a legal Subgroup ID, so a
209 /// `Some(0)` under a type that carries the field is written as the caller
210 /// asked.
211 ///
212 /// # Errors
213 ///
214 /// [`CodecError::InvalidField`] if the presence of `subgroup_id` does not
215 /// match what `stream_type` puts on the wire.
216 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
217 if self.stream_type.has_subgroup_id_field() != self.subgroup_id.is_some() {
218 return Err(CodecError::InvalidField);
219 }
220 self.encode(buf);
221 Ok(())
222 }
223
224 /// Decode a subgroup header (leading type byte + remaining fields).
225 ///
226 /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
227 /// not assign the leading type, which this draft answers with a close, and
228 /// with [`CodecError::InvalidField`] for `0x05`, which it assigns to a
229 /// fetch stream. `stream_type_error` draws that line.
230 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
231 let type_val = VarInt::decode(buf)?.into_inner();
232 if type_val > 0xFF {
233 return Err(stream_type_error(type_val));
234 }
235 let stream_type = SubgroupStreamType::from_u8(type_val as u8)
236 .ok_or_else(|| stream_type_error(type_val))?;
237 let track_alias = VarInt::decode(buf)?;
238 let group_id = VarInt::decode(buf)?;
239 let subgroup_id =
240 if stream_type.has_subgroup_id_field() { Some(VarInt::decode(buf)?) } else { None };
241 if buf.remaining() < 1 {
242 return Err(CodecError::UnexpectedEnd);
243 }
244 let publisher_priority = buf.get_u8();
245 Ok(SubgroupHeader { stream_type, track_alias, group_id, subgroup_id, publisher_priority })
246 }
247}
248
249/// One object within a subgroup stream, with the Object ID already
250/// resolved from its delta encoding.
251#[derive(Debug, Clone, PartialEq, Eq)]
252pub struct SubgroupObject {
253 /// Resolved Object ID (delta decoded to an absolute value).
254 pub object_id: VarInt,
255 /// Raw extension-header bytes. Empty when the stream type has
256 /// `Extensions Present = No`, or when present but the length was 0.
257 /// The content is a sequence of Key-Value-Pairs but is
258 /// left opaque here — relays and subscribers that do not understand
259 /// specific extensions must forward or ignore the bytes unchanged.
260 pub extension_headers: Vec<u8>,
261 /// Object Status when `payload.is_empty()` and the object was sent
262 /// with an explicit status code; `None` when a non-empty payload
263 /// follows (status is implicitly [`ObjectStatus::Normal`]).
264 pub status: Option<ObjectStatus>,
265 /// Object payload. Empty when `status` is `Some(..)`.
266 pub payload: Vec<u8>,
267}
268
269/// The framing of one subgroup object, without its payload.
270///
271/// Produced by [`SubgroupObjectReader::read_object_meta`] for callers that
272/// forward an object's bytes verbatim and never inspect the payload.
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274pub struct SubgroupObjectMeta {
275 /// Resolved Object ID (delta decoded to an absolute value).
276 pub object_id: u64,
277 /// Byte length of the extension-header block's contents, excluding its
278 /// length prefix.
279 pub extension_headers_len: u64,
280 /// Declared payload length. Zero when `status` is `Some`.
281 pub payload_length: u64,
282 /// Object Status wire code, present only when the payload is empty.
283 pub status: Option<u64>,
284 /// Total bytes this object occupies on the wire, prefix fields included.
285 pub wire_len: u64,
286}
287
288/// Whether an object carrying a given status may hold a non-empty payload.
289///
290/// Section 10.2.1.1 states the rule in one sentence — "Any object with a
291/// status code other than zero MUST have an empty payload" — so on this draft
292/// the answer falls out of the code being zero or not, and every status but
293/// [`ObjectStatus::Normal`] forbids a payload.
294///
295/// It is worth a type all the same, because that arithmetic is not something a
296/// consumer can safely perform on a raw wire code. A code this draft does not
297/// assign is not *non-zero, and therefore forbidden*: it is a code with no
298/// meaning at all, and no payload rule attaches to it. Handing back a
299/// `PayloadPermission` keeps the two apart, and lets a caller ask the question
300/// without restating the rule — or, worse, restating it slightly differently.
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
302pub enum PayloadPermission {
303 /// The status permits a payload but does not require one: a zero-length
304 /// object with such a status is well formed, and this draft's encodings
305 /// have a way to spell it.
306 Permitted,
307 /// An object with such a status has an empty payload, and one carrying
308 /// bytes is malformed.
309 Forbidden,
310}
311
312impl PayloadPermission {
313 /// The permission this draft gives objects carrying `status`.
314 ///
315 /// Written as a match over every assigned status rather than as a test for
316 /// zero, so that a status added to [`ObjectStatus`] later cannot quietly
317 /// inherit *not Normal, therefore forbidden* — it stops the crate
318 /// compiling until its own answer is written down.
319 pub fn for_status(status: ObjectStatus) -> Self {
320 match status {
321 ObjectStatus::Normal => PayloadPermission::Permitted,
322 ObjectStatus::ObjectDoesNotExist => PayloadPermission::Forbidden,
323 ObjectStatus::EndOfGroup => PayloadPermission::Forbidden,
324 ObjectStatus::EndOfTrack => PayloadPermission::Forbidden,
325 }
326 }
327
328 /// `true` for [`PayloadPermission::Permitted`].
329 ///
330 /// The permission answers on its own, with no payload length in hand,
331 /// which is the point of asking the status rather than the framing.
332 pub fn permits(self) -> bool {
333 matches!(self, PayloadPermission::Permitted)
334 }
335}
336
337impl SubgroupObject {
338 /// The status this object resolves to.
339 ///
340 /// The wire carries a status field only on an empty object, so an object
341 /// holding bytes is [`ObjectStatus::Normal`] whatever `status` says.
342 pub fn status(&self) -> ObjectStatus {
343 if self.payload.is_empty() {
344 self.status.unwrap_or(ObjectStatus::Normal)
345 } else {
346 ObjectStatus::Normal
347 }
348 }
349
350 /// Whether this object's status permits it a non-empty payload.
351 ///
352 /// Answered from the status alone. `payload` is not consulted: on a status
353 /// that permits a payload it says only whether this particular object took
354 /// the offer, and on one that forbids a payload a non-empty payload is the
355 /// malformation this reports, not evidence about the rule.
356 pub fn permits_payload(&self) -> bool {
357 PayloadPermission::for_status(self.status()).permits()
358 }
359}
360
361impl SubgroupObjectMeta {
362 /// Whether this object's status permits it a non-empty payload.
363 ///
364 /// The same question [`SubgroupObject::permits_payload`] answers, from the
365 /// declared length and the wire code rather than from the bytes. A relay
366 /// that forwards an object verbatim reads it through
367 /// [`SubgroupObjectReader::read_object_meta`] and never copies the payload,
368 /// so asking this must not require having it.
369 ///
370 /// An absent status answers [`PayloadPermission::Permitted`] rather than
371 /// `None`. A meta has no status only when its payload length is non-zero,
372 /// so the object has a status — the encoding just does not spell it.
373 ///
374 /// `None` means the code is one this draft leaves unassigned, and so one it
375 /// gives no payload rule for. That is not reachable through
376 /// [`SubgroupObjectReader::read_object_meta`], which refuses such a code
377 /// before it can reach the field, but the fields here are public and a meta
378 /// assembled by hand — by a relay carrying a status across from a draft
379 /// that numbers them differently, say — can hold anything a varint can. The
380 /// answer there is that this draft has none, not that the payload is
381 /// forbidden.
382 pub fn payload_permission(&self) -> Option<PayloadPermission> {
383 match self.status {
384 None => Some(PayloadPermission::Permitted),
385 Some(code) => ObjectStatus::from_u64(code).map(PayloadPermission::for_status),
386 }
387 }
388}
389
390/// Stateful reader for the object fields on a subgroup stream.
391///
392/// Object IDs on a subgroup stream are delta-encoded against the
393/// previous Object ID, and whether extension headers are present is
394/// fixed by the enclosing [`SubgroupHeader`]'s stream type. This reader
395/// carries that context across successive `read_object` calls.
396#[derive(Debug, Clone)]
397pub struct SubgroupObjectReader {
398 extensions_present: bool,
399 prev_object_id: Option<u64>,
400}
401
402impl SubgroupObjectReader {
403 /// Create a reader from a parsed subgroup header.
404 pub fn new(header: &SubgroupHeader) -> Self {
405 Self { extensions_present: header.stream_type.extensions_present(), prev_object_id: None }
406 }
407
408 /// Decode the next object from `buf`. Caller is responsible for
409 /// ensuring the buffer contains a complete object (draft-14 objects
410 /// are length-delimited by the payload-length field, so the buffer
411 /// boundary is known once the header portion has been consumed).
412 pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<SubgroupObject, CodecError> {
413 let delta = VarInt::decode(buf)?.into_inner();
414 let object_id_val = match self.prev_object_id {
415 None => delta,
416 Some(prev) => prev
417 .checked_add(1)
418 .and_then(|v| v.checked_add(delta))
419 .ok_or(CodecError::InvalidField)?,
420 };
421 self.prev_object_id = Some(object_id_val);
422 let object_id = VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?;
423
424 let extension_headers = if self.extensions_present {
425 let ext_len = VarInt::decode(buf)?.into_inner() as usize;
426 crate::types::read_bytes(buf, ext_len)?
427 } else {
428 Vec::new()
429 };
430
431 let payload_length = VarInt::decode(buf)?.into_inner() as usize;
432 let (status, payload) = if payload_length == 0 {
433 let status_val = VarInt::decode(buf)?.into_inner();
434 let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
435 (Some(status), Vec::new())
436 } else {
437 let payload = crate::types::read_bytes(buf, payload_length)?;
438 (None, payload)
439 };
440 check_extensions_against_status(
441 status.map(|s| s.as_u64()),
442 extension_headers.len() as u64,
443 )?;
444
445 Ok(SubgroupObject { object_id, extension_headers, status, payload })
446 }
447
448 /// Decode the next object's framing without copying its payload.
449 ///
450 /// Consumes exactly the bytes [`Self::read_object`] consumes and leaves
451 /// the same delta state behind, so the two are interchangeable on a
452 /// given stream.
453 pub fn read_object_meta(
454 &mut self,
455 buf: &mut impl Buf,
456 ) -> Result<SubgroupObjectMeta, CodecError> {
457 let start = buf.remaining();
458 let delta = VarInt::decode(buf)?.into_inner();
459 let object_id_val = match self.prev_object_id {
460 None => delta,
461 Some(prev) => prev
462 .checked_add(1)
463 .and_then(|v| v.checked_add(delta))
464 .ok_or(CodecError::InvalidField)?,
465 };
466 self.prev_object_id = Some(object_id_val);
467 let object_id =
468 VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?.into_inner();
469
470 let extension_headers_len = if self.extensions_present {
471 let ext_len = VarInt::decode(buf)?.into_inner();
472 skip(buf, ext_len)?;
473 ext_len
474 } else {
475 0
476 };
477
478 let payload_length = VarInt::decode(buf)?.into_inner();
479 let status = if payload_length == 0 {
480 let status_val = VarInt::decode(buf)?.into_inner();
481 Some(ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?.as_u64())
482 } else {
483 skip(buf, payload_length)?;
484 None
485 };
486 check_extensions_against_status(status, extension_headers_len)?;
487
488 Ok(SubgroupObjectMeta {
489 object_id,
490 extension_headers_len,
491 payload_length,
492 status,
493 wire_len: (start - buf.remaining()) as u64,
494 })
495 }
496
497 /// Serialize a subgroup object using the reader's delta state. Intended
498 /// for senders that want to build a stream incrementally — tracks
499 /// `prev_object_id` so successive calls produce correct deltas.
500 ///
501 /// Returns an error if `object.object_id <= prev_object_id`, which
502 /// would produce an invalid delta.
503 ///
504 /// It also refuses the three shapes the stream cannot carry, rather than
505 /// writing whichever half fits and dropping the rest:
506 ///
507 /// * A non-empty payload beside a status other than Normal. Section
508 /// 10.2.1.1: "Any object with a status code other than zero MUST have an
509 /// empty payload." A truncated payload is worse than a refusal — the
510 /// receiver has no way to tell that anything was there.
511 /// * Extension headers on an object whose stream type says the subgroup has
512 /// none. The type byte is fixed for the whole stream by the header, so
513 /// this object cannot opt in, and its extensions would simply vanish.
514 /// * Extension headers on an Object Does Not Exist status, per Section
515 /// 10.2.1.2.
516 ///
517 /// Normal beside a non-empty payload is not one of those and is written as
518 /// an ordinary payload-bearing object: it is the status such an object
519 /// already has, so naming it asks for the bytes leaving it out asks for.
520 /// Normal beside an empty payload keeps the explicit status form, which is
521 /// the only way to send a zero-length object at all.
522 pub fn write_object(
523 &mut self,
524 object: &SubgroupObject,
525 buf: &mut impl BufMut,
526 ) -> Result<(), CodecError> {
527 let explicit_status = match object.status {
528 Some(status) if status != ObjectStatus::Normal => {
529 if !object.payload.is_empty() {
530 return Err(CodecError::InvalidField);
531 }
532 Some(status)
533 }
534 Some(ObjectStatus::Normal) if object.payload.is_empty() => Some(ObjectStatus::Normal),
535 _ => None,
536 };
537 if !self.extensions_present && !object.extension_headers.is_empty() {
538 return Err(CodecError::InvalidField);
539 }
540 check_extensions_against_status(
541 explicit_status.map(|s| s.as_u64()),
542 object.extension_headers.len() as u64,
543 )?;
544
545 let oid = object.object_id.into_inner();
546 let delta = match self.prev_object_id {
547 None => oid,
548 Some(prev) => oid
549 .checked_sub(prev)
550 .and_then(|v| v.checked_sub(1))
551 .ok_or(CodecError::InvalidField)?,
552 };
553 VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?.encode(buf);
554 if self.extensions_present {
555 VarInt::from_u64(object.extension_headers.len() as u64)
556 .map_err(|_| CodecError::InvalidField)?
557 .encode(buf);
558 buf.put_slice(&object.extension_headers);
559 }
560 if let Some(status) = explicit_status {
561 VarInt::from_u64(0).unwrap().encode(buf);
562 VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
563 } else {
564 VarInt::from_u64(object.payload.len() as u64)
565 .map_err(|_| CodecError::InvalidField)?
566 .encode(buf);
567 buf.put_slice(&object.payload);
568 }
569 self.prev_object_id = Some(oid);
570 Ok(())
571 }
572}
573
574// ============================================================
575// Fetch stream (Type 0x05)
576// ============================================================
577
578/// Which failure a leading unidirectional stream type that is not the one a
579/// reader wants is.
580///
581/// Section 10: "An endpoint that receives an unknown stream or datagram type
582/// MUST close the session." One sentence, two tables. The stream table assigns
583/// 0x05 for FETCH_HEADER and the range 0x10 to 0x1D for SUBGROUP_HEADER;
584/// everything outside them is unknown at the head of a stream, and the session
585/// ends.
586///
587/// The two assigned kinds are what the [`CodecError::InvalidField`] arm is for:
588/// a fetch stream reaching the subgroup reader, or a subgroup stream reaching
589/// the fetch reader, is a value this draft defines, and the disagreement is
590/// with the reader that was called rather than with the draft. Reporting it as
591/// unknown would end sessions over streams draft-14 permits.
592///
593/// Values above 0xFF land here too. None of them is assigned — this draft
594/// carries its control messages on a bidirectional stream and so has no
595/// multi-byte unidirectional type the way drafts 17 and later do.
596fn stream_type_error(raw: u64) -> CodecError {
597 let assigned = raw == FETCH_STREAM_TYPE as u64
598 || (raw <= 0xFF && SubgroupStreamType::from_u8(raw as u8).is_some());
599 if assigned {
600 CodecError::InvalidField
601 } else {
602 CodecError::UnknownStreamType(raw)
603 }
604}
605
606/// Draft-14 fetch stream type byte.
607pub const FETCH_STREAM_TYPE: u8 = 0x05;
608
609/// Fetch stream header: the type byte `0x05` followed by the Request ID.
610#[derive(Debug, Clone, PartialEq, Eq)]
611pub struct FetchHeader {
612 /// Request ID from the originating FETCH control message.
613 pub request_id: VarInt,
614}
615
616impl FetchHeader {
617 /// Encode the header including the leading type byte.
618 pub fn encode(&self, buf: &mut impl BufMut) {
619 VarInt::from_u64(FETCH_STREAM_TYPE as u64).unwrap().encode(buf);
620 self.request_id.encode(buf);
621 }
622
623 /// Decode the header.
624 ///
625 /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
626 /// not assign the leading type, which this draft answers with a close, and
627 /// with [`CodecError::InvalidField`] for the subgroup types, which it does
628 /// assign. `stream_type_error` draws that line.
629 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
630 let type_val = VarInt::decode(buf)?.into_inner();
631 if type_val != FETCH_STREAM_TYPE as u64 {
632 return Err(stream_type_error(type_val));
633 }
634 let request_id = VarInt::decode(buf)?;
635 Ok(FetchHeader { request_id })
636 }
637}
638
639/// One object carried on a fetch stream.
640///
641/// Every object on a fetch stream is self-describing — unlike subgroup
642/// streams, there is no delta encoding and extension headers are always
643/// length-prefixed (the length is zero when absent).
644#[derive(Debug, Clone, PartialEq, Eq)]
645pub struct FetchObject {
646 /// Group ID.
647 pub group_id: VarInt,
648 /// Subgroup ID. For objects whose Forwarding Preference is Datagram,
649 /// this is set to the Object ID.
650 pub subgroup_id: VarInt,
651 /// Object ID.
652 pub object_id: VarInt,
653 /// Publisher priority.
654 pub publisher_priority: u8,
655 /// Raw extension-header bytes (opaque sequence of Key-Value-Pairs).
656 pub extension_headers: Vec<u8>,
657 /// Object status when `payload.is_empty()`, otherwise `None`.
658 pub status: Option<ObjectStatus>,
659 /// Object payload.
660 pub payload: Vec<u8>,
661}
662
663/// The framing of one fetch object, without its payload.
664///
665/// Produced by [`FetchObject::decode_meta`] for callers that forward an
666/// object's bytes verbatim and never inspect the payload.
667#[derive(Debug, Clone, Copy, PartialEq, Eq)]
668pub struct FetchObjectMeta {
669 /// Group ID.
670 pub group_id: u64,
671 /// Subgroup ID.
672 pub subgroup_id: u64,
673 /// Object ID.
674 pub object_id: u64,
675 /// Publisher priority.
676 pub publisher_priority: u8,
677 /// Byte length of the extension-header block's contents, excluding its
678 /// length prefix.
679 pub extension_headers_len: u64,
680 /// Declared payload length. Zero when `status` is `Some`.
681 pub payload_length: u64,
682 /// Object Status wire code, present only when the payload is empty.
683 pub status: Option<u64>,
684 /// Total bytes this object occupies on the wire, prefix fields included.
685 pub wire_len: u64,
686}
687
688impl FetchObjectMeta {
689 /// Whether this object's status permits it a non-empty payload.
690 ///
691 /// The fetch-stream twin of [`SubgroupObjectMeta::payload_permission`], and
692 /// it answers on the same terms: `None` for a code this draft does not
693 /// assign, and [`PayloadPermission::Permitted`] for an absent status, which
694 /// a meta has only when its payload length is non-zero.
695 pub fn payload_permission(&self) -> Option<PayloadPermission> {
696 match self.status {
697 None => Some(PayloadPermission::Permitted),
698 Some(code) => ObjectStatus::from_u64(code).map(PayloadPermission::for_status),
699 }
700 }
701}
702
703impl FetchObject {
704 /// The status this object resolves to.
705 ///
706 /// The wire carries a status field only on an empty object, so an object
707 /// holding bytes is [`ObjectStatus::Normal`] whatever `status` says.
708 pub fn status(&self) -> ObjectStatus {
709 if self.payload.is_empty() {
710 self.status.unwrap_or(ObjectStatus::Normal)
711 } else {
712 ObjectStatus::Normal
713 }
714 }
715
716 /// Whether this object's status permits it a non-empty payload.
717 ///
718 /// Answered from the status alone, for the reason
719 /// [`SubgroupObject::permits_payload`] gives. Note that
720 /// [`Self::encode_checked`] refuses the pairing this reports on, so a
721 /// `false` here is a value that will not be written rather than one
722 /// already on the wire.
723 pub fn permits_payload(&self) -> bool {
724 PayloadPermission::for_status(self.status()).permits()
725 }
726
727 /// Encode one fetch object, refusing a value the wire shape cannot carry.
728 ///
729 /// A fetch object holds a status and a payload in the same value, and the
730 /// wire form has room for only one: the Object Status field is written only
731 /// when Object Payload Length is zero. [`Self::encode`] settles that by
732 /// following the status and dropping the payload, which loses the payload
733 /// without saying so. This refuses instead, before any byte is written, so
734 /// a rejected object leaves `buf` untouched.
735 ///
736 /// Section 10.2.1.1: "Any object with a status code other than zero MUST
737 /// have an empty payload." Normal beside a payload is therefore not a
738 /// disagreement — it is the status a payload-bearing object already has —
739 /// and it is written as an ordinary payload-bearing object.
740 ///
741 /// Extension headers on an Object Does Not Exist status are refused for the
742 /// separate reason given in Section 10.2.1.2.
743 ///
744 /// Unlike a subgroup stream, a fetch object always carries its Extension
745 /// Headers Length field, so a length of zero is an ordinary object with no
746 /// extensions and is written as such.
747 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
748 if matches!(self.status, Some(status) if status != ObjectStatus::Normal)
749 && !self.payload.is_empty()
750 {
751 return Err(CodecError::InvalidField);
752 }
753 check_extensions_against_status(
754 self.status.map(|s| s.as_u64()),
755 self.extension_headers.len() as u64,
756 )?;
757 self.encode(buf);
758 Ok(())
759 }
760
761 /// Encode one fetch object.
762 ///
763 /// Infallible because the status is taken as the authority on framing, and
764 /// lossy for the same reason: a payload set beside a status is discarded
765 /// here without a word. Prefer [`Self::encode_checked`], which refuses that
766 /// combination rather than resolving it.
767 pub fn encode(&self, buf: &mut impl BufMut) {
768 self.group_id.encode(buf);
769 self.subgroup_id.encode(buf);
770 self.object_id.encode(buf);
771 buf.put_u8(self.publisher_priority);
772 VarInt::from_u64(self.extension_headers.len() as u64).unwrap().encode(buf);
773 buf.put_slice(&self.extension_headers);
774 if let Some(status) = self.status {
775 VarInt::from_u64(0).unwrap().encode(buf);
776 VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
777 } else {
778 VarInt::from_u64(self.payload.len() as u64).unwrap().encode(buf);
779 buf.put_slice(&self.payload);
780 }
781 }
782
783 /// Decode one fetch object.
784 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
785 let group_id = VarInt::decode(buf)?;
786 let subgroup_id = VarInt::decode(buf)?;
787 let object_id = VarInt::decode(buf)?;
788 if buf.remaining() < 1 {
789 return Err(CodecError::UnexpectedEnd);
790 }
791 let publisher_priority = buf.get_u8();
792 let ext_len = VarInt::decode(buf)?.into_inner() as usize;
793 let extension_headers = crate::types::read_bytes(buf, ext_len)?;
794 let payload_length = VarInt::decode(buf)?.into_inner() as usize;
795 let (status, payload) = if payload_length == 0 {
796 let status_val = VarInt::decode(buf)?.into_inner();
797 let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
798 (Some(status), Vec::new())
799 } else {
800 (None, crate::types::read_bytes(buf, payload_length)?)
801 };
802 check_extensions_against_status(
803 status.map(|s| s.as_u64()),
804 extension_headers.len() as u64,
805 )?;
806 Ok(FetchObject {
807 group_id,
808 subgroup_id,
809 object_id,
810 publisher_priority,
811 extension_headers,
812 status,
813 payload,
814 })
815 }
816
817 /// Decode one fetch object's framing without copying its payload.
818 ///
819 /// Consumes exactly the bytes [`Self::decode`] consumes.
820 pub fn decode_meta(buf: &mut impl Buf) -> Result<FetchObjectMeta, CodecError> {
821 let start = buf.remaining();
822 let group_id = VarInt::decode(buf)?.into_inner();
823 let subgroup_id = VarInt::decode(buf)?.into_inner();
824 let object_id = VarInt::decode(buf)?.into_inner();
825 if buf.remaining() < 1 {
826 return Err(CodecError::UnexpectedEnd);
827 }
828 let publisher_priority = buf.get_u8();
829 let extension_headers_len = VarInt::decode(buf)?.into_inner();
830 skip(buf, extension_headers_len)?;
831 let payload_length = VarInt::decode(buf)?.into_inner();
832 let status = if payload_length == 0 {
833 let status_val = VarInt::decode(buf)?.into_inner();
834 Some(ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?.as_u64())
835 } else {
836 skip(buf, payload_length)?;
837 None
838 };
839 check_extensions_against_status(status, extension_headers_len)?;
840 Ok(FetchObjectMeta {
841 group_id,
842 subgroup_id,
843 object_id,
844 publisher_priority,
845 extension_headers_len,
846 payload_length,
847 status,
848 wire_len: (start - buf.remaining()) as u64,
849 })
850 }
851}
852
853// ============================================================
854// Datagram (Type 0x00..=0x07, 0x20..=0x21)
855// ============================================================
856
857/// Datagram type byte.
858///
859/// Bit layout (low nibble):
860///
861/// * bit 0 (`0x01`) — Extensions Present
862/// * bit 1 (`0x02`) — End of Group
863/// * bit 2 (`0x04`) — Object ID **absent** (when set, Object ID = 0)
864///
865/// Status variants use the high nibble (`0x20..=0x21`). Only types
866/// `0x00..=0x07`, `0x20`, `0x21` are defined.
867#[derive(Debug, Clone, Copy, PartialEq, Eq)]
868pub struct DatagramType(u8);
869
870impl DatagramType {
871 /// Raw wire byte.
872 pub fn as_u8(self) -> u8 {
873 self.0
874 }
875
876 /// Validate and wrap a raw wire byte.
877 pub fn from_u8(v: u8) -> Option<Self> {
878 if (0x00..=0x07).contains(&v) || v == 0x20 || v == 0x21 {
879 Some(DatagramType(v))
880 } else {
881 None
882 }
883 }
884
885 /// Build a payload-bearing datagram type (`0x00..=0x07`).
886 pub fn payload(object_id_present: bool, extensions_present: bool, end_of_group: bool) -> Self {
887 let mut v: u8 = 0x00;
888 if extensions_present {
889 v |= 0x01;
890 }
891 if end_of_group {
892 v |= 0x02;
893 }
894 if !object_id_present {
895 v |= 0x04;
896 }
897 DatagramType(v)
898 }
899
900 /// Build a status-only datagram type (`0x20` or `0x21`).
901 pub fn status(extensions_present: bool) -> Self {
902 if extensions_present {
903 DatagramType(0x21)
904 } else {
905 DatagramType(0x20)
906 }
907 }
908
909 /// True when the datagram carries an Object Status instead of a
910 /// payload (types `0x20` / `0x21`).
911 pub fn is_status(self) -> bool {
912 self.0 >= 0x20
913 }
914
915 /// True when the datagram carries an explicit Object ID field.
916 pub fn object_id_present(self) -> bool {
917 // Bit 2 is only meaningful in the 0x00..=0x07 range; status
918 // variants (0x20/0x21) always carry an Object ID.
919 if self.is_status() {
920 true
921 } else {
922 self.0 & 0x04 == 0
923 }
924 }
925
926 /// True if the last object of the group is conveyed.
927 pub fn end_of_group(self) -> bool {
928 !self.is_status() && (self.0 & 0x02 != 0)
929 }
930
931 /// True if extension headers are present in this datagram.
932 pub fn extensions_present(self) -> bool {
933 self.0 & 0x01 != 0
934 }
935}
936
937/// Datagram carrying a single object.
938#[derive(Debug, Clone, PartialEq, Eq)]
939pub struct DatagramObject {
940 /// Datagram type byte.
941 pub datagram_type: DatagramType,
942 /// Track alias.
943 pub track_alias: VarInt,
944 /// Group ID.
945 pub group_id: VarInt,
946 /// Object ID. Defaults to 0 when
947 /// [`DatagramType::object_id_present`] is false.
948 pub object_id: VarInt,
949 /// Publisher priority.
950 pub publisher_priority: u8,
951 /// Raw extension-header bytes (empty unless
952 /// [`DatagramType::extensions_present`] is true).
953 pub extension_headers: Vec<u8>,
954 /// Object status (only present for status-type datagrams).
955 pub status: Option<ObjectStatus>,
956 /// Object payload (empty for status-type datagrams).
957 pub payload: Vec<u8>,
958}
959
960impl DatagramObject {
961 /// Encode the datagram in full, refusing a field the type byte cannot
962 /// carry.
963 ///
964 /// Draft-14 Section 10.3.1 states the framing rule outright: "The Object
965 /// Status field and Object Payload are mutually exclusive." Types 0x00
966 /// through 0x07 carry a payload and omit the status field; types 0x20 and
967 /// 0x21 carry a status field and have no payload. This value can hold both
968 /// at once, and [`Self::encode`] resolves the disagreement by writing
969 /// whichever one the type byte announces and discarding the other without a
970 /// word. An End of Group marker written under a payload type does not
971 /// arrive late or malformed — it does not arrive at all, and the receiver
972 /// sees an ordinary object in its place; a payload written under a status
973 /// type vanishes the same way.
974 ///
975 /// [`ObjectStatus::Normal`] under a payload type is not that case and is
976 /// accepted. Section 10.2.1.1 says "Any object with a status code other
977 /// than zero MUST have an empty payload", so Normal is the status a
978 /// payload-bearing object already has, and stating it asks for exactly the
979 /// bytes leaving it out asks for.
980 ///
981 /// Errors with [`CodecError::InvalidField`] on either lossy combination,
982 /// before any byte is written, so a refused datagram leaves `buf`
983 /// untouched.
984 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
985 if self.datagram_type.is_status() {
986 if !self.payload.is_empty() {
987 return Err(CodecError::InvalidField);
988 }
989 } else if matches!(self.status, Some(status) if status != ObjectStatus::Normal) {
990 return Err(CodecError::InvalidField);
991 }
992 // The extension block is the other field the type byte governs, and it
993 // is governed in both directions. A type that announces extensions must
994 // carry some, because Section 10.3.1 makes a declared length of 0 a
995 // session-closing offence on receipt; a type that announces none cannot
996 // carry any, and `encode` would drop them in silence.
997 if self.datagram_type.extensions_present() {
998 if self.extension_headers.is_empty() {
999 return Err(CodecError::InvalidField);
1000 }
1001 } else if !self.extension_headers.is_empty() {
1002 return Err(CodecError::InvalidField);
1003 }
1004 check_extensions_against_status(
1005 self.status.map(|s| s.as_u64()),
1006 self.extension_headers.len() as u64,
1007 )?;
1008 self.encode(buf);
1009 Ok(())
1010 }
1011
1012 /// Encode the datagram in full.
1013 ///
1014 /// The type byte is taken as the authority on framing, which is what makes
1015 /// this infallible — and what makes it lossy when the value disagrees with
1016 /// itself. A `status` set under a payload type, or a `payload` set under a
1017 /// status type, is discarded here without a word. Prefer
1018 /// [`Self::encode_checked`], which refuses those combinations instead of
1019 /// resolving them.
1020 pub fn encode(&self, buf: &mut impl BufMut) {
1021 VarInt::from_u64(self.datagram_type.as_u8() as u64).unwrap().encode(buf);
1022 self.track_alias.encode(buf);
1023 self.group_id.encode(buf);
1024 if self.datagram_type.object_id_present() {
1025 self.object_id.encode(buf);
1026 }
1027 buf.put_u8(self.publisher_priority);
1028 if self.datagram_type.extensions_present() {
1029 VarInt::from_u64(self.extension_headers.len() as u64).unwrap().encode(buf);
1030 buf.put_slice(&self.extension_headers);
1031 }
1032 if self.datagram_type.is_status() {
1033 let status = self.status.unwrap_or(ObjectStatus::Normal);
1034 VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
1035 } else {
1036 buf.put_slice(&self.payload);
1037 }
1038 }
1039
1040 /// Decode a datagram. The buffer must contain the full datagram —
1041 /// payload-bearing types extend to the end of the QUIC datagram,
1042 /// which the caller is responsible for delimiting.
1043 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1044 let type_val = VarInt::decode(buf)?.into_inner();
1045 if type_val > 0xFF {
1046 return Err(CodecError::UnknownDatagramType(type_val));
1047 }
1048 let datagram_type = DatagramType::from_u8(type_val as u8)
1049 .ok_or(CodecError::UnknownDatagramType(type_val))?;
1050 let track_alias = VarInt::decode(buf)?;
1051 let group_id = VarInt::decode(buf)?;
1052 let object_id = if datagram_type.object_id_present() {
1053 VarInt::decode(buf)?
1054 } else {
1055 VarInt::from_u64(0).unwrap()
1056 };
1057 if buf.remaining() < 1 {
1058 return Err(CodecError::UnexpectedEnd);
1059 }
1060 let publisher_priority = buf.get_u8();
1061 let extension_headers = if datagram_type.extensions_present() {
1062 let ext_len = VarInt::decode(buf)?.into_inner() as usize;
1063 // Section 10.3.1: "If an endpoint receives a datagram with
1064 // Extensions Present as 'Yes' and a Extension Headers Length of 0,
1065 // it MUST close the session with PROTOCOL_VIOLATION." A datagram
1066 // with no extensions has a type byte that says so, and the two
1067 // spellings of "no extensions" are not interchangeable here.
1068 //
1069 // Subgroup streams say the opposite in Section 10.4.2 — there the
1070 // type byte is fixed for the whole stream, so an object with no
1071 // extensions has nowhere to say it but a length of 0. Only the
1072 // datagram carries this rule.
1073 if ext_len == 0 {
1074 return Err(CodecError::InvalidField);
1075 }
1076 crate::types::read_bytes(buf, ext_len)?
1077 } else {
1078 Vec::new()
1079 };
1080 let (status, payload) = if datagram_type.is_status() {
1081 let status_val = VarInt::decode(buf)?.into_inner();
1082 let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
1083 (Some(status), Vec::new())
1084 } else {
1085 let remaining = buf.remaining();
1086 (None, crate::types::read_bytes(buf, remaining)?)
1087 };
1088 check_extensions_against_status(
1089 status.map(|s| s.as_u64()),
1090 extension_headers.len() as u64,
1091 )?;
1092 Ok(DatagramObject {
1093 datagram_type,
1094 track_alias,
1095 group_id,
1096 object_id,
1097 publisher_priority,
1098 extension_headers,
1099 status,
1100 payload,
1101 })
1102 }
1103}
1104
1105#[cfg(test)]
1106mod tests {
1107 use super::*;
1108
1109 fn vi(v: u64) -> VarInt {
1110 VarInt::from_u64(v).unwrap()
1111 }
1112
1113 // ── SubgroupStreamType flag helpers ─────────────────────
1114
1115 #[test]
1116 fn subgroup_type_0x10_all_off() {
1117 let t = SubgroupStreamType::from_u8(0x10).unwrap();
1118 assert!(!t.has_subgroup_id_field());
1119 assert!(!t.subgroup_id_is_first_object());
1120 assert!(!t.extensions_present());
1121 assert!(!t.contains_end_of_group());
1122 }
1123
1124 #[test]
1125 fn subgroup_type_0x15_explicit_with_ext() {
1126 let t = SubgroupStreamType::from_u8(0x15).unwrap();
1127 assert!(t.has_subgroup_id_field());
1128 assert!(!t.subgroup_id_is_first_object());
1129 assert!(t.extensions_present());
1130 assert!(!t.contains_end_of_group());
1131 }
1132
1133 #[test]
1134 fn subgroup_type_0x1d_all_on() {
1135 let t = SubgroupStreamType::from_u8(0x1D).unwrap();
1136 assert!(t.has_subgroup_id_field());
1137 assert!(t.extensions_present());
1138 assert!(t.contains_end_of_group());
1139 }
1140
1141 #[test]
1142 fn subgroup_type_0x12_first_object() {
1143 let t = SubgroupStreamType::from_u8(0x12).unwrap();
1144 assert!(!t.has_subgroup_id_field());
1145 assert!(t.subgroup_id_is_first_object());
1146 assert!(!t.extensions_present());
1147 }
1148
1149 #[test]
1150 fn subgroup_type_rejects_undefined() {
1151 for bad in [0x00u8, 0x0F, 0x16, 0x17, 0x1E, 0x1F, 0x20] {
1152 assert!(SubgroupStreamType::from_u8(bad).is_none(), "0x{bad:02x} should be rejected");
1153 }
1154 }
1155
1156 #[test]
1157 fn subgroup_type_from_flags_roundtrip() {
1158 for &f_sg in &[false, true] {
1159 for &f_first in &[false, true] {
1160 for &f_ext in &[false, true] {
1161 for &f_eog in &[false, true] {
1162 let t = SubgroupStreamType::from_flags(f_sg, f_first, f_ext, f_eog);
1163 assert_eq!(t.has_subgroup_id_field(), f_sg);
1164 // subgroup_id_is_first_object only meaningful when
1165 // explicit field is absent
1166 if !f_sg {
1167 assert_eq!(t.subgroup_id_is_first_object(), f_first);
1168 }
1169 assert_eq!(t.extensions_present(), f_ext);
1170 assert_eq!(t.contains_end_of_group(), f_eog);
1171 }
1172 }
1173 }
1174 }
1175 }
1176
1177 // ── SubgroupHeader round-trip ───────────────────────────
1178
1179 #[test]
1180 fn subgroup_header_roundtrip_0x10() {
1181 let h = SubgroupHeader {
1182 stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1183 track_alias: vi(1),
1184 group_id: vi(0),
1185 subgroup_id: None,
1186 publisher_priority: 128,
1187 };
1188 let mut buf = Vec::new();
1189 h.encode(&mut buf);
1190 assert_eq!(buf[0], 0x10);
1191 let decoded = SubgroupHeader::decode(&mut &buf[..]).unwrap();
1192 assert_eq!(decoded, h);
1193 }
1194
1195 #[test]
1196 fn subgroup_header_roundtrip_explicit_subgroup() {
1197 let h = SubgroupHeader {
1198 stream_type: SubgroupStreamType::from_u8(0x14).unwrap(),
1199 track_alias: vi(5),
1200 group_id: vi(10),
1201 subgroup_id: Some(vi(2)),
1202 publisher_priority: 64,
1203 };
1204 let mut buf = Vec::new();
1205 h.encode(&mut buf);
1206 let decoded = SubgroupHeader::decode(&mut &buf[..]).unwrap();
1207 assert_eq!(decoded, h);
1208 }
1209
1210 #[test]
1211 fn subgroup_header_decode_rejects_bad_type() {
1212 // 0x16 falls in the gap between the two ranges Table 4 assigns,
1213 // 0x10-0x15 and 0x18-0x1D, so no table names it. Draft-14 states no
1214 // list of invalid Types the way drafts 16 and later do — the value is
1215 // simply one it does not have, which Section 10 answers by ending the
1216 // session.
1217 let buf = [0x16u8, 0x01, 0x00, 0x80];
1218 let err = SubgroupHeader::decode(&mut &buf[..]).unwrap_err();
1219 assert!(
1220 matches!(err, CodecError::UnknownStreamType(0x16)),
1221 "an unassigned Type must be named as one, got {err:?}"
1222 );
1223 }
1224
1225 #[test]
1226 fn subgroup_header_decode_does_not_call_the_fetch_type_unknown() {
1227 // 0x05 is FETCH_HEADER, which Table 4 assigns. A subgroup reader
1228 // refuses it, but the disagreement is with the caller rather than with
1229 // the draft, so it must not reach for the rule that ends the session.
1230 let buf = [0x05u8, 0x01, 0x00, 0x80];
1231 let err = SubgroupHeader::decode(&mut &buf[..]).unwrap_err();
1232 assert!(
1233 matches!(err, CodecError::InvalidField),
1234 "a fetch stream at the subgroup reader must be refused without naming the \
1235 unknown-stream-type rule, got {err:?}"
1236 );
1237 }
1238
1239 // ── Subgroup object reader (delta + extensions) ─────────
1240
1241 #[test]
1242 fn subgroup_reader_delta_sequential_ids() {
1243 // Type 0x10: no subgroup field, no extensions, no eog
1244 let header = SubgroupHeader {
1245 stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1246 track_alias: vi(1),
1247 group_id: vi(0),
1248 subgroup_id: None,
1249 publisher_priority: 0,
1250 };
1251
1252 let mut write = SubgroupObjectReader::new(&header);
1253 let mut buf = Vec::new();
1254 for i in 0..3u64 {
1255 let obj = SubgroupObject {
1256 object_id: vi(i),
1257 extension_headers: vec![],
1258 status: None,
1259 payload: vec![0xAA + i as u8; 4],
1260 };
1261 write.write_object(&obj, &mut buf).unwrap();
1262 }
1263
1264 let mut read = SubgroupObjectReader::new(&header);
1265 let mut cursor = &buf[..];
1266 let o0 = read.read_object(&mut cursor).unwrap();
1267 assert_eq!(o0.object_id.into_inner(), 0);
1268 assert_eq!(o0.payload, vec![0xAA; 4]);
1269 let o1 = read.read_object(&mut cursor).unwrap();
1270 assert_eq!(o1.object_id.into_inner(), 1);
1271 let o2 = read.read_object(&mut cursor).unwrap();
1272 assert_eq!(o2.object_id.into_inner(), 2);
1273 }
1274
1275 #[test]
1276 fn subgroup_reader_delta_sparse_ids() {
1277 // Object IDs 5, 10, 11 — deltas are 5, 4, 0
1278 let header = SubgroupHeader {
1279 stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1280 track_alias: vi(1),
1281 group_id: vi(0),
1282 subgroup_id: None,
1283 publisher_priority: 0,
1284 };
1285 let mut write = SubgroupObjectReader::new(&header);
1286 let mut buf = Vec::new();
1287 for &id in &[5u64, 10, 11] {
1288 write
1289 .write_object(
1290 &SubgroupObject {
1291 object_id: vi(id),
1292 extension_headers: vec![],
1293 status: None,
1294 payload: vec![1, 2, 3],
1295 },
1296 &mut buf,
1297 )
1298 .unwrap();
1299 }
1300 let mut read = SubgroupObjectReader::new(&header);
1301 let mut cursor = &buf[..];
1302 assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 5);
1303 assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 10);
1304 assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 11);
1305 }
1306
1307 #[test]
1308 fn subgroup_reader_with_extensions() {
1309 // Type 0x11: extensions present
1310 let header = SubgroupHeader {
1311 stream_type: SubgroupStreamType::from_u8(0x11).unwrap(),
1312 track_alias: vi(1),
1313 group_id: vi(0),
1314 subgroup_id: None,
1315 publisher_priority: 0,
1316 };
1317 let mut write = SubgroupObjectReader::new(&header);
1318 let mut buf = Vec::new();
1319 write
1320 .write_object(
1321 &SubgroupObject {
1322 object_id: vi(0),
1323 extension_headers: vec![0x01, 0x02, 0x03],
1324 status: None,
1325 payload: vec![0xFF],
1326 },
1327 &mut buf,
1328 )
1329 .unwrap();
1330 let mut read = SubgroupObjectReader::new(&header);
1331 let o = read.read_object(&mut &buf[..]).unwrap();
1332 assert_eq!(o.extension_headers, vec![0x01, 0x02, 0x03]);
1333 assert_eq!(o.payload, vec![0xFF]);
1334 }
1335
1336 #[test]
1337 fn subgroup_reader_status_object() {
1338 let header = SubgroupHeader {
1339 stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1340 track_alias: vi(1),
1341 group_id: vi(0),
1342 subgroup_id: None,
1343 publisher_priority: 0,
1344 };
1345 let mut write = SubgroupObjectReader::new(&header);
1346 let mut buf = Vec::new();
1347 write
1348 .write_object(
1349 &SubgroupObject {
1350 object_id: vi(7),
1351 extension_headers: vec![],
1352 status: Some(ObjectStatus::EndOfGroup),
1353 payload: vec![],
1354 },
1355 &mut buf,
1356 )
1357 .unwrap();
1358 let mut read = SubgroupObjectReader::new(&header);
1359 let o = read.read_object(&mut &buf[..]).unwrap();
1360 assert_eq!(o.object_id.into_inner(), 7);
1361 assert_eq!(o.status, Some(ObjectStatus::EndOfGroup));
1362 assert!(o.payload.is_empty());
1363 }
1364
1365 #[test]
1366 fn subgroup_reader_meta_matches_read_object() {
1367 // Type 0x11: extensions present, so every field is exercised.
1368 let header = SubgroupHeader {
1369 stream_type: SubgroupStreamType::from_u8(0x11).unwrap(),
1370 track_alias: vi(1),
1371 group_id: vi(0),
1372 subgroup_id: None,
1373 publisher_priority: 0,
1374 };
1375 let mut write = SubgroupObjectReader::new(&header);
1376 let mut buf = Vec::new();
1377 for (id, status) in
1378 [(0u64, None), (4, Some(ObjectStatus::EndOfGroup)), (9, None)].into_iter()
1379 {
1380 write
1381 .write_object(
1382 &SubgroupObject {
1383 object_id: vi(id),
1384 extension_headers: vec![0x0A, 0x0B],
1385 status,
1386 payload: if status.is_some() { vec![] } else { vec![0xEE; 3] },
1387 },
1388 &mut buf,
1389 )
1390 .unwrap();
1391 }
1392
1393 let mut full = SubgroupObjectReader::new(&header);
1394 let mut meta = SubgroupObjectReader::new(&header);
1395 let mut full_cursor = &buf[..];
1396 let mut meta_cursor = &buf[..];
1397 for _ in 0..3 {
1398 let before = meta_cursor.remaining();
1399 let o = full.read_object(&mut full_cursor).unwrap();
1400 let m = meta.read_object_meta(&mut meta_cursor).unwrap();
1401 assert_eq!(m.object_id, o.object_id.into_inner());
1402 assert_eq!(m.extension_headers_len, o.extension_headers.len() as u64);
1403 assert_eq!(m.payload_length, o.payload.len() as u64);
1404 assert_eq!(m.status, o.status.map(|s| s.as_u64()));
1405 assert_eq!(m.wire_len, (before - meta_cursor.remaining()) as u64);
1406 assert_eq!(full_cursor.remaining(), meta_cursor.remaining());
1407 }
1408 assert!(meta_cursor.is_empty());
1409 }
1410
1411 #[test]
1412 fn subgroup_reader_meta_short_buffer_is_unexpected_end() {
1413 let header = SubgroupHeader {
1414 stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
1415 track_alias: vi(1),
1416 group_id: vi(0),
1417 subgroup_id: None,
1418 publisher_priority: 0,
1419 };
1420 let mut write = SubgroupObjectReader::new(&header);
1421 let mut buf = Vec::new();
1422 write
1423 .write_object(
1424 &SubgroupObject {
1425 object_id: vi(0),
1426 extension_headers: vec![],
1427 status: None,
1428 payload: vec![1, 2, 3, 4],
1429 },
1430 &mut buf,
1431 )
1432 .unwrap();
1433
1434 for cut in 1..buf.len() {
1435 let mut read = SubgroupObjectReader::new(&header);
1436 let err = read.read_object_meta(&mut &buf[..cut]).unwrap_err();
1437 assert!(
1438 matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
1439 "cut {cut} gave {err:?}"
1440 );
1441 }
1442 }
1443
1444 // ── FetchHeader + FetchObject ───────────────────────────
1445
1446 #[test]
1447 fn fetch_header_roundtrip() {
1448 let h = FetchHeader { request_id: vi(99) };
1449 let mut buf = Vec::new();
1450 h.encode(&mut buf);
1451 assert_eq!(buf[0], 0x05);
1452 assert_eq!(FetchHeader::decode(&mut &buf[..]).unwrap(), h);
1453 }
1454
1455 #[test]
1456 fn fetch_header_rejects_wrong_type() {
1457 let buf = [0x10u8, 0x05];
1458 assert!(FetchHeader::decode(&mut &buf[..]).is_err());
1459 }
1460
1461 #[test]
1462 fn fetch_object_roundtrip_with_payload() {
1463 let obj = FetchObject {
1464 group_id: vi(3),
1465 subgroup_id: vi(1),
1466 object_id: vi(7),
1467 publisher_priority: 200,
1468 extension_headers: vec![0xAA, 0xBB],
1469 status: None,
1470 payload: vec![1, 2, 3, 4],
1471 };
1472 let mut buf = Vec::new();
1473 obj.encode(&mut buf);
1474 assert_eq!(FetchObject::decode(&mut &buf[..]).unwrap(), obj);
1475 }
1476
1477 #[test]
1478 fn fetch_object_roundtrip_status() {
1479 let obj = FetchObject {
1480 group_id: vi(3),
1481 subgroup_id: vi(1),
1482 object_id: vi(8),
1483 publisher_priority: 200,
1484 extension_headers: vec![],
1485 status: Some(ObjectStatus::ObjectDoesNotExist),
1486 payload: vec![],
1487 };
1488 let mut buf = Vec::new();
1489 obj.encode(&mut buf);
1490 assert_eq!(FetchObject::decode(&mut &buf[..]).unwrap(), obj);
1491 }
1492
1493 #[test]
1494 fn fetch_object_meta_matches_decode() {
1495 for obj in [
1496 FetchObject {
1497 group_id: vi(3),
1498 subgroup_id: vi(1),
1499 object_id: vi(7),
1500 publisher_priority: 200,
1501 extension_headers: vec![0xAA, 0xBB],
1502 status: None,
1503 payload: vec![1, 2, 3, 4],
1504 },
1505 FetchObject {
1506 group_id: vi(4),
1507 subgroup_id: vi(0),
1508 object_id: vi(8),
1509 publisher_priority: 1,
1510 extension_headers: vec![],
1511 status: Some(ObjectStatus::EndOfTrack),
1512 payload: vec![],
1513 },
1514 ] {
1515 let mut buf = Vec::new();
1516 obj.encode(&mut buf);
1517 let mut decode_cursor = &buf[..];
1518 let mut meta_cursor = &buf[..];
1519 let decoded = FetchObject::decode(&mut decode_cursor).unwrap();
1520 let meta = FetchObject::decode_meta(&mut meta_cursor).unwrap();
1521 assert_eq!(meta.group_id, decoded.group_id.into_inner());
1522 assert_eq!(meta.subgroup_id, decoded.subgroup_id.into_inner());
1523 assert_eq!(meta.object_id, decoded.object_id.into_inner());
1524 assert_eq!(meta.publisher_priority, decoded.publisher_priority);
1525 assert_eq!(meta.extension_headers_len, decoded.extension_headers.len() as u64);
1526 assert_eq!(meta.payload_length, decoded.payload.len() as u64);
1527 assert_eq!(meta.status, decoded.status.map(|s| s.as_u64()));
1528 assert_eq!(meta.wire_len, buf.len() as u64);
1529 assert!(meta_cursor.is_empty());
1530 assert_eq!(decode_cursor.remaining(), meta_cursor.remaining());
1531 }
1532 }
1533
1534 // ── DatagramType ────────────────────────────────────────
1535
1536 #[test]
1537 fn datagram_type_variants() {
1538 let t0 = DatagramType::from_u8(0x00).unwrap();
1539 assert!(t0.object_id_present());
1540 assert!(!t0.extensions_present());
1541 assert!(!t0.end_of_group());
1542 assert!(!t0.is_status());
1543
1544 let t7 = DatagramType::from_u8(0x07).unwrap();
1545 assert!(!t7.object_id_present()); // bit 2 set
1546 assert!(t7.extensions_present());
1547 assert!(t7.end_of_group());
1548 assert!(!t7.is_status());
1549
1550 let t20 = DatagramType::from_u8(0x20).unwrap();
1551 assert!(t20.is_status());
1552 assert!(!t20.extensions_present());
1553 // Status datagrams always carry Object ID
1554 assert!(t20.object_id_present());
1555
1556 let t21 = DatagramType::from_u8(0x21).unwrap();
1557 assert!(t21.is_status());
1558 assert!(t21.extensions_present());
1559 }
1560
1561 #[test]
1562 fn datagram_type_rejects_undefined() {
1563 for bad in [0x08u8, 0x10, 0x1F, 0x22, 0x80] {
1564 assert!(DatagramType::from_u8(bad).is_none(), "0x{bad:02x}");
1565 }
1566 }
1567
1568 // ── DatagramObject round-trip ───────────────────────────
1569
1570 #[test]
1571 fn datagram_object_0x00_roundtrip() {
1572 let d = DatagramObject {
1573 datagram_type: DatagramType::from_u8(0x00).unwrap(),
1574 track_alias: vi(1),
1575 group_id: vi(2),
1576 object_id: vi(3),
1577 publisher_priority: 100,
1578 extension_headers: vec![],
1579 status: None,
1580 payload: vec![0xDE, 0xAD, 0xBE, 0xEF],
1581 };
1582 let mut buf = Vec::new();
1583 d.encode(&mut buf);
1584 assert_eq!(DatagramObject::decode(&mut &buf[..]).unwrap(), d);
1585 }
1586
1587 #[test]
1588 fn datagram_object_0x04_no_object_id() {
1589 // 0x04: no object id field, implicit 0
1590 let d = DatagramObject {
1591 datagram_type: DatagramType::from_u8(0x04).unwrap(),
1592 track_alias: vi(1),
1593 group_id: vi(2),
1594 object_id: vi(0),
1595 publisher_priority: 100,
1596 extension_headers: vec![],
1597 status: None,
1598 payload: vec![0xAA],
1599 };
1600 let mut buf = Vec::new();
1601 d.encode(&mut buf);
1602 let decoded = DatagramObject::decode(&mut &buf[..]).unwrap();
1603 assert_eq!(decoded, d);
1604 }
1605
1606 #[test]
1607 fn datagram_object_0x21_status_with_extensions() {
1608 let d = DatagramObject {
1609 datagram_type: DatagramType::from_u8(0x21).unwrap(),
1610 track_alias: vi(9),
1611 group_id: vi(4),
1612 object_id: vi(11),
1613 publisher_priority: 50,
1614 extension_headers: vec![0xCA, 0xFE],
1615 status: Some(ObjectStatus::EndOfTrack),
1616 payload: vec![],
1617 };
1618 let mut buf = Vec::new();
1619 d.encode(&mut buf);
1620 assert_eq!(DatagramObject::decode(&mut &buf[..]).unwrap(), d);
1621 }
1622
1623 /// A datagram under `type_byte` holding both `status` and `payload`.
1624 fn datagram(type_byte: u8, status: Option<ObjectStatus>, payload: Vec<u8>) -> DatagramObject {
1625 DatagramObject {
1626 datagram_type: DatagramType::from_u8(type_byte).unwrap(),
1627 track_alias: vi(1),
1628 group_id: vi(0),
1629 object_id: vi(0),
1630 publisher_priority: 128,
1631 extension_headers: vec![],
1632 status,
1633 payload,
1634 }
1635 }
1636
1637 /// Neither of the two fields the type byte cannot carry is dropped in
1638 /// silence; both are refused.
1639 ///
1640 /// Draft-14 Section 10.3.1: "The Object Status field and Object Payload are
1641 /// mutually exclusive." Types 0x00 through 0x07 write a payload and no
1642 /// status; types 0x20 and 0x21 write a status and no payload. A
1643 /// [`DatagramObject`] can hold both at once, and [`DatagramObject::encode`]
1644 /// resolves that by writing whichever the type byte announces and
1645 /// discarding the other — the loss this gate exists for. The middle of each
1646 /// half observes the discard directly, so the gate states the old behaviour
1647 /// as well as the new.
1648 ///
1649 /// Normal beside a payload is exempt and checked at the end. Section
1650 /// 10.2.1.1 says "Any object with a status code other than zero MUST have
1651 /// an empty payload", so Normal is the status a payload-bearing object
1652 /// already has, and naming it asks for the same bytes as leaving it out.
1653 ///
1654 /// # What this catches, observed by making each change and running it
1655 ///
1656 /// Dropping the status half of the check, leaving the type byte to decide
1657 /// as it did before:
1658 ///
1659 /// ```text
1660 /// encode_checked must refuse ObjectDoesNotExist under a payload type; got Ok(())
1661 /// ```
1662 ///
1663 /// Dropping the payload half instead:
1664 ///
1665 /// ```text
1666 /// encode_checked must refuse a payload under a status type; got Ok(())
1667 /// ```
1668 ///
1669 /// Widening the status half to refuse Normal beside a payload as well:
1670 ///
1671 /// ```text
1672 /// encode_checked refused a Normal status under a payload type: InvalidField
1673 /// ```
1674 #[test]
1675 fn encode_checked_refuses_the_field_the_type_byte_cannot_carry() {
1676 for &status in ObjectStatus::ALL {
1677 if status == ObjectStatus::Normal {
1678 continue;
1679 }
1680
1681 // A status under a payload type: the status is what would go.
1682 let object = datagram(0x00, Some(status), vec![0xDE, 0xAD]);
1683 let mut refused = Vec::new();
1684 let result = object.encode_checked(&mut refused);
1685 assert!(
1686 matches!(result, Err(CodecError::InvalidField)),
1687 "encode_checked must refuse {status:?} under a payload type; got {result:?}"
1688 );
1689 assert!(refused.is_empty(), "a refused {status:?} datagram still wrote {refused:?}");
1690
1691 let mut dropped = Vec::new();
1692 object.encode(&mut dropped);
1693 let decoded = DatagramObject::decode(&mut &dropped[..])
1694 .unwrap_or_else(|e| panic!("the lossy encoding of {status:?} must parse: {e:?}"));
1695 assert_eq!(decoded.status, None, "{status:?} is exactly what `encode` loses here");
1696 assert_eq!(decoded.payload, vec![0xDE, 0xAD]);
1697
1698 // The same status under a status type is representable, so it is
1699 // written and read back unchanged.
1700 let mut carried = Vec::new();
1701 datagram(0x20, Some(status), vec![]).encode_checked(&mut carried).unwrap_or_else(|e| {
1702 panic!("encode_checked refused a status-type {status:?}: {e:?}")
1703 });
1704 let decoded = DatagramObject::decode(&mut &carried[..]).unwrap();
1705 assert_eq!(decoded.status, Some(status), "{status:?} lost its status");
1706 }
1707
1708 // A payload under a status type: now the payload is what would go.
1709 let object = datagram(0x20, Some(ObjectStatus::EndOfGroup), vec![0xDE, 0xAD]);
1710 let mut refused = Vec::new();
1711 let result = object.encode_checked(&mut refused);
1712 assert!(
1713 matches!(result, Err(CodecError::InvalidField)),
1714 "encode_checked must refuse a payload under a status type; got {result:?}"
1715 );
1716 assert!(refused.is_empty(), "a refused datagram still wrote {refused:?}");
1717
1718 let mut dropped = Vec::new();
1719 object.encode(&mut dropped);
1720 let decoded = DatagramObject::decode(&mut &dropped[..]).unwrap();
1721 assert!(decoded.payload.is_empty(), "the payload is exactly what `encode` loses here");
1722 assert_eq!(decoded.status, Some(ObjectStatus::EndOfGroup));
1723
1724 // Normal beside a payload asks for the bytes a payload datagram
1725 // already writes, so it is accepted and writes exactly those.
1726 let mut named = Vec::new();
1727 datagram(0x00, Some(ObjectStatus::Normal), vec![0xDE, 0xAD])
1728 .encode_checked(&mut named)
1729 .unwrap_or_else(|e| {
1730 panic!("encode_checked refused a Normal status under a payload type: {e:?}")
1731 });
1732 let mut unnamed = Vec::new();
1733 datagram(0x00, None, vec![0xDE, 0xAD]).encode_checked(&mut unnamed).unwrap();
1734 assert_eq!(named, unnamed, "naming Normal must ask for the bytes leaving it out asks for");
1735 }
1736}