AoAH Day 11: HTTP Cookies and vibing RFCs for breakfast / Dec 2025

I'm switching focus for a few days to build a complete HTTP(S) client to use in my literature downloader. This requires building a few support libraries before we build the full client, so I figured I'd dive in them in the next few days. First up is RFC6264 HTTP Cookie support. There is are some excellent existing cookie libraries already on opam, notably http-cookie and ocaml-cookie, but I wasn't sure what their coverage of the protocol is, and there's no Eio serialisation support.

So I thought I'd have a go at a different approach today using agentic coding: can we synthesise a complete HTTP Cookie implementation purely from the RFC 6265 prose itself, and then differentially compare this OCaml implementation against the others? In theory, running a single test suite across all three libraries might be a good way of discovering how to improve the existing implementations. In the long-term, http-cookie is probably the upstream library I want to use, but I don't want to generate a giant diff against it today due to my groundrules of not disturbing other maintainers.

Starting with RFC 6265: HTTP State Management

I downloaded RFC 6265 and gave the agent all my previously generate code in the same directory to act as examples. My first run went verrrrry strangely as it generated a plan for a carrier-grade NAT implementation, until I realised that I'd typoed the RFC number and picked 6264 by mistake. Ah, such human frailty...

Once I got the right RFC number, I stuck Claude into planning mode with the RFC text and also instructed it to search through opam to find relevant packages. This opam search went well, as it identified some missing dependent logic that a full implementation of cookies would require. So I went down a sub-rabbithole of implementing those dependent packages first.

First up is Punycode, as RFC6265 Section 5.1.2 requests that:

Convert each label that is not a Non-Reserved LDH (NR-LDH) label, to an A-label (see Section 2.3.2.1 of [RFC5890] for the former and latter), or to a "punycode label" (a label resulting from the "ToASCII" conversion in Section 4 of [RFC3490]), as appropriate (see Section 6.3 of this specification).)

Diving into RFC 3490: Punycode

I'd never heard of this Punycode before, but perusing RFC3490 introduces it such:

Until now, there has been no standard method for domain names to use characters outside the ASCII repertoire. This document defines internationalized domain names (IDNs) and a mechanism called Internationalizing Domain Names in Applications (IDNA) for handling them in a standard fashion. IDNs use characters drawn from a large repertoire (Unicode), but IDNA allows the non-ASCII characters to be represented using only the ASCII characters already allowed in so- called host names today. This backward-compatible representation is required in existing protocols like DNS, so that IDNs can be introduced with no changes to the existing infrastructure. IDNA is only meant for processing domain names, not free text. -- RFC3490, 2003

Ok, ok so this is some DNS thing needed to reliably compare cookie domains that might be in different languages. The RFC is a little unusual in that it embeds C code and also test vectors, so this seems ideal for an agentic session.

Result: an OCaml-punycode library

I pushed ocaml-punycode to Tangled. It's fairly straightforward, with one module for the core Unicode (using uufn for Unicode normalisation and the domain-name by Hannes Mehnert for RFC1035 Internet Domain Name handling. Although we didn't use cram tests for this, the Punycode alcotest seem to encode the test vectors from the RFC faithfully, and also do roundtrip testing.

One trick that helped get more idiomatic OCaml code here was to do the generation in two passes. First I asked it to transcribe the RFC into OCaml along with the test cases, and then a second pass to refactor the code using higher-order functions and Stdlib combinators. You can see the results of the second pass here, which does look like more normal OCaml that I might write.

Linking code to RFC sections

Coding with the RFC as the source spec actually resurrected an issue I've been thinking about since 2015. Back then, Luke Dunstan wanted to add multicast DNS support to ocaml-dns, and as part of that he proposed using extension attributes in the code to link the relevant section of the RFC spec to the associated OCaml function.

Coding agents go a long way to automating this. In the punycode ocamldoc, I instructed it to use the online RFC repository as an anchor base, and it successfully links almost every type to where it got the specification constraints from.

