Skip to main content

ObjectFramer

Struct ObjectFramer 

Source
pub struct ObjectFramer {
Show 16 fields stream_type: DataStreamType, draft: DraftVersion, config: FramerConfig, buf: BytesMut, stage: Stage, context: SubgroupContext, passthrough_remaining: u64, bypassed: bool, pending_bypass: Option<BypassReason>, index_in_stream: u64, last_forwarded_id: Option<u64>, fixup_pending: bool, fetch_rollback: Option<AnyFetchObjectWriter>, pending_disposition: Option<PendingObject>, counters: Arc<Recorder>, fetch_orders: Option<Arc<FetchGroupOrders>>,
}
Expand description

Frames a unidirectional MoQT data stream into individually addressable objects while preserving the exact wire bytes.

Feed it the raw bytes of one stream, in order, from the stream’s first byte. Drain poll after each feed until it returns FramerOut::NeedMore. The concatenation of every raw and Passthrough payload it yields equals the concatenation of every chunk fed to it, unless note_elided has been called — see Self::poll.

Fields§

§stream_type: DataStreamType§draft: DraftVersion§config: FramerConfig§buf: BytesMut§stage: Stage§context: SubgroupContext§passthrough_remaining: u64

Bytes still owed to an oversized object that is being streamed through uninterpreted. Denominated in source bytes — see Self::poll_oversized.

§bypassed: bool

Set once parsing has been abandoned for the rest of the stream.

§pending_bypass: Option<BypassReason>

Why parsing was abandoned, waiting for Self::poll to drain it as a FramerOut::Bypassed.

fixup_owed is read off Self::fixup_pending at drain time rather than being captured here, and the two are the same value: once bypassed is set, poll never re-enters poll_object, which is the only thing that clears the flag, and no Object item can be emitted between the latch and the drain for note_elided to set it.

§index_in_stream: u64§last_forwarded_id: Option<u64>

Absolute Object ID of the last object actually forwarded.

§fixup_pending: bool

Whether the next object emitted still owes a fix-up: on a subgroup stream the leading Object ID varint rewritten against Self::last_forwarded_id, on a fetch stream a survivor re-encoded against the frame that is now in front of it.

Both are settled by emitting one object, after which the writing cursor is level with the reading one again.

§fetch_rollback: Option<AnyFetchObjectWriter>

The fetch writer as it stood before the object emitted most recently was re-emitted through it.

Re-emitting advances the writer, and the caller’s verdict on that object does not land until the next poll, so an elide has to put the writer back where it was — otherwise the survivor after it is encoded against an object nobody received.

§pending_disposition: Option<PendingObject>

The object emitted most recently, awaiting the caller’s verdict. Committed lazily at the top of Self::poll_object.

§counters: Arc<Recorder>

Slow-path counters for the session this framer belongs to.

§fetch_orders: Option<Arc<FetchGroupOrders>>

The session’s answered fetches, on the drafts where a fetch stream cannot be read without one. None for a framer built outside a session, and on every draft that needs no order.

Implementations§

Source§

impl ObjectFramer

Source

pub fn with_recorder( stream_type: DataStreamType, draft: DraftVersion, config: FramerConfig, counters: Arc<Recorder>, ) -> Self

A framer whose slow-path counters go to counters.

The only constructor session.rs may use. The counters are session-scoped, and a framer that cannot reach its session’s Recorder would leave framers_created, framer_header_polls, framer_object_polls, objects_not_addressable and object_ids_rewritten at zero for every real session — which is a passing Interest::NONE proof obtained by measuring nothing.

Self::new is kept, unchanged, for tests and downstream callers that construct a framer to parse bytes rather than to forward them; it is equivalent to passing a fresh Recorder whose counts nobody reads. That is why this is an addition and not a signature change.

Source

pub fn with_fetch_group_orders(self, orders: Arc<FetchGroupOrders>) -> Self

Read this stream’s fetch Objects against the order its FETCH asked for.

Only drafts 18, 19 and 20 need it — see FetchGroupOrders — and only a fetch stream consults it; a subgroup framer given one ignores it. A framer built without it on a draft that needs one reports BypassReason::FetchGroupOrderUnknown and forwards the stream uninterpreted, which is what every caller outside a session gets and what the session itself gets for a stream naming a request it never saw asked for.

Source

pub fn new( stream_type: DataStreamType, draft: DraftVersion, config: FramerConfig, ) -> Self

Create a framer for a stream of the given kind on the given draft, discarding its counters.

Increments go to a private Recorder nothing can read, so a caller that wants a session’s counters to move must use Self::with_recorder. Retained for callers that parse bytes rather than forward them, where the counts are not the point.

Source

pub fn elide_cursor(&self) -> ElideCursor

The state an elide has left behind on this stream.

