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.
| Hostname | Negotiates |
|---|---|
lab.shorenet.co.uk | The estate apex: the endpoint list, over HTTP/1.1 or HTTP/2 |
h1.lab.shorenet.co.uk | HTTP/1.1 over TLS 1.3 |
h2.lab.shorenet.co.uk | HTTP/2 over TLS 1.3 |
ws9573.lab.shorenet.co.uk:9573 | TLS 1.3 on a non-standard port |
resume.lab.shorenet.co.uk | TLS 1.3 resumption / 0-RTT |
h1.lab.shorenet.co.uk:80 | Plain HTTP/1.1, no TLS |
kex-x25519.lab.shorenet.co.uk | group X25519 |
kex-p256.lab.shorenet.co.uk | group secp256r1 |
kex-p384.lab.shorenet.co.uk | group secp384r1 |
kex-mlkem768.lab.shorenet.co.uk | group X25519MLKEM768 (post-quantum hybrid; what current Chrome/Edge/Firefox send by default) |
kex-hrr.lab.shorenet.co.uk | HelloRetryRequest: the server insists on a group the client did not key-share |
resume-psk.lab.shorenet.co.uk | PSK resumption, no early data |
resume-ticket.lab.shorenet.co.uk | Session tickets issued on one connection and used on the next |
ticket-reject.lab.shorenet.co.uk | issues session tickets and accepts none back: every connection is a full handshake |
ticket-none.lab.shorenet.co.uk | sends no NewSessionTicket |
ech-own.lab.shorenet.co.uk | ECH with keys this server holds; the ECHConfig is published in an HTTPS record |
ech-reject.lab.shorenet.co.uk | ECH offered but rejected, with retry_configs returned |
ech-grease.lab.shorenet.co.uk | No 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.
| Hostname | Negotiates |
|---|---|
noalpn.lab.shorenet.co.uk | TLS 1.3, the server selects no ALPN |
dot.lab.shorenet.co.uk | TLS 1.3, ALPN dot (DNS over TLS, RFC 7858) |
alpn.lab.shorenet.co.uk | TLS 1.3, a custom non-HTTP ALPN |
Paths — the exchange
| Path | Behaviour |
|---|---|
/f/ce-identity | HTTP 200, Content-Encoding: identity, 43-byte image/gif. |
/f/ce-none | HTTP 200, Content-Encoding: none, same 43-byte body, untransformed. |
/f/ce-utf8 | HTTP 200, Content-Encoding: utf-8, body untransformed. Variants: v626, v7132. |
/f/ce-gzip | HTTP 200, a correctly gzip-encoded body. |
/f/ce-gzip-mislabelled | HTTP 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-body | HTTP 200, a large deterministic body. Variants: mb2, mb8, mb32. |
/f/sse-trickle | text/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-slow | HTTP 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-gzip | Identical gzip responses (Content-Length 1990, 5430 B decoded) on one keep-alive connection. Request it repeatedly on the same connection. |
/f/close-delimited | 302, Connection: close, no Content-Length and no Transfer-Encoding, empty body, then close_notify then FIN. |
/f/ws-echo | WebSocket upgrade (permessage-deflate when offered), then a byte-exact echo. |
/f/ws-ce-none | WebSocket upgrade whose 101 carries Content-Encoding: none. |
/f/h2-trailers | Response trailers after DATA (a trailer field carrying the body's SHA-256). |
/f/ech-h3 | HTTP 200, text/plain. |
/f/probe | HTTP 200, a short deterministic body. |
/f/early-hints | 103 Early Hints with two Link fields, then a 200 HTML page. |
/f/interim-100 | An unsolicited 100 Continue, then 200. |
/f/chunked-trailers | HTTP/1.1 chunked body followed by a trailer field carrying the body's SHA-256. |
/f/trailers-bodiless | A response head, no content, then a trailer section (on HTTP/2: HEADERS, then HEADERS with END_STREAM and no DATA). |
/f/data-frames | A 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-body | 16 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-length | Content-Length 65536, 16 KiB delivered, then the server stops (RST_STREAM on HTTP/2, connection close on HTTP/1.1). |
/f/h2-early-response | A 200 sent before the request content has ended; the stream then ends with RST_STREAM(NO_ERROR). |
/f/hpack-churn | 24 distinct header fields of about 200 bytes each: more than a 4,096-byte HPACK dynamic table holds. |
/f/huge-headers | About 48 KiB of response header fields (on HTTP/2: HEADERS plus CONTINUATION frames). |
/f/header-obs-text | Header field values containing bytes above 0x7F: a UTF-8 byte order mark and a Latin-1 byte. |
/f/set-cookie-multi | Three Set-Cookie fields in one response. |
/f/range | A 19,883-byte body that honours Range: 206 for one range, multipart/byteranges for several. |
/f/ce-br | HTTP 200, a brotli-encoded body. |
/f/ce-zstd | HTTP 200, a zstd-encoded body. |
/f/ce-zstd-skippable | HTTP 200, a zstd body of two frames with skippable frames before, between and after them. |
/f/ce-gzip-br | Content-Encoding: gzip, br (gzip applied first, then brotli). |
/f/ce-deflate | HTTP 200, a zlib-wrapped deflate body. |
/f/ce-gzip-header | HTTP 200, a gzip body whose header sets FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT and MTIME. |
/f/dict/source | A cacheable text body with Use-As-Dictionary: match="/f/dict/*". |
/f/dict/dcb | The next version of that text, brotli-compressed against it (Content-Encoding: dcb) when the request names the dictionary, unencoded otherwise. |
/f/dict/dcz | The next version of that text, zstd-compressed against it (Content-Encoding: dcz) when the request names the dictionary, unencoded otherwise. |
/f/http10 | An HTTP/1.0 status line, Content-Length, then the connection closes. |
/f/h1-no-reason | A status line with an empty reason phrase ("HTTP/1.1 200 "). |
/f/chunked-ext | A chunked body whose chunk-size lines carry chunk extensions. |
/f/te-gzip-chunked | Transfer-Encoding: gzip, chunked. |
/f/conn-close | A 200 with Connection: close on a kept-alive connection, which then closes. |
/f/close-notify-rst | A complete response, close_notify, then a TCP reset instead of a FIN. |
/f/rst-after-response | A complete response, then a TCP reset with no close_notify. |
/f/ws-plain | WebSocket upgrade declining permessage-deflate, then a byte-exact echo. |
/f/ws-server-first | WebSocket upgrade whose first server message travels in the same write as the 101, then an echo. |
/f/ws-large | WebSocket upgrade, then one 256 KiB binary message (a 64-bit payload length), then an echo. |
/f/ws-fragmented | WebSocket upgrade, then one text message in 16 fragments with a PING between two of them, then an echo. |
/f/ws-server-close | WebSocket 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.
| Page | Behaviour | Hostnames |
|---|---|---|
/f/early-hints | Navigates 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-100 | Fetches a response preceded by an unsolicited 100 Continue. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/h2-trailers | Fetches a body followed by trailers. | h2.lab.shorenet.co.uk |
/t/chunked-trailers | Fetches a chunked body followed by a trailer field. | h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80 |
/t/trailers-bodiless | Fetches a response with trailers and no content. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/data-frames | Fetches a body delivered in 512 pieces. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/abort-mid-body | Fetches a response the server stops partway through. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/abort-mid-length | Fetches a response that ends before its declared length. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/h2-early-response | Streams an upload and receives the response before the upload ends. | h2.lab.shorenet.co.uk |
/t/hpack-churn | Fetches 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-table | Fetches four of those responses at once on one connection, twice. | h2.lab.shorenet.co.uk |
/t/huge-headers | Fetches a response with about 48 KiB of header fields. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/header-obs-text | Fetches a response whose header values contain bytes above 0x7F. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/set-cookie-multi | Fetches a response with three Set-Cookie fields. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/range | Fetches the whole body, one range, then two ranges. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/head-request | Sends HEAD requests: a response head with no content. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/content-codings | Fetches 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-transport | Fetches a dictionary, then two resources a browser may request compressed against it. | h2.lab.shorenet.co.uk, h1.lab.shorenet.co.uk |
/t/http10 | Fetches a response with an HTTP/1.0 status line. | h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80 |
/t/h1-no-reason | Fetches a response whose status line has no reason phrase. | h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80 |
/t/chunked-ext | Fetches a chunked body with chunk extensions. | h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80 |
/t/conn-close | Fetches 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-delimited | Fetches a redirect whose body is delimited by the connection closing. | h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80 |
/t/close-notify-rst | Fetches a response after which the connection ends with close_notify and a TCP reset. | h1.lab.shorenet.co.uk |
/t/rst-after-response | Fetches a response after which the connection is reset. | h1.lab.shorenet.co.uk, h1.lab.shorenet.co.uk:80 |
/t/ws-echo | Opens a WebSocket and has two messages echoed. | h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk |
/t/ws-plain | The same, with no compression extension. | h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk |
/t/ws-ce-none | Opens a WebSocket whose 101 response carries Content-Encoding: none. | h1.lab.shorenet.co.uk |
/t/ws-server-first | Opens a WebSocket whose server speaks first. | h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk |
/t/ws-large | Opens a WebSocket and receives one 256 KiB message. | h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk |
/t/ws-fragmented | Opens a WebSocket and receives one message in 16 fragments. | h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk |
/t/ws-server-close | Opens a WebSocket that the server closes. | h1.lab.shorenet.co.uk, h2.lab.shorenet.co.uk |