type error =
| Overflow of position
   (** Arithmetic overflow during encode/decode. This can occur with
       very long strings or extreme Unicode code point values.
       See {{:https://datatracker.ietf.org/doc/html/rfc3492#section-6.4}
       RFC 3492 Section 6.4} for overflow handling requirements. *)
| Invalid_character of position * Uchar.t
   (** A non-basic code point appeared where only basic code points
       (ASCII < 128) are allowed. Per
       {{:https://datatracker.ietf.org/doc/html/rfc3492#section-3.1}
       RFC 3492 Section 3.1}, basic code points must be segregated
       at the beginning of the encoded string. *)

I've therefore mechanised this insight into a claude-ocaml-internet-rfc skill which formalises my future approach to RFCs and allows this part of the workflow to be reused in future OCaml code.

This post was fueled by delicious Swedish mulled wine and 'cookies'
This post was fueled by delicious Swedish mulled wine and 'cookies'

Segwaying into Public Suffix lists

But then the exploration of the OCaml RFCs told me that for real compliance, we needed to support Public Suffix lists, something else I'd never heard about!

A "public suffix" is one under which Internet users can (or historically could) directly register names. Some examples of public suffixes are com, co.uk and pvt.k12.ma.us. The Public Suffix List is a list of all known public suffixes.

The Public Suffix List is an initiative of Mozilla, but is maintained as a community resource. It is available for use in any software, but was originally created to meet the needs of browser manufacturers. It allows browsers to, for example:

  • Avoid privacy-damaging "supercookies" being set for high-level domain name suffixes
  • Highlight the most important part of a domain name in the user interface
  • Accurately sort history entries by site -- Public Suffix Lists, 2025

This one's different from the previous library in that there's a large dataset that needs to be embedded inside the library, and we need reasonably efficient datastructures to traverse them and do the domain comparison (using the Punycode logic from earlier for domain name normalisation).

So my approach for this was to download the dataset and clone the public suffix list wiki and prompt the agent to come up with a generation architecture that would first parse the list into an OCaml data structure and then provide a Public_suffix OCaml interface that we could use without having to download the data as a library user.

Results: a public-suffix opam package

The agent managed all the dune build rules fine for the generated package, so I pushed a ocaml-publicsuffix Git repository using the opam metadata skill. I also then used the earlier ocaml-internet-rfc skill to add references to the various RFCs we used.

The interface is very simple, since the dataset is embedded as OCaml code:

let psl = Publicsuffix.create () in

Publicsuffix.public_suffix psl "www.example.com"
(* Returns: Ok "com" *)

Publicsuffix.public_suffix psl "www.example.co.uk"
(* Returns: Ok "co.uk" *)

Publicsuffix.registrable_domain psl "www.example.com"
(* Returns: Ok "example.com" *

And so finally onto Cookeio!

After these segways. we finally have enough for an Eio-based Cookie library that's derived from the main cookie RFC!

This was pretty plain sailing given the previous libraries. The published ocaml-cookeio library library has subpackages for the Cookie jar interface that provide a pretty simple persistent cookie implementation that we can use in the next few days for our HTTP client.

val create : unit -> t
(** Create an empty cookie jar. *)

val load : clock:_ Eio.Time.clock -> Eio.Fs.dir_ty Eio.Path.t -> t
(** Load cookies from Mozilla format file.

    Loads cookies from a file in Mozilla format, using the provided clock to set
    creation and last access times. Returns an empty jar if the file doesn't
    exist or cannot be loaded. *)

val save : Eio.Fs.dir_ty Eio.Path.t -> t -> unit
(** Save cookies to Mozilla format file. *)

(** {1 Cookie Jar Management} *)

val add_cookie : t -> Cookeio.t -> unit

Reflections

I didn't expect to generate three libraries in one day, but I was massively sped up by creating this Claude skill for Internet RFC handling. The three libraries today, punycode, publicsuffix and cookeio do a lot of fairly ad-hoc checking that's specified throughout a variety of RFCs, but do benefit from public test sets to check their behaviour.

Now that these are out of the way, I'm going to continue on with another essential library tomorrow: Eio TCP/TLS connection pooling.

# 10th Dec 2025agents, ai, aoah, llms, ocaml, rfcs

Loading recent items...