Skip to main content

moqtap_proxy/
framer.rs

1//! Byte-exact object framing for MoQT data streams.
2//!
3//! [`ObjectFramer`] turns the raw bytes of one unidirectional data stream
4//! into individually addressable objects without altering them: every item
5//! it yields is a slice of the bytes that were fed in, and concatenating
6//! those items reproduces the stream exactly.
7//!
8//! Framing is opt-in because it costs latency and memory — an object is
9//! only emitted once it is buffered whole. The proxy pays that cost only
10//! when something is observing; see `session::pipe_data`.
11
12use std::collections::HashMap;
13use std::sync::{Arc, Mutex};
14
15use bytes::{Buf, Bytes, BytesMut};
16
17use moqtap_codec::dispatch::{
18    reemit_subgroup_object, AnyFetchFrame, AnyFetchGroupOrder, AnyFetchHeader, AnyFetchObjectMeta,
19    AnyFetchObjectReader, AnyFetchObjectWriter, AnySubgroupHeader, AnySubgroupObjectMeta,
20    AnySubgroupObjectReader, FetchReemit,
21};
22use moqtap_codec::version::DraftVersion;
23
24use crate::capability::fetch_group_order_is_needed;
25use crate::event::DataStreamHeaderKind;
26use crate::instrument::Recorder;
27use crate::parser::data::is_incomplete_error;
28use crate::types::DataStreamType;
29
30pub use crate::types::{BypassReason, ObjectMeta};
31
32/// Every fetch this session has learned a Group Order for, keyed by the
33/// Request ID that names it on both the control plane and the data stream.
34///
35/// # Why a fetch stream needs something from outside itself
36///
37/// Drafts 18, 19 and 20 write a fetch Object's Group ID as a difference from the
38/// Object before it, and the fetch's Group Order decides whether the
39/// difference counts up or down — draft-19 Section 11.4.4.1. Nothing on the
40/// data stream states the order, so a framer handed only the stream cannot
41/// reach an absolute Group ID, and the wrong choice decodes every Object
42/// without an error under Group IDs walking the wrong way.
43///
44/// The order is on the FETCH. Draft-19 Section 10.12.3: "The publisher
45/// responding to a FETCH is responsible for delivering all available Objects
46/// in the requested range in the requested order (see Section 10.2.8)." The
47/// session's control pipes read it off each FETCH they carry and file it
48/// here; [`ObjectFramer`] takes it out again when the response stream opens.
49///
50/// # Why an entry is taken rather than read
51///
52/// One FETCH opens one response stream, so an entry has exactly one reader
53/// and is spent by it. Taking bounds the table to the fetches that have been
54/// asked for and not yet answered, on a session that may run for hours, and
55/// it needs no rule about when to forget: the reader is the rule.
56///
57/// What that leaves is a fetch the publisher answered with an error rather
58/// than a stream, whose entry no reader ever comes for. A cap is the
59/// bound on those, and it fails closed — past it nothing is recorded, so the
60/// affected streams are bypassed and say so rather than being read against
61/// somebody else's order.
62#[derive(Debug, Default)]
63pub struct FetchGroupOrders {
64    known: Mutex<HashMap<u64, AnyFetchGroupOrder>>,
65}
66
67impl FetchGroupOrders {
68    /// How many asked-for-but-unanswered fetches one session may hold.
69    ///
70    /// Reached only by a peer that sends FETCHes whose responses never open a
71    /// stream, since every answered one takes its own entry away again.
72    const CAP: usize = 1024;
73
74    /// File the order a FETCH asked for, under the Request ID it asked under.
75    ///
76    /// Call with the order the *peer will act on*, which on a session whose
77    /// control frames a hook may rewrite is the one leaving the proxy rather
78    /// than the one that arrived.
79    pub fn record(&self, request_id: u64, order: AnyFetchGroupOrder) {
80        let mut known = self.known.lock().expect("no task holds the fetch orders across a panic");
81        if known.len() >= Self::CAP && !known.contains_key(&request_id) {
82            return;
83        }
84        known.insert(request_id, order);
85    }
86
87    /// Take the order filed for `request_id`, if one was.
88    #[must_use]
89    pub fn take(&self, request_id: u64) -> Option<AnyFetchGroupOrder> {
90        self.known
91            .lock()
92            .expect("no task holds the fetch orders across a panic")
93            .remove(&request_id)
94    }
95}
96
97/// Padding width that puts every wire length a varint can express within
98/// measuring reach.
99///
100/// Half of `usize::MAX`, so adding the buffered bytes cannot overflow
101/// [`PaddedBuf::remaining`]; a varint tops out at `2^62 - 1`, well inside
102/// it on a 64-bit target.
103const UNBOUNDED_MEASURING_PAD: usize = usize::MAX / 2;
104
105/// Configuration for [`ObjectFramer`].
106///
107/// Construct with [`FramerConfig::default`] and adjust fields, or with
108/// [`FramerConfig::new`] and the builder setters. The struct is
109/// `#[non_exhaustive]` so later releases can add knobs without a break;
110/// that also means a struct literal no longer compiles from outside this
111/// crate.
112#[derive(Debug, Clone)]
113#[non_exhaustive]
114pub struct FramerConfig {
115    /// Largest object the framer will buffer whole, in bytes. Objects
116    /// larger than this are streamed through as
117    /// [`FramerOut::Passthrough`] and are not individually addressable.
118    /// Default 4 MiB.
119    pub max_buffered_object_bytes: usize,
120}
121
122impl Default for FramerConfig {
123    fn default() -> Self {
124        Self { max_buffered_object_bytes: 4 * 1024 * 1024 }
125    }
126}
127
128impl FramerConfig {
129    /// A config with default values.
130    #[must_use]
131    pub fn new() -> Self {
132        Self::default()
133    }
134
135    /// Set the largest object the framer will buffer whole.
136    #[must_use]
137    pub fn with_max_buffered_object_bytes(mut self, bytes: usize) -> Self {
138        self.max_buffered_object_bytes = bytes;
139        self
140    }
141}
142
143/// One item produced by [`ObjectFramer::poll`].
144#[derive(Debug)]
145#[non_exhaustive]
146pub enum FramerOut {
147    /// The stream's header, with its exact wire bytes (stream-type field
148    /// included).
149    Header {
150        /// The decoded header.
151        header: DataStreamHeaderKind,
152        /// The header's wire bytes, including the stream-type field.
153        raw: Bytes,
154    },
155    /// A complete object, with its exact wire bytes.
156    Object {
157        /// The object's framing, without its payload.
158        meta: ObjectMeta,
159        /// The object's complete wire bytes, framing and payload.
160        raw: Bytes,
161    },
162    /// Bytes the framer is forwarding without interpreting them: an object
163    /// larger than the buffer cap, a fetch stream on a draft whose fetch
164    /// objects are not addressed, or any stream the framer has stopped
165    /// parsing. These bytes are not individually addressable.
166    Passthrough(Bytes),
167    /// The framer has stopped parsing this stream.
168    ///
169    /// Carries **no bytes**, so [`ObjectFramer::poll`]'s concatenation
170    /// invariant is untouched: this item contributes nothing to the
171    /// reconstruction. Emitted exactly once per stream.
172    ///
173    /// **Ordering: immediately after the item the bypass was decided
174    /// during, not in place of it.** Every one of `latch_bypass`'s call
175    /// sites returns a *different* `FramerOut` from the same `poll()` —
176    /// the two header sites fall through to [`Self::Header`], the
177    /// measuring-reach sites return [`Self::Passthrough`], the decode and
178    /// fix-up sites return [`Self::Error`] — and `poll()` returns one
179    /// item. The variant is therefore **deferred**: `latch_bypass` stores
180    /// a `pending_bypass: Option<BypassReason>`, and `poll()` drains it at
181    /// the top of its next call, before anything else.
182    Bypassed {
183        /// Why parsing stopped.
184        reason: BypassReason,
185        /// Whether an elide fix-up was still owed when parsing stopped.
186        ///
187        /// `true` means the remainder of this stream cannot be renumbered
188        /// and the destination must be reset rather than forwarded — the
189        /// bytes still in flight decode to Object IDs one ahead of what
190        /// was actually delivered.
191        fixup_owed: bool,
192    },
193    /// Not enough buffered bytes to produce another item.
194    NeedMore,
195    /// The stream could not be parsed. The framer switches to
196    /// [`Passthrough`](Self::Passthrough) for the remainder of the stream
197    /// and never reports a second error.
198    Error(String),
199}
200
201/// The state an elide leaves behind on a subgroup stream.
202///
203/// Read it with [`ObjectFramer::elide_cursor`]. The framer owns this state
204/// rather than the caller because it forwards objects the caller never
205/// sees an [`ObjectMeta`] for — an object one byte over
206/// [`FramerConfig::max_buffered_object_bytes`] leaves as
207/// [`FramerOut::Passthrough`], and a cursor kept outside the framer would
208/// let its stale delta go out on the wire.
209#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub struct ElideCursor {
211    /// Absolute Object ID of the last object actually forwarded, or `None`
212    /// when none has been.
213    pub last_forwarded_id: Option<u64>,
214    /// Whether the next object the framer emits will have its leading ID
215    /// field rewritten. Always `false` on drafts 07-13, where IDs are
216    /// absolute, and on fetch streams.
217    pub fixup_pending: bool,
218}
219
220/// The object the framer emitted most recently, whose forward-or-elide
221/// disposition has not been committed to the cursor yet.
222#[derive(Debug, Clone, Copy)]
223struct PendingObject {
224    object_id: u64,
225    index_in_stream: u64,
226}
227
228/// Identity fields the stream header contributes to every object on a
229/// subgroup stream.
230#[derive(Debug, Clone, Copy, Default)]
231struct SubgroupContext {
232    track_alias: u64,
233    group_id: u64,
234    subgroup_id: Option<u64>,
235    publisher_priority: Option<u8>,
236}
237
238/// What the framer is currently decoding.
239#[derive(Debug)]
240enum Stage {
241    /// Nothing decoded yet; the next bytes are the stream header.
242    AwaitingHeader,
243    /// Subgroup objects, with the per-stream delta state.
244    Subgroup(AnySubgroupObjectReader),
245    /// Fetch objects, with the state on both sides of the framer: what the
246    /// stream said, and what has actually been forwarded from it.
247    ///
248    /// The two are the same value until something is elided, and every frame
249    /// goes through the writer regardless. A frame it never saw would leave
250    /// it a frame behind, and a survivor re-encoded against a stale
251    /// predecessor is a renumbered stream rather than a decode error.
252    Fetch { reader: AnyFetchObjectReader, writer: AnyFetchObjectWriter },
253}
254
255/// Frames a unidirectional MoQT data stream into individually addressable
256/// objects while preserving the exact wire bytes.
257///
258/// Feed it the raw bytes of one stream, in order, from the stream's first
259/// byte. Drain [`poll`](Self::poll) after each [`feed`](Self::feed) until
260/// it returns [`FramerOut::NeedMore`]. The concatenation of every `raw`
261/// and `Passthrough` payload it yields equals the concatenation of every
262/// chunk fed to it, unless [`note_elided`](Self::note_elided) has been
263/// called — see [`Self::poll`].
264#[derive(Debug)]
265pub struct ObjectFramer {
266    stream_type: DataStreamType,
267    draft: DraftVersion,
268    config: FramerConfig,
269    buf: BytesMut,
270    stage: Stage,
271    context: SubgroupContext,
272    /// Bytes still owed to an oversized object that is being streamed
273    /// through uninterpreted. Denominated in **source** bytes — see
274    /// [`Self::poll_oversized`].
275    passthrough_remaining: u64,
276    /// Set once parsing has been abandoned for the rest of the stream.
277    bypassed: bool,
278    /// Why parsing was abandoned, waiting for [`Self::poll`] to drain it
279    /// as a [`FramerOut::Bypassed`].
280    ///
281    /// `fixup_owed` is read off [`Self::fixup_pending`] at drain time
282    /// rather than being captured here, and the two are the same value:
283    /// once `bypassed` is set, `poll` never re-enters `poll_object`, which
284    /// is the only thing that clears the flag, and no `Object` item can be
285    /// emitted between the latch and the drain for `note_elided` to set it.
286    pending_bypass: Option<BypassReason>,
287    index_in_stream: u64,
288    /// Absolute Object ID of the last object actually forwarded.
289    last_forwarded_id: Option<u64>,
290    /// Whether the next object emitted still owes a fix-up: on a subgroup
291    /// stream the leading Object ID varint rewritten against
292    /// [`Self::last_forwarded_id`], on a fetch stream a survivor re-encoded
293    /// against the frame that is now in front of it.
294    ///
295    /// Both are settled by emitting one object, after which the writing
296    /// cursor is level with the reading one again.
297    fixup_pending: bool,
298    /// The fetch writer as it stood before the object emitted most recently
299    /// was re-emitted through it.
300    ///
301    /// Re-emitting advances the writer, and the caller's verdict on that
302    /// object does not land until the next poll, so an elide has to put the
303    /// writer back where it was — otherwise the survivor after it is encoded
304    /// against an object nobody received.
305    fetch_rollback: Option<AnyFetchObjectWriter>,
306    /// The object emitted most recently, awaiting the caller's verdict.
307    /// Committed lazily at the top of [`Self::poll_object`].
308    pending_disposition: Option<PendingObject>,
309    /// Slow-path counters for the session this framer belongs to.
310    counters: Arc<Recorder>,
311    /// The session's answered fetches, on the drafts where a fetch stream
312    /// cannot be read without one. `None` for a framer built outside a
313    /// session, and on every draft that needs no order.
314    fetch_orders: Option<Arc<FetchGroupOrders>>,
315}
316
317impl ObjectFramer {
318    /// A framer whose slow-path counters go to `counters`.
319    ///
320    /// **The only constructor `session.rs` may use.** The counters are
321    /// session-scoped, and a framer that cannot reach its session's
322    /// [`Recorder`] would leave `framers_created`, `framer_header_polls`,
323    /// `framer_object_polls`, `objects_not_addressable` and
324    /// `object_ids_rewritten` at zero for every real session — which is a
325    /// *passing* `Interest::NONE` proof obtained by measuring nothing.
326    ///
327    /// [`Self::new`] is kept, unchanged, for tests and downstream callers
328    /// that construct a framer to parse bytes rather than to forward them;
329    /// it is equivalent to passing a fresh `Recorder` whose counts nobody
330    /// reads. That is why this is an **addition** and not a signature
331    /// change.
332    pub fn with_recorder(
333        stream_type: DataStreamType,
334        draft: DraftVersion,
335        config: FramerConfig,
336        counters: Arc<Recorder>,
337    ) -> Self {
338        counters.note_framer_created();
339        Self {
340            stream_type,
341            draft,
342            config,
343            buf: BytesMut::with_capacity(4096),
344            stage: Stage::AwaitingHeader,
345            context: SubgroupContext::default(),
346            passthrough_remaining: 0,
347            bypassed: false,
348            pending_bypass: None,
349            index_in_stream: 0,
350            last_forwarded_id: None,
351            fixup_pending: false,
352            fetch_rollback: None,
353            pending_disposition: None,
354            counters,
355            fetch_orders: None,
356        }
357    }
358
359    /// Read this stream's fetch Objects against the order its FETCH asked for.
360    ///
361    /// Only drafts 18, 19 and 20 need it — see [`FetchGroupOrders`] — and only a
362    /// fetch stream consults it; a subgroup framer given one ignores it. A
363    /// framer built without it on a draft that needs one reports
364    /// [`BypassReason::FetchGroupOrderUnknown`] and forwards the stream
365    /// uninterpreted, which is what every caller outside a session gets and
366    /// what the session itself gets for a stream naming a request it never
367    /// saw asked for.
368    #[must_use]
369    pub fn with_fetch_group_orders(mut self, orders: Arc<FetchGroupOrders>) -> Self {
370        self.fetch_orders = Some(orders);
371        self
372    }
373
374    /// Create a framer for a stream of the given kind on the given draft,
375    /// discarding its counters.
376    ///
377    /// Increments go to a private [`Recorder`] nothing can read, so a
378    /// caller that wants a session's counters to move must use
379    /// [`Self::with_recorder`]. Retained for callers that parse bytes
380    /// rather than forward them, where the counts are not the point.
381    pub fn new(stream_type: DataStreamType, draft: DraftVersion, config: FramerConfig) -> Self {
382        Self::with_recorder(stream_type, draft, config, Arc::new(Recorder::new()))
383    }
384
385    /// The state an elide has left behind on this stream.
386    ///
387    /// Read-only, and read-anytime: the framer applies the fix-up itself,
388    /// so nothing outside has to act on this.
389    #[must_use]
390    pub fn elide_cursor(&self) -> ElideCursor {
391        ElideCursor { last_forwarded_id: self.last_forwarded_id, fixup_pending: self.fixup_pending }
392    }
393
394    /// Record that the object the framer emitted **most recently** was not
395    /// forwarded.
396    ///
397    /// Call exactly once, immediately after deciding to drop an object,
398    /// and before the next [`Self::poll`]. `meta` must be the meta the
399    /// framer handed out for that object; it is `debug_assert`ed against
400    /// the framer's own record, because calling this out of order is the
401    /// one way to corrupt a stream silently.
402    ///
403    /// On drafts 07-13 subgroup streams and on drafts 07-14 fetch streams
404    /// this only suppresses the cursor advance — those Object IDs are
405    /// absolute, so the bytes of every later object already say the truth.
406    /// Elsewhere it also arms a fix-up: the leading Object ID varint on a
407    /// drafts 14-21 subgroup stream, and the whole framing of the next frame
408    /// on a drafts 15-21 fetch stream, where it additionally puts the fetch
409    /// writer back to where the last forwarded frame left it.
410    pub fn note_elided(&mut self, meta: &ObjectMeta) {
411        let pending = self.pending_disposition.take();
412        debug_assert!(
413            matches!(
414                pending,
415                Some(p) if p.object_id == meta.object_id
416                    && p.index_in_stream == meta.index_in_stream
417            ),
418            "note_elided must name the object the framer emitted most recently; \
419             got object {} at index {}, framer holds {:?}",
420            meta.object_id,
421            meta.index_in_stream,
422            pending.map(|p| (p.object_id, p.index_in_stream)),
423        );
424        // Taking `pending` is the elide: the cursor simply does not
425        // advance to it. Arming the fix-up on a `None` would renumber an
426        // object whose predecessor was already committed as forwarded.
427        if pending.is_none() {
428            return;
429        }
430        if self.elide_owes_a_fixup() {
431            self.fixup_pending = true;
432        }
433        // The frame was re-emitted through the writer before the caller saw
434        // it, so the writer is one frame ahead of what the destination
435        // received. Nothing else can restore it: the per-draft state is what
436        // the *forwarded* frames left behind, and this frame was not one.
437        if let Some(rollback) = self.fetch_rollback.take() {
438            if let Stage::Fetch { writer, .. } = &mut self.stage {
439                *writer = rollback;
440            }
441        }
442    }
443
444    /// Buffer a chunk of stream bytes.
445    pub fn feed(&mut self, chunk: &[u8]) {
446        self.buf.extend_from_slice(chunk);
447    }
448
449    /// Bytes buffered but not yet emitted. Non-zero only mid-object.
450    pub fn buffered(&self) -> usize {
451        self.buf.len()
452    }
453
454    /// `true` once the framer has stopped parsing this stream and is
455    /// forwarding bytes uninterpreted.
456    pub fn is_bypassed(&self) -> bool {
457        self.bypassed
458    }
459
460    /// Flush any buffered bytes at end of stream.
461    ///
462    /// Called when the source signals FIN. Returns whatever the framer
463    /// still holds — a truncated final object, or bytes buffered behind an
464    /// incomplete framing — so the caller can forward them before
465    /// finishing the destination stream. Forgetting this call turns a
466    /// clean FIN into silent truncation.
467    pub fn finish(&mut self) -> Option<Bytes> {
468        self.passthrough_remaining = 0;
469        if self.buf.is_empty() {
470            None
471        } else {
472            Some(self.buf.split().freeze())
473        }
474    }
475
476    /// Produce the next item, or [`FramerOut::NeedMore`].
477    ///
478    /// # Invariant
479    ///
480    /// Concatenating the `raw` field of every [`Header`](FramerOut::Header)
481    /// and [`Object`](FramerOut::Object) and the payload of every
482    /// [`Passthrough`](FramerOut::Passthrough), in the order produced,
483    /// reproduces the fed bytes exactly — on every draft, including
484    /// streams that fall back to bypass — **unless**
485    /// [`Self::note_elided`] has been called, which deliberately removes
486    /// an object's bytes and may rewrite one leading varint.
487    ///
488    /// That exception is the *only* one, and it is opt-in per stream: a
489    /// framer used as a pure observer never calls `note_elided`, so its
490    /// output stays byte-identical to its input. When `note_elided` has
491    /// been called on a drafts 14-21 subgroup stream, the next object the
492    /// framer emits has its leading Object ID varint re-encoded against
493    /// the last object actually forwarded — one field, in one object, and
494    /// every byte after it copied verbatim. Everything else, on every
495    /// other stream, still concatenates back to the source exactly.
496    ///
497    /// [`FramerOut::Bypassed`] and [`FramerOut::NeedMore`] carry no bytes
498    /// and so contribute nothing to the reconstruction either way.
499    pub fn poll(&mut self) -> FramerOut {
500        // Drained before anything else. `latch_bypass` cannot return this
501        // item itself — each of its call sites returns a different
502        // `FramerOut` from the same `poll()` — so the bypass is reported
503        // on the next call, immediately after the item it was decided
504        // during. Zero bytes, so the invariant above is untouched.
505        if let Some(reason) = self.pending_bypass.take() {
506            return FramerOut::Bypassed { reason, fixup_owed: self.fixup_pending };
507        }
508        if self.passthrough_remaining > 0 {
509            let owed = self.passthrough_remaining;
510            return self.emit_passthrough(owed);
511        }
512        if self.bypassed {
513            return self.emit_passthrough(u64::MAX);
514        }
515        match self.stage {
516            Stage::AwaitingHeader => self.poll_header(),
517            Stage::Subgroup(_) | Stage::Fetch { .. } => self.poll_object(),
518        }
519    }
520
521    /// Hand out up to `limit` buffered bytes without interpreting them.
522    fn emit_passthrough(&mut self, limit: u64) -> FramerOut {
523        if self.buf.is_empty() {
524            return FramerOut::NeedMore;
525        }
526        let take = usize::try_from(limit).unwrap_or(usize::MAX).min(self.buf.len());
527        self.passthrough_remaining = self.passthrough_remaining.saturating_sub(take as u64);
528        FramerOut::Passthrough(self.buf.split_to(take).freeze())
529    }
530
531    /// Stop parsing this stream for good, record why, and release
532    /// everything buffered.
533    ///
534    /// Draining here is what keeps memory bounded: without it a stream the
535    /// framer cannot parse grows the buffer for as long as the peer keeps
536    /// writing.
537    ///
538    /// `reason` is stored rather than returned, because every call site
539    /// returns a different [`FramerOut`] from the same
540    /// [`poll`](Self::poll); the next `poll` drains it. Guarded on
541    /// `bypassed` so a second latch — which today cannot happen, since
542    /// `poll` short-circuits once the flag is set — could never overwrite
543    /// the first reason or double-count
544    /// `Counters::streams_not_shapeable`, whose whole job is to be one per
545    /// stream.
546    fn latch_bypass(&mut self, reason: BypassReason) {
547        if !self.bypassed {
548            self.bypassed = true;
549            self.pending_bypass = Some(reason);
550            self.counters.note_stream_not_shapeable();
551        }
552        self.passthrough_remaining = 0;
553    }
554
555    fn poll_header(&mut self) -> FramerOut {
556        self.counters.note_framer_header_poll();
557        if self.buf.is_empty() {
558            return FramerOut::NeedMore;
559        }
560
561        let snapshot: &[u8] = &self.buf[..];
562        let mut cursor: &[u8] = snapshot;
563        let decoded = match self.stream_type {
564            DataStreamType::Subgroup => AnySubgroupHeader::decode_stream(self.draft, &mut cursor)
565                .map(DataStreamHeaderKind::Subgroup),
566            DataStreamType::Fetch => AnyFetchHeader::decode_stream(self.draft, &mut cursor)
567                .map(DataStreamHeaderKind::Fetch),
568        };
569
570        match decoded {
571            Ok(header) => {
572                let consumed = snapshot.len() - cursor.remaining();
573                match &header {
574                    DataStreamHeaderKind::Subgroup(h) => match AnySubgroupObjectReader::new(h) {
575                        Ok(reader) => {
576                            self.context = subgroup_context(h);
577                            self.stage = Stage::Subgroup(reader);
578                        }
579                        // A subgroup stream type the object reader
580                        // rejects. The header still decoded, so report it,
581                        // then forward the rest uninterpreted.
582                        Err(_) => self.latch_bypass(BypassReason::UnsupportedSubgroupStreamType),
583                    },
584                    // Whether this stream's Objects can be addressed is
585                    // asked of `fetch_group_order_is_needed` and of the
586                    // session's own record of what each FETCH asked for,
587                    // rather than inferred from the reader refusing. A codec
588                    // able to decode a layout is not on its own enough: on
589                    // drafts 18, 19 and 20 it decodes under a Group Order nothing
590                    // on this stream states, and the wrong one decodes as
591                    // willingly as the right one.
592                    DataStreamHeaderKind::Fetch(h) => match self.fetch_stage(h) {
593                        Ok(stage) => self.stage = stage,
594                        Err(reason) => self.latch_bypass(reason),
595                    },
596                }
597                let raw = self.buf.split_to(consumed).freeze();
598                FramerOut::Header { header, raw }
599            }
600            Err(e) if is_incomplete_error(&e) => {
601                if self.buf.len() >= self.config.max_buffered_object_bytes {
602                    self.latch_bypass(BypassReason::ObjectBeyondMeasuringReach);
603                    self.emit_passthrough(u64::MAX)
604                } else {
605                    FramerOut::NeedMore
606                }
607            }
608            Err(e) => {
609                self.latch_bypass(BypassReason::DecodeError);
610                FramerOut::Error(format!("data stream header decode: {e}"))
611            }
612        }
613    }
614
615    /// The reader and writer a fetch stream is parsed with, or why it is not.
616    ///
617    /// Three drafts need an answer from off the stream and the other eleven
618    /// do not, which is [`fetch_group_order_is_needed`]. Where one is needed
619    /// it comes from the FETCH the session carried, filed under the Request ID
620    /// this header names; a stream naming a request that was never asked for
621    /// is bypassed rather than guessed at, because the guess would decode.
622    ///
623    /// The reader and writer are built with the same order. They are the two
624    /// halves of one stream — the writer re-encodes a survivor after an elide
625    /// against the frames now in front of it — so a pair built against
626    /// different orders would renumber the stream it was meant to preserve.
627    fn fetch_stage(&mut self, h: &AnyFetchHeader) -> Result<Stage, BypassReason> {
628        let order = if fetch_group_order_is_needed(self.draft) {
629            let orders = self.fetch_orders.as_ref().ok_or(BypassReason::FetchGroupOrderUnknown)?;
630            Some(orders.take(h.request_id()).ok_or(BypassReason::FetchGroupOrderUnknown)?)
631        } else {
632            None
633        };
634        // Ascending where the draft needs no answer: the branch above supplies
635        // one for every draft that does, so the fallback is only ever reached
636        // where nothing on the stream is signed and the value cannot be wrong.
637        let order = order.unwrap_or(AnyFetchGroupOrder::Ascending);
638        let built = (AnyFetchObjectReader::new(h, order), AnyFetchObjectWriter::new(h, order));
639        match built {
640            (Ok(reader), Ok(writer)) => Ok(Stage::Fetch { reader, writer }),
641            // A draft this build did not compile. Not an error — the stream
642            // is simply not addressable. The header decode above would
643            // already have failed on such a draft, so this is a second line
644            // rather than the first.
645            _ => Err(BypassReason::NoFetchObjectCodec),
646        }
647    }
648
649    fn poll_object(&mut self) -> FramerOut {
650        self.counters.note_framer_object_poll();
651
652        // Lazy commit, once, here. The pipe loop is sequential — the
653        // caller's verdict on object *N* always lands before `poll`
654        // produces object *N+1* — so an object still pending at the top of
655        // this call was forwarded. `poll_oversized` deliberately does
656        // **not** repeat this: it is only ever reached from inside this
657        // function, and a second commit would consume a disposition this
658        // one already took. If it ever gains an entry path of its own,
659        // make the commit idempotent rather than adding a second site.
660        self.commit_disposition();
661
662        if self.buf.is_empty() {
663            return FramerOut::NeedMore;
664        }
665
666        let snapshot: &[u8] = &self.buf[..];
667        let mut cursor: &[u8] = snapshot;
668
669        // Probe against a clone: a reader mutated before the object is
670        // known to be complete carries a corrupt delta state into every
671        // later object on the stream.
672        let probed = match &self.stage {
673            Stage::Subgroup(reader) => {
674                let mut probe = reader.clone();
675                probe
676                    .read_object_meta(&mut cursor)
677                    .map(|m| (Probe::Subgroup(probe), Meta::Sub(m), None))
678            }
679            // `read_object_frame` rather than `read_object_meta`: it reports
680            // the same framing and additionally keeps the shape the frame
681            // arrived in, which is what re-encoding it against a different
682            // predecessor takes.
683            Stage::Fetch { reader, .. } => {
684                let mut probe = reader.clone();
685                probe
686                    .read_object_frame(&mut cursor)
687                    .map(|frame| (Probe::Fetch(probe), Meta::Fetch(frame.meta), Some(frame)))
688            }
689            Stage::AwaitingHeader => return FramerOut::NeedMore,
690        };
691
692        match probed {
693            Ok((probe, meta, frame)) => {
694                let consumed = snapshot.len() - cursor.remaining();
695                let object_id = meta.object_id();
696                // Before the source bytes are split off, so a failure
697                // leaves them in the buffer for the bypassed poll that
698                // follows to forward verbatim rather than dropping them.
699                let rewritten = match self.apply_elide_fixup(consumed, object_id, frame.as_ref()) {
700                    Ok(rewritten) => rewritten,
701                    Err(e) => {
702                        self.latch_bypass(BypassReason::DecodeError);
703                        return FramerOut::Error(e);
704                    }
705                };
706                let source = self.buf.split_to(consumed).freeze();
707                let raw = rewritten.unwrap_or(source);
708                self.commit(probe);
709                // An object that arrives in one read completes before
710                // buffering ever reaches the cap, so the incomplete-decode
711                // path below never sees it. Judging it by its own wire
712                // length too is what makes the cap a property of the object
713                // rather than of the caller's read size. Judged on the
714                // *source* length, so an elide fix-up that widens the ID
715                // varint cannot move an object across the boundary.
716                let out = if consumed > self.config.max_buffered_object_bytes {
717                    // A `Passthrough` that is a whole object: the meta was
718                    // decoded and is about to be discarded, so nothing
719                    // outside the framer can address it.
720                    self.counters.note_object_not_addressable();
721                    FramerOut::Passthrough(raw)
722                } else {
723                    FramerOut::Object { meta: self.object_meta(meta), raw }
724                };
725                // Both branches emitted an object, so both arm the cursor:
726                // the oversized one has no `ObjectMeta` for the caller to
727                // name, which is exactly why the framer keeps the record.
728                self.pending_disposition =
729                    Some(PendingObject { object_id, index_in_stream: self.index_in_stream });
730                self.index_in_stream += 1;
731                out
732            }
733            Err(e) if is_incomplete_error(&e) => {
734                if self.buf.len() < self.config.max_buffered_object_bytes {
735                    FramerOut::NeedMore
736                } else {
737                    self.poll_oversized()
738                }
739            }
740            Err(e) => {
741                self.latch_bypass(BypassReason::DecodeError);
742                FramerOut::Error(format!("object decode: {e}"))
743            }
744        }
745    }
746
747    /// The buffer hit its cap without completing an object. Decide whether
748    /// the object's *framing* is understood — in which case its bytes can
749    /// be streamed through and framing resumes afterwards — or whether the
750    /// stream has to be abandoned.
751    ///
752    /// The framing is read against the buffered bytes followed by padding,
753    /// so the decoder can advance past a payload that has not arrived. How
754    /// wide that padding may be is [`Self::measuring_pad`]'s call.
755    fn poll_oversized(&mut self) -> FramerOut {
756        let pad = self.measuring_pad();
757        let mut padded = PaddedBuf::new(&self.buf[..], pad);
758        let probed = match &self.stage {
759            Stage::Subgroup(reader) => {
760                let mut probe = reader.clone();
761                probe.read_object_meta(&mut padded).map(|m| {
762                    (Probe::Subgroup(probe), m.object_id, m.wire_len, m.payload_length, None)
763                })
764            }
765            Stage::Fetch { reader, .. } => {
766                let mut probe = reader.clone();
767                probe.read_object_frame(&mut padded).map(|frame| {
768                    let meta = frame.meta;
769                    (
770                        Probe::Fetch(probe),
771                        meta.object_id,
772                        meta.wire_len,
773                        meta.payload_length,
774                        Some(frame),
775                    )
776                })
777            }
778            Stage::AwaitingHeader => return FramerOut::NeedMore,
779        };
780
781        let Ok((probe, object_id, wire_len, payload_length, frame)) = probed else {
782            self.latch_bypass(BypassReason::ObjectBeyondMeasuringReach);
783            return self.emit_passthrough(u64::MAX);
784        };
785
786        // Only trust the framing when every field ahead of the payload
787        // came from real bytes rather than padding.
788        let framing_len = wire_len - payload_length;
789        let buffered = self.buf.len() as u64;
790        if framing_len > buffered || wire_len <= buffered {
791            self.latch_bypass(BypassReason::ObjectBeyondMeasuringReach);
792            return self.emit_passthrough(u64::MAX);
793        }
794
795        // `framing_len <= buffered` is what makes the fix-up reachable
796        // here: the whole framing, leading Object ID varint included, is
797        // in the chunk about to be emitted. The chunk is a *prefix* of the
798        // object, which `reemit_subgroup_object` accepts — it decodes the
799        // ID field and copies the rest verbatim without validating any
800        // length.
801        let chunk = self.buf.len();
802        let rewritten = match self.apply_elide_fixup(chunk, object_id, frame.as_ref()) {
803            Ok(rewritten) => rewritten,
804            Err(e) => {
805                self.latch_bypass(BypassReason::DecodeError);
806                return FramerOut::Error(e);
807            }
808        };
809
810        self.commit(probe);
811        // The second `Passthrough`-as-an-object path. Counted once here,
812        // not once per emitted chunk: this function is entered once per
813        // oversized object, and the chunks that follow come from `poll`'s
814        // `passthrough_remaining` branch.
815        self.counters.note_object_not_addressable();
816        // Denominated in **source** bytes, and deliberately not adjusted
817        // by the fix-up's `id_bytes_after - id_bytes_before`. Its job is
818        // to say how many more bytes of the *incoming* stream belong to
819        // this object; the emitted stream is a byte or two shorter or
820        // longer for exactly one object, which is what an elide fix-up is.
821        // Correcting it here would make the framer stop consuming this
822        // object early or late and resynchronise mid-way through the next
823        // one — a silent corruption that only fires when the new delta
824        // needs a wider varint.
825        self.passthrough_remaining = wire_len - buffered;
826        self.pending_disposition =
827            Some(PendingObject { object_id, index_in_stream: self.index_in_stream });
828        self.index_in_stream += 1;
829        let source = self.buf.split().freeze();
830        FramerOut::Passthrough(rewritten.unwrap_or(source))
831    }
832
833    /// Commit the disposition of the object emitted most recently.
834    ///
835    /// An object still pending was forwarded; one the caller elided was
836    /// taken out of `pending_disposition` by [`Self::note_elided`], so the
837    /// cursor never advances to it.
838    fn commit_disposition(&mut self) {
839        if let Some(pending) = self.pending_disposition.take() {
840            self.last_forwarded_id = Some(pending.object_id);
841            // Forwarded, so the writer's advance stands and there is nothing
842            // to put back. Cleared rather than left for the next re-emit to
843            // overwrite, so that a rollback can only ever undo the frame it
844            // was taken for.
845            self.fetch_rollback = None;
846        }
847    }
848
849    /// `true` when this stream's Object IDs are written as `id - prev - 1`
850    /// rather than absolutely, so eliding one renumbers every later object.
851    ///
852    /// Subgroup streams on drafts 14-21, and no fetch stream on any draft:
853    /// this is the predicate for the *varint rewrite*, and a fetch frame is
854    /// paid for by [`Self::reemit_fetch_frame`] instead. Drafts 07-13 write
855    /// subgroup Object IDs absolutely and need neither.
856    ///
857    /// The draft half is an exhaustive match rather than a `matches!`: a draft
858    /// left off the list answers "written absolutely", the framer then forwards
859    /// the object after an elide without rewriting its leading varint, and
860    /// every later Object ID on the stream is off by one with nothing to say
861    /// so. The stream-kind half stays a `matches!` — `DataStreamType` is not a
862    /// draft list and its two variants are both named here.
863    fn delta_encodes_object_ids(&self) -> bool {
864        matches!(self.stream_type, DataStreamType::Subgroup)
865            && match self.draft {
866                DraftVersion::Draft07
867                | DraftVersion::Draft08
868                | DraftVersion::Draft09
869                | DraftVersion::Draft10
870                | DraftVersion::Draft11
871                | DraftVersion::Draft12
872                | DraftVersion::Draft13 => false,
873                DraftVersion::Draft14
874                | DraftVersion::Draft15
875                | DraftVersion::Draft16
876                | DraftVersion::Draft17
877                | DraftVersion::Draft18
878                | DraftVersion::Draft19
879                | DraftVersion::Draft20
880                | DraftVersion::Draft21 => true,
881            }
882    }
883
884    /// `true` when eliding an object from this stream leaves the next one
885    /// encoded against something that is no longer on the wire, so a fix-up
886    /// is owed before another object may be forwarded.
887    ///
888    /// The two stream kinds owe it for different reasons and pay it in
889    /// different ways, and the debt itself is the same: until one more object
890    /// has been emitted, the bytes the destination would receive decode to
891    /// Locations nobody sent. `fixup_owed` on a `Bypassed` is what a session
892    /// resets its destination over, and it reads this.
893    ///
894    /// Fetch streams on drafts 15-21, where a Serialization Flags field lets
895    /// a frame take any of its Group ID, Subgroup ID, Object ID and Priority
896    /// from the frame before it — draft-17 Section 10.4.4.1, Table 7: "Object
897    /// ID is the prior Object's ID plus one". Not drafts 07-14, whose fetch
898    /// objects state all four outright.
899    ///
900    /// Exhaustive rather than a `matches!` for the same reason as
901    /// [`Self::delta_encodes_object_ids`], and the consequence is larger here:
902    /// `false` means no fix-up is owed, so the session never resets the
903    /// destination and the receiver keeps a stream whose frames decode to
904    /// Locations nobody sent.
905    fn elide_owes_a_fixup(&self) -> bool {
906        match self.stream_type {
907            DataStreamType::Subgroup => self.delta_encodes_object_ids(),
908            DataStreamType::Fetch => match self.draft {
909                DraftVersion::Draft07
910                | DraftVersion::Draft08
911                | DraftVersion::Draft09
912                | DraftVersion::Draft10
913                | DraftVersion::Draft11
914                | DraftVersion::Draft12
915                | DraftVersion::Draft13
916                | DraftVersion::Draft14 => false,
917                DraftVersion::Draft15
918                | DraftVersion::Draft16
919                | DraftVersion::Draft17
920                | DraftVersion::Draft18
921                | DraftVersion::Draft19
922                | DraftVersion::Draft20
923                | DraftVersion::Draft21 => true,
924            },
925        }
926    }
927
928    /// Rewrite the leading Object ID varint of `self.buf[..len]` when an
929    /// elide has left the wire's delta chain one object ahead of what was
930    /// actually forwarded.
931    ///
932    /// `len` may be a prefix of the object rather than the whole of it —
933    /// the oversized path calls this with the first chunk. Returns
934    /// `Ok(None)` when no fix-up was owed, in which case the caller emits
935    /// the source bytes untouched; the borrow of `self.buf` ends before
936    /// this returns, so the caller is free to split it afterwards.
937    ///
938    /// The error is unreachable in practice — Object IDs are strictly
939    /// increasing within a stream and the caller has already established
940    /// that the ID field is whole in the buffer — but it is reported
941    /// rather than swallowed, because the alternative is emitting bytes
942    /// known to decode to the wrong ID.
943    fn apply_elide_fixup(
944        &mut self,
945        len: usize,
946        object_id: u64,
947        frame: Option<&AnyFetchFrame>,
948    ) -> Result<Option<Bytes>, String> {
949        // `frame` is `Some` exactly on a fetch stream, where the payment is a
950        // re-encode of the whole framing rather than a rewrite of one varint,
951        // and where it is the writer rather than a flag that decides whether
952        // anything is owed.
953        if let Some(frame) = frame {
954            return self.reemit_fetch_frame(len, frame);
955        }
956        if !self.fixup_pending || !self.delta_encodes_object_ids() {
957            return Ok(None);
958        }
959        let mut out = BytesMut::with_capacity(len + 8);
960        let outcome = reemit_subgroup_object(
961            self.draft,
962            self.last_forwarded_id,
963            object_id,
964            &self.buf[..len],
965            &mut out,
966        );
967        if let Err(e) = outcome {
968            // Leaves `fixup_pending` set, so the cursor keeps reporting
969            // that this stream still owes a fix-up.
970            return Err(format!("elide fix-up: {e}"));
971        }
972        self.fixup_pending = false;
973        self.counters.note_object_id_rewritten();
974        Ok(Some(out.freeze()))
975    }
976
977    /// Re-emit one fetch frame through this stream's writer.
978    ///
979    /// Called for **every** fetch frame the framer emits, not only after an
980    /// elide. The writer is what a survivor is re-encoded against, and it
981    /// only moves when it is shown a frame, so skipping the frames nothing
982    /// was owed for would leave it as many frames behind as were skipped.
983    ///
984    /// Answers `Ok(None)` when the frame's own bytes still say what the frame
985    /// says, which is every frame on a stream nothing has been removed from.
986    /// The writer is cloned first so that [`Self::note_elided`] can put it
987    /// back: this runs before the caller's verdict on the frame, and a frame
988    /// the caller drops must leave no trace on the writing side.
989    ///
990    /// `len` may be a prefix of the frame — the oversized path calls this
991    /// with the first chunk — and the codec asks only that the whole framing
992    /// be inside it, which is what the caller has already established.
993    fn reemit_fetch_frame(
994        &mut self,
995        len: usize,
996        frame: &AnyFetchFrame,
997    ) -> Result<Option<Bytes>, String> {
998        let mut out = BytesMut::with_capacity(len + 16);
999        let (outcome, rollback) = {
1000            let Stage::Fetch { writer, .. } = &mut self.stage else {
1001                return Ok(None);
1002            };
1003            let rollback = writer.clone();
1004            (writer.reemit_object(frame, &self.buf[..len], &mut out), rollback)
1005        };
1006        self.fetch_rollback = Some(rollback);
1007
1008        match outcome {
1009            Ok(FetchReemit::Unchanged) => {
1010                // The debt such as it was is settled either way. A removal
1011                // whose survivor happened to state every field it needed
1012                // costs nothing, and leaving the flag set would have the
1013                // stream report an unpaid fix-up at every later bypass.
1014                self.fixup_pending = false;
1015                Ok(None)
1016            }
1017            Ok(FetchReemit::Reframed { .. }) => {
1018                self.fixup_pending = false;
1019                self.counters.note_object_id_rewritten();
1020                Ok(Some(out.freeze()))
1021            }
1022            // Leaves `fixup_pending` set, as the subgroup path does, so the
1023            // stream keeps reporting that it still owes one.
1024            Err(e) => Err(format!("elide fix-up: {e}")),
1025        }
1026    }
1027
1028    /// How much padding [`Self::poll_oversized`] may put behind the
1029    /// buffered bytes when measuring an object that has not arrived whole.
1030    ///
1031    /// The pad costs no memory by itself — it is never materialised — but
1032    /// every copying read in the codec checks `Buf::remaining()` before it
1033    /// allocates, so the pad is exactly the bound on the allocation a
1034    /// hostile length field can provoke. That makes the choice per layout
1035    /// rather than global:
1036    ///
1037    /// * Subgroup objects on drafts 14-21, and fetch objects on draft-14,
1038    ///   are measured without a single copy — their `read_object_meta`
1039    ///   advances past the extension block and the payload rather than
1040    ///   reading them. Nothing can be talked into allocating, so the pad is
1041    ///   effectively unbounded and an object of *any* declared size stays
1042    ///   measurable: it streams through and framing resumes after it.
1043    /// * Every other object layout decodes its extension block by copying
1044    ///   it out of the buffer. Those keep a pad of one cap, which bounds
1045    ///   the copy at the price of reach: an object whose wire length
1046    ///   exceeds `buffered + cap` cannot be measured and the stream falls
1047    ///   back to passthrough for its remainder.
1048    ///
1049    /// Widening the second case needs the codec to reject an extension
1050    /// length larger than the bytes actually present, which is not this
1051    /// crate's to change.
1052    ///
1053    /// **"Every other" includes the newest fetch layouts, and that is not an
1054    /// omission.** Drafts 15 through 20 all read a fetch frame through
1055    /// `FetchObjectReader::read_object_header`, which materialises the frame's
1056    /// properties — `data_dispatch.rs` `fo15`..`fo20` each carry the block out
1057    /// of the buffer before `read_object_frame` skips the payload — so a
1058    /// declared extension length is still an allocation those drafts can be
1059    /// talked into. Only draft-14's `FetchObject::decode_meta` advances past
1060    /// the block instead. Both arms are exhaustive matches rather than
1061    /// `matches!` so that this stays a decision: a new draft answering `false`
1062    /// by omission would be *safe* here, which is precisely why nothing would
1063    /// ever notice that no one had looked at its fetch layout.
1064    fn measuring_pad(&self) -> usize {
1065        let copy_free = match self.stage {
1066            Stage::Subgroup(_) => match self.draft {
1067                DraftVersion::Draft07
1068                | DraftVersion::Draft08
1069                | DraftVersion::Draft09
1070                | DraftVersion::Draft10
1071                | DraftVersion::Draft11
1072                | DraftVersion::Draft12
1073                | DraftVersion::Draft13 => false,
1074                DraftVersion::Draft14
1075                | DraftVersion::Draft15
1076                | DraftVersion::Draft16
1077                | DraftVersion::Draft17
1078                | DraftVersion::Draft18
1079                | DraftVersion::Draft19
1080                | DraftVersion::Draft20
1081                | DraftVersion::Draft21 => true,
1082            },
1083            Stage::Fetch { .. } => match self.draft {
1084                DraftVersion::Draft14 => true,
1085                DraftVersion::Draft07
1086                | DraftVersion::Draft08
1087                | DraftVersion::Draft09
1088                | DraftVersion::Draft10
1089                | DraftVersion::Draft11
1090                | DraftVersion::Draft12
1091                | DraftVersion::Draft13
1092                | DraftVersion::Draft15
1093                | DraftVersion::Draft16
1094                | DraftVersion::Draft17
1095                | DraftVersion::Draft18
1096                | DraftVersion::Draft19
1097                | DraftVersion::Draft20
1098                | DraftVersion::Draft21 => false,
1099            },
1100            Stage::AwaitingHeader => false,
1101        };
1102        if copy_free {
1103            UNBOUNDED_MEASURING_PAD
1104        } else {
1105            self.config.max_buffered_object_bytes
1106        }
1107    }
1108
1109    /// Adopt a probe reader whose decode succeeded.
1110    fn commit(&mut self, probe: Probe) {
1111        match (&mut self.stage, probe) {
1112            (Stage::Subgroup(reader), Probe::Subgroup(probe)) => *reader = probe,
1113            (Stage::Fetch { reader, .. }, Probe::Fetch(probe)) => *reader = probe,
1114            // `probe` was cloned from `stage` a few lines earlier, so the
1115            // pairing always matches.
1116            _ => {}
1117        }
1118    }
1119
1120    fn object_meta(&self, meta: Meta) -> ObjectMeta {
1121        match meta {
1122            Meta::Sub(m) => ObjectMeta {
1123                draft: self.draft,
1124                stream_kind: DataStreamType::Subgroup,
1125                track_alias: Some(self.context.track_alias),
1126                group_id: self.context.group_id,
1127                subgroup_id: self.context.subgroup_id,
1128                object_id: m.object_id,
1129                publisher_priority: self.context.publisher_priority,
1130                index_in_stream: self.index_in_stream,
1131                payload_len: m.payload_length,
1132                status: m.status,
1133                end_of_range: None,
1134            },
1135            Meta::Fetch(m) => ObjectMeta {
1136                draft: self.draft,
1137                stream_kind: DataStreamType::Fetch,
1138                track_alias: None,
1139                group_id: m.group_id,
1140                // Not `Some(m.subgroup_id)`: from draft-15 a fetch frame
1141                // may carry no Subgroup ID at all — an object forwarded
1142                // over a datagram has no subgroup, and an End of Range
1143                // indicator names a Location rather than an object — and
1144                // the codec leaves a placeholder in the field when it says
1145                // so. Forwarding that placeholder would key a matcher on a
1146                // subgroup the publisher never named.
1147                subgroup_id: m.has_subgroup_id.then_some(m.subgroup_id),
1148                object_id: m.object_id,
1149                publisher_priority: Some(m.publisher_priority),
1150                index_in_stream: self.index_in_stream,
1151                payload_len: m.payload_length,
1152                status: m.status,
1153                end_of_range: m.end_of_range,
1154            },
1155        }
1156    }
1157}
1158
1159/// A reader clone whose decode succeeded and is ready to be adopted.
1160enum Probe {
1161    Subgroup(AnySubgroupObjectReader),
1162    Fetch(AnyFetchObjectReader),
1163}
1164
1165/// The codec-level framing of one object, either stream kind.
1166#[derive(Clone, Copy)]
1167enum Meta {
1168    Sub(AnySubgroupObjectMeta),
1169    Fetch(AnyFetchObjectMeta),
1170}
1171
1172impl Meta {
1173    /// The object's absolute Object ID, delta encoding already resolved.
1174    fn object_id(self) -> u64 {
1175        match self {
1176            Meta::Sub(m) => m.object_id,
1177            Meta::Fetch(m) => m.object_id,
1178        }
1179    }
1180}
1181
1182/// The identity fields a subgroup header contributes to its objects.
1183///
1184/// Every field comes from an [`AnySubgroupHeader`] accessor, so the
1185/// per-draft knowledge behind them — which stream types leave the subgroup
1186/// ID to the first object, which drafts fold a mode field into the
1187/// header-type octet, which drafts may omit the publisher priority — stays
1188/// in the codec beside the decoders that define it. This crate keeps no
1189/// copy of it, which is the point: the copy it used to keep (a mask
1190/// constant and a thirteen-arm match) had already drifted from draft-16's
1191/// own decoder.
1192///
1193/// Where a draft encodes the subgroup ID implicitly as the first object's
1194/// ID, the codec stores zero and [`AnySubgroupHeader::subgroup_id`]
1195/// reports `None` rather than that zero.
1196fn subgroup_context(header: &AnySubgroupHeader) -> SubgroupContext {
1197    SubgroupContext {
1198        track_alias: header.track_alias(),
1199        group_id: header.group_id(),
1200        subgroup_id: header.subgroup_id(),
1201        publisher_priority: header.publisher_priority(),
1202    }
1203}
1204
1205/// A slice followed by a run of zero bytes.
1206///
1207/// Lets a decoder walk an object's framing and advance past a payload that
1208/// has not arrived yet, so the framer can learn an object's total wire
1209/// length without buffering it. Only fields decoded from the real prefix
1210/// are trustworthy; the caller checks that before using the result, and
1211/// picks the pad width from what the decode path may copy — see
1212/// `ObjectFramer::measuring_pad`.
1213struct PaddedBuf<'a> {
1214    real: &'a [u8],
1215    pad: usize,
1216}
1217
1218/// Backing bytes for [`PaddedBuf`]'s padded region.
1219const ZEROS: [u8; 1024] = [0u8; 1024];
1220
1221impl<'a> PaddedBuf<'a> {
1222    fn new(real: &'a [u8], pad: usize) -> Self {
1223        Self { real, pad }
1224    }
1225}
1226
1227impl Buf for PaddedBuf<'_> {
1228    fn remaining(&self) -> usize {
1229        self.real.len() + self.pad
1230    }
1231
1232    fn chunk(&self) -> &[u8] {
1233        if self.real.is_empty() {
1234            &ZEROS[..self.pad.min(ZEROS.len())]
1235        } else {
1236            self.real
1237        }
1238    }
1239
1240    fn advance(&mut self, cnt: usize) {
1241        let from_real = cnt.min(self.real.len());
1242        self.real = &self.real[from_real..];
1243        self.pad = self.pad.saturating_sub(cnt - from_real);
1244    }
1245}
1246
1247// Every test below builds its input with a codec writer and reads it back
1248// with a codec reader, so every one of them needs at least one draft
1249// compiled in. With none, `AnySubgroupHeader` and friends are uninhabited
1250// enums and the helpers stop type-checking — `-D warnings` reports the
1251// `decode_stream` call as an unreachable definition. Gating the module is
1252// the honest shape: with no draft there is no framing to test, so the
1253// module vanishes rather than being kept alive by `#[allow]`.
1254#[cfg(test)]
1255#[cfg(any(
1256    feature = "draft07",
1257    feature = "draft08",
1258    feature = "draft09",
1259    feature = "draft10",
1260    feature = "draft11",
1261    feature = "draft12",
1262    feature = "draft13",
1263    feature = "draft14",
1264    feature = "draft15",
1265    feature = "draft16",
1266    feature = "draft17",
1267    feature = "draft18",
1268    feature = "draft19",
1269    feature = "draft20",
1270    feature = "draft21"
1271))]
1272mod tests {
1273    //! The elide cursor, on the wire.
1274    //! Everything here builds its input with the codec's *writer* and checks
1275    //! the framer's output by *decoding* it again — so the claim under test
1276    //! (*after eliding object N the stream still decodes to the IDs that
1277    //! survived*) is settled by the codec's readers rather than by the rewrite
1278    //! logic being tested. Byte identity in the absence of an elide stays the
1279    //! property `tests/framer_tests.rs` and
1280    //! `tests/object_framing_acceptance.rs` own; the fence here only pins that
1281    //! the new code does not fire when nothing asked it to.
1282
1283    use super::*;
1284
1285    use moqtap_codec::dispatch::{AnySubgroupObject, AnySubgroupObjectWriter};
1286
1287    /// The drafts this build actually compiled.
1288    ///
1289    /// Each element carries its own `#[cfg]`, so the sweep is the enabled
1290    /// set rather than a hardcoded list: a `--features draft14` build
1291    /// sweeps one draft and does not try to decode the headers whose
1292    /// decoders were not compiled.
1293    const DRAFTS: &[DraftVersion] = &[
1294        #[cfg(feature = "draft07")]
1295        DraftVersion::Draft07,
1296        #[cfg(feature = "draft08")]
1297        DraftVersion::Draft08,
1298        #[cfg(feature = "draft09")]
1299        DraftVersion::Draft09,
1300        #[cfg(feature = "draft10")]
1301        DraftVersion::Draft10,
1302        #[cfg(feature = "draft11")]
1303        DraftVersion::Draft11,
1304        #[cfg(feature = "draft12")]
1305        DraftVersion::Draft12,
1306        #[cfg(feature = "draft13")]
1307        DraftVersion::Draft13,
1308        #[cfg(feature = "draft14")]
1309        DraftVersion::Draft14,
1310        #[cfg(feature = "draft15")]
1311        DraftVersion::Draft15,
1312        #[cfg(feature = "draft16")]
1313        DraftVersion::Draft16,
1314        #[cfg(feature = "draft17")]
1315        DraftVersion::Draft17,
1316        #[cfg(feature = "draft18")]
1317        DraftVersion::Draft18,
1318        #[cfg(feature = "draft19")]
1319        DraftVersion::Draft19,
1320        #[cfg(feature = "draft20")]
1321        DraftVersion::Draft20,
1322        #[cfg(feature = "draft21")]
1323        DraftVersion::Draft21,
1324    ];
1325
1326    /// The drafts that write an Object ID as `id - prev - 1`, and so are
1327    /// the only ones where eliding an object corrupts its successors —
1328    /// intersected with the ones this build compiled.
1329    const DELTA_DRAFTS: &[DraftVersion] = &[
1330        #[cfg(feature = "draft14")]
1331        DraftVersion::Draft14,
1332        #[cfg(feature = "draft15")]
1333        DraftVersion::Draft15,
1334        #[cfg(feature = "draft16")]
1335        DraftVersion::Draft16,
1336        #[cfg(feature = "draft17")]
1337        DraftVersion::Draft17,
1338        #[cfg(feature = "draft18")]
1339        DraftVersion::Draft18,
1340        #[cfg(feature = "draft19")]
1341        DraftVersion::Draft19,
1342        #[cfg(feature = "draft20")]
1343        DraftVersion::Draft20,
1344        #[cfg(feature = "draft21")]
1345        DraftVersion::Draft21,
1346    ];
1347
1348    /// The stream-type field opening a subgroup stream that carries an
1349    /// explicit subgroup ID and no extensions, per draft.
1350    fn subgroup_stream_type(draft: DraftVersion) -> u8 {
1351        match draft {
1352            DraftVersion::Draft07
1353            | DraftVersion::Draft08
1354            | DraftVersion::Draft09
1355            | DraftVersion::Draft10 => 0x04,
1356            DraftVersion::Draft11 => 0x0C,
1357            _ => 0x14,
1358        }
1359    }
1360
1361    /// Wire bytes of a subgroup header: track alias 1, group 0, subgroup 0,
1362    /// publisher priority 128, no extensions.
1363    fn header_bytes(draft: DraftVersion) -> Vec<u8> {
1364        vec![subgroup_stream_type(draft), 0x01, 0x00, 0x00, 0x80]
1365    }
1366
1367    fn object(object_id: u64, payload_len: usize) -> AnySubgroupObject {
1368        AnySubgroupObject {
1369            object_id,
1370            extension_headers: Vec::new(),
1371            extension_count: None,
1372            status: None,
1373            payload: vec![0xAB; payload_len],
1374        }
1375    }
1376
1377    /// Encode a whole subgroup stream: header plus `objects`, on `draft`.
1378    fn build_stream(draft: DraftVersion, objects: &[AnySubgroupObject]) -> Vec<u8> {
1379        let head = header_bytes(draft);
1380        let mut cursor: &[u8] = &head;
1381        let header = AnySubgroupHeader::decode_stream(draft, &mut cursor)
1382            .unwrap_or_else(|e| panic!("[{draft}] header decode: {e}"));
1383        let mut writer = AnySubgroupObjectWriter::new(&header)
1384            .unwrap_or_else(|e| panic!("[{draft}] writer: {e}"));
1385
1386        let mut out = head;
1387        for obj in objects {
1388            writer
1389                .write_object(obj, &mut out)
1390                .unwrap_or_else(|e| panic!("[{draft}] write object {}: {e}", obj.object_id));
1391        }
1392        out
1393    }
1394
1395    /// A subgroup framer on `draft`, reporting into `counters`.
1396    ///
1397    /// Every framer in this module goes through
1398    /// [`ObjectFramer::with_recorder`] rather than [`ObjectFramer::new`]:
1399    /// it keeps `src/` free of the string standing gate 7 greps for
1400    /// without the gate having to filter comments, and it gives the
1401    /// counter assertions below something to read.
1402    fn framer_for(
1403        draft: DraftVersion,
1404        config: FramerConfig,
1405        counters: &Arc<Recorder>,
1406    ) -> ObjectFramer {
1407        ObjectFramer::with_recorder(DataStreamType::Subgroup, draft, config, Arc::clone(counters))
1408    }
1409
1410    /// What one run of the framer produced.
1411    struct Run {
1412        /// Every byte the framer emitted, in order.
1413        bytes: Vec<u8>,
1414        /// The Object IDs the framer itself resolved while producing them.
1415        ///
1416        /// Objects that left as `Passthrough` are absent — they were never
1417        /// addressable — so this is also the record of *where framing
1418        /// resumed* after one of them.
1419        framed_ids: Vec<u64>,
1420        /// The run's own counters.
1421        counters: Arc<Recorder>,
1422    }
1423
1424    /// Feed `stream` to a framer in `chunk`-sized pieces, dropping every
1425    /// object whose ID is in `elide`, and return what the framer produced.
1426    ///
1427    /// A bypass is a panic rather than a recorded outcome: every test that
1428    /// uses this helper is about an elide surviving, and a stream that
1429    /// quietly stopped being parsed would satisfy the byte assertions
1430    /// while proving nothing.
1431    fn run_eliding(
1432        draft: DraftVersion,
1433        stream: &[u8],
1434        chunk: usize,
1435        config: FramerConfig,
1436        elide: &[u64],
1437    ) -> Run {
1438        let counters = Arc::new(Recorder::new());
1439        let mut framer = framer_for(draft, config, &counters);
1440        let mut run = Run { bytes: Vec::new(), framed_ids: Vec::new(), counters };
1441        for piece in stream.chunks(chunk.max(1)) {
1442            framer.feed(piece);
1443            loop {
1444                match framer.poll() {
1445                    FramerOut::NeedMore => break,
1446                    FramerOut::Header { raw, .. } => run.bytes.extend_from_slice(&raw),
1447                    FramerOut::Object { meta, raw } => {
1448                        run.framed_ids.push(meta.object_id);
1449                        if elide.contains(&meta.object_id) {
1450                            framer.note_elided(&meta);
1451                        } else {
1452                            run.bytes.extend_from_slice(&raw);
1453                        }
1454                    }
1455                    FramerOut::Passthrough(raw) => run.bytes.extend_from_slice(&raw),
1456                    FramerOut::Bypassed { reason, fixup_owed } => {
1457                        panic!("[{draft}] unexpected bypass: {reason:?}, fixup_owed {fixup_owed}")
1458                    }
1459                    FramerOut::Error(e) => panic!("[{draft}] framer error: {e}"),
1460                }
1461            }
1462        }
1463        if let Some(tail) = framer.finish() {
1464            run.bytes.extend_from_slice(&tail);
1465        }
1466        run
1467    }
1468
1469    /// Decode `bytes` as a subgroup stream and report the Object IDs the
1470    /// codec's readers resolve from it.
1471    ///
1472    /// The framer used here has the default cap, so every object is framed
1473    /// individually no matter how the producing run chose to emit it.
1474    fn framed_object_ids(draft: DraftVersion, bytes: &[u8]) -> Vec<u64> {
1475        let counters = Arc::new(Recorder::new());
1476        let mut framer = framer_for(draft, FramerConfig::default(), &counters);
1477        framer.feed(bytes);
1478        let mut ids = Vec::new();
1479        loop {
1480            match framer.poll() {
1481                FramerOut::NeedMore => break,
1482                FramerOut::Header { .. } => {}
1483                FramerOut::Object { meta, .. } => ids.push(meta.object_id),
1484                FramerOut::Passthrough(raw) => {
1485                    panic!("[{draft}] re-decode fell back to passthrough after {} bytes", raw.len())
1486                }
1487                FramerOut::Bypassed { reason, .. } => {
1488                    panic!("[{draft}] re-decode bypassed: {reason:?}")
1489                }
1490                FramerOut::Error(e) => panic!("[{draft}] re-decode: {e}"),
1491            }
1492        }
1493        assert_eq!(framer.buffered(), 0, "[{draft}] re-decode left bytes buffered");
1494        ids
1495    }
1496
1497    /// Eliding an object leaves a stream that still decodes to exactly the
1498    /// objects that survived, on every draft.
1499    ///
1500    /// On drafts 14-21 that is only true because the framer rewrites the
1501    /// next object's leading Object ID varint; on 07-13 the IDs are
1502    /// absolute and the surviving bytes already say the truth.
1503    ///
1504    /// *Ablation:* returning `Ok(None)` unconditionally from
1505    /// `apply_elide_fixup` (never rewriting) fails this on the six delta
1506    /// drafts, `[0, 2, 3]` decoding as `[0, 1, 2]` — the whole tail of the
1507    /// stream shifted by one — and leaves 07-13 passing, which is exactly
1508    /// the blast radius the fix-up is scoped to.
1509    #[test]
1510    fn eliding_an_object_renumbers_the_rest_of_the_stream() {
1511        for &draft in DRAFTS {
1512            let objects: Vec<_> = (0..4).map(|id| object(id, 16)).collect();
1513            let stream = build_stream(draft, &objects);
1514
1515            let run = run_eliding(draft, &stream, stream.len(), FramerConfig::default(), &[1]);
1516
1517            assert_eq!(
1518                framed_object_ids(draft, &run.bytes),
1519                vec![0, 2, 3],
1520                "[{draft}] elided stream must decode to the surviving IDs"
1521            );
1522        }
1523    }
1524
1525    /// The elide survives an object the framer cannot address
1526    /// individually.
1527    ///
1528    /// Object 65 is over the buffer cap, so it leaves as `Passthrough` with
1529    /// no `ObjectMeta` for anyone outside the framer to name. A cursor
1530    /// owned by the caller could not renumber it, and its stale delta would
1531    /// shift every later object for the rest of the stream. The IDs are
1532    /// chosen so the correction changes the varint's *width*:
1533    /// `65 - 1 - 1 = 63` is one byte, `65 - 0 - 1 = 64` is two.
1534    ///
1535    /// *Ablation:* returning `Ok(None)` from `apply_elide_fixup` fails this
1536    /// on all six delta drafts, decoding `[0, 65, 66]` as `[0, 64, 65]`.
1537    #[test]
1538    fn an_oversized_object_after_an_elide_is_still_renumbered() {
1539        for &draft in DRAFTS {
1540            let objects = vec![object(0, 8), object(1, 8), object(65, 4096), object(66, 8)];
1541            let stream = build_stream(draft, &objects);
1542            let config = FramerConfig::new().with_max_buffered_object_bytes(64);
1543
1544            // Whole stream in one feed: the oversized object decodes
1545            // completely and `poll_object` routes it by its own wire length.
1546            let run = run_eliding(draft, &stream, stream.len(), config, &[1]);
1547            assert_eq!(
1548                run.framed_ids,
1549                vec![0, 1, 66],
1550                "[{draft}] object 65 left as passthrough and framing resumed on 66"
1551            );
1552            assert_eq!(
1553                framed_object_ids(draft, &run.bytes),
1554                vec![0, 65, 66],
1555                "[{draft}] oversized successor of an elide, decoded whole"
1556            );
1557        }
1558    }
1559
1560    /// The same, through `poll_oversized`: the object never arrives whole,
1561    /// so it is measured against padding and streamed out in chunks.
1562    ///
1563    /// This is the path where `passthrough_remaining` matters. It is
1564    /// computed from the *source* wire length while the emitted chunk is a
1565    /// byte longer, and the objects are picked so that gap is real.
1566    ///
1567    /// Restricted to the six delta drafts because they are the only ones
1568    /// whose object layout the codec can measure past an unarrived payload;
1569    /// on 07-13 a 4 KiB object with a 64-byte cap is out of measuring reach
1570    /// and the stream bypasses instead, which is a different property.
1571    ///
1572    /// *Ablation:* setting `passthrough_remaining = wire_len - emitted`
1573    /// instead of `wire_len - buffered` — the plausible-looking
1574    /// "correction" that redenominates the counter in emitted bytes when it
1575    /// has to stay in source bytes — fails this on all six drafts with
1576    /// `framed_ids == [0, 1]`: the
1577    /// framer leaves one payload byte of object 65 in the buffer, reads it
1578    /// as the head of the next object, and never frames object 66 at all.
1579    /// It fails **nothing else**, including this file's byte-level
1580    /// assertions, because the framer still emits every byte it was fed —
1581    /// just partitioned wrongly. That is why `Run` reports `framed_ids`:
1582    /// the first draft of this test asserted only on bytes and the
1583    /// ablation walked straight through it.
1584    ///
1585    /// Returning `Ok(None)` from `apply_elide_fixup` fails it earlier,
1586    /// with the re-decode reading `[0, 64, 65]`.
1587    #[test]
1588    fn an_elide_survives_an_object_streamed_through_in_chunks() {
1589        for &draft in DELTA_DRAFTS {
1590            let objects = vec![object(0, 8), object(1, 8), object(65, 4096), object(66, 8)];
1591            let stream = build_stream(draft, &objects);
1592            let config = FramerConfig::new().with_max_buffered_object_bytes(64);
1593
1594            let run = run_eliding(draft, &stream, 32, config, &[1]);
1595            // The accounting claim: `passthrough_remaining` is denominated
1596            // in source bytes, so the framer consumes object 65 exactly and
1597            // resynchronises on object 66's first byte — even though what
1598            // it emitted for object 65 is a byte longer than what it read.
1599            assert_eq!(
1600                run.framed_ids,
1601                vec![0, 1, 66],
1602                "[{draft}] framing must resume exactly on object 66"
1603            );
1604            assert_eq!(
1605                framed_object_ids(draft, &run.bytes),
1606                vec![0, 65, 66],
1607                "[{draft}] oversized successor of an elide, streamed through"
1608            );
1609        }
1610    }
1611
1612    /// A framer nobody elides on emits its input back, byte for byte.
1613    ///
1614    /// The regression fence for the fix-up code: it must be inert until
1615    /// [`ObjectFramer::note_elided`] is called. This is the property the
1616    /// acceptance suite rests on, restated where the code that could break
1617    /// it lives.
1618    ///
1619    /// *Ablation:* dropping the `self.fixup_pending` guard from
1620    /// `apply_elide_fixup` (rewriting every object unconditionally) still
1621    /// passes here, and passes `tests/framer_tests.rs` and
1622    /// `tests/object_framing_acceptance.rs` too — a minimally encoded delta
1623    /// re-encodes to itself, so the rewrite is invisible on any stream
1624    /// these build. `a_widened_object_id_field_is_not_re_encoded` below is
1625    /// the fence for that line; this test is a pin.
1626    #[test]
1627    fn a_stream_with_no_elide_is_emitted_byte_for_byte() {
1628        for &draft in DRAFTS {
1629            let objects: Vec<_> = (0..4).map(|id| object(id, 16)).collect();
1630            let stream = build_stream(draft, &objects);
1631
1632            for chunk in [1usize, 7, stream.len()] {
1633                let run = run_eliding(draft, &stream, chunk, FramerConfig::default(), &[]);
1634                assert_eq!(run.bytes, stream, "[{draft}] chunk {chunk}: byte identity");
1635            }
1636        }
1637    }
1638
1639    /// A non-minimally encoded Object ID field survives untouched when
1640    /// nothing was elided.
1641    ///
1642    /// QUIC varints may be written wider than they need to be, and a
1643    /// producer that does so is still emitting a legal stream. Re-encoding
1644    /// such a field would change bytes the proxy was asked to forward
1645    /// unchanged — so the fix-up must be reached only when an elide
1646    /// actually armed it, not merely when the draft delta-encodes.
1647    ///
1648    /// *Ablation:* dropping the `self.fixup_pending` guard from
1649    /// `apply_elide_fixup` fails this on all six delta drafts, and fails
1650    /// **nothing else** — not this file's other tests, not
1651    /// `tests/framer_tests.rs`, not `tests/object_framing_acceptance.rs` — because
1652    /// a minimally encoded field re-encodes to itself. This test is the
1653    /// only fence that line has.
1654    #[test]
1655    fn a_widened_object_id_field_is_not_re_encoded() {
1656        for &draft in DELTA_DRAFTS {
1657            let stream = build_stream(draft, &[object(0, 8), object(1, 8)]);
1658
1659            // Object 0 starts right after the five header bytes, and its
1660            // leading field is the one-byte varint 0. The two-byte form of the
1661            // same value differs by draft: `0x40 0x00` under RFC 9000, and
1662            // `0x80 0x00` from draft-17, where `0x40` is the one-byte 64.
1663            let head = header_bytes(draft).len();
1664            assert_eq!(stream[head], 0x00, "[{draft}] object 0's ID field is not where expected");
1665            let two_byte_zero: [u8; 2] =
1666                if draft.uses_moqt_varint() { [0x80, 0x00] } else { [0x40, 0x00] };
1667            let mut widened = stream[..head].to_vec();
1668            widened.extend_from_slice(&two_byte_zero);
1669            widened.extend_from_slice(&stream[head + 1..]);
1670
1671            let run = run_eliding(draft, &widened, widened.len(), FramerConfig::default(), &[]);
1672            assert_eq!(run.framed_ids, vec![0, 1], "[{draft}] widened field must still decode");
1673            assert_eq!(run.bytes, widened, "[{draft}] widened field must be forwarded verbatim");
1674        }
1675    }
1676
1677    /// No draft-16 subgroup header the decoder accepts sets both the
1678    /// explicit-subgroup-ID bit and the first-object bit, so the proxy
1679    /// never has to choose between them.
1680    ///
1681    /// Setting `0x04` (explicit subgroup ID) and `0x02` (subgroup ID is the
1682    /// first object's) together puts bits one and two at `0b11`, and
1683    /// draft-16 Section 10.4.2 reserves that Subgroup ID mode. The decoder
1684    /// refuses all eight bytes that spell it, so no such header decodes,
1685    /// and the sweep below covers the whole reserved mode rather than one
1686    /// byte of it.
1687    ///
1688    /// Why the proxy reads the field through the uniform accessor rather
1689    /// than off the header's bits: those two bits are one two-bit field,
1690    /// and a predicate reading `0x02` alone answers `true` for the reserved
1691    /// mode as well — at the same time as one reading `0x04` alone, for a
1692    /// state one two-bit field cannot be in.
1693    /// [`AnySubgroupHeader::subgroup_id`] reads the mode as a field
1694    /// instead: it answers `None` for the reserved mode — the header
1695    /// determines no Subgroup ID — and reads the explicit field only for
1696    /// mode 2, which is the only mode the decoder reads one for.
1697    ///
1698    /// *Ablation:* accepting the reserved mode again — dropping the
1699    /// Subgroup ID mode arm from the draft-16 `validate_subgroup_type` —
1700    /// lets all eight headers decode, and this fails on the first:
1701    ///
1702    /// ```text
1703    /// thread 'framer::tests::draft16_refuses_the_reserved_subgroup_id_mode'
1704    /// panicked at crates\moqtap-proxy\src\framer.rs:
1705    /// type 0x16 sets the reserved Subgroup ID mode and must be refused
1706    /// ```
1707    #[test]
1708    #[cfg(feature = "draft16")]
1709    fn draft16_refuses_the_reserved_subgroup_id_mode() {
1710        let draft = DraftVersion::Draft16;
1711        // Bits one and two are the Subgroup ID mode; 0b11 is reserved.
1712        // These are the eight subgroup types that set it, with and without
1713        // the extensions and priority bits.
1714        for ty in [0x16u8, 0x17, 0x1E, 0x1F, 0x36, 0x37, 0x3E, 0x3F] {
1715            // Track alias 1, group 0, subgroup 42, publisher priority 128 —
1716            // a complete body, so a refusal is of the type and never of a
1717            // short buffer.
1718            let head = vec![ty, 0x01, 0x00, 0x2A, 0x80];
1719            let mut cursor: &[u8] = &head;
1720            assert!(
1721                AnySubgroupHeader::decode_stream(draft, &mut cursor).is_err(),
1722                "type {ty:#04x} sets the reserved Subgroup ID mode and must be refused",
1723            );
1724        }
1725    }
1726
1727    /// The cursor advances lazily, one object behind the framer.
1728    ///
1729    /// An object's disposition is not known when it is emitted — the caller
1730    /// decides afterwards — so `last_forwarded_id` moves at the top of the
1731    /// *next* `poll_object`. The fix-up flag is the part that must be
1732    /// visible immediately, because it is what a bypass has to report as
1733    /// still owed.
1734    ///
1735    /// *Ablation:* committing eagerly (adding
1736    /// `self.last_forwarded_id = Some(object_id)` where
1737    /// `pending_disposition` is assigned) makes the elided object count as
1738    /// forwarded. This test fails at its first cursor assertion, on
1739    /// draft-07 — `Some(0)` where `None` is owed — and the three elide
1740    /// tests fail alongside it from draft-14 on, reading `[0, 1, 2]` and
1741    /// `[0, 64, 65]`.
1742    #[test]
1743    fn the_elide_cursor_tracks_the_last_object_actually_forwarded() {
1744        for &draft in DRAFTS {
1745            let delta = DELTA_DRAFTS.contains(&draft);
1746            let objects: Vec<_> = (0..3).map(|id| object(id, 8)).collect();
1747            let stream = build_stream(draft, &objects);
1748
1749            let counters = Arc::new(Recorder::new());
1750            let mut framer = framer_for(draft, FramerConfig::default(), &counters);
1751            framer.feed(&stream);
1752
1753            assert_eq!(
1754                framer.elide_cursor(),
1755                ElideCursor { last_forwarded_id: None, fixup_pending: false },
1756                "[{draft}] fresh framer"
1757            );
1758
1759            assert!(matches!(framer.poll(), FramerOut::Header { .. }));
1760
1761            // Object 0, forwarded.
1762            let FramerOut::Object { meta, .. } = framer.poll() else {
1763                panic!("[{draft}] expected object 0");
1764            };
1765            assert_eq!(meta.object_id, 0);
1766            assert_eq!(
1767                framer.elide_cursor().last_forwarded_id,
1768                None,
1769                "[{draft}] object 0's disposition is not known yet"
1770            );
1771
1772            // Object 1, elided. Polling for it commits object 0 first.
1773            let FramerOut::Object { meta, .. } = framer.poll() else {
1774                panic!("[{draft}] expected object 1");
1775            };
1776            assert_eq!(meta.object_id, 1);
1777            assert_eq!(
1778                framer.elide_cursor().last_forwarded_id,
1779                Some(0),
1780                "[{draft}] object 0 committed as forwarded"
1781            );
1782            framer.note_elided(&meta);
1783            assert_eq!(
1784                framer.elide_cursor(),
1785                ElideCursor { last_forwarded_id: Some(0), fixup_pending: delta },
1786                "[{draft}] the elide is visible at once, and only delta drafts owe a fix-up"
1787            );
1788
1789            // Object 2 clears the fix-up as it is emitted, and the cursor
1790            // still names object 0 until the object after it is asked for.
1791            let FramerOut::Object { meta, .. } = framer.poll() else {
1792                panic!("[{draft}] expected object 2");
1793            };
1794            assert_eq!(meta.object_id, 2);
1795            assert_eq!(
1796                framer.elide_cursor(),
1797                ElideCursor { last_forwarded_id: Some(0), fixup_pending: false },
1798                "[{draft}] the fix-up is spent on the object that carried it"
1799            );
1800
1801            assert!(matches!(framer.poll(), FramerOut::NeedMore));
1802            assert_eq!(
1803                framer.elide_cursor().last_forwarded_id,
1804                Some(2),
1805                "[{draft}] object 2 committed as forwarded"
1806            );
1807        }
1808    }
1809
1810    /// A bypass is reported once, as a zero-byte item, on the poll *after*
1811    /// the one that decided it.
1812    ///
1813    /// A draft-19 fetch stream is the cleanest case: the header decodes,
1814    /// so it is emitted, and only then is the stream found to be one whose
1815    /// objects are not addressed. `latch_bypass` cannot return the
1816    /// `Bypassed` item from that poll — the poll already owes a `Header` —
1817    /// so it is deferred.
1818    ///
1819    /// *Ablation:* dropping the `pending_bypass` drain from the top of
1820    /// `poll` fails this at the second poll with a `Passthrough`, and the
1821    /// stream's whole reason for not being addressable becomes
1822    /// unreportable — which is the state `BypassReason` was in before this
1823    /// unit: declared, and never constructed.
1824    #[test]
1825    #[cfg(feature = "draft19")]
1826    fn a_bypass_is_reported_once_on_the_poll_after_the_item_that_decided_it() {
1827        let draft = DraftVersion::Draft19;
1828        // Fetch stream type 0x05, request ID 42, then four bytes of
1829        // whatever the publisher was sending.
1830        let stream = vec![0x05u8, 0x2A, 0xDE, 0xAD, 0xBE, 0xEF];
1831
1832        let counters = Arc::new(Recorder::new());
1833        let mut framer = ObjectFramer::with_recorder(
1834            DataStreamType::Fetch,
1835            draft,
1836            FramerConfig::default(),
1837            Arc::clone(&counters),
1838        );
1839        framer.feed(&stream);
1840
1841        let FramerOut::Header { raw, .. } = framer.poll() else {
1842            panic!("expected the fetch header");
1843        };
1844        assert_eq!(&raw[..], &stream[..2], "the header is still emitted in full");
1845        assert!(framer.is_bypassed(), "the bypass was decided before the header was handed out");
1846
1847        assert!(
1848            matches!(
1849                framer.poll(),
1850                FramerOut::Bypassed {
1851                    reason: BypassReason::FetchGroupOrderUnknown,
1852                    fixup_owed: false
1853                }
1854            ),
1855            "the bypass follows the header rather than replacing it"
1856        );
1857
1858        let FramerOut::Passthrough(rest) = framer.poll() else {
1859            panic!("expected the remainder to be forwarded uninterpreted");
1860        };
1861        assert_eq!(&rest[..], &stream[2..], "no byte is lost to the bypass");
1862
1863        assert!(matches!(framer.poll(), FramerOut::NeedMore));
1864        assert!(matches!(framer.poll(), FramerOut::NeedMore), "reported exactly once");
1865
1866        let c = counters.snapshot();
1867        assert_eq!(c.framers_created, 1);
1868        assert_eq!(c.framer_header_polls, 1);
1869        assert_eq!(c.framer_object_polls, 0, "a bypassed stream never enters the object path");
1870        assert_eq!(c.streams_not_shapeable, 1);
1871        assert_eq!(c.objects_not_addressable, 0, "no object was ever decoded");
1872    }
1873
1874    /// A bypass while a fix-up is owed says so, because the bytes still in
1875    /// flight decode to Object IDs one ahead of what was delivered.
1876    ///
1877    /// Objects 0 and 1 are written normally and object 1 is elided, which
1878    /// arms the fix-up. The third object is a hand-written all-ones varint —
1879    /// `2^62 - 1` in eight bytes under RFC 9000, `2^64 - 1` in nine from
1880    /// draft-17. Either is legal and its delta resolves to an Object ID above
1881    /// the ceiling, so `read_object_meta` returns `InvalidField` rather than
1882    /// an incomplete-input error and the framer abandons the stream with the
1883    /// fix-up unspent.
1884    ///
1885    /// *Ablation:* reporting `fixup_owed: false` unconditionally passes
1886    /// every other test in this file — nothing else reads the flag — and
1887    /// leaves the session forwarding a tail that renumbers itself.
1888    #[test]
1889    fn a_bypass_that_strands_a_fix_up_reports_it_as_owed() {
1890        for &draft in DELTA_DRAFTS {
1891            let mut stream = build_stream(draft, &[object(0, 8), object(1, 8)]);
1892            // All-ones is the largest varint the draft's encoding can express:
1893            // eight bytes under RFC 9000, nine from draft-17.
1894            if draft.uses_moqt_varint() {
1895                stream.extend_from_slice(&[0xFFu8; 9]);
1896            } else {
1897                stream.extend_from_slice(&[0xFFu8; 8]);
1898            }
1899
1900            let counters = Arc::new(Recorder::new());
1901            let mut framer = framer_for(draft, FramerConfig::default(), &counters);
1902            framer.feed(&stream);
1903
1904            assert!(matches!(framer.poll(), FramerOut::Header { .. }));
1905            assert!(matches!(framer.poll(), FramerOut::Object { .. }), "[{draft}] object 0");
1906            let FramerOut::Object { meta, .. } = framer.poll() else {
1907                panic!("[{draft}] expected object 1");
1908            };
1909            framer.note_elided(&meta);
1910            assert!(framer.elide_cursor().fixup_pending, "[{draft}] the fix-up is armed");
1911
1912            assert!(
1913                matches!(framer.poll(), FramerOut::Error(_)),
1914                "[{draft}] the third object must fail to decode outright"
1915            );
1916            assert!(
1917                matches!(
1918                    framer.poll(),
1919                    FramerOut::Bypassed { reason: BypassReason::DecodeError, fixup_owed: true }
1920                ),
1921                "[{draft}] the stranded fix-up must be reported"
1922            );
1923
1924            assert_eq!(counters.snapshot().streams_not_shapeable, 1, "[{draft}] one latch");
1925            assert_eq!(
1926                counters.snapshot().object_ids_rewritten,
1927                0,
1928                "[{draft}] the fix-up never got the chance to be written"
1929            );
1930        }
1931    }
1932
1933    /// The counters move where — and only where — the slow path ran.
1934    ///
1935    /// This is the falsifiable half of the `Interest::NONE` claim: the
1936    /// framer is the slow path, so a framer that counts nothing makes the
1937    /// proof pass by measuring nothing.
1938    ///
1939    /// *Ablation:* passing a throwaway `Recorder` from `with_recorder`
1940    /// instead of the caller's — the shape `ObjectFramer::new` has by
1941    /// design — leaves every assertion here reading 0.
1942    #[test]
1943    fn the_framer_counts_the_slow_path_work_it_did() {
1944        for &draft in DRAFTS {
1945            let delta = DELTA_DRAFTS.contains(&draft);
1946            let objects: Vec<_> = (0..4).map(|id| object(id, 16)).collect();
1947            let stream = build_stream(draft, &objects);
1948
1949            let run = run_eliding(draft, &stream, stream.len(), FramerConfig::default(), &[1]);
1950            let c = run.counters.snapshot();
1951
1952            assert_eq!(c.framers_created, 1, "[{draft}]");
1953            assert_eq!(c.framer_header_polls, 1, "[{draft}] one poll decoded the header");
1954            // Four objects plus the poll that returned `NeedMore`.
1955            assert_eq!(c.framer_object_polls, 5, "[{draft}]");
1956            assert_eq!(
1957                c.object_ids_rewritten,
1958                u64::from(delta),
1959                "[{draft}] one rewrite, and only where IDs are deltas"
1960            );
1961            assert_eq!(c.objects_not_addressable, 0, "[{draft}] every object was framed");
1962            assert_eq!(c.streams_not_shapeable, 0, "[{draft}] nothing was bypassed");
1963        }
1964    }
1965
1966    /// An object too big to buffer is counted as unaddressable exactly
1967    /// once, on both of the paths that produce one.
1968    ///
1969    /// The whole-stream feed routes object 65 through `poll_object`'s
1970    /// oversized branch; the 32-byte feed routes it through
1971    /// `poll_oversized`, which emits it as several `Passthrough` chunks
1972    /// and must still count *one* object.
1973    ///
1974    /// *Ablation:* counting in `poll`'s `passthrough_remaining` branch
1975    /// instead — the obvious place, since that is where most of the bytes
1976    /// leave — reports 4 for the chunked run and 0 for the whole-stream
1977    /// one.
1978    #[test]
1979    fn an_unaddressable_object_is_counted_once_per_object_not_per_chunk() {
1980        for &draft in DELTA_DRAFTS {
1981            let objects = vec![object(0, 8), object(1, 8), object(65, 4096), object(66, 8)];
1982            let stream = build_stream(draft, &objects);
1983            let config = FramerConfig::new().with_max_buffered_object_bytes(64);
1984
1985            for chunk in [stream.len(), 32] {
1986                let run = run_eliding(draft, &stream, chunk, config.clone(), &[1]);
1987                let c = run.counters.snapshot();
1988                assert_eq!(
1989                    c.objects_not_addressable, 1,
1990                    "[{draft}] chunk {chunk}: one object, however many chunks it left in"
1991                );
1992                assert_eq!(c.object_ids_rewritten, 1, "[{draft}] chunk {chunk}");
1993                assert_eq!(c.streams_not_shapeable, 0, "[{draft}] chunk {chunk}: no bypass");
1994            }
1995        }
1996    }
1997}