Changelog
View SourceAll notable changes to this project will be documented in this file.
[Unreleased]
[1.0.1] - 2026-04-15
Fixed
- h3: consult
stream_type_handleron fresh peer-initiated bidi streams so extensions can claim them before default request handling (#62) - docs:
rebar3 ex_docnow runs clean (#63)
[1.0.0] - 2026-04-15
First release with HTTP/3. Brings full client + server HTTP/3 (RFC 9114) with QPACK (RFC 9204), HTTP Datagrams (RFC 9297), Server Push, Extensible Priorities, Extended CONNECT, and the extension-stream hooks WebTransport needs. Also a critical flow-control deadlock fix in the QUIC core, a BBR loopback throughput fix, and the H3 server owner default change.
HTTP/3 (quic_h3, new module)
Added
- HTTP/3 client and server (RFC 9114) with QPACK header compression
(RFC 9204): request/response, body data, trailers, GOAWAY,
cancellation, CLI tools (
bin/quic_h3c,bin/quic_h3d) - Server Push (RFC 9114 §4.6):
push/3,send_push_response/4,send_push_data/4,set_max_push_id/2,cancel_push/2 - Extensible Priorities (RFC 9218):
priorityrequest option, PRIORITY_UPDATE frames, urgency / incremental hints - Extended CONNECT (RFC 9220) for WebTransport-style upgrades
- HTTP Datagrams (RFC 9297):
send_datagram/3,h3_datagrams_enabled/1,max_datagram_size/2, capsule framing - Extension-stream hook:
stream_type_handleroption onstart_server/3claims peer-initiated uni and bidi streams whose first varint matches a caller-supplied filter; claimed bytes are delivered as{stream_type_data, ...}owner messages instead of being parsed as HTTP/3 requests. Owner also receivesstream_type_open,stream_type_closed,stream_type_reset,stream_type_stop_sendingevents - Client-initiated extension streams:
quic_h3:open_bidi_stream/1,2pre-claims a bidi stream with a signal-type varint (e.g. WebTransport's0x41) so inbound bytes route through the claimed-bidi path - Per-connection owner override via
connection_handlercallback onstart_server/3for hosting many sessions per listener - Per-stream handler registration:
set_stream_handler/3,4,unset_stream_handler/2to redirect body data to a worker pid - Query API:
get_settings/1,get_peer_settings/1,get_quic_conn/1 - Documentation:
docs/HTTP3.mdreference + benchmarks section - E2E test infrastructure:
quic_h3_e2e_SUITE,quic_h3_h3spec_SUITE,quic_h3_owner_SUITE; dedicated CI job - Performance benchmark:
quic_h3_bench
Changed
- Server connection owner now defaults to the listener gen_server
(long-lived, trap_exit'ed) instead of the
start_servercaller pid; durable owners for datagram / stream-type events should be supplied via the per-connectionconnection_handlercallback - SETTINGS directionality validation tightened to RFC 9114
Fixed
- Server connections wedged with
connect_timeoutwhen the process that calledstart_server/3exited before a client arrived and eitherh3_datagram_enabledorstream_type_handlerwas set - Discard unknown unidirectional stream payload (RFC 9114 §6.2 unknown-stream-type rule) instead of erroring the connection
- Emit trailing empty DATA event when response carries FIN so owners
always see
Fin = trueexactly once - Strict PRIORITY_UPDATE frame parsing per RFC 9218
- DoS hardening on header / capsule / frame parsing
- Header / trailer /
:path/:statussymmetry between client and server validation - GOAWAY drain enforcement: reject new requests after a GOAWAY is sent or received
- Server push lifecycle correctness (PUSH_PROMISE pairing, duplicate detection, MAX_PUSH_ID enforcement)
- Tighten RFC 9114 / 9204 compliance across multiple parsers
syncoption onconnect/3resolves an E2E race where the client tried to send before SETTINGS exchange completed- Improved frame error handling and header validation
- aioquic SETTINGS compatibility
- QPACK: encoder eviction guard prevents references to
unacknowledged dynamic-table entries; rejects
Increment = 0
QUIC transport
Added
- Spin bit (RFC 9000 §17.4)
- Stateless reset support (RFC 9000 §10.3)
- Full NEW_TOKEN issuance and validation loop
RESET_STREAM_ATtransport parameter and frame plumbingquic:set_congestion_control/2runtime CC switch APIquic:get_peer_transport_params/1introspection API
Changed
- BBR internal clock switched to microseconds; loopback transfers no longer pin to the InitialRtt fallback
Fixed
- Stream-level
MAX_STREAM_DATAwindow stopped sliding oncerecv_max_datareachedfc_max_receive_window(8 MB default). Past the cap, the auto-tune re-sent the same value forever and the sender stalled at 8 MB lifetime per stream. The window now slides pastrecv_offsetlike the connection-level window already does - BBR loopback throughput regression: ms-precision clock collapsed delivery-rate intervals to 0/1 ms and clamped BDP to the 4-packet minimum, holding throughput at ~0.03 Mbps. Microsecond-precision internal clock restores expected behavior
- Send
MAX_STREAMSas peer-initiated streams complete (RFC 9000 §4.6); previously peers could exhaust the stream-id space
Distribution (quic_dist)
Added
- User-accessible streams API:
quic_dist:open_stream/1,2,send/3,close_stream/1,reset_stream/1,2,controlling_process/2,list_streams/0,1, with acceptor pool and stream priorities - Connection migration logging
- Distributed Erlang benchmarks + multi-node test scripts
- Per-iteration latency stats in throughput benchmark (min/p50/p99/max
- timeout counts)
Changed
- Test runner logs each test's results as it returns rather than at the end, so a stalled middle test no longer hides the others
Tests and infrastructure
quic_e2e_*_SUITEandquic_h3_e2e_SUITErun against in-process servers; Docker no longer required for these jobs
[0.11.0] - 2026-04-09
Added
- Full QUIC connection migration support (RFC 9000 Section 9)
- Server-side address change detection (NAT rebinding vs active migration)
- Path validation with PATH_CHALLENGE/PATH_RESPONSE
- CID rotation for path unlinkability
disable_active_migrationtransport parameter
- Application error code support for CONNECTION_CLOSE frames
- Client certificate support (
verifyserver option) - CUBIC congestion control (RFC 9438)
- BBR congestion control
- HyStart++ slow start (RFC 9406) for all CC algorithms
- UDP packet batching with GSO/GRO support
- Configurable UDP buffer sizing (recbuf/sndbuf options)
- QLOG tracing for debug visibility
- Pluggable congestion control behavior
- Stream deadlines for per-stream timeout control
- STOP_SENDING API (
quic:stop_sending/3) max_udp_payload_sizetransport parameter- Async send API and socket receive optimizations
- Throughput benchmarks (
quic_throughput_bench,quic_batch_bench) - QUIC-based Erlang distribution (
quic_dist) for node communication over QUIC - Distribution modules:
quic_dist,quic_dist_controller,quic_dist_sup - EPMD replacement module (
quic_epmd) for QUIC-based node discovery - Discovery backends:
quic_discovery_static(static config),quic_discovery_dns(DNS SRV) - Session ticket storage (
quic_dist_tickets) for 0-RTT reconnection - Stream prioritization for distribution: control stream (urgency 0), data streams (urgency 4-6)
- Backpressure mechanism for distribution congestion control
- Keep-alive PING frames for transport-level liveness (configurable via
keep_alive_interval) quic:get_stats/1API for connection packet counts (used for liveness detection)quic:send_ping/1API for transport-level PING frames- RTT-based flow control auto-tuning for improved throughput
- Packet pacing (RFC 9002 Section 7.7) to prevent bursts
Changed
- ConnRef is now connection PID (simpler API)
- Improved ACK processing performance (O(n^2) to O(n) with gb_sets)
- Timer batching for reduced overhead
- Zero-copy packet processing optimizations
- Distribution liveness detection now uses QUIC packet counts instead of application ticks
- Improved congestion control with quic-go-inspired settings (larger initial cwnd)
- Flow control windows auto-tune based on RTT measurements
Fixed
- Throughput regression in connection migration (wasteful binary allocation)
- CUBIC cwnd collapse issue
- BBR delivery rate interval causing cwnd collapse
- BBR initial pacing rate causing transfer hangs
- Pacing precision loss causing transfer stalls
- Various RFC compliance fixes for QUIC connection migration
net_tick_timeouterrors under heavy load by using QUIC-level activity as liveness proof- Stream flow control
recv_max_datausing wrong limits - Distribution controller backpressure data loss
- Congestion control protocol compliance issues
- Recovery exit when only non-ack-eliciting packets are ACKed
- Tick timeout issues in distribution controller
- Flow control blocking that caused deadlocks
- Message framing for large message transfers
Removed
- NAT traversal support from
quic_dist(use standard QUIC connection migration instead)
[0.10.2] - 2026-02-21
Fixed
- Deprecated
catchexpressions replaced withtry...catch...end - Undefined
dynamic()type replaced withterm()in type specs - CI workflow consolidated with separate unit-tests, e2e, and interop jobs
[0.10.1] - 2026-02-21
Fixed
- ACK range encoding crash for out-of-order packets: when packets arrived out
of order (e.g., 10, 5, 6), ACK ranges were not properly maintained in
descending order or merged, causing negative Gap values that crashed
quic_varint:encode/1withbadarg
[0.10.0] - 2026-02-21
Added
- RFC 9312 QUIC-LB Connection ID encoding support for load balancer routing
- New
quic_lbmodule with three encoding algorithms:- Plaintext: server_id visible in CID (no encryption)
- Stream Cipher: AES-128-CTR encryption of server_id
- Block Cipher: 4-round Feistel network for <16 bytes, AES-CTR for 16 bytes, truncated cipher for >16 bytes
#lb_config{}record for LB configuration (algorithm, server_id, key, nonce_len)#cid_config{}record for CID generation configurationlb_configoption inquic_listenerto enable LB-aware CID generation- Variable DCID length support in short header packet parsing
- LB-aware CID generation in
quic_connectionfor NEW_CONNECTION_ID frames - E2E test suite
quic_lb_e2e_SUITEwith 21 integration tests quic:server_spec/3to get a child spec for embedding QUIC servers in custom supervision trees- Stream reassembly test suite
quic_stream_reassembly_SUITEfor ordered delivery verification
Changed
quic:set_owner/2is now asynchronous (cast instead of call)
Fixed
quic:get_server_port/1now returns the actual OS-assigned port when server was started with port 0 (ephemeral port), instead of returning 0quic:get_server_connections/1now correctly returns connection PIDs; was returning empty list due toget_listeners/1returning supervisor pids instead of actual listener processes- Removed redundant
link/1call in listener (connection already linked viagen_statem:start_link) - Unhandled calls in connection state machine now return
{error, {invalid_state, State}}instead of silently timing out - Server-side connection termination no longer closes shared listener socket: previously when a server connection terminated, it would close the UDP socket shared with the listener, breaking all subsequent connections
- Cancel delayed ACK timer in connection terminate to prevent timer messages to dead processes
- Session ticket table now has TTL (7 days) and size limit (10,000 entries) to prevent unbounded memory growth
- Listener now properly cleans up ETS tables on terminate (standalone mode only, pool mode tables are managed by the pool manager)
- Draining state now uses calculated
3 * PTOtimeout per RFC 9000 Section 10.2 instead of hardcoded 3 seconds - Pre-connection pending data queue now has size limit (1000 entries) to prevent memory exhaustion from slow handshakes
- Buffer contiguity calculation now has iteration limit to prevent stack overflow with highly fragmented receive buffers
- Stream data is now properly reassembled before delivery: previously data was delivered immediately as received, causing corruption when packets arrived out of order during large file transfers. Data is still streamed incrementally as contiguous chunks become available
- Server connections no longer modify listener's socket active state: server-side
connections were calling
inet:setopts(Socket, [{active, once}])on the shared listener socket, overriding the listener's{active, N}configuration and causing the socket to go passive after receiving packets
[0.9.0] - 2026-02-20
Added
- Multi-pool server support with ranch-style named server pools
quic:start_server/3to start named server with connection poolingquic:stop_server/1to stop named serverquic:get_server_info/1to get server information (pid, port, opts, started_at)quic:get_server_port/1to get server listening portquic:get_server_connections/1to get server connection PIDsquic:which_servers/0to list all running servers- Application supervision structure (
quic_app,quic_sup,quic_server_sup) - ETS-based server registry (
quic_server_registry) with process monitoring pool_sizeoption for listener process pooling with SO_REUSEPORT- FreeBSD CI testing workflow
- Expanded Linux CI matrix (Ubuntu 22.04/24.04, OTP 26-28)
Changed
quic.app.srcnow includes{mod, {quic_app, []}}for OTP application behaviour- Listener supervisor registers with server registry on init for restart recovery
[0.8.0] - 2026-02-20
Added
- Stream prioritization (RFC 9218): urgency-based scheduling with 8 priority levels (0-7) and incremental delivery flag
quic:set_stream_priority/4andquic:get_stream_priority/2API- Bucket-based priority queue for O(1) stream scheduling
- Preferred address handling (RFC 9000 Section 9.6): server can advertise a preferred address during handshake, client validates via PATH_CHALLENGE and automatically migrates to validated preferred address
preferred_ipv4andpreferred_ipv6listener options for server configuration#preferred_address{}record for IPv4/IPv6 addresses, CID, and reset tokenquic_tls:encode_preferred_address/1andquic_tls:decode_preferred_address/1- Idle timeout enforcement (RFC 9000 Section 10.1): when
idle_timeoutoption is set, internal timer automatically closes connection after timeout with no activity (set to 0 to disable) - Persistent congestion detection (RFC 9002 Section 7.6): detects prolonged packet loss spanning > PTO * 3 and resets cwnd to minimum window
- Frame coalescing: ACK frames are coalesced with small pending stream data (< 500 bytes) for more efficient packet utilization
[0.7.1] - 2026-02-20
Fixed
- Packet number reconstruction per RFC 9000 Appendix A: truncated packet numbers are now properly reconstructed using the largest received PN, fixing decryption failures for large responses (>255 packets with 1-byte PN encoding)
[0.7.0] - 2026-02-20
Added
- Docker interop runner integration (client and server images)
- Session resumption interop test (
resumption) - 0-RTT early data interop test (
zerortt) - Connection migration interop test (
connectionmigration) quic:migrate/1API for triggering active path migration- All 10 QUIC Interop Runner test cases now pass:
- handshake, transfer, retry, keyupdate, chacha20, multiconnect, v2, resumption, zerortt, connectionmigration
Fixed
- Connection-level flow control: now properly tracks
data_receivedand sends MAX_DATA frames when 50% of connection window is consumed (RFC 9000 Section 4.1) - Large downloads: interop client now writes to disk incrementally (streaming) instead of accumulating in memory
- Server DCID initialization: server now correctly sets DCID from client's Initial packet SCID field, fixing short header packet alignment
- Key update HP key preservation: header protection keys are no longer rotated during key updates per RFC 9001 Section 6.6
- Fixed bit validation: skip padding bytes (0x00) and invalid short headers (fixed bit not set) in coalesced packets
- Role-based key selection in 1-RTT packet decryption
[0.6.5] - 2026-02-19
Added
quic_listener:start/2for unlinked listener processesset_ownercall handling in idle and handshaking states
Fixed
- IPv4/IPv6 address family matching when opening client sockets
- Race condition: transfer socket ownership before sending packet
- Handle header unprotection errors gracefully in packet decryption
- Removed verbose debug logging from listener
[0.6.4] - 2026-02-17
Fixed
- Server now selects correct signature algorithm based on key type (EC vs RSA)
[0.6.3] - 2026-02-17
Fixed
- Fixed transport params parsing in ClientHello - properly unwrap {ok, Map} result
[0.6.2] - 2026-02-17
Fixed
- Fixed key selection for all packet types based on role (server vs client)
- Server now uses correct keys for both sending and receiving packets
- Fixed Initial, Handshake, and 1-RTT packet encryption/decryption
[0.6.1] - 2026-02-17
Fixed
- Server-side packet decryption now uses correct keys (client keys for Initial/Handshake packets received from clients)
[0.6.0] - 2026-02-17
Added
- DATAGRAM frame support (RFC 9221) for unreliable data transmission
quic:set_owner/2to transfer connection ownership (like gen_tcp:controlling_process/2)quic:peercert/1to retrieve peer certificate (DER-encoded)quic:send_datagram/2to send QUIC datagrams- Connection handler callback in
quic_listenerfor custom connection handling - ACK delay for datagram-only packets per RFC 9221 Section 5.2
- Proper ACK generation at packet level for all ack-eliciting frames
Fixed
- Datagrams are not retransmitted on loss (RFC 9221 compliance)
- ACKs now sent for all ack-eliciting frames, not just stream data
[0.5.1] - 2026-02-17
Fixed
- Pad payload for header protection sampling to prevent crashes during PTO timeout
[0.5.0] - 2026-02-17
Added
- Retry packet handling (RFC 9000 Section 8.1)
- Stateless reset support (RFC 9000 Section 10.3)
- Connection ID limit enforcement (RFC 9000 Section 5.1.1)
- ECN support for congestion control (RFC 9002 Section 7.1)
- RFC 9000/9001 test vectors
- Interoperability test suite with quic-go server
- E2E tests in CI pipeline
Fixed
- CI compatibility with OTP 28 (use rebar3 nightly)
- quic-go Docker build (pin to v0.48.2)
[0.4.0] - 2025-02-17
Changed
- Moved
doc/todocs/to prevent ex_doc from overwriting documentation - Consolidated
hash_len/1andcipher_to_hash/1functions inquic_cryptomodule - Refactored key derivation in
quic_keysusingcipher_params/1helper - Improved socket cleanup on initialization failure in
quic_connection
Removed
- Removed
send_headers/4API (HTTP/3 functionality, not core QUIC transport)
Fixed
- Added bounds checking for header protection sample extraction in
quic_aead - Added CID length validation (max 20 bytes per RFC 9000) in
quic_packet - Added token length validation in
quic_packet - Added frame data length limits in
quic_frameto prevent memory exhaustion - Added ACK range limits in
quic_ackto prevent DoS attacks - Fixed weak random: use
crypto:strong_rand_bytes/1for ticket age_add - Fixed dialyzer warning in
quic_tlsby adding error handling todecode_transport_params/1
[0.3.0] - 2025-02-16
Added
- Server mode with
quic_listenermodule - 0-RTT early data support (RFC 9001 Section 4.6)
- Connection migration support (RFC 9000 Section 9)
- Key update support (RFC 9001 Section 6)
[0.2.0] - 2025-02-15
Added
- Stream multiplexing (bidirectional and unidirectional)
- Flow control (connection and stream level)
- Congestion control (NewReno)
- Loss detection and packet retransmission (RFC 9002)
[0.1.0] - 2025-02-14
Added
- Initial release
- TLS 1.3 handshake (RFC 8446)
- Basic QUIC transport (RFC 9000)
- AEAD packet protection (RFC 9001)