Repository navigation
colibri 0.5.0: the server over QUIC, and content codings
colibri 0.5.0 adds the server over QUIC and content codings. server.Endpoint holds QUIC connections behind the caller's UDP socket, and server.QuicConnection serves h3 with the same calls and events as the TCP Connection. A TCP connection over TLS advertises h3 with Alt-Svc when its configuration names a port. The server codes a response in gzip or deflate when its request accepts one and the caller marks the response codable, and the client offers both codings and decodes them. The client retires a QUIC connection near its idle timeout, and keeps one with an exchange outstanding open.
Breaking
server:respond(id, .{ .status, .fields, .end })andwrite_body(id, .{ .octets, .end })take the response's head and content as structs,server.Responseandserver.Content(3542f0f).server:Eventgainsdone, which says a response's memory is the caller's again (decision 103,0fb5878).Protocolgainsh3(c533709).h2:Eventgainsalt_svc, an ALTSVC frame a client read (RFC 7838 §4,9796e95). Each write call asserts that the connection's preface went out first, as h2's contract always asked: callwrite_pendingbefore writing a request or a response (e3126a9).client:Outcome.malformedalso covers content whose coding is corrupt or cut short (23c454b).qlog:Log.inittakes the CPU features stdx's writer uses,qlog.Features.detect()or.target()(6b7e03d).
To move from 0.4.0:
- Pass
respondandwrite_bodya struct. - Handle
donein a server's event switch, and release the response's memory there. - Handle
alt_svcin an h2 event switch; only a client receives it. - Call
h2.Connection.write_pendingbefore writing any frame of your own. - Pass the CPU features to
qlog.Log.init.
Added
server(design §8 step 17b):Endpoint, orEndpointOf(connections_max, receive_capacity), routes each datagram by its first packet's Destination Connection ID (RFC 9000 §5.2) and starts a connection from a client's first Initial. It answers Version Negotiation and Retry itself.sendwrites each datagram it owes with the address it goes to, andendedhands back each connection that is over.QuicConnectionserves h3.write_bodysends the caller's octets from where they are, and a request isdoneonce the peer acknowledged every octet of its response.EndpointConfig.logs, a log provider the endpoint asks for each connection's qlog log, and hands each log back once its connection is over (decision 102 as amended,7cfa88c).Config.h3_alternativenames a UDP port that each connection over TLS advertises: an Alt-Svc line on each final h11 response, and one ALTSVC frame per h2 connection (RFC 7838 §3, §4).
- Content codings (design §8 step 17e, decision 101):
server:Config.codingsandConfig.encoders, and the same inQuicConfig, name the codings a server applies, in its order of preference, and the pool of encoders,EncoderPool(count, level)orDefaultEncoderPool. A final response markedResponse.codablegoes out in the coding its request's Accept-Encoding accepts with the highest weight. It gains Content-Encoding and loses Content-Length, and a strong ETag becomes weak. A marked response Accept-Encoding could choose carriesVary: accept-encoding, coded or not. With every encoder taken, the response goes out uncoded.client:Config.codingsandConfig.decoders, and the same inQuicConfig, name the codings a client offers and the pool of decoders,DecoderPool(count)orDefaultDecoderPool. The client offers them in Accept-Encoding, each after the first a tenth lighter, takes a decoder as it writes the request, decodes a response in a coding it offered into the exchange's body, and reports the coding inHttpExchange.coding. A response coded twice, in a coding it did not offer, or in a 206 goes on as it arrived.server.Codingandclient.Codingname the codings,http.content_codingreads Accept-Encoding's weights (RFC 9110 §12.4.2, §12.5.3), andh11.coding.reservetakes a decoder before a message's coding is known.- colibri's package exports stdx's
codec,gzip,zlib,zstdandbrotli, so a dependent codes request content with the stdx the library pins.
client(step 17g): a QUIC connection that has held no exchange until its idle deadline less a margin takes no new exchange and closes, and the channel's next exchange opens another (RFC 9114 §5.1). One holding an exchange sends a PING at that instant instead (RFC 9000 §10.1.2). The margin is one PTO, at least 1 s and at most half the timeout. The client also learns h3 from an ALTSVC frame over h2.quic:connection_idlegives the idle deadline and the effective timeout, andowe_keep_alivehas the next 1-RTT packet elicit an acknowledgment.qlog: each packet event names the tuple it went to or came from, andquic:tuple_assignednumbers each new address (84cd3ad).h2: the ALTSVC frame, written withConnection.write_alt_svcand read by a client.
Dependencies
- stdx is pinned at
8c2a3b8, where 0.4.0 pinnedeb0f0c7. chapulin's pin is unchanged.
Fixed
server: an h2 connection that read a request with the client's preface, over TLS in the flight of the client's Finished, answered it before its own SETTINGS, which RFC 9113 §3.4 requires first, and colibri's client failed the connection (e3126a9).quic: a probe above the Initial level repeats its space's latest ACK frame, so a peer whose acknowledgments were all lost learns which packets arrived (RFC 9000 §13.2,1a4bcda).server: a client's close no longer ends a QUIC connection as a failure (87fff2f).
Checked against
CI ran tools/ci.sh on bb9263e, 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.
- Content codings over h11 and h2, in cleartext and over TLS: curl with
--compressedand Go's client read the server's gzip, and the client decoded gzip from Go's server and h2o's, octet for octet. - 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,325 tests in Debug and in ReleaseSafe. The tests also passed on Linux and macOS arm64.
The HTTP Garden fed its 72 streams to colibri's server and to 35 other origins on bb9263e, and compared every one.
The QUIC Interop Runner ran on bb9263e. colibri passed every case against quic-go, ngtcp2, neqo, quinn and itself, as the server and as the client, and quic-go ran no ecn case. Every qlog file colibri wrote, in 168 test cases, passed tools/qlog_check.py.
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.5.0