Shorenet for developers

Chart locker

Downloadable captures with known contents: synthetic captures built to exercise the corners of each protocol. Open them in the tool you use and compare what it shows with the expected output.

The TLS captures use a minimal synthetic handshake: there is no certificate and no key-exchange message, so they show decryption, not who the server is.

Each encrypted capture comes with its TLS keys embedded in the file and the same keys as a separate key log; every capture comes with the decoded output a correct tool should produce. The expected output is checked against at least one independent decoder before a capture is published.

Each capture below opens to show its files. Each zip holds the files, the licence text and a SHA256SUMS of them, and is built reproducibly: rebuilding it from the same captures gives the same bytes.

Download the whole Chart locker (.zip)
140.2 KB
SHA-256 d33924727b66cb80753b6be6fe02c1298cec95ab959f1649235d670619e440e9

Check what you downloaded: SHA256SUMS lists the SHA-256 of every file, and manifest.json lists every file with its size and SHA-256. Both are served over HTTPS from this site, and the same SHA-256 is printed beside each file below.

The expected output

Each .expected.json file is JSON: one object whose fields say what a correct decoder finds in the capture. Which fields appear depends on check.

FieldWhat it holds
formatAlways 1: the version of this format.
captureThe capture's id, matching the file names.
checkWhat kind of decoding the other fields describe: http1, h2c, h2-frames, h2-body, h3 or h2-ws.
tls_cipher_suiteThe cipher suite the ServerHello names, in hex (for example 0x1301).
portThe TCP port the cleartext HTTP/2 connection uses.
exchangesEach request and its response, in order: method, path or url, authority, protocol, stream and status, and the body as body_text, or as body_sha256 with body_length.
requestsEach request, in order: its stream and path.
framesEvery HTTP/2 frame, in order: dir (c2s is client to server, s2c is server to client), type, stream, end_stream, headers as [name, value] pairs, the DATA payload as data_text, and the RST_STREAM error code as rst_code.
messagesEvery WebSocket message, in order: stream, dir, opcode, and the payload as text or hex, after inflating.
stream, method, path, statusFor h2-body: the one exchange whose body is checked.
body_sha256, body_length, data_framesFor h2-body: the SHA-256 and length of the reassembled body, and how many DATA frames carried it.

Each .md file describes its capture in plain text.

The smallest decryptable TLS 1.3 exchange
What it tests: The smallest decryptable TLS 1.3 exchange (AES-128-GCM): a minimal synthetic handshake, one HTTP/1.1 GET and its response.
Synthetic · HTTP/1.1, TLS 1.3 · 3.5 KB

What success looks like: The GET and its 200 response decoded, the body byte for byte.

What error it exposes: A key-schedule or record-layer bug.

Download all (.zip)
22.9 KB
SHA-256 a2560311c91e13a48d95bdbcdfe145bbe1de98cd6004cbe2ce23d140f737daef

Expected output, as in tls13-minimal.expected.json:

  • format: 1
  • capture: "tls13-minimal"
  • check: "http1"
  • tls_cipher_suite: "0x1301"
  • exchanges:
    • {"method":"GET","path":"/minimal","status":200,"body_text":"minimal-body"}
HTTP/1.1 over TLS 1.2 with AES-GCM
What it tests: HTTP/1.1 over TLS 1.2 with AES-128-GCM: a minimal synthetic handshake, then one GET and its response.
Synthetic · HTTP/1.1, TLS 1.2 · 2.5 KB

What success looks like: The request and the response decoded.

What error it exposes: A TLS 1.2 PRF or explicit-nonce bug.

Download all (.zip)
21.7 KB
SHA-256 f0b609db18523699df23b3c18f3324fcbfbf6d5b39d9cdc552c48bce727188e7

  • tls12-h1.pcapng 1.1 KB
    SHA-256 829b2b0d9a63b13e333782ba9c3134808756549ddb3a4917424422a19746f2c6
  • tls12-h1.keys.txt 176 B
    SHA-256 9e374083e59e16508bffd1e1916ba6192e91a1648ddabf3af7ae6b56dbd1735d
  • tls12-h1.expected.json 229 B
    SHA-256 c3de73aff6c0f46d624596fe5186b8ec24c8494884c3b0f7b8cfa2a6f7051b05
  • tls12-h1.md 1018 B
    SHA-256 d1ef07c79f76c5d17ccc224d41615911a7ac999e4f42c105f30ba5b73df7d94f

Expected output, as in tls12-h1.expected.json:

  • format: 1
  • capture: "tls12-h1"
  • check: "http1"
  • tls_cipher_suite: "0xc02f"
  • exchanges:
    • {"method":"GET","path":"/tls12","status":200,"body_text":"tls12-body"}
