Repository navigation
colibri 0.4.0: server and client modules, and a Channel that chooses h3 or TCP
colibri 0.4.0 adds the server and client modules. Each answers or sends HTTP requests behind one set of calls for h11 and h2, and the client also carries them over h3 on QUIC, choosing QUIC or TCP for each server. QUIC and h3 write qlog when the caller asks.
Breaking
h3: each call that reads or writes a frame takes the instant of the call,now_ns, which its qlog events carry:start,receive,shutdown,write_request,write_responseandwrite_trailers.write_data_headeris now a method ofh3.Connectionthat takes the stream's id too (3028902).quic:connection_send.pacing_deadline_nsis gone.connection_timer.nextstill names the pacer's instant among the connection's deadlines (d5ba436).qlog, for a caller that pinned an untagged commit beforeabdad55, such asa5a97d2, and writes its own events:qlog.Jsonis gone,qlog.jsonis stdx's JSON module, and each event'swritetakes a*qlog.TextWriter.qlog.Logis unchanged, and 0.3.0 had noqlog.
To move from 0.3.0, pass h3 the instant you pass quic, call write_data_header on the connection with the stream's id, and read the pacer's instant from connection_timer.next.
Added
server(design §8 step 17a):Connectionanswers h11 and h2 requests, the version chosen by ALPN over TLS and named in cleartext.receivereports each request's head, content, trailers and cancellation; the caller answers withrespond,write_bodyandwrite_trailers.client(steps 17c and 17d):Connectionsends h11 and h2 requests over TCP, with TLS through chapulin. The caller places each request and its response's memory in anHttpExchange, and gets onefinishedevent per exchange.QuicConnectioncarries the same exchanges over h3 on QUIC.Channelcarries exchanges to one server over a QUIC and a TCP connection it chooses between. It tries QUIC first when h3 is known or allowed, and opens TCP when QUIC fails or a fallback delay passes. The caller passes DNS's answers as values, and learns h3 from a TCP response's Alt-Svc for its next channel.- The caller places the QUIC receive pool, sized with
ReceivePool(capacity), and every window the client advertises follows its capacity. - An exchange can send its path and field lines never-indexed (
28d3736).
qlog: QUIC's packets, frames, states, losses and metrics, and h3's frames and streams, as JSON-SEQ records in the caller's buffer (decision 102). A connection logs only when its caller passes a log.tools/qlog_to_qvis.pyrewrites a file into the form qvis reads.
Fixed
- h2: a server's DATA before its final response is refused (RFC 9113 §8.1), and so is END_STREAM on an interim response. Frames above a GOAWAY colibri sent are ignored (RFC 9113 §6.8). A field block that finds no room leaves nothing behind (
3f6e609). - QUIC: a second handshake probe repeats the first one's CRYPTO octets, and a closing connection arms no acknowledgment or path deadline, which came due at every instant.
Checked against
CI ran tools/ci.sh on fd00073, on Ubuntu 24.04, and every section passed:
- h2spec 2.6.0: 144 passed in cleartext and over TLS, and 2 skipped by name, which test an RFC 7540 rule RFC 9113 dropped. h3spec 0.1.13: 49 examples, 0 failures.
- The client and the server over h11 and h2 against Go 1.27.1, nghttpd 1.52.0, h2o 2.2.5, curl 7.88.1 and nghttp, in cleartext and over TLS.
- The client's
Channelfetched over h3 from aioquic 1.3.0 and quic-go9d085cc, and over h2 from Go's server with no UDP. - aioquic in both directions over hq-interop and h3, QPACK against ls-qpack, the TLA+ models with the h2, h3 and client traces, the Lean proofs, and 2,184 tests in Debug and in ReleaseSafe.
The QUIC Interop Runner ran on 2026-09-28 at a5a97d2, whose endpoint this release leaves as it was. colibri passed every case against quic-go, ngtcp2, neqo and quinn in both directions. Against itself it failed rebind-port alone.
docs/design.md §8 records each check: what ran, on what machine, and what it printed.
Install
zig fetch --save git+https://github.com/c4milo/colibri#v0.4.0