Setup for driving the pure-Kotlin QUIC client against real reference
servers, kicking off the live-interop validation that closes the loop on
the audit cycles.
PermissiveCertificateValidator (commonMain test-only):
Accepts any cert chain and any signature. For local dev servers (picoquic,
quic-go, nests-rs in Docker) where the system trust store would reject the
self-signed cert. Documented as test-only — must never be wired into
production code.
InteropRunner.main (jvmTest):
Standalone runnable that opens a UDP socket, builds a QuicConnection with
PermissiveCertificateValidator, drives the handshake under a configurable
timeout, and reports CONNECTED / HandshakeFailed / Timeout / UdpFailed
with peer transport parameters on success. Exit code 0 on connect, 1 on
any failure mode — wirable into CI.
`quic/scripts/run-picoquic.sh`:
One-line Docker harness that runs Christian Huitema's picoquic reference
server on UDP 4433. Picoquic is the IETF QUIC WG's reference impl and
the most permissive target for a fresh client (clear qlog traces, accepts
a wide range of transport params).
`./gradlew :quic:interop` Gradle task:
Wraps the runner so live-interop is one command:
./gradlew :quic:interop -PinteropHost=127.0.0.1 -PinteropPort=4433
Validated against a non-running server: returns Timeout cleanly with
exit 1 (the failure-path proves the harness wires correctly).
Workflow documented in `quic/scripts/README.md` covering picoquic,
quic-go, aioquic, quiche, and the eventual graduation path to the
quic-interop-runner Docker matrix at https://interop.seemann.io.
Next step: actually run picoquic in Docker and chase whatever real-world
incompatibilities surface. Each will be a focused fix commit.
https://claude.ai/code/session_01EC1tfXfap8k8GyKvrxkxZx
:quic interop harness
Scripts and test entry points for driving the pure-Kotlin QUIC client against real reference servers.
Quickstart — picoquic
# Terminal 1: run the picoquic reference server in Docker.
quic/scripts/run-picoquic.sh -d
# Terminal 2: drive our client at it.
./gradlew :quic:jvmTestClasses
java -cp "$(./gradlew -q :quic:printTestRuntimeClasspath)" \
com.vitorpamplona.quic.interop.InteropRunnerKt 127.0.0.1 4433
Expected output:
== :quic interop runner ==
target: 127.0.0.1:4433
timeout: 10s
✓ HANDSHAKE COMPLETE
status: CONNECTED
negotiated ALPN: h3
peer transport params: max_data=…, max_streams_bidi=…, …
What this proves
- TCP-equivalent UDP connection setup
- QUIC v1 Initial / Handshake / 1-RTT packet flow with RFC-correct PADDING
- TLS 1.3 over QUIC with the SHA-256 cipher suites
- ALPN
h3negotiation - QUIC transport parameters round-trip
- Header protection (AES-ECB)
- AEAD payload protection (AES-128-GCM)
It does NOT yet prove WebTransport / MoQ — picoquic doesn't speak WT.
Live nests interop
For a full WebTransport + MoQ test against the actual Nostr nests server, the
target is nostrnests.com:443:
java -cp "..." com.vitorpamplona.quic.interop.InteropRunnerKt nostrnests.com 443
This goes against the real CA-signed cert, so revert to the default
JdkCertificateValidator (the InteropRunner currently uses
PermissiveCertificateValidator for self-signed dev servers — change the
constant before pointing at production).
Other reference servers worth trying
| Server | Image | Notes |
|---|---|---|
| picoquic | privateoctopus/picoquic |
Most permissive; clear qlog traces |
| quic-go | martenseemann/quic-go-interop |
Stable, widest scenario coverage |
| aioquic | aiortc/aioquic |
Easy to debug, Python reference |
| quiche | cloudflare/quiche |
Production-grade, strict |
| nests-rs | (local cargo build) | The actual MoQ relay; needs WebTransport |
The IETF's quic-interop-runner
exposes all of these via a single Docker matrix. Wrapping our client in its
container contract (TESTCASE env, REQUESTS= URL list, /certs mount) would
let us join the public matrix at https://interop.seemann.io.