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: u64Bytes still owed to an oversized object that is being streamed
through uninterpreted. Denominated in source bytes — see
Self::poll_oversized.
bypassed: boolSet 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: boolWhether 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
impl ObjectFramer
Sourcepub fn with_recorder(
stream_type: DataStreamType,
draft: DraftVersion,
config: FramerConfig,
counters: Arc<Recorder>,
) -> Self
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.
Sourcepub fn with_fetch_group_orders(self, orders: Arc<FetchGroupOrders>) -> Self
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.
Sourcepub fn new(
stream_type: DataStreamType,
draft: DraftVersion,
config: FramerConfig,
) -> Self
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.
Sourcepub fn elide_cursor(&self) -> ElideCursor
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.
Sourcepub fn note_elided(&mut self, meta: &ObjectMeta)
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.
Sourcepub fn is_bypassed(&self) -> bool
pub fn is_bypassed(&self) -> bool
true once the framer has stopped parsing this stream and is
forwarding bytes uninterpreted.
Sourcepub fn finish(&mut self) -> Option<Bytes>
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.
Sourcepub fn poll(&mut self) -> FramerOut
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.
Sourcefn emit_passthrough(&mut self, limit: u64) -> FramerOut
fn emit_passthrough(&mut self, limit: u64) -> FramerOut
Hand out up to limit buffered bytes without interpreting them.
Sourcefn latch_bypass(&mut self, reason: BypassReason)
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.
fn poll_header(&mut self) -> FramerOut
Sourcefn fetch_stage(&mut self, h: &AnyFetchHeader) -> Result<Stage, BypassReason>
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.
fn poll_object(&mut self) -> FramerOut
Sourcefn poll_oversized(&mut self) -> FramerOut
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.
Sourcefn commit_disposition(&mut self)
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.
Sourcefn delta_encodes_object_ids(&self) -> bool
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.
Sourcefn elide_owes_a_fixup(&self) -> bool
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.
Sourcefn apply_elide_fixup(
&mut self,
len: usize,
object_id: u64,
frame: Option<&AnyFetchFrame>,
) -> Result<Option<Bytes>, String>
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.
Sourcefn reemit_fetch_frame(
&mut self,
len: usize,
frame: &AnyFetchFrame,
) -> Result<Option<Bytes>, String>
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.
Sourcefn measuring_pad(&self) -> usize
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_metaadvances 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 + capcannot 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.