struct ControlFrameWalker {
draft: DraftVersion,
remaining: usize,
header: [u8; 16],
header_len: usize,
lost: bool,
}Expand description
Where the message boundaries are on a control stream being forwarded verbatim.
A control stream is one framed byte sequence — type, length, payload,
repeated — and a byte injected into the middle of a payload is read by
the peer as part of that payload, leaving its decoder wrong about every
message after it. So an injection has to be placed between messages,
and on the pass-through pipe nothing else knows where that is: that pipe
forwards whatever recv.read returned, and read boundaries are not
message boundaries.
This walks the framing without decoding anything. It reads a type varint’s length from its first byte, reads the payload length, and then counts payload bytes down to zero — one varint decode per message and no per-byte work beyond the header. It allocates nothing and never holds a message; the bytes go straight out as they always did.
§Why not the control parser
ControlStreamParser already knows this framing and is already built
on the pipes that observe or mutate. It also buffers each message whole
and decodes it into an AnyControlMessage, which is the cost the
pass-through pipe exists not to pay — and on an Interest::NONE session
with no observer it is not built at all, so a stream that has never been
parsed has no idea where it stands.
§It is only as right as the draft it was given
The framing changed at draft 11: earlier drafts write the payload length
as a QUIC varint, later ones as a fixed 16-bit big-endian field. This
walker is built from the session’s current draft, which for the moq-00
cohort (drafts 07-14) is a configured guess until a SETUP is peeked. A
wrong guess makes the lengths wrong and the boundaries wrong with them.
It is the same exposure the object framer already documents for the same
cohort, and it fails the same way: Self::at_boundary latches to
false as soon as a header cannot be made sense of, so an injection on
a stream whose framing has been lost is held rather than written into
the middle of something.
Fields§
§draft: DraftVersion§remaining: usizePayload bytes still owed on the message being forwarded.
header: [u8; 16]Header bytes of the next message collected so far.
header_len: usizeHow many of header are populated.
lost: boolSet once the framing stops making sense, and never cleared. A walker that has lost the stream reports no boundaries at all, which holds every later injection instead of placing it by guesswork.
Implementations§
Source§impl ControlFrameWalker
impl ControlFrameWalker
Sourcefn new(draft: DraftVersion) -> Self
fn new(draft: DraftVersion) -> Self
A walker positioned at the start of a control stream, which is a message boundary.
Sourcefn at_boundary(&self) -> bool
fn at_boundary(&self) -> bool
Whether everything written so far ends on a message boundary, so another message may be written now.
Sourcefn is_mid_message(&self) -> bool
fn is_mid_message(&self) -> bool
Whether a message has been started and not finished.
Distinct from !at_boundary(): a walker that has lost the framing
is at no boundary but also cannot claim a message is half-written,
and reporting a truncation it cannot see would be a fabrication.
Sourcefn advance(&mut self, data: &[u8]) -> Option<usize>
fn advance(&mut self, data: &[u8]) -> Option<usize>
Account for data being forwarded, and answer the offset within it
of the first message boundary it reaches.
None when no message completes inside data — either because it
is a middle slice of a long message, or because the framing has been
lost. The first boundary rather than the last, so an injection
held over from an earlier chunk goes out as early as this chunk
allows.
Sourcefn header_step(&self) -> HeaderStep
fn header_step(&self) -> HeaderStep
Read the header collected so far, if it is complete.