erlang_quic Features
View SourceCore Protocol (RFC 9000)
Connection Management
- [x] Connection establishment with TLS 1.3 handshake
- [x] Connection close (immediate and draining states)
- [x] Idle timeout enforcement (configurable via
idle_timeoutoption) - [x] Lazy idle and keep-alive timers: armed once and re-armed only when they
fire, using the
last_activitytimestamp, so steady-state traffic does not cancel and reschedule a timer on every packet - [x] Version negotiation: server emits a Version Negotiation packet for an unknown version; a client that receives one and shares no version closes with
{version_negotiation, Versions}(RFC 9000 §6.2) - [x] Retry packets for address validation
- [x] IPv6 client connections: hostname, IP-literal (bracketed or bare), or
inet:ip_address()tuple host - [x] Happy Eyeballs v2 (RFC 8305): dual-stack hostnames race IPv6-first, with
happy_eyeballs,family,connection_attempt_delayandconnect_timeoutoptions onquic:connect/4 [x] Latency spin bit (RFC 9000 §17.4) with
spin_bit => true | false- [x] NEW_TOKEN frame dispatch (server rejects peer-received tokens per §8.1.3); client caches received tokens keyed by
{Host, Port}and reuses them in the Initial of the next connect to the same endpoint - [x] Stateless reset (RFC 9000 §10.3): listener emits resets for orphan packets; per-connection
NEW_CONNECTION_IDtokens share the listener's HMAC secret so they match orphan-path tokens - [x] Server-side address validation (RFC 9000 §8.1): opt in with
address_validation => alwaysonquic:start_server/3. Listener emits a Retry packet with an HMAC-signed retry token when a client Initial arrives without one; subsequent Initials carrying a valid token skip retry and spawn a connection that echoesretry_source_connection_id. Server issues a NEW_TOKEN after handshake so the next reconnect skips retry entirely
Streams
- [x] Bidirectional streams (client and server initiated)
- [x] Unidirectional streams
- [x] Stream prioritization (RFC 9218) with 8 urgency levels
- [x] Incremental delivery flag support
- [x] RESET_STREAM_AT extension (draft-ietf-quic-reliable-stream-reset-11)
Flow Control
- [x] Connection-level flow control (MAX_DATA)
- [x] Stream-level flow control (MAX_STREAM_DATA)
- [x] MAX_STREAMS limits (bidirectional and unidirectional)
- [x] HTTP/3 receive backpressure:
flow_control => manualonquic_h3:set_stream_handler/4orquic_h3:request/3, credit returned withquic_h3:consume/3
Packet Handling
- [x] Initial, Handshake, and 1-RTT packet types
- [x] Short header (1-RTT) packets
- [x] Packet number encoding (1-4 bytes)
- [x] Packet number reconstruction per RFC 9000 Appendix A
- [x] Coalesced packets
- [x] Frame coalescing (ACK + small stream data in single packet)
- [x] ACK frequency extension (draft-ietf-quic-ack-frequency-14, the latest revision, expired 2026-08-09 while the working group prepares its write-up): every connection advertises
min_ack_delay(1 ms) and follows the peer's ACK_FREQUENCY and IMMEDIATE_ACK; with BBR it also asks a peer that advertisedmin_ack_delayfor fewer ACKs in steady state. PTO probes, PMTU probes and the first packet after a client migration carry IMMEDIATE_ACK to such a peer - [x] HTTP/3 response HEADERS coalesced with the first DATA frame so the response headers and first body bytes ride in one 1-RTT packet (a large body still fragments; only the standalone HEADERS packet is removed)
Connection Migration (RFC 9000 Section 9)
- [x] PATH_CHALLENGE / PATH_RESPONSE validation
- [x] Active connection migration (
quic:migrate/1,quic:migrate/2) - [x] Preferred address handling (RFC 9000 Section 9.6)
- [x] Server-side address change detection (NAT rebinding and active migration)
- [x] Congestion control reset on path change (RFC 9002 Section 9.4)
- [x] Per-path connection IDs, bound before the path's first packet, with the
replaced one retired (RFC 9000 Section 9.5). A migration with no unused CID
is refused rather than reusing one:
quic:migrate/1,2returns{error, no_available_connection_id} - [x]
disable_active_migrationtransport parameter support - [x] Path validation timeout with retry (3 * PTO, up to 3 attempts)
Not yet enforced: the Section 9.3 limit on bytes sent to an unvalidated path, and the Section 9.4 rule that old-path packets must not be charged to the new path's congestion controller.
Connection ID Management
- [x] Multiple connection IDs
- [x] NEW_CONNECTION_ID frames
- [x] RETIRE_CONNECTION_ID frames
- [x] Active connection ID limit
Loss Detection & Congestion Control (RFC 9002)
Loss Detection
- [x] Packet loss detection
- [x] Bytes in flight count the packets that are ack-eliciting or carry PADDING (Section 2), a client's padded ACK-only Initial included; the loss tracker and the congestion controller count the same packets. Packets that are not ack-eliciting are never held back by the congestion window (Section 7)
- [x] Per-packet-number-space recovery (RFC 9002 Appendix A): each space keeps its own sent packets and loss state, while the RTT estimate, the probe backoff and bytes in flight stay connection-wide
- [x] Probe timeout (PTO), armed for one space at a time and probing at that encryption level. The application space is not armed until the handshake is confirmed (Section 6.2.1), an endpoint over its anti-amplification limit arms nothing, and a client with nothing in flight and an unvalidated address arms the anti-deadlock probe
- [x] Packet number space discard on key discard (Section 6.4), removing those packets from both the loss tracker and the congestion controller
- [x] Handshake packets are admitted against the congestion window like any other (Section 7). A refusal holds the frames rather than the encoded packet, so no packet number is spent, and probes are exempt
- [x] Recovery and congestion state reset on Retry (Section 6.3), preserving the data to resend and the controller's configuration
- [x] RTT measurement (smoothed RTT, RTT variance)
The probe interval is capped at 5 seconds, which RFC 9002's unbounded exponential backoff does not require; this is a deliberate bound on how late a probe may arrive. Time-threshold loss detection runs on acknowledgement rather than from its own timer.
Congestion Control
- [x] Pluggable congestion control behavior
- [x] NewReno (default, RFC 9002)
- [x] CUBIC (RFC 9438)
- [x] BBR (Bottleneck Bandwidth and RTT), asking the peer for fewer ACKs in steady state through the ACK frequency extension
- [x] HyStart++ slow start (RFC 9406) for all algorithms
- [x] Slow start with improved exit detection
- [x] Congestion avoidance
- [x] Recovery on packet loss
- [x] Persistent congestion detection (RFC 9002 Section 7.6.2): two lost ack-eliciting packets sent after the first RTT sample, more than the Section 7.6.1 duration apart (the PTO without its exponential backoff, times 3), with no packet sent between them acknowledged. Another packet number space is known only by the newest packet it acknowledged, so its acknowledgments end the period conservatively
- [x] ECN support (ECN-CE triggers congestion response)
- [x] Packet pacing (RFC 9002 Section 7.7) to prevent bursts
- [x] RTT-based flow control auto-tuning
Path MTU Discovery (RFC 8899 - DPLPMTUD)
- [x] Binary search probing for optimal MTU
- [x] Integration with peer's
max_udp_payload_sizetransport parameter - [x] Black hole detection and recovery
- [x] Automatic MTU reset on connection migration
- [x] Periodic re-probing for MTU increases
- [x] Congestion control integration (updates cwnd-related parameters)
TLS 1.3 Integration (RFC 9001)
Handshake
- [x] Full TLS 1.3 handshake
- [x] ALPN negotiation
- [x] Transport parameters exchange
- [x] Certificate verification
- [x] HelloRetryRequest (RFC 9846 §4.2.4)
Key-exchange groups
- [x] x25519 (default)
- [x] secp256r1, secp384r1 (opt-in via
groups) - [x] x25519mlkem768 post-quantum hybrid (draft-ietf-tls-ecdhe-mlkem;
opt-in via `groups`, needs the ML-KEM APIs in `crypto`, OTP 28.1+) - [x] Multi-group
key_share+supported_groupsnegotiation - [ ] secp521r1, x448 (constants only; see Roadmap)
Signature algorithms
- [x] ECDSA secp256r1-SHA256, secp384r1-SHA384
- [x] RSA-PSS-RSAE SHA256/384/512
- [x] Ed25519
- [x] Per-handshake
signature_algorithmsnegotiation (signature_algs) - [ ] Ed448, ECDSA secp521r1-SHA512 (constants only; see Roadmap)
Encryption
- [x] AES-128-GCM cipher suite
- [x] AES-256-GCM cipher suite
- [x] ChaCha20-Poly1305 cipher suite
- [x] Header protection
- [x] Key derivation (HKDF)
Key Management
- [x] Initial secrets derivation
- [x] Handshake secrets
- [x] Application secrets
- [x] Key updates (RFC 9001 Section 6)
Session Resumption
- [x] Session tickets (NewSessionTicket)
- [x] PSK-based resumption
- [x] 0-RTT early data
External PSK (RFC 9846 §4.3.11)
- [x]
psk_dhe_kemode (forward-secret) - [x]
psk_kemode (no DHE) - [x] Server-side binder verification (constant-time)
- [x] Identity lookup via callback and/or static map
- [x] Cert + PSK coexistence on the same listener
- [x] Client downgrade protection
See docs/PSK.md. Pending follow-ups tracked in the Roadmap below.
QUIC Version 2 (RFC 9369)
- [x] Version 2 (0x6b3343cf) support
- [x] Updated initial salt
- [x] Updated retry integrity tag key
QUIC-LB Load Balancer Support (draft-ietf-quic-load-balancers-22)
- [x] Server ID encoding in Connection IDs for LB routing
- [x] Config rotation bits for LB coordination
- [x] Variable CID length support (1-20 bytes)
- [x] Two encoding algorithms:
- Plaintext: Server ID visible in CID (no encryption)
- Encrypted: AES-128-ECB, one pass when server ID and nonce are 16 bytes, a four-pass Feistel network otherwise
- [x] LB-aware CID generation in listener and connection
Reliable Stream Reset (draft-ietf-quic-reliable-stream-reset-11)
RESET_STREAM_AT allows resetting a stream while ensuring data up to a specified offset is reliably delivered. Required for WebTransport where stream headers must be received even if the stream is immediately reset.
Features
- [x] Frame type 0x24 (RESET_STREAM_AT) encode/decode
- [x] Transport parameter negotiation (0x1d, and 0x17f7586d2cb571 for draft -07 peers)
- [x] Reliable delivery guarantee up to ReliableSize
- [x] Retransmission filtering (data beyond ReliableSize not retransmitted)
- [x] Validation: ReliableSize cannot exceed FinalSize
- [x] Validation: ReliableSize cannot be increased after initial reset
- [x] Validation: ErrorCode cannot change after initial reset
Usage
%% Enable in connection options (both client and server)
Opts = #{reset_stream_at => true, alpn => [<<"webtransport">>]},
{ok, Conn} = quic:connect(Host, Port, Opts, self()),
%% Send stream header (e.g., WebTransport session ID)
{ok, StreamId} = quic:open_stream(Conn),
ok = quic:send_data(Conn, StreamId, Header, false),
%% Reset stream but ensure header is delivered
ok = quic:reset_stream_at(Conn, StreamId, ErrorCode, byte_size(Header)).When the peer stops the stream with STOP_SENDING, quic resets it by itself with a reliable size of 0 (draft-ietf-quic-reliable-stream-reset-11 Section 5.4). Give the stream its reliable size up front and that reset keeps the header too, as WebTransport needs:
ok = quic:set_stream_reliable_size(Conn, StreamId, byte_size(Header)).API
Connection
quic:connect/4- Connect to serverquic:close/1,2,3- Close connection (with optional app error code)quic:peername/1- Get peer addressquic:sockname/1- Get local addressquic:peercert/1- Get peer certificatequic:migrate/1,2- Trigger connection migration (with optional timeout)quic:has_early_keys/1- Whether 0-RTT (early data) keys are availablequic:early_data_accepted/1- Whether the server accepted 0-RTT early data
Datagrams (RFC 9221)
quic:send_datagram/2- Send unreliable datagramquic:datagram_max_size/1- Largest payloadsend_datagram/2accepts now, at the confirmed path MTU (0 if unsupported)quic:datagram_stats/1- Delivered / dropped / sent counters (backpressure); DATAGRAMs that arrive while the owner is behind are dropped
HTTP Datagrams (RFC 9297)
quic_h3:send_datagram/3- Send an HTTP datagram bound to a request streamquic_h3:h3_datagrams_enabled/1- Whether both sides negotiated supportquic_h3:max_datagram_size/2- Largest payload per datagram for a given stream that can be sent now- Owner event:
{quic_h3, Conn, {datagram, StreamId, Payload}} - Set
h3_datagram_enabled => trueonconnect/3/start_server/3to enable. CONNECT-UDP (RFC 9298) builds on this in a separate library.
Capsule Protocol (RFC 9297 §3.2)
quic_h3_capsule:encode/2- EncodeType | Length | Valueas an iolistquic_h3_capsule:decode/1- Decode one capsule, or report that more bytes are needed- Constants in
include/quic_h3.hrl:?H3_CAPSULE_DATAGRAM(0x00),?H3_CAPSULE_LEGACY_DATAGRAM(0xff37a0) - A pure codec with no callers inside this library: extension libraries
drive it. Same wire format as
h1_capsuleandh2_capsule.
HTTP/3 Extension Streams
quic_h3:open_bidi_stream/1,2- Open a client-initiated bidi stream; with a non-negativeSignalTypevarint the stream is pre-claimed and inbound bytes route as{stream_type_data, bidi, ...}owner messages instead of HTTP/3 request frames (e.g. WebTransport's0x41)stream_type_handleroption onstart_server/3claims peer-initiated uni / bidi streams whose first varint matches a caller-supplied filter- Owner events:
{stream_type_open, Direction, StreamId, VarintType},{stream_type_data, Direction, StreamId, Data, Fin},{stream_type_closed, Direction, StreamId},{stream_type_reset, Direction, StreamId, ErrorCode},{stream_type_stop_sending, Direction, StreamId, ErrorCode} - Per-connection owner override via
connection_handlercallback onstart_server/3for hosting many sessions on one listener quic_h3:accept/2- Start HTTP/3 on a connection analpn_handlerrouted by ALPN, so one listener serves HTTP/3 beside other protocols
Streams
quic:open_stream/1- Open bidirectional streamquic:open_unidirectional_stream/1- Open unidirectional streamquic:send_data/4,5- Send data on stream (Fin = truecloses the send side)quic:send_data_async/4- Send without waiting for the connection processquic:set_stream_flow_control/3-autoormanualreceive credit for a streamquic:consume/3- Return receive credit on amanualstreamquic:stop_sending/3- Ask the peer to stop sending on a streamquic:reset_stream/3- Reset stream with error codequic:reset_stream_at/4- Reset stream with reliable delivery up to specified sizequic:set_stream_reliable_size/3- Make the resets quic sends on its own for a stream (answering STOP_SENDING, a send deadline) RESET_STREAM_AT with this reliable size, capped at what was written, when the peer supports itquic:set_stream_priority/4- Set stream priority (urgency, incremental)quic:get_stream_priority/2- Get stream priority
Flow Control Accounting
quic_flow:new/0,1- Create accounting statequic_flow:can_send/2,on_data_sent/2,on_max_data_received/2,send_blocked/1- Send sidequic_flow:on_data_received/2,should_send_max_data/1,generate_max_data/1- Receive sidequic_flow:bytes_sent/1,bytes_received/1,send_limit/1,recv_limit/1,send_window/1,recv_window/1- Queries- Connection-level only, and it emits no frames.
quic_connectiondoes not use it: the flow control that runs on the wire is inline there, auto-tuned from the RTT and with per-stream limits.
Server / Multi-Pool Server Management
quic:start_server/3- Start named server poolquic:stop_server/1- Stop named serverquic:get_server_info/1- Get server informationquic:get_server_port/1- Get server listening portquic:get_server_sockname/1- Get server bound address ({IP, Port})quic:get_server_connections/1- Get server connection PIDsquic:which_servers/0- List all running servers
Load Balancer (draft-ietf-quic-load-balancers-22)
quic_lb:new_config/1- Create LB configuration from options mapquic_lb:new_cid_config/1- Create CID generation configurationquic_lb:generate_cid/1- Generate CID with encoded server_idquic_lb:decode_server_id/2- Extract server_id from CIDquic_lb:is_lb_routable/1- Check if CID has valid LB routing bitsquic_lb:get_config_rotation/1- Get config rotation bits from CIDquic_lb:expected_cid_len/1- Calculate expected CID length from config
Options
idle_timeout- Connection idle timeout in milliseconds (0 to disable). It also bounds the handshake as a whole: a handshake that has not completed within it from its start is closed, and the owner gets{closed, handshake_timeout}max_data- Connection-level flow control limitmax_stream_data_bidi_local- Stream-level limit for streams we openmax_stream_data_bidi_remote- Stream-level limit for streams the peer opensmax_stream_data_uni- Stream-level limit for unidirectional streamsmax_receive_window- Largest window the receive limits grow to (default: 8 MiB, minimum: 16 KiB)max_owner_queue- Receive credit waits, and DATAGRAMs are dropped, while the owner's mailbox holds more messages than this (default: 1000, orinfinity)max_datagram_frame_size- Max datagram size to accept (0 = disabled, default: 0)datagram_recv_queue_len- Bounded receive queue for inbound datagrams (default:infinity; drops oldest on overflow, tracked viadatagram_stats/1)reset_stream_at- Enable RESET_STREAM_AT extension (default: false)notify_streams_available- Afterquic:open_stream/1orquic:open_unidirectional_stream/1is refused withstream_limit, the owner receives{quic, Conn, {streams_available, bidi | uni}}once the peer's MAX_STREAMS lets an open succeed again, one message per refusal (default: false).quic_h3:connect/3andquic_h3:start_server/3take it too and pass on{quic_h3, Conn, {streams_available, bidi}}stream_reset_final_size- The owner's notice of a peer's reset is{stream_reset, StreamId, ErrorCode, FinalSize}instead of{stream_reset, StreamId, ErrorCode},FinalSizebeing the RESET_STREAM or RESET_STREAM_AT final size (default: false).quic_h3sets it on the connections it ownsactive_connection_id_limit- How many of the peer's connection IDs to hold, counting the handshake CID (default: 2). Advertised to the peer and enforced on inbound NEW_CONNECTION_ID; exceeding it closes with CONNECTION_ID_LIMIT_ERRORalpn- ALPN protocols listverify- Server certificate verification on the client (default:true; verifies the CertificateVerify signature, the chain, and the hostname). The hostname check follows RFC 9525: it matches the subjectAltName only, never the subject CN, and a wildcard SAN such as*.example.commatches one leftmost label, sohost.example.combut notexample.comora.b.example.com. Setfalseto accept any certificate, e.g. a self-signed test server.cacerts- Trust anchors for chain validation, as a list of DER-encoded certificates. On a client, for the server's chain (default: the operating system trust store). On a server that asks for client certificates, for the client's chain: required there, withcacertfileas the alternative, and the start fails with{error, client_auth_needs_cacerts}without eitherrequire_client_cert- On a server: ask the client for a certificate and refuse one that sends none (certificate_required), whateververifysays (default:false). A server withverify => true(orverify_peer) alone asks for a certificate, validates one if sent, and still accepts a client without one.quic:peercert/1returns the client's DER leaf once validated. A client certificate with an Extended Key Usage must listclientAuth, and one with a Key Usage must setdigitalSignaturecacertfile- Path to a PEM file holding those anchors, read once when the connection or server starts. Ignored whencacertsis given; an unreadable file, or one with no certificate in it, fails the callpreferred_ipv4- Server preferred IPv4 addresspreferred_ipv6- Server preferred IPv6 addresspool_size- Number of listener processes for server pools (default: 1)connection_handler- Callback for handling new connectionsalpn_handler- On a server,fun((Conn, Alpn) -> {ok, Owner} | {error, Reason})called in the connection process once the ClientHello has negotiatedAlpn, before any stream data;Ownerbecomes the connection's owner. An error, a bad result or an exception fails the handshake withhandshake_failure. Not called when no ALPN is negotiated. A value that is not a fun of arity 2 failsquic:start_server/3with{error, {invalid_alpn_handler, Value}}monitor_owner- Stop the connection with{shutdown, owner_down}and send CONNECTION_CLOSE when its owner process exits (default:truefor accepted connections,falsefor client connections, which are linked to their caller unless supervised). Setfalsein the listener options to keep an accepted connection alive after its handler exitslb_config- QUIC-LB configuration map for load balancer routingkeep_alive_interval- Keep-alive PING interval (disabled,auto, or milliseconds)pmtu_enabled- Enable Path MTU Discovery (default: true)pmtu_max_mtu- Maximum MTU to probe (default: 1500)max_udp_payload_size- Largest UDP payload this endpoint is willing to receive, advertised as the RFC 9000 §18.2 transport parameter (minimum 1200; default: what a 1500-byte path carries for the address family, 1472 over IPv4 and 1452 over IPv6)recbuf- UDP receive buffer size in bytes (default: 7MB)sndbuf- UDP send buffer size in bytes (default: 7MB)server_send_batching- Per-connection send batching on the server (default: true). On Linux +socket_backend => socketwith UDP_SEGMENT, outgoing packets are coalesced into GSO super-datagrams viasendmsgcmsg; neutral on macOS / gen_udp. Set tofalseto fall back to directgen_udp:send/4hibernate_after- Hibernate an idle connection process after this many milliseconds, running a fullsweep so handshake garbage is not pinned to a heap that never collects (default: 5000;infinityopts out)versions- QUIC versions this connection also accepts, for RFC 9368 compatible version negotiation (default: the negotiatedversionalone)ciphers- TLS 1.3 cipher suites to offer or accept, in preference order (default:[aes_128_gcm, aes_256_gcm, chacha20_poly1305]on a CPU with AES instructions,[chacha20_poly1305, aes_128_gcm, aes_256_gcm]without them, per/proc/cpuinfo). A server honours a client that listschacha20_poly1305first whenever its own list allows the suite, so a peer without AES hardware gets ChaCha from either sidemax_burst_packets- Packets allowed to leave per send drain, so a large queued write cannot monopolise the scheduler (default: 64)ack_packet_tolerance- Ack-eliciting packets received before a 1-RTT ACK is sent, the RFC 9000 section 13.2.1 decimation threshold (default: 2). A peer's ACK_FREQUENCY request replaces it for the connectiondisconnect_timeout- Milliseconds without a response from the peer before the connection is given up on, checked on its own timer (default: 16000). Withidle_timeout => 0it bounds the handshake as a whole instead: a handshake that has not completed within it from its start is closedpmtu_raise_interval- Milliseconds between PMTU raise probes (default: 600000)delivery_coalescing- Merge consecutive same-streamstream_datadeliveries of one receive pass into a single owner message (default:false). Arrival order across streams is preserved and astream_resetnever overtakes data delivered before it, but an owner that counts messages rather than bytes sees fewer of them
PMTU Discovery
quic:get_mtu/1- Get current effective MTU for a connection
Erlang Distribution (quic_dist)
QUIC-based Erlang distribution protocol implementation.
Features
- [x] Full distribution protocol over QUIC transport
- [x] TLS 1.3 encryption built-in (no separate SSL setup)
- [x] 0-RTT session resumption for fast reconnection
- [x] Multiple streams: control (urgency 0) + data (urgency 4-6)
- [x] Stream prioritization for tick/control messages
- [x] QUIC-level liveness detection (packet counts, not blocked by flow control)
- [x] Keep-alive PING frames for transport liveness
- [x] Backpressure mechanism for congestion control
- [x] Session ticket storage for 0-RTT
Modules
quic_dist- Distribution protocol callbacksquic_dist_controller- Per-connection state machinequic_dist_sup- Distribution supervisorquic_dist_tickets- Session ticket storagequic_epmd- EPMD replacement modulequic_dist_auth- Optional auth-handshake behaviour
Discovery Backends
quic_discovery_static- Static node configurationquic_discovery_dns- DNS SRV-based discovery- Custom backends via
quic_discoverybehaviour
Distribution API
quic:get_stats/1- Get packet counts for liveness detectionquic:send_ping/1- Send transport-level PING frame
Extension Hooks
auth_callback(defaultundefined):{Mod, Fun}orfun/3invoked on both sides after the QUIC handshake but before the distutil handshake. Returning `{error, }closes the connection without the node ever joining. Returning{ok, {authenticated_node, Node}}refuses a peer whose dist node name is notNode. Seequic_dist_authand the Configuration Reference indocs/QUIC_DIST.md`.register_with_epmd(defaultfalse): whentrue, the listener registers its port via the configuredepmd_moduleso external tooling (e.g.epmd -names) can resolve the node.
Roadmap
Tracked future work. Items here are not committed deliverables; they mark known gaps that may land in a later release.
TLS 1.3 external PSK follow-ups
- [ ] 0-RTT on external PSK. The server currently ignores
early_dataon PSK handshakes; full EndOfEarlyData state-machine support is deferred. - [ ]
NewSessionTicketon PSK-authenticated handshakes. Suppressed in v1 to avoid binding-identity ambiguity; revisit when a concrete use case appears. - [ ] RFC 9258 PSK Importer. The current API consumes the secret as
raw IKM; an importer layer would derive an
epsk -> pskmapping bound to a target protocol/KDF.
TLS 1.3 negotiation follow-ups
- [ ] secp521r1 / x448 key-exchange groups (constants defined; key generation and key_share wiring deferred).
- [ ] Ed448 / ECDSA secp521r1-SHA512 signature schemes (constants defined; sign/verify branches deferred).
Interop Runner Compliance
All 10 QUIC Interop Runner test cases pass against the external runner.
The Covered by column says which of them quic_interop_SUITE also
checks here, so the table is not taken on trust:
| Test Case | Status | Covered by quic_interop_SUITE |
|---|---|---|
| handshake | Pass | yes |
| transfer | Pass | yes |
| retry | Pass | no |
| keyupdate | Pass | no, nothing exposes the key phase to assert on |
| chacha20 | Pass | yes |
| multiconnect | Pass | yes |
| v2 | Pass | yes |
| resumption | Pass | yes |
| zerortt | Pass | yes, early data sent, accepted and answered |
| connectionmigration | Pass | yes, data echoed over the new path |