Skip to content

refactor: swap quicer to native quick implementation#313

Open
kevinschweikert wants to merge 1 commit into
emqx:masterfrom
kevinschweikert:feat/native-quic
Open

refactor: swap quicer to native quick implementation#313
kevinschweikert wants to merge 1 commit into
emqx:masterfrom
kevinschweikert:feat/native-quic

Conversation

@kevinschweikert

Copy link
Copy Markdown
Contributor

Closes #304

Full disclosure: This PR was AI assisted. I ran all the tests and did a sweep myself. I am no Erlang expert but want to simplify the build of emqtt. I'll appreciate feedback and will iterate over this PR but if this is too big of a change or too far away from expectations i can understand.

Changes made:

  • exchanged quicer for erlang_quic which is a native QUIC implementaion in Erlang
  • simplified build to not depend on a rebar script
  • i had to remove one test t_ssl_keylogfile_dump because this feature is not supported AFAIK

Comment thread src/emqtt.erl
}).

%% QUIC stream options, e.g. #{priority => non_neg_integer()}.
-type stream_opts() :: map() | proplists:proplist().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not ok.

Comment thread test/emqtt_quic_SUITE.erl
Port = 4568,
SslOpts = [ {certfile, certfile(Config)}
, {keyfile, keyfile(Config)}
, {idle_timeout_ms, 10000}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they are unsupported or?

Comment thread test/emqtt_quic_SUITE.erl
[ {alpn, ["mqtt"]}, {active, false}
, {quic_event_mask, 1}
],
[{alpn, ["mqtt"]}],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is {active, false} default?

Comment thread test/emqtt_quic_SUITE.erl
{error, _} ->
ok;
{ok, Sock2} ->
ok = emqtt_quic:close(Sock2)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this hides error.

Comment thread test/emqtt_quic_SUITE.erl
case emqtt_quic:connect("localhost",
Port,
[{alpn, ["mqtt"]},
{nst, Corrupted}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nst is a term in quicer, I guess it is not in quic

Comment thread test/emqtt_quic_SUITE.erl
{error, closed} -> ok
case quic:send_data(MConn, MalformStream, <<0,0,0,0,0,0,0,0,0,0>>, false) of
ok -> ok;
{error, _} -> ok

@qzhuyan qzhuyan Jun 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both ok and error map to ok why ?

Comment thread test/emqtt_quic_SUITE.erl
%% Client is alive.
?assert(is_list(emqtt:info(C))).

t_ssl_keylogfile_dump(Config) ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need it. very useful in troubleshooting.

Comment thread test/emqtt_quic_SUITE.erl
{error, closed} -> ok
case quic:send_data(MConn, MalformStream, <<0,0,0,0,0,0,0,0,0,0>>, false) of
ok -> ok;
{error, _} -> ok

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to have error reason match.

Comment thread src/emqtt.erl
qoe = IsQoE
} = State) ->
State0 = maybe_init_quic_state(ConnMod, State),
IsUsingQuicHandle = proplists:is_defined(handle, SockOpts),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes the support of indirect start connection.

Comment thread src/emqtt_quic.erl

-export([ connect/4
, send/2
, recv/2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need passive recv.

Comment thread src/emqtt_quic.erl
{connect_timeout, translate_timeout(Timeout)},
{server_name, sni(SslOpts, Host)},
{cacerts, proplists:get_value(cacerts, SslOpts)},
{session_ticket, proplists:get_value(nst, SockOpts)}],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing support for stream_count (bidi, unidi)

Comment thread src/emqtt_quic.erl
quicer:getstat(Conn, Options).
%% Stub: QUIC keepalive uses `last_packet_id' (see `emqtt:should_ping/1');
%% the byte counters fetched by the generic keepalive path are unused.
getstat({quic, _Conn, _Stream}, Options) ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO.

Comment thread src/emqtt_quic.erl
%% gracefully shutdown the stream to flush all the msg in sndbuf.
_ = quicer:shutdown_stream(Stream, 500),
quicer:close_connection(Conn, ?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0, 500).
close({quic, Conn, _Stream}) ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need gracefully shutdown stream with send flush.

Comment thread src/emqtt_quic.erl
maps:get(LogicId, LSM, undefined).

%% @doc Establish a connection and open the control stream. Blocks until
%% the QUIC handshake completes; the synchronous contract matches the one

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true, we need async conn.

Comment thread test/emqtt_quic_SUITE.erl
shutdown_stream(Conn, Stream, graceful) ->
quic:send_data(Conn, Stream, <<>>, true);
shutdown_stream(Conn, Stream, _Abort) ->
quic:reset_stream(Conn, Stream, 0).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

abort send or abort recv.

@qzhuyan

qzhuyan commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

thanks for the PR, good start.

Our team member also push a couple of times about this, I cannot ignore that.

One general problem is that this PR almost change everything to sync ops.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the previous QUIC transport backend (quicer, external/C-dependent) with the pure-Erlang quic library, aiming to simplify builds and reduce native dependencies while keeping QUIC support for emqtt and its test suite.

Changes:

  • Swapped QUIC implementation from quicer to quic across client, CLI, and test utilities.
  • Simplified build/release setup by removing rebar.config.script and adding quic as a normal dependency/application.
  • Refactored MQTT frame parsing flow to a new parse_all/2 helper and adjusted QUIC test expectations (including removal of an unsupported keylogfile test).

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/quic_server.erl Reworked test QUIC server to use quic:start_server/3 and handle stream data events.
test/emqtt_test_lib.erl Starts quic for tests, but still contains remaining quicer startup logic elsewhere in the file.
test/emqtt_quic_SUITE.erl Updated suite for new QUIC event model and stream shutdown semantics; still had a quicer startup call.
src/emqtt.erl Adapted QUIC connection/open-stream flow and switched packet parsing to emqtt_frame:parse_all/2.
src/emqtt.app.src Adds quic as an OTP application dependency and removes rebar.config.script from packaged files.
src/emqtt_sock.erl Routes QUIC sends/sockname through emqtt_quic using {quic, ConnPid, StreamId} tuples.
src/emqtt_quic.erl New QUIC transport implementation built on quic (connection lifecycle, stream parsing, session tickets).
src/emqtt_quic_stream.erl Removed (old quicer stream callback implementation).
src/emqtt_quic_connection.erl Removed (old quicer connection callback implementation).
src/emqtt_frame.erl Adds parse_all/2 to parse a full binary chunk into ordered packets + residual parse state.
src/emqtt_cli.erl Starts quic instead of quicer.
rebar.config.script Removed to simplify build configuration.
rebar.config Adds quic dependency; updates relx releases and dialyzer extra apps from quicer to quic.
README.md Updates QUIC option type docs; other QUIC build docs may now be outdated.
Dockerfile.test Removes cmake install step (no longer needed without quicer native build).
.gitignore Ignores dialyzer PLT artifacts.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/emqtt_quic.erl
Comment on lines +108 to +119
case do_connect(Host, Port, Opts, Timeout) of
{ok, Conn} ->
case open_stream(Conn) of
{ok, Stream} ->
{ok, {quic, Conn, Stream}};
{error, _} = Error ->
_ = quic:safe_close(Conn),
Error
end;
{error, _} = Error ->
Error
end
Comment thread src/emqtt_quic.erl
Comment on lines +122 to +127
%% @doc Connect without opening a stream; pairs with `emqtt:quic_mqtt_connect/1'.
-spec open_connection([{inet:hostname() | inet:ip_address(), inet:port_number()}],
proplists:proplist(), timeout())
-> {ok, conn()} | {error, term()}.
open_connection([{Host, Port} | _], Opts, Timeout) ->
do_connect(Host, Port, Opts, Timeout).
Comment thread src/emqtt_quic.erl
Comment on lines +251 to +265
on_stream_closed(Via, Conn, #{ data_stream_socks := DataStreams
, stream_parse_state := PSS
, control_stream_sock := CtrlSock
} = CBData) ->
case lists:member(Via, DataStreams) of
true ->
{CBData#{ data_stream_socks := lists:delete(Via, DataStreams)
, stream_parse_state := maps:remove(Via, PSS)
}, []};
false when Via =:= CtrlSock ->
{CBData, [{next_event, info, {quic_closed, Conn}}]};
false ->
?LOG(warning, "unknown_stream_closed", #{stream => Via}, CBData),
{CBData, []}
end.
Comment thread test/emqtt_quic_SUITE.erl
Comment on lines 974 to 976
emqtt_test_lib:start_emqx(),
application:ensure_all_started(quicer),
ok = emqtt_test_lib:ensure_quic_listener(mqtt, UdpPort).
Comment thread test/emqtt_test_lib.erl
Comment on lines 47 to 52
start_emqx() ->
ensure_test_module(emqx_common_test_helpers),
emqx_common_test_helpers:start_apps([]),
{ok, _} = application:ensure_all_started(quic),
ok = ensure_quic_listener(mqtt, 14567),
ok.
Comment thread README.md
Comment on lines 377 to 381
{tcp_opts, [gen_tcp:option()]} |
{ssl, boolean()} |
{ssl_opts, [ssl:tls_client_option()]} |
{quic_opts, {quicer:conn_opts(), quicer:stream_opts()}} |
{quic_opts, {ConnOpts :: proplists:proplist(), StreamOpts :: proplists:proplist()}} |
{ws_path, string()} |
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Native Erlang QUIC

3 participants