Shorenet protocol fixtures

Endpoints that return a fixed, documented shape, for testing protocol tooling. Every response is deterministic: the same request returns the same bytes.

There are two layers. A hostname fixes the handshake — the transport, the TLS version, the cipher and signature choices, the ALPN. A path selects the exchange that follows. Most paths work on most hostnames, so you can hold one constant and vary the other.

Hostnames — the handshake

Each one presents its own certificate and negotiates exactly what it says.

HostnameNegotiates
lab.shorenet.co.ukThe estate apex: the endpoint list, over HTTP/1.1 or HTTP/2
h1.lab.shorenet.co.ukHTTP/1.1 over TLS 1.3
h2.lab.shorenet.co.ukHTTP/2 over TLS 1.3
ws9573.lab.shorenet.co.uk:9573TLS 1.3 on a non-standard port
resume.lab.shorenet.co.ukTLS 1.3 resumption / 0-RTT
h1.lab.shorenet.co.uk:80Plain HTTP/1.1, no TLS
kex-x25519.lab.shorenet.co.ukgroup X25519
kex-p256.lab.shorenet.co.ukgroup secp256r1
kex-p384.lab.shorenet.co.ukgroup secp384r1
kex-mlkem768.lab.shorenet.co.ukgroup X25519MLKEM768 (post-quantum hybrid; what current Chrome/Edge/Firefox send by default)
kex-hrr.lab.shorenet.co.ukHelloRetryRequest: the server insists on a group the client did not key-share
resume-psk.lab.shorenet.co.ukPSK resumption, no early data
resume-ticket.lab.shorenet.co.ukSession tickets issued on one connection and used on the next
ticket-reject.lab.shorenet.co.ukissues session tickets and accepts none back: every connection is a full handshake
ticket-none.lab.shorenet.co.uksends no NewSessionTicket
ech-own.lab.shorenet.co.ukECH with keys this server holds; the ECHConfig is published in an HTTPS record
ech-reject.lab.shorenet.co.ukECH offered but rejected, with retry_configs returned
ech-grease.lab.shorenet.co.ukNo real ECH; the client sends GREASE ECH (what Chrome does by default)

Hostnames that speak no HTTP

These negotiate a non-HTTP application protocol, or none at all. After the handshake they echo whatever you send, byte for byte. There are no paths on these.

HostnameNegotiates
noalpn.lab.shorenet.co.ukTLS 1.3, the server selects no ALPN
dot.lab.shorenet.co.ukTLS 1.3, ALPN dot (DNS over TLS, RFC 7858)
alpn.lab.shorenet.co.ukTLS 1.3, a custom non-HTTP ALPN

Paths — the exchange

