Changelog
All notable changes to http3-zig are documented in this file.
The format is based on Keep a Changelog,
and this project aims to follow Semantic Versioning
once it reaches 1.0. Until then, any release in the 0.x line may include
breaking changes; see notes per release.
[Unreleased]
[0.4.8] - 2026-07-05
Fixed
- Raised the per-push
h3-interop-self-testtimeout so fresh CI runners can
finish compiling the in-tree HTTP/3 interop binaries instead of cancelling
during the build step.
[0.4.7] - 2026-07-05
Fixed
- Updated the boringssl pin checker to understand the quic-zig
git+https
tag pin used for v0.7.5, keeping CI's pre-build dependency alignment check
green.
[0.4.6] - 2026-07-05
Changed
- Repointed to
quic-zigv0.7.5, carrying the pinned quic-go interop
output fix and Windows CI test-harness cleanup while keeping the
boringssl-zigv0.6.4 pin aligned.
[0.4.5] - 2026-07-05
Fixed
- Fixed the in-tree WebTransport self-test completion race. The harness now
treats a post-close datagram echo as proof that the exchange flushed, keeps
the server-side session handle through same-batch close/data ordering, and
gives the server a short post-completion drain window before exit.
[0.4.4] - 2026-07-05
Fixed
- Raised the per-push
wt-interop-self-testtimeout so current CI runners
can finish compiling the WebTransport self-test binaries instead of
cancelling during install.
[0.4.3] - 2026-07-05
Changed
- Repointed to
quic-zigv0.7.2 andboringssl-zigv0.6.4. This keeps
the 0.7.x hardening line while carrying the Windows QNS build fixes,
BoringSSL Windows macro hygiene/no-asm fallback, and Winsock linking.
[0.4.2] - 2026-07-05
Changed
- Repointed to
quic-zigv0.7.1 andboringssl-zigv0.6.2. This keeps
the 0.7.0 transport hardening while avoiding CI failures from the
BoringSSL googlesource tarball endpoint and the stale boringssl-zig
consumer example build API.
[0.4.1] - 2026-07-05
Added
- Opt-in eager reclaim of peer-RESET streams. New
Config.reclaim_peer_reset_streams(defaultfalse). A peer RESET leaves a
bidirectional request/response stream half-closed — receive side terminal,
local send side still open — so itsStreamStatelingers in thestreams
map (bounded bymax_concurrent_peer_streams, but never released) until the
local side closes. When enabled, a peer RESET tears the local side down
immediately and the per-drain GC reclaims the entry the same pass the
stream_resetevent is surfaced. Deliberately narrow: peer FIN is not
reclaimed (a server legitimately keeps responding after the client's request
FIN), and referencing the stream id after itsstream_resetevent with this
on is a use-after-reclaim. - WebTransport initial flow-control SETTINGS (draft-ietf-webtrans-http3-15
§9.2).SETTINGS_WT_INITIAL_MAX_DATA/_STREAMS_UNI/_STREAMS_BIDI
are now emitted, parsed, and wired into per-session flow control end to
end — previously they existed only as unused constants. Set
ProductionOptions.wt_initial_max_data/wt_initial_max_streams_uni/
wt_initial_max_streams_bidi(or the correspondingSettingsfields) to
grant peers initial per-session credit, so a WebTransport session opens
with limits already in force instead of waiting for the first
WT_MAX_DATA/WT_MAX_STREAMScapsule. Each side's advertised values
become the receive-side limits it enforces; the peer's advertised values
gate its sends. Unset (null, the default) advertises nothing and
preserves the prior capsule-only behavior — no new enforcement unless you
opt in.
Changed
- Repointed the transport dependency to the tagged
quic-zigv0.7.0
hardening release and theboringssl-zigv0.6.1 tag, keeping the
cross-repo BoringSSL pin byte-for-byte identical while pulling in the
strict persisted 0-RTT formats, sanitizer propagation, and pinned
quic-go CI scaffolding.
Fixed
-
Real-network WebTransport interop client now drives the QUIC
handshake.interop/external_wt/client.zig's pump loop was
missing an explicitquic_zig.Connection.advance()call, so the
TLS state machine never produced a ClientHello andpoll()had
nothing to emit. Symptom:wt-interop-self-testand the third-party
wt-interopmatrix both timed out at phase 1 with
SettingsExchangeTimedOut. After this fix the self-test progresses
cleanly through phase 1 (SETTINGS), phase 2 (CONNECT), and phase 3
(datagram + uni stream + CLOSE_WT). With the quic-zig v0.6.1 transport
fix, the full WebTransport flow now completes against webtransport-go;
the remaining issue is a post-success harness clean-shutdown timeout.
Seedocs/wt-third-party-interop.md.
The third-party matrix step iscontinue-on-error: trueso it doesn't
block merges. -
Real-network HTTP/3 interop client now drives the QUIC handshake.
interop/external_h3/client.zighad the same gap as the WebTransport
client: its pump loop never calledquic_zig.Connection.advance()(so
no ClientHello was ever emitted) orh3.start()(so the control stream
and SETTINGS were never opened). Both interop clients also still used
Allocator.dupeZ, renamed todupeSentinelin the tracked Zig
toolchain. Fixed all three; the client now completes a full HTTP/3
request/response against the in-treecurl-h3server over loopback.
Security (hardening)
- Capped four adversarial-reachable session maps a hostile peer could
grow without bound:received_push_promises,request_priorities,
push_priorities, andwt_pending_sessions. NewConfigcaps
(max_tracked_priorities,max_tracked_push_promises,
max_pending_wt_sessions) are wired into the production presets
(1024/256/256). Priority floods drop the excess update (advisory,
RFC 9218 §7); push-promise and WebTransport-session floods close with
H3_EXCESSIVE_LOAD.
Changed (BREAKING)
Confignaming + type consistency (pre-1.0 cleanup).qpack_huffman→enable_qpack_huffmanandopen_qpack_streams→
enable_qpack_streams, aligning on/off toggles under theenable_
prefix used byenable_connect_protocol/enable_datagram/
enable_webtransport.Config.max_field_section_size(and theClient.Config/
Server.Config/ message-codec mirrors) is now?u64instead of
?usize, matching the wire type inSettings. On 64-bit targets this
is source-compatible for integer-literal callers.
Removed (BREAKING — post-deprecation cleanup)
-
finishSendremoved from six wrapper types. The v0.3
deprecation cycle is complete. Affected types — each kept its
canonicalfinishmethod:WebTransportClientStream,WebSocketClientStream,
ConnectUdpClientStreamWebTransportServerStream,WebSocketServerStream,
ConnectUdpServerStream
Migration: one-character rename in callers (
finishSend()→
finish()). -
WTStreamDirectionremoved. v0.3 made it an alias for
webtransport.StreamKind; v0.4 deletes the alias entirely. The
canonical type iswebtransport.StreamKind(re-exported as
WebTransportStreamKind). Internal session-machine signatures
migrated to the canonical name.
Added (test infrastructure)
-
30 new property-fuzz corpus seeds at
fuzz/corpus/wt-interleaved/21-..50-..(corpus is now 50 seeds,
was 20). Categories: multi-stream interleaving (5),
datagram bursts including pre-SETTINGS / post-CLOSE_WT (5), capsule
races (WT_MAX_DATA/WT_MAX_STREAMS/WT_DATA_BLOCKED
arriving close together) (5), DRAIN-then-activity (3), aborted
sequences (RESET at 5 lifecycle points) (5), extreme sizes (3),
borderline UTF-8 (2), plus 2 long mixed-op sequences. Zero crashes
in the harness sweep. -
CI workflow:
.github/workflows/wt-interop.ymlmatrix step now
carriescontinue-on-error: true. The per-push real-socket gate
(wt-interop-self-test.yml) remains hard-gating; third-party
interop is advisory until the gap doc's investigation lands. -
HTTP/3 interop self-test (
.github/workflows/h3-interop-self-test.ymlinterop/external_h3/self_test.sh): the HTTP/3 counterpart to the
WebTransport self-test. Brings up the in-treecurl-h3server on a real
UDP socket and drives it with the in-treeexternal-h3client — a full
handshake plus request/response using only http3-zig binaries (GET
/helloand POST/echo), gating the real-socket pump path the
in-process tests don't reach.
Performance / correctness (memory)
Session.streamsis now garbage-collected at the tail of every
drain. Previously the per-streamStreamStatemap was effectively
monotonic — a long-lived session that opens many streams accumulated
O(N) heap whether or not those streams had finished. New mechanism:
StreamState.locally_finishedflips whenfinishStream/
resetStreamruns, paired with the existingrecv_finished/
recv_reset_seenflags.Session.gcClosedStreamsremoves any
StreamState whoseisFullyClosed()returns true (peer-uni: receive
side closed; local-uni: locally_finished; bidi: both). Iteration
safety via fixed-size 128-id batch buffer; surplus rolls to the next
drain. Per-iteration cost in the long-running profile dropped from
≈ 2 755 → ≈ 2 239 bytes (≈ 19 % reduction).- Caveat (v0.4 follow-up): the residual ~2 239 B/iter lives in
the underlyingquic_zig.Connection.streamsmap which has no
cleanup of its own. A matching pass in quic-zig (or a public
compaction API we can call from here) is the cross-repo follow-up.
Documented indocs/memory-profile.md.
- Caveat (v0.4 follow-up): the residual ~2 239 B/iter lives in
Added (test infrastructure)
-
bench/wt_memory.zig+zig build mem-profile— long-running
WebTransport session profiler that runs N iterations of a fixed
unit of work (open uni stream + 256 B + finish + datagram round-trip- drain to quiescence) and reports
bytes_in_use/max_bytes_ever
at three checkpoints. Wraps a 0.16DebugAllocatorwith a custom
CountingAllocator. ReleaseSafe build keeps allocator safety on for
leak detection. Seedocs/memory-profile.md.
- drain to quiescence) and reports
-
bench/wt_load.zig+zig build wt-load— concurrent-session
load test exercising N parallel WebTransport sessions on one QUIC
connection. Establishes the dispatch routing is correct and surfaces
any hidden cross-session contention. See
docs/load-baseline.mdfor the baseline
numbers (M5 Max, ReleaseFast, 100 sessions: 7.89 ms median,
≈ 12.6 k sessions/sec). -
fuzz/wt_interleaved.zig+fuzz/wt_interleaved_main.zig—
property-based fuzz harness that interprets random bytes as a
sequence of WebTransport operations (open / write / finish / reset /
datagram / capsule) and runs them on an H3Pair. 20 hand-written
corpus seeds atfuzz/corpus/wt-interleaved/. Wired intobuild.zig
with the standard fuzz-target pattern. Zero panics surfaced on the
initial sweep — but the harness is the actual deliverable; the seeds
are the floor, not the ceiling.
Documentation
-
README: new
## Datagram sendsand## Stream lifecycleindex
sections (per V7 narrowing rec #2 and #3). Each is a short
decision-tree pointing at the canonical methods. -
Doc comments on
WebTransportClientStream.requestWriter()/
WebTransportServerStream.responseWriter()warning that
datagramCapsule/datagramContextCapsuleinvoked through the
underlying writer are NOT valid WebTransport sends (per V7 rec #6). -
Tracker doc clarification that
ResponseTracker/
RequestTracker/PushedResponseTrackercover the CONNECT-message
body only — they do not accumulate WebTransport substream data,
which surfaces viawebtransport_stream_dataevents (per V7 rec #4).
Added (correctness)
-
Session.observeWebTransportCapsuleis now tolerant of just-torn-down
sessions. A capsule that arrived on the wire BEFORE a close
(CLOSE_WEBTRANSPORT_SESSION or implicit FIN of CONNECT) often surfaces
to the application AFTER the session's local state has been
destroyed — a single drain pass emits the body bytes and the close
event together. CallingobserveCapsulefor a.nonesession now
returns silently rather thanError.UnknownWebTransportSession. The
capsule's value is lost (no flow state to fold it into) but the
application's drain loop doesn't crash mid-close. Regression:
WebTransport: CLOSE_WT capsule interleaved with WT_MAX_DATA. -
gateWebTransportStreamOpendistinguishes.nonefrom.pending.
Previously a null lookup inwebTransportFlowMutwas treated as a
silent no-op (allowing opens to silently succeed against a dead
session id). The gate now explicitly switches:.none→Error.UnknownWebTransportSession(caller bug — session
never existed or was torn down).pending→ allow, gating not applicable yet (bootstrap race —
application is opening streams before the session is confirmed).established→ apply per-session limit / drain checks.
Regression:
WebTransport: peer FIN of CONNECT while local mid-send on three streams.
Added (interop / wire format)
- Six previously-missing WebTransport wire constants from the
draft-15 audit (no behavioral change — applications now have names
for spec-defined values they may need to inspect):protocol.SettingId.wt_initial_max_data=0x2b61(re-exported as
webtransport.SettingId.wt_initial_max_data).protocol.SettingId.wt_initial_max_streams_uni=0x2b64.protocol.SettingId.wt_initial_max_streams_bidi=0x2b65.webtransport.flow_control_error_code=0x045d4487.webtransport.alpn_error_code=0x0817b3dd.webtransport.requirements_not_met_code=0x212c0d48.
Audit doc:design/error-code-audit-v0.2.md.
Added (test coverage)
-
WebTransport multiplexing tests at
tests/integration/webtransport_multiplexing.zig.
Four scenarios verify N concurrent WT sessions on one QUIC connection
route correctly: 5 sessions × 10 uni streams (no cross-session bleed),
3 sessions where one DRAINs while others stay active, 3 sessions
with independent datagram delivery, per-session
WT_MAX_STREAMS_UNIenforcement. -
WebTransport race / interleaving tests at
tests/integration/webtransport_races.zig.
Four scenarios cover ordering corner cases: DRAIN in the same drain
batch as 50 peer-opened streams; CLOSE_WT interleaved with WT_MAX_DATA
(regression for the tolerance fix above); peer FIN of CONNECT during
local mid-send on three streams (regression for the gate fix above);
peer RESET of CONNECT while buffered streams are pending under the
.bufferpolicy. -
Eleven adversarial WebTransport fuzz seeds added under
fuzz/corpus/webtransport/and
fuzz/corpus/webtransport-session/.
Multi-frame sequences (CLOSE-then-MAX_DATA, DRAIN-twice, MAX_DATA
regression, BLOCKED-then-grant) plus single-frame malformed inputs
(oversized close reason, truncated reason, overlong varint, invalid
UTF-8 reason, u62-max bidi/uni limits). Zero panics surfaced.
Documentation
-
docs/webtransport-tour.md—
application-author walkthrough (683 lines) covering session
establishment, streams, datagrams, flow control, closing, draining,
error handling, and seven common pitfalls. Pulls real code from the
integration tests. -
docs/perf-baseline.md— three
performance numbers measured in-process (loopback shim, no sockets):
WT session establishment, datagram round-trip, 1 KiB stream
round-trip. Apple M5 Max, ReleaseFast, Zig 0.16.0. Published as a
baseline for regression tracking, not optimization targets.
Run viazig build bench -Doptimize=ReleaseFast. -
design/error-code-audit-v0.2.md—
exhaustive comparison of our wire constants vs.
draft-ietf-webtrans-http3-15 §9. 17 items audited; 6 drift items
(now closed by the additions above); zero codepoint mismatches on
the values we emit. -
design/api-narrowing-proposal.md—
audit of duplicate / redundant API paths with v0.3 / v0.4
recommendations. Identifies five clusters (datagram sends, lifecycle
verbs, stream-open paths, trackers vs raw events, re-exports).
Deprecated
-
finishSendonWebTransportClientStream,
WebTransportServerStream,WebSocketClientStream,
WebSocketServerStream,ConnectUdpClientStream,
ConnectUdpServerStreamis deprecated. Each type now has a
canonicalfinishmethod with identical wire effect;finishSend
remains as an alias and will be removed in v0.4. Migration is a
one-character rename. Rationale:
design/api-narrowing-proposal.md. -
WTStreamDirectionis deprecated, now an alias for
webtransport.StreamKind. Will be removed in v0.4. Migration:
usewebtransport.StreamKind(re-exported as
WebTransportStreamKind) directly. Note:StreamKind's order is
{ uni, bidi }, reversed from the deprecated alias's
{ bidi, uni }. No call site in this repo serialized the enum via
@intFromEnum, so the rename is source-compatible at the switch /
constant-construction level.
Performance
zig build benchstep runs the new in-process WebTransport
benchmark and prints per-operation p50 / p99 / mean / max.
[0.2.0]
Tagged at commit 2ce728f. This section covers the 0.1.0 and 0.2.0
releases together; see git log v0.1.0..v0.2.0 for precise per-tag
attribution.
Added
SECURITY.mdwith private-disclosure address and 90-day SLA.CONTRIBUTING.mdwith build / test / interop instructions.LICENSE(Apache 2.0, matching sister project quic-zig).
Changed (BREAKING)
-
closeWebTransportSessionis no longer public. The function only
ever tore down local registry state — it never sent
CLOSE_WEBTRANSPORT_SESSIONon the wire — so exposing it as a public
verb invited misuse. Renamed to privateendWebTransportSession.
Application close path is unchanged: call
WebTransportClientStream.close(code, reason)/
WebTransportServerStream.close(code, reason)to send the capsule, or
finishSend()for an implicit close (now also tears down local
registry state — see "Fixed" below). -
WTSessionFlowStateis no longer exported fromhttp3_zig.*. The
mutable per-session flow-accounting struct is an implementation
detail; applications already observe a read-only snapshot via
WebTransportClientStream.flowState()/
WebTransportServerStream.flowState()returning
?WTSessionFlowSnapshot. The snapshot remains exported. -
WebTransport wire-format pin: draft-13 → draft-15.
http3-zig now tracksdraft-ietf-webtrans-http3-15(July 2025
revision). The visible knob: SETTINGS bootstrap moved from the
numericSETTINGS_WT_MAX_SESSIONS = 0x14e9cd29(draft-13) to the
booleanSETTINGS_WT_ENABLED = 0x2c7cf000(draft-15).Settings.wt_max_sessions: ?u64→Settings.wt_enabled: bool.ProductionOptions.wt_max_sessionsremoved; the numeric
session-count limit is no longer in the spec.- Server-side N+1 session-rejection enforcement removed
(applications can still bound concurrent sessions in
Server.acceptWebTransport). - Dual-peer interop in CI: webtransport-go (draft-15 via master) +
pywebtransport v0.17.1 (a Python facade shipping draft-15).
-
Removed
recordPeerDataReceivedfrom the public WT API. The
session auto-bumpspeer_data_receivedas it surfaces
webtransport_stream_dataevents; calling the legacy public
helper double-counted and synthesized spurious
webtransport_flow_violatedevents. Affected:
WebTransportClientStream.recordPeerDataReceived,
WebTransportServerStream.recordPeerDataReceived,
Session.webTransportRecordDataReceived. -
buildRequestFieldsnow omits:schemeand:pathfor classic
CONNECT (:method = "CONNECT", no:protocol) per
RFC 9114 §4.4 ¶3. Extended CONNECT (with:protocol) is
unchanged. Migration: callers that were passing:scheme = "https":path = "/"for classic CONNECT no longer need to clear them
manually.
Added (correctness)
-
HTTP/3 message validation hardening (RFC 9114 §4 + RFC 9110 §6.5):
content-lengthis parsed and cross-checked against decoded
body length; mismatched / duplicate / non-decimal values are
rejected asH3_MESSAGE_ERROR. Closes a header-smuggling
surface.- Empty
:authorityis now rejected (MalformedAuthority). - Classic CONNECT (
:method = "CONNECT"without:protocol) is
validated per spec::schemeand:pathMUST be omitted, and
:authorityMUST be present and non-empty. - Trailers reject
content-length,host,te, plus the
request-modifier set (cache-control,expect,
max-forwards,pragma,range, auth-related).
-
WebTransport session-state hardening:
observeFinno longer emits a phantom
webtransport_stream_finishedevent for a stream whose
_openedevent was never produced (peer FINs after sending
only the type byte but before the Session ID lands).- Per-session
received_drainflag is set when the peer's
DRAIN_WEBTRANSPORT_SESSIONcapsule arrives; further
openWebTransportUniStream/openWebTransportBidiStream
calls return the newerror.WebTransportSessionDraining
(draft-15 §5.5). Client.startWebTransportandServer.acceptWebTransport
eagerly check that the peer advertisedSETTINGS_WT_ENABLEDH3_DATAGRAM+ENABLE_CONNECT_PROTOCOL; missing settings
surface aserror.PeerDidNotEnableWebTransport/
error.PeerSettingsNotReceivedbefore the request goes on
the wire (draft-15 §9.2).
-
Resource-exhaustion caps (defense-in-depth):
Config.max_concurrent_peer_streams(production default 1024)
bounds the size ofSession.streams. Peer-opened streams past
the cap are rejected withSTOP_SENDING(H3_REQUEST_REJECTED)
and the newerror.PeerStreamLimitExceeded. QUIC's MAX_STREAMS
already provides per-direction caps; this is a session-layer
knob covering the case where MAX_STREAMS is generous but
HTTP/3 state shouldn't grow proportionally.Config.wt_max_buffered_bytes_per_stream(production default
64 KiB) bounds bytes a single peer-opened WebTransport stream
holds instate.rxwhile waiting for its session to be
confirmed underBufferedStreamPolicy.buffer. Streams that
overflow get reset with
WEBTRANSPORT_BUFFERED_STREAM_REJECTED.peer_data_receivedaccumulation now uses saturating addition
so a long-lived flooded session can't wrap u64 and silently
pass the receive-side flow-control gate.
-
HTTP Datagram capsule path (RFC 9297 §3.4) now gates on peer
SETTINGS_H3_DATAGRAM. Previously
sendRequestDatagramCapsule/sendResponseDatagramCapsule(and the
context variants) would emit a DATAGRAM-typed capsule even if the peer
hadn't advertised h3_datagram; the QUIC-DATAGRAM path
(sendDatagram) was already gated. Both paths now share the same
MissingSettings/DatagramNotEnabledsemantics. -
Client.Config.production/Server.Config.productionpresets —
one-line opt-in to bounded resource caps (max_concurrent_peer_streams = 256,
max_field_section_size = 16 KiB, wt_max_buffered_bytes_per_stream = 16 KiB,
buffered_stream_policy = .reject, max_event_payload_bytes_per_drain = 4 MiB,
max_events_per_drain = 512). Defaults are unchanged; the preset is a
snapshot, not a new field.
Fixed
-
Test fixtures invoke
markPathValidatedon the synthetic
handshake.tests/integration/_fixtures.zigand
tests/conformance/_h3_fixture.zigshuttle TLS data through an
in-process outbox→inbox shim instead of real datagrams; that
bypasses RFC 9000 §8.1's anti-amplification budget validation.
Fixture now flips the bit explicitly soSession.closecan flush
CONNECTION_CLOSE in the server-initiates-close case (was failing
thelifecycle_closeintegration test). -
Local FIN / RESET of a WT CONNECT stream now tears down the local
session registry (draft-ietf-webtrans-http3-15 §5.4). Previously
Session.finishStream/Session.resetStreamonly sent the QUIC
frame; the local-sidewt_established_sessionsentry leaked. The
receive side (observeFin,observeReset) already tore down the
peer's view; this restores symmetry. Covered by the new test
WebTransport: peer FINs CONNECT control stream without CLOSE_WEBTRANSPORT_SESSION cleanly closes session. -
PRIORITY_UPDATE-Push for an unpromised push id now buffers
instead of closing the connection (RFC 9218 §7.2 — receiver SHOULD
buffer the priority signal and apply it when the push is later
promised). PreviouslyvalidatePriorityPushIdrejected
push_id >= next_push_idwithH3_ID_ERROR. The peer's
MAX_PUSH_IDbound is still enforced — only the timing race is
relaxed. Companion to the existing buffering for unopened request
streams. New test:
PRIORITY_UPDATE for unpromised push id is buffered, not rejected [RFC9218 §7.2].
Performance
-
Drain scratch buffers reused across
Session.draincalls. The
per-drainread_chunk_sizeandmax_datagram_payload_size
scratch allocations are now stored on the session and grown
monotonically; previously each drain paid alloc + free.
Released inSession.deinit. -
Session.freeEvent(event)helper binds the right allocator for
drained events implicitly. Equivalent toevent.deinit(session.allocator),
but the caller no longer has to remember which allocator pairs with
the event bytes (cloned out of the session's allocator, not the
events list's allocator).
Documentation
-
Allocator contract documented at the top of
src/root.zig. Six-bullet section explaining
Session.initallocator lifetime,Client/Server.initfacade
semantics, Event byte ownership transfer on drain, the arena-with-reset
warning for QPACK tables and rx buffers,Session.deinitinvariants,
and the independent allocator held by trackers. -
Event-variant role audit. Each variant of
Session.Eventnow
carries aRole: client | server | bothdoc tag, plus a 30-line
summary block listing the role split (3 client-only, 2 server-only,
20 shared). Makes a futureClientEvent/ServerEventAPI split a
mechanical refactor. -
Stream lifecycle verbs documented in
src/session.zig.finishStream(clean FIN,
no error code),resetStream/resetRequest/resetResponse
(outbound abort, RESET_STREAM with an error code),cancelRequest/
rejectRequest/stopSending(inbound abort, STOP_SENDING) — each
has a doc comment explaining the QUIC frame it sends and when to use
it. Same documentation cascaded to theClient/Servertop-level
wrappers. -
Session.Errorvariants documented. The 28 session-specific
error variants now each carry a doc comment explaining when they
fire and (where applicable) the spec section that defines the
underlying behaviour.
Observability
- QPACK trace events. Four new
observability.TraceEventNamevariants emitted through the existing
Hooksinfrastructure:qpack_dynamic_insert— fires after a successful dynamic-table
append inDynamicTable.insertOwned.qpack_dynamic_evict— fires per evicted entry inside
DynamicTable.evictToCapacity.qpack_section_blocked— fires inDecoderState.beginFieldSection
when a header section is held pending dynamic-table insertions.qpack_section_unblocked— fires when a previously-blocked
section's required insert count is satisfied.
Each event carriesstream_id/value/bytes/countas
appropriate; matchingMetricscounters increment on emit.
Tooling / infra
-
CI hardening:
timeout-minutesset on every job in
.github/workflows/test.ymland
.github/workflows/fuzz.yml. The test
matrix now coversDebug×ReleaseSafe×{ubuntu-latest, ubuntu-24.04-arm, macos-latest}. The fuzz workflow uploads crash
artifacts on failure and runs on macOS as well as Linux. -
.github/workflows/release.ymltriggers onv*.*.*tag pushes,
validates the tag matchesbuild.zig.zon's.version, runs the
full test suite, and publishes a GitHub release with autogenerated
notes plusCHANGELOG.mdbody. -
.github/workflows/fuzz-nightly.ymlruns the corpus walker for
30 minutes nightly + on demand, uploading crashes on failure. -
WebTransport interop runs on every push — both Go
(webtransport-go) and Python (pywebtransport) peers, with
continue-on-error: trueremoved so peer failures gate merges. -
New conformance test
SETTINGS frame with zero settings is accepted [RFC9114 §7.2.4 ¶3](peer-sends-empty-SETTINGS). -
Stricter Priority field-value parsing (RFC 8941 §3.1.2 +
RFC 9218 §4): dictionary keys outside the lowercase-token grammar
and internal whitespace inside member-values now produce
Error.InvalidParameterrather than slipping through as
silently-ignored unknowns. Two new tests undertests/conformance/rfc9218_priority.zig. -
Dual-peer WebTransport interop in
.github/workflows/wt-interop.yml:
webtransport-go + pywebtransport, both pinned, both brought up on
every scheduled run. -
In-tree WT echo server at
interop/external_wt/server.zig- per-push real-socket gate in
.github/workflows/wt-interop-self-test.yml.
- per-push real-socket gate in
-
Seeded fuzz corpus at
fuzz/corpus/(105 hand-curated inputs across 16
codec targets); regenerated viazig build seed-fuzz-corpus,
walked per-push by thefuzz
workflow.
Full Changelog: v0.4.7...v0.4.8