The past week or so I’ve been working on implementing the QUIC protocol in Clojure. Currently there is no Java implementation to use either (that I know of), and I just found out the other day that netty decided to use the Cloudflare’s Rust library quiche under the hood instead of rolling their own. The protocol is currently a IETF draft at version 32, expected to turn into an RFC soon.

QUIC’s a lower layer protocol to carry HTTP/3. They decided to ditch TCP altogether and instead implement its ordering and delivery guarantees on top of UDP. This was apparently the smoothest way to deal with bad network conditions in large swaths of the internet (rural India for example) where packet loss and connection drops are common. Using UDP means existing network infrastructure can be utilized without the need for any costly (and often impossible) upgrades.

brown wooden house on mountain cliff

Unlike HTTP/2, which in the end operates in cleartext (h2c), HTTP/3 and QUIC has TLSv1.3 built in. 1.3 is the current-latest version of TLS that “just” came out in 2018. “Built in” may be a bit of an exaggeration though: while QUIC “assumes responsibility for the confidentiality and integrity protection of packets” (which was the responsibility of the TLS record layer), the rest is still TLS.

That makes things really tricky: I’d prefer to use an existing TLS implementation (like the one provided by the JDK), but sadly these don’t have an abstraction over the record layer, instead it’s just “raw packets in, data out” without a way to make that “plaintext TLS protocol data in, data out” after clearing QUIC’s packet protection.

The Go implementation has its own fork of the standard lib TLS, while quiche uses Google’s BoringSSL under the hood. Since my preferred language at this point is Clojure, I’d need either the Java implementation to make some progress or call out to LLVM (like netty decided to). Once netty’s version is stable, I guess I can just use that too, but until then I decided to try and roll my own (also watch the OpenJDK and netty implementations and contribute where I see a chance).

Mostly because while coding the process of unpacking protected packets (the part where the “hard” parts of TLS aren’t yet involved) I realized just how much I could learn from doing this. There are solid and trusted implementations out there to learn from, but even with that the protocol itself is different (see the QUIC-TLS draft) meaning I actually have to understand what I’m doing instead of just copy-pasting. Which is great.