quiche-h3
⚠️ Experimental / work in progress. This crate is pre-1.0 and under active development. The API is unstable and may change without notice, and it is not yet recommended for production use.
An h3::quic transport-adapter bridge that runs hyperium
h3 (HTTP/3) over Cloudflare
quiche, driven asynchronously by
tokio-quiche.
It exposes a standalone acceptor/connector whose accept() / connect() yield a
Connection that implements h3::quic::Connection<Bytes>, so it plugs directly
into h3::client / h3::server. It is usable as the quiche backend for
tonic-h3 (gRPC over HTTP/3).
Design
The full design lives in
docs/design/quiche-h3-bridge.md. In short:
- A single-task
QuicheDriverworker (tokio_quiche::ApplicationOverQuic) is the sole toucher ofquiche::Connection. It owns all cross-task state and neverawaits inside its synchronous read/write callbacks (try_send/try_reserve). - The front end implements the
h3::quictraits over bounded byte/accept channels, an unbounded control channel, and race-free sticky terminal cells, so a full data queue can never hide a stream/connection terminal, and every terminal reason is typed data carried over the channel — channel closure is never itself a semantic signal. - Error mapping is designed from quiche's synchronous error surface (not inherited
from a lossy adapter): peer
RESET_STREAM/STOP_SENDINGcodes, timeouts, and local vs. peer connection closes are all distinguished.
Pinned build
Depends on semver ranges (tokio-quiche 0.19, which pulls quiche 0.29; h3 0.0.8)
and pins exact builds via the committed Cargo.lock. A CI compatibility test
(tests/ci_compat.rs) constructs one value of every mapped h3 error variant and
names every load-bearing quiche/tokio-quiche API, so a minor upstream bump that
reshapes the surface fails the build rather than silently mismapping.
Usage
Server:
use ;
# async
Graceful shutdown (server): obtain a cloneable H3QuicheEndpoint from the
acceptor before serving, then close and wait for idle. accept() stops
yielding new connections once close() is observed and returns None once the
pending handshakes drain (already-established connections keep their own workers,
which are torn down by the broadcast close). Await the serve task to completion
to drop the acceptor, then drive wait_idle() from the retained handle.
use ;
# async
Client:
use ;
# async
tokio_quiche and quiche are re-exported (quiche_h3::tokio_quiche,
quiche_h3::quiche) so downstreams can build QuicSettings, TLS material, and
Hooks without a separate dependency.
Known limitation (§5.5)
At zero connection-level send capacity, a peer that opens a writable-only
bidi stream (e.g. via STOP_SENDING) is undiscoverable through any public
quiche 0.29 API — quiche's tx_cap == 0 guard precedes the stopped-stream
branch. This pathological case is an explicitly documented gap in the otherwise
drop-in adapter contract, pending an upstream stream-enumeration API.
Features
tracing— structured instrumentation.gcongestion/rpk— pass-through toggles fortokio-quiche.
Testing
The ignored suite includes a real end-to-end HTTP/3 GET round-trip
(tests/h3_e2e.rs) through h3::client ↔ h3::server over the bridge.
License
MIT.