HTTP/3 (QUIC) foundation: Phase 6 (Stream layer and flow control) - #27882
Open
quaesitor-scientiam wants to merge 2 commits into
Open
HTTP/3 (QUIC) foundation: Phase 6 (Stream layer and flow control)#27882quaesitor-scientiam wants to merge 2 commits into
quaesitor-scientiam wants to merge 2 commits into
Conversation
This was referenced Jul 21, 2026
Extends frame.v with all stream-related frames (STREAM, RESET_STREAM, STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS, DATA_BLOCKED, STREAM_DATA_BLOCKED, STREAMS_BLOCKED). Adds stream.v (stream ID categories, send/recv state machines, QuicStreamSet), stream_reassembly.v (per-stream offset-ordered reassembly with final-size reconciliation, mirroring Phase 4's crypto_stream.v), and flow_control.v (connection- and stream-level windows, the RFC 9000 §4.1 initial-limit naming inversion resolved in one place). /vreview found and fixed a real design footgun: QuicStream.send/recv were Optional value fields, so mutating the unwrapped copy via note_data()/note_size_known() silently didn't persist unless the caller remembered to reassign it back. Fixed by switching to nilable pointers (matching Tls13ClientHandshake.verified_chain's established convention) before any real caller could hit it. Also fixed two mechanical V-compiler quirks: match on a repeated array-index expression stops reliably narrowing a sum type past a certain variant count (affected two pre-existing tests too), and a stale "type 0x08 unimplemented" test now that Phase 6 implements STREAM frames there. Includes the plan's own named integration test: three interleaved streams (client bidi, server-initiated uni, second client bidi) with connection-level flow control tracked across all three. Co-Authored-By: WOZCODE <contact@withwoz.com>
…EAMS frames /vreview pass on this branch after rebasing onto master (Phase 5 merged): found and fixed 2 confirmed sibling-parity gaps before push. - frame.v: parse_stream_frame/encode_stream_frame were missing the "offset + length <= 2^62-1" bound RFC 9000 §19.8 requires (identical to §19.6's CRYPTO-frame requirement, which parse_crypto_frame/ encode_crypto_frame already correctly enforce in this same file). A STREAM frame with offset near 2^62-1 and nonzero data previously parsed successfully with no FRAME_ENCODING_ERROR, only accidentally caught downstream by stream_reassembly.v's unrelated 1 MiB DoS cap. - frame.v: parse_max_streams_frame/encode_max_streams_frame had no check against RFC 9000 §4.6's 2^60 cap (verified against the primary RFC text). transport_parameters.v already enforces this exact limit on the transport-parameter half of the same RFC requirement, with full boundary tests -- the frame half, added in this same PR, wasn't checked against that in-repo sibling before this pass. Also documented (not changed): stream.v's open_local_stream has no max_streams enforcement, unlike its receive-side sibling get_or_create -- plausibly a future Phase 9 caller's responsibility (it owns the peer's currently-advertised limit), but wasn't documented as deferred the way other genuinely-deferred hooks in this file are. Added that note. 5 new regression tests (STREAM offset+length bound: encode, parse with explicit length, parse with implicit/LEN-bit-clear length; MAX_STREAMS 2^60 bound: encode, parse, boundary-accepted). Full suite: 28/28. Co-Authored-By: WOZCODE <contact@withwoz.com>
quaesitor-scientiam
force-pushed
the
http3-quic-stream-layer
branch
from
August 8, 2026 03:16
a261faf to
f78f16e
Compare
quaesitor-scientiam
marked this pull request as ready for review
August 8, 2026 03:18
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 6 of HTTP/3/QUIC support (#27675): the stream layer and flow
control, continuing from the completed Phase 5 work in #27881 (full
handshake completion). Builds on the completed Phase 0-5 foundation —
#27680, #27877, #27880, #27881 — all merged, and targets
masterdirectly.See
vlib/net/quic/PROGRESS.mdfor the exact phase-by-phase checklist.
Scope
frame.vextended with every stream-related frame: STREAM, RESET_STREAM,STOP_SENDING, MAX_DATA, MAX_STREAM_DATA, MAX_STREAMS, DATA_BLOCKED,
STREAM_DATA_BLOCKED, STREAMS_BLOCKED.
stream.v—QuicStream, stream ID categories (RFC 9000 §2.1:client/server-initiated, bidirectional/unidirectional, encoded in the
low 2 bits), independent send/receive state machines per bidi stream.
Even this client-first phase must correctly receive server-initiated
unidirectional streams from day one -- HTTP/3's control stream and
QPACK encoder/decoder streams are all server-to-client uni streams,
not deferrable to a later server-support phase.
flow_control.v— connection- and stream-level windows enforcedtogether (a frame within its own stream's window can still be blocked
by the connection-level aggregate window and vice versa); three
separate stream-level initial limits depending on who opened the stream
and its directionality; auto-window-growth heuristic to avoid
throughput stalls; RESET_STREAM's Final Size reconciled against
previously-received offsets (FINAL_SIZE_ERROR on mismatch).
stream_reassembly.v— per-stream offset-ordered reassembly, mirroringPhase 4's crypto_stream.v pattern but for STREAM frames' implicit-length-
at-end-of-packet handling.
Test plan
(
id -> categoryandcategory -> first id).named test, confirming a server-initiated uni stream has exactly a
receive half and no send half from the client's perspective, plus
exercised end-to-end in the integration test below.
connection window blocking a send the stream window alone would
allow, and vice versa).
smaller than already-received data, a final size that later changes,
a final size conflicting with an already-buffered out-of-order
fragment, and data arriving after the final size that would exceed it.
bidi, server-initiated uni, second client bidi), STREAM frames
delivered genuinely interleaved and out of order, each independently
reassembled while one connection-level window tracks the running
total across all three.
./vnew test <path>-- 28/28files in
vlib/net/quic/../vnew fmt -wapplied to all touched.vfiles.upstream/master(2026-08-07) now that HTTP/3 (QUIC) foundation: Phase 5 (Full handshake completion) #27881merged (also via squash) -- clean
git rebase --onto, no testadaptation needed (unlike HTTP/3 (QUIC) foundation: Phase 5 (Full handshake completion) #27881's rebase, no upstream validation
change broke this branch's own tests).
/vreview(full A-G pass, run once while writing this phase and againafter the rebase before push) found and fixed:
Before commit:
QuicStream.send/recvwere Optional VALUE fields, somutating the unwrapped copy via
note_data()/note_size_known()lookedlike in-place mutation but silently didn't persist unless the caller
remembered to reassign it back (
s.recv = recv) -- a future caller couldeasily miss this and get stale
state/final_sizewhile the underlyingdata was still correct. Fixed by switching to nilable pointers (matching
Tls13ClientHandshake.verified_chain's established convention) before anyreal caller could hit it, eliminating the bug class rather than
documenting the trap. Also fixed two mechanical V-compiler quirks along
the way:
matchon a repeated array-index expression (frames[N]) stopsreliably narrowing a sum type once it has enough variants -- affected two
pre-existing tests in
frame_test.v/initial_exchange_test.vthat hadworked fine with fewer variants -- and a pre-existing "frame type 0x08 is
unimplemented" test that became false once this phase implemented STREAM
frames at that exact value (retargeted to 0x1e/HANDSHAKE_DONE).
After the rebase, before push: 2 confirmed sibling-parity gaps, both
citations verified against the primary RFC 9000 text before fixing.
parse_stream_frame/encode_stream_framewere missing RFC 9000 §19.8's"offset + length ≤ 2^62-1" bound -- identical to §19.6's CRYPTO-frame
requirement, which
parse_crypto_frame/encode_crypto_framealreadycorrectly enforce in this same file.
parse_max_streams_frame/encode_max_streams_framewere missing RFC 9000 §4.6's 2^60 cap --already correctly enforced (with its own boundary tests) on the
transport-parameter half of the identical requirement in
transport_parameters.v. Fixed both, 5 new regression tests. Alsodocumented (not changed, since it's plausibly a future Phase 9 caller's
responsibility):
open_local_streamhas nomax_streamsenforcement,unlike its receive-side sibling
get_or_create.🧙 Built with WOZCODE