Read-only, and read-anytime: the framer applies the fix-up itself, so nothing outside has to act on this.

Source

pub fn note_elided(&mut self, meta: &ObjectMeta)

Record that the object the framer emitted most recently was not forwarded.

Call exactly once, immediately after deciding to drop an object, and before the next Self::poll. meta must be the meta the framer handed out for that object; it is debug_asserted against the framer’s own record, because calling this out of order is the one way to corrupt a stream silently.

On drafts 07-13 subgroup streams and on drafts 07-14 fetch streams this only suppresses the cursor advance — those Object IDs are absolute, so the bytes of every later object already say the truth. Elsewhere it also arms a fix-up: the leading Object ID varint on a drafts 14-20 subgroup stream, and the whole framing of the next frame on a drafts 15-20 fetch stream, where it additionally puts the fetch writer back to where the last forwarded frame left it.

Source

pub fn feed(&mut self, chunk: &[u8])

Buffer a chunk of stream bytes.

Source

pub fn buffered(&self) -> usize

Bytes buffered but not yet emitted. Non-zero only mid-object.

Source

pub fn is_bypassed(&self) -> bool

true once the framer has stopped parsing this stream and is forwarding bytes uninterpreted.

Source

pub fn finish(&mut self) -> Option<Bytes>

Flush any buffered bytes at end of stream.

Called when the source signals FIN. Returns whatever the framer still holds — a truncated final object, or bytes buffered behind an incomplete framing — so the caller can forward them before finishing the destination stream. Forgetting this call turns a clean FIN into silent truncation.

Source

pub fn poll(&mut self) -> FramerOut

Produce the next item, or FramerOut::NeedMore.

§Invariant

Concatenating the raw field of every Header and Object and the payload of every Passthrough, in the order produced, reproduces the fed bytes exactly — on every draft, including streams that fall back to bypass — unless Self::note_elided has been called, which deliberately removes an object’s bytes and may rewrite one leading varint.

That exception is the only one, and it is opt-in per stream: a framer used as a pure observer never calls note_elided, so its output stays byte-identical to its input. When note_elided has been called on a drafts 14-19 subgroup stream, the next object the framer emits has its leading Object ID varint re-encoded against the last object actually forwarded — one field, in one object, and every byte after it copied verbatim. Everything else, on every other stream, still concatenates back to the source exactly.

FramerOut::Bypassed and FramerOut::NeedMore carry no bytes and so contribute nothing to the reconstruction either way.

Source

fn emit_passthrough(&mut self, limit: u64) -> FramerOut

Hand out up to limit buffered bytes without interpreting them.

Source

fn latch_bypass(&mut self, reason: BypassReason)

Stop parsing this stream for good, record why, and release everything buffered.

Draining here is what keeps memory bounded: without it a stream the framer cannot parse grows the buffer for as long as the peer keeps writing.

reason is stored rather than returned, because every call site returns a different FramerOut from the same poll; the next poll drains it. Guarded on bypassed so a second latch — which today cannot happen, since poll short-circuits once the flag is set — could never overwrite the first reason or double-count Counters::streams_not_shapeable, whose whole job is to be one per stream.

Source

fn poll_header(&mut self) -> FramerOut

Source

fn fetch_stage(&mut self, h: &AnyFetchHeader) -> Result<Stage, BypassReason>

The reader and writer a fetch stream is parsed with, or why it is not.

Two drafts need an answer from off the stream and the other eleven do not, which is fetch_group_order_is_needed. Where one is needed it comes from the FETCH the session carried, filed under the Request ID this header names; a stream naming a request that was never asked for is bypassed rather than guessed at, because the guess would decode.

The reader and writer are built with the same order. They are the two halves of one stream — the writer re-encodes a survivor after an elide against the frames now in front of it — so a pair built against different orders would renumber the stream it was meant to preserve.

Source

fn poll_object(&mut self) -> FramerOut

Source

fn poll_oversized(&mut self) -> FramerOut

The buffer hit its cap without completing an object. Decide whether the object’s framing is understood — in which case its bytes can be streamed through and framing resumes afterwards — or whether the stream has to be abandoned.

The framing is read against the buffered bytes followed by padding, so the decoder can advance past a payload that has not arrived. How wide that padding may be is Self::measuring_pad’s call.

Source

fn commit_disposition(&mut self)

Commit the disposition of the object emitted most recently.

An object still pending was forwarded; one the caller elided was taken out of pending_disposition by Self::note_elided, so the cursor never advances to it.

Source

fn delta_encodes_object_ids(&self) -> bool

true when this stream’s Object IDs are written as id - prev - 1 rather than absolutely, so eliding one renumbers every later object.

Subgroup streams on drafts 14-20, and no fetch stream on any draft: this is the predicate for the varint rewrite, and a fetch frame is paid for by Self::reemit_fetch_frame instead. Drafts 07-13 write subgroup Object IDs absolutely and need neither.