TLS 1.3 traffic keys rotated mid-connection
What it tests: TLS 1.3 traffic keys rotated mid-connection: the server sends a KeyUpdate between two responses on one connection.
Synthetic · HTTP/1.1, TLS 1.3 · 4.3 KB

Its keys file is byte-identical to tls13-minimal's, on purpose: both are built from the same synthetic handshake secrets, and the keys after the KeyUpdate are derived from those (RFC 8446 section 7.2), so the file needs no more lines.

What success looks like: Both requests and both responses decode, including the response sent under the new keys.

What error it exposes: A decoder that keeps using the old traffic secret after a KeyUpdate.

Download all (.zip)
23.7 KB
SHA-256 b682e87fcc9cdcf87ac013cec269479ccaba95974366081e9cb3d779f347f93c

Expected output, as in tls13-key-update.expected.json:

  • format: 1
  • capture: "tls13-key-update"
  • check: "http1"
  • exchanges:
    • {"method":"GET","path":"/pre","status":200,"body_text":"pre-body"}
    • {"method":"GET","path":"/post","status":200,"body_text":"post-body"}
HTTP/2 response sent while the upload is still going
What it tests: An HTTP/2 server sends its complete response (a 413) while the client is still uploading, then resets the stream with NO_ERROR; the rest of the upload arrives after the reset.
Synthetic · HTTP/2 (cleartext) · 3.9 KB

The response here is a 413; the live Sea trials case, h2.lab.shorenet.co.uk/f/h2-early-response, answers 200 instead, with the same framing.

What success looks like: The whole response, and every DATA frame of the upload, including those sent after the reset.

What error it exposes: A parser that waits for the request to end before reading the response.

Download all (.zip)
23.0 KB
SHA-256 36c6a443326d02ef86f4b853e00a52f34ddfc4f0e2fcbf21e7e7cde6ab674392

Expected output, as in h2-early-response.expected.json:

  • format: 1
  • capture: "h2-early-response"
  • check: "h2-frames"
  • port: 80
  • frames:
    • {"dir":"c2s","type":"HEADERS","stream":1,"end_stream":false,"headers":[[":method","POST"],[":scheme","https"],[":authority","upload.example"],[":path","/upload"],["content-type","application/octet-stream"]]}
    • {"dir":"c2s","type":"DATA","stream":1,"end_stream":false,"data_text":"first-chunk-of-the-upload"}
    • {"dir":"s2c","type":"HEADERS","stream":1,"end_stream":false,"headers":[[":status","413"],["content-type","text/plain"]]}
    • {"dir":"s2c","type":"DATA","stream":1,"end_stream":true,"data_text":"payload too large"}
    • {"dir":"s2c","type":"RST_STREAM","stream":1,"rst_code":0}
    • {"dir":"c2s","type":"DATA","stream":1,"end_stream":false,"data_text":"second-chunk-sent-after-the-response"}
    • {"dir":"c2s","type":"DATA","stream":1,"end_stream":true,"data_text":"tail"}
HTTP/2 response body spread over many frames and segments
What it tests: One HTTP/2 response body over TLS 1.3, spread over 60 DATA frames, each in its own TLS record, cut into small TCP segments.
Synthetic · HTTP/2, TLS 1.3 · 73.9 KB

What success looks like: The reassembled 60000-byte body matches its SHA-256.

What error it exposes: Reassembly that drops or doubles bytes.

Download all (.zip)
93.3 KB
SHA-256 574a1004790bc25d32002a4b1da815156ec794217a246d48c997271ef39cedb3

Expected output, as in h2-data-reassembly.expected.json:

  • format: 1
  • capture: "h2-data-reassembly"
  • check: "h2-body"
  • stream: 1
  • method: "GET"
  • path: "/large"
  • status: 200
  • body_sha256: "e661c4ee8abf4381c60c19c2acd1cbb33e7e44f5ed7d7b85815f0022931cf3fb"
  • body_length: 60000
  • data_frames: 60
HTTP/2 headers across an out-of-order segment
What it tests: Twelve cleartext HTTP/2 requests in small TCP segments, one segment out of order; later requests reuse HPACK table entries built by earlier ones.
Synthetic · HTTP/2 (cleartext), HPACK, TCP · 3.3 KB

What success looks like: All twelve request paths decoded.

What error it exposes: HPACK decoded in arrival order instead of stream order.

Download all (.zip)
22.5 KB
SHA-256 89123c7ad2349b27d8ea6439d401896dbbbd77d924e85eae72e3cd79259c96c6

