Pure Erlang QUIC implementation (RFC 9000/9001).
RESET_STREAM_AT extensionpsk_dhe_ke
and psk_ke). See docs/PSK.md.quic:get_path_stats/1) for routing layersquic_h3)stream_type_handler (peer-claimed uni/bidi)
and quic_h3:open_bidi_stream/1,2 (client-initiated, pre-claimed
with a signal-type varint)connection_handler callback for
multi-tenant serversquic_h3_client and quic_h3_server, built with
rebar3 as h3_client escriptize / rebar3 as h3_server escriptize into
_build/<profile>/bin/quic_dist)quic_discovery_static, quic_discovery_dns)priv/bin/quic_call.sh: erl_call-style one-shot RPC against a -proto_dist quic nodeauth_callback for an app-level handshake between TLS and dist_util, plus register_with_epmd for stock-EPMD discoverabilityAdd to your rebar.config dependencies:
{deps, [
{quic, {git, "https://github.com/benoitc/erlang_quic.git", {branch, "main"}}}
]}.
%% Connect to a QUIC server
{ok, ConnRef} = quic:connect(<<"example.com">>, 443, #{
alpn => [<<"h3">>],
verify => false
}, self()),
%% Wait for connection
receive
{quic, ConnRef, {connected, Info}} ->
io:format("Connected: ~p~n", [Info])
end,
%% Open a bidirectional stream
{ok, StreamId} = quic:open_stream(ConnRef),
%% Send data on the stream
ok = quic:send_data(ConnRef, StreamId, <<"Hello, QUIC!">>, true),
%% Receive data
receive
{quic, ConnRef, {stream_data, StreamId, Data, _Fin}} ->
io:format("Received: ~p~n", [Data])
end,
%% Close connection
quic:close(ConnRef, normal).
%% Load certificate and key
{ok, CertDer} = file:read_file("server.crt"),
{ok, KeyDer} = file:read_file("server.key"),
%% Start a named server (recommended)
{ok, _Pid} = quic:start_server(my_server, 4433, #{
cert => CertDer,
key => KeyDer,
alpn => [<<"h3">>]
}),
%% Get the port (useful if 0 was specified for ephemeral port)
{ok, Port} = quic:get_server_port(my_server),
io:format("Listening on port ~p~n", [Port]),
%% Incoming connections are handled automatically
%% The server spawns quic_connection processes for each client
%% Stop the server when done
quic:stop_server(my_server).
Alternatively, use the low-level listener API directly:
{ok, Listener} = quic_listener:start_link(4433, #{
cert => CertDer,
key => KeyDer,
alpn => [<<"h3">>]
}),
Port = quic_listener:get_port(Listener).
%% Server
{ok, _} = quic_h3:start_server(my_h3, 4433, #{
cert => CertDer,
key => KeyDer,
handler => fun(Conn, StreamId, <<"GET">>, Path, _Headers) ->
Body = <<"hello from ", Path/binary>>,
quic_h3:send_response(Conn, StreamId, 200,
[{<<"content-type">>, <<"text/plain">>}]),
quic_h3:send_data(Conn, StreamId, Body, true)
end
}).
%% Client
{ok, H3} = quic_h3:connect("example.com", 4433, #{verify => false, sync => true}),
{ok, StreamId} = quic_h3:request(H3, [
{<<":method">>, <<"GET">>},
{<<":scheme">>, <<"https">>},
{<<":path">>, <<"/hi">>},
{<<":authority">>, <<"example.com">>}
]),
receive
{quic_h3, H3, {response, StreamId, 200, _Headers}} -> ok
end,
receive
{quic_h3, H3, {data, StreamId, Body, true}} ->
io:format("got ~p~n", [Body])
end,
quic_h3:close(H3).
See docs/HTTP3.md for datagrams, push, priorities, extended CONNECT, extension streams, and per-connection owners.
The owner process receives messages in the format {quic, ConnRef, Event}:
| Event | Description |
|---|---|
{connected, Info} | Connection established |
{stream_opened, StreamId} | New stream opened by peer |
{stream_data, StreamId, Data, Fin} | Data received on stream |
{stream_reset, StreamId, ErrorCode} | Stream reset by peer |
{closed, Reason} | Connection closed |
{transport_error, Code, Reason} | Transport error |
{session_ticket, Ticket} | Session ticket for 0-RTT resumption |
{datagram, Data} | Datagram received (RFC 9221) |
{stop_sending, StreamId, ErrorCode} | Stop sending requested by peer |
{send_ready, StreamId} | Stream ready for writing |
See docs/features.md for the complete API reference and feature list.
Connection: quic:connect/4, quic:close/2, quic:peername/1, quic:migrate/1
Streams: quic:open_stream/1, quic:send_data/4, quic:reset_stream/3
Server: quic:start_server/3, quic:stop_server/1, quic:get_server_port/1
Datagrams: quic:send_datagram/2 (RFC 9221)
HTTP/3: quic_h3:connect/3, quic_h3:request/2,3, quic_h3:send_response/4,
quic_h3:send_data/3,4, quic_h3:start_server/3, quic_h3:open_bidi_stream/1,2,
quic_h3:send_datagram/3 (RFC 9114 / 9204 / 9297)
Load Balancer: quic_lb:new_config/1, quic_lb:generate_cid/1 (RFC 9312)
rebar3 compile
rebar3 fmt
rebar3 lint
rebar3 xref
rebar3 dialyzer
# Run unit tests
rebar3 eunit
# Run property-based tests
rebar3 proper
# Run all tests
rebar3 eunit && rebar3 proper
This implementation passes all 10 QUIC Interop Runner test cases. See docs/features.md for the full test matrix and interop/README.md for details on running interop tests.
Topic guides under docs/:
Changing the library itself: CONTRIBUTING.md.
Generate API documentation with:
rebar3 ex_doc
Apache License 2.0
Benoit Chesneau
Erlang
97.2%
Pure Erlang QUIC implementation (RFC 9000/9001).
RESET_STREAM_AT extensionpsk_dhe_ke
and psk_ke). See docs/PSK.md.quic:get_path_stats/1) for routing layersquic_h3)stream_type_handler (peer-claimed uni/bidi)
and quic_h3:open_bidi_stream/1,2 (client-initiated, pre-claimed
with a signal-type varint)connection_handler callback for
multi-tenant serversquic_h3_client and quic_h3_server, built with
rebar3 as h3_client escriptize / rebar3 as h3_server escriptize into
_build/<profile>/bin/quic_dist)quic_discovery_static, quic_discovery_dns)priv/bin/quic_call.sh: erl_call-style one-shot RPC against a -proto_dist quic nodeauth_callback for an app-level handshake between TLS and dist_util, plus register_with_epmd for stock-EPMD discoverabilityAdd to your rebar.config dependencies:
{deps, [
{quic, {git, "https://github.com/benoitc/erlang_quic.git", {branch, "main"}}}
]}.
%% Connect to a QUIC server
{ok, ConnRef} = quic:connect(<<"example.com">>, 443, #{
alpn => [<<"h3">>],
verify => false
}, self()),
%% Wait for connection
receive
{quic, ConnRef, {connected, Info}} ->
io:format("Connected: ~p~n", [Info])
end,
%% Open a bidirectional stream
{ok, StreamId} = quic:open_stream(ConnRef),
%% Send data on the stream
ok = quic:send_data(ConnRef, StreamId, <<"Hello, QUIC!">>, true),
%% Receive data
receive
{quic, ConnRef, {stream_data, StreamId, Data, _Fin}} ->
io:format("Received: ~p~n", [Data])
end,
%% Close connection
quic:close(ConnRef, normal).
%% Load certificate and key
{ok, CertDer} = file:read_file("server.crt"),
{ok, KeyDer} = file:read_file("server.key"),
%% Start a named server (recommended)
{ok, _Pid} = quic:start_server(my_server, 4433, #{
cert => CertDer,
key => KeyDer,
alpn => [<<"h3">>]
}),
%% Get the port (useful if 0 was specified for ephemeral port)
{ok, Port} = quic:get_server_port(my_server),
io:format("Listening on port ~p~n", [Port]),
%% Incoming connections are handled automatically
%% The server spawns quic_connection processes for each client
%% Stop the server when done
quic:stop_server(my_server).
Alternatively, use the low-level listener API directly:
{ok, Listener} = quic_listener:start_link(4433, #{
cert => CertDer,
key => KeyDer,
alpn => [<<"h3">>]
}),
Port = quic_listener:get_port(Listener).
%% Server
{ok, _} = quic_h3:start_server(my_h3, 4433, #{
cert => CertDer,
key => KeyDer,
handler => fun(Conn, StreamId, <<"GET">>, Path, _Headers) ->
Body = <<"hello from ", Path/binary>>,
quic_h3:send_response(Conn, StreamId, 200,
[{<<"content-type">>, <<"text/plain">>}]),
quic_h3:send_data(Conn, StreamId, Body, true)
end
}).
%% Client
{ok, H3} = quic_h3:connect("example.com", 4433, #{verify => false, sync => true}),
{ok, StreamId} = quic_h3:request(H3, [
{<<":method">>, <<"GET">>},
{<<":scheme">>, <<"https">>},
{<<":path">>, <<"/hi">>},
{<<":authority">>, <<"example.com">>}
]),
receive
{quic_h3, H3, {response, StreamId, 200, _Headers}} -> ok
end,
receive
{quic_h3, H3, {data, StreamId, Body, true}} ->
io:format("got ~p~n", [Body])
end,
quic_h3:close(H3).
See docs/HTTP3.md for datagrams, push, priorities, extended CONNECT, extension streams, and per-connection owners.
The owner process receives messages in the format {quic, ConnRef, Event}:
| Event | Description |
|---|---|
{connected, Info} | Connection established |
{stream_opened, StreamId} | New stream opened by peer |
{stream_data, StreamId, Data, Fin} | Data received on stream |
{stream_reset, StreamId, ErrorCode} | Stream reset by peer |
{closed, Reason} | Connection closed |
{transport_error, Code, Reason} | Transport error |
{session_ticket, Ticket} | Session ticket for 0-RTT resumption |
{datagram, Data} | Datagram received (RFC 9221) |
{stop_sending, StreamId, ErrorCode} | Stop sending requested by peer |
{send_ready, StreamId} | Stream ready for writing |
See docs/features.md for the complete API reference and feature list.
Connection: quic:connect/4, quic:close/2, quic:peername/1, quic:migrate/1
Streams: quic:open_stream/1, quic:send_data/4, quic:reset_stream/3
Server: quic:start_server/3, quic:stop_server/1, quic:get_server_port/1
Datagrams: quic:send_datagram/2 (RFC 9221)
HTTP/3: quic_h3:connect/3, quic_h3:request/2,3, quic_h3:send_response/4,
quic_h3:send_data/3,4, quic_h3:start_server/3, quic_h3:open_bidi_stream/1,2,
quic_h3:send_datagram/3 (RFC 9114 / 9204 / 9297)
Load Balancer: quic_lb:new_config/1, quic_lb:generate_cid/1 (RFC 9312)
rebar3 compile
rebar3 fmt
rebar3 lint
rebar3 xref
rebar3 dialyzer
# Run unit tests
rebar3 eunit
# Run property-based tests
rebar3 proper
# Run all tests
rebar3 eunit && rebar3 proper
This implementation passes all 10 QUIC Interop Runner test cases. See docs/features.md for the full test matrix and interop/README.md for details on running interop tests.
Topic guides under docs/:
Changing the library itself: CONTRIBUTING.md.
Generate API documentation with:
rebar3 ex_doc
Apache License 2.0
Benoit Chesneau
Erlang
97.2%