Repository navigation
fix(web-transport-moq): send raw QUIC stream codes as is - #27
Conversation
A raw QUIC session mapped RESET_STREAM and STOP_SENDING codes through the HTTP/3 WebTransport code space, so a plain QUIC peer saw CANCELLED (1) as 0x52e4a40fa8dc and its own codes read back as invalid. Raw sessions now send the code as is and read a plain code as is. A code in the HTTP/3 range still decodes, since older raw peers send that form and no u32 reaches it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The maintainer accepted the raw QUIC wire change for moq's (Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
No blocking findings.
Low (test coverage): tests/raw_stream_codes.rs only exercises uni streams. Bidi streams go through the same SendStream::new / RecvStream::new with self.close (session.rs:251-252, 291-292), so this is not a bug today, just an untested path.
What I checked:
- Every path uses the session's raw flag.
reset(send.rs:45) andstop(recv.rs:45) encode viaCloseReason::encode_stream_code.read,read_exact,read_chunk,read_many_chunks,read_to_end,write,write_all,write_chunk,write_many_chunks,write_all_chunksall decode through the newmap_error.received_resetandstoppedusedecode_stream_code, and theweb_transport_traitclosed()impls go through those. No other.reset(/.stop(call with an app code exists in the crate. - Open and accept. All stream construction (
session.rs:238, 251-252, 272, 291-292, 815, 896-897) passes the session'sclose, whichSession::rawbuilds withCloseReason::new(true)(session.rs:547) and the HTTP/3 path withnew(false)(session.rs:121). - HTTP/3 unchanged. Non-raw encode is the old
error_to_http3+try_from. Non-raw decode iserror_from_http3only, so a small code is stillInvalidReset/InvalidStopped/None, as before. The unit test pins this. - u32 / VarInt. Raw encode is the infallible
u32 -> VarInt. Raw decode tries the HTTP/3 range first, thenu32::try_from.error_to_http3(0)is already pastu32::MAX, so the two forms can't overlap. Codes pastu32that aren't in the HTTP/3 range becomeInvalidReset/InvalidStopped/None. - Poll APIs.
AsyncRead::poll_readandAsyncWrite::poll_*forward noq'sio::Errorwithout mapping the code. That was already true before this PR and stays the same for both session kinds. - Header-read path.
read_varint/log_header_error(session.rs:930-955) only matchReset(_)and don't decode the code, so nothing changes there. - Parity with moq-dev/web-transport#408. Same
encode_stream_code/decode_stream_codelogic and the samemap_errorsplit. The one difference is that the raw flag lives on the sharedCloseReasoninstead of a per-streamraw: bool, which fits this crate because it already tracks raw-ness there for close codes (#11). - Tests fail without the fix. I swapped
web-transport-moq/srcback to the base (0aa0af85) and rantests/raw_stream_codes.rs.reset_is_sent_as_is,stop_is_sent_as_is,peer_reset_is_read_as_isandpeer_stop_is_read_as_isfail, andlegacy_peer_codes_are_unmappedpasses, which matches the description. On the PR head the wholeweb-transport-moqsuite passes.
Impact (agrees with the description):
- Public API: none. The
From<noq::ReadError>/From<noq::WriteError>impls still decode HTTP/3 only, so external callers using them on a raw connection keep the old behavior. - Wire (raw QUIC only), compatible in one direction only:
- A fixed peer reads an older raw peer's mapped codes correctly.
- An older raw peer sees a fixed peer's plain codes as
InvalidReset/InvalidStopped, andNonefromreceived_reset/stopped.
(Written by Claude Opus 5.5)
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (4)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. WalkthroughCloseReason now encodes and decodes stream error codes based on whether a session uses raw QUIC. Receive and send stream operations use these conversions for reset and stop codes. Tests cover WebTransport mappings, raw code exchange with a plain QUIC peer, and legacy HTTP/3-mapped codes. Priority: ⬇️ Low Merge Risk: ⚪ Minimal · up to Raw QUIC sessions now send reset and stop codes unchanged, and WebTransport behavior is preserved. I found no merge-blocking risk in the supplied change. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Updated raw QUIC peers accept legacy codes, but older peers reject the newly emitted plain codes. This can change cancellation error handling during mixed-version operation. The inspected paths preserve session ownership and do not introduce new authentication or privilege authority. Retained concerns
Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Thanks. On the bidi coverage note: bidi streams are built through the same (Written by Claude Opus 5.5) |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(proto): yield error on too many gaps in assembler Port of quinn-rs/quinn@fed0321a (quinn-rs/quinn#2694, RUSTSEC-2026-0185): a peer sending small gapped STREAM or CRYPTO frames could make the assembler buffer an unbounded number of chunks. After defragmenting, more than 1024 chunks now closes the connection with INTERNAL_ERROR. Co-Authored-By: Luke Curley <kixelated@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(proto): bound assembler chunk count regardless of over-allocation Port of quinn-rs/quinn@4b88c940 (quinn-rs/quinn#2789). The cap above was only checked inside the over-allocation branch, so gapped frames whose allocation matches their length, or frames handled entirely by the unordered dedup loop, still grew the chunk count without bound. Compact once the count passes twice the cap and reject only if compaction cannot bring it back under. Co-Authored-By: Luke Curley <kixelated@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(proto): coalesce contiguous chunks during defragment Port of quinn-rs/quinn@c3f8b098 (quinn-rs/quinn#2814). defragment now combines contiguous chunks smaller than buffered / MAX_CHUNKS, so a large stream_receive_window with a slow in-order reader no longer trips TooManyChunks on lossless data. Chunks above the minimum size are left alone so they are not copied on every compaction. Co-Authored-By: Luke Curley <kixelated@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: release 2.0.1 Bumps the shared workspace version and the internal pins, and records #25 (reassembly cap, RUSTSEC-2026-0185), #19, and #23 in CHANGELOG-MOQ.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: cover #27 and the unordered compaction fix in 2.0.1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dirkjan Ochtman <dirkjan@ochtman.nl> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Otto Hermann <code@otto-hermann.me> Co-authored-by: Eanna Lyons <eanna@magic.dev>
A raw QUIC session (
Session::raw) mapped RESET_STREAM and STOP_SENDING codes through the HTTP/3 WebTransport code space, the same mix-up #11 fixed for close codes. Against moxygen over raw QUIC, moq's CANCELLED (1) arrived as0x52e4a40fa8dc, and moxygen's CANCELLED reached moq asinvalid RESET_STREAM: 1(moq-dev/moq#4602).CloseReason, which already knows whether the session is raw, encodes and decodes stream codes for the session's streams.resetandstopcodes as is, and read a peer's code as is when it fits in au32.web-transport-moqraw peers send that form, and nou32reaches that range, so it cannot be mistaken for a raw code.Impact
From<noq::ReadError>/From<noq::WriteError>impls keep their HTTP/3 decoding; the streams decode through the session instead.InvalidReset/InvalidStopped, which moq-net reports as a transport error rather than a stream code. See moq-dev/moq'squest/m1/raw-stream-codes.mdfor what that costs across moq versions.Tests
tests/raw_stream_codes.rsdrives a raw session against a plainnoq::Connection: reset and stop in each direction arrive verbatim, and a legacy HTTP/3-mapped code from the plain peer still decodes. The four verbatim cases fail without the fix; the legacy case guards compatibility and passes both ways. Unit tests pin the HTTP/3 mapping for non-raw sessions.Upstream: none,
web-transport-moqis fork-only.Follow-up: a 1.3.x backport for moq's
main(web-transport-trait 0.4) is a separate decision; see the moq PR.🤖 Generated with Claude Code
(Written by Claude Opus 5.5)