ReadUntil is documented as "Keep discarding atoms until the desired atom is found", but it only discards headers — it never reads or skips the body of a non-matching atom, so the next iteration parses a "header" out of the middle of the skipped atom's body:
src/atom.rs:112-123 (sync):
while let Some(header) = <Option<Header> as ReadFrom>::read_from(r)? {
if header.kind == T::KIND {
let body = &mut header.read_body(r)?;
return Ok(Some(T::decode_atom(&header, body)?));
}
// no else: header.size body bytes are never consumed
}
src/tokio/atom.rs:56-67 (async) has the identical bug.
Reproduced: for a stream of [free box with 4-byte body "ABCD"][valid ftyp], Option::<Ftyp>::read_until(...) returns Ok(None) — it interprets ABCD… as a header and consumes the rest of the stream as garbage. Any stream where the sought atom is not the first atom (the typical use case for this API) misparses.
Fix: when header.kind != T::KIND, read and discard header.size bytes (or seek forward) before looping.
Found during an extensive automated correctness review (Claude Code); reproduced against the current main sources.
ReadUntilis documented as "Keep discarding atoms until the desired atom is found", but it only discards headers — it never reads or skips the body of a non-matching atom, so the next iteration parses a "header" out of the middle of the skipped atom's body:src/atom.rs:112-123(sync):src/tokio/atom.rs:56-67(async) has the identical bug.Reproduced: for a stream of
[free box with 4-byte body "ABCD"][valid ftyp],Option::<Ftyp>::read_until(...)returnsOk(None)— it interpretsABCD…as a header and consumes the rest of the stream as garbage. Any stream where the sought atom is not the first atom (the typical use case for this API) misparses.Fix: when
header.kind != T::KIND, read and discardheader.sizebytes (or seek forward) before looping.Found during an extensive automated correctness review (Claude Code); reproduced against the current
mainsources.