The draft half is an exhaustive match rather than a matches!: a draft left off the list answers “written absolutely”, the framer then forwards the object after an elide without rewriting its leading varint, and every later Object ID on the stream is off by one with nothing to say so. The stream-kind half stays a matches!DataStreamType is not a draft list and its two variants are both named here.

Source

fn elide_owes_a_fixup(&self) -> bool

true when eliding an object from this stream leaves the next one encoded against something that is no longer on the wire, so a fix-up is owed before another object may be forwarded.

The two stream kinds owe it for different reasons and pay it in different ways, and the debt itself is the same: until one more object has been emitted, the bytes the destination would receive decode to Locations nobody sent. fixup_owed on a Bypassed is what a session resets its destination over, and it reads this.

Fetch streams on drafts 15-20, where a Serialization Flags field lets a frame take any of its Group ID, Subgroup ID, Object ID and Priority from the frame before it — draft-17 Section 10.4.4.1, Table 7: “Object ID is the prior Object’s ID plus one”. Not drafts 07-14, whose fetch objects state all four outright.

Exhaustive rather than a matches! for the same reason as Self::delta_encodes_object_ids, and the consequence is larger here: false means no fix-up is owed, so the session never resets the destination and the receiver keeps a stream whose frames decode to Locations nobody sent.

Source

fn apply_elide_fixup( &mut self, len: usize, object_id: u64, frame: Option<&AnyFetchFrame>, ) -> Result<Option<Bytes>, String>

Rewrite the leading Object ID varint of self.buf[..len] when an elide has left the wire’s delta chain one object ahead of what was actually forwarded.

len may be a prefix of the object rather than the whole of it — the oversized path calls this with the first chunk. Returns Ok(None) when no fix-up was owed, in which case the caller emits the source bytes untouched; the borrow of self.buf ends before this returns, so the caller is free to split it afterwards.

The error is unreachable in practice — Object IDs are strictly increasing within a stream and the caller has already established that the ID field is whole in the buffer — but it is reported rather than swallowed, because the alternative is emitting bytes known to decode to the wrong ID.

Source

fn reemit_fetch_frame( &mut self, len: usize, frame: &AnyFetchFrame, ) -> Result<Option<Bytes>, String>

Re-emit one fetch frame through this stream’s writer.

Called for every fetch frame the framer emits, not only after an elide. The writer is what a survivor is re-encoded against, and it only moves when it is shown a frame, so skipping the frames nothing was owed for would leave it as many frames behind as were skipped.

Answers Ok(None) when the frame’s own bytes still say what the frame says, which is every frame on a stream nothing has been removed from. The writer is cloned first so that Self::note_elided can put it back: this runs before the caller’s verdict on the frame, and a frame the caller drops must leave no trace on the writing side.

len may be a prefix of the frame — the oversized path calls this with the first chunk — and the codec asks only that the whole framing be inside it, which is what the caller has already established.

Source

fn measuring_pad(&self) -> usize

How much padding Self::poll_oversized may put behind the buffered bytes when measuring an object that has not arrived whole.

The pad costs no memory by itself — it is never materialised — but every copying read in the codec checks Buf::remaining() before it allocates, so the pad is exactly the bound on the allocation a hostile length field can provoke. That makes the choice per layout rather than global:

  • Subgroup objects on drafts 14-20, and fetch objects on draft-14, are measured without a single copy — their read_object_meta advances past the extension block and the payload rather than reading them. Nothing can be talked into allocating, so the pad is effectively unbounded and an object of any declared size stays measurable: it streams through and framing resumes after it.
  • Every other object layout decodes its extension block by copying it out of the buffer. Those keep a pad of one cap, which bounds the copy at the price of reach: an object whose wire length exceeds buffered + cap cannot be measured and the stream falls back to passthrough for its remainder.

Widening the second case needs the codec to reject an extension length larger than the bytes actually present, which is not this crate’s to change.

“Every other” includes the newest fetch layouts, and that is not an omission. Drafts 15 through 20 all read a fetch frame through FetchObjectReader::read_object_header, which materialises the frame’s properties — data_dispatch.rs fo15..fo20 each carry the block out of the buffer before read_object_frame skips the payload — so a declared extension length is still an allocation those drafts can be talked into. Only draft-14’s FetchObject::decode_meta advances past the block instead. Both arms are exhaustive matches rather than matches! so that this stays a decision: a new draft answering false by omission would be safe here, which is precisely why nothing would ever notice that no one had looked at its fetch layout.

Source

fn commit(&mut self, probe: Probe)

Adopt a probe reader whose decode succeeded.

Source

fn object_meta(&self, meta: Meta) -> ObjectMeta

Trait Implementations§

Source§

impl Debug for ObjectFramer

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more