moqtap_codec/draft16/data_stream.rs
1//! Draft-16 data stream header encoding and decoding.
2//!
3//! Draft-16 subgroup type-byte flag layout:
4//! - `& 0x01`: extensions present on objects
5//! - `& 0x06`: SUBGROUP_ID_MODE. Section 10.4.2: "The SUBGROUP_ID_MODE field
6//! (bits 1-2, mask 0x06) is a two-bit field that determines the encoding of
7//! the Subgroup ID. To extract this value, perform a bitwise AND with mask
8//! 0x06 and right-shift by 1 bit". Mode 0 puts no Subgroup ID on the wire
9//! and it is zero, mode 1 takes it from the first object, mode 2 reads it as
10//! a field after the Group ID, and mode 3 is reserved
11//! - `& 0x08`: end-of-group marker
12//! - `& 0x20`: no publisher_priority (0x30+ types)
13//!
14//! Because the mode is one field and not two independent bits, the three
15//! carriers are mutually exclusive: a Type setting both `0x02` and `0x04` names
16//! the reserved mode, not two carriers at once. This module described those bits
17//! separately and read them separately, which let a single Type answer to two
18//! carriers.
19//!
20//! Draft-15 spells the same three carriers out of the same two bits, as a pair
21//! of table columns rather than a named field, and the two drafts end up
22//! admitting the same twenty-four Types — draft-16 by excluding the reserved
23//! mode from the ranges `0x10`-`0x1F` and `0x30`-`0x3F`, draft-15 by listing
24//! them.
25//!
26//! Draft-16 datagram type-byte flag layout:
27//! - `0x01`: extensions present (byte-length-prefixed blob)
28//! - `0x02`: end-of-group
29//! - `0x04`: no object_id (object_id = 0 implied)
30//! - `0x08`: default priority (priority omitted, inherited)
31//! - `0x20`: status datagram (carries object_status instead of payload)
32//!
33//! Draft-16 fetch objects are framed differently again: a per-object
34//! Serialization Flags varint decides which of Group ID, Subgroup ID, Object ID,
35//! Priority and Extensions are on the wire, and a field left off is taken from
36//! the object before it. See [`FetchObjectHeader`] for the flag layout and
37//! [`FetchObjectReader`] for the inheritance. A fetch object carries no Object
38//! Status at all — draft-16 Section 10.2.1.1 puts that field on subscription
39//! deliveries only.
40//!
41//! Extension headers in draft-16 are byte-length-prefixed opaque blobs
42//! (not count-prefixed as in draft-14).
43
44use super::types::ObjectStatus;
45use crate::error::CodecError;
46use crate::varint::VarInt;
47use bytes::{Buf, BufMut};
48
49/// Advance `buf` past `len` bytes without copying them.
50fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
51 let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
52 if buf.remaining() < len {
53 return Err(CodecError::UnexpectedEnd);
54 }
55 buf.advance(len);
56 Ok(())
57}
58
59/// Turn a wire Object Status code into the status draft-16 gives it, refusing
60/// any code the draft does not assign.
61///
62/// Draft-16 Section 10.2.1.1 lists the codes an object may carry and says any
63/// other value SHOULD be treated as a protocol error and the session closed
64/// with a PROTOCOL_VIOLATION. Every place this module reads a status runs the
65/// wire code through here. Draft-16 is where the set narrowed to three: 0x1,
66/// which drafts 07-15 assign to Object Does Not Exist, is refused here.
67///
68/// [`SubgroupObject::object_status`] and [`DatagramHeader::object_status`]
69/// then store the [`ObjectStatus`] this returns rather than the raw code, so
70/// the refusal is not something a future decode site can forget: those fields
71/// cannot hold an unassigned value at all, in either direction, and the encode
72/// paths need no check of their own.
73/// [`SubgroupObjectMeta::status`] deliberately keeps the raw code — it is a
74/// decode-only view that never feeds an encoder — but it is filtered through
75/// here too, so the two readers agree byte for byte on what parses.
76fn decoded_status(code: u64) -> Result<ObjectStatus, CodecError> {
77 ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)
78}
79
80/// Whether an Object at `status` is allowed to carry `extensions_len` bytes of
81/// extension headers.
82///
83/// Draft-16 Section 10.2.1.2: "Any Object with status Normal can have extension
84/// headers", with a reference to Section 2.5 inside the sentence, and "If an
85/// endpoint receives extension headers on Objects with status that is not
86/// Normal, it MUST close the session with a PROTOCOL_VIOLATION."
87///
88/// Draft-16 is the draft where the status set narrowed to Normal, End of Group
89/// and End of Track, so the rule reaches two codes rather than the single
90/// "Object Does Not Exist" earlier drafts name, draft-15 Section 10.2.1.1 among
91/// them. It reaches every carrier that can announce a status: an Object on a
92/// subgroup stream and a status datagram. An Object on a fetch stream carries
93/// no status field on this draft — Section 10.2.1.1 puts the field on
94/// subscription deliveries only — so it is the one carrier that cannot break
95/// the rule.
96///
97/// So this is `false` for exactly one shape: a non-empty extension block on an
98/// Object whose status is not Normal. An Object with no extensions is fine at
99/// any status, and an Object at Normal may carry any extensions.
100///
101/// `status` is taken as a raw code so the two subgroup readers can share this:
102/// one resolves the status into [`ObjectStatus`] and the other keeps the wire
103/// code. `None` means the Object carried a payload, which is Normal by
104/// definition and always permitted.
105///
106/// # Why the readers do not apply this themselves
107///
108/// A deliberate contrast with the payload rule beside it. A status next to a
109/// payload has no encoding — the status field and the payload occupy the same
110/// position on the wire — so a writer refuses that pair as unrepresentable.
111/// Extensions next to a status encode perfectly well: the block sits between
112/// the extensions length and the status field and reads back byte for byte. The
113/// frame is well formed and merely non-conforming, which is a judgement about
114/// what a peer may send, not about what the bytes mean.
115///
116/// A decoder that refused it could not report the violation, and a writer that
117/// refused it could not reproduce a capture containing one — including the
118/// committed `subgroup-extensions-status-object` vector, which is exactly this
119/// frame. The rule addresses an endpoint *receiving* such an Object, so the
120/// endpoint is where it is enforced. This predicate is what it asks.
121fn extensions_permitted_at(status: Option<u64>, extensions_len: u64) -> bool {
122 match status {
123 None => true,
124 Some(code) => extensions_len == 0 || code == ObjectStatus::Normal.as_u64(),
125 }
126}
127
128// ── Subgroup and datagram Type validation ─────────────────────
129//
130// Both Types are read as a single byte rather than as a varint. Every Type
131// draft-16 assigns to either is below 0x40, which is exactly the one-byte range
132// of the varint encoding this draft uses, and both forms independently forbid
133// bits 6 and 7 — so a first byte that begins a longer varint is a Type the
134// draft rules out anyway.
135//
136// Reading a full varint and narrowing it to `u8` was the defect: a Type of
137// 0x110, spelled as the two-byte varint 0x41 0x10, truncated to 0x10 and was
138// accepted as a valid subgroup header. An out-of-range Type aliased onto a
139// valid one instead of being refused, and the stream behind it was parsed under
140// framing its sender never asked for.
141
142/// Bit 4, which every subgroup header Type sets.
143const SUBGROUP_BASE_BIT: u8 = 0x10;
144/// Bits 6 and 7, which the subgroup header's 0b00X1XXXX form leaves clear.
145///
146/// Narrower than the equivalent on draft-19, whose form is 0b0XX1XXXX and which
147/// therefore admits 0x50..0x5F and 0x70..0x7F as well. Draft-16 Section 10.4.2
148/// names only "the ranges 0x10..0x1F and 0x30..0x3F".
149const SUBGROUP_FORM_FORBIDDEN_BITS: u8 = 0xC0;
150/// Bits 1-2, the SUBGROUP_ID_MODE field.
151const SUBGROUP_ID_MODE_MASK: u8 = 0x06;
152/// The SUBGROUP_ID_MODE value draft-16 reserves, once the mask is applied and
153/// the field shifted down.
154const SUBGROUP_ID_MODE_RESERVED: u8 = 0b11;
155
156/// Refuse a subgroup header Type value draft-16 Section 10.4.2 lists as invalid.
157///
158/// The section gives two lists and says of both that an endpoint receiving a
159/// stream header with such a Type MUST close the session with a
160/// PROTOCOL_VIOLATION:
161///
162/// - "Type values with SUBGROUP_ID_MODE set to 0b11: 0x16, 0x17, 0x1E, 0x1F,
163/// 0x36, 0x37, 0x3E, 0x3F. This mode is reserved for future use."
164/// - "Type values that do not match the form 0b00X1XXXX (i.e., Type values
165/// outside the ranges 0x10..0x1F and 0x30..0x3F, or values where bit 4 is
166/// not set)."
167///
168/// The reserved mode is worth separating from a merely unassigned code point,
169/// because it is not decodable rather than merely unknown. The other three modes
170/// each say whether a Subgroup ID field follows the Group ID; 0b11 says nothing,
171/// so a decoder has to guess, and a wrong guess shifts every later field by the
172/// width of that varint. This module read those eight Types as though an
173/// explicit Subgroup ID were present, which turned a header the draft says to
174/// reject into objects with plausible, wrong contents.
175///
176/// Which of the two lists a Type failed is what `stream_type_error` reports,
177/// and the answer is not the same error: one is a Type no table assigns and the
178/// other is a Type this draft assigns a form to and then forbids.
179fn validate_subgroup_type(raw: u64) -> Result<(), CodecError> {
180 if subgroup_type_is_valid(raw) {
181 Ok(())
182 } else {
183 Err(stream_type_error(raw))
184 }
185}
186
187/// The unidirectional stream Type draft-16 Section 10.4.4 gives a fetch stream.
188const FETCH_STREAM_TYPE: u64 = 0x05;
189
190/// Refuse a Type field spelled in more than one byte, before anything narrows
191/// it to a byte.
192///
193/// Returns `Ok(None)` when the next Type is a single byte and the caller should
194/// read it itself, `Ok(Some(err))` when it is wider and `refusal` has named the
195/// failure, and `Err` only when the buffer does not hold the whole field yet.
196///
197/// Every Type draft-16 assigns is below 0x40 and so occupies one byte under
198/// this draft's variable-length integer encoding. A wider spelling is therefore
199/// one of two things, and neither may be read as a header: a Type this draft
200/// does not assign, or a non-minimal spelling of one it does. The second is the
201/// dangerous one — narrowing a two-byte 0x4001 to its low octet turns it into
202/// the assigned Type 0x01, so a peer could name any Type it liked and have it
203/// parsed as another.
204///
205/// The full varint is decoded before `refusal` sees it, so the refusal reports
206/// the number the field actually carried rather than its first byte.
207fn wide_type_refusal(
208 buf: &mut impl Buf,
209 refusal: fn(u64) -> CodecError,
210) -> Result<Option<CodecError>, CodecError> {
211 if !buf.has_remaining() {
212 return Err(CodecError::UnexpectedEnd);
213 }
214 // Under the draft-16 encoding the top two bits of the first byte give the
215 // field's length, so a first byte below 0x40 is the whole of it.
216 if buf.chunk()[0] < 0x40 {
217 return Ok(None);
218 }
219 let raw = VarInt::decode(buf)?.into_inner();
220 Ok(Some(refusal(raw)))
221}
222
223/// The refusal a datagram Type deserves, as a plain function so
224/// `wide_type_refusal` can take it.
225fn datagram_type_refusal(raw: u64) -> CodecError {
226 match validate_datagram_type(raw) {
227 Ok(()) => CodecError::InvalidField,
228 Err(e) => e,
229 }
230}
231
232/// Whether `raw` is a subgroup Type draft-16 admits: inside the form, and not
233/// the reserved SUBGROUP_ID_MODE.
234fn subgroup_type_is_valid(raw: u64) -> bool {
235 raw <= 0xFF && {
236 let t = raw as u8;
237 t & SUBGROUP_FORM_FORBIDDEN_BITS == 0
238 && t & SUBGROUP_BASE_BIT != 0
239 && (t & SUBGROUP_ID_MODE_MASK) >> 1 != SUBGROUP_ID_MODE_RESERVED
240 }
241}
242
243/// Whether `raw` sits inside the subgroup form but names the reserved
244/// SUBGROUP_ID_MODE — the first of Section 10.4.2's two lists.
245fn subgroup_type_is_reserved_mode(raw: u64) -> bool {
246 raw <= 0xFF && {
247 let t = raw as u8;
248 t & SUBGROUP_FORM_FORBIDDEN_BITS == 0
249 && t & SUBGROUP_BASE_BIT != 0
250 && (t & SUBGROUP_ID_MODE_MASK) >> 1 == SUBGROUP_ID_MODE_RESERVED
251 }
252}
253
254/// Which failure a leading unidirectional stream Type that is not the one a
255/// reader wants is.
256///
257/// Draft-16 states two rules about such a Type and answers both with a close,
258/// and telling them apart is the whole job of this function.
259///
260/// Section 10 is about the table: "An endpoint that receives an unknown stream
261/// or datagram type MUST close the session." A Type the table does not assign
262/// is [`CodecError::UnknownStreamType`]. The table has two entries — 0x05 for
263/// FETCH_HEADER and the subgroup form — because draft-16 carries its control
264/// messages on a bidirectional stream and defines no padding stream, so
265/// FETCH_HEADER is the only assigned value a subgroup reader can be handed.
266///
267/// Section 10.4.2 is about the subgroup form specifically, and the eight Types
268/// inside it that name the reserved SUBGROUP_ID_MODE. Those are not unknown —
269/// the form is assigned and the draft lists the values outright — but they are
270/// unreadable, and they are [`CodecError::InvalidStreamTypeValue`].
271///
272/// A fetch stream at the subgroup reader is neither. The value is one this
273/// draft defines, the disagreement is with the reader that was called, and the
274/// session survives it.
275fn stream_type_error(raw: u64) -> CodecError {
276 if raw == FETCH_STREAM_TYPE || subgroup_type_is_valid(raw) {
277 CodecError::InvalidField
278 } else if subgroup_type_is_reserved_mode(raw) {
279 CodecError::InvalidStreamTypeValue {
280 raw,
281 detail: "its SUBGROUP_ID_MODE is 0b11, which this draft reserves",
282 }
283 } else {
284 CodecError::UnknownStreamType(raw)
285 }
286}
287
288/// The END_OF_GROUP bit of a datagram Type.
289const DATAGRAM_END_OF_GROUP_BIT: u8 = 0x02;
290/// The STATUS bit of a datagram Type.
291const DATAGRAM_STATUS_BIT: u8 = 0x20;
292/// Bits 4, 6 and 7, which the datagram's 0b00X0XXXX form leaves clear.
293const DATAGRAM_FORM_FORBIDDEN_BITS: u8 = 0xD0;
294
295/// Refuse a datagram Type value draft-16 Section 10.3.1 lists as invalid.
296///
297/// The section gives two lists and says of both that an endpoint receiving a
298/// datagram with such a Type MUST close the session with a PROTOCOL_VIOLATION:
299///
300/// - "Type values with both the STATUS bit (0x20) and END_OF_GROUP bit (0x02)
301/// set: 0x22, 0x23, 0x26, 0x27, 0x2A, 0x2B, 0x2E, 0x2F. An object status
302/// message cannot signal end of group."
303/// - "Type values that do not match the form 0b00X0XXXX (i.e., Type values
304/// outside the ranges 0x00..0x0F and 0x20..0x2F)."
305///
306/// That leaves 24 of the 256 byte values valid. Without this check every other
307/// one is accepted, and bit 4 is the bit that separates a datagram Type from a
308/// subgroup stream header Type — so an unchecked datagram Type could name a
309/// stream header and be parsed as a datagram anyway.
310///
311/// The two lists are not the same failure. A Type outside the form is one no
312/// table assigns, and so is [`CodecError::UnknownDatagramType`]; a Type inside
313/// the form setting STATUS and END_OF_GROUP together is one this draft names
314/// and forbids, and so is [`CodecError::InvalidDatagramTypeValue`].
315fn validate_datagram_type(raw: u64) -> Result<(), CodecError> {
316 if datagram_type_is_valid(raw) {
317 Ok(())
318 } else if datagram_type_is_status_end_of_group(raw) {
319 Err(CodecError::InvalidDatagramTypeValue {
320 raw,
321 detail: "it sets both the STATUS bit and the END_OF_GROUP bit",
322 })
323 } else {
324 Err(CodecError::UnknownDatagramType(raw))
325 }
326}
327
328/// Whether `raw` is a datagram Type draft-16 admits.
329fn datagram_type_is_valid(raw: u64) -> bool {
330 raw <= 0xFF && {
331 let t = raw as u8;
332 t & DATAGRAM_FORM_FORBIDDEN_BITS == 0
333 && !(t & DATAGRAM_STATUS_BIT != 0 && t & DATAGRAM_END_OF_GROUP_BIT != 0)
334 }
335}
336
337/// Whether `raw` sits inside the datagram form but sets STATUS and END_OF_GROUP
338/// together — the first of Section 10.3.1's two lists.
339fn datagram_type_is_status_end_of_group(raw: u64) -> bool {
340 raw <= 0xFF && {
341 let t = raw as u8;
342 t & DATAGRAM_FORM_FORBIDDEN_BITS == 0
343 && t & DATAGRAM_STATUS_BIT != 0
344 && t & DATAGRAM_END_OF_GROUP_BIT != 0
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct SubgroupHeader {
350 pub header_type: u8,
351 pub track_alias: VarInt,
352 pub group_id: VarInt,
353 pub subgroup_id: VarInt,
354 pub publisher_priority: Option<u8>,
355}
356
357impl SubgroupHeader {
358 pub fn has_extensions(&self) -> bool {
359 self.header_type & 0x01 != 0
360 }
361
362 /// Whether SUBGROUP_ID_MODE is 1: the Subgroup ID is the first object's
363 /// Object ID and is not transmitted on the wire.
364 ///
365 /// Reads the two bits as the one field Section 10.4.2 defines, rather than
366 /// as the single bit `0x02`. The single-bit reading answered `true` for a
367 /// Type naming the reserved mode 3, where `0x02` and `0x04` are both set —
368 /// so this predicate and [`Self::has_explicit_subgroup_id`] answered `true`
369 /// together, for a state one two-bit field cannot be in.
370 pub fn subgroup_id_from_first_object(&self) -> bool {
371 (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1 == 1
372 }
373
374 /// Whether SUBGROUP_ID_MODE is 2: an explicit Subgroup ID field follows the
375 /// Group ID.
376 ///
377 /// [`Self::decode`] refuses the reserved mode before reading any field, so
378 /// on a decoded header this agrees with the single-bit `0x04` reading it
379 /// replaces. The two differ only on a header built by hand, which is the
380 /// only way to hold a reserved-mode Type at all.
381 pub fn has_explicit_subgroup_id(&self) -> bool {
382 (self.header_type & SUBGROUP_ID_MODE_MASK) >> 1 == 2
383 }
384
385 pub fn has_end_of_group(&self) -> bool {
386 self.header_type & 0x08 != 0
387 }
388
389 pub fn has_priority(&self) -> bool {
390 self.header_type & 0x20 == 0
391 }
392
393 /// Serialize the header exactly as its Type byte describes it.
394 ///
395 /// Infallible, and so willing to write a Type draft-16 Section 10.4.2 tells
396 /// an endpoint to reject — including a reserved-mode Type this module's own
397 /// [`Self::decode`] refuses to read back. Prefer [`Self::encode_checked`],
398 /// which refuses those Types instead.
399 ///
400 /// A reserved-mode Type gets no Subgroup ID field, because mode 3 defines
401 /// none: the draft says what the other three modes put on the wire and
402 /// nothing about this one. Writing the field there was an artifact of
403 /// reading `0x04` on its own, and it disagreed with every neighbouring
404 /// draft — 15, 17, 18 and 19 all leave it off.
405 ///
406 /// Every field is driven by the Type byte, because that is the only thing
407 /// [`Self::decode`] and the peer have to go on — the Priority byte
408 /// included. Driving that one off `publisher_priority` being `Some`
409 /// instead desyncs the stream: a Type with DEFAULT_PRIORITY (`0x20`) set
410 /// beside a `Some` writes a stray byte the reader takes for the first
411 /// Object's Object ID Delta, and a Type with the bit clear beside a `None`
412 /// leaves the reader taking that Delta for the priority. Either way every
413 /// later field shifts — which is precisely the disagreement
414 /// [`Self::encode_checked`]'s doc comment says this pair must not have.
415 ///
416 /// A `None` under a Type whose bit is clear therefore writes 128 rather
417 /// than dropping the byte. Draft-16 Section 11.1.1.1: "A subscription has
418 /// Publisher Priorty 128 if this extension is omitted", so 128 is this
419 /// draft's own name for an unstated priority and not an invented filler.
420 /// Drafts 17-21 write the same value in the same place. Use
421 /// [`Self::encode_checked`] to be told about the disagreement rather than
422 /// having it resolved silently.
423 pub fn encode(&self, buf: &mut impl BufMut) {
424 VarInt::from_usize(self.header_type as usize).encode(buf);
425 self.track_alias.encode(buf);
426 self.group_id.encode(buf);
427 if self.has_explicit_subgroup_id() {
428 self.subgroup_id.encode(buf);
429 }
430 if self.has_priority() {
431 buf.put_u8(self.publisher_priority.unwrap_or(128));
432 }
433 }
434
435 /// Serialize the header, refusing a Type value draft-16 forbids.
436 ///
437 /// Errors with [`CodecError::InvalidField`] for exactly the Types Section
438 /// 10.4.2 lists as invalid — the same set [`Self::decode`] refuses — before
439 /// any byte is written, so a refused header leaves `buf` untouched.
440 ///
441 /// The check belongs on this side as well because the two halves would
442 /// otherwise disagree about which streams exist: a reserved-mode header
443 /// written by [`Self::encode`] cannot be read back by [`Self::decode`], and
444 /// a codec used to rewrite captured traffic would emit a stream it could not
445 /// then parse.
446 ///
447 /// That argument covers the Priority byte too. `publisher_priority`
448 /// disagreeing with the Type byte's DEFAULT_PRIORITY bit (`0x20`) is the
449 /// same failure one field along: a header that does not mean what it says,
450 /// whose bytes the peer reads under framing its writer never asked for.
451 /// [`Self::encode`] does not desync the stream over it — the byte follows
452 /// the Type byte — but resolving the disagreement is not the same as being
453 /// told about it, and the caller who set the wrong half is the only one who
454 /// can fix it. Same check, same reason, as draft-15 Section 10.4.2's
455 /// encoder.
456 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
457 validate_subgroup_type(self.header_type as u64)?;
458 if self.has_priority() != self.publisher_priority.is_some() {
459 return Err(CodecError::InvalidField);
460 }
461 self.encode(buf);
462 Ok(())
463 }
464
465 /// Decode a subgroup header, Type field included.
466 ///
467 /// A Type spelled in more than one byte is refused before it is narrowed,
468 /// by `wide_type_refusal`. Every Type draft-16 assigns fits a single
469 /// byte, so a wider spelling is either a Type this draft does not have or a
470 /// non-minimal spelling of one it does, and both have to be refused rather
471 /// than truncated into a Type that happens to be valid.
472 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
473 if let Some(err) = wide_type_refusal(buf, stream_type_error)? {
474 return Err(err);
475 }
476 let header_type = buf.get_u8();
477 validate_subgroup_type(header_type as u64)?;
478 let track_alias = VarInt::decode(buf)?;
479 let group_id = VarInt::decode(buf)?;
480 // `validate_subgroup_type` has already refused the reserved mode, so
481 // the field is read for mode 2 and for nothing else. Reading the mask
482 // rather than the `0x04` bit makes that independent of the check above
483 // instead of contingent on it.
484 let subgroup_id = if (header_type & SUBGROUP_ID_MODE_MASK) >> 1 == 2 {
485 VarInt::decode(buf)?
486 } else {
487 VarInt::from_usize(0)
488 };
489 let publisher_priority = if header_type & 0x20 == 0 {
490 if buf.remaining() < 1 {
491 return Err(CodecError::UnexpectedEnd);
492 }
493 Some(buf.get_u8())
494 } else {
495 None
496 };
497 Ok(Self { header_type, track_alias, group_id, subgroup_id, publisher_priority })
498 }
499}
500
501/// One object within a draft-16 subgroup stream with its Object ID
502/// already resolved from the delta encoding. See
503/// [`SubgroupObjectReader`] for stateful encode/decode.
504#[derive(Debug, Clone, PartialEq, Eq)]
505pub struct SubgroupObject {
506 pub object_id: VarInt,
507 /// Raw extension-header bytes, excluding the byte-length prefix that
508 /// precedes them on the wire. Empty when the stream header does not
509 /// set the extensions-present bit, or when the block is present but
510 /// zero-length. Opaque: [`SubgroupObjectReader::write_object`] re-emits
511 /// the prefix and these bytes verbatim.
512 pub extension_headers: Vec<u8>,
513 pub payload_length: VarInt,
514 /// Object status; `Some` when `payload_length == 0`.
515 ///
516 /// The wire field is a varint, so it can carry any value up to 2^62-1;
517 /// draft-16 Section 10.2.1.1 assigns three of them and says a peer SHOULD
518 /// treat the rest as a protocol error. This field is typed to the assigned
519 /// set, so it refuses to hold the codes the draft leaves unassigned —
520 /// including 0x1, Object Does Not Exist, which drafts 07-15 assign and
521 /// draft-16 dropped. That makes the refusal a property of the struct
522 /// rather than of any one code path: an encoder cannot be handed a status
523 /// the draft does not define, and does not have to check.
524 ///
525 /// `None` on a zero-length object means the same thing as
526 /// [`ObjectStatus::Normal`] and encodes as it; the wire field is not
527 /// optional once `payload_length` is zero.
528 pub object_status: Option<ObjectStatus>,
529 pub payload: Vec<u8>,
530}
531
532impl SubgroupObject {
533 /// The status this Object resolves to.
534 ///
535 /// The wire carries a status field only on a zero-length Object, so an
536 /// Object holding bytes is [`ObjectStatus::Normal`] whatever
537 /// [`Self::object_status`] says — draft-16 Section 10.2.1.1: "This status is
538 /// implicit for any non-zero length object."
539 pub fn status(&self) -> ObjectStatus {
540 if self.payload_length.into_inner() == 0 {
541 self.object_status.unwrap_or(ObjectStatus::Normal)
542 } else {
543 ObjectStatus::Normal
544 }
545 }
546
547 /// Whether this Object's status is allowed to carry the extension headers
548 /// it has.
549 ///
550 /// `false` for exactly one shape: a non-empty extension block on an Object
551 /// whose status is not [`ObjectStatus::Normal`]. See
552 /// `extensions_permitted_at` for the rule and for why neither
553 /// [`SubgroupObjectReader::read_object`] nor
554 /// [`SubgroupObjectReader::write_object`] applies it — this is the codec's
555 /// way of reporting the violation to the endpoint that must act on it,
556 /// rather than refusing bytes that are well formed.
557 pub fn extensions_permitted(&self) -> bool {
558 extensions_permitted_at(
559 self.object_status.map(|s| s.as_u64()),
560 self.extension_headers.len() as u64,
561 )
562 }
563}
564
565/// Whether an Object carrying a given status is allowed a non-empty payload.
566///
567/// Draft-16 Section 10.2.1.1 states the rule in one sentence — "Any object with
568/// a status code other than zero MUST have an empty payload" — and the same
569/// section makes Normal (0x0) "implicit for any non-zero length object". So the
570/// permission follows from the status code alone, with no payload length in
571/// hand, which is what makes it worth naming: a caller that has only an
572/// object's framing can ask whether the bytes it is about to forward are
573/// allowed there at all.
574#[derive(Debug, Clone, Copy, PartialEq, Eq)]
575pub enum PayloadPermission {
576 /// The status permits a payload but does not require one: a zero-length
577 /// object with such a status is well formed, and draft-16's encodings can
578 /// spell it.
579 Permitted,
580 /// An object with such a status has an empty payload, and one carrying
581 /// bytes is malformed.
582 Forbidden,
583}
584
585impl PayloadPermission {
586 /// `true` for [`PayloadPermission::Permitted`].
587 pub fn permits(self) -> bool {
588 matches!(self, PayloadPermission::Permitted)
589 }
590}
591
592/// The framing of one draft-16 subgroup object, without its payload.
593///
594/// Produced by [`SubgroupObjectReader::read_object_meta`] for callers that
595/// forward an object's bytes verbatim and never inspect the payload.
596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
597pub struct SubgroupObjectMeta {
598 /// Resolved absolute Object ID.
599 pub object_id: u64,
600 /// Byte length of the extension-header block's contents, excluding its
601 /// length prefix.
602 pub extension_headers_len: u64,
603 /// Declared payload length. Zero when `status` is `Some`.
604 pub payload_length: u64,
605 /// Object status wire code, present only when the payload is empty.
606 ///
607 /// Kept as the raw code, unlike [`SubgroupObject::object_status`]: a meta
608 /// is produced by [`SubgroupObjectReader::read_object_meta`] and is never
609 /// an encode input, and a relay that reads a status on one draft may hand
610 /// it to a draft that numbers the same value differently. The code is
611 /// still one draft-16 assigns — `read_object_meta` refuses the others.
612 pub status: Option<u64>,
613 /// Total bytes this object occupies on the wire, prefix fields included.
614 pub wire_len: u64,
615}
616
617impl SubgroupObjectMeta {
618 /// Whether draft-16 allows this object a non-empty payload, or `None` when
619 /// the answer does not exist.
620 ///
621 /// Draft-16 Section 10.2.1.1 gives the rule for the three codes it assigns:
622 /// Normal (0x0) permits a payload, and "any object with a status code other
623 /// than zero MUST have an empty payload", which covers End of Group (0x3)
624 /// and End of Track (0x4). No status at all means the object carried bytes
625 /// — the status field is on the wire only when `payload_length` is zero —
626 /// and the same section makes Normal "implicit for any non-zero length
627 /// object", so that case answers [`PayloadPermission::Permitted`] as well.
628 ///
629 /// `None` is reserved for a `status` the draft does not assign. A meta read
630 /// off the wire cannot hold one — [`SubgroupObjectReader::read_object_meta`]
631 /// refuses those bytes — but the field is public and holds the raw code
632 /// precisely so a value carried over from another draft's numbering can sit
633 /// in it. Draft-16 states no payload rule for a code it never assigned, and
634 /// there is no safe direction to guess in: answering `Permitted` would wave
635 /// through a payload the peer may have to reject, and `Forbidden` would
636 /// discard one the peer may accept.
637 pub fn payload_permission(&self) -> Option<PayloadPermission> {
638 match self.status {
639 None => Some(PayloadPermission::Permitted),
640 Some(code) => match ObjectStatus::from_u64(code)? {
641 ObjectStatus::Normal => Some(PayloadPermission::Permitted),
642 ObjectStatus::EndOfGroup | ObjectStatus::EndOfTrack => {
643 Some(PayloadPermission::Forbidden)
644 }
645 },
646 }
647 }
648
649 /// Whether this Object's status is allowed to carry the extension block it
650 /// declares.
651 ///
652 /// The meta form of [`SubgroupObject::extensions_permitted`], answering
653 /// from the declared block length rather than its contents, so a relay that
654 /// forwards bytes verbatim can report the violation without copying the
655 /// block.
656 pub fn extensions_permitted(&self) -> bool {
657 extensions_permitted_at(self.status, self.extension_headers_len)
658 }
659}
660
661/// Stateful reader/writer for draft-16 subgroup objects. Mirrors the
662/// draft-15 semantics (delta-encoded object IDs and header-typed
663/// extension presence).
664#[derive(Debug, Clone)]
665pub struct SubgroupObjectReader {
666 extensions_present: bool,
667 prev_object_id: Option<u64>,
668}
669
670impl SubgroupObjectReader {
671 pub fn new(header: &SubgroupHeader) -> Self {
672 Self { extensions_present: header.has_extensions(), prev_object_id: None }
673 }
674
675 pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<SubgroupObject, CodecError> {
676 let delta = VarInt::decode(buf)?.into_inner();
677 // The first object's field is its absolute Object ID; every later
678 // object encodes the gap to its predecessor, biased by one because
679 // two objects on a subgroup stream cannot share an ID.
680 let object_id_val = match self.prev_object_id {
681 None => delta,
682 Some(prev) => prev
683 .checked_add(1)
684 .and_then(|v| v.checked_add(delta))
685 .ok_or(CodecError::InvalidField)?,
686 };
687 self.prev_object_id = Some(object_id_val);
688 let object_id = VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?;
689
690 // Draft-16: extensions are a byte-length-prefixed opaque blob.
691 let extension_headers = if self.extensions_present {
692 let ext_len = VarInt::decode(buf)?.into_inner() as usize;
693 crate::types::read_bytes(buf, ext_len)?
694 } else {
695 Vec::new()
696 };
697
698 let payload_length_vi = VarInt::decode(buf)?;
699 let payload_length_val = payload_length_vi.into_inner() as usize;
700 let (object_status, payload) = if payload_length_val == 0 {
701 let status = decoded_status(VarInt::decode(buf)?.into_inner())?;
702 (Some(status), Vec::new())
703 } else {
704 let payload = crate::types::read_bytes(buf, payload_length_val)?;
705 (None, payload)
706 };
707 Ok(SubgroupObject {
708 object_id,
709 extension_headers,
710 payload_length: payload_length_vi,
711 object_status,
712 payload,
713 })
714 }
715
716 /// Decode the next object's framing without copying its payload.
717 ///
718 /// Consumes exactly the bytes [`Self::read_object`] consumes and leaves
719 /// the same delta state behind, so the two are interchangeable on a
720 /// given stream.
721 pub fn read_object_meta(
722 &mut self,
723 buf: &mut impl Buf,
724 ) -> Result<SubgroupObjectMeta, CodecError> {
725 let start = buf.remaining();
726 let delta = VarInt::decode(buf)?.into_inner();
727 let object_id_val = match self.prev_object_id {
728 None => delta,
729 Some(prev) => prev
730 .checked_add(1)
731 .and_then(|v| v.checked_add(delta))
732 .ok_or(CodecError::InvalidField)?,
733 };
734 self.prev_object_id = Some(object_id_val);
735 let object_id =
736 VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?.into_inner();
737
738 let extension_headers_len = if self.extensions_present {
739 let ext_len = VarInt::decode(buf)?.into_inner();
740 skip(buf, ext_len)?;
741 ext_len
742 } else {
743 0
744 };
745
746 let payload_length = VarInt::decode(buf)?.into_inner();
747 let status = if payload_length == 0 {
748 let code = VarInt::decode(buf)?.into_inner();
749 decoded_status(code)?;
750 Some(code)
751 } else {
752 skip(buf, payload_length)?;
753 None
754 };
755 Ok(SubgroupObjectMeta {
756 object_id,
757 extension_headers_len,
758 payload_length,
759 status,
760 wire_len: (start - buf.remaining()) as u64,
761 })
762 }
763
764 /// Serialize an object, producing the correct delta encoding.
765 ///
766 /// Errors with [`CodecError::InvalidField`] when `object.object_id` is
767 /// not strictly greater than the previously written object's ID, since
768 /// no valid delta exists for that case.
769 ///
770 /// A zero `payload_length` makes this a status object, and the status
771 /// field is then mandatory on the wire: an absent
772 /// [`SubgroupObject::object_status`] is written as
773 /// [`ObjectStatus::Normal`]. The code written is always one draft-16
774 /// assigns, because the field cannot hold any other, so the bytes this
775 /// produces are always bytes [`SubgroupObjectReader::read_object`] accepts.
776 ///
777 /// Errors with [`CodecError::InvalidField`] when `payload_length` is not
778 /// exactly `payload.len()`. The declared length is written ahead of the
779 /// payload, so a mismatch is a frame [`Self::read_object`] cannot parse
780 /// and one no caller could fix by appending bytes.
781 pub fn write_object(
782 &mut self,
783 object: &SubgroupObject,
784 buf: &mut impl BufMut,
785 ) -> Result<(), CodecError> {
786 // A declared length that disagrees with the payload framed under it
787 // produces bytes no reader can parse and no caller can repair: the
788 // length is already on the wire ahead of the payload. Checked before
789 // anything is written, so a refused object leaves `buf` untouched
790 // rather than half an object the next read would run into.
791 //
792 // Zero is not "an empty payload" here; it is the marker that puts a
793 // status code where the payload would go, so an object carrying bytes
794 // under it is asking for two framings at once.
795 let declared = object.payload_length.into_inner();
796 if declared != object.payload.len() as u64 {
797 return Err(CodecError::InvalidField);
798 }
799
800 // Extensions on a non-Normal status are NOT refused here, though
801 // Section 10.2.1.2 forbids them. The two rules differ in kind. A status
802 // beside a payload has no encoding at all — the status field and the
803 // payload occupy the same position — so writing one is impossible
804 // rather than merely wrong. Extensions beside a status encode perfectly
805 // well; the frame is well formed and non-conforming, which is a
806 // judgement about what a peer may send, not about what these bytes
807 // mean.
808 //
809 // Refusing it here would also make this writer unable to reproduce a
810 // frame the decoder must be able to read, including the committed
811 // `subgroup-extensions-status-object` vector.
812 // [`SubgroupObject::extensions_permitted`] reports the violation
813 // instead, and the endpoint acts on it.
814
815 let oid = object.object_id.into_inner();
816 let delta = match self.prev_object_id {
817 None => oid,
818 Some(prev) => oid
819 .checked_sub(prev)
820 .and_then(|v| v.checked_sub(1))
821 .ok_or(CodecError::InvalidField)?,
822 };
823 VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?.encode(buf);
824 if self.extensions_present {
825 let ext_len = object.extension_headers.len();
826 VarInt::from_usize(ext_len).encode(buf);
827 buf.put_slice(&object.extension_headers);
828 }
829 object.payload_length.encode(buf);
830 if object.payload_length.into_inner() == 0 {
831 // Zero-length means a status object, and the status is not
832 // optional on the wire; an unset one is Normal.
833 let status = object.object_status.unwrap_or(ObjectStatus::Normal);
834 VarInt::from_usize(status.as_u64() as usize).encode(buf);
835 } else {
836 buf.put_slice(&object.payload);
837 }
838 self.prev_object_id = Some(oid);
839 Ok(())
840 }
841}
842
843#[derive(Debug, Clone, PartialEq, Eq)]
844pub struct DatagramHeader {
845 pub datagram_type: u8,
846 pub track_alias: VarInt,
847 pub group_id: VarInt,
848 pub object_id: VarInt,
849 /// Publisher priority — `None` when the DEFAULT_PRIORITY flag is set and
850 /// the priority is inherited from the subscription's control message.
851 pub publisher_priority: Option<u8>,
852 /// Opaque extension-headers blob (only when flag 0x01 is set).
853 pub extension_headers: Vec<u8>,
854 /// Object status (only when the `0x20` status flag is set).
855 ///
856 /// The wire field is a varint and can carry any value up to 2^62-1;
857 /// draft-16 Section 10.2.1.1 assigns three of them and says a peer SHOULD
858 /// treat the rest as a protocol error. This field is typed to the assigned
859 /// set, so it cannot hold 0x1, 0x2 or anything from 0x5 up —
860 /// [`Self::encode`] therefore needs no check and cannot emit a datagram
861 /// that [`Self::decode`] would reject.
862 ///
863 /// `None` while the status flag is set encodes as
864 /// [`ObjectStatus::Normal`]: once the flag is set the field is present on
865 /// the wire, so there is nothing for `None` to mean but the default.
866 pub object_status: Option<ObjectStatus>,
867}
868
869impl DatagramHeader {
870 pub fn has_extensions(&self) -> bool {
871 self.datagram_type & 0x01 != 0
872 }
873
874 pub fn is_end_of_group(&self) -> bool {
875 self.datagram_type & 0x02 != 0
876 }
877
878 pub fn has_object_id(&self) -> bool {
879 self.datagram_type & 0x04 == 0
880 }
881
882 /// When set, publisher_priority is omitted on the wire and inherited
883 /// from the subscription / control-message context.
884 pub fn has_default_priority(&self) -> bool {
885 self.datagram_type & 0x08 != 0
886 }
887
888 pub fn is_status(&self) -> bool {
889 self.datagram_type & 0x20 != 0
890 }
891
892 /// Whether this datagram's status is allowed to carry the extension headers
893 /// it has.
894 ///
895 /// The same rule the subgroup form obeys — draft-16 Section 10.3.1 builds
896 /// the datagram's Extensions field out of the structure Section 10.2.1.2
897 /// defines, and that section is where the rule sits. See
898 /// [`SubgroupObject::extensions_permitted`] for why [`Self::decode`]
899 /// reports this instead of refusing it.
900 ///
901 /// The block is on the wire only when the type byte sets the EXTENSIONS
902 /// bit, so contents held here with the bit clear are not written and do not
903 /// count against the rule.
904 pub fn extensions_permitted(&self) -> bool {
905 extensions_permitted_at(
906 self.object_status.map(|s| s.as_u64()),
907 if self.has_extensions() { self.extension_headers.len() as u64 } else { 0 },
908 )
909 }
910
911 /// Encode the datagram header, refusing a status the framing cannot carry.
912 ///
913 /// A datagram states a status only when its type byte sets the STATUS bit
914 /// (0x20). With the bit clear there is no status field on the wire, so an
915 /// `object_status` of anything but [`ObjectStatus::Normal`] has nowhere to
916 /// go: [`Self::encode`] drops it, and the datagram parses back as an
917 /// ordinary payload object. An End of Group marker written that way does
918 /// not arrive late or malformed — it does not arrive at all, and the
919 /// receiver sees a normal object in its place.
920 ///
921 /// Draft-16 Section 10.3.1 puts the framing side plainly — "The STATUS bit
922 /// (0x20) indicates whether the datagram contains an Object Status or
923 /// Object Payload" — and Section 10.2.1.1 the conformance side: "Any object
924 /// with a status code other than zero MUST have an empty payload." Between
925 /// them there is no datagram that carries a non-zero status and a payload,
926 /// so the pair being refused here is not one this encoder merely declines
927 /// to spell.
928 ///
929 /// [`ObjectStatus::Normal`] with the bit clear is not that case and is
930 /// accepted. It is the status the encoding elides for every datagram that
931 /// carries a payload, so stating it asks for exactly the bytes leaving it
932 /// out asks for, and nothing is lost.
933 ///
934 /// Errors with [`CodecError::InvalidField`] on the lossy combination,
935 /// before any byte is written, so a refused header leaves `buf` untouched.
936 ///
937 /// Three further refusals, each of them a datagram this codec would
938 /// otherwise emit and then decline to read back:
939 ///
940 /// * A Type value Section 10.3.1 lists as invalid. See
941 /// `validate_datagram_type`.
942 /// * An EXTENSIONS bit set over an empty extension block. Section 10.3.1:
943 /// "If an endpoint receives a datagram with the EXTENSIONS bit set and an
944 /// Extension Headers Length of 0, it MUST close the session with a
945 /// PROTOCOL_VIOLATION." [`Self::encode`] writes the length prefix from the
946 /// block's own length, so it spells exactly the datagram the peer must
947 /// close the session over. This is a datagram rule only: on a subgroup
948 /// stream the same section says "Objects with no extensions set Extension
949 /// Headers Length to 0", because the Type byte is fixed for the whole
950 /// stream and an Object with no extensions has no other way to say so.
951 /// * Extensions on an Object whose status is not Normal (Section 10.2.1.2).
952 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
953 validate_datagram_type(self.datagram_type as u64)?;
954 if !self.is_status() && matches!(self.object_status, Some(s) if s != ObjectStatus::Normal) {
955 return Err(CodecError::InvalidField);
956 }
957 if self.has_extensions() && self.extension_headers.is_empty() {
958 return Err(CodecError::InvalidField);
959 }
960 // Section 10.2.1.2. [`Self::decode`] reports this rather than refusing
961 // it, because the datagram it describes is well framed and a codec that
962 // could not read one could not reproduce a capture containing it.
963 // Writing one is the other direction and has no such excuse: a
964 // conforming peer answers with a PROTOCOL_VIOLATION, so emitting one
965 // costs the session and not merely the datagram.
966 if !self.extensions_permitted() {
967 return Err(CodecError::InvalidField);
968 }
969 self.encode(buf);
970 Ok(())
971 }
972
973 /// Encode the datagram header to `buf`.
974 ///
975 /// When the type byte sets the status flag the status field is written
976 /// unconditionally, defaulting to [`ObjectStatus::Normal`]. Omitting it
977 /// would truncate the datagram: [`Self::decode`] reads a status whenever
978 /// the flag is set, and answers [`CodecError::UnexpectedEnd`] when the
979 /// bytes stop first.
980 ///
981 /// The type byte is taken as the authority on framing, which is what makes
982 /// this infallible — and what makes it lossy when the struct disagrees with
983 /// itself. An `object_status` set while the type byte leaves the STATUS bit
984 /// clear is discarded here without a word. Prefer [`Self::encode_checked`],
985 /// which refuses that combination instead of resolving it.
986 pub fn encode(&self, buf: &mut impl BufMut) {
987 VarInt::from_usize(self.datagram_type as usize).encode(buf);
988 self.track_alias.encode(buf);
989 self.group_id.encode(buf);
990 if self.has_object_id() {
991 self.object_id.encode(buf);
992 }
993 if !self.has_default_priority() {
994 buf.put_u8(self.publisher_priority.unwrap_or(128));
995 }
996 if self.has_extensions() {
997 VarInt::from_usize(self.extension_headers.len()).encode(buf);
998 buf.put_slice(&self.extension_headers);
999 }
1000 if self.is_status() {
1001 let status = self.object_status.unwrap_or(ObjectStatus::Normal);
1002 VarInt::from_usize(status.as_u64() as usize).encode(buf);
1003 }
1004 }
1005
1006 /// Decode a datagram header, Type field included.
1007 ///
1008 /// A Type spelled in more than one byte is refused first, for the reason
1009 /// given on [`SubgroupHeader::decode`].
1010 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1011 if let Some(err) = wide_type_refusal(buf, datagram_type_refusal)? {
1012 return Err(err);
1013 }
1014 let datagram_type = buf.get_u8();
1015 validate_datagram_type(datagram_type as u64)?;
1016 let track_alias = VarInt::decode(buf)?;
1017 let group_id = VarInt::decode(buf)?;
1018 let object_id =
1019 if datagram_type & 0x04 == 0 { VarInt::decode(buf)? } else { VarInt::from_usize(0) };
1020 let publisher_priority = if datagram_type & 0x08 != 0 {
1021 None
1022 } else {
1023 if buf.remaining() < 1 {
1024 return Err(CodecError::UnexpectedEnd);
1025 }
1026 Some(buf.get_u8())
1027 };
1028 let extension_headers = if datagram_type & 0x01 != 0 {
1029 let ext_len = VarInt::decode(buf)?.into_inner() as usize;
1030 // A datagram whose type says extensions are present must actually carry
1031 // some: receiving one with an Extension Headers Length of 0 closes the
1032 // session. The opposite holds on a subgroup stream, where the type byte is
1033 // fixed for the whole stream and an object with no extensions has no other
1034 // way to say so, which is why this check belongs to the datagram readers
1035 // alone.
1036 if ext_len == 0 {
1037 return Err(CodecError::InvalidField);
1038 }
1039 crate::types::read_bytes(buf, ext_len)?
1040 } else {
1041 Vec::new()
1042 };
1043 let object_status = if datagram_type & 0x20 != 0 {
1044 Some(decoded_status(VarInt::decode(buf)?.into_inner())?)
1045 } else {
1046 None
1047 };
1048 Ok(Self {
1049 datagram_type,
1050 track_alias,
1051 group_id,
1052 object_id,
1053 publisher_priority,
1054 extension_headers,
1055 object_status,
1056 })
1057 }
1058}
1059
1060#[derive(Debug, Clone, PartialEq, Eq)]
1061pub struct FetchHeader {
1062 pub request_id: VarInt,
1063}
1064
1065impl FetchHeader {
1066 pub fn encode(&self, buf: &mut impl BufMut) {
1067 VarInt::from_usize(FETCH_STREAM_TYPE as usize).encode(buf);
1068 self.request_id.encode(buf);
1069 }
1070
1071 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1072 let stream_type = VarInt::decode(buf)?.into_inner();
1073 if stream_type != FETCH_STREAM_TYPE {
1074 return Err(stream_type_error(stream_type));
1075 }
1076 let request_id = VarInt::decode(buf)?;
1077 Ok(Self { request_id })
1078 }
1079}
1080
1081/// The two Serialization Flags values draft-16 gives a meaning of their own
1082/// instead of reading as a bit field, from Section 10.4.4 Table 4.
1083///
1084/// The bits of Serialization Flags are flags only "when less than 128"; Table 4
1085/// assigns two larger values, and of everything else at or above 128 the
1086/// section says "Any other value is a PROTOCOL_VIOLATION". That is why
1087/// [`FetchObjectHeader::decode`] refuses an unassigned large value outright
1088/// rather than carrying it through as an unknown flag word — there is no
1089/// framing to parse behind it.
1090///
1091/// Both markers stand in for a span of Objects rather than one: Section 10.4.4.2
1092/// reads them as covering every Location between the previous serialized
1093/// Object, if any, and this one, inclusive.
1094#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1095#[repr(u64)]
1096pub enum FetchEndOfRange {
1097 /// End of Non-Existent Range (0x8C): every Object in the covered span is
1098 /// known not to exist. Section 10.4.4.2 adds that a publisher SHOULD NOT
1099 /// use this except to split a span it will not serialize into the part
1100 /// known absent and the part whose status is unknown.
1101 NonExistent = 0x8c,
1102 /// End of Unknown Range (0x10C): the status of every Object in the covered
1103 /// span is unknown.
1104 Unknown = 0x10c,
1105}
1106
1107impl FetchEndOfRange {
1108 /// Both values Table 4 assigns, in ascending wire order.
1109 ///
1110 /// This is exactly the set [`FetchEndOfRange::from_u64`] accepts above the
1111 /// flag range.
1112 pub const ALL: &[FetchEndOfRange] = &[FetchEndOfRange::NonExistent, FetchEndOfRange::Unknown];
1113
1114 /// The Table 4 meaning of `v`, or `None` when `v` is a flag word (anything
1115 /// below 128) or a large value the table leaves unassigned.
1116 ///
1117 /// The two answers `None` covers are not the same thing, and callers must
1118 /// not merge them: a flag word is ordinary framing, an unassigned large
1119 /// value is a protocol violation. [`FetchObjectHeader::decode`] separates
1120 /// them by testing the 128 boundary itself.
1121 pub fn from_u64(v: u64) -> Option<Self> {
1122 match v {
1123 0x8c => Some(FetchEndOfRange::NonExistent),
1124 0x10c => Some(FetchEndOfRange::Unknown),
1125 _ => None,
1126 }
1127 }
1128
1129 /// Return the wire value.
1130 pub fn as_u64(self) -> u64 {
1131 self as u64
1132 }
1133}
1134
1135/// How an Object on a draft-16 fetch stream states its Subgroup ID.
1136///
1137/// The two least significant bits of Serialization Flags form this field;
1138/// draft-16 Section 10.4.4.1 Table 5 lists the four readings. Three of them put
1139/// no Subgroup ID on the wire.
1140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1141pub enum FetchSubgroupMode {
1142 /// 0x00 — the Subgroup ID is zero.
1143 Zero,
1144 /// 0x01 — the Subgroup ID is the prior Object's Subgroup ID.
1145 SameAsPrior,
1146 /// 0x02 — the Subgroup ID is the prior Object's Subgroup ID plus one.
1147 PriorPlusOne,
1148 /// 0x03 — the Subgroup ID field is present on the wire.
1149 Present,
1150}
1151
1152/// One Object as it is framed on a draft-16 fetch stream, before any field is
1153/// resolved against the Object before it.
1154///
1155/// Draft-16 Section 10.4.4 Figure 31 gives the layout: a Serialization Flags
1156/// varint, then Group ID, Subgroup ID, Object ID, Publisher Priority and
1157/// Extensions, each present only when the flags say so, then an Object Payload
1158/// Length and the payload itself. Every optional field here is `Some` exactly
1159/// when its bytes were on the wire, so [`Self::encode`] can put back the same
1160/// bytes [`Self::decode`] took off — including the difference between an absent
1161/// extensions block and a present, zero-length one.
1162///
1163/// The payload is not part of this struct: `payload_length` bytes follow it on
1164/// the stream. That mirrors [`SubgroupObjectMeta`], and it is what lets a relay
1165/// that forwards bytes verbatim read the framing without copying the payload.
1166///
1167/// Two things separate this from the fetch object of drafts 07-13. There is no
1168/// Object Status field — Section 10.2.1.1 says the status "is only present in
1169/// objects that are delivered via a SUBSCRIPTION, and is absent in Objects
1170/// delivered via a FETCH" — so a zero `payload_length` is simply an empty
1171/// object, with no status varint behind it. And the fields that are present are
1172/// absolute: the flags decide presence, and Table 5 and Table 6 decide what an
1173/// absent field inherits, but a field that is on the wire carries its own value
1174/// rather than a delta. Draft-16 spells "Object ID Delta" out by name where it
1175/// means one, on subgroup streams; Figure 31 says "Object ID".
1176///
1177/// Resolving the absent fields needs the Object before this one, which a single
1178/// header does not have. [`FetchObjectReader`] carries that state.
1179#[derive(Debug, Clone, PartialEq, Eq)]
1180pub struct FetchObjectHeader {
1181 /// Serialization Flags exactly as they appeared on the wire: a flag word
1182 /// below 128, or one of the [`FetchEndOfRange`] markers.
1183 pub serialization_flags: VarInt,
1184 /// Group ID, when the flags put it on the wire.
1185 pub group_id: Option<VarInt>,
1186 /// Subgroup ID, present only under [`FetchSubgroupMode::Present`].
1187 pub subgroup_id: Option<VarInt>,
1188 /// Object ID, when the flags put it on the wire.
1189 pub object_id: Option<VarInt>,
1190 /// Publisher Priority, when the flags put it on the wire.
1191 pub publisher_priority: Option<u8>,
1192 /// Raw extension-header bytes, excluding the byte-length prefix that
1193 /// precedes them on the wire, and `None` when the flags carry no extensions
1194 /// block at all. Opaque: [`Self::encode`] re-emits the prefix and these
1195 /// bytes verbatim. The block's own shape is the one Section 10.2.1.2
1196 /// defines for every draft-16 object.
1197 pub extensions: Option<Vec<u8>>,
1198 /// Declared Object Payload Length. The payload follows on the stream and is
1199 /// not held here.
1200 pub payload_length: VarInt,
1201}
1202
1203impl FetchObjectHeader {
1204 /// Serialization Flags as a plain integer.
1205 fn flags(&self) -> u64 {
1206 self.serialization_flags.into_inner()
1207 }
1208
1209 /// The Table 4 marker this object is, or `None` when its Serialization
1210 /// Flags are an ordinary flag word.
1211 pub fn end_of_range(&self) -> Option<FetchEndOfRange> {
1212 FetchEndOfRange::from_u64(self.flags())
1213 }
1214
1215 /// Bit 0x40: the Object's Forwarding Preference is Datagram, so it has no
1216 /// Subgroup ID.
1217 ///
1218 /// Draft-16 Section 10.4.4.1 requires the publisher to set this bit for such
1219 /// an Object, says it SHOULD then zero the two least significant bits, and
1220 /// requires the subscriber to ignore them — so a Subgroup ID is not read
1221 /// even when those bits spell [`FetchSubgroupMode::Present`]. Ignoring them
1222 /// is a framing decision, not a cosmetic one: reading a Subgroup ID there
1223 /// would consume a varint that belongs to the next field.
1224 ///
1225 /// Never true for a [`FetchEndOfRange`] marker, whose value is not a flag
1226 /// word.
1227 pub fn is_datagram(&self) -> bool {
1228 self.end_of_range().is_none() && self.flags() & 0x40 != 0
1229 }
1230
1231 /// The Table 5 reading of the two least significant bits.
1232 ///
1233 /// Answers [`FetchSubgroupMode::Zero`] for a [`FetchEndOfRange`] marker and
1234 /// for a Datagram-forwarded Object, neither of which has a Subgroup ID on
1235 /// the wire: Section 10.4.4.2 lists Subgroup ID among the fields an End of
1236 /// Range does not carry, and Section 10.4.4.1 says a Datagram Object has
1237 /// none at all.
1238 pub fn subgroup_mode(&self) -> FetchSubgroupMode {
1239 if self.end_of_range().is_some() || self.is_datagram() {
1240 return FetchSubgroupMode::Zero;
1241 }
1242 match self.flags() & 0x03 {
1243 0x00 => FetchSubgroupMode::Zero,
1244 0x01 => FetchSubgroupMode::SameAsPrior,
1245 0x02 => FetchSubgroupMode::PriorPlusOne,
1246 _ => FetchSubgroupMode::Present,
1247 }
1248 }
1249
1250 /// Whether a Group ID field is on the wire (bit 0x08).
1251 ///
1252 /// Always true for a [`FetchEndOfRange`] marker: Section 10.4.4.2 says both
1253 /// the Group ID and the Object ID fields are present.
1254 pub fn has_group_id(&self) -> bool {
1255 self.end_of_range().is_some() || self.flags() & 0x08 != 0
1256 }
1257
1258 /// Whether a Subgroup ID field is on the wire, which is
1259 /// [`FetchSubgroupMode::Present`] and nothing else.
1260 pub fn has_subgroup_id(&self) -> bool {
1261 matches!(self.subgroup_mode(), FetchSubgroupMode::Present)
1262 }
1263
1264 /// Whether an Object ID field is on the wire (bit 0x04).
1265 ///
1266 /// Always true for a [`FetchEndOfRange`] marker, per Section 10.4.4.2.
1267 pub fn has_object_id(&self) -> bool {
1268 self.end_of_range().is_some() || self.flags() & 0x04 != 0
1269 }
1270
1271 /// Whether a Publisher Priority byte is on the wire (bit 0x10).
1272 ///
1273 /// Never true for a [`FetchEndOfRange`] marker: Section 10.4.4.2 lists
1274 /// Priority among the fields it does not carry.
1275 pub fn has_priority(&self) -> bool {
1276 self.end_of_range().is_none() && self.flags() & 0x10 != 0
1277 }
1278
1279 /// Whether an Extensions block is on the wire (bit 0x20).
1280 ///
1281 /// Never true for a [`FetchEndOfRange`] marker, per Section 10.4.4.2.
1282 pub fn has_extensions(&self) -> bool {
1283 self.end_of_range().is_none() && self.flags() & 0x20 != 0
1284 }
1285
1286 /// Whether these flags read any field off the Object before this one.
1287 ///
1288 /// Draft-16 Section 10.4.4.1 closes the section on flags with: "If the first
1289 /// Object in the FETCH response uses a flag that references fields in the
1290 /// prior Object, the Subscriber MUST close the session with a
1291 /// PROTOCOL_VIOLATION." Four of the readings do that — an absent Group ID,
1292 /// Object ID or Priority each names the prior Object in Table 5 or Table 6,
1293 /// as do the two middle Subgroup ID modes.
1294 ///
1295 /// [`FetchSubgroupMode::Zero`] does not: it states a value outright. Nor
1296 /// does an absent Extensions block, which means the Object has none rather
1297 /// than the ones before it. A [`FetchEndOfRange`] marker references nothing
1298 /// either — its Group ID and Object ID are always present, and Section
1299 /// 10.4.4.2 gives it no Priority or Extensions to inherit.
1300 ///
1301 /// [`FetchObjectReader::resolve`] refuses the first Object of a stream when
1302 /// this is true of a field it would have to produce a value for. It is
1303 /// exposed separately because the draft's rule is wider than that: it also
1304 /// covers an absent Priority, for which Section 11.1.1.1 supplies a default
1305 /// that makes resolution possible anyway.
1306 pub fn references_prior_object(&self) -> bool {
1307 if self.end_of_range().is_some() {
1308 return false;
1309 }
1310 !self.has_group_id()
1311 || !self.has_object_id()
1312 || !self.has_priority()
1313 || matches!(
1314 self.subgroup_mode(),
1315 FetchSubgroupMode::SameAsPrior | FetchSubgroupMode::PriorPlusOne
1316 )
1317 }
1318
1319 /// Encode the object's framing, refusing a struct that disagrees with its
1320 /// own Serialization Flags.
1321 ///
1322 /// The flags are the authority on which fields are on the wire, so a field
1323 /// that is `Some` while its flag is clear has nowhere to go, and one that is
1324 /// `None` while its flag is set leaves a hole the reader would fill from the
1325 /// bytes of the next field. Either way the result is a stream
1326 /// [`Self::decode`] cannot read back as what was handed in, so both are
1327 /// refused here.
1328 ///
1329 /// Errors with [`CodecError::InvalidField`] on any such disagreement, and on
1330 /// a Serialization Flags value at or above 128 that Table 4 does not assign,
1331 /// before a single byte is written — a refused object leaves `buf` untouched
1332 /// rather than half an object the next read would run into.
1333 ///
1334 /// The payload is not written: `payload_length` bytes of it belong on the
1335 /// stream immediately after these.
1336 pub fn encode(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
1337 let flags = self.flags();
1338 if flags >= 128 && FetchEndOfRange::from_u64(flags).is_none() {
1339 return Err(CodecError::InvalidField);
1340 }
1341 if self.group_id.is_some() != self.has_group_id()
1342 || self.subgroup_id.is_some() != self.has_subgroup_id()
1343 || self.object_id.is_some() != self.has_object_id()
1344 || self.publisher_priority.is_some() != self.has_priority()
1345 || self.extensions.is_some() != self.has_extensions()
1346 {
1347 return Err(CodecError::InvalidField);
1348 }
1349
1350 self.serialization_flags.encode(buf);
1351 if let Some(group_id) = self.group_id {
1352 group_id.encode(buf);
1353 }
1354 if let Some(subgroup_id) = self.subgroup_id {
1355 subgroup_id.encode(buf);
1356 }
1357 if let Some(object_id) = self.object_id {
1358 object_id.encode(buf);
1359 }
1360 if let Some(priority) = self.publisher_priority {
1361 buf.put_u8(priority);
1362 }
1363 if let Some(extensions) = &self.extensions {
1364 VarInt::from_usize(extensions.len()).encode(buf);
1365 buf.put_slice(extensions);
1366 }
1367 self.payload_length.encode(buf);
1368 Ok(())
1369 }
1370
1371 /// Decode one object's framing, leaving `buf` positioned at its payload.
1372 ///
1373 /// Errors with [`CodecError::InvalidField`] when Serialization Flags is at
1374 /// or above 128 and is not one of the two values Table 4 assigns — draft-16
1375 /// Section 10.4.4 makes any other such value a PROTOCOL_VIOLATION, and there
1376 /// is no way to guess which fields follow it.
1377 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
1378 let serialization_flags = VarInt::decode(buf)?;
1379 let flags = serialization_flags.into_inner();
1380 if flags >= 128 && FetchEndOfRange::from_u64(flags).is_none() {
1381 return Err(CodecError::InvalidField);
1382 }
1383 // Presence is settled before any field is read, so the field order of
1384 // Figure 31 is followed exactly once: Group ID, Subgroup ID, Object ID,
1385 // Priority, Extensions, Object Payload Length.
1386 let probe = Self {
1387 serialization_flags,
1388 group_id: None,
1389 subgroup_id: None,
1390 object_id: None,
1391 publisher_priority: None,
1392 extensions: None,
1393 payload_length: VarInt::from_usize(0),
1394 };
1395
1396 let group_id = if probe.has_group_id() { Some(VarInt::decode(buf)?) } else { None };
1397 let subgroup_id = if probe.has_subgroup_id() { Some(VarInt::decode(buf)?) } else { None };
1398 let object_id = if probe.has_object_id() { Some(VarInt::decode(buf)?) } else { None };
1399 let publisher_priority = if probe.has_priority() {
1400 if buf.remaining() < 1 {
1401 return Err(CodecError::UnexpectedEnd);
1402 }
1403 Some(buf.get_u8())
1404 } else {
1405 None
1406 };
1407 let extensions = if probe.has_extensions() {
1408 let ext_len = VarInt::decode(buf)?.into_inner() as usize;
1409 Some(crate::types::read_bytes(buf, ext_len)?)
1410 } else {
1411 None
1412 };
1413 let payload_length = VarInt::decode(buf)?;
1414
1415 Ok(Self {
1416 serialization_flags,
1417 group_id,
1418 subgroup_id,
1419 object_id,
1420 publisher_priority,
1421 extensions,
1422 payload_length,
1423 })
1424 }
1425}
1426
1427/// One fetch object's Location and priority, with every field the flags left
1428/// off the wire filled in from the Object before it.
1429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1430pub struct FetchObjectLocation {
1431 /// Resolved absolute Group ID.
1432 pub group_id: u64,
1433 /// Resolved absolute Subgroup ID, and `None` for an Object whose Forwarding
1434 /// Preference is Datagram — draft-16 Section 10.2.1 omits the Subgroup ID
1435 /// for those, and Section 10.4.4.1 marks them with bit 0x40.
1436 pub subgroup_id: Option<u64>,
1437 /// Resolved absolute Object ID.
1438 pub object_id: u64,
1439 /// Publisher Priority, and `None` when no Object on the stream so far has
1440 /// stated one.
1441 ///
1442 /// That happens only after a [`FetchEndOfRange`] marker, which carries no
1443 /// Priority of its own and leaves the running value alone; any other first
1444 /// Object with no Priority is refused by [`FetchObjectReader::resolve`].
1445 /// Draft-16 Section 11.1.1.1 supplies the fallback for the gap — a
1446 /// subscription has Publisher Priority 128 when DEFAULT PUBLISHER PRIORITY
1447 /// is omitted — and it is left to the caller rather than substituted here,
1448 /// so that *the stream never said* stays distinguishable from "the stream
1449 /// said 128".
1450 pub publisher_priority: Option<u8>,
1451 /// The Table 4 marker this Object is, or `None` for an ordinary Object.
1452 ///
1453 /// When set, this Location is the far end of a span: Section 10.4.4.2 reads
1454 /// every Location between the previously serialized Object, if any, and
1455 /// this one — inclusive — as non-existent or unknown.
1456 pub end_of_range: Option<FetchEndOfRange>,
1457}
1458
1459/// Stateful resolver for the Objects on one draft-16 fetch stream.
1460///
1461/// Draft-16 Section 10.4.4.1 lets an Object leave its Group ID, Subgroup ID,
1462/// Object ID or Priority off the wire and take the value from the Object before
1463/// it, so the fields of a fetch object are only meaningful in stream order.
1464/// This carries that running state; [`FetchObjectHeader`] carries only what the
1465/// bytes said.
1466///
1467/// Unlike a subgroup stream, nothing here is a delta: a field that is on the
1468/// wire replaces the running value outright, and a field that is absent either
1469/// repeats it or steps it by one, as Table 5 and Table 6 say.
1470#[derive(Debug, Clone, Default)]
1471pub struct FetchObjectReader {
1472 group_id: Option<u64>,
1473 subgroup_id: Option<u64>,
1474 object_id: Option<u64>,
1475 publisher_priority: Option<u8>,
1476}
1477
1478impl FetchObjectReader {
1479 /// A reader positioned before the first Object of a fetch stream, with no
1480 /// prior Object to inherit from.
1481 pub fn new() -> Self {
1482 Self::default()
1483 }
1484
1485 /// Resolve one Object's Location against the Object before it, advancing
1486 /// the running state.
1487 ///
1488 /// Errors with [`CodecError::InvalidField`] when the first Object of the
1489 /// stream leaves out a Group ID or an Object ID, or names the prior
1490 /// Object's Subgroup ID: draft-16 Section 10.4.4.1 makes a first Object
1491 /// that "uses a flag that references fields in the prior Object" a
1492 /// PROTOCOL_VIOLATION, and for these three fields there is no value to
1493 /// produce even if it were not. An absent Priority is the same violation by
1494 /// the draft's letter and is reported by
1495 /// [`FetchObjectHeader::references_prior_object`], but it is resolvable —
1496 /// the Location simply carries no priority — so it is not refused here.
1497 ///
1498 /// Errors with [`CodecError::InvalidField`] when a Subgroup ID one greater
1499 /// than the prior Object's would not fit a varint.
1500 ///
1501 /// A [`FetchEndOfRange`] marker takes part in the running state like any
1502 /// other Object: Section 10.4.4.2 gives it a Location, and it is a
1503 /// "serialized Object" in the same section's words, so the Object after it
1504 /// inherits its Group ID and steps from its Object ID.
1505 pub fn resolve(
1506 &mut self,
1507 header: &FetchObjectHeader,
1508 ) -> Result<FetchObjectLocation, CodecError> {
1509 let group_id = match header.group_id {
1510 Some(v) => v.into_inner(),
1511 None => self.group_id.ok_or(CodecError::InvalidField)?,
1512 };
1513
1514 let subgroup_id = if header.is_datagram() {
1515 None
1516 } else {
1517 match header.subgroup_mode() {
1518 FetchSubgroupMode::Zero => Some(0),
1519 FetchSubgroupMode::SameAsPrior => {
1520 Some(self.subgroup_id.ok_or(CodecError::InvalidField)?)
1521 }
1522 FetchSubgroupMode::PriorPlusOne => {
1523 let prior = self.subgroup_id.ok_or(CodecError::InvalidField)?;
1524 let next = prior.checked_add(1).ok_or(CodecError::InvalidField)?;
1525 VarInt::from_u64(next).map_err(|_| CodecError::InvalidField)?;
1526 Some(next)
1527 }
1528 FetchSubgroupMode::Present => {
1529 Some(header.subgroup_id.ok_or(CodecError::InvalidField)?.into_inner())
1530 }
1531 }
1532 };
1533
1534 let object_id = match header.object_id {
1535 Some(v) => v.into_inner(),
1536 None => {
1537 let prior = self.object_id.ok_or(CodecError::InvalidField)?;
1538 let next = prior.checked_add(1).ok_or(CodecError::InvalidField)?;
1539 VarInt::from_u64(next).map_err(|_| CodecError::InvalidField)?;
1540 next
1541 }
1542 };
1543
1544 // An Object that states no Priority leaves the running one alone, which
1545 // is what "Priority is the prior Object's Priority" asks for and also
1546 // what an End of Range marker — which has no Priority of its own —
1547 // needs.
1548 if let Some(priority) = header.publisher_priority {
1549 self.publisher_priority = Some(priority);
1550 }
1551 self.group_id = Some(group_id);
1552 // A Datagram-forwarded Object has no Subgroup ID at all, so it leaves no
1553 // "prior Object's Subgroup ID" behind: the next Object naming one is
1554 // refused rather than reaching past it to the Object before.
1555 self.subgroup_id = subgroup_id;
1556 self.object_id = Some(object_id);
1557
1558 Ok(FetchObjectLocation {
1559 group_id,
1560 subgroup_id,
1561 object_id,
1562 publisher_priority: self.publisher_priority,
1563 end_of_range: header.end_of_range(),
1564 })
1565 }
1566}
1567
1568/// Re-encodes resolved fetch Objects onto one FETCH stream.
1569///
1570/// The exact inverse of [`FetchObjectReader`], and it exists for one caller:
1571/// something that has read a stream and is writing a different stream from the
1572/// same Objects. Draft-16 Section 10.4.4.1 lets an Object leave out its Group
1573/// ID, Object ID, Subgroup ID and Priority and take the prior Object's, so
1574/// removing an Object changes what the Objects after it are read against: a
1575/// field the survivor left off has to appear, and a flag bit with it.
1576///
1577/// Every field draft-16 puts on the wire here is the absolute value rather than
1578/// a difference — the deltas arrive at draft-18. What is stateful is the
1579/// *omission*, and that is enough to make removal a re-encode.
1580///
1581/// # Why this is not a general encoder
1582///
1583/// Every Object it writes came off a stream, so the caller holds the Object's
1584/// own [`FetchObjectHeader`] beside its resolved
1585/// [`FetchObjectLocation`]. The header is used as the preference: wherever the
1586/// original shape still says the same thing against the new predecessor it is
1587/// kept, so a stream with nothing removed is reproduced byte for byte.
1588#[derive(Debug, Clone, Default)]
1589pub struct FetchObjectWriter {
1590 group_id: Option<u64>,
1591 subgroup_id: Option<u64>,
1592 object_id: Option<u64>,
1593 publisher_priority: Option<u8>,
1594}
1595
1596impl FetchObjectWriter {
1597 /// A writer positioned before the first Object of a fetch stream, with no
1598 /// prior Object for anything to be written against.
1599 pub fn new() -> Self {
1600 Self::default()
1601 }
1602
1603 /// The header that encodes `location` against everything written so far,
1604 /// starting from the shape `original` arrived in.
1605 ///
1606 /// Does not advance the writer — [`Self::write_object_header`] is the call
1607 /// that does both.
1608 ///
1609 /// # Errors
1610 ///
1611 /// [`CodecError::InvalidField`] for an Object with neither a Subgroup ID
1612 /// nor the Datagram bit, and where a value does not fit a variable-length
1613 /// integer.
1614 pub fn header_for(
1615 &self,
1616 original: &FetchObjectHeader,
1617 location: &FetchObjectLocation,
1618 ) -> Result<FetchObjectHeader, CodecError> {
1619 // A marker's Group ID and Object ID are on the wire by definition —
1620 // `has_group_id` and `has_object_id` are true for both marker values —
1621 // and it carries no Subgroup ID, Priority or Extensions.
1622 if original.end_of_range().is_some() {
1623 return Ok(FetchObjectHeader {
1624 serialization_flags: original.serialization_flags,
1625 group_id: Some(VarInt::from_u64(location.group_id)?),
1626 subgroup_id: None,
1627 object_id: Some(VarInt::from_u64(location.object_id)?),
1628 publisher_priority: None,
1629 extensions: None,
1630 payload_length: original.payload_length,
1631 });
1632 }
1633
1634 let group_id = if !original.has_group_id() && self.group_id == Some(location.group_id) {
1635 None
1636 } else {
1637 Some(VarInt::from_u64(location.group_id)?)
1638 };
1639 let object_id = if !original.has_object_id()
1640 && self.object_id.and_then(|p| p.checked_add(1)) == Some(location.object_id)
1641 {
1642 None
1643 } else {
1644 Some(VarInt::from_u64(location.object_id)?)
1645 };
1646 let (subgroup_mode, subgroup_id) = self.subgroup_field(original, location)?;
1647 let publisher_priority = self.priority_field(original, location);
1648
1649 let flags = original.flags();
1650 let mut new_flags = subgroup_mode;
1651 if flags & 0x40 != 0 {
1652 new_flags |= 0x40;
1653 }
1654 if group_id.is_some() {
1655 new_flags |= 0x08;
1656 }
1657 if object_id.is_some() {
1658 new_flags |= 0x04;
1659 }
1660 if publisher_priority.is_some() {
1661 new_flags |= 0x10;
1662 }
1663 if original.has_extensions() {
1664 new_flags |= 0x20;
1665 }
1666
1667 Ok(FetchObjectHeader {
1668 serialization_flags: VarInt::from_u64(new_flags)?,
1669 group_id,
1670 subgroup_id,
1671 object_id,
1672 publisher_priority,
1673 extensions: original.extensions.clone(),
1674 payload_length: original.payload_length,
1675 })
1676 }
1677
1678 /// The Subgroup ID mode bits and the explicit field, if one is needed.
1679 ///
1680 /// The Object's own mode is tried first, so a run that inherited its
1681 /// Subgroup ID keeps inheriting it and its bytes do not move.
1682 fn subgroup_field(
1683 &self,
1684 original: &FetchObjectHeader,
1685 location: &FetchObjectLocation,
1686 ) -> Result<(u64, Option<VarInt>), CodecError> {
1687 // With the Datagram bit set the two low bits say nothing and no field
1688 // is on the wire, so the Object's own bits are carried across.
1689 if original.is_datagram() {
1690 return Ok((original.flags() & 0x03, None));
1691 }
1692
1693 let subgroup_id = location.subgroup_id.ok_or(CodecError::InvalidField)?;
1694 let inherits = self.subgroup_id == Some(subgroup_id);
1695 let successor = self.subgroup_id.is_some_and(|p| p.checked_add(1) == Some(subgroup_id));
1696
1697 let kept = match original.subgroup_mode() {
1698 FetchSubgroupMode::Zero if subgroup_id == 0 => Some((0x00, None)),
1699 FetchSubgroupMode::SameAsPrior if inherits => Some((0x01, None)),
1700 FetchSubgroupMode::PriorPlusOne if successor => Some((0x02, None)),
1701 FetchSubgroupMode::Present => Some((0x03, Some(subgroup_id))),
1702 _ => None,
1703 };
1704 let (mode, explicit) = match kept {
1705 Some(pair) => pair,
1706 None if subgroup_id == 0 => (0x00, None),
1707 None if inherits => (0x01, None),
1708 None if successor => (0x02, None),
1709 None => (0x03, Some(subgroup_id)),
1710 };
1711 Ok((mode, explicit.map(VarInt::from_u64).transpose()?))
1712 }
1713
1714 /// The Publisher Priority field, or `None` when the running one already
1715 /// says it.
1716 ///
1717 /// `location.publisher_priority` is the Priority in force rather than one
1718 /// this Object stated, so the comparison is against the Priority in force
1719 /// on the stream being written. Where the Object that stated it was the one
1720 /// removed, the two differ and this Object states it instead.
1721 fn priority_field(
1722 &self,
1723 original: &FetchObjectHeader,
1724 location: &FetchObjectLocation,
1725 ) -> Option<u8> {
1726 if original.has_priority() || self.publisher_priority != location.publisher_priority {
1727 return location.publisher_priority;
1728 }
1729 None
1730 }
1731
1732 /// Encode `location` against everything written so far and advance.
1733 ///
1734 /// Writes the header only. The payload is `original.payload_length` bytes
1735 /// and is the caller's to copy, unchanged.
1736 ///
1737 /// # Errors
1738 ///
1739 /// [`CodecError::InvalidField`] for an Object with no encoding; the writer
1740 /// is left untouched when this happens.
1741 pub fn write_object_header(
1742 &mut self,
1743 original: &FetchObjectHeader,
1744 location: &FetchObjectLocation,
1745 out: &mut impl BufMut,
1746 ) -> Result<FetchObjectHeader, CodecError> {
1747 let header = self.header_for(original, location)?;
1748 header.encode(out)?;
1749 self.advance(&header, location);
1750 Ok(header)
1751 }
1752
1753 /// Record what was written as the predecessor of whatever comes next.
1754 ///
1755 /// Mirrors [`FetchObjectReader::resolve`] exactly, including the two places
1756 /// draft-16 differs from the drafts after it: a Datagram-forwarded Object
1757 /// clears the running Subgroup ID rather than leaving it standing, and an
1758 /// Object that states no Priority leaves the running one alone.
1759 ///
1760 /// Public because a re-emitting caller has a second way of putting a frame
1761 /// on the wire: when the framing it arrived in still encodes the same
1762 /// meaning against the frame before it, its own bytes are forwarded
1763 /// untouched — no header is produced and nothing is copied. The writer
1764 /// still has to move, or the frame after it is encoded against a
1765 /// predecessor one frame stale. `written` is then the Object's own header, which is
1766 /// what was put on the wire.
1767 pub fn advance(&mut self, written: &FetchObjectHeader, location: &FetchObjectLocation) {
1768 if let Some(priority) = written.publisher_priority {
1769 self.publisher_priority = Some(priority);
1770 }
1771 self.group_id = Some(location.group_id);
1772 self.subgroup_id = location.subgroup_id;
1773 self.object_id = Some(location.object_id);
1774 }
1775}
1776
1777#[cfg(test)]
1778mod tests {
1779 use super::*;
1780
1781 /// Canonically encoded subgroup stream vectors from
1782 /// `test-vectors/transport/draft16/codec/data-streams/subgroup.json`.
1783 /// `subgroup-explicit-subgroup-id` is omitted: it encodes group_id 100 as a
1784 /// two-byte varint, which does not survive a minimal-width re-encode.
1785 const VECTORS: &[&str] = &[
1786 // subgroup-single-object
1787 "100100800004deadbeef",
1788 // subgroup-two-objects
1789 "100100800004deadbeef0002cafe",
1790 // subgroup-no-priority
1791 "3001000004deadbeef",
1792 // subgroup-with-extensions
1793 "11010080000004deadbeef",
1794 // subgroup-nonempty-extensions
1795 "1101008000023c0104deadbeef",
1796 // subgroup-status-end-of-group
1797 "100105800004deadbeef000003",
1798 // subgroup-status-end-of-track
1799 "10010a800004deadbeef000004",
1800 // subgroup-extensions-two-objects-empty
1801 "11010080000004deadbeef000002cafe",
1802 // subgroup-extensions-two-objects-nonempty
1803 "1101008000023c0204deadbeef00023c0302cafe",
1804 // subgroup-extensions-status-object
1805 "1101008000023c010003",
1806 // subgroup-end-of-group
1807 "180105800004deadbeef",
1808 // subgroup-id-first-object
1809 "120103800504deadbeef",
1810 ];
1811
1812 fn vi(v: u64) -> VarInt {
1813 VarInt::from_u64(v).unwrap()
1814 }
1815
1816 fn hex(s: &str) -> Vec<u8> {
1817 (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
1818 }
1819
1820 /// Decode a whole subgroup stream: the header, then every object up to
1821 /// the end of the buffer.
1822 fn decode_all(bytes: &[u8]) -> (SubgroupHeader, Vec<SubgroupObject>) {
1823 let mut cursor = bytes;
1824 let header = SubgroupHeader::decode(&mut cursor)
1825 .unwrap_or_else(|e| panic!("header decode failed: {e:?}"));
1826 let mut reader = SubgroupObjectReader::new(&header);
1827 let mut objects = Vec::new();
1828 while cursor.has_remaining() {
1829 objects.push(
1830 reader
1831 .read_object(&mut cursor)
1832 .unwrap_or_else(|e| panic!("object {} decode failed: {e:?}", objects.len())),
1833 );
1834 }
1835 (header, objects)
1836 }
1837
1838 fn encode_all(header: &SubgroupHeader, objects: &[SubgroupObject]) -> Vec<u8> {
1839 let mut buf = Vec::new();
1840 header.encode(&mut buf);
1841 let mut writer = SubgroupObjectReader::new(header);
1842 for o in objects {
1843 writer.write_object(o, &mut buf).unwrap_or_else(|e| panic!("write failed: {e:?}"));
1844 }
1845 buf
1846 }
1847
1848 fn object(id: u64, extensions: Vec<u8>, payload: Vec<u8>) -> SubgroupObject {
1849 SubgroupObject {
1850 object_id: vi(id),
1851 extension_headers: extensions,
1852 payload_length: vi(payload.len() as u64),
1853 object_status: None,
1854 payload,
1855 }
1856 }
1857
1858 // ── Object ID deltas ────────────────────────────────────
1859
1860 #[test]
1861 fn two_objects_with_extensions_have_distinct_ids() {
1862 // Vector `subgroup-extensions-two-objects-empty`: two objects, each
1863 // carrying an empty extensions block and a delta of 0. The delta is
1864 // biased by one whether or not the extensions bit is set, so the IDs
1865 // are 0 and 1 — not 0 and 0.
1866 let bytes = hex("11010080000004deadbeef000002cafe");
1867 let (header, objects) = decode_all(&bytes);
1868 assert!(header.has_extensions());
1869 assert_eq!(objects.len(), 2);
1870 assert_eq!(objects[0].object_id.into_inner(), 0);
1871 assert_eq!(objects[1].object_id.into_inner(), 1);
1872 assert_eq!(objects[0].payload, hex("deadbeef"));
1873 assert_eq!(objects[1].payload, hex("cafe"));
1874 assert!(objects.iter().all(|o| o.extension_headers.is_empty()));
1875 }
1876
1877 #[test]
1878 fn deltas_resolve_sparse_ids() {
1879 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
1880 let objects: Vec<_> =
1881 [3u64, 4, 40].iter().map(|&id| object(id, vec![], vec![0xAA, id as u8])).collect();
1882 let (_, decoded) = decode_all(&encode_all(&header, &objects));
1883 let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
1884 assert_eq!(ids, vec![3, 4, 40]);
1885 }
1886
1887 #[test]
1888 fn write_rejects_non_increasing_ids() {
1889 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
1890 let mut writer = SubgroupObjectReader::new(&header);
1891 let mut buf = Vec::new();
1892 writer.write_object(&object(7, vec![], vec![0x01]), &mut buf).unwrap();
1893 for id in [7u64, 6, 0] {
1894 let err = writer.write_object(&object(id, vec![], vec![0x01]), &mut buf).unwrap_err();
1895 assert!(matches!(err, CodecError::InvalidField), "id {id} gave {err:?}");
1896 }
1897 }
1898
1899 #[test]
1900 fn eliding_an_object_renumbers_its_successor() {
1901 let header = SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap();
1902 let all: Vec<_> = (0..5u64).map(|id| object(id, vec![], vec![id as u8])).collect();
1903 for elided in 0..5u64 {
1904 let kept: Vec<_> =
1905 all.iter().filter(|o| o.object_id.into_inner() != elided).cloned().collect();
1906 let (_, decoded) = decode_all(&encode_all(&header, &kept));
1907 let ids: Vec<u64> = decoded.iter().map(|o| o.object_id.into_inner()).collect();
1908 let expected: Vec<u64> = (0..5u64).filter(|&i| i != elided).collect();
1909 assert_eq!(ids, expected, "eliding object {elided}");
1910 }
1911 }
1912
1913 // ── Extension blocks ──────────────────────────────
1914
1915 #[test]
1916 fn extensions_blob_excludes_its_length_prefix() {
1917 // Vector `subgroup-extensions-two-objects-nonempty`: each
1918 // object carries a two-byte block, so the blob is those two bytes
1919 // with the `02` length prefix stripped.
1920 let bytes = hex("1101008000023c0204deadbeef00023c0302cafe");
1921 let (_, objects) = decode_all(&bytes);
1922 assert_eq!(objects.len(), 2);
1923 assert_eq!(objects[0].object_id.into_inner(), 0);
1924 assert_eq!(objects[1].object_id.into_inner(), 1);
1925 assert_eq!(objects[0].extension_headers, hex("3c02"));
1926 assert_eq!(objects[1].extension_headers, hex("3c03"));
1927 assert_eq!(objects[0].payload, hex("deadbeef"));
1928 assert_eq!(objects[1].payload, hex("cafe"));
1929 }
1930
1931 #[test]
1932 fn status_object_carries_its_extensions_block() {
1933 let (_, objects) = decode_all(&hex("1101008000023c010003"));
1934 assert_eq!(objects.len(), 1);
1935 assert_eq!(objects[0].extension_headers, hex("3c01"));
1936 assert_eq!(objects[0].payload_length.into_inner(), 0);
1937 assert_eq!(objects[0].object_status.map(ObjectStatus::as_u64), Some(3));
1938 assert!(objects[0].payload.is_empty());
1939 }
1940
1941 // ── Object status ───────────────────────────────────────
1942
1943 /// A subgroup header with neither extensions nor an explicit subgroup ID,
1944 /// so an object on this stream is just `delta, payload_length, [status]`.
1945 fn plain_header() -> SubgroupHeader {
1946 SubgroupHeader::decode(&mut &hex("100100800004deadbeef")[..]).unwrap()
1947 }
1948
1949 /// A status object on a [`plain_header`] stream: object 0, empty payload,
1950 /// `code` as the status. Every code used here is one wire byte.
1951 fn subgroup_status_body(code: u64) -> Vec<u8> {
1952 let mut buf = vec![0x00, 0x00];
1953 VarInt::from_u64(code).unwrap().encode(&mut buf);
1954 buf
1955 }
1956
1957 fn status_object(status: Option<ObjectStatus>) -> SubgroupObject {
1958 SubgroupObject {
1959 object_id: vi(0),
1960 extension_headers: vec![],
1961 payload_length: vi(0),
1962 object_status: status,
1963 payload: vec![],
1964 }
1965 }
1966
1967 /// A status datagram carrying `code`. Type 0x20 sets the status flag and
1968 /// leaves the object-id (0x04), default-priority (0x08) and extensions
1969 /// (0x01) flags clear, so the layout is `type, track_alias, group_id,
1970 /// object_id, priority, status`.
1971 fn datagram_status_bytes(code: u64) -> Vec<u8> {
1972 let mut buf = vec![0x20, 0x01, 0x02, 0x03, 0x80];
1973 VarInt::from_u64(code).unwrap().encode(&mut buf);
1974 buf
1975 }
1976
1977 fn status_datagram(status: Option<ObjectStatus>) -> DatagramHeader {
1978 DatagramHeader {
1979 datagram_type: 0x20,
1980 track_alias: vi(1),
1981 group_id: vi(2),
1982 object_id: vi(3),
1983 publisher_priority: Some(0x80),
1984 extension_headers: vec![],
1985 object_status: status,
1986 }
1987 }
1988
1989 /// Every status the type can hold reaches the wire as its own code and
1990 /// comes back unchanged, through both subgroup readers and the datagram.
1991 ///
1992 /// Observed by making `write_object` encode `ObjectStatus::Normal` instead
1993 /// of the object's own status, which fails this with:
1994 ///
1995 /// ```text
1996 /// assertion `left == right` failed: EndOfGroup on the subgroup wire
1997 /// left: [0, 0, 0]
1998 /// right: [0, 0, 3]
1999 /// ```
2000 #[test]
2001 fn assigned_statuses_round_trip() {
2002 let header = plain_header();
2003 for &status in ObjectStatus::ALL {
2004 let mut bytes = Vec::new();
2005 SubgroupObjectReader::new(&header)
2006 .write_object(&status_object(Some(status)), &mut bytes)
2007 .unwrap();
2008 assert_eq!(
2009 bytes,
2010 subgroup_status_body(status.as_u64()),
2011 "{status:?} on the subgroup wire"
2012 );
2013
2014 let decoded = SubgroupObjectReader::new(&header)
2015 .read_object(&mut &bytes[..])
2016 .unwrap_or_else(|e| panic!("{status:?} was written and then refused: {e:?}"));
2017 assert_eq!(decoded.object_status, Some(status), "{status:?} through read_object");
2018
2019 let meta = SubgroupObjectReader::new(&header)
2020 .read_object_meta(&mut &bytes[..])
2021 .unwrap_or_else(|e| panic!("{status:?} was written and then refused: {e:?}"));
2022 assert_eq!(meta.status, Some(status.as_u64()), "{status:?} through read_object_meta");
2023
2024 let datagram = status_datagram(Some(status));
2025 let mut bytes = Vec::new();
2026 datagram.encode(&mut bytes);
2027 assert_eq!(
2028 bytes,
2029 datagram_status_bytes(status.as_u64()),
2030 "{status:?} on the datagram wire"
2031 );
2032 let decoded = DatagramHeader::decode(&mut &bytes[..]).unwrap_or_else(|e| {
2033 panic!("{status:?} datagram was written and then refused: {e:?}")
2034 });
2035 assert_eq!(decoded, datagram, "{status:?} datagram round trip");
2036 }
2037 }
2038
2039 /// A datagram whose type byte sets the status flag always carries a status
2040 /// field, because the flag is what puts the field on the wire — an unset
2041 /// `object_status` writes Normal rather than nothing.
2042 ///
2043 /// Observed by putting back the `if let Some(s) = &self.object_status`
2044 /// with no `else`, which emits a datagram that stops before its status
2045 /// field and fails this with:
2046 ///
2047 /// ```text
2048 /// assertion `left == right` failed
2049 /// left: [32, 1, 2, 3, 128]
2050 /// right: [32, 1, 2, 3, 128, 0]
2051 /// ```
2052 ///
2053 /// and, with the byte comparison removed, fails the decode of its own
2054 /// output with `own output refused: VarInt(UnexpectedEnd)`.
2055 #[test]
2056 fn a_status_datagram_without_a_status_encodes_normal() {
2057 let mut bytes = Vec::new();
2058 status_datagram(None).encode(&mut bytes);
2059 assert_eq!(bytes, datagram_status_bytes(ObjectStatus::Normal.as_u64()));
2060 let decoded = DatagramHeader::decode(&mut &bytes[..])
2061 .unwrap_or_else(|e| panic!("own output refused: {e:?}"));
2062 assert_eq!(decoded.object_status, Some(ObjectStatus::Normal));
2063 }
2064
2065 /// The codes this draft's decoders accept are exactly the codes its
2066 /// encoders can emit.
2067 ///
2068 /// The sweep covers `0x00..=0x3f`, the whole one-byte varint range, so it
2069 /// contains every code draft-16 assigns, the gap inside that range (0x2)
2070 /// and the code draft-16 dropped (0x1, Object Does Not Exist). The
2071 /// expected set is read from [`ObjectStatus::ALL`] rather than written out
2072 /// here, so reassigning a code moves both halves of the test at once.
2073 ///
2074 /// Observed by teaching `ObjectStatus::from_u64` to answer `Some` for 0x1
2075 /// again — the code draft-15 assigned to Object Does Not Exist and
2076 /// draft-16 dropped, mapped onto a surviving variant since draft-16 has no
2077 /// variant of its own for it — which fails this with:
2078 ///
2079 /// ```text
2080 /// assertion `left == right` failed: subgroup read_object on status 0x1: Ok(SubgroupObject { object_id: VarInt(0), extension_headers: [], payload_length: VarInt(0), object_status: Some(Normal), payload: [] })
2081 /// left: true
2082 /// right: false
2083 /// ```
2084 #[test]
2085 fn the_wire_accepts_exactly_what_the_type_can_hold() {
2086 let header = plain_header();
2087 for code in 0x00u64..=0x3f {
2088 let assigned = ObjectStatus::ALL.iter().any(|s| s.as_u64() == code);
2089 let body = subgroup_status_body(code);
2090
2091 let read = SubgroupObjectReader::new(&header).read_object(&mut &body[..]);
2092 assert_eq!(
2093 read.is_ok(),
2094 assigned,
2095 "subgroup read_object on status {code:#x}: {read:?}"
2096 );
2097
2098 let meta = SubgroupObjectReader::new(&header).read_object_meta(&mut &body[..]);
2099 assert_eq!(
2100 meta.is_ok(),
2101 assigned,
2102 "subgroup read_object_meta on status {code:#x}: {meta:?}"
2103 );
2104
2105 let datagram = DatagramHeader::decode(&mut &datagram_status_bytes(code)[..]);
2106 assert_eq!(
2107 datagram.is_ok(),
2108 assigned,
2109 "status datagram on status {code:#x}: {datagram:?}"
2110 );
2111
2112 // The other direction: an accepted code is one the encoders can
2113 // reach, and they reach it with exactly these bytes.
2114 if assigned {
2115 let status = ObjectStatus::from_u64(code).unwrap();
2116 let mut bytes = Vec::new();
2117 SubgroupObjectReader::new(&header)
2118 .write_object(&status_object(Some(status)), &mut bytes)
2119 .unwrap();
2120 assert_eq!(bytes, body, "write_object on status {code:#x}");
2121 let mut bytes = Vec::new();
2122 status_datagram(Some(status)).encode(&mut bytes);
2123 assert_eq!(
2124 bytes,
2125 datagram_status_bytes(code),
2126 "datagram encode on status {code:#x}"
2127 );
2128 }
2129 }
2130 }
2131
2132 // ── Re-encoding ─────────────────────────────────────────
2133
2134 #[test]
2135 fn vectors_re_encode_byte_identically() {
2136 for vector in VECTORS {
2137 let bytes = hex(vector);
2138 let (header, objects) = decode_all(&bytes);
2139 assert_eq!(encode_all(&header, &objects), bytes, "[{vector}] re-encode");
2140 }
2141 }
2142
2143 // ── Payload-free framing ────────────────────────────────
2144
2145 #[test]
2146 fn meta_matches_read_object() {
2147 for vector in VECTORS {
2148 let bytes = hex(vector);
2149 let mut cursor = &bytes[..];
2150 let header = SubgroupHeader::decode(&mut cursor).unwrap();
2151 let mut full_reader = SubgroupObjectReader::new(&header);
2152 let mut meta_reader = SubgroupObjectReader::new(&header);
2153 let mut full_cursor = cursor;
2154 let mut meta_cursor = cursor;
2155 while meta_cursor.has_remaining() {
2156 let before = meta_cursor.remaining();
2157 let object = full_reader.read_object(&mut full_cursor).unwrap();
2158 let meta = meta_reader.read_object_meta(&mut meta_cursor).unwrap();
2159 assert_eq!(meta.object_id, object.object_id.into_inner(), "[{vector}]");
2160 assert_eq!(
2161 meta.extension_headers_len,
2162 object.extension_headers.len() as u64,
2163 "[{vector}]"
2164 );
2165 assert_eq!(meta.payload_length, object.payload_length.into_inner(), "[{vector}]");
2166 assert_eq!(
2167 meta.status,
2168 object.object_status.map(ObjectStatus::as_u64),
2169 "[{vector}]"
2170 );
2171 assert_eq!(meta.wire_len, (before - meta_cursor.remaining()) as u64, "[{vector}]");
2172 assert_eq!(full_cursor.remaining(), meta_cursor.remaining(), "[{vector}]");
2173 }
2174 }
2175 }
2176
2177 #[test]
2178 fn short_buffers_report_unexpected_end() {
2179 let bytes = hex("1101008000023c0204deadbeef00023c0302cafe");
2180 let mut cursor = &bytes[..];
2181 let header = SubgroupHeader::decode(&mut cursor).unwrap();
2182 let objects_start = bytes.len() - cursor.len();
2183 for cut in objects_start..bytes.len() {
2184 let mut reader = SubgroupObjectReader::new(&header);
2185 let mut meta_reader = SubgroupObjectReader::new(&header);
2186 let mut cursor = &bytes[objects_start..cut];
2187 let mut meta_cursor = cursor;
2188 while cursor.has_remaining() {
2189 if let Err(err) = reader.read_object(&mut cursor) {
2190 assert!(
2191 matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2192 "cut {cut} gave {err:?}"
2193 );
2194 break;
2195 }
2196 }
2197 while meta_cursor.has_remaining() {
2198 if let Err(err) = meta_reader.read_object_meta(&mut meta_cursor) {
2199 assert!(
2200 matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
2201 "cut {cut} gave {err:?}"
2202 );
2203 break;
2204 }
2205 }
2206 }
2207 }
2208}