The QUIC Interop Runner tests implementations against each other in a lab matrix. This is the other thing: a public service you can point a client library at to find out whether it handles the protocol's awkward corners.
Offering one requires a server that will misbehave on demand โ emit a reserved frame type, duplicate a SETTINGS identifier, open a control stream with the wrong frame. That is not something a CDN customer can arrange, because they do not own the implementation. We fork the QUIC stack, so we can.
Running it
Each test has its own UDP port. Connect to it, and the server records what your client did. Results are filed under a session started from your address, so the test connections need nothing special.
# start a session
SESSION=$(curl -sX POST https://conformance.pqcrypta.com/session \
| sed 's/.*"id":"//;s/".*//')
# walk the catalogue
curl -s https://conformance.pqcrypta.com/catalog.json \
| grep -o '"port": [0-9]*' | grep -o '[0-9]*' | while read PORT; do
curl -s --http3-only --max-time 15 \
"https://conformance.pqcrypta.com:$PORT/" -o /dev/null
done
# read the result
curl -s https://conformance.pqcrypta.com/report/$SESSION.json
The report is also available as a page at
/report/<session> and as an SVG badge at
/badge/<session>.svg.
How a verdict is reached
The server is the judge. A library that crashes on an unknown frame is in no position to report on itself, so every verdict comes from what the server observed.
Every test ends with a liveness probe: after the anomaly, the server expects one ordinary request on the same connection. For the extensibility tests this is the whole game โ "ignored it" and "died on it" look identical on the wire up to that point, and without the probe a crashed client would read as compliant.
Extensibility
Ignore something unrecognised and carry on. Rejecting it is the failure โ that is how protocols ossify.
Correctness
Reject something invalid, with the specific error code the RFC names. Accepting it is the bug.
Interoperability
Correctly decode something valid but demanding โ Huffman-coded fields, a dynamic-table reference, trailers.
Resilience
Recover โ retry, re-probe, migrate โ rather than give up.
Discretionary
The RFC permits more than one answer. Both pass; the report says which yours chose.
Inconclusive
The run never put your client in the situation the test is about. It proves nothing either way, and says so.
Those last two exist because the model was wrong twice. Scoring a MAY as a MUST failed clients for making a legal choice, and scoring "the request never reached the window" as a failure accused clients of accepting violations that were never sent. A suite that wrongly accuses the thing it tests is worse than no suite, because nobody then believes the failures that are real.
The catalogue
Read live from the suite, so this table cannot drift from what is actually served. Every entry cites the clause it exercises.
HTTP/3 layer
| Test | Port | Class | Specification |
|---|---|---|---|
h-grease-settings
Reserved SETTINGS identifier (0x1fยทN+0x21)
Ignore the unknown setting and complete the request.
|
4473 | Extensibility | RFC 9114 ยง7.2.4.1 |
h-grease-frame
Reserved frame type on the response stream
Skip the frame using its length and read the response that follows.
|
4474 | Extensibility | RFC 9114 ยง7.2.8 |
h-reserved-uni-stream
Unidirectional stream with a reserved stream type
Abort reading the stream or discard it โ ยง6.2.3 permits either. What it forbids is treating it as meaningful, or as fatal to the connection.
|
4475 | Extensibility | RFC 9114 ยง6.2.3 |
h-duplicate-setting
SETTINGS containing the same identifier twice
Either reject with H3_SETTINGS_ERROR or ignore the repeat โ the specification says a receiver MAY treat this as an error, so both are conformant.
|
4476 | Discretionary | RFC 9114 ยง7.2.4 |
h-control-frame-unexpected
A DATA frame on the control stream
Close the connection with H3_FRAME_UNEXPECTED.
|
4477 | Correctness | RFC 9114 ยง7.2.1 |
h-missing-settings
Control stream whose first frame is not SETTINGS
Close the connection with H3_MISSING_SETTINGS.
|
4478 | Correctness | RFC 9114 ยง6.2.1 |
h-second-control-stream
A second control stream opened by the server
Close the connection with H3_STREAM_CREATION_ERROR.
|
4479 | Correctness | RFC 9114 ยง6.2.1 |
h-qpack-dynamic-table
Field lines referencing dynamic table insertions
Apply the encoder-stream insertions and decode the headers correctly.
|
4480 | Interoperability | RFC 9204 ยง4.3.3, ยง4.5.2 |
h-qpack-huffman
Huffman-coded field lines with maximal padding
Decode without error. Padding of up to 7 bits is legal, not corruption.
|
4481 | Interoperability | RFC 9204 ยง4.1.2, RFC 7541 ยง5.2 |
h-oversized-field-section
Field section larger than the client's advertised maximum
Handle it as an error against that one request, not the whole connection.
|
4482 | Interoperability | RFC 9114 ยง4.2.2 |
h-trailers
Trailing field section after the body
Deliver the trailers to the application after the body completes.
|
4483 | Interoperability | RFC 9114 ยง4.1 |
h-early-hints
103 Early Hints before the final response
Treat 103 as informational and keep reading for the final response.
|
4484 | Resilience | RFC 8297 |
h-goaway
GOAWAY sent mid-connection
Stop opening requests, finish those in flight, and retry idempotent ones elsewhere.
|
4485 | Resilience | RFC 9114 ยง5.2 |
h-max-push-id
MAX_PUSH_ID sent by the server
Reject with H3_FRAME_UNEXPECTED. MAX_PUSH_ID travels client to server only, so a server sending one is using a frame in a direction the specification does not allow.
|
4486 | Correctness | RFC 9114 ยง7.2.7 |
QUIC layer
| Test | Port | Class | Specification |
|---|---|---|---|
q-version-negotiation
Version Negotiation offering a reserved version alongside v1
Abandon the connection attempt, or retry with a version both ends support. ยง6.2 requires a client that supports only one version to abandon it rather than persist.
|
4460 | Correctness | RFC 9000 ยง6 |
q-retry
Retry packet for source-address validation
Echo the Retry token in a second Initial packet and complete the handshake.
|
4461 | Correctness | RFC 9000 ยง8.1.2 |
q-reserved-transport-param
Reserved transport parameter (31ยทN+27)
Ignore the unknown parameter and complete the handshake normally.
|
4462 | Extensibility | RFC 9000 ยง18.1 |
q-reserved-frame
Unknown frame type in a 1-RTT packet
Close the connection with FRAME_ENCODING_ERROR. Unlike HTTP/3, QUIC reserves no ignorable frame types โ ยง12.4 makes an unknown frame a connection error, so ignoring it is the failure here.
|
4463 | Correctness | RFC 9000 ยง12.4 |
q-cid-rotation
NEW_CONNECTION_ID followed by RETIRE_CONNECTION_ID
Adopt the new connection ID, retire the old one, and stay connected.
|
4464 | Resilience | RFC 9000 ยง5.1 |
q-stateless-reset
Stateless reset
Recognise the token and close immediately. The packet cannot be authenticated, so there is nothing to reply with and nothing to report to the peer โ the connection simply ends.
|
4465 | Correctness | RFC 9000 ยง10.3 |
q-flow-control
Deliberately tight MAX_DATA and MAX_STREAM_DATA
Respect the limit. Announcing the stall with DATA_BLOCKED or STREAM_DATA_BLOCKED is a SHOULD in ยง4.1, not a MUST, so a client that stays silent is still conformant.
|
4466 | Discretionary | RFC 9000 ยง4 |
q-ack-frequency
ACK Frequency extension offered
Negotiate the extension, or ignore it. Either is correct; failing is not.
|
4467 | Discretionary | draft-ietf-quic-ack-frequency |
q-ecn
Packets marked ECT(0)
Report the ECN counts back in ACK_ECN frames.
|
4468 | Correctness | RFC 9000 ยง13.4 |
q-pmtu-blackhole
Path MTU black hole above a threshold
Detect the black hole, probe down to a working size, and keep the connection.
|
4469 | Resilience | RFC 9000 ยง14, RFC 8899 |
q-path-challenge
Server-initiated PATH_CHALLENGE
Reply with PATH_RESPONSE carrying the identical 8-byte payload.
|
4470 | Correctness | RFC 9000 ยง8.2 |
q-zero-rtt-reject
0-RTT rejected after the client sends early data
Reset the state of every stream, including application state bound to them. Section 4.6.2 requires the reset because a rejected 0-RTT means every assumed connection characteristic may have been wrong. It does not require retransmission, which is the application concern, not QUIC's.
|
4471 | Resilience | RFC 9001 ยง4.6.2 |
q-multipath
A second path offered mid-connection
Use the additional path, or decline it cleanly. Do not abort the connection.
|
4472 | Discretionary | draft-ietf-quic-multipath |
What this cost to get right
Building it turned up more bugs in the suite than in any client. Every failure it reported during development was ours, and each would have produced a false verdict:
- Any stream counted as the liveness probe โ but every client opens its control stream before reading a byte of our SETTINGS, so every extensibility test passed the moment a client connected.
- The critical streams were held in the wrong scope, so dropping them closed the control stream โ our violation, scored against the client.
- Verdicts were decided at the probe, before the client had read the anomaly, so a correct rejection was recorded as acceptance.
- A hand-written Huffman table produced the wrong code for several characters. It looked entirely plausible; only the RFC 7541 test vectors caught it.
- The QPACK dynamic table was used without checking whether the client had granted any capacity. The default is zero.
Every expected_code in the catalogue has since been checked
against the published RFC text rather than recollection, and three tests
are pinned to the sentence that requires them so a later edit cannot
drift.
Status
This is new and openly incomplete. Tests whose anomaly is not yet implemented serve a correct control stream and record inconclusive โ never a pass, and never a failure for something that was not sent.
If your client scores a failure you believe is wrong, say so. Given the above, the suite is the more likely culprit, and a false accusation is a bug worth fixing quickly.