Repository navigation
Releases
colibri 0.14.1
colibri 0.14.1 fixes colibri.Server over h3. It pins stdx 51c91698, chapulin v0.3.0 (4da5b220) and Rotor v0.9.0 (5a5d7d72).
Fixes
colibri.Serverread the ECN codepoint of each datagram but did not turn ECN on in its endpoint, so the first datagram marked ECT(0) or ECT(1) stopped the server on an assertion (RFC 9000 §13.4, decision 68).Server.initnow turns it on, and a test sends an ECT(0) Initial.- On Linux arm64, the
colibritests stopped inThread.spawn: the test server lived in thread-local storage, and glibc puts that block on each thread's stack. The test server is now a plain global.
Checked
tools/ci.sh passed on 393dd94f, with Zig 0.16.0 on macOS arm64, without the TLA+ models and trace checks, which the nightly job runs. The colibri tests passed on Ubuntu 26.04 arm64, 32 of 32.
colibri 0.14.0
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_lenandclient.WantedHeaderare gone (decision 152). A response holds its header section inResponse.headers, aclient.Headers, and its trailer section inResponse.trailers, aclient.Trailers, each over a buffer the program gives. A program reads a value withresponse.headers.get("content-type")and every line withiterator(). An empty buffer drops its section, and a section that does not fit ends the exchangetoo_large.- Rotor is no longer a lazy package: the library's
colibrimodule imports it, so a project that depends on colibri fetches Rotor too. - An h3 request whose stream ended with its head now reports
request.endwith that head, and no emptybodyfollows (decision 116 as amended). A program that reads h3 events itself readsrequest.end;server.routeralready did.
What's new
colibri.Server(decision 150, design §8 step 37):init(config)reads the identity, given as PEM text incolibri.Identity { chain, key }, and binds. The program reads its own files and may wipe the key text afterinit.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 untilshutdown(), which any thread may call.deinit()closes whatinitopened 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.zigserves 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.
colibri 0.13.0
colibri 0.13.0 lets a QUIC connection resume its congestion state by RFC 9959's Careful Resume, and adds RFC 9002's NewReno as a third congestion controller. It pins stdx 51c91698, which carries stdx's Zstandard encoder, so a program that links colibri can encode zstd with the same stdx. The h3 server spends 15% fewer instructions a request. It pins chapulin 965668d2.
Breaking changes
- stdx's
platform.Cpugainsavx2andvaes. A program that writes aplatform.Cpuliteral, for example in atls.Cpu, must name both. A program that passes the valueprobe()returns changes nothing. - The QUIC structs grow:
quic.Connectionfrom 208,304 to 208,456 bytes, andserver.Endpointat its default capacity from 52,433,296 to 52,448,528 bytes. docs/performance.md lists each size.
What's new
- Careful Resume (RFC 9959, decision 149). A server or a client gives a
ResumeProviderwithtake,putanddrop, and a connection starts from the window and round-trip time saved for its peer. It runs under CUBIC, NewReno and BBRv3 (Appendix C).ResumeStateis a versioned structure of 32 bytes, andResumeBoundslimits what a connection takes. examples/server_resume.zig shows it. - NewReno (RFC 9002 §7, decision 133 as amended) is a third value of
quic.CongestionController. BBRv3 stays the default. - The bulk run of
bench/run.shmeasures NewReno, and can keep each colibri connection's qlog.
Planned
These are ruled and documented, not built:
- Decision 150:
serve, which binds, reads its files and runs the loop through astd.Io. - Decision 151: JSON in
serverthrough stdx'sjsonmodule. - Decision 152:
client.Headersandclient.TrailersreplaceWantedHeader,wantedandvalues, and a client can send trailers. This breaks dependents' code in a later release.
Speed
The judge counted the h3 server's instructions over steps 29d.9 to 29d.12 (design §8):
- h3-many fell from 42,280 to 35,552 instructions a request.
- Each step won on h3-many, and no h2 input lost.
Checked
tools/ci.sh passed all 39 sections on a62c4eb9, with Zig 0.16.0 on macOS arm64.
colibri 0.12.0
colibri 0.12.0 makes the server prefer the CNSA 2.0 algorithms: ML-KEM-1024, AES-256-GCM and ML-DSA-87 (#115). It gives every server identity one shape, and RSA-PSS returns in that shape. The h3 server spends 38% fewer instructions a request. It pins chapulin 965668d2 and stdx 5fd03aa.
Breaking changes
- A server identity has one shape for every key (decision 147).
tls.Key,tls.Identity,tls.SignatureSchemeandtls.pem.keyreplacetls.P256Keyand the names for each algorithm.tls.Server.identitiestakes a list oftls.Identity, at most one for each scheme.server.StartErroraddsIdentityRepeated,IdentityUnfilledandIdentityUnavailable. client.ConnectionPool.canceltakesnow_ns(decision 146). The instant starts the drop of an h11 response that the caller left.- Each TLS session is larger by about 300 bytes:
tls.record.Servergrows from 49,160 to 49,464 bytes,tls.quic.Serverfrom 100,736 to 101,080,tls.record.Clientfrom 49,800 to 50,104, andtls.quic.Clientfrom 101,304 to 101,608.server.Endpointat its default capacity grows from 52,422,080 to 52,433,296 bytes. docs/performance.md lists each size.
What's new
- The server selects ML-KEM-1024 when a client lists it, and sends a HelloRetryRequest for it when the client sent only an X25519MLKEM768 share.
- The server prefers TLS_AES_256_GCM_SHA384 when a client lists it.
- The server signs with ML-DSA-87 (draft-ietf-tls-mldsa-05, RFC 9881) when a client lists it, and
tls.pem.keyreads an ML-DSA-87 key. tls.pem.keyreads an RSA key in PKCS #8, and the server signs withrsa_pss_rsae_sha256when a client offers neither ML-DSA-87 nor ECDSA P-256. chapulin signs once with the key when it loads, so a key that does not match the leaf is refused there.client.Timeoutsbounds the drop of an h11 response that the caller left: 256 KiB and 50 ms, and no drop when the response closes the connection (decision 146).tools/tls_openssl.shchecks each choice with OpenSSL's s_client over TCP and QUIC.
Fixes
- chapulin
965668d2wipes the stack below each private call of its 64-bit RSA signer and each x86-64 AES-GCM seal and open.
Speed
The judge counted the h3 server's instructions over steps 29d.3 to 29d.8 (design §8):
- h3-many fell from 67,680 to 42,011 instructions a request.
- Each step won on h3-many and h3-bbr-many, and no h2 input lost.
Checked
GitHub's main workflow passed on 39b8cd6d (run 41763427588), and tools/ci.sh passed all 39 sections on it.
colibri 0.11.0: BBRv3, PEM files and DNSSEC checks
colibri 0.11.0 runs BBRv3 on each QUIC connection by default, and CUBIC stays a choice. It reads the PEM files an operator holds, checks DNSSEC signatures in a new dnssec module, and sends more request content from the program's memory with no copy. It pins chapulin f59c7e3f and stdx 5fd03aa.
Breaking changes
- Each module's constants are grouped by who sets them (decision 137):
protocolfor what the RFCs fix,limitsfor the bounds colibri chose,policyfor the other values colibri chose, and one file for each algorithm.quic.constants.frame_ackbecomesquic.constants.protocol.frame_ack, and the same holds for every module. - A QUIC connection runs BBRv3 (draft-ietf-ccwg-bbr-06) by default, where 0.10.0 ran CUBIC (decision 143).
server.Config,client.Configandquic.Connection's options take.congestion_controller = .cubicto keep CUBIC. Under BBRv3 a connection draws its probe waits from the sourceinittakes (decision 136). - A program with its own
tls_provider.Providermust fillNegotiated.group(decision 140) and givewritable_lenandencrypt_parts(decision 139). - A response whose Content-Length is larger than its
bodyendstoo_largeat its head (decision 124 as amended). h11 closes the connection, and h2 and h3 reset the stream. Before, colibri read and dropped the whole content. - Each session and connection is larger:
tls.record.Clientgrows from 40,968 to 49,800 bytes,quic.Connectionfrom 176,888 to 208,304 bytes, andserver.Endpointat its default capacity from 51,310,368 to 52,422,080 bytes.tls.constants.limits.anchors_maxis 256. docs/performance.md lists each size.
What's new
quic.CongestionControllerselects BBRv3 or CUBIC. BBRv3 keeps its model of the path from delivery rate samples, moves through Startup, Drain, ProbeBW and ProbeRTT, and undoes a spurious loss episode. spec/lean proves its arithmetic, and spec/tla/bbr checks its states.tls.pemreads a certificate chain, a P-256 key and a CA bundle from PEM (RFC 7468, decision 141).tls.P256Keyholds the key.- The new
dnssecmodule checks DNSSEC signatures and DS digests through chapulin:dnssec.verify,dnssec.Hash,dnssec.Algorithmanddnssec.DigestType(decision 142). tls_provider.Negotiated.groupandtls.quic.Client.negotiated()report the key-exchange group beside the cipher suite (decision 140).quic.PeerAddress.scope_idcarries an IPv6 address's interface, so a pool dials a link-local address (RFC 9000 §9).h2.Connection.write_data_headerwrites a DATA frame's header for data the caller writes after it (decision 138).
Fixes
- A cancel during a handshake that then failed left the pool's attempt open, so the next request ended refused with no connection opened (#100).
Speed
The judge's bulk run downloads a file of 2 MiB over h3 through a bottleneck of 20 Mbit/s and 25 ms (design §8 steps 13k and 29c.2):
- At 1% loss from one connection, BBRv3 gives 14.5 to 14.8 Mbit/s, and CUBIC 1.7. From four connections, BBRv3 gives 17.1 Mbit/s, and CUBIC 8.1.
- With no loss and four connections, a small GET's p99 beside the download is 186 ms under BBRv3, and 509 to 718 ms under CUBIC.
- When the two share the bottleneck with no loss, CUBIC takes about 0.82 of it. #123 tracks CUBIC's goodput at 1% loss.
- The h11 client sends request content from the program's memory, and the h2 client seals each DATA frame from two slices over TLS and writes its data after the header in cleartext (decision 139).
Checked
GitHub's main workflow passed on 83c922b1 (run 41306132768), and tools/ci.sh passed all 38 sections on it.
colibri 0.10.0: CUBIC, HyStart++ and h3 under loss
colibri 0.10.0 makes h3 hold up on paths with delay and loss. QUIC now runs CUBIC with HyStart++ in place of NewReno, carries up to 16 STREAM frames in a packet, and holds 100 h3 requests open. The server's router matches a path that ends in "/" with its own route, and h2 indexes a response's field lines in HPACK. It pins chapulin 1f3a99cb and stdx 5fd03aa, as 0.9.0 did.
Breaking changes
quic.constants.congestion_loss_reduction_divisoris gone, because CUBIC scales the window to 0.7 on a congestion event, not to half (decision 133).quic.constants.cubicandquic.constants.hystarthold the new constants.server.constants.quic_requests_maxis 100, where it was 32 (decision 130). A server's QUIC connection holds more streams, soserver.Endpointat its default capacity grows from 40,987,680 to 51,310,368 bytes, andquic.Connectionfrom 141,912 to 176,888 bytes. docs/performance.md lists each size.
What's new
- The congestion controller is CUBIC (RFC 9438, decision 133), in integer arithmetic, so one seed still replays on every host. Its first slow start is HyStart++ (RFC 9406, decision 135), which ends slow start when the round trip grows, before a loss does. spec/lean proves the cube root, the target's bounds and the HyStart++ stages, and two TLA+ models check the window and the stages in every order.
- A packet carries up to 16 STREAM frames (decision 131), so many small responses share one packet. h3-many takes 0.685 of 0.9.0's instructions a request.
- A server holds 100 h3 requests open and sends MAX_STREAMS each time a stream closes (decision 130). h3 body meters wait on data credit alone.
- A route whose pattern ends in "/" matches a path that ends in "/" (decision 122 as amended). A parameter does not match the empty last segment, and the wildcard does not match a path that ends in "/".
- h2 writes a response's field lines with incremental indexing in HPACK, so a repeated line is one byte (decision 132).
quic.data_credit_owedsays whether a connection waits on the peer's data credit, which the server's body meters read.
Fixes
- A server dropped every packet of a connection whose client started its first key update before the server had opened any of its 1-RTT packets, until the idle timeout. RFC 9001 §6.1 allows that update once the client confirms the handshake. quinn's client did it after a lost handshake datagram.
Speed
Each figure is colibri's p99 over h3 divided by its p99 over h2 with TLS, on the arm64 judge with tc netem on the loopback device (design §8 step 13i). Below 1, h3 answers the slowest requests sooner. The 0.9.0 tree's figure follows where it differs:
far(25 ms each way): 0.55 to 0.64 at 1,000 to 4,000 req/s.lossy(25 ms each way, 1% loss): 0.70 to 0.76, where it was 1.32 to 1.34 at 4,000 req/s. colibri's p99 over h3 is about 108 ms there, and quic-go's chi's about 107 ms.mobile(50 ms each way with jitter, 2% loss): 0.62 to 0.82 at 1,000 and 2,000 req/s, where it was 4.46 to 5.02 at 2,000. At 4,000 no h3 server reaches the rate: colibri reaches about 3,240 req/s with every request answered, and chi about 3,270.- h3-many takes 0.685 of the instructions a request it took before 16 STREAM frames shared a packet.
Checked
GitHub's main workflow passed on a23be1b2 (run 41012227601).
colibri 0.9.0: routes, queries, one set of HTTP names, and TLS alerts
colibri 0.9.0 gives the server's router a program's functions and middleware, gives a request its query, names each HTTP concept once in the server and the client, and lets a TLS record session send a fatal alert the program names. The client's connection pool opens TCP connections on demand. It pins chapulin 1f3a99cb and stdx 5fd03aa.
Breaking changes
- One name for each HTTP concept in
serverandclient(decision 126):server.BodyisRequestBody,server.ContentisResponseBody, and the client'sRequest.contentisbody.Fields,FieldandFieldSectionareHeadersandHeader, andfieldsisheaders.Trailersholdsheaders: Headers,WantedisWantedHeader, andNeverIndexed.fieldsisheaders.- The server's
doneandcancelledevents are onefinishedevent with an outcome, on both sides.RequestIdnames a request on both sides. VersionsisProtocols,DeadlinesandDeadlineareTimeoutsandTimeout, and the close reasondeadlineistimeout.client.Channelisclient.ConnectionPool, andChannelOfisConnectionPoolOf.client.HttpsisHttpsRecord,client.ValuesisDestination, andset_valuesisset_destination.client.Optionsfolds intoConfig:memory_bytes(config)andinit(memory, config, destination).- Names say bytes, not octets:
StreamOctetsisStreamBytes.ReceivedisReceiveResult, andaccept(Security)takestls: bool. - Decision 126 holds the whole table.
- The client's connection pool opens TCP connections on demand and holds its connections and requests in memory the program places (decision 125). Each event and call about a connection names a
ConnectionHandle, and asendevent names a TCP connection that owes bytes. The counts say requests:Config.requests,constants.requests_maxandpool_requests_max. - A request's query is apart from its path (decision 127):
Request.pathholds no query, andRequest.queryholds the raw query or null.http.QueryParametersreads itsname=valuepairs, from the WHATWG URL Standard. The router percent-decodes each path segment and takes%. A segment that encodes/,\or NUL getsnot_found. server.router(Context, table)replacesrouter(Value, table)(decision 127). The table is.{ .error_handler, .middleware, .routes }. A route holds a function, which takes typed path and query structs, or a struct withon_request,on_body,on_writable,on_finishedand aState.Router.Of(options)sizes the router's pool, andhandle(context, endpoint, event)drives it. New names:server.ResponseWriter,server.Handledandserver.middleware.Next.matchgives aRoutetag.- A route table's walk backs up past a leaf that does not serve the method (decision 128), so a table such as
/v1/charges/searchbeside/v1/charges/:chargecompiles, androute_hiddenis gone. A 405'sAllowlists the methods of every leaf the path reaches, so its value can grow. A table that names more thanserver.constants.route_methods_max(10) methods does not compile. - chapulin
50d7d17banswers a KeyUpdate with its next record of data.tls_provider.Content.new_session_ticketand.key_updateare.without_data, and h2's and h11'sDecrypted.owes_handshakeis gone. More than 16 KeyUpdates in a run close the connection:OpenError.KeyUpdates,h2.Limit.key_updatesandserver.Limit.key_updatesare new, andtls.constants.key_update_replies_maxis removed. A program with its owntls_providerfollows the new contract. - stdx's
platform.Cpuholdssha256,sha512,sha3,ssse3andsse4_1, with no default. A program that writes aplatform.Cpuliteral states them. One that callsplatform.probe()changes nothing.
What's new
tls.record.Client.failandtls.record.Server.failsend the fatal alert the program names, for an error it found itself, such as a record longer than RFC 9846 §5.2 allows. They take the error alerts of RFC 9846 §6.2 alone, on a connected session, and fail the session. A refused call returns atls.record.FailErrorand leaves the session as it was (#114).- Middleware may wrap a body's reader and writer:
State,wrap_body,wrap_responseandfinish_response. - Each route names its own parameters, so two routes may name one position differently (decision 129).
- On arm64, a session runs the SHA instructions where the probe has them and the timing is stated as data-independent.
Fixes
- chapulin
1f3a99cbsends record_overflow before it decrypts a record longer than RFC 9846 §5.1 or §5.4 allows, or longer than the record_size_limit this side sent (RFC 8449 §4). Each TLS session over TCP grows 8 bytes. - A server sends
record_size_limitonly to a client that offered it (chapulina6703587, RFC 9846 §4.3). Clients on rustls refused it before. - h11's
write_responsereturnsFramingInvalidfor a bad Content-Length, where it panicked.
Speed
No API change. Against the competitors' run of 2026-10-20, per request or connection, kernel and user together, on the arm64 judge:
- A TLS handshake costs about half: h11-tls-one, h2-tls-one and h3-one fall 48%, 47% and 59%.
- h2-many falls 7%, h11-many 11%, and h11-tls-many 20%.
- The router matches a path in one pass: h2-routes-many, which routes each request over the GitHub API's GET routes, falls 4% (ratio 0.960 in two judge runs).
Checked
GitHub's main workflow passed on bf4fe21f (run 40828900211).
colibri 0.8.0: the server's router, client.Request, h3 deadlines, and the KeyUpdate read fix
colibri 0.8.0 gives the server a router and one endpoint for TCP and QUIC, and gives the client client.Request and client.Response. Each module now exports its names from its root alone. It pins chapulin e6e3c1e7 and stdx 5da4397.
Breaking changes
- Each module exports the names a program calls from its root, and no file (decision 115). A type named after its file has its own name: write
h2.Connectionforh2.connection.Connection, and the same forhttp.Field,core.Reader,qpack.Encoder,quic.PeerAddressand the others. A method that only a module's own files call is private. server.Endpointholds every TCP and QUIC connection, and a program answers each request by itsId(decision 119).server.ConfigreplacesEndpointConfigand takes the TLS identity once.Connection,QuicConnectionandQuicConfigleave the root.Endpoint.initreturnsStartError, witherror.DeadlineInvalidfor deadlines a connection refuses.versionsreplacescleartexton the server and the client, andlimitsholds the bounds that named h2 (decision 117). A server that allows both versions reads h2's preface in cleartext (RFC 9113 §3.3).server.Limitsholdspeer_resets_maxandresets_sent_max, andserver.constants.quic_peer_reset_rate_maxis removed. A server refuses a body rate that an honest peer cannot meet over TLS or h2 (decision 110).client.Requestandclient.Responsereplaceclient.HttpExchange(decision 124).client.ConfigreplacesChannelConfigand takes onetls.Clientvalue. The channel's types lose their prefix:Values,Input,Received,Event,OpenandPhase.ConnectionandQuicConnectionleave the root.Channel.start_quicdraws a QUIC connection's IDs and grease itself, soQuicStartandQuicStartErrorleave the root.RequestErrornames only whatChannel.requestreturns.client.Codinggainszstdandbr, so a switch over it must name both.tls.Cpuholds the program's probe and atls.Timingthat the program states (chapulin 0.2.0, decision 97 as amended).tls.quic.versionis private.
What's new
server.router: literal paths, parameters, a trailing wildcard, and HEAD answered by a route's GET. A table that hides a route does not compile (decision 122).- The server runs its deadlines and rate limits over h3 too.
- The client decodes zstd and br from decoder pools the program places. It sends an idempotent request again once after an unanswered close, and holds a long h11 head over TLS.
client.ChannelOfsizes a channel. - A
tls.Clientvalue can take no tickets, withtakes_tickets = false. - After a KeyUpdate that asks for an answer, the client and the server read on, and send the answer before their next record of application data (RFC 9846 §4.7.3).
Checked
GitHub's main workflow passed on 7496cdc (run 39183420881).
colibri 0.7.0: QUIC version 2, the program's CPU probe, and the server's deadlines
colibri 0.7.0 speaks QUIC version 2 beside version 1, takes the program's probe of its CPU for TLS, and ends slow and silent peers at the server. It pins chapulin 900ce67 and stdx fa53aa4.
Breaking changes
tls.Clientandtls.Servervalues takecpu, theplatform.Cputhat stdx'splatform.probe()returns, with no default. A session runs the AES instructions only whenaes_clmulisyes, and ChaCha20 alone otherwise (decision 97). Depend on stdx at the commit colibri pins, with the options colibri gives it (.targetand.release), so both reach oneplatform.Cpu. colibri's package does not exportplatform.serverenforces deadlines: first request, idle, head, body, send, h2's SETTINGS acknowledgment and drain. Each is a named limit in its configuration that a caller may change or turn off (decision 110).
What's new
- QUIC version 2 (RFC 9369) with compatible version negotiation (RFC 9368). A client lists version 2 and switches when its server answers in it. A server switches every client that lists it, unless its
switch_tois null. A client offered a ticket starts in the ticket's version (decisions 108 and 111). - h2 ends a connection whose peer opens and resets more than 100 streams a second (CVE-2023-44487).
- One chapulin object per architecture runs the AES instructions or ChaCha20, as the probe says. ChaCha20 runs four blocks at a time on SSE2 and NEON. chapulin
900ce67seals a 16 KiB AES-256-GCM record in about 5.5 µs on an Apple M1 Pro, where044a49ctook 27 to 36 µs (chapulin's own figures). bench/: the judge and its workflow (design §8 step 13a).- h11: CONNECT and Upgrade fixes.
Checked
tools/ci.sh passed every section on macOS arm64, with 2505 of 2505 tests in Debug and ReleaseSafe.
colibri 0.6.0: AES-256-GCM first on the AES instructions, and a client's own suite order
colibri 0.6.0 runs AES-256-GCM first where the CPU has the AES instructions, as a TLS client and as a server, and lets a client choose the suites it offers. It pins chapulin at 10a5bc8, whose default order this is, and stdx at c134f2c. qlog writes each record's tokens many at a call, and the client and the server send a failed record's alert before they close.
Breaking
qlog:TextWriteris now qlog's batch writer, which gathers a record's tokens and hands them to stdx's JSON writer many at a call (81707f2). It keepsjson.TextWriter's calls, butwritten()returns an error union, because it writes what is still gathered. The records are the same octets.
To move from 0.5.0, handle the error qlog.TextWriter.written() may return.
Changed
tls: on a target with the AES instructions, both roles prefer TLS_AES_256_GCM_SHA384, then TLS_AES_128_GCM_SHA256, then TLS_CHACHA20_POLY1305_SHA256, where 0.5.0 put ChaCha20 first (b3880da, chapulin's decision 80). AES-256-GCM comes first for CNSA 2.0. Against Go's server a client still runs AES-128-GCM, because Go chooses among the AES suites by its own order once the client lists one first. A target without the AES instructions holds ChaCha20 alone, as before. A program that wants another order setstls.values.Server.cipher_suites, or the new client field below.
Added
tls:values.Client.cipher_suites, the suites a client offers, most preferred first, as RFC 9846 Appendix B.4 codepoints. The ClientHello offers exactly this list, and an empty one keeps chapulin's order. An object without AES-GCM refuses a list withSuitesUnavailable, and chapulin refuses a suite named twice when the session starts (b3880da).tls.quic:version, the QUIC version every session and Retry protects packets for: version 1 (RFC 9000 §15).
Dependencies
- chapulin is pinned at
10a5bc8, where 0.5.0 pinned0adcf33. It brings the default order above, a client's own order, and a version argument on each QUIC packet call, which colibri fills in. - stdx is pinned at
c134f2c, where 0.5.0 pinned8c2a3b8(a5bfb50). Its DEFLATE encoder, which the server's content codings run, writes smaller output, and runs level 1 at 0.91 and 0.93 of its old speed (stdx's decision 36). Its brotli command loop runs without Zig's runtime checks, and colibri never runs brotli.
Fixed
clientandserver: after a record failed,should_closesaid to close beforesendwrote thebad_record_macalert the connection owes (RFC 9846 §5.2), so a caller that asked before its nextsendclosed without it. Each now waits for the alert (4ed2108). The interop scripts check it with a record no key sealed (628449b, #74).
Checked against
CI ran tools/ci.sh on 830af4c, on Ubuntu 24.04 with an AMD EPYC 7763, and every section passed:
- TLS against Go 1.27.1: colibri's server selected TLS_AES_256_GCM_SHA384 (
suite=0x1302), and its client ran TLS_AES_128_GCM_SHA256 (suite=0x1301), the suite Go's server chose. On 0.5.0 both ran ChaCha20 (0x1303). - 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, with gzip content codings in both directions and the scripts' forged-record checks.
- 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,337 tests in Debug and in ReleaseSafe. The tests also passed on Linux and macOS arm64.
The HTTP Garden and the QUIC Interop Runner ran on b5a7c79, which adds documentation alone to 830af4c. The Garden fed its 72 streams to colibri's server and to 35 other origins, and compared every one. In the runner, colibri passed every case against quic-go, ngtcp2, neqo, quinn and itself, as the server and as the client, but two:
chacha20, wherever colibri was the client. The case requires the client to offer ChaCha20 alone, and the test endpoint still offered its object's order, now AES-256-GCM first.0af377eon main gives the endpoint achacha20option.handshakelosswith quinn's client, which also failed at9796e95and passed atbb9263e.
The runner ran again at 0af377e, main with that fix and the QUIC version 2 work that follows this release, and colibri passed every case, chacha20 and quinn's handshakeloss included.
quic-go runs 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.6.0