Skip to content

colibri 0.14.0

Choose a tag to compare

@c4milo c4milo released this 06 Nov 03:08
· 96 commits to main since this release

colibri 0.14.0 adds colibri, a module that binds the sockets and runs the loop: a program gives colibri.Server a host, a port, a PEM identity and a server.router table, and calls run. It serves h11 and h2 over TCP and h3 over QUIC on the UDP socket of the same port, on Rotor's own loop. The client keeps a response's headers and trailers whole, and an h3 client no longer stops on a status that RFC 9110 does not name. It pins stdx 51c91698, chapulin 965668d2 and Rotor 4f818b7.

Breaking changes

  • client.Response.wanted, Response.values, Response.values_len and client.WantedHeader are gone (decision 152). A response holds its header section in Response.headers, a client.Headers, and its trailer section in Response.trailers, a client.Trailers, each over a buffer the program gives. A program reads a value with response.headers.get("content-type") and every line with iterator(). An empty buffer drops its section, and a section that does not fit ends the exchange too_large.
  • Rotor is no longer a lazy package: the library's colibri module imports it, so a project that depends on colibri fetches Rotor too.
  • An h3 request whose stream ended with its head now reports request.end with that head, and no empty body follows (decision 116 as amended). A program that reads h3 events itself reads request.end; server.router already did.

What's new

  • colibri.Server (decision 150, design §8 step 37):
    • init(config) reads the identity, given as PEM text in colibri.Identity { chain, key }, and binds. The program reads its own files and may wipe the key text after init.
    • port() returns the bound port, so a program may ask for port 0.
    • run(router, context) serves on one Rotor loop on the calling thread until shutdown(), which any thread may call.
    • deinit() closes what init opened and wipes the private key and the cookie key.
    • With an identity, the server opens UDP on the same port for h3, with UDP_GRO and UDP_SEGMENT on Linux, and its TLS connections advertise h3 there (Alt-Svc).
    • With no source of randomness given, it draws from the operating system's entropy.
    • examples/colibri_server.zig serves a route table on 127.0.0.1 port 0.
  • An h3 client reads a response's trailers, and h11 and h2 clients read them too, into Response.trailers.

Fixes

  • An h3 client converted a server's status to an enum of RFC 9110's codes, so a 429 (RFC 6585) or a 599 stopped it on an assertion. It now compares the number.
  • h11 kept a chunked message's trailer section into the next message, so a message without chunked coding that followed one with trailers reported them again, in the client and the server. h11 now clears it at each head.

Speed

  • h3: a request whose stream ended with its head takes one event, not three (design §8 step 29d.15), and a field section that inserts nothing into the QPACK table takes no encoder stream room (step 29d.13).

Checked

tools/ci.sh passed on 8ae5faf1, with Zig 0.16.0 on macOS arm64. As the owner ruled on 2026-11-05, the run left out the TLA+ models and the seven trace checks, which a nightly job will run.