Expected output, as in h2c-headers-out-of-order.expected.json:

  • format: 1
  • capture: "h2c-headers-out-of-order"
  • check: "h2c"
  • port: 80
  • requests:
    • {"stream":1,"path":"/alpha"}
    • {"stream":3,"path":"/beta"}
    • {"stream":5,"path":"/gamma"}
    • {"stream":7,"path":"/delta"}
    • {"stream":9,"path":"/epsilon"}
    • {"stream":11,"path":"/zeta"}
    • {"stream":13,"path":"/eta"}
    • {"stream":15,"path":"/theta"}
    • {"stream":17,"path":"/iota"}
    • {"stream":19,"path":"/kappa"}
    • {"stream":21,"path":"/lambda"}
    • {"stream":23,"path":"/mu"}
HTTP/3 with QPACK instructions out of order
What it tests: Twelve HTTP/3 requests whose QPACK encoder instructions arrive out of order.
Synthetic · HTTP/3, QPACK, QUIC v1 · 17.9 KB

What success looks like: Every request header field decoded.

What error it exposes: A decoder that does not wait for the Required Insert Count.

Download all (.zip)
37.2 KB
SHA-256 80a5ce4c8e97b795c7eb5eb9813212afe6b7324a5968c6bce5c67edfc6195e2e

Expected output, as in h3-qpack-ooo.expected.json:

  • format: 1
  • capture: "h3-qpack-ooo"
  • check: "h3"
  • exchanges:
    • {"stream":0,"method":"GET","authority":"synthetic.example","path":"/alpha"}
    • {"stream":4,"method":"GET","authority":"synthetic.example","path":"/beta"}
    • {"stream":8,"method":"GET","authority":"synthetic.example","path":"/gamma"}
    • {"stream":12,"method":"GET","authority":"synthetic.example","path":"/delta"}
    • {"stream":16,"method":"GET","authority":"synthetic.example","path":"/epsilon"}
    • {"stream":20,"method":"GET","authority":"synthetic.example","path":"/zeta"}
    • {"stream":24,"method":"GET","authority":"synthetic.example","path":"/eta"}
    • {"stream":28,"method":"GET","authority":"synthetic.example","path":"/theta"}
    • {"stream":32,"method":"GET","authority":"synthetic.example","path":"/iota"}
    • {"stream":36,"method":"GET","authority":"synthetic.example","path":"/kappa"}
    • {"stream":40,"method":"GET","authority":"synthetic.example","path":"/lambda"}
    • {"stream":44,"method":"GET","authority":"synthetic.example","path":"/mu"}
WebSocket carried inside HTTP/2
What it tests: WebSocket inside HTTP/2 (RFC 8441 extended CONNECT) over TLS 1.3, with permessage-deflate; two compressed messages share one compression window.
Synthetic · WebSocket, HTTP/2, HPACK, TLS 1.3 · 6.1 KB

What success looks like: Every WebSocket message, inflated.

What error it exposes: No RFC 8441 support, or a lost deflate context.

Download all (.zip)
25.5 KB
SHA-256 3030ad9c09088d87b336ca125e422b8d9f3116d68adc49cff92295ab1961eec7

Expected output, as in h2-websocket-rfc8441.expected.json:

  • format: 1
  • capture: "h2-websocket-rfc8441"
  • check: "h2-ws"
  • exchanges:
    • {"stream":1,"method":"CONNECT","protocol":"websocket","url":"https://ws.example.com/chat?room=1","status":200}
    • {"stream":3,"method":"GET","protocol":null,"url":"https://ws.example.com/other","status":200}
  • messages:
    • {"stream":1,"dir":"c2s","opcode":"TEXT","text":"hello from client"}
    • {"stream":1,"dir":"c2s","opcode":"PING","hex":"70"}
    • {"stream":1,"dir":"s2c","opcode":"TEXT","text":"hello from server"}
    • {"stream":1,"dir":"s2c","opcode":"BINARY","hex":"deadbeef00010203"}
    • {"stream":1,"dir":"s2c","opcode":"TEXT","text":"compressed compressed compressed"}
    • {"stream":1,"dir":"s2c","opcode":"TEXT","text":"compressed again, same window"}

Licence

Licence: the released capture files (.pcapng and .keys.txt) and expected-output files (.expected.json and .md) are licensed under the Creative Commons Attribution 4.0 International licence (CC BY 4.0), with attribution to Shorenet Ltd. It covers only those released files, not the software that generates them, Shorenet's other software, the Shorenet name or branding, or anything else on this site.

The captures contain no third-party content and no personal data: every byte was generated by our own code.