PathBehaviour
/f/ce-identityHTTP 200, Content-Encoding: identity, 43-byte image/gif.
/f/ce-noneHTTP 200, Content-Encoding: none, same 43-byte body, untransformed.
/f/ce-utf8HTTP 200, Content-Encoding: utf-8, body untransformed. Variants: v626, v7132.
/f/ce-gzipHTTP 200, a correctly gzip-encoded body.
/f/ce-gzip-mislabelledHTTP 200, gzip bytes labelled Content-Encoding: deflate.
/f/status-{code}A bare response at that status. Variants: 200, 204, 206, 304, 404, 500.
/f/bulk-bodyHTTP 200, a large deterministic body. Variants: mb2, mb8, mb32.
/f/sse-trickletext/event-stream, a fixed number of events at a fixed spacing, then the stream held open for 30 s and closed by the server. Set the count with ?n= (1, 5, 20 or 100) and the gap in milliseconds with ?ms= (10, 50, 250 or 1000); any other value is refused with 400.
/f/body-slowHTTP 200, body written one byte at a time, 50 ms apart. Set the length with ?n= (8, 64 or 256 bytes); any other value is refused with 400.
/f/h1-keepalive-gzipIdentical gzip responses (Content-Length 1990, 5430 B decoded) on one keep-alive connection. Request it repeatedly on the same connection.
/f/close-delimited302, Connection: close, no Content-Length and no Transfer-Encoding, empty body, then close_notify then FIN.
/f/ws-echoWebSocket upgrade (permessage-deflate when offered), then a byte-exact echo.
/f/ws-ce-noneWebSocket upgrade whose 101 carries Content-Encoding: none.
/f/h2-trailersResponse trailers after DATA (a trailer field carrying the body's SHA-256).
/f/ech-h3HTTP 200, text/plain.
/f/probeHTTP 200, a short deterministic body.
/f/early-hints103 Early Hints with two Link fields, then a 200 HTML page.
/f/interim-100An unsolicited 100 Continue, then 200.
/f/chunked-trailersHTTP/1.1 chunked body followed by a trailer field carrying the body's SHA-256.
/f/trailers-bodilessA response head, no content, then a trailer section (on HTTP/2: HEADERS, then HEADERS with END_STREAM and no DATA).
/f/data-framesA 32 KiB body written as 512 separately flushed 64-byte pieces (512 DATA frames on HTTP/2, 512 chunks on HTTP/1.1).
/f/abort-mid-body16 KiB of a body with no declared length, then the server stops: RST_STREAM(INTERNAL_ERROR) on HTTP/2, a chunked body with no last chunk and a closed connection on HTTP/1.1.
/f/abort-mid-lengthContent-Length 65536, 16 KiB delivered, then the server stops (RST_STREAM on HTTP/2, connection close on HTTP/1.1).
/f/h2-early-responseA 200 sent before the request content has ended; the stream then ends with RST_STREAM(NO_ERROR).
/f/hpack-churn24 distinct header fields of about 200 bytes each: more than a 4,096-byte HPACK dynamic table holds.
/f/huge-headersAbout 48 KiB of response header fields (on HTTP/2: HEADERS plus CONTINUATION frames).
/f/header-obs-textHeader field values containing bytes above 0x7F: a UTF-8 byte order mark and a Latin-1 byte.
/f/set-cookie-multiThree Set-Cookie fields in one response.
/f/rangeA 19,883-byte body that honours Range: 206 for one range, multipart/byteranges for several.
/f/ce-brHTTP 200, a brotli-encoded body.
/f/ce-zstdHTTP 200, a zstd-encoded body.
/f/ce-zstd-skippableHTTP 200, a zstd body of two frames with skippable frames before, between and after them.
/f/ce-gzip-brContent-Encoding: gzip, br (gzip applied first, then brotli).
/f/ce-deflateHTTP 200, a zlib-wrapped deflate body.
/f/ce-gzip-headerHTTP 200, a gzip body whose header sets FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT and MTIME.
/f/dict/sourceA cacheable text body with Use-As-Dictionary: match="/f/dict/*".
/f/dict/dcbThe next version of that text, brotli-compressed against it (Content-Encoding: dcb) when the request names the dictionary, unencoded otherwise.
/f/dict/dczThe next version of that text, zstd-compressed against it (Content-Encoding: dcz) when the request names the dictionary, unencoded otherwise.
/f/http10An HTTP/1.0 status line, Content-Length, then the connection closes.
/f/h1-no-reasonA status line with an empty reason phrase ("HTTP/1.1 200 ").
/f/chunked-extA chunked body whose chunk-size lines carry chunk extensions.
/f/te-gzip-chunkedTransfer-Encoding: gzip, chunked.
/f/conn-closeA 200 with Connection: close on a kept-alive connection, which then closes.
/f/close-notify-rstA complete response, close_notify, then a TCP reset instead of a FIN.
/f/rst-after-responseA complete response, then a TCP reset with no close_notify.
/f/ws-plainWebSocket upgrade declining permessage-deflate, then a byte-exact echo.
/f/ws-server-firstWebSocket upgrade whose first server message travels in the same write as the 101, then an echo.
/f/ws-largeWebSocket upgrade, then one 256 KiB binary message (a 64-bit payload length), then an echo.
/f/ws-fragmentedWebSocket upgrade, then one text message in 16 fragments with a PING between two of them, then an echo.
/f/ws-server-closeWebSocket upgrade, one message, then the server's own Close 1000.

Pages that exercise the paths

Each page makes a short, fixed sequence of requests to the paths above and shows what came back. Each is served only on the hostnames listed.

PageBehaviourHostnames
/f/early-hintsNavigates to a page whose 200 is preceded by a 103 Early Hints response.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/interim-100Fetches a response preceded by an unsolicited 100 Continue.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/h2-trailersFetches a body followed by trailers.h2.lab.shorenet.co.uk
/t/chunked-trailersFetches a chunked body followed by a trailer field.h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80
/t/trailers-bodilessFetches a response with trailers and no content.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/data-framesFetches a body delivered in 512 pieces.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/abort-mid-bodyFetches a response the server stops partway through.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/abort-mid-lengthFetches a response that ends before its declared length.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/h2-early-responseStreams an upload and receives the response before the upload ends.h2.lab.shorenet.co.uk
/t/hpack-churnFetches a response with many large header fields, three times on one connection.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/hpack-shared-tableFetches four of those responses at once on one connection, twice.h2.lab.shorenet.co.uk
/t/huge-headersFetches a response with about 48 KiB of header fields.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/header-obs-textFetches a response whose header values contain bytes above 0x7F.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/set-cookie-multiFetches a response with three Set-Cookie fields.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/rangeFetches the whole body, one range, then two ranges.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/head-requestSends HEAD requests: a response head with no content.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/content-codingsFetches bodies in brotli, zstd, zstd with skippable frames, gzip-then-brotli, deflate and a gzip with every header field.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/dictionary-transportFetches a dictionary, then two resources a browser may request compressed against it.h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk
/t/http10Fetches a response with an HTTP/1.0 status line.h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80
/t/h1-no-reasonFetches a response whose status line has no reason phrase.h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80
/t/chunked-extFetches a chunked body with chunk extensions.h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80
/t/conn-closeFetches twice on a kept-alive connection, then a response that closes it, then once more.h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80
/t/close-delimitedFetches a redirect whose body is delimited by the connection closing.h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80
/t/close-notify-rstFetches a response after which the connection ends with close_notify and a TCP reset.h1.lab.shorenet.co.uk
/t/rst-after-responseFetches a response after which the connection is reset.h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80
/t/ws-echoOpens a WebSocket and has two messages echoed.h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk
/t/ws-plainThe same, with no compression extension.h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk
/t/ws-ce-noneOpens a WebSocket whose 101 response carries Content-Encoding: none.h1.lab.shorenet.co.uk
/t/ws-server-firstOpens a WebSocket whose server speaks first.h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk
/t/ws-largeOpens a WebSocket and receives one 256 KiB message.h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk
/t/ws-fragmentedOpens a WebSocket and receives one message in 16 fragments.h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk
/t/ws-server-closeOpens a WebSocket that the server closes.h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk