The sse_parser re-frame probe emitted 'data: <line>' unguarded, so a
payload CR (interior CR in multi-line data: the payload line is '\r')
was consumed by strip_cr at re-parse time and the round-trip lost it.
Wave 1 had skipped only the terminal-CR case. Found ~32 min into the
first 45-min sse_parser campaign.
- frame_event emits 'data: <line>\r': the guard CR is the line
terminator, strip_cr removes exactly it, the payload CR survives
- drop the terminal-CR skip; the round trip is now total over
JSON-encodable payloads (JSON never encodes a raw CR, so the
residual asymmetry is the serde_json non-preserved key-order class)
- no crate-side change: the parser was correct (data: x\r\r\n ->
x\r); only the harness re-encode was weak
- regression: 2 new seeds (interior CR via \r escape; raw CR via CRLF
framing, two-line doc) + unit cases
Verification: shared 21/21 (default + no-default-features), main
crate green, clippy -D warnings clean, fmt clean, cargo fuzz build
clean
Two stateful op-sequence harnesses (fuzz/shared):
- ws_session_opseq: the WS byte adapter's generic pumps driven
end-to-end over a mock socket flavor through the new
byte_adapter::fuzzing drive hub (FuzzMockSocket flavor,
FuzzChannels/FuzzWriteSide channel bundle, pump spawn funnels,
FuzzInboundChunkProgress). Asserts lossless inbound forwarding
(exact after the teardown joins), the WS-01 walk's completed-chunk
count vs the declared frame count, the WS-15 close mapping
(1002/1011/verbatim-requested + per-cause reasons), lossless write
framing onto the declared chunk stream (FWD-21's failure-carry
included), lane-failure isolation (the alkcall 0.8.1 class), and
pump termination within the bound.
- retry_throttle: the inlined RetryAfterMiddleware's throttle map
through the new client::fuzzing hub — capacity bound, the
expired-first + Farthest-future eviction policy, the ceiling clamp,
refresh contraction, the Retry-After parse partition, and the
jitter bounds.
16 committed seeds (8 per target) in gen_fuzz_seeds.py, hand-encoded
against the arbitrary 1.4.x derive layout with ceiling-division
variant tags and per-element keep-going bytes, pinned by
seed-decode tests. fuzz/shared gains bytes/futures/tokio/url/
http/httpdate/arbitrary deps.
Verification: main crate 441 default tests, fuzz shared replay 21/21
(default + no-default-features), cargo fuzz build clean (5 targets),
clippy+fmt clean both crates.
FWD-21, found minutes into the first ws_session_opseq campaign: one
feed that drains a complete chunk and then hits an oversize header
(or the pending cap) in the same call returned
Walk::Oversize/PendingOverCap and silently discarded the already
drained pieces — the walk's own consumption-accounting contract
('every parsed byte is either emitted or retained', wave-1
invariant 6) was violated by the crate. Production could not reach
the shape (the mux's per-write invariant caps pending at one chunk +
header), so impact was nil, but WriteChunkWalk is public API and the
wave-1 split_walk equality check was skipped on exactly this path.
- Walk::Oversize(Vec<Vec<u8>>) / Walk::PendingOverCap(Vec<Vec<u8>>)
carry the pre-failure pieces (user-approved semver-relevant API
change)
- run_write_pump emits the carried pieces before fail_write_pump, so
a 1011 close never swallows a legal chunk the mux already counted
as written
- the ws_chunk_framing harness's split-drive failure arm now
reconciles the parsed-before-failure bytes across the whole and
split drives; a regression seed covers the drain-then-fail shape
Verification: main crate 441 default / 520 all-features tests, fuzz
shared replay 21/21 (default + no-default-features), cargo fuzz build
clean, clippy+fmt clean both crates.
Adopt the alk*/fuzzing posture (alkcall 0.8.1 precedent) — the dep
bump this ride-along builds on was 4005517:
- docs/plans/fuzzing.md: the four-target plan (sse_parser,
ws_chunk_framing this session; spec_import + the stateful wave
later), the infrastructure blueprint, and the corpus/campaign
policies (§3's table tracks progress)
- fuzz/ workspace (the sibling layout): nightly-pinned subtree,
stable fuzz/shared invariant library (corpus replay = the standing
fuzz gate), detached runner (never a foreground child of an agent
session), JSON dict, deterministic seed generator; 80 committed seeds
- two wave-1 targets: sse_parser (SseParser::feed + the FWD-17
envelope mapping — the parser every subscription forwards through)
and ws_chunk_framing (WriteChunkWalk, the write pump's chunk walk)
- crate-side surfacing: SseParser/SseEvent/sse_event_envelope are pub
behind the adapters::forward::fuzzing re-export hub; the write
pump's inline chunk loop extracted as WriteChunkWalk (pump byte
behavior unchanged — 441 tests green); retained_bytes() diagnostic
(the alktunnels/alksocks posture)
- root Cargo.toml gains the explicit [workspace] table with
fuzz/ excluded (auto-discovery would pull the nightly-consumed
dev-deps into the stable build — the alkcall trap); fuzz/ joins the
publish exclude list
- AGENTS.md verification checklist gains the corpus-replay line
FWD-20 fix, found by driving the new sse_parser harness: feed()
rescanned the whole reassembly buffer per feed — a dribble-fed
subscription (one unterminated line over many small chunks) cost
quadratic CPU (1 MiB dribble ≈ minutes per connection, unbounded
total). Line assembly now walks only the arriving chunk: the buffer
holds just the current line, the scan never revisits drained bytes.
1 MiB dribble: ~5 min (quadratic) → 21 ms (linear). Regression test
sse_parser_dribble_feeds_do_not_rescan_the_buffer.
Verification: cargo test 441 passed; cargo test --all-features ok;
clippy --all-targets -- -D warnings (main + fuzz/shared) clean;
fmt --check (main, fuzz, fuzz/shared) clean; cargo fuzz build clean;
corpus replay (cargo test --manifest-path fuzz/shared/Cargo.toml) 80
seeds green.
Refs: alkcall docs/research/fuzzing.md (§7.9 posture), alksocks
08a3694 (the sibling wave-1 shape), docs/plans/fuzzing.md §2