diff --git a/apps/emqx_gateway/include/emqx_gateway.hrl b/apps/emqx_gateway/include/emqx_gateway.hrl index de6ecc97f..7acb5d4c2 100644 --- a/apps/emqx_gateway/include/emqx_gateway.hrl +++ b/apps/emqx_gateway/include/emqx_gateway.hrl @@ -21,19 +21,20 @@ %% @doc The Gateway definition -type gateway() :: - #{ name := gateway_name() - %% Description - , descr => binary() | undefined - %% Appears only in getting gateway info - , status => stopped | running | unloaded - %% Timestamp in millisecond - , created_at => integer() - %% Timestamp in millisecond - , started_at => integer() - %% Timestamp in millisecond - , stopped_at => integer() - %% Appears only in getting gateway info - , config => emqx_config:config() - }. + #{ + name := gateway_name(), + %% Description + descr => binary() | undefined, + %% Appears only in getting gateway info + status => stopped | running | unloaded, + %% Timestamp in millisecond + created_at => integer(), + %% Timestamp in millisecond + started_at => integer(), + %% Timestamp in millisecond + stopped_at => integer(), + %% Appears only in getting gateway info + config => emqx_config:config() + }. -endif. diff --git a/apps/emqx_gateway/include/emqx_gateway_http.hrl b/apps/emqx_gateway/include/emqx_gateway_http.hrl index 75f661d7f..a3c935576 100644 --- a/apps/emqx_gateway/include/emqx_gateway_http.hrl +++ b/apps/emqx_gateway/include/emqx_gateway_http.hrl @@ -19,9 +19,11 @@ -define(RESOURCE_NOT_FOUND, 'RESOURCE_NOT_FOUND'). -define(INTERNAL_ERROR, 'INTERNAL_SERVER_ERROR'). --define(STANDARD_RESP(R), - R#{ 400 => emqx_dashboard_swagger:error_codes( - [?BAD_REQUEST], <<"Bad request">>) - , 404 => emqx_dashboard_swagger:error_codes( - [?NOT_FOUND, ?RESOURCE_NOT_FOUND], <<"Not Found">>) - }). +-define(STANDARD_RESP(R), R#{ + 400 => emqx_dashboard_swagger:error_codes( + [?BAD_REQUEST], <<"Bad request">> + ), + 404 => emqx_dashboard_swagger:error_codes( + [?NOT_FOUND, ?RESOURCE_NOT_FOUND], <<"Not Found">> + ) +}). diff --git a/apps/emqx_gateway/rebar.config b/apps/emqx_gateway/rebar.config index 74d66e182..49279e35e 100644 --- a/apps/emqx_gateway/rebar.config +++ b/apps/emqx_gateway/rebar.config @@ -2,30 +2,38 @@ {erl_opts, [debug_info]}. {deps, [ - {emqx, {path, "../emqx"}}, - {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.4"}}} + {emqx, {path, "../emqx"}}, + {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.4"}}} ]}. {plugins, [ - {grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}} + {grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}} ]}. -{grpc, - [{protos, ["src/exproto/protos"]}, - {out_dir, "src/exproto/"}, - {gpb_opts, [{module_name_prefix, "emqx_"}, - {module_name_suffix, "_pb"}]} +{grpc, [ + {protos, ["src/exproto/protos"]}, + {out_dir, "src/exproto/"}, + {gpb_opts, [ + {module_name_prefix, "emqx_"}, + {module_name_suffix, "_pb"} + ]} ]}. -{provider_hooks, - [{pre, [{compile, {grpc, gen}}, - {clean, {grpc, clean}}]} +{provider_hooks, [ + {pre, [ + {compile, {grpc, gen}}, + {clean, {grpc, clean}} + ]} ]}. {xref_ignores, [emqx_exproto_pb]}. -{cover_excl_mods, [emqx_exproto_pb, - emqx_exproto_v_1_connection_adapter_client, - emqx_exproto_v_1_connection_adapter_bhvr, - emqx_exproto_v_1_connection_handler_client, - emqx_exproto_v_1_connection_handler_bhvr]}. +{cover_excl_mods, [ + emqx_exproto_pb, + emqx_exproto_v_1_connection_adapter_client, + emqx_exproto_v_1_connection_adapter_bhvr, + emqx_exproto_v_1_connection_handler_client, + emqx_exproto_v_1_connection_handler_bhvr +]}. + +{project_plugins, [erlfmt]}. diff --git a/apps/emqx_gateway/src/bhvrs/emqx_gateway_channel.erl b/apps/emqx_gateway/src/bhvrs/emqx_gateway_channel.erl index 1f4a192d4..fc78c4dfe 100644 --- a/apps/emqx_gateway/src/bhvrs/emqx_gateway_channel.erl +++ b/apps/emqx_gateway/src/bhvrs/emqx_gateway_channel.erl @@ -46,56 +46,57 @@ -type gen_server_from() :: {pid(), Tag :: term()}. --type reply() :: {outgoing, emqx_gateway_frame:packet()} - | {outgoing, [emqx_gateway_frame:packet()]} - | {event, conn_state() | updated} - | {close, Reason :: atom()}. +-type reply() :: + {outgoing, emqx_gateway_frame:packet()} + | {outgoing, [emqx_gateway_frame:packet()]} + | {event, conn_state() | updated} + | {close, Reason :: atom()}. -type replies() :: reply() | [reply()]. %% @doc Handle the incoming frame --callback handle_in(emqx_gateway_frame:frame() | {frame_error, any()}, - channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: any(), channel()} - | {shutdown, Reason :: any(), replies(), channel()}. +-callback handle_in( + emqx_gateway_frame:frame() | {frame_error, any()}, + channel() +) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: any(), channel()} + | {shutdown, Reason :: any(), replies(), channel()}. %% @doc Handle the outgoing messages dispatched from PUB/SUB system --callback handle_deliver(list(emqx_types:deliver()), channel()) - -> {ok, channel()} - | {ok, replies(), channel()}. +-callback handle_deliver(list(emqx_types:deliver()), channel()) -> + {ok, channel()} + | {ok, replies(), channel()}. %% @doc Handle the timeout event --callback handle_timeout(reference(), Msg :: any(), channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: any(), channel()}. +-callback handle_timeout(reference(), Msg :: any(), channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: any(), channel()}. %% @doc Handle the custom gen_server:call/2 for its connection process --callback handle_call(Req :: any(), From :: gen_server_from(), channel()) - -> {reply, Reply :: any(), channel()} - %% Reply to caller and trigger an event(s) - | {reply, Reply :: any(), - EventOrEvents :: tuple() | list(tuple()), channel()} - | {noreply, channel()} - | {noreply, EventOrEvents :: tuple() | list(tuple()), channel()} - | {shutdown, Reason :: any(), Reply :: any(), channel()} - %% Shutdown the process, reply to caller and write a packet to client - | {shutdown, Reason :: any(), Reply :: any(), - emqx_gateway_frame:frame(), channel()}. +-callback handle_call(Req :: any(), From :: gen_server_from(), channel()) -> + {reply, Reply :: any(), channel()} + %% Reply to caller and trigger an event(s) + | {reply, Reply :: any(), EventOrEvents :: tuple() | list(tuple()), channel()} + | {noreply, channel()} + | {noreply, EventOrEvents :: tuple() | list(tuple()), channel()} + | {shutdown, Reason :: any(), Reply :: any(), channel()} + %% Shutdown the process, reply to caller and write a packet to client + | {shutdown, Reason :: any(), Reply :: any(), emqx_gateway_frame:frame(), channel()}. %% @doc Handle the custom gen_server:cast/2 for its connection process --callback handle_cast(Req :: any(), channel()) - -> ok - | {ok, channel()} - | {shutdown, Reason :: any(), channel()}. +-callback handle_cast(Req :: any(), channel()) -> + ok + | {ok, channel()} + | {shutdown, Reason :: any(), channel()}. %% @doc Handle the custom process messages for its connection process --callback handle_info(Info :: any(), channel()) - -> ok - | {ok, channel()} - | {shutdown, Reason :: any(), channel()}. +-callback handle_info(Info :: any(), channel()) -> + ok + | {ok, channel()} + | {shutdown, Reason :: any(), channel()}. %%-------------------------------------------------------------------- %% Terminate diff --git a/apps/emqx_gateway/src/bhvrs/emqx_gateway_conn.erl b/apps/emqx_gateway/src/bhvrs/emqx_gateway_conn.erl index d13af6f67..676b024dd 100644 --- a/apps/emqx_gateway/src/bhvrs/emqx_gateway_conn.erl +++ b/apps/emqx_gateway/src/bhvrs/emqx_gateway_conn.erl @@ -21,70 +21,74 @@ -include_lib("emqx/include/logger.hrl"). %% API --export([ start_link/3 - , stop/1 - ]). +-export([ + start_link/3, + stop/1 +]). --export([ info/1 - , stats/1 - ]). +-export([ + info/1, + stats/1 +]). --export([ call/2 - , call/3 - , cast/2 - ]). +-export([ + call/2, + call/3, + cast/2 +]). %% Callback -export([init/6]). %% Sys callbacks --export([ system_continue/3 - , system_terminate/4 - , system_code_change/4 - , system_get_state/1 - ]). +-export([ + system_continue/3, + system_terminate/4, + system_code_change/4, + system_get_state/1 +]). %% Internal callback -export([wakeup_from_hib/2, recvloop/2]). -record(state, { - %% TCP/SSL/UDP/DTLS Wrapped Socket - socket :: {esockd_transport, esockd:socket()} | {udp, _, _}, - %% Peername of the connection - peername :: emqx_types:peername(), - %% Sockname of the connection - sockname :: emqx_types:peername(), - %% Sock State - sockstate :: emqx_types:sockstate(), - %% The {active, N} option - active_n :: pos_integer(), - %% Limiter - limiter :: maybe(emqx_limiter:limiter()), - %% Limit Timer - limit_timer :: maybe(reference()), - %% Parse State - parse_state :: emqx_gateway_frame:parse_state(), - %% Serialize options - serialize :: emqx_gateway_frame:serialize_opts(), - %% Channel State - channel :: emqx_gateway_channel:channel(), - %% GC State - gc_state :: maybe(emqx_gc:gc_state()), - %% Stats Timer - stats_timer :: disabled | maybe(reference()), - %% Idle Timeout - idle_timeout :: integer(), - %% Idle Timer - idle_timer :: maybe(reference()), - %% OOM Policy - oom_policy :: maybe(emqx_types:oom_policy()), - %% Frame Module - frame_mod :: atom(), - %% Channel Module - chann_mod :: atom(), - %% Listener Tag - listener :: listener() | undefined - }). + %% TCP/SSL/UDP/DTLS Wrapped Socket + socket :: {esockd_transport, esockd:socket()} | {udp, _, _}, + %% Peername of the connection + peername :: emqx_types:peername(), + %% Sockname of the connection + sockname :: emqx_types:peername(), + %% Sock State + sockstate :: emqx_types:sockstate(), + %% The {active, N} option + active_n :: pos_integer(), + %% Limiter + limiter :: maybe(emqx_limiter:limiter()), + %% Limit Timer + limit_timer :: maybe(reference()), + %% Parse State + parse_state :: emqx_gateway_frame:parse_state(), + %% Serialize options + serialize :: emqx_gateway_frame:serialize_opts(), + %% Channel State + channel :: emqx_gateway_channel:channel(), + %% GC State + gc_state :: maybe(emqx_gc:gc_state()), + %% Stats Timer + stats_timer :: disabled | maybe(reference()), + %% Idle Timeout + idle_timeout :: integer(), + %% Idle Timer + idle_timer :: maybe(reference()), + %% OOM Policy + oom_policy :: maybe(emqx_types:oom_policy()), + %% Frame Module + frame_mod :: atom(), + %% Channel Module + chann_mod :: atom(), + %% Listener Tag + listener :: listener() | undefined +}). -type listener() :: {GwName :: atom(), LisType :: atom(), LisName :: atom()}. -type state() :: #state{}. @@ -95,20 +99,21 @@ -define(ENABLED(X), (X =/= undefined)). --dialyzer({nowarn_function, - [ system_terminate/4 - , handle_call/3 - , handle_msg/2 - , shutdown/3 - , stop/3 - , parse_incoming/3 - ]}). +-dialyzer( + {nowarn_function, [ + system_terminate/4, + handle_call/3, + handle_msg/2, + shutdown/3, + stop/3, + parse_incoming/3 + ]} +). %% udp start_link(Socket = {udp, _SockPid, _Sock}, Peername, Options) -> Args = [self(), Socket, Peername, Options] ++ callback_modules(Options), {ok, proc_lib:spawn_link(?MODULE, init, Args)}; - %% tcp/ssl/dtls start_link(esockd_transport, Sock, Options) -> Socket = {esockd_transport, Sock}, @@ -116,21 +121,24 @@ start_link(esockd_transport, Sock, Options) -> {ok, proc_lib:spawn_link(?MODULE, init, Args)}. callback_modules(Options) -> - [maps:get(frame_mod, Options), - maps:get(chann_mod, Options)]. + [ + maps:get(frame_mod, Options), + maps:get(chann_mod, Options) + ]. %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- %% @doc Get infos of the connection/channel. --spec(info(pid()|state()) -> emqx_types:infos()). +-spec info(pid() | state()) -> emqx_types:infos(). info(CPid) when is_pid(CPid) -> call(CPid, info); info(State = #state{chann_mod = ChannMod, channel = Channel}) -> ChanInfo = ChannMod:info(Channel), SockInfo = maps:from_list( - info(?INFO_KEYS, State)), + info(?INFO_KEYS, State) + ), ChanInfo#{sockinfo => SockInfo}. info(Keys, State) when is_list(Keys) -> @@ -146,16 +154,19 @@ info(sockstate, #state{sockstate = SockSt}) -> info(active_n, #state{active_n = ActiveN}) -> ActiveN. --spec(stats(pid()|state()) -> emqx_types:stats()). +-spec stats(pid() | state()) -> emqx_types:stats(). stats(CPid) when is_pid(CPid) -> call(CPid, stats); -stats(#state{socket = Socket, - chann_mod = ChannMod, - channel = Channel}) -> - SockStats = case esockd_getstat(Socket, ?SOCK_STATS) of - {ok, Ss} -> Ss; - {error, _} -> [] - end, +stats(#state{ + socket = Socket, + chann_mod = ChannMod, + channel = Channel +}) -> + SockStats = + case esockd_getstat(Socket, ?SOCK_STATS) of + {ok, Ss} -> Ss; + {error, _} -> [] + end, ConnStats = emqx_pd:get_counters(?CONN_STATS), ChanStats = ChannMod:stats(Channel), ProcStats = emqx_misc:proc_stats(), @@ -221,8 +232,10 @@ esockd_getstat({udp, _SockPid, Sock}, Stats) -> esockd_getstat({esockd_transport, Sock}, Stats) -> esockd_transport:getstat(Sock, Stats). -esockd_send(Data, #state{socket = {udp, _SockPid, Sock}, - peername = {Ip, Port}}) -> +esockd_send(Data, #state{ + socket = {udp, _SockPid, Sock}, + peername = {Ip, Port} +}) -> gen_udp:send(Sock, Ip, Port, Data); esockd_send(Data, #state{socket = {esockd_transport, Sock}}) -> esockd_transport:async_send(Sock, Data). @@ -238,8 +251,16 @@ init(Parent, WrappedSock, Peername0, Options, FrameMod, ChannMod) -> case esockd_wait(WrappedSock) of {ok, NWrappedSock} -> Peername = esockd_peername(NWrappedSock, Peername0), - run_loop(Parent, init_state(NWrappedSock, Peername, - Options, FrameMod, ChannMod)); + run_loop( + Parent, + init_state( + NWrappedSock, + Peername, + Options, + FrameMod, + ChannMod + ) + ); {error, Reason} -> ok = esockd_close(WrappedSock), exit_on_sock_error(Reason) @@ -248,12 +269,13 @@ init(Parent, WrappedSock, Peername0, Options, FrameMod, ChannMod) -> init_state(WrappedSock, Peername, Options, FrameMod, ChannMod) -> {ok, Sockname} = esockd_ensure_ok_or_exit(sockname, WrappedSock), Peercert = esockd_ensure_ok_or_exit(peercert, WrappedSock), - ConnInfo = #{socktype => esockd_type(WrappedSock), - peername => Peername, - sockname => Sockname, - peercert => Peercert, - conn_mod => ?MODULE - }, + ConnInfo = #{ + socktype => esockd_type(WrappedSock), + peername => Peername, + sockname => Sockname, + peercert => Peercert, + conn_mod => ?MODULE + }, ActiveN = emqx_gateway_utils:active_n(Options), %% FIXME: %%Limiter = emqx_limiter:init(Options), @@ -267,29 +289,34 @@ init_state(WrappedSock, Peername, Options, FrameMod, ChannMod) -> IdleTimeout = emqx_gateway_utils:idle_timeout(Options), OomPolicy = emqx_gateway_utils:oom_policy(Options), IdleTimer = emqx_misc:start_timer(IdleTimeout, idle_timeout), - #state{socket = WrappedSock, - peername = Peername, - sockname = Sockname, - sockstate = idle, - active_n = ActiveN, - limiter = Limiter, - parse_state = ParseState, - serialize = Serialize, - channel = Channel, - gc_state = GcState, - stats_timer = StatsTimer, - idle_timeout = IdleTimeout, - idle_timer = IdleTimer, - oom_policy = OomPolicy, - frame_mod = FrameMod, - chann_mod = ChannMod, - listener = maps:get(listener, Options, undefined) - }. + #state{ + socket = WrappedSock, + peername = Peername, + sockname = Sockname, + sockstate = idle, + active_n = ActiveN, + limiter = Limiter, + parse_state = ParseState, + serialize = Serialize, + channel = Channel, + gc_state = GcState, + stats_timer = StatsTimer, + idle_timeout = IdleTimeout, + idle_timer = IdleTimer, + oom_policy = OomPolicy, + frame_mod = FrameMod, + chann_mod = ChannMod, + listener = maps:get(listener, Options, undefined) + }. -run_loop(Parent, State = #state{socket = Socket, - peername = Peername, - oom_policy = OomPolicy - }) -> +run_loop( + Parent, + State = #state{ + socket = Socket, + peername = Peername, + oom_policy = OomPolicy + } +) -> emqx_logger:set_metadata_peername(esockd:format(Peername)), _ = emqx_misc:tune_heap_size(OomPolicy), case activate_socket(State) of @@ -301,9 +328,11 @@ run_loop(Parent, State = #state{socket = Socket, end. -spec exit_on_sock_error(atom()) -> no_return(). -exit_on_sock_error(Reason) when Reason =:= einval; - Reason =:= enotconn; - Reason =:= closed -> +exit_on_sock_error(Reason) when + Reason =:= einval; + Reason =:= enotconn; + Reason =:= closed +-> erlang:exit(normal); exit_on_sock_error(timeout) -> erlang:exit({shutdown, ssl_upgrade_timeout}); @@ -317,9 +346,8 @@ recvloop(Parent, State = #state{idle_timeout = IdleTimeout}) -> receive Msg -> handle_recv(Msg, Parent, State) - after - IdleTimeout + 100 -> - hibernate(Parent, cancel_stats_timer(State)) + after IdleTimeout + 100 -> + hibernate(Parent, cancel_stats_timer(State)) end. handle_recv({system, From, Request}, Parent, State) -> @@ -347,20 +375,23 @@ wakeup_from_hib(Parent, State) -> ensure_stats_timer(Timeout, State = #state{stats_timer = undefined}) -> State#state{stats_timer = emqx_misc:start_timer(Timeout, emit_stats)}; -ensure_stats_timer(_Timeout, State) -> State. +ensure_stats_timer(_Timeout, State) -> + State. -cancel_stats_timer(State = #state{stats_timer = TRef}) - when is_reference(TRef) -> +cancel_stats_timer(State = #state{stats_timer = TRef}) when + is_reference(TRef) +-> ok = emqx_misc:cancel_timer(TRef), State#state{stats_timer = undefined}; -cancel_stats_timer(State) -> State. +cancel_stats_timer(State) -> + State. %%-------------------------------------------------------------------- %% Process next Msg process_msg([], State) -> {ok, State}; -process_msg([Msg|More], State) -> +process_msg([Msg | More], State) -> try case handle_msg(Msg, State) of ok -> @@ -373,21 +404,26 @@ process_msg([Msg|More], State) -> {stop, Reason, NState} end catch - exit : normal -> + exit:normal -> {stop, normal, State}; - exit : shutdown -> + exit:shutdown -> {stop, shutdown, State}; - exit : {shutdown, _} = Shutdown -> + exit:{shutdown, _} = Shutdown -> {stop, Shutdown, State}; - Exception : Context : Stack -> - {stop, #{exception => Exception, - context => Context, - stacktrace => Stack}, State} + Exception:Context:Stack -> + {stop, + #{ + exception => Exception, + context => Context, + stacktrace => Stack + }, + State} end. append_msg([], Msgs) when is_list(Msgs) -> Msgs; -append_msg([], Msg) -> [Msg]; +append_msg([], Msg) -> + [Msg]; append_msg(Q, Msgs) when is_list(Msgs) -> lists:append(Q, Msgs); append_msg(Q, Msg) -> @@ -412,39 +448,37 @@ handle_msg({'$gen_call', From, Req}, State) -> gen_server:reply(From, Reply), stop(Reason, NState) end; - handle_msg({'$gen_cast', Req}, State) -> with_channel(handle_cast, [Req], State); - handle_msg({datagram, _SockPid, Data}, State) -> parse_incoming(Data, State); - -handle_msg({Inet, _Sock, Data}, State) - when Inet == tcp; - Inet == ssl -> +handle_msg({Inet, _Sock, Data}, State) when + Inet == tcp; + Inet == ssl +-> parse_incoming(Data, State); - -handle_msg({incoming, Packet}, - State = #state{idle_timer = IdleTimer}) -> +handle_msg( + {incoming, Packet}, + State = #state{idle_timer = IdleTimer} +) -> IdleTimer /= undefined andalso emqx_misc:cancel_timer(IdleTimer), NState = State#state{idle_timer = undefined}, handle_incoming(Packet, NState); - handle_msg({outgoing, Data}, State) -> handle_outgoing(Data, State); - -handle_msg({Error, _Sock, Reason}, State) - when Error == tcp_error; Error == ssl_error -> +handle_msg({Error, _Sock, Reason}, State) when + Error == tcp_error; Error == ssl_error +-> handle_info({sock_error, Reason}, State); - -handle_msg({Closed, _Sock}, State) - when Closed == tcp_closed; Closed == ssl_closed -> +handle_msg({Closed, _Sock}, State) when + Closed == tcp_closed; Closed == ssl_closed +-> handle_info({sock_closed, Closed}, close_socket(State)); - %% TODO: udp_passive??? -handle_msg({Passive, _Sock}, State) - when Passive == tcp_passive; Passive == ssl_passive -> +handle_msg({Passive, _Sock}, State) when + Passive == tcp_passive; Passive == ssl_passive +-> %% In Stats Bytes = emqx_pd:reset_counter(incoming_bytes), Pubs = emqx_pd:reset_counter(incoming_pkt), @@ -454,12 +488,12 @@ handle_msg({Passive, _Sock}, State) %% Run GC and Check OOM NState1 = check_oom(run_gc(InStats, NState)), handle_info(activate_socket, NState1); - -handle_msg(Deliver = {deliver, _Topic, _Msg}, - State = #state{active_n = ActiveN}) -> - Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)], +handle_msg( + Deliver = {deliver, _Topic, _Msg}, + State = #state{active_n = ActiveN} +) -> + Delivers = [Deliver | emqx_misc:drain_deliver(ActiveN)], with_channel(handle_deliver, [Delivers], State); - %% Something sent %% TODO: Who will deliver this message? handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) -> @@ -469,52 +503,57 @@ handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) -> Bytes = emqx_pd:reset_counter(outgoing_bytes), OutStats = #{cnt => Pubs, oct => Bytes}, {ok, check_oom(run_gc(OutStats, State))}; - false -> ok + false -> + ok end; - handle_msg({inet_reply, _Sock, {error, Reason}}, State) -> handle_info({sock_error, Reason}, State); - handle_msg({close, Reason}, State) -> ?SLOG(debug, #{msg => "force_socket_close", reason => Reason}), handle_info({sock_closed, Reason}, close_socket(State)); - -handle_msg({event, connected}, State = #state{ - chann_mod = ChannMod, - channel = Channel}) -> +handle_msg( + {event, connected}, + State = #state{ + chann_mod = ChannMod, + channel = Channel + } +) -> Ctx = ChannMod:info(ctx, Channel), ClientId = ChannMod:info(clientid, Channel), emqx_gateway_ctx:insert_channel_info( - Ctx, - ClientId, - info(State), - stats(State) - ); - -handle_msg({event, disconnected}, State = #state{ - chann_mod = ChannMod, - channel = Channel}) -> + Ctx, + ClientId, + info(State), + stats(State) + ); +handle_msg( + {event, disconnected}, + State = #state{ + chann_mod = ChannMod, + channel = Channel + } +) -> Ctx = ChannMod:info(ctx, Channel), ClientId = ChannMod:info(clientid, Channel), emqx_gateway_ctx:set_chan_info(Ctx, ClientId, info(State)), emqx_gateway_ctx:connection_closed(Ctx, ClientId), {ok, State}; - -handle_msg({event, _Other}, State = #state{ - chann_mod = ChannMod, - channel = Channel}) -> +handle_msg( + {event, _Other}, + State = #state{ + chann_mod = ChannMod, + channel = Channel + } +) -> Ctx = ChannMod:info(ctx, Channel), ClientId = ChannMod:info(clientid, Channel), emqx_gateway_ctx:set_chan_info(Ctx, ClientId, info(State)), emqx_gateway_ctx:set_chan_stats(Ctx, ClientId, stats(State)), {ok, State}; - handle_msg({timeout, TRef, TMsg}, State) -> handle_timeout(TRef, TMsg, State); - handle_msg(Shutdown = {shutdown, _Reason}, State) -> stop(Shutdown, State); - handle_msg(Msg, State) -> handle_info(Msg, State). @@ -522,9 +561,13 @@ handle_msg(Msg, State) -> %% Terminate -spec terminate(atom(), state()) -> no_return(). -terminate(Reason, State = #state{ - chann_mod = ChannMod, - channel = Channel}) -> +terminate( + Reason, + State = #state{ + chann_mod = ChannMod, + channel = Channel + } +) -> ?SLOG(debug, #{msg => "conn_process_terminated", reason => Reason}), _ = ChannMod:terminate(Reason, Channel), _ = close_socket(State), @@ -549,13 +592,16 @@ system_get_state(State) -> {ok, State}. handle_call(_From, info, State) -> {reply, info(State), State}; - handle_call(_From, stats, State) -> {reply, stats(State), State}; - -handle_call(From, Req, State = #state{ - chann_mod = ChannMod, - channel = Channel}) -> +handle_call( + From, + Req, + State = #state{ + chann_mod = ChannMod, + channel = Channel + } +) -> case ChannMod:handle_call(Req, From, Channel) of {noreply, NChannel} -> {noreply, State#state{channel = NChannel}}; @@ -578,24 +624,32 @@ handle_call(From, Req, State = #state{ handle_timeout(_TRef, idle_timeout, State) -> shutdown(idle_timeout, State); - handle_timeout(_TRef, limit_timeout, State) -> - NState = State#state{sockstate = idle, - limit_timer = undefined - }, + NState = State#state{ + sockstate = idle, + limit_timer = undefined + }, handle_info(activate_socket, NState); -handle_timeout(TRef, Keepalive, State = #state{ - chann_mod = ChannMod, - socket = Socket, - channel = Channel}) - when Keepalive == keepalive; - Keepalive == keepalive_send -> - Stat = case Keepalive of - keepalive -> recv_oct; - keepalive_send -> send_oct - end, +handle_timeout( + TRef, + Keepalive, + State = #state{ + chann_mod = ChannMod, + socket = Socket, + channel = Channel + } +) when + Keepalive == keepalive; + Keepalive == keepalive_send +-> + Stat = + case Keepalive of + keepalive -> recv_oct; + keepalive_send -> send_oct + end, case ChannMod:info(conn_state, Channel) of - disconnected -> {ok, State}; + disconnected -> + {ok, State}; _ -> case esockd_getstat(Socket, [Stat]) of {ok, [{Stat, RecvOct}]} -> @@ -604,22 +658,29 @@ handle_timeout(TRef, Keepalive, State = #state{ handle_info({sock_error, Reason}, State) end end; -handle_timeout(_TRef, emit_stats, State = - #state{chann_mod = ChannMod, channel = Channel}) -> +handle_timeout( + _TRef, + emit_stats, + State = + #state{chann_mod = ChannMod, channel = Channel} +) -> Ctx = ChannMod:info(ctx, Channel), ClientId = ChannMod:info(clientid, Channel), emqx_gateway_ctx:set_chan_stats(Ctx, ClientId, stats(State)), {ok, State#state{stats_timer = undefined}}; - handle_timeout(TRef, Msg, State) -> with_channel(handle_timeout, [TRef, Msg], State). %%-------------------------------------------------------------------- %% Parse incoming data -parse_incoming(Data, State = #state{ - chann_mod = ChannMod, - channel = Channel}) -> +parse_incoming( + Data, + State = #state{ + chann_mod = ChannMod, + channel = Channel + } +) -> ?SLOG(debug, #{msg => "RECV_data", data => Data}), Oct = iolist_size(Data), inc_counter(incoming_bytes, Oct), @@ -630,25 +691,29 @@ parse_incoming(Data, State = #state{ parse_incoming(<<>>, Packets, State) -> {Packets, State}; - -parse_incoming(Data, Packets, - State = #state{ - frame_mod = FrameMod, - parse_state = ParseState}) -> +parse_incoming( + Data, + Packets, + State = #state{ + frame_mod = FrameMod, + parse_state = ParseState + } +) -> try FrameMod:parse(Data, ParseState) of {more, NParseState} -> {Packets, State#state{parse_state = NParseState}}; {ok, Packet, Rest, NParseState} -> NState = State#state{parse_state = NParseState}, - parse_incoming(Rest, [Packet|Packets], NState) + parse_incoming(Rest, [Packet | Packets], NState) catch error:Reason:Stk -> - ?SLOG(error, #{ msg => "parse_frame_failed" - , at_state => ParseState - , input_bytes => Data - , reason => Reason - , stacktrace => Stk - }), + ?SLOG(error, #{ + msg => "parse_frame_failed", + at_state => ParseState, + input_bytes => Data, + reason => Reason, + stacktrace => Stk + }), {[{frame_error, Reason} | Packets], State} end. @@ -660,26 +725,36 @@ next_incoming_msgs(Packets) -> %%-------------------------------------------------------------------- %% Handle incoming packet -handle_incoming(Packet, State = #state{ - channel = Channel, - frame_mod = FrameMod, - chann_mod = ChannMod - }) -> +handle_incoming( + Packet, + State = #state{ + channel = Channel, + frame_mod = FrameMod, + chann_mod = ChannMod + } +) -> Ctx = ChannMod:info(ctx, Channel), ok = inc_incoming_stats(Ctx, FrameMod, Packet), - ?SLOG(debug, #{ msg => "RECV_packet" - , packet => FrameMod:format(Packet) - }), + ?SLOG(debug, #{ + msg => "RECV_packet", + packet => FrameMod:format(Packet) + }), with_channel(handle_in, [Packet], State). %%-------------------------------------------------------------------- %% With Channel -with_channel(Fun, Args, State = #state{ - chann_mod = ChannMod, - channel = Channel}) -> +with_channel( + Fun, + Args, + State = #state{ + chann_mod = ChannMod, + channel = Channel + } +) -> case erlang:apply(ChannMod, Fun, Args ++ [Channel]) of - ok -> {ok, State}; + ok -> + {ok, State}; {ok, NChannel} -> {ok, State#state{channel = NChannel}}; {ok, Replies, NChannel} -> @@ -697,69 +772,84 @@ with_channel(Fun, Args, State = #state{ handle_outgoing(_Packets = [], _State) -> ok; -handle_outgoing(Packets, - State = #state{socket = Socket}) when is_list(Packets) -> +handle_outgoing( + Packets, + State = #state{socket = Socket} +) when is_list(Packets) -> case is_datadram_socket(Socket) of false -> send( - lists:map(serialize_and_inc_stats_fun(State), Packets), - State); + lists:map(serialize_and_inc_stats_fun(State), Packets), + State + ); _ -> - lists:foreach(fun(Packet) -> - handle_outgoing(Packet, State) - end, Packets) + lists:foreach( + fun(Packet) -> + handle_outgoing(Packet, State) + end, + Packets + ) end; - handle_outgoing(Packet, State) -> send((serialize_and_inc_stats_fun(State))(Packet), State). serialize_and_inc_stats_fun(#state{ - frame_mod = FrameMod, - chann_mod = ChannMod, - serialize = Serialize, - channel = Channel}) -> + frame_mod = FrameMod, + chann_mod = ChannMod, + serialize = Serialize, + channel = Channel +}) -> Ctx = ChannMod:info(ctx, Channel), fun(Packet) -> try Data = FrameMod:serialize_pkt(Packet, Serialize), - ?SLOG(debug, #{ msg => "SEND_packet" - %% XXX: optimize it, less cpu comsuption? - , packet => FrameMod:format(Packet) - }), + ?SLOG(debug, #{ + msg => "SEND_packet", + %% XXX: optimize it, less cpu comsuption? + packet => FrameMod:format(Packet) + }), ok = inc_outgoing_stats(Ctx, FrameMod, Packet), Data catch - _ : too_large -> - ?SLOG(warning, #{ msg => "packet_too_large_discarded" - , packet => FrameMod:format(Packet) - }), - ok = emqx_gateway_ctx:metrics_inc(Ctx, 'delivery.dropped.too_large'), - ok = emqx_gateway_ctx:metrics_inc(Ctx, 'delivery.dropped'), - <<>>; - _ : Reason -> - ?SLOG(warning, #{ msg => "packet_serialize_failure" - , reason => Reason - , packet => FrameMod:format(Packet) - }), - ok = emqx_gateway_ctx:metrics_inc(Ctx, 'delivery.dropped'), - <<>> + _:too_large -> + ?SLOG(warning, #{ + msg => "packet_too_large_discarded", + packet => FrameMod:format(Packet) + }), + ok = emqx_gateway_ctx:metrics_inc(Ctx, 'delivery.dropped.too_large'), + ok = emqx_gateway_ctx:metrics_inc(Ctx, 'delivery.dropped'), + <<>>; + _:Reason -> + ?SLOG(warning, #{ + msg => "packet_serialize_failure", + reason => Reason, + packet => FrameMod:format(Packet) + }), + ok = emqx_gateway_ctx:metrics_inc(Ctx, 'delivery.dropped'), + <<>> end end. %%-------------------------------------------------------------------- %% Send data --spec(send(iodata(), state()) -> ok). -send(IoData, State = #state{socket = Socket, - chann_mod = ChannMod, - channel = Channel}) -> +-spec send(iodata(), state()) -> ok. +send( + IoData, + State = #state{ + socket = Socket, + chann_mod = ChannMod, + channel = Channel + } +) -> ?SLOG(debug, #{msg => "SEND_data", data => IoData}), Ctx = ChannMod:info(ctx, Channel), Oct = iolist_size(IoData), ok = emqx_gateway_ctx:metrics_inc(Ctx, 'bytes.sent', Oct), inc_counter(outgoing_bytes, Oct), case esockd_send(IoData, State) of - ok -> ok; + ok -> + ok; Error = {error, _Reason} -> %% Send an inet_reply to postpone handling the error self() ! {inet_reply, Socket, Error}, @@ -772,20 +862,21 @@ send(IoData, State = #state{socket = Socket, handle_info(activate_socket, State = #state{sockstate = OldSst}) -> case activate_socket(State) of {ok, NState = #state{sockstate = NewSst}} -> - if OldSst =/= NewSst -> - {ok, {event, NewSst}, NState}; - true -> {ok, NState} + if + OldSst =/= NewSst -> + {ok, {event, NewSst}, NState}; + true -> + {ok, NState} end; {error, Reason} -> handle_info({sock_error, Reason}, State) end; - handle_info({sock_error, Reason}, State) -> - ?SLOG(debug, #{ msg => "sock_error" - , reason => Reason - }), + ?SLOG(debug, #{ + msg => "sock_error", + reason => Reason + }), handle_info({sock_closed, Reason}, close_socket(State)); - handle_info(Info, State) -> with_channel(handle_info, [Info], State). @@ -794,19 +885,22 @@ handle_info(Info, State) -> ensure_rate_limit(Stats, State = #state{limiter = Limiter}) -> case ?ENABLED(Limiter) andalso emqx_limiter:check(Stats, Limiter) of - false -> State; + false -> + State; {ok, Limiter1} -> State#state{limiter = Limiter1}; {pause, Time, Limiter1} -> %% XXX: which limiter reached? - ?SLOG(warning, #{ msg => "reach_rate_limit" - , pause => Time - }), + ?SLOG(warning, #{ + msg => "reach_rate_limit", + pause => Time + }), TRef = emqx_misc:start_timer(Time, limit_timeout), - State#state{sockstate = blocked, - limiter = Limiter1, - limit_timer = TRef - } + State#state{ + sockstate = blocked, + limiter = Limiter1, + limit_timer = TRef + } end. %%-------------------------------------------------------------------- @@ -815,8 +909,7 @@ ensure_rate_limit(Stats, State = #state{limiter = Limiter}) -> run_gc(Stats, State = #state{gc_state = GcSt}) -> case ?ENABLED(GcSt) andalso emqx_gc:run(Stats, GcSt) of false -> State; - {_IsGC, GcSt1} -> - State#state{gc_state = GcSt1} + {_IsGC, GcSt1} -> State#state{gc_state = GcSt1} end. check_oom(State = #state{oom_policy = OomPolicy}) -> @@ -824,7 +917,8 @@ check_oom(State = #state{oom_policy = OomPolicy}) -> {shutdown, Reason} -> %% triggers terminate/2 callback immediately erlang:exit({shutdown, Reason}); - _Other -> ok + _Other -> + ok end, State. @@ -835,8 +929,12 @@ activate_socket(State = #state{sockstate = closed}) -> {ok, State}; activate_socket(State = #state{sockstate = blocked}) -> {ok, State}; -activate_socket(State = #state{socket = Socket, - active_n = N}) -> +activate_socket( + State = #state{ + socket = Socket, + active_n = N + } +) -> %% FIXME: Works on dtls/udp ??? %% How to handle buffer? case esockd_setopts(Socket, [{active, N}]) of @@ -847,7 +945,8 @@ activate_socket(State = #state{socket = Socket, %%-------------------------------------------------------------------- %% Close Socket -close_socket(State = #state{sockstate = closed}) -> State; +close_socket(State = #state{sockstate = closed}) -> + State; close_socket(State = #state{socket = Socket}) -> ok = esockd_close(Socket), State#state{sockstate = closed}. @@ -865,7 +964,8 @@ inc_incoming_stats(Ctx, FrameMod, Packet) -> ok end, Name = list_to_atom( - lists:concat(["packets.", FrameMod:type(Packet), ".received"])), + lists:concat(["packets.", FrameMod:type(Packet), ".received"]) + ), emqx_gateway_ctx:metrics_inc(Ctx, Name). inc_outgoing_stats(Ctx, FrameMod, Packet) -> @@ -878,7 +978,8 @@ inc_outgoing_stats(Ctx, FrameMod, Packet) -> ok end, Name = list_to_atom( - lists:concat(["packets.", FrameMod:type(Packet), ".sent"])), + lists:concat(["packets.", FrameMod:type(Packet), ".sent"]) + ), emqx_gateway_ctx:metrics_inc(Ctx, Name). %%-------------------------------------------------------------------- diff --git a/apps/emqx_gateway/src/bhvrs/emqx_gateway_frame.erl b/apps/emqx_gateway/src/bhvrs/emqx_gateway_frame.erl index fed06173f..909d6f7b3 100644 --- a/apps/emqx_gateway/src/bhvrs/emqx_gateway_frame.erl +++ b/apps/emqx_gateway/src/bhvrs/emqx_gateway_frame.erl @@ -26,17 +26,18 @@ -type frame() :: any(). --type parse_result() :: {ok, frame(), - Rest :: binary(), NewState :: parse_state()} - | {more, NewState :: parse_state()}. +-type parse_result() :: + {ok, frame(), Rest :: binary(), NewState :: parse_state()} + | {more, NewState :: parse_state()}. -type serialize_options() :: map(). --export_type([ parse_state/0 - , parse_result/0 - , serialize_options/0 - , frame/0 - ]). +-export_type([ + parse_state/0, + parse_result/0, + serialize_options/0, + frame/0 +]). %% Callbacks @@ -60,4 +61,3 @@ %% @doc -callback is_message(Frame :: any()) -> boolean(). - diff --git a/apps/emqx_gateway/src/bhvrs/emqx_gateway_impl.erl b/apps/emqx_gateway/src/bhvrs/emqx_gateway_impl.erl index e108c99c0..cb4c61771 100644 --- a/apps/emqx_gateway/src/bhvrs/emqx_gateway_impl.erl +++ b/apps/emqx_gateway/src/bhvrs/emqx_gateway_impl.erl @@ -22,21 +22,25 @@ -type reason() :: any(). %% @doc --callback on_gateway_load(Gateway :: gateway(), - Ctx :: emqx_gateway_ctx:context()) - -> {error, reason()} - | {ok, [ChildPid :: pid()], GwState :: state()} - %% TODO: v0.2 The child spec is better for restarting child process - | {ok, [Childspec :: supervisor:child_spec()], GwState :: state()}. +-callback on_gateway_load( + Gateway :: gateway(), + Ctx :: emqx_gateway_ctx:context() +) -> + {error, reason()} + | {ok, [ChildPid :: pid()], GwState :: state()} + %% TODO: v0.2 The child spec is better for restarting child process + | {ok, [Childspec :: supervisor:child_spec()], GwState :: state()}. %% @doc --callback on_gateway_update(Config :: emqx_config:config(), - Gateway :: gateway(), - GwState :: state()) - -> ok - | {ok, [ChildPid :: pid()], NGwState :: state()} - | {ok, [Childspec :: supervisor:child_spec()], NGwState :: state()} - | {error, reason()}. +-callback on_gateway_update( + Config :: emqx_config:config(), + Gateway :: gateway(), + GwState :: state() +) -> + ok + | {ok, [ChildPid :: pid()], NGwState :: state()} + | {ok, [Childspec :: supervisor:child_spec()], NGwState :: state()} + | {error, reason()}. %% @doc -callback on_gateway_unload(Gateway :: gateway(), GwState :: state()) -> ok. diff --git a/apps/emqx_gateway/src/coap/emqx_coap_api.erl b/apps/emqx_gateway/src/coap/emqx_coap_api.erl index 0b08681e1..118aef798 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_api.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_api.erl @@ -44,17 +44,22 @@ paths() -> [?PREFIX ++ "/request"]. schema(?PREFIX ++ "/request") -> - #{operationId => request, - post => #{ tags => [<<"gateway|coap">>] - , desc => <<"Send a CoAP request message to the client">> - , parameters => request_parameters() - , requestBody => request_body() - , responses => #{200 => coap_message(), - 404 => error_codes(['CLIENT_NOT_FOUND'], <<"Client not found error">>), - 504 => error_codes(['CLIENT_NOT_RESPONSE'], <<"Waiting for client response timeout">>)} - } - }. - + #{ + operationId => request, + post => #{ + tags => [<<"gateway|coap">>], + desc => <<"Send a CoAP request message to the client">>, + parameters => request_parameters(), + requestBody => request_body(), + responses => #{ + 200 => coap_message(), + 404 => error_codes(['CLIENT_NOT_FOUND'], <<"Client not found error">>), + 504 => error_codes( + ['CLIENT_NOT_RESPONSE'], <<"Waiting for client response timeout">> + ) + } + } + }. request(post, #{body := Body, bindings := Bindings}) -> ClientId = maps:get(clientid, Bindings, undefined), @@ -66,8 +71,12 @@ request(post, #{body := Body, bindings := Bindings}) -> CT = erlang:atom_to_binary(AtomCT), Payload2 = parse_payload(CT, Payload), - Msg = emqx_coap_message:request(con, - Method, Payload2, #{content_format => CT}), + Msg = emqx_coap_message:request( + con, + Method, + Payload2, + #{content_format => CT} + ), Msg2 = Msg#coap_message{token = Token}, @@ -78,7 +87,7 @@ request(post, #{body := Body, bindings := Bindings}) -> {404, #{code => 'CLIENT_NOT_FOUND'}}; Response -> {200, format_to_response(CT, Response)} - end. + end. %%-------------------------------------------------------------------- %% Internal functions @@ -87,42 +96,49 @@ request_parameters() -> [{clientid, mk(binary(), #{in => path, required => true})}]. request_body() -> - [ {token, mk(binary(), #{desc => "message token, can be empty"})} - , {method, mk(enum([get, put, post, delete]), #{desc => "request method type"})} - , {timeout, mk(emqx_schema:duration_ms(), #{desc => "timespan for response"})} - , {content_type, mk(enum(['text/plain', 'application/json', 'application/octet-stream']), - #{desc => "payload type"})} - , {payload, mk(binary(), #{desc => "the content of the payload"})} + [ + {token, mk(binary(), #{desc => "message token, can be empty"})}, + {method, mk(enum([get, put, post, delete]), #{desc => "request method type"})}, + {timeout, mk(emqx_schema:duration_ms(), #{desc => "timespan for response"})}, + {content_type, + mk( + enum(['text/plain', 'application/json', 'application/octet-stream']), + #{desc => "payload type"} + )}, + {payload, mk(binary(), #{desc => "the content of the payload"})} ]. coap_message() -> - [ {id, mk(integer(), #{desc => "message id"})} - , {token, mk(string(), #{desc => "message token, can be empty"})} - , {method, mk(string(), #{desc => "response code"})} - , {payload, mk(string(), #{desc => "payload"})} + [ + {id, mk(integer(), #{desc => "message id"})}, + {token, mk(string(), #{desc => "message token, can be empty"})}, + {method, mk(string(), #{desc => "response code"})}, + {payload, mk(string(), #{desc => "payload"})} ]. -format_to_response(ContentType, #coap_message{id = Id, - token = Token, - method = Method, - payload = Payload}) -> - #{id => Id, - token => Token, - method => format_to_binary(Method), - payload => format_payload(ContentType, Payload)}. +format_to_response(ContentType, #coap_message{ + id = Id, + token = Token, + method = Method, + payload = Payload +}) -> + #{ + id => Id, + token => Token, + method => format_to_binary(Method), + payload => format_payload(ContentType, Payload) + }. format_to_binary(Obj) -> erlang:list_to_binary(io_lib:format("~p", [Obj])). format_payload(<<"application/octet-stream">>, Payload) -> base64:encode(Payload); - format_payload(_, Payload) -> Payload. parse_payload(<<"application/octet-stream">>, Body) -> base64:decode(Body); - parse_payload(_, Body) -> Body. @@ -140,10 +156,13 @@ call_client(ClientId, Msg, Timeout) -> _ -> not_found end - catch _:Error:Trace -> - ?SLOG(warning, #{msg => "coap_client_call_exception", - clientid => ClientId, - error => Error, - stacktrace => Trace}), + catch + _:Error:Trace -> + ?SLOG(warning, #{ + msg => "coap_client_call_exception", + clientid => ClientId, + error => Error, + stacktrace => Trace + }), not_found end. diff --git a/apps/emqx_gateway/src/coap/emqx_coap_channel.erl b/apps/emqx_gateway/src/coap/emqx_coap_channel.erl index bed769ad6..04c296a39 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_channel.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_channel.erl @@ -19,26 +19,29 @@ -behaviour(emqx_gateway_channel). %% API --export([ info/1 - , info/2 - , stats/1 - , validator/4 - , metrics_inc/2 - , run_hooks/3 - , send_request/2 - ]). +-export([ + info/1, + info/2, + stats/1, + validator/4, + metrics_inc/2, + run_hooks/3, + send_request/2 +]). --export([ init/2 - , handle_in/2 - , handle_deliver/2 - , handle_timeout/3 - , terminate/2 - ]). +-export([ + init/2, + handle_in/2, + handle_deliver/2, + handle_timeout/3, + terminate/2 +]). --export([ handle_call/3 - , handle_cast/2 - , handle_info/2 - ]). +-export([ + handle_call/3, + handle_cast/2, + handle_info/2 +]). -export_type([channel/0]). @@ -49,34 +52,35 @@ -define(AUTHN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). -record(channel, { - %% Context - ctx :: emqx_gateway_ctx:context(), - %% Connection Info - conninfo :: emqx_types:conninfo(), - %% Client Info - clientinfo :: emqx_types:clientinfo(), - %% Session - session :: emqx_coap_session:session() | undefined, - %% Keepalive - keepalive :: emqx_keepalive:keepalive() | undefined, - %% Timer - timers :: #{atom() => disable | undefined | reference()}, - %% Connection mode - connection_required :: boolean(), - %% Connection State - conn_state :: conn_state(), - %% Session token to identity this connection - token :: binary() | undefined - }). + %% Context + ctx :: emqx_gateway_ctx:context(), + %% Connection Info + conninfo :: emqx_types:conninfo(), + %% Client Info + clientinfo :: emqx_types:clientinfo(), + %% Session + session :: emqx_coap_session:session() | undefined, + %% Keepalive + keepalive :: emqx_keepalive:keepalive() | undefined, + %% Timer + timers :: #{atom() => disable | undefined | reference()}, + %% Connection mode + connection_required :: boolean(), + %% Connection State + conn_state :: conn_state(), + %% Session token to identity this connection + token :: binary() | undefined +}). -type channel() :: #channel{}. -type conn_state() :: idle | connecting | connected | disconnected. --type reply() :: {outgoing, coap_message()} - | {outgoing, [coap_message()]} - | {event, conn_state()|updated} - | {close, Reason :: atom()}. +-type reply() :: + {outgoing, coap_message()} + | {outgoing, [coap_message()]} + | {event, conn_state() | updated} + | {close, Reason :: atom()}. -type replies() :: reply() | [reply()]. @@ -97,10 +101,9 @@ info(Channel) -> maps:from_list(info(?INFO_KEYS, Channel)). --spec info(list(atom())|atom(), channel()) -> term(). +-spec info(list(atom()) | atom(), channel()) -> term(). info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; - info(conninfo, #channel{conninfo = ConnInfo}) -> ConnInfo; info(conn_state, #channel{conn_state = ConnState}) -> @@ -119,41 +122,47 @@ stats(_) -> []. -spec init(map(), map()) -> channel(). -init(ConnInfo = #{peername := {PeerHost, _}, - sockname := {_, SockPort}}, - #{ctx := Ctx} = Config) -> +init( + ConnInfo = #{ + peername := {PeerHost, _}, + sockname := {_, SockPort} + }, + #{ctx := Ctx} = Config +) -> Peercert = maps:get(peercert, ConnInfo, undefined), Mountpoint = maps:get(mountpoint, Config, <<>>), - ListenerId = case maps:get(listener, Config, undefined) of - undefined -> undefined; - {GwName, Type, LisName} -> - emqx_gateway_utils:listener_id(GwName, Type, LisName) - end, + ListenerId = + case maps:get(listener, Config, undefined) of + undefined -> undefined; + {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName) + end, ClientInfo = set_peercert_infos( - Peercert, - #{ zone => default - , listener => ListenerId - , protocol => 'coap' - , peerhost => PeerHost - , sockport => SockPort - , clientid => emqx_guid:to_base62(emqx_guid:gen()) - , username => undefined - , is_bridge => false - , is_superuser => false - , mountpoint => Mountpoint - } - ), + Peercert, + #{ + zone => default, + listener => ListenerId, + protocol => 'coap', + peerhost => PeerHost, + sockport => SockPort, + clientid => emqx_guid:to_base62(emqx_guid:gen()), + username => undefined, + is_bridge => false, + is_superuser => false, + mountpoint => Mountpoint + } + ), Heartbeat = ?GET_IDLE_TIME(Config), - #channel{ ctx = Ctx - , conninfo = ConnInfo - , clientinfo = ClientInfo - , timers = #{} - , session = emqx_coap_session:new() - , keepalive = emqx_keepalive:init(Heartbeat) - , connection_required = maps:get(connection_required, Config, false) - , conn_state = idle - }. + #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo, + timers = #{}, + session = emqx_coap_session:new(), + keepalive = emqx_keepalive:init(Heartbeat), + connection_required = maps:get(connection_required, Config, false), + conn_state = idle + }. validator(Type, Topic, Ctx, ClientInfo) -> emqx_gateway_ctx:authorize(Ctx, ClientInfo, Type, Topic). @@ -166,11 +175,11 @@ send_request(Channel, Request) -> %% Handle incoming packet %%-------------------------------------------------------------------- --spec handle_in(coap_message() | {frame_error, any()}, channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()} - | {shutdown, Reason :: term(), replies(), channel()}. +-spec handle_in(coap_message() | {frame_error, any()}, channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), replies(), channel()}. handle_in(Msg, ChannleT) -> Channel = ensure_keepalive_timer(ChannleT), case emqx_coap_message:is_request(Msg) of @@ -183,8 +192,13 @@ handle_in(Msg, ChannleT) -> %%-------------------------------------------------------------------- %% Handle Delivers from broker to client %%-------------------------------------------------------------------- -handle_deliver(Delivers, #channel{session = Session, - ctx = Ctx} = Channel) -> +handle_deliver( + Delivers, + #channel{ + session = Session, + ctx = Ctx + } = Channel +) -> handle_result(emqx_coap_session:deliver(Delivers, Ctx, Session), Channel). %%-------------------------------------------------------------------- @@ -199,13 +213,10 @@ handle_timeout(_, {keepalive, NewVal}, #channel{keepalive = KeepAlive} = Channel {error, timeout} -> {shutdown, timeout, ensure_disconnected(keepalive_timeout, Channel)} end; - handle_timeout(_, {transport, Msg}, Channel) -> call_session(timeout, Msg, Channel); - handle_timeout(_, disconnect, Channel) -> {shutdown, normal, Channel}; - handle_timeout(_, _, Channel) -> {ok, Channel}. @@ -213,72 +224,89 @@ handle_timeout(_, _, Channel) -> %% Handle call %%-------------------------------------------------------------------- --spec(handle_call(Req :: term(), From :: term(), channel()) - -> {reply, Reply :: term(), channel()} - | {reply, Reply :: term(), replies(), channel()} - | {shutdown, Reason :: term(), Reply :: term(), channel()} - | {shutdown, Reason :: term(), Reply :: term(), coap_message(), channel()}). +-spec handle_call(Req :: term(), From :: term(), channel()) -> + {reply, Reply :: term(), channel()} + | {reply, Reply :: term(), replies(), channel()} + | {shutdown, Reason :: term(), Reply :: term(), channel()} + | {shutdown, Reason :: term(), Reply :: term(), coap_message(), channel()}. handle_call({send_request, Msg}, From, Channel) -> Result = call_session(handle_out, {{send_request, From}, Msg}, Channel), erlang:setelement(1, Result, noreply); - -handle_call({subscribe, Topic, SubOpts}, _From, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - = #{clientid := ClientId, - mountpoint := Mountpoint}, - session = Session}) -> - Token = maps:get(token, - maps:get(sub_props, SubOpts, #{}), - <<>>), +handle_call( + {subscribe, Topic, SubOpts}, + _From, + Channel = #channel{ + ctx = Ctx, + clientinfo = + ClientInfo = + #{ + clientid := ClientId, + mountpoint := Mountpoint + }, + session = Session + } +) -> + Token = maps:get( + token, + maps:get(sub_props, SubOpts, #{}), + <<>> + ), NSubOpts = maps:merge( - emqx_gateway_utils:default_subopts(), - SubOpts), + emqx_gateway_utils:default_subopts(), + SubOpts + ), MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), _ = emqx_broker:subscribe(MountedTopic, ClientId, NSubOpts), - _ = run_hooks(Ctx, 'session.subscribed', - [ClientInfo, MountedTopic, NSubOpts]), + _ = run_hooks( + Ctx, + 'session.subscribed', + [ClientInfo, MountedTopic, NSubOpts] + ), %% modify session state SubReq = {Topic, Token}, TempMsg = #coap_message{type = non}, %% FIXME: The subopts is not used for emqx_coap_session - Result = emqx_coap_session:process_subscribe( - SubReq, TempMsg, #{}, Session), + Result = emqx_coap_session:process_subscribe( + SubReq, TempMsg, #{}, Session + ), NSession = maps:get(session, Result), {reply, {ok, {MountedTopic, NSubOpts}}, Channel#channel{session = NSession}}; - -handle_call({unsubscribe, Topic}, _From, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - = #{mountpoint := Mountpoint}, - session = Session}) -> +handle_call( + {unsubscribe, Topic}, + _From, + Channel = #channel{ + ctx = Ctx, + clientinfo = + ClientInfo = + #{mountpoint := Mountpoint}, + session = Session + } +) -> MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), ok = emqx_broker:unsubscribe(MountedTopic), - _ = run_hooks(Ctx, 'session.unsubscribe', - [ClientInfo, MountedTopic, #{}]), + _ = run_hooks( + Ctx, + 'session.unsubscribe', + [ClientInfo, MountedTopic, #{}] + ), %% modify session state UnSubReq = Topic, TempMsg = #coap_message{type = non}, - Result = emqx_coap_session:process_subscribe( - UnSubReq, TempMsg, #{}, Session), + Result = emqx_coap_session:process_subscribe( + UnSubReq, TempMsg, #{}, Session + ), NSession = maps:get(session, Result), {reply, ok, Channel#channel{session = NSession}}; - handle_call(subscriptions, _From, Channel = #channel{session = Session}) -> Subs = emqx_coap_session:info(subscriptions, Session), {reply, {ok, maps:to_list(Subs)}, Channel}; - handle_call(kick, _From, Channel) -> NChannel = ensure_disconnected(kicked, Channel), shutdown_and_reply(kicked, ok, NChannel); - handle_call(discard, _From, Channel) -> shutdown_and_reply(discarded, ok, Channel); - handle_call(Req, _From, Channel) -> ?SLOG(error, #{msg => "unexpected_call", call => Req}), {reply, ignored, Channel}. @@ -287,8 +315,8 @@ handle_call(Req, _From, Channel) -> %% Handle Cast %%-------------------------------------------------------------------- --spec handle_cast(Req :: term(), channel()) - -> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. +-spec handle_cast(Req :: term(), channel()) -> + ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. handle_cast(Req, Channel) -> ?SLOG(error, #{msg => "unexpected_cast", cast => Req}), {ok, Channel}. @@ -297,11 +325,10 @@ handle_cast(Req, Channel) -> %% Handle Info %%-------------------------------------------------------------------- --spec(handle_info(Info :: term(), channel()) - -> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}). +-spec handle_info(Info :: term(), channel()) -> + ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. handle_info({subscribe, _AutoSubs}, Channel) -> {ok, Channel}; - handle_info(Info, Channel) -> ?SLOG(warning, #{msg => "unexpected_info", info => Info}), {ok, Channel}. @@ -309,21 +336,23 @@ handle_info(Info, Channel) -> %%-------------------------------------------------------------------- %% Terminate %%-------------------------------------------------------------------- -terminate(Reason, #channel{clientinfo = ClientInfo, - ctx = Ctx, - session = Session}) -> +terminate(Reason, #channel{ + clientinfo = ClientInfo, + ctx = Ctx, + session = Session +}) -> run_hooks(Ctx, 'session.terminated', [ClientInfo, Reason, Session]). %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- -set_peercert_infos(NoSSL, ClientInfo) - when NoSSL =:= nossl; - NoSSL =:= undefined -> +set_peercert_infos(NoSSL, ClientInfo) when + NoSSL =:= nossl; + NoSSL =:= undefined +-> ClientInfo; set_peercert_infos(Peercert, ClientInfo) -> - {DN, CN} = {esockd_peercert:subject(Peercert), - esockd_peercert:common_name(Peercert)}, + {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)}, ClientInfo#{dn => DN, cn => CN}. ensure_timer(Name, Time, Msg, #channel{timers = Timers} = Channel) -> @@ -348,15 +377,21 @@ ensure_keepalive_timer(Fun, #channel{keepalive = KeepAlive} = Channel) -> check_auth_state(Msg, #channel{connection_required = Required} = Channel) -> check_token(Required, Msg, Channel). -check_token(true, - Msg, - #channel{token = Token, - clientinfo = ClientInfo, - conn_state = CState} = Channel) -> +check_token( + true, + Msg, + #channel{ + token = Token, + clientinfo = ClientInfo, + conn_state = CState + } = Channel +) -> #{clientid := ClientId} = ClientInfo, case emqx_coap_message:get_option(uri_query, Msg) of - #{<<"clientid">> := ClientId, - <<"token">> := Token} -> + #{ + <<"clientid">> := ClientId, + <<"token">> := Token + } -> call_session(handle_request, Msg, Channel); #{<<"clientid">> := DesireId} -> try_takeover(CState, DesireId, Msg, Channel); @@ -364,7 +399,6 @@ check_token(true, Reply = emqx_coap_message:piggyback({error, unauthorized}, Msg), {ok, {outgoing, Reply}, Channel} end; - check_token(false, Msg, Channel) -> call_session(handle_request, Msg, Channel). @@ -383,7 +417,6 @@ try_takeover(idle, DesireId, Msg, Channel) -> do_takeover(DesireId, Msg, Channel) end end; - try_takeover(_, DesireId, Msg, Channel) -> do_takeover(DesireId, Msg, Channel). @@ -392,43 +425,57 @@ do_takeover(_DesireId, Msg, Channel) -> Reset = emqx_coap_message:reset(Msg), {ok, {outgoing, Reset}, Channel}. -run_conn_hooks(Input, Channel = #channel{ctx = Ctx, - conninfo = ConnInfo}) -> +run_conn_hooks( + Input, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo + } +) -> ConnProps = #{}, case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of Error = {error, _Reason} -> Error; - _NConnProps -> - {ok, Input, Channel} + _NConnProps -> {ok, Input, Channel} end. -enrich_conninfo({Queries, _Msg}, - Channel = #channel{ - keepalive = KeepAlive, - conninfo = ConnInfo}) -> +enrich_conninfo( + {Queries, _Msg}, + Channel = #channel{ + keepalive = KeepAlive, + conninfo = ConnInfo + } +) -> case Queries of #{<<"clientid">> := ClientId} -> Interval = maps:get(interval, emqx_keepalive:info(KeepAlive)), - NConnInfo = ConnInfo#{ clientid => ClientId - , proto_name => <<"CoAP">> - , proto_ver => <<"1">> - , clean_start => true - , keepalive => Interval - , expiry_interval => 0 - }, + NConnInfo = ConnInfo#{ + clientid => ClientId, + proto_name => <<"CoAP">>, + proto_ver => <<"1">>, + clean_start => true, + keepalive => Interval, + expiry_interval => 0 + }, {ok, Channel#channel{conninfo = NConnInfo}}; _ -> {error, "invalid queries", Channel} end. -enrich_clientinfo({Queries, Msg}, - Channel = #channel{clientinfo = ClientInfo0}) -> +enrich_clientinfo( + {Queries, Msg}, + Channel = #channel{clientinfo = ClientInfo0} +) -> case Queries of - #{<<"username">> := UserName, - <<"password">> := Password, - <<"clientid">> := ClientId} -> - ClientInfo = ClientInfo0#{username => UserName, - password => Password, - clientid => ClientId}, + #{ + <<"username">> := UserName, + <<"password">> := Password, + <<"clientid">> := ClientId + } -> + ClientInfo = ClientInfo0#{ + username => UserName, + password => Password, + clientid => ClientId + }, {ok, NClientInfo} = fix_mountpoint(Msg, ClientInfo), {ok, Channel#channel{clientinfo = NClientInfo}}; _ -> @@ -439,19 +486,27 @@ set_log_meta(_Input, #channel{clientinfo = #{clientid := ClientId}}) -> emqx_logger:set_metadata_clientid(ClientId), ok. -auth_connect(_Input, Channel = #channel{ctx = Ctx, - clientinfo = ClientInfo}) -> - #{clientid := ClientId, - username := Username} = ClientInfo, +auth_connect( + _Input, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> + #{ + clientid := ClientId, + username := Username + } = ClientInfo, case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of {ok, NClientInfo} -> {ok, Channel#channel{clientinfo = NClientInfo}}; {error, Reason} -> - ?SLOG(warning, #{ msg => "client_login_failed" - , username => Username - , clientid => ClientId - , reason => Reason - }), + ?SLOG(warning, #{ + msg => "client_login_failed", + username => Username, + clientid => ClientId, + reason => Reason + }), {error, Reason} end. @@ -461,33 +516,44 @@ fix_mountpoint(_Packet, ClientInfo = #{mountpoint := Mountpoint}) -> Mountpoint1 = emqx_mountpoint:replvar(Mountpoint, ClientInfo), {ok, ClientInfo#{mountpoint := Mountpoint1}}. -process_connect(#channel{ctx = Ctx, - session = Session, - conninfo = ConnInfo, - clientinfo = ClientInfo} = Channel, - Msg, Result, Iter) -> +process_connect( + #channel{ + ctx = Ctx, + session = Session, + conninfo = ConnInfo, + clientinfo = ClientInfo + } = Channel, + Msg, + Result, + Iter +) -> %% inherit the old session - SessFun = fun(_,_) -> Session end, - case emqx_gateway_ctx:open_session( - Ctx, - true, - ClientInfo, - ConnInfo, - SessFun, - emqx_coap_session - ) of + SessFun = fun(_, _) -> Session end, + case + emqx_gateway_ctx:open_session( + Ctx, + true, + ClientInfo, + ConnInfo, + SessFun, + emqx_coap_session + ) + of {ok, _Sess} -> RandVal = rand:uniform(?TOKEN_MAXIMUM), Token = erlang:list_to_binary(erlang:integer_to_list(RandVal)), NResult = Result#{events => [{event, connected}]}, - iter(Iter, - reply({ok, created}, Token, Msg, NResult), - Channel#channel{token = Token}); + iter( + Iter, + reply({ok, created}, Token, Msg, NResult), + Channel#channel{token = Token} + ); {error, Reason} -> - ?SLOG(error, #{ msg => "failed_open_session" - , clientid => maps:get(clientid, ClientInfo) - , reason => Reason - }), + ?SLOG(error, #{ + msg => "failed_open_session", + clientid => maps:get(clientid, ClientInfo), + reason => Reason + }), iter(Iter, reply({error, bad_request}, Msg, Result), Channel) end. @@ -505,11 +571,14 @@ metrics_inc(Name, Ctx) -> %%-------------------------------------------------------------------- %% Ensure connected -ensure_connected(Channel = #channel{ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> - NConnInfo = ConnInfo#{ connected_at => erlang:system_time(millisecond) - }, +ensure_connected( + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> + NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)}, _ = run_hooks(Ctx, 'client.connack', [NConnInfo, connection_accepted, []]), ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), Channel#channel{conninfo = NConnInfo, conn_state = connected}. @@ -517,10 +586,14 @@ ensure_connected(Channel = #channel{ctx = Ctx, %%-------------------------------------------------------------------- %% Ensure disconnected -ensure_disconnected(Reason, Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_disconnected( + Reason, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, ok = run_hooks(Ctx, 'client.disconnected', [ClientInfo, Reason, NConnInfo]), Channel#channel{conninfo = NConnInfo, conn_state = disconnected}. @@ -540,18 +613,32 @@ call_session(Fun, Msg, #channel{session = Session} = Channel) -> handle_result(Result, Channel). handle_result(Result, Channel) -> - iter([ session, fun process_session/4 - , proto, fun process_protocol/4 - , reply, fun process_reply/4 - , out, fun process_out/4 - , fun process_nothing/3 - ], - Result, - Channel). + iter( + [ + session, + fun process_session/4, + proto, + fun process_protocol/4, + reply, + fun process_reply/4, + out, + fun process_out/4, + fun process_nothing/3 + ], + Result, + Channel + ). -call_handler(request, Msg, Result, - #channel{ctx = Ctx, - clientinfo = ClientInfo} = Channel, Iter) -> +call_handler( + request, + Msg, + Result, + #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } = Channel, + Iter +) -> HandlerResult = case emqx_coap_message:get_option(uri_path, Msg) of [<<"ps">> | RestPath] -> @@ -561,15 +648,20 @@ call_handler(request, Msg, Result, _ -> reply({error, bad_request}, Msg) end, - iter([ connection, fun process_connection/4 - , subscribe, fun process_subscribe/4 | Iter], - maps:merge(Result, HandlerResult), - Channel); - + iter( + [ + connection, + fun process_connection/4, + subscribe, + fun process_subscribe/4 + | Iter + ], + maps:merge(Result, HandlerResult), + Channel + ); call_handler(response, {{send_request, From}, Response}, Result, Channel, Iter) -> gen_server:reply(From, Response), iter(Iter, Result, Channel); - call_handler(_, _, Result, Channel, Iter) -> iter(Iter, Result, Channel). @@ -582,12 +674,13 @@ process_protocol({Type, Msg}, Result, Channel, Iter) -> %% leaf node process_out(Outs, Result, Channel, _) -> Outs2 = lists:reverse(Outs), - Outs3 = case maps:get(reply, Result, undefined) of - undefined -> - Outs2; - Reply -> - [Reply | Outs2] - end, + Outs3 = + case maps:get(reply, Result, undefined) of + undefined -> + Outs2; + Reply -> + [Reply | Outs2] + end, Events = maps:get(events, Result, []), {ok, [{outgoing, Outs3}] ++ Events, Channel}. @@ -595,32 +688,48 @@ process_out(Outs, Result, Channel, _) -> process_nothing(_, _, Channel) -> {ok, Channel}. -process_connection({open, Req}, Result, - Channel = #channel{conn_state = idle}, Iter) -> +process_connection( + {open, Req}, + Result, + Channel = #channel{conn_state = idle}, + Iter +) -> Queries = emqx_coap_message:get_option(uri_query, Req), - case emqx_misc:pipeline( - [ fun enrich_conninfo/2 - , fun run_conn_hooks/2 - , fun enrich_clientinfo/2 - , fun set_log_meta/2 - , fun auth_connect/2 - ], - {Queries, Req}, - Channel) of + case + emqx_misc:pipeline( + [ + fun enrich_conninfo/2, + fun run_conn_hooks/2, + fun enrich_clientinfo/2, + fun set_log_meta/2, + fun auth_connect/2 + ], + {Queries, Req}, + Channel + ) + of {ok, _Input, NChannel} -> process_connect(ensure_connected(NChannel), Req, Result, Iter); {error, ReasonCode, NChannel} -> ErrMsg = io_lib:format("Login Failed: ~ts", [ReasonCode]), Payload = iolist_to_binary(ErrMsg), - iter(Iter, - reply({error, bad_request}, Payload, Req, Result), - NChannel) + iter( + Iter, + reply({error, bad_request}, Payload, Req, Result), + NChannel + ) end; -process_connection({open, Req}, Result, - Channel = #channel{ - conn_state = ConnState, - clientinfo = #{clientid := ClientId}}, Iter) - when ConnState == connected -> +process_connection( + {open, Req}, + Result, + Channel = #channel{ + conn_state = ConnState, + clientinfo = #{clientid := ClientId} + }, + Iter +) when + ConnState == connected +-> Queries = emqx_coap_message:get_option(uri_query, Req), ErrMsg0 = case Queries of @@ -633,9 +742,11 @@ process_connection({open, Req}, Result, end, ErrMsg = io_lib:format("Bad Request: ~ts", [ErrMsg0]), Payload = iolist_to_binary(ErrMsg), - iter(Iter, - reply({error, bad_request}, Payload, Req, Result), - Channel); + iter( + Iter, + reply({error, bad_request}, Payload, Req, Result), + Channel + ); process_connection({close, Msg}, _, Channel, _) -> Reply = emqx_coap_message:piggyback({ok, deleted}, Msg), NChannel = ensure_disconnected(normal, Channel), @@ -651,5 +762,4 @@ process_reply(Reply, Result, #channel{session = Session} = Channel, _) -> Outs = maps:get(out, Result, []), Outs2 = lists:reverse(Outs), Events = maps:get(events, Result, []), - {ok, [{outgoing, [Reply | Outs2]}] ++ Events, - Channel#channel{session = Session2}}. + {ok, [{outgoing, [Reply | Outs2]}] ++ Events, Channel#channel{session = Session2}}. diff --git a/apps/emqx_gateway/src/coap/emqx_coap_frame.erl b/apps/emqx_gateway/src/coap/emqx_coap_frame.erl index cc14643da..687cb7bc8 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_frame.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_frame.erl @@ -19,14 +19,15 @@ -behaviour(emqx_gateway_frame). %% emqx_gateway_frame callbacks --export([ initial_parse_state/1 - , serialize_opts/0 - , serialize_pkt/2 - , parse/2 - , format/1 - , type/1 - , is_message/1 - ]). +-export([ + initial_parse_state/1, + serialize_opts/0, + serialize_pkt/2, + parse/2, + format/1, + type/1, + is_message/1 +]). -include("src/coap/include/emqx_coap.hrl"). -include_lib("emqx/include/types.hrl"). @@ -37,7 +38,8 @@ -define(OPTION_URI_HOST, 3). -define(OPTION_ETAG, 4). -define(OPTION_IF_NONE_MATCH, 5). --define(OPTION_OBSERVE, 6). % draft-ietf-core-observe-16 +% draft-ietf-core-observe-16 +-define(OPTION_OBSERVE, 6). -define(OPTION_URI_PORT, 7). -define(OPTION_LOCATION_PATH, 8). -define(OPTION_URI_PATH, 11). @@ -46,7 +48,8 @@ -define(OPTION_URI_QUERY, 15). -define(OPTION_ACCEPT, 17). -define(OPTION_LOCATION_QUERY, 20). --define(OPTION_BLOCK2, 23). % draft-ietf-core-block-17 +% draft-ietf-core-block-17 +-define(OPTION_BLOCK2, 23). -define(OPTION_BLOCK1, 27). -define(OPTION_PROXY_URI, 35). -define(OPTION_PROXY_SCHEME, 39). @@ -70,22 +73,25 @@ serialize_opts() -> %% empty message serialize_pkt(#coap_message{type = Type, method = undefined, id = MsgId}, _Opts) -> <>; - -serialize_pkt(#coap_message{ type = Type - , method = Method - , id = MsgId - , token = Token - , options = Options - , payload = Payload - }, - _Opts) -> +serialize_pkt( + #coap_message{ + type = Type, + method = Method, + id = MsgId, + token = Token, + options = Options, + payload = Payload + }, + _Opts +) -> TKL = byte_size(Token), {Class, Code} = method_to_class_code(Method), - Head = <>, + Head = + <>, FlatOpts = flatten_options(Options), encode_option_list(FlatOpts, 0, Head, Payload). --spec encode_type(message_type()) -> 0 .. 3. +-spec encode_type(message_type()) -> 0..3. encode_type(con) -> 0; encode_type(non) -> 1; encode_type(ack) -> 2; @@ -96,91 +102,121 @@ flatten_options(Opts) -> flatten_options([{_OptId, undefined} | T], Acc) -> flatten_options(T, Acc); - flatten_options([{OptId, OptVal} | T], Acc) -> - flatten_options(T, - case is_repeatable_option(OptId) of - false -> - [encode_option(OptId, OptVal) | Acc]; - _ -> - try_encode_repeatable(OptId, OptVal) ++ Acc - end); - + flatten_options( + T, + case is_repeatable_option(OptId) of + false -> + [encode_option(OptId, OptVal) | Acc]; + _ -> + try_encode_repeatable(OptId, OptVal) ++ Acc + end + ); flatten_options([], Acc) -> %% sort by option id for calculate the deltas lists:keysort(1, Acc). encode_option_list([{OptNum, OptVal} | OptionList], LastNum, Acc, Payload) -> NumDiff = OptNum - LastNum, - {Delta, ExtNum} = if - NumDiff >= 269 -> - {14, <<(NumDiff - 269):16>>}; - OptNum - LastNum >= 13 -> - {13, <<(NumDiff - 13)>>}; - true -> - {NumDiff, <<>>} - end, + {Delta, ExtNum} = + if + NumDiff >= 269 -> + {14, <<(NumDiff - 269):16>>}; + OptNum - LastNum >= 13 -> + {13, <<(NumDiff - 13)>>}; + true -> + {NumDiff, <<>>} + end, Binaryize = byte_size(OptVal), - {Len, ExtLen} = if - Binaryize >= 269 -> - {14, <<(Binaryize - 269):16>>}; - Binaryize >= 13 -> - {13, <<(Binaryize - 13)>>}; - true -> - {Binaryize, <<>>} - end, + {Len, ExtLen} = + if + Binaryize >= 269 -> + {14, <<(Binaryize - 269):16>>}; + Binaryize >= 13 -> + {13, <<(Binaryize - 13)>>}; + true -> + {Binaryize, <<>>} + end, Acc2 = <>, encode_option_list(OptionList, OptNum, Acc2, Payload); - encode_option_list([], _LastNum, Acc, <<>>) -> Acc; encode_option_list([], _, Acc, Payload) -> <>. try_encode_repeatable(uri_query, Val) when is_map(Val) -> - maps:fold(fun(K, V, Acc) -> - [encode_option(uri_query, <>) | Acc] - end, - [], Val); - + maps:fold( + fun(K, V, Acc) -> + [encode_option(uri_query, <>) | Acc] + end, + [], + Val + ); try_encode_repeatable(K, Val) -> - lists:foldr(fun(undefined, Acc) -> - Acc; - (E, Acc) -> - [encode_option(K, E) | Acc] - end, [], Val). + lists:foldr( + fun + (undefined, Acc) -> + Acc; + (E, Acc) -> + [encode_option(K, E) | Acc] + end, + [], + Val + ). %% RFC 7252 -encode_option(if_match, OptVal) -> {?OPTION_IF_MATCH, OptVal}; -encode_option(uri_host, OptVal) -> {?OPTION_URI_HOST, OptVal}; -encode_option(etag, OptVal) -> {?OPTION_ETAG, OptVal}; -encode_option(if_none_match, true) -> {?OPTION_IF_NONE_MATCH, <<>>}; -encode_option(uri_port, OptVal) -> {?OPTION_URI_PORT, binary:encode_unsigned(OptVal)}; -encode_option(location_path, OptVal) -> {?OPTION_LOCATION_PATH, OptVal}; -encode_option(uri_path, OptVal) -> {?OPTION_URI_PATH, OptVal}; +encode_option(if_match, OptVal) -> + {?OPTION_IF_MATCH, OptVal}; +encode_option(uri_host, OptVal) -> + {?OPTION_URI_HOST, OptVal}; +encode_option(etag, OptVal) -> + {?OPTION_ETAG, OptVal}; +encode_option(if_none_match, true) -> + {?OPTION_IF_NONE_MATCH, <<>>}; +encode_option(uri_port, OptVal) -> + {?OPTION_URI_PORT, binary:encode_unsigned(OptVal)}; +encode_option(location_path, OptVal) -> + {?OPTION_LOCATION_PATH, OptVal}; +encode_option(uri_path, OptVal) -> + {?OPTION_URI_PATH, OptVal}; encode_option(content_format, OptVal) when is_integer(OptVal) -> {?OPTION_CONTENT_FORMAT, binary:encode_unsigned(OptVal)}; encode_option(content_format, OptVal) -> Num = content_format_to_code(OptVal), {?OPTION_CONTENT_FORMAT, binary:encode_unsigned(Num)}; -encode_option(max_age, OptVal) -> {?OPTION_MAX_AGE, binary:encode_unsigned(OptVal)}; -encode_option(uri_query, OptVal) -> {?OPTION_URI_QUERY, OptVal}; -encode_option('accept', OptVal) -> {?OPTION_ACCEPT, binary:encode_unsigned(OptVal)}; -encode_option(location_query, OptVal) -> {?OPTION_LOCATION_QUERY, OptVal}; -encode_option(proxy_uri, OptVal) -> {?OPTION_PROXY_URI, OptVal}; -encode_option(proxy_scheme, OptVal) -> {?OPTION_PROXY_SCHEME, OptVal}; -encode_option(size1, OptVal) -> {?OPTION_SIZE1, binary:encode_unsigned(OptVal)}; -encode_option(observe, OptVal) -> {?OPTION_OBSERVE, binary:encode_unsigned(OptVal)}; -encode_option(block2, OptVal) -> {?OPTION_BLOCK2, encode_block(OptVal)}; -encode_option(block1, OptVal) -> {?OPTION_BLOCK1, encode_block(OptVal)}; +encode_option(max_age, OptVal) -> + {?OPTION_MAX_AGE, binary:encode_unsigned(OptVal)}; +encode_option(uri_query, OptVal) -> + {?OPTION_URI_QUERY, OptVal}; +encode_option('accept', OptVal) -> + {?OPTION_ACCEPT, binary:encode_unsigned(OptVal)}; +encode_option(location_query, OptVal) -> + {?OPTION_LOCATION_QUERY, OptVal}; +encode_option(proxy_uri, OptVal) -> + {?OPTION_PROXY_URI, OptVal}; +encode_option(proxy_scheme, OptVal) -> + {?OPTION_PROXY_SCHEME, OptVal}; +encode_option(size1, OptVal) -> + {?OPTION_SIZE1, binary:encode_unsigned(OptVal)}; +encode_option(observe, OptVal) -> + {?OPTION_OBSERVE, binary:encode_unsigned(OptVal)}; +encode_option(block2, OptVal) -> + {?OPTION_BLOCK2, encode_block(OptVal)}; +encode_option(block1, OptVal) -> + {?OPTION_BLOCK1, encode_block(OptVal)}; %% unknown opton encode_option(Option, Value) -> erlang:throw({bad_option, Option, Value}). encode_block({Num, More, Size}) -> - encode_block1(Num, - if More -> 1; true -> 0 end, - trunc(math:log2(Size))-4). + encode_block1( + Num, + if + More -> 1; + true -> 0 + end, + trunc(math:log2(Size)) - 4 + ). encode_block1(Num, M, SizEx) when Num < 16 -> <>; @@ -192,14 +228,15 @@ encode_block1(Num, M, SizEx) -> -spec content_format_to_code(binary()) -> non_neg_integer(). content_format_to_code(<<"text/plain">>) -> 0; content_format_to_code(<<"application/link-format">>) -> 40; -content_format_to_code(<<"application/xml">>) ->41; +content_format_to_code(<<"application/xml">>) -> 41; content_format_to_code(<<"application/octet-stream">>) -> 42; content_format_to_code(<<"application/exi">>) -> 47; content_format_to_code(<<"application/json">>) -> 50; content_format_to_code(<<"application/cbor">>) -> 60; content_format_to_code(<<"application/vnd.oma.lwm2m+tlv">>) -> 11542; content_format_to_code(<<"application/vnd.oma.lwm2m+json">>) -> 11543; -content_format_to_code(_) -> 42. %% use octet-stream as default +%% use octet-stream as default +content_format_to_code(_) -> 42. method_to_class_code(get) -> {0, 01}; method_to_class_code(post) -> {0, 02}; @@ -229,54 +266,58 @@ method_to_class_code({error, bad_gateway}) -> {5, 02}; method_to_class_code({error, service_unavailable}) -> {5, 03}; method_to_class_code({error, gateway_timeout}) -> {5, 04}; method_to_class_code({error, proxying_not_supported}) -> {5, 05}; -method_to_class_code(Method) -> - erlang:throw({bad_method, Method}). +method_to_class_code(Method) -> erlang:throw({bad_method, Method}). %%-------------------------------------------------------------------- %% parse %%-------------------------------------------------------------------- --spec parse(binary(), emqx_gateway_frame:parse_state()) - -> emqx_gateway_frame:parse_result(). +-spec parse(binary(), emqx_gateway_frame:parse_state()) -> + emqx_gateway_frame:parse_result(). parse(<>, ParseState) -> {ok, - #coap_message{ type = decode_type(Type) - , id = MsgId}, - <<>>, - ParseState}; - -parse(<>, - ParseState) -> + #coap_message{ + type = decode_type(Type), + id = MsgId + }, + <<>>, ParseState}; +parse( + <>, + ParseState +) -> {Options, Payload} = decode_option_list(Tail), - Options2 = maps:fold(fun(K, V, Acc) -> - Acc#{K => get_option_val(K, V)} - end, - #{}, - Options), + Options2 = maps:fold( + fun(K, V, Acc) -> + Acc#{K => get_option_val(K, V)} + end, + #{}, + Options + ), {ok, - #coap_message{ type = decode_type(Type) - , method = class_code_to_method({Class, Code}) - , id = MsgId - , token = Token - , options = Options2 - , payload = Payload - }, - <<>>, - ParseState}. + #coap_message{ + type = decode_type(Type), + method = class_code_to_method({Class, Code}), + id = MsgId, + token = Token, + options = Options2, + payload = Payload + }, + <<>>, ParseState}. get_option_val(uri_query, V) -> - KVList = lists:foldl(fun(E, Acc) -> - case re:split(E, "=") of - [Key, Val] -> - [{Key, Val} | Acc]; - _ -> - Acc - end - end, - [], - V), + KVList = lists:foldl( + fun(E, Acc) -> + case re:split(E, "=") of + [Key, Val] -> + [{Key, Val} | Acc]; + _ -> + Acc + end + end, + [], + V + ), maps:from_list(KVList); - get_option_val(K, V) -> case is_repeatable_option(K) of true -> @@ -285,8 +326,8 @@ get_option_val(K, V) -> V end. --spec decode_type(X) -> message_type() - when X :: 0 .. 3. +-spec decode_type(X) -> message_type() when + X :: 0..3. decode_type(0) -> con; decode_type(1) -> non; decode_type(2) -> ack; @@ -298,10 +339,8 @@ decode_option_list(Bin) -> decode_option_list(<<>>, _OptNum, OptMap) -> {OptMap, <<>>}; - decode_option_list(<<16#FF, Payload/binary>>, _OptNum, OptMap) -> {OptMap, Payload}; - decode_option_list(<>, OptNum, OptMap) -> case Delta of Any when Any < 13 -> @@ -349,30 +388,48 @@ append_option(OptNum, RawOptVal, OptMap) -> end. %% RFC 7252 -decode_option(?OPTION_IF_MATCH, OptVal) -> {if_match, OptVal}; -decode_option(?OPTION_URI_HOST, OptVal) -> {uri_host, OptVal}; -decode_option(?OPTION_ETAG, OptVal) -> {etag, OptVal}; -decode_option(?OPTION_IF_NONE_MATCH, <<>>) -> {if_none_match, true}; -decode_option(?OPTION_URI_PORT, OptVal) -> {uri_port, binary:decode_unsigned(OptVal)}; -decode_option(?OPTION_LOCATION_PATH, OptVal) -> {location_path, OptVal}; -decode_option(?OPTION_URI_PATH, OptVal) -> {uri_path, OptVal}; +decode_option(?OPTION_IF_MATCH, OptVal) -> + {if_match, OptVal}; +decode_option(?OPTION_URI_HOST, OptVal) -> + {uri_host, OptVal}; +decode_option(?OPTION_ETAG, OptVal) -> + {etag, OptVal}; +decode_option(?OPTION_IF_NONE_MATCH, <<>>) -> + {if_none_match, true}; +decode_option(?OPTION_URI_PORT, OptVal) -> + {uri_port, binary:decode_unsigned(OptVal)}; +decode_option(?OPTION_LOCATION_PATH, OptVal) -> + {location_path, OptVal}; +decode_option(?OPTION_URI_PATH, OptVal) -> + {uri_path, OptVal}; decode_option(?OPTION_CONTENT_FORMAT, OptVal) -> Num = binary:decode_unsigned(OptVal), {content_format, content_code_to_format(Num)}; -decode_option(?OPTION_MAX_AGE, OptVal) -> {max_age, binary:decode_unsigned(OptVal)}; -decode_option(?OPTION_URI_QUERY, OptVal) -> {uri_query, OptVal}; -decode_option(?OPTION_ACCEPT, OptVal) -> {'accept', binary:decode_unsigned(OptVal)}; -decode_option(?OPTION_LOCATION_QUERY, OptVal) -> {location_query, OptVal}; -decode_option(?OPTION_PROXY_URI, OptVal) -> {proxy_uri, OptVal}; -decode_option(?OPTION_PROXY_SCHEME, OptVal) -> {proxy_scheme, OptVal}; -decode_option(?OPTION_SIZE1, OptVal) -> {size1, binary:decode_unsigned(OptVal)}; +decode_option(?OPTION_MAX_AGE, OptVal) -> + {max_age, binary:decode_unsigned(OptVal)}; +decode_option(?OPTION_URI_QUERY, OptVal) -> + {uri_query, OptVal}; +decode_option(?OPTION_ACCEPT, OptVal) -> + {'accept', binary:decode_unsigned(OptVal)}; +decode_option(?OPTION_LOCATION_QUERY, OptVal) -> + {location_query, OptVal}; +decode_option(?OPTION_PROXY_URI, OptVal) -> + {proxy_uri, OptVal}; +decode_option(?OPTION_PROXY_SCHEME, OptVal) -> + {proxy_scheme, OptVal}; +decode_option(?OPTION_SIZE1, OptVal) -> + {size1, binary:decode_unsigned(OptVal)}; %% draft-ietf-core-observe-16 -decode_option(?OPTION_OBSERVE, OptVal) -> {observe, binary:decode_unsigned(OptVal)}; +decode_option(?OPTION_OBSERVE, OptVal) -> + {observe, binary:decode_unsigned(OptVal)}; %% draft-ietf-core-block-17 -decode_option(?OPTION_BLOCK2, OptVal) -> {block2, decode_block(OptVal)}; -decode_option(?OPTION_BLOCK1, OptVal) -> {block1, decode_block(OptVal)}; +decode_option(?OPTION_BLOCK2, OptVal) -> + {block2, decode_block(OptVal)}; +decode_option(?OPTION_BLOCK1, OptVal) -> + {block1, decode_block(OptVal)}; %% unknown option -decode_option(OptNum, OptVal) -> {OptNum, OptVal}. +decode_option(OptNum, OptVal) -> + {OptNum, OptVal}. decode_block(<>) -> decode_block1(Num, M, SizEx); decode_block(<>) -> decode_block1(Num, M, SizEx); @@ -391,7 +448,8 @@ content_code_to_format(50) -> <<"application/json">>; content_code_to_format(60) -> <<"application/cbor">>; content_code_to_format(11542) -> <<"application/vnd.oma.lwm2m+tlv">>; content_code_to_format(11543) -> <<"application/vnd.oma.lwm2m+json">>; -content_code_to_format(_) -> <<"application/octet-stream">>. %% use octet as default +%% use octet as default +content_code_to_format(_) -> <<"application/octet-stream">>. %% RFC 7252 %% atom indicate a request @@ -399,7 +457,6 @@ class_code_to_method({0, 01}) -> get; class_code_to_method({0, 02}) -> post; class_code_to_method({0, 03}) -> put; class_code_to_method({0, 04}) -> delete; - %% success is a tuple {ok, ...} class_code_to_method({2, 01}) -> {ok, created}; class_code_to_method({2, 02}) -> {ok, deleted}; @@ -407,8 +464,8 @@ class_code_to_method({2, 03}) -> {ok, valid}; class_code_to_method({2, 04}) -> {ok, changed}; class_code_to_method({2, 05}) -> {ok, content}; class_code_to_method({2, 07}) -> {ok, nocontent}; -class_code_to_method({2, 31}) -> {ok, continue}; % block - +% block +class_code_to_method({2, 31}) -> {ok, continue}; %% error is a tuple {error, ...} class_code_to_method({4, 00}) -> {error, bad_request}; class_code_to_method({4, 01}) -> {error, unauthorized}; @@ -417,7 +474,8 @@ class_code_to_method({4, 03}) -> {error, forbidden}; class_code_to_method({4, 04}) -> {error, not_found}; class_code_to_method({4, 05}) -> {error, method_not_allowed}; class_code_to_method({4, 06}) -> {error, not_acceptable}; -class_code_to_method({4, 08}) -> {error, request_entity_incomplete}; % block +% block +class_code_to_method({4, 08}) -> {error, request_entity_incomplete}; class_code_to_method({4, 12}) -> {error, precondition_failed}; class_code_to_method({4, 13}) -> {error, request_entity_too_large}; class_code_to_method({4, 15}) -> {error, unsupported_content_format}; diff --git a/apps/emqx_gateway/src/coap/emqx_coap_impl.erl b/apps/emqx_gateway/src/coap/emqx_coap_impl.erl index 9a4c902dc..d44dce142 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_impl.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_impl.erl @@ -21,29 +21,33 @@ -include_lib("emqx/include/logger.hrl"). -include_lib("emqx_gateway/include/emqx_gateway.hrl"). --import(emqx_gateway_utils, - [ normalize_config/1 - , start_listeners/4 - , stop_listeners/2 - ]). +-import( + emqx_gateway_utils, + [ + normalize_config/1, + start_listeners/4, + stop_listeners/2 + ] +). %% APIs --export([ reg/0 - , unreg/0 - ]). +-export([ + reg/0, + unreg/0 +]). --export([ on_gateway_load/2 - , on_gateway_update/3 - , on_gateway_unload/2 - ]). +-export([ + on_gateway_load/2, + on_gateway_update/3, + on_gateway_unload/2 +]). %%-------------------------------------------------------------------- %% APIs %%-------------------------------------------------------------------- reg() -> - RegistryOptions = [ {cbkmod, ?MODULE} - ], + RegistryOptions = [{cbkmod, ?MODULE}], emqx_gateway_registry:reg(coap, RegistryOptions). unreg() -> @@ -53,22 +57,33 @@ unreg() -> %% emqx_gateway_registry callbacks %%-------------------------------------------------------------------- -on_gateway_load(_Gateway = #{name := GwName, - config := Config - }, Ctx) -> +on_gateway_load( + _Gateway = #{ + name := GwName, + config := Config + }, + Ctx +) -> Listeners = normalize_config(Config), - ModCfg = #{frame_mod => emqx_coap_frame, - chann_mod => emqx_coap_channel - }, - case start_listeners( - Listeners, GwName, Ctx, ModCfg) of + ModCfg = #{ + frame_mod => emqx_coap_frame, + chann_mod => emqx_coap_channel + }, + case + start_listeners( + Listeners, GwName, Ctx, ModCfg + ) + of {ok, ListenerPids} -> {ok, ListenerPids, #{ctx => Ctx}}; {error, {Reason, Listener}} -> - throw({badconf, #{ key => listeners - , vallue => Listener - , reason => Reason - }}) + throw( + {badconf, #{ + key => listeners, + vallue => Listener, + reason => Reason + }} + ) end. on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> @@ -79,15 +94,21 @@ on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> on_gateway_unload(Gateway, GwState), on_gateway_load(Gateway#{config => Config}, Ctx) catch - Class : Reason : Stk -> - logger:error("Failed to update ~ts; " - "reason: {~0p, ~0p} stacktrace: ~0p", - [GwName, Class, Reason, Stk]), + Class:Reason:Stk -> + logger:error( + "Failed to update ~ts; " + "reason: {~0p, ~0p} stacktrace: ~0p", + [GwName, Class, Reason, Stk] + ), {error, Reason} end. -on_gateway_unload(_Gateway = #{ name := GwName, - config := Config - }, _GwState) -> +on_gateway_unload( + _Gateway = #{ + name := GwName, + config := Config + }, + _GwState +) -> Listeners = normalize_config(Config), stop_listeners(GwName, Listeners). diff --git a/apps/emqx_gateway/src/coap/emqx_coap_medium.erl b/apps/emqx_gateway/src/coap/emqx_coap_medium.erl index d11c7477d..fc0541a33 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_medium.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_medium.erl @@ -23,10 +23,15 @@ -include("src/coap/include/emqx_coap.hrl"). %% API --export([ empty/0, reset/1, reset/2 - , out/1, out/2, proto_out/1 - , proto_out/2, iter/3, iter/4 - , reply/2, reply/3, reply/4]). +-export([ + empty/0, + reset/1, reset/2, + out/1, out/2, + proto_out/1, + proto_out/2, + iter/3, iter/4, + reply/2, reply/3, reply/4 +]). %%-type result() :: map() | empty. @@ -46,7 +51,6 @@ out(Msg) -> out(Msg, #{out := Outs} = Result) -> Result#{out := [Msg | Outs]}; - out(Msg, Result) -> Result#{out => [Msg]}. @@ -58,13 +62,11 @@ proto_out(Proto, Result) -> reply(Method, Req) when not is_record(Method, coap_message) -> reply(Method, <<>>, Req); - reply(Reply, Result) -> Result#{reply => Reply}. reply(Method, Req, Result) when is_record(Req, coap_message) -> reply(Method, <<>>, Req, Result); - reply(Method, Payload, Req) -> reply(Method, Payload, Req, #{}). @@ -78,16 +80,15 @@ iter([Key, Fun | T], Input, State) -> iter(T, Input, State); Val -> Fun(Val, maps:remove(Key, Input), State, T) - %% reserved - %% if is_function(Fun) -> - %% Fun(Val, maps:remove(Key, Input), State, T); - %% true -> - %% %% switch to sub branch - %% [FunH | FunT] = Fun, - %% FunH(Val, maps:remove(Key, Input), State, FunT) - %% end + %% reserved + %% if is_function(Fun) -> + %% Fun(Val, maps:remove(Key, Input), State, T); + %% true -> + %% %% switch to sub branch + %% [FunH | FunT] = Fun, + %% FunH(Val, maps:remove(Key, Input), State, FunT) + %% end end; - %% terminal node iter([Fun], Input, State) -> Fun(undefined, Input, State). @@ -100,7 +101,6 @@ iter([Key, Fun | T], Input, Arg, State) -> Val -> Fun(Val, maps:remove(Key, Input), Arg, State, T) end; - iter([Fun], Input, Arg, State) -> Fun(undefined, Input, Arg, State). diff --git a/apps/emqx_gateway/src/coap/emqx_coap_message.erl b/apps/emqx_gateway/src/coap/emqx_coap_message.erl index 52d822ffc..d7a229532 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_message.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_message.erl @@ -24,15 +24,24 @@ %% convenience functions for message construction -module(emqx_coap_message). --export([ request/2, request/3, request/4 - , ack/1, response/1, response/2 - , reset/1, piggyback/2, piggyback/3 - , response/3]). +-export([ + request/2, request/3, request/4, + ack/1, + response/1, response/2, + reset/1, + piggyback/2, piggyback/3, + response/3 +]). -export([is_request/1]). --export([ set/3, set_payload/2, get_option/2 - , get_option/3, set_payload_block/3, set_payload_block/4]). +-export([ + set/3, + set_payload/2, + get_option/2, + get_option/3, + set_payload_block/3, set_payload_block/4 +]). -include("src/coap/include/emqx_coap.hrl"). @@ -43,10 +52,12 @@ request(Type, Method, Payload) -> request(Type, Method, Payload, []). request(Type, Method, Payload, Options) when is_binary(Payload) -> - #coap_message{type = Type, - method = Method, - payload = Payload, - options = to_options(Options)}. + #coap_message{ + type = Type, + method = Method, + payload = Payload, + options = to_options(Options) + }. ack(#coap_message{id = Id}) -> #coap_message{type = ack, id = Id}. @@ -61,14 +72,18 @@ response(Request) -> response(Method, Request) -> response(Method, <<>>, Request). -response(Method, Payload, #coap_message{type = Type, - id = Id, - token = Token}) -> - #coap_message{type = Type, - id = Id, - token = Token, - method = Method, - payload = Payload}. +response(Method, Payload, #coap_message{ + type = Type, + id = Id, + token = Token +}) -> + #coap_message{ + type = Type, + id = Id, + token = Token, + method = Method, + payload = Payload + }. %% make a response which maybe is a piggyback ack piggyback(Method, Request) -> @@ -84,8 +99,8 @@ piggyback(Method, Payload, Request) -> end. %% omit option for its default value -set(max_age, ?DEFAULT_MAX_AGE, Msg) -> Msg; - +set(max_age, ?DEFAULT_MAX_AGE, Msg) -> + Msg; %% set non-default value set(Option, Value, Msg = #coap_message{options = Options}) -> Msg#coap_message{options = Options#{Option => Value}}. @@ -98,13 +113,11 @@ get_option(Option, #coap_message{options = Options}, Def) -> set_payload(Payload, Msg) when is_binary(Payload) -> Msg#coap_message{payload = Payload}; - set_payload(Payload, Msg) when is_list(Payload) -> Msg#coap_message{payload = list_to_binary(Payload)}. set_payload_block(Content, Block, Msg = #coap_message{method = Method}) when is_atom(Method) -> set_payload_block(Content, block1, Block, Msg); - set_payload_block(Content, Block, Msg = #coap_message{}) -> set_payload_block(Content, block2, Block, Msg). @@ -114,16 +127,21 @@ set_payload_block(Content, BlockId, {Num, _, Size}, Msg) -> OffsetEnd = OffsetBegin + Size, case ContentSize > OffsetEnd of true -> - set(BlockId, {Num, true, Size}, - set_payload(binary:part(Content, OffsetBegin, Size), Msg)); + set( + BlockId, + {Num, true, Size}, + set_payload(binary:part(Content, OffsetBegin, Size), Msg) + ); _ -> - set(BlockId, {Num, false, Size}, - set_payload(binary:part(Content, OffsetBegin, ContentSize - OffsetBegin), Msg)) + set( + BlockId, + {Num, false, Size}, + set_payload(binary:part(Content, OffsetBegin, ContentSize - OffsetBegin), Msg) + ) end. is_request(#coap_message{method = Method}) when is_atom(Method) -> Method =/= undefined; - is_request(_) -> false. diff --git a/apps/emqx_gateway/src/coap/emqx_coap_observe_res.erl b/apps/emqx_gateway/src/coap/emqx_coap_observe_res.erl index ec7e0f7ba..c087dce1c 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_observe_res.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_observe_res.erl @@ -17,18 +17,25 @@ -module(emqx_coap_observe_res). %% API --export([ new_manager/0, insert/3, remove/2 - , res_changed/2, foreach/2, subscriptions/1]). +-export([ + new_manager/0, + insert/3, + remove/2, + res_changed/2, + foreach/2, + subscriptions/1 +]). -export_type([manager/0]). -define(MAX_SEQ_ID, 16777215). -type token() :: binary(). --type seq_id() :: 0 .. ?MAX_SEQ_ID. +-type seq_id() :: 0..?MAX_SEQ_ID. --type res() :: #{ token := token() - , seq_id := seq_id() - }. +-type res() :: #{ + token := token(), + seq_id := seq_id() +}. -type manager() :: #{emqx_types:topic() => res()}. @@ -41,12 +48,13 @@ new_manager() -> -spec insert(emqx_types:topic(), token(), manager()) -> {seq_id(), manager()}. insert(Topic, Token, Manager) -> - Res = case maps:get(Topic, Manager, undefined) of - undefined -> - new_res(Token); - Any -> - Any - end, + Res = + case maps:get(Topic, Manager, undefined) of + undefined -> + new_res(Token); + Any -> + Any + end, {maps:get(seq_id, Res), Manager#{Topic => Res}}. -spec remove(emqx_types:topic(), manager()) -> manager(). @@ -59,17 +67,21 @@ res_changed(Topic, Manager) -> undefined -> undefined; Res -> - #{token := Token, - seq_id := SeqId} = Res2 = res_changed(Res), + #{ + token := Token, + seq_id := SeqId + } = Res2 = res_changed(Res), {Token, SeqId, Manager#{Topic := Res2}} end. foreach(F, Manager) -> - maps:fold(fun(K, V, _) -> - F(K, V) - end, - ok, - Manager), + maps:fold( + fun(K, V, _) -> + F(K, V) + end, + ok, + Manager + ), ok. -spec subscriptions(manager()) -> [emqx_types:topic()]. @@ -81,8 +93,10 @@ subscriptions(Manager) -> %%-------------------------------------------------------------------- -spec new_res(token()) -> res(). new_res(Token) -> - #{token => Token, - seq_id => 0}. + #{ + token => Token, + seq_id => 0 + }. -spec res_changed(res()) -> res(). res_changed(#{seq_id := SeqId} = Res) -> diff --git a/apps/emqx_gateway/src/coap/emqx_coap_session.erl b/apps/emqx_gateway/src/coap/emqx_coap_session.erl index 108b4c55b..cc451fce4 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_session.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_session.erl @@ -21,50 +21,57 @@ -include("src/coap/include/emqx_coap.hrl"). %% API --export([ new/0 - , process_subscribe/4 - ]). +-export([ + new/0, + process_subscribe/4 +]). --export([ info/1 - , info/2 - , stats/1 - ]). +-export([ + info/1, + info/2, + stats/1 +]). --export([ handle_request/2 - , handle_response/2 - , handle_out/2 - , set_reply/2 - , deliver/3 - , timeout/2]). +-export([ + handle_request/2, + handle_response/2, + handle_out/2, + set_reply/2, + deliver/3, + timeout/2 +]). -export_type([session/0]). --record(session, { transport_manager :: emqx_coap_tm:manager() - , observe_manager :: emqx_coap_observe_res:manager() - , created_at :: pos_integer() - }). +-record(session, { + transport_manager :: emqx_coap_tm:manager(), + observe_manager :: emqx_coap_observe_res:manager(), + created_at :: pos_integer() +}). -type session() :: #session{}. %% steal from emqx_session --define(INFO_KEYS, [subscriptions, - upgrade_qos, - retry_interval, - await_rel_timeout, - created_at - ]). +-define(INFO_KEYS, [ + subscriptions, + upgrade_qos, + retry_interval, + await_rel_timeout, + created_at +]). --define(STATS_KEYS, [subscriptions_cnt, - subscriptions_max, - inflight_cnt, - inflight_max, - mqueue_len, - mqueue_max, - mqueue_dropped, - next_pkt_id, - awaiting_rel_cnt, - awaiting_rel_max - ]). +-define(STATS_KEYS, [ + subscriptions_cnt, + subscriptions_max, + inflight_cnt, + inflight_max, + mqueue_len, + mqueue_max, + mqueue_dropped, + next_pkt_id, + awaiting_rel_cnt, + awaiting_rel_max +]). -import(emqx_coap_medium, [iter/3]). -import(emqx_coap_channel, [metrics_inc/2]). @@ -75,16 +82,18 @@ -spec new() -> session(). new() -> _ = emqx_misc:rand_seed(), - #session{ transport_manager = emqx_coap_tm:new() - , observe_manager = emqx_coap_observe_res:new_manager() - , created_at = erlang:system_time(millisecond)}. + #session{ + transport_manager = emqx_coap_tm:new(), + observe_manager = emqx_coap_observe_res:new_manager(), + created_at = erlang:system_time(millisecond) + }. %%-------------------------------------------------------------------- %% Info, Stats %%-------------------------------------------------------------------- %% @doc Compatible with emqx_session %% do we need use inflight and mqueue in here? --spec(info(session()) -> emqx_types:infos()). +-spec info(session()) -> emqx_types:infos(). info(Session) -> maps:from_list(info(?INFO_KEYS, Session)). @@ -93,8 +102,10 @@ info(Keys, Session) when is_list(Keys) -> info(subscriptions, #session{observe_manager = OM}) -> Topics = emqx_coap_observe_res:subscriptions(OM), lists:foldl( - fun(T, Acc) -> Acc#{T => emqx_gateway_utils:default_subopts()} end, - #{}, Topics); + fun(T, Acc) -> Acc#{T => emqx_gateway_utils:default_subopts()} end, + #{}, + Topics + ); info(subscriptions_cnt, #session{observe_manager = OM}) -> erlang:length(emqx_coap_observe_res:subscriptions(OM)); info(subscriptions_max, _) -> @@ -131,16 +142,18 @@ info(created_at, #session{created_at = CreatedAt}) -> CreatedAt. %% @doc Get stats of the session. --spec(stats(session()) -> emqx_types:stats()). +-spec stats(session()) -> emqx_types:stats(). stats(Session) -> info(?STATS_KEYS, Session). %%%------------------------------------------------------------------- %%% Process Message %%%------------------------------------------------------------------- handle_request(Msg, Session) -> - call_transport_manager(?FUNCTION_NAME, - Msg, - Session). + call_transport_manager( + ?FUNCTION_NAME, + Msg, + Session + ). handle_response(Msg, Session) -> call_transport_manager(?FUNCTION_NAME, Msg, Session). @@ -152,26 +165,36 @@ set_reply(Msg, #session{transport_manager = TM} = Session) -> TM2 = emqx_coap_tm:set_reply(Msg, TM), Session#session{transport_manager = TM2}. -deliver(Delivers, Ctx, #session{observe_manager = OM, - transport_manager = TM} = Session) -> +deliver( + Delivers, + Ctx, + #session{ + observe_manager = OM, + transport_manager = TM + } = Session +) -> Fun = fun({_, Topic, Message}, {OutAcc, OMAcc, TMAcc} = Acc) -> - case emqx_coap_observe_res:res_changed(Topic, OMAcc) of - undefined -> - metrics_inc('delivery.dropped', Ctx), - metrics_inc('delivery.dropped.no_subid', Ctx), - Acc; - {Token, SeqId, OM2} -> - metrics_inc('messages.delivered', Ctx), - Msg = mqtt_to_coap(Message, Token, SeqId), - #{out := Out, tm := TM2} = emqx_coap_tm:handle_out(Msg, TMAcc), - {Out ++ OutAcc, OM2, TM2} - end - end, + case emqx_coap_observe_res:res_changed(Topic, OMAcc) of + undefined -> + metrics_inc('delivery.dropped', Ctx), + metrics_inc('delivery.dropped.no_subid', Ctx), + Acc; + {Token, SeqId, OM2} -> + metrics_inc('messages.delivered', Ctx), + Msg = mqtt_to_coap(Message, Token, SeqId), + #{out := Out, tm := TM2} = emqx_coap_tm:handle_out(Msg, TMAcc), + {Out ++ OutAcc, OM2, TM2} + end + end, {Outs, OM2, TM2} = lists:foldl(Fun, {[], OM, TM}, lists:reverse(Delivers)), - #{out => lists:reverse(Outs), - session => Session#session{observe_manager = OM2, - transport_manager = TM2}}. + #{ + out => lists:reverse(Outs), + session => Session#session{ + observe_manager = OM2, + transport_manager = TM2 + } + }. timeout(Timer, Session) -> call_transport_manager(?FUNCTION_NAME, Timer, Session). @@ -179,13 +202,17 @@ timeout(Timer, Session) -> %%%------------------------------------------------------------------- %%% Internal functions %%%------------------------------------------------------------------- -call_transport_manager(Fun, - Msg, - #session{transport_manager = TM} = Session) -> +call_transport_manager( + Fun, + Msg, + #session{transport_manager = TM} = Session +) -> Result = emqx_coap_tm:Fun(Msg, TM), - iter([tm, fun process_tm/4, fun process_session/3], - Result, - Session). + iter( + [tm, fun process_tm/4, fun process_session/3], + Result, + Session + ). process_tm(TM, Result, Session, Cursor) -> iter(Cursor, Result, Session#session{transport_manager = TM}). @@ -193,8 +220,12 @@ process_tm(TM, Result, Session, Cursor) -> process_session(_, Result, Session) -> Result#{session => Session}. -process_subscribe(Sub, Msg, Result, - #session{observe_manager = OM} = Session) -> +process_subscribe( + Sub, + Msg, + Result, + #session{observe_manager = OM} = Session +) -> case Sub of undefined -> Result; @@ -202,22 +233,28 @@ process_subscribe(Sub, Msg, Result, {SeqId, OM2} = emqx_coap_observe_res:insert(Topic, Token, OM), Replay = emqx_coap_message:piggyback({ok, content}, Msg), Replay2 = Replay#coap_message{options = #{observe => SeqId}}, - Result#{reply => Replay2, - session => Session#session{observe_manager = OM2}}; + Result#{ + reply => Replay2, + session => Session#session{observe_manager = OM2} + }; Topic -> OM2 = emqx_coap_observe_res:remove(Topic, OM), Replay = emqx_coap_message:piggyback({ok, nocontent}, Msg), - Result#{reply => Replay, - session => Session#session{observe_manager = OM2}} + Result#{ + reply => Replay, + session => Session#session{observe_manager = OM2} + } end. mqtt_to_coap(MQTT, Token, SeqId) -> #message{payload = Payload} = MQTT, - #coap_message{type = get_notify_type(MQTT), - method = {ok, content}, - token = Token, - payload = Payload, - options = #{observe => SeqId}}. + #coap_message{ + type = get_notify_type(MQTT), + method = {ok, content}, + token = Token, + payload = Payload, + options = #{observe => SeqId} + }. get_notify_type(#message{qos = Qos}) -> case emqx_conf:get([gateway, coap, notify_qos], non) of diff --git a/apps/emqx_gateway/src/coap/emqx_coap_tm.erl b/apps/emqx_gateway/src/coap/emqx_coap_tm.erl index 2b28f2723..9e7c400c3 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_tm.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_tm.erl @@ -17,13 +17,15 @@ %% the transport state machine manager -module(emqx_coap_tm). --export([ new/0 - , handle_request/2 - , handle_response/2 - , handle_out/2 - , handle_out/3 - , set_reply/2 - , timeout/2]). +-export([ + new/0, + handle_request/2, + handle_response/2, + handle_out/2, + handle_out/3, + set_reply/2, + timeout/2 +]). -export_type([manager/0, event_result/1]). @@ -32,41 +34,47 @@ -type direction() :: in | out. --record(state_machine, { seq_id :: seq_id() - , id :: state_machine_key() - , token :: token() | undefined - , observe :: 0 | 1 | undefined | observed - , state :: atom() - , timers :: maps:map() - , transport :: emqx_coap_transport:transport()}). +-record(state_machine, { + seq_id :: seq_id(), + id :: state_machine_key(), + token :: token() | undefined, + observe :: 0 | 1 | undefined | observed, + state :: atom(), + timers :: maps:map(), + transport :: emqx_coap_transport:transport() +}). -type state_machine() :: #state_machine{}. --type message_id() :: 0 .. ?MAX_MESSAGE_ID. +-type message_id() :: 0..?MAX_MESSAGE_ID. -type token_key() :: {token, token()}. -type state_machine_key() :: {direction(), message_id()}. -type seq_id() :: pos_integer(). -type manager_key() :: token_key() | state_machine_key() | seq_id(). --type manager() :: #{ seq_id => seq_id() - , next_msg_id => coap_message_id() - , token_key() => seq_id() - , state_machine_key() => seq_id() - , seq_id() => state_machine() - }. +-type manager() :: #{ + seq_id => seq_id(), + next_msg_id => coap_message_id(), + token_key() => seq_id(), + state_machine_key() => seq_id(), + seq_id() => state_machine() +}. --type ttimeout() :: {state_timeout, pos_integer(), any()} - | {stop_timeout, pos_integer()}. +-type ttimeout() :: + {state_timeout, pos_integer(), any()} + | {stop_timeout, pos_integer()}. -type topic() :: binary(). -type token() :: binary(). -type sub_register() :: {topic(), token()} | topic(). -type event_result(State) :: - #{next => State, - outgoing => coap_message(), - timeouts => list(ttimeout()), - has_sub => undefined | sub_register(), - transport => emqx_coap_transport:transport()}. + #{ + next => State, + outgoing => coap_message(), + timeouts => list(ttimeout()), + has_sub => undefined | sub_register(), + transport => emqx_coap_transport:transport() + }. -define(TOKEN_ID(T), {token, T}). @@ -78,9 +86,10 @@ -spec new() -> manager(). new() -> - #{ seq_id => 1 - , next_msg_id => rand:uniform(?MAX_MESSAGE_ID) - }. + #{ + seq_id => 1, + next_msg_id => rand:uniform(?MAX_MESSAGE_ID) + }. handle_request(#coap_message{id = MsgId} = Msg, TM) -> Id = {in, MsgId}, @@ -111,7 +120,6 @@ handle_response(#coap_message{type = Type, id = MsgId, token = Token} = Msg, TM) %% send to a client, msg can be request/piggyback/separate/notify handle_out({Ctx, Msg}, TM) -> handle_out(Msg, Ctx, TM); - handle_out(Msg, TM) -> handle_out(Msg, undefined, TM). @@ -135,8 +143,10 @@ set_reply(#coap_message{id = MsgId} = Msg, TM) -> case find_machine(Id, TM) of undefined -> TM; - #state_machine{transport = Transport, - seq_id = SeqId} = Machine -> + #state_machine{ + transport = Transport, + seq_id = SeqId + } = Machine -> Transport2 = emqx_coap_transport:set_cache(Msg, Transport), Machine2 = Machine#state_machine{transport = Transport2}, TM#{SeqId => Machine2} @@ -161,35 +171,52 @@ timeout({SeqId, Type, Msg}, TM) -> %%-------------------------------------------------------------------- process_event(stop_timeout, _, TM, Machine) -> process_manager(stop, #{}, Machine, TM); - -process_event(Event, Msg, TM, #state_machine{state = State, - transport = Transport} = Machine) -> +process_event( + Event, + Msg, + TM, + #state_machine{ + state = State, + transport = Transport + } = Machine +) -> Result = emqx_coap_transport:State(Event, Msg, Transport), - iter([ proto, fun process_observe_response/5 - , next, fun process_state_change/5 - , transport, fun process_transport_change/5 - , timeouts, fun process_timeouts/5 - , fun process_manager/4], - Result, - Machine, - TM). + iter( + [ + proto, + fun process_observe_response/5, + next, + fun process_state_change/5, + transport, + fun process_transport_change/5, + timeouts, + fun process_timeouts/5, + fun process_manager/4 + ], + Result, + Machine, + TM + ). -process_observe_response({response, {_, Msg}} = Response, - Result, - #state_machine{observe = 0} = Machine, - TM, - Iter) -> +process_observe_response( + {response, {_, Msg}} = Response, + Result, + #state_machine{observe = 0} = Machine, + TM, + Iter +) -> Result2 = proto_out(Response, Result), case Msg#coap_message.method of {ok, _} -> - iter(Iter, - Result2#{next => observe}, - Machine#state_machine{observe = observed}, - TM); + iter( + Iter, + Result2#{next => observe}, + Machine#state_machine{observe = observed}, + TM + ); _ -> iter(Iter, Result2, Machine, TM) end; - process_observe_response(Proto, Result, Machine, TM, Iter) -> iter(Iter, proto_out(Proto, Result), Machine, TM). @@ -198,10 +225,12 @@ process_state_change(Next, Result, Machine, TM, Iter) -> stop -> process_manager(stop, Result, Machine, TM); _ -> - iter(Iter, - Result, - cancel_state_timer(Machine#state_machine{state = Next}), - TM) + iter( + Iter, + Result, + cancel_state_timer(Machine#state_machine{state = Next}), + TM + ) end. process_transport_change(Transport, Result, Machine, TM, Iter) -> @@ -209,22 +238,30 @@ process_transport_change(Transport, Result, Machine, TM, Iter) -> process_timeouts([], Result, Machine, TM, Iter) -> iter(Iter, Result, Machine, TM); - -process_timeouts(Timeouts, Result, - #state_machine{seq_id = SeqId, - timers = Timers} = Machine, TM, Iter) -> - NewTimers = lists:foldl(fun({state_timeout, _, _} = Timer, Acc) -> - process_timer(SeqId, Timer, Acc); - ({stop_timeout, I}, Acc) -> - process_timer(SeqId, {stop_timeout, I, stop}, Acc) - end, - Timers, - Timeouts), +process_timeouts( + Timeouts, + Result, + #state_machine{ + seq_id = SeqId, + timers = Timers + } = Machine, + TM, + Iter +) -> + NewTimers = lists:foldl( + fun + ({state_timeout, _, _} = Timer, Acc) -> + process_timer(SeqId, Timer, Acc); + ({stop_timeout, I}, Acc) -> + process_timer(SeqId, {stop_timeout, I, stop}, Acc) + end, + Timers, + Timeouts + ), iter(Iter, Result, Machine#state_machine{timers = NewTimers}, TM). process_manager(stop, Result, #state_machine{seq_id = SeqId}, TM) -> Result#{tm => delete_machine(SeqId, TM)}; - process_manager(_, Result, #state_machine{seq_id = SeqId} = Machine2, TM) -> Result#{tm => TM#{SeqId => Machine2}}. @@ -246,14 +283,18 @@ delete_machine(Id, Manager) -> case find_machine(Id, Manager) of undefined -> Manager; - #state_machine{seq_id = SeqId, - id = MachineId, - token = Token, - timers = Timers} -> - lists:foreach(fun({_, Ref}) -> - emqx_misc:cancel_timer(Ref) - end, - maps:to_list(Timers)), + #state_machine{ + seq_id = SeqId, + id = MachineId, + token = Token, + timers = Timers + } -> + lists:foreach( + fun({_, Ref}) -> + emqx_misc:cancel_timer(Ref) + end, + maps:to_list(Timers) + ), maps:without([SeqId, MachineId, ?TOKEN_ID(Token)], Manager) end. @@ -264,12 +305,14 @@ find_machine(SeqId, Manager) -> find_machine_by_seqid(SeqId, Manager). -spec find_machine_by_seqid(seq_id() | undefined, manager()) -> - state_machine() | undefined. + state_machine() | undefined. find_machine_by_seqid(SeqId, Manager) -> maps:get(SeqId, Manager, undefined). --spec find_machine_by_keys(list(manager_key()), - manager()) -> state_machine() | undefined. +-spec find_machine_by_keys( + list(manager_key()), + manager() +) -> state_machine() | undefined. find_machine_by_keys([H | T], Manager) -> case H of ?TOKEN_ID(<<>>) -> @@ -286,53 +329,63 @@ find_machine_by_keys(_, _) -> undefined. -spec new_in_machine(state_machine_key(), manager()) -> - {state_machine(), manager()}. + {state_machine(), manager()}. new_in_machine(MachineId, #{seq_id := SeqId} = Manager) -> - Machine = #state_machine{ seq_id = SeqId - , id = MachineId - , state = idle - , timers = #{} - , transport = emqx_coap_transport:new()}, - {Machine, Manager#{seq_id := SeqId + 1, - SeqId => Machine, - MachineId => SeqId}}. + Machine = #state_machine{ + seq_id = SeqId, + id = MachineId, + state = idle, + timers = #{}, + transport = emqx_coap_transport:new() + }, + {Machine, Manager#{ + seq_id := SeqId + 1, + SeqId => Machine, + MachineId => SeqId + }}. -spec new_out_machine(state_machine_key(), any(), coap_message(), manager()) -> - {state_machine(), manager()}. -new_out_machine(MachineId, - Ctx, - #coap_message{type = Type, token = Token, options = Opts}, - #{seq_id := SeqId} = Manager) -> + {state_machine(), manager()}. +new_out_machine( + MachineId, + Ctx, + #coap_message{type = Type, token = Token, options = Opts}, + #{seq_id := SeqId} = Manager +) -> Observe = maps:get(observe, Opts, undefined), - Machine = #state_machine{ seq_id = SeqId - , id = MachineId - , token = Token - , observe = Observe - , state = idle - , timers = #{} - , transport = emqx_coap_transport:new(Ctx)}, + Machine = #state_machine{ + seq_id = SeqId, + id = MachineId, + token = Token, + observe = Observe, + state = idle, + timers = #{}, + transport = emqx_coap_transport:new(Ctx) + }, - Manager2 = Manager#{seq_id := SeqId + 1, - SeqId => Machine, - MachineId => SeqId}, + Manager2 = Manager#{ + seq_id := SeqId + 1, + SeqId => Machine, + MachineId => SeqId + }, {Machine, - if Token =:= <<>> -> - Manager2; - Observe =:= 1 -> - TokenId = ?TOKEN_ID(Token), - delete_machine(TokenId, Manager2); - Type =:= con orelse Observe =:= 0 -> - TokenId = ?TOKEN_ID(Token), - case maps:get(TokenId, Manager, undefined) of - undefined -> - Manager2#{TokenId => SeqId}; - _ -> - throw("token conflict") - end; - true -> - Manager2 - end - }. + if + Token =:= <<>> -> + Manager2; + Observe =:= 1 -> + TokenId = ?TOKEN_ID(Token), + delete_machine(TokenId, Manager2); + Type =:= con orelse Observe =:= 0 -> + TokenId = ?TOKEN_ID(Token), + case maps:get(TokenId, Manager, undefined) of + undefined -> + Manager2#{TokenId => SeqId}; + _ -> + throw("token conflict") + end; + true -> + Manager2 + end}. alloc_message_id(#{next_msg_id := MsgId} = TM) -> alloc_message_id(MsgId, TM). @@ -348,8 +401,9 @@ alloc_message_id(MsgId, TM) -> next_message_id(MsgId) -> Next = MsgId + 1, - if Next >= ?MAX_MESSAGE_ID -> + if + Next >= ?MAX_MESSAGE_ID -> 1; - true -> + true -> Next end. diff --git a/apps/emqx_gateway/src/coap/emqx_coap_transport.erl b/apps/emqx_gateway/src/coap/emqx_coap_transport.erl index d3db897ae..06c7fc0b1 100644 --- a/apps/emqx_gateway/src/coap/emqx_coap_transport.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_transport.erl @@ -11,71 +11,102 @@ -type request_context() :: any(). --record(transport, { cache :: undefined | coap_message() - , req_context :: request_context() - , retry_interval :: non_neg_integer() - , retry_count :: non_neg_integer() - , observe :: non_neg_integer() | undefined - }). +-record(transport, { + cache :: undefined | coap_message(), + req_context :: request_context(), + retry_interval :: non_neg_integer(), + retry_count :: non_neg_integer(), + observe :: non_neg_integer() | undefined +}). -type transport() :: #transport{}. --export([ new/0, new/1, idle/3, maybe_reset/3, set_cache/2 - , maybe_resend_4request/3, wait_ack/3, until_stop/3 - , observe/3, maybe_resend_4response/3]). +-export([ + new/0, new/1, + idle/3, + maybe_reset/3, + set_cache/2, + maybe_resend_4request/3, + wait_ack/3, + until_stop/3, + observe/3, + maybe_resend_4response/3 +]). -export_type([transport/0]). --import(emqx_coap_medium, [ empty/0, reset/2, proto_out/2 - , out/1, out/2, proto_out/1 - , reply/2]). +-import(emqx_coap_medium, [ + empty/0, + reset/2, + proto_out/2, + out/1, out/2, + proto_out/1, + reply/2 +]). -spec new() -> transport(). new() -> new(undefined). new(ReqCtx) -> - #transport{cache = undefined, - retry_interval = 0, - retry_count = 0, - req_context = ReqCtx}. + #transport{ + cache = undefined, + retry_interval = 0, + retry_count = 0, + req_context = ReqCtx + }. -idle(in, - #coap_message{type = non, method = Method} = Msg, - _) -> +idle( + in, + #coap_message{type = non, method = Method} = Msg, + _ +) -> case Method of undefined -> reset(Msg, #{next => stop}); _ -> - proto_out({request, Msg}, - #{next => until_stop, - timeouts => - [{stop_timeout, ?NON_LIFETIME}]}) + proto_out( + {request, Msg}, + #{ + next => until_stop, + timeouts => + [{stop_timeout, ?NON_LIFETIME}] + } + ) end; - -idle(in, - #coap_message{type = con, method = Method} = Msg, - _) -> +idle( + in, + #coap_message{type = con, method = Method} = Msg, + _ +) -> case Method of undefined -> reset(Msg, #{next => stop}); _ -> - proto_out({request, Msg}, - #{next => maybe_resend_4request, - timeouts =>[{stop_timeout, ?EXCHANGE_LIFETIME}]}) + proto_out( + {request, Msg}, + #{ + next => maybe_resend_4request, + timeouts => [{stop_timeout, ?EXCHANGE_LIFETIME}] + } + ) end; - idle(out, #coap_message{type = non} = Msg, _) -> - out(Msg, #{next => maybe_reset, - timeouts => [{stop_timeout, ?NON_LIFETIME}]}); - + out(Msg, #{ + next => maybe_reset, + timeouts => [{stop_timeout, ?NON_LIFETIME}] + }); idle(out, Msg, Transport) -> _ = emqx_misc:rand_seed(), Timeout = ?ACK_TIMEOUT + rand:uniform(?ACK_RANDOM_FACTOR), - out(Msg, #{next => wait_ack, - transport => Transport#transport{cache = Msg}, - timeouts => [ {state_timeout, Timeout, ack_timeout} - , {stop_timeout, ?EXCHANGE_LIFETIME}]}). + out(Msg, #{ + next => wait_ack, + transport => Transport#transport{cache = Msg}, + timeouts => [ + {state_timeout, Timeout, ack_timeout}, + {stop_timeout, ?EXCHANGE_LIFETIME} + ] + }). maybe_resend_4request(in, Msg, Transport) -> maybe_resend(Msg, true, Transport). @@ -98,19 +129,26 @@ maybe_resend(Msg, IsExpecteReq, #transport{cache = Cache}) -> reset(Msg, #{next => stop}) end. -maybe_reset(in, #coap_message{type = Type, method = Method} = Message, - #transport{req_context = Ctx} = Transport) -> +maybe_reset( + in, + #coap_message{type = Type, method = Method} = Message, + #transport{req_context = Ctx} = Transport +) -> Ret = #{next => stop}, CtxMsg = {Ctx, Message}, - if Type =:= reset -> + if + Type =:= reset -> proto_out({reset, CtxMsg}, Ret); - is_tuple(Method) -> - on_response(Message, - Transport, - if Type =:= non -> until_stop; - true -> maybe_resend_4response - end); - true -> + is_tuple(Method) -> + on_response( + Message, + Transport, + if + Type =:= non -> until_stop; + true -> maybe_resend_4response + end + ); + true -> reset(Message, Ret) end. @@ -131,26 +169,37 @@ wait_ack(in, #coap_message{type = Type, method = Method} = Msg, #transport{req_c reset(Msg, #{next => stop}) end end; - -wait_ack(state_timeout, - ack_timeout, - #transport{cache = Msg, - retry_interval = Timeout, - retry_count = Count} =Transport) -> +wait_ack( + state_timeout, + ack_timeout, + #transport{ + cache = Msg, + retry_interval = Timeout, + retry_count = Count + } = Transport +) -> case Count < ?MAX_RETRANSMIT of true -> Timeout2 = Timeout * 2, - out(Msg, - #{transport => Transport#transport{retry_interval = Timeout2, - retry_count = Count + 1}, - timeouts => [{state_timeout, Timeout2, ack_timeout}]}); + out( + Msg, + #{ + transport => Transport#transport{ + retry_interval = Timeout2, + retry_count = Count + 1 + }, + timeouts => [{state_timeout, Timeout2, ack_timeout}] + } + ); _ -> proto_out({ack_failure, Msg}, #{next_state => stop}) end. -observe(in, - #coap_message{method = Method} = Message, - #transport{observe = Observe} = Transport) -> +observe( + in, + #coap_message{method = Method} = Message, + #transport{observe = Observe} = Transport +) -> case Method of {ok, _} -> case emqx_coap_message:get_option(observe, Message, Observe) of @@ -158,9 +207,11 @@ observe(in, %% repeatd notify, ignore empty(); NewObserve -> - on_response(Message, - Transport#transport{observe = NewObserve}, - ?FUNCTION_NAME) + on_response( + Message, + Transport#transport{observe = NewObserve}, + ?FUNCTION_NAME + ) end; {error, _} -> #{next => stop}; @@ -174,17 +225,24 @@ until_stop(_, _, _) -> set_cache(Cache, Transport) -> Transport#transport{cache = Cache}. -on_response(#coap_message{type = Type} = Message, - #transport{req_context = Ctx} = Transport, - NextState) -> +on_response( + #coap_message{type = Type} = Message, + #transport{req_context = Ctx} = Transport, + NextState +) -> CtxMsg = {Ctx, Message}, - if Type =:= non -> + if + Type =:= non -> proto_out({response, CtxMsg}, #{next => NextState}); - Type =:= con -> + Type =:= con -> Ack = emqx_coap_message:ack(Message), - proto_out({response, CtxMsg}, - out(Ack, #{next => NextState, - transport => Transport#transport{cache = Ack}})); - true -> + proto_out( + {response, CtxMsg}, + out(Ack, #{ + next => NextState, + transport => Transport#transport{cache = Ack} + }) + ); + true -> emqx_coap_message:reset(Message) end. diff --git a/apps/emqx_gateway/src/coap/handler/emqx_coap_mqtt_handler.erl b/apps/emqx_gateway/src/coap/handler/emqx_coap_mqtt_handler.erl index 94aba40b4..49573b799 100644 --- a/apps/emqx_gateway/src/coap/handler/emqx_coap_mqtt_handler.erl +++ b/apps/emqx_gateway/src/coap/handler/emqx_coap_mqtt_handler.erl @@ -24,18 +24,14 @@ handle_request([<<"connection">>], #coap_message{method = Method} = Msg, _Ctx, _CInfo) -> handle_method(Method, Msg); - handle_request(_, Msg, _, _) -> reply({error, bad_request}, Msg). handle_method(put, Msg) -> reply({ok, changed}, Msg); - handle_method(post, Msg) -> #{connection => {open, Msg}}; - handle_method(delete, Msg) -> #{connection => {close, Msg}}; - handle_method(_, Msg) -> reply({error, method_not_allowed}, Msg). diff --git a/apps/emqx_gateway/src/coap/handler/emqx_coap_pubsub_handler.erl b/apps/emqx_gateway/src/coap/handler/emqx_coap_pubsub_handler.erl index 6f733c9db..49df1db23 100644 --- a/apps/emqx_gateway/src/coap/handler/emqx_coap_pubsub_handler.erl +++ b/apps/emqx_gateway/src/coap/handler/emqx_coap_pubsub_handler.erl @@ -49,7 +49,6 @@ handle_method(get, Topic, Msg, Ctx, CInfo) -> _ -> reply({error, bad_request}, <<"invalid observe value">>, Msg) end; - handle_method(post, Topic, #coap_message{payload = Payload} = Msg, Ctx, CInfo) -> case emqx_coap_channel:validator(publish, Topic, Ctx, CInfo) of allow -> @@ -64,22 +63,23 @@ handle_method(post, Topic, #coap_message{payload = Payload} = Msg, Ctx, CInfo) - _ -> reply({error, unauthorized}, Msg) end; - handle_method(_, _, Msg, _, _) -> reply({error, method_not_allowed}, Msg). check_topic([]) -> error; - check_topic(Path) -> Sep = <<"/">>, {ok, - emqx_http_lib:uri_decode( - lists:foldl(fun(Part, Acc) -> - <> - end, - <<>>, - Path))}. + emqx_http_lib:uri_decode( + lists:foldl( + fun(Part, Acc) -> + <> + end, + <<>>, + Path + ) + )}. get_sub_opts(#coap_message{options = Opts} = Msg) -> SubOpts = maps:fold(fun parse_sub_opts/3, #{}, Opts), @@ -100,9 +100,12 @@ parse_sub_opts(<<"rh">>, V, Opts) -> parse_sub_opts(_, _, Opts) -> Opts. -type_to_qos(qos0, _) -> ?QOS_0; -type_to_qos(qos1, _) -> ?QOS_1; -type_to_qos(qos2, _) -> ?QOS_2; +type_to_qos(qos0, _) -> + ?QOS_0; +type_to_qos(qos1, _) -> + ?QOS_1; +type_to_qos(qos2, _) -> + ?QOS_2; type_to_qos(coap, #coap_message{type = Type}) -> case Type of non -> @@ -121,24 +124,28 @@ get_publish_qos(Msg) -> end. apply_publish_opts(Msg, MQTTMsg) -> - maps:fold(fun(<<"retain">>, V, Acc) -> - Val = erlang:binary_to_atom(V), - emqx_message:set_flag(retain, Val, Acc); - (<<"expiry">>, V, Acc) -> - Val = erlang:binary_to_integer(V), - Props = emqx_message:get_header(properties, Acc), - emqx_message:set_header(properties, - Props#{'Message-Expiry-Interval' => Val}, - Acc); - (_, _, Acc) -> - Acc - end, - MQTTMsg, - emqx_coap_message:get_option(uri_query, Msg)). + maps:fold( + fun + (<<"retain">>, V, Acc) -> + Val = erlang:binary_to_atom(V), + emqx_message:set_flag(retain, Val, Acc); + (<<"expiry">>, V, Acc) -> + Val = erlang:binary_to_integer(V), + Props = emqx_message:get_header(properties, Acc), + emqx_message:set_header( + properties, + Props#{'Message-Expiry-Interval' => Val}, + Acc + ); + (_, _, Acc) -> + Acc + end, + MQTTMsg, + emqx_coap_message:get_option(uri_query, Msg) + ). subscribe(#coap_message{token = <<>>} = Msg, _, _, _) -> reply({error, bad_request}, <<"observe without token">>, Msg); - subscribe(#coap_message{token = Token} = Msg, Topic, Ctx, CInfo) -> case emqx_coap_channel:validator(subscribe, Topic, Ctx, CInfo) of allow -> diff --git a/apps/emqx_gateway/src/coap/include/emqx_coap.hrl b/apps/emqx_gateway/src/coap/include/emqx_coap.hrl index 4112d7026..2f818f653 100644 --- a/apps/emqx_gateway/src/coap/include/emqx_coap.hrl +++ b/apps/emqx_gateway/src/coap/include/emqx_coap.hrl @@ -22,55 +22,60 @@ -define(DEFAULT_MAX_AGE, 60). -define(MAXIMUM_MAX_AGE, 4294967295). --type coap_message_id() :: 1 .. ?MAX_MESSAGE_ID. +-type coap_message_id() :: 1..?MAX_MESSAGE_ID. -type message_type() :: con | non | ack | reset. --type max_age() :: 1 .. ?MAXIMUM_MAX_AGE. +-type max_age() :: 1..?MAXIMUM_MAX_AGE. --type message_option_name() :: if_match - | uri_host - | etag - | if_none_match - | uri_port - | location_path - | uri_path - | content_format - | max_age - | uri_query - | 'accept' - | location_query - | proxy_uri - | proxy_scheme - | size1 - | observer - | block1 - | block2. +-type message_option_name() :: + if_match + | uri_host + | etag + | if_none_match + | uri_port + | location_path + | uri_path + | content_format + | max_age + | uri_query + | 'accept' + | location_query + | proxy_uri + | proxy_scheme + | size1 + | observer + | block1 + | block2. --type message_options() :: #{ if_match => list(binary()) - , uri_host => binary() - , etag => list(binary()) - , if_none_match => boolean() - , uri_port => 0 .. 65535 - , location_path => list(binary()) - , uri_path => list(binary()) - , content_format => 0 .. 65535 - , max_age => non_neg_integer() - , uri_query => list(binary()) | map() - , 'accept' => 0 .. 65535 - , location_query => list(binary()) - , proxy_uri => binary() - , proxy_scheme => binary() - , size1 => non_neg_integer() - , observer => non_neg_integer() - , block1 => {non_neg_integer(), boolean(), non_neg_integer()} - , block2 => {non_neg_integer(), boolean(), non_neg_integer()}}. +-type message_options() :: #{ + if_match => list(binary()), + uri_host => binary(), + etag => list(binary()), + if_none_match => boolean(), + uri_port => 0..65535, + location_path => list(binary()), + uri_path => list(binary()), + content_format => 0..65535, + max_age => non_neg_integer(), + uri_query => list(binary()) | map(), + 'accept' => 0..65535, + location_query => list(binary()), + proxy_uri => binary(), + proxy_scheme => binary(), + size1 => non_neg_integer(), + observer => non_neg_integer(), + block1 => {non_neg_integer(), boolean(), non_neg_integer()}, + block2 => {non_neg_integer(), boolean(), non_neg_integer()} +}. -record(coap_mqtt_auth, {clientid, username, password}). --record(coap_message, { type :: message_type() - , method - , id - , token = <<>> - , options = #{} - , payload = <<>>}). +-record(coap_message, { + type :: message_type(), + method, + id, + token = <<>>, + options = #{}, + payload = <<>> +}). -type coap_message() :: #coap_message{}. diff --git a/apps/emqx_gateway/src/emqx_gateway.app.src b/apps/emqx_gateway/src/emqx_gateway.app.src index b300c659c..b673fd867 100644 --- a/apps/emqx_gateway/src/emqx_gateway.app.src +++ b/apps/emqx_gateway/src/emqx_gateway.app.src @@ -1,12 +1,12 @@ %% -*- mode: erlang -*- -{application, emqx_gateway, - [{description, "The Gateway management application"}, - {vsn, "0.1.0"}, - {registered, []}, - {mod, {emqx_gateway_app, []}}, - {applications, [kernel, stdlib, grpc, emqx]}, - {env, []}, - {modules, []}, - {licenses, ["Apache 2.0"]}, - {links, []} - ]}. +{application, emqx_gateway, [ + {description, "The Gateway management application"}, + {vsn, "0.1.0"}, + {registered, []}, + {mod, {emqx_gateway_app, []}}, + {applications, [kernel, stdlib, grpc, emqx]}, + {env, []}, + {modules, []}, + {licenses, ["Apache 2.0"]}, + {links, []} +]}. diff --git a/apps/emqx_gateway/src/emqx_gateway.erl b/apps/emqx_gateway/src/emqx_gateway.erl index 74a8244d4..ecc7a96f3 100644 --- a/apps/emqx_gateway/src/emqx_gateway.erl +++ b/apps/emqx_gateway/src/emqx_gateway.erl @@ -19,15 +19,16 @@ -include("include/emqx_gateway.hrl"). %% Gateway APIs --export([ registered_gateway/0 - , load/2 - , unload/1 - , lookup/1 - , update/2 - , start/1 - , stop/1 - , list/0 - ]). +-export([ + registered_gateway/0, + load/2, + unload/1, + lookup/1, + update/2, + start/1, + stop/1, + list/0 +]). %%-------------------------------------------------------------------- %% APIs @@ -46,14 +47,15 @@ registered_gateway() -> list() -> emqx_gateway_sup:list_gateway_insta(). --spec load(gateway_name(), emqx_config:config()) - -> {ok, pid()} - | {error, any()}. +-spec load(gateway_name(), emqx_config:config()) -> + {ok, pid()} + | {error, any()}. load(Name, Config) -> - Gateway = #{ name => Name - , descr => undefined - , config => Config - }, + Gateway = #{ + name => Name, + descr => undefined, + config => Config + }, emqx_gateway_sup:load_gateway(Gateway). -spec unload(gateway_name()) -> ok | {error, not_found}. diff --git a/apps/emqx_gateway/src/emqx_gateway_api.erl b/apps/emqx_gateway/src/emqx_gateway_api.erl index 85aad78d2..c69b8e13a 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api.erl @@ -26,25 +26,31 @@ -import(hoconsc, [mk/2, ref/1, ref/2]). --import(emqx_gateway_http, - [ return_http_error/2 - , with_gateway/2 - ]). +-import( + emqx_gateway_http, + [ + return_http_error/2, + with_gateway/2 + ] +). %% minirest/dashbaord_swagger behaviour callbacks --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). --export([ roots/0 - , fields/1 - ]). +-export([ + roots/0, + fields/1 +]). %% http handlers --export([ gateway/2 - , gateway_insta/2 - ]). +-export([ + gateway/2, + gateway_insta/2 +]). %%-------------------------------------------------------------------- %% minirest behaviour callbacks @@ -54,8 +60,9 @@ api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/gateway" - , "/gateway/:name" + [ + "/gateway", + "/gateway/:name" ]. %%-------------------------------------------------------------------- @@ -63,10 +70,11 @@ paths() -> gateway(get, Request) -> Params = maps:get(query_string, Request, #{}), - Status = case maps:get(<<"status">>, Params, undefined) of - undefined -> all; - S0 -> binary_to_existing_atom(S0, utf8) - end, + Status = + case maps:get(<<"status">>, Params, undefined) of + undefined -> all; + S0 -> binary_to_existing_atom(S0, utf8) + end, {200, emqx_gateway_http:gateways(Status)}; gateway(post, Request) -> Body = maps:get(body, Request, #{}), @@ -74,7 +82,8 @@ gateway(post, Request) -> Name0 = maps:get(<<"name">>, Body), GwName = binary_to_existing_atom(Name0), case emqx_gateway_registry:lookup(GwName) of - undefined -> error(badarg); + undefined -> + error(badarg); _ -> GwConf = maps:without([<<"name">>], Body), case emqx_gateway_conf:load_gateway(GwName, GwConf) of @@ -85,9 +94,9 @@ gateway(post, Request) -> end end catch - error : {badkey, K} -> + error:{badkey, K} -> return_http_error(400, [K, " is required"]); - error : badarg -> + error:badarg -> return_http_error(404, "Bad gateway name") end. @@ -101,9 +110,7 @@ gateway_insta(delete, #{bindings := #{name := Name0}}) -> end end); gateway_insta(get, #{bindings := #{name := Name0}}) -> - try - binary_to_existing_atom(Name0) - of + try binary_to_existing_atom(Name0) of GwName -> case emqx_gateway:lookup(GwName) of undefined -> @@ -111,22 +118,29 @@ gateway_insta(get, #{bindings := #{name := Name0}}) -> Gateway -> GwConf = emqx_gateway_conf:gateway(Name0), GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339( - [created_at, started_at, stopped_at], - Gateway), - GwInfo1 = maps:with([name, - status, - created_at, - started_at, - stopped_at], GwInfo0), + [created_at, started_at, stopped_at], + Gateway + ), + GwInfo1 = maps:with( + [ + name, + status, + created_at, + started_at, + stopped_at + ], + GwInfo0 + ), {200, maps:merge(GwConf, GwInfo1)} end catch - error : badarg -> + error:badarg -> return_http_error(400, "Bad gateway name") end; -gateway_insta(put, #{body := GwConf0, - bindings := #{name := Name0} - }) -> +gateway_insta(put, #{ + body := GwConf0, + bindings := #{name := Name0} +}) -> with_gateway(Name0, fun(GwName, _) -> %% XXX: Clear the unused fields GwConf = maps:without([<<"name">>], GwConf0), @@ -143,465 +157,597 @@ gateway_insta(put, #{body := GwConf0, %%-------------------------------------------------------------------- schema("/gateway") -> - #{ 'operationId' => gateway, - get => - #{ desc => <<"Get gateway list">> - , parameters => params_gateway_status_in_qs() - , responses => - ?STANDARD_RESP( - #{200 => emqx_dashboard_swagger:schema_with_example( - hoconsc:array(ref(gateway_overview)), - examples_gateway_overview())}) - }, - post => - #{ desc => <<"Load a gateway">> - %% TODO: distinguish create & response swagger schema - , 'requestBody' => schema_gateways_conf() - , responses => - ?STANDARD_RESP(#{201 => schema_gateways_conf()}) - } - }; + #{ + 'operationId' => gateway, + get => + #{ + desc => <<"Get gateway list">>, + parameters => params_gateway_status_in_qs(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + hoconsc:array(ref(gateway_overview)), + examples_gateway_overview() + ) + } + ) + }, + post => + #{ + desc => <<"Load a gateway">>, + %% TODO: distinguish create & response swagger schema + 'requestBody' => schema_gateways_conf(), + responses => + ?STANDARD_RESP(#{201 => schema_gateways_conf()}) + } + }; schema("/gateway/:name") -> - #{ 'operationId' => gateway_insta, - get => - #{ desc => <<"Get the gateway configurations">> - , parameters => params_gateway_name_in_path() - , responses => - ?STANDARD_RESP(#{200 => schema_gateways_conf()}) - }, - delete => - #{ desc => <<"Delete/Unload the gateway">> - , parameters => params_gateway_name_in_path() - , responses => - ?STANDARD_RESP(#{204 => <<"Deleted">>}) - }, - put => - #{ desc => <<"Update the gateway configurations/status">> - , parameters => params_gateway_name_in_path() - , 'requestBody' => schema_update_gateways_conf() - , responses => - ?STANDARD_RESP(#{200 => schema_gateways_conf()}) - } - }. + #{ + 'operationId' => gateway_insta, + get => + #{ + desc => <<"Get the gateway configurations">>, + parameters => params_gateway_name_in_path(), + responses => + ?STANDARD_RESP(#{200 => schema_gateways_conf()}) + }, + delete => + #{ + desc => <<"Delete/Unload the gateway">>, + parameters => params_gateway_name_in_path(), + responses => + ?STANDARD_RESP(#{204 => <<"Deleted">>}) + }, + put => + #{ + desc => <<"Update the gateway configurations/status">>, + parameters => params_gateway_name_in_path(), + 'requestBody' => schema_update_gateways_conf(), + responses => + ?STANDARD_RESP(#{200 => schema_gateways_conf()}) + } + }. %%-------------------------------------------------------------------- %% params defines params_gateway_name_in_path() -> - [{name, - mk(binary(), - #{ in => path - , desc => <<"Gateway Name">> - , example => <<"">> - })} + [ + {name, + mk( + binary(), + #{ + in => path, + desc => <<"Gateway Name">>, + example => <<"">> + } + )} ]. params_gateway_status_in_qs() -> %% FIXME: enum in swagger ?? - [{status, - mk(binary(), - #{ in => query - , required => false - , desc => <<"Gateway Status">> - , example => <<"">> - })} + [ + {status, + mk( + binary(), + #{ + in => query, + required => false, + desc => <<"Gateway Status">>, + example => <<"">> + } + )} ]. %%-------------------------------------------------------------------- %% schemas roots() -> - [ gateway_overview - , gateway_stats + [ + gateway_overview, + gateway_stats ]. fields(gateway_overview) -> - [ {name, - mk(binary(), - #{ desc => <<"Gateway Name">>})} - , {status, - mk(hoconsc:enum([running, stopped, unloaded]), - #{ desc => <<"The Gateway status">>})} - , {created_at, - mk(binary(), - #{desc => <<"The Gateway created datetime">>})} - , {started_at, - mk(binary(), - #{ required => false - , desc => <<"The Gateway started datetime">>})} - , {stopped_at, - mk(binary(), - #{ required => false - , desc => <<"The Gateway stopped datetime">>})} - , {max_connections, - mk(integer(), - #{ desc => <<"The Gateway allowed maximum connections/clients">>})} - , {current_connections, - mk(integer(), - #{ desc => <<"The Gateway current connected connections/clients">> - })} - , {listeners, - mk(hoconsc:array(ref(gateway_listener_overview)), - #{ required => {false, recursively} - , desc => <<"The Gateway listeners overview">>})} + [ + {name, + mk( + binary(), + #{desc => <<"Gateway Name">>} + )}, + {status, + mk( + hoconsc:enum([running, stopped, unloaded]), + #{desc => <<"The Gateway status">>} + )}, + {created_at, + mk( + binary(), + #{desc => <<"The Gateway created datetime">>} + )}, + {started_at, + mk( + binary(), + #{ + required => false, + desc => <<"The Gateway started datetime">> + } + )}, + {stopped_at, + mk( + binary(), + #{ + required => false, + desc => <<"The Gateway stopped datetime">> + } + )}, + {max_connections, + mk( + integer(), + #{desc => <<"The Gateway allowed maximum connections/clients">>} + )}, + {current_connections, + mk( + integer(), + #{desc => <<"The Gateway current connected connections/clients">>} + )}, + {listeners, + mk( + hoconsc:array(ref(gateway_listener_overview)), + #{ + required => {false, recursively}, + desc => <<"The Gateway listeners overview">> + } + )} ]; fields(gateway_listener_overview) -> - [ {id, - mk(binary(), - #{ desc => <<"Listener ID">>})} - , {running, - mk(boolean(), - #{ desc => <<"Listener Running status">>})} - , {type, - mk(hoconsc:enum([tcp, ssl, udp, dtls]), - #{ desc => <<"Listener Type">>})} + [ + {id, + mk( + binary(), + #{desc => <<"Listener ID">>} + )}, + {running, + mk( + boolean(), + #{desc => <<"Listener Running status">>} + )}, + {type, + mk( + hoconsc:enum([tcp, ssl, udp, dtls]), + #{desc => <<"Listener Type">>} + )} ]; - -fields(Gw) when Gw == stomp; Gw == mqttsn; - Gw == coap; Gw == lwm2m; - Gw == exproto -> - [{name, - mk(hoconsc:union([Gw]), #{ desc => <<"Gateway Name">>})} - ] ++ convert_listener_struct(emqx_gateway_schema:fields(Gw)); - -fields(Gw) when Gw == update_stomp; Gw == update_mqttsn; - Gw == update_coap; Gw == update_lwm2m; - Gw == update_exproto -> +fields(Gw) when + Gw == stomp; + Gw == mqttsn; + Gw == coap; + Gw == lwm2m; + Gw == exproto +-> + [{name, mk(hoconsc:union([Gw]), #{desc => <<"Gateway Name">>})}] ++ + convert_listener_struct(emqx_gateway_schema:fields(Gw)); +fields(Gw) when + Gw == update_stomp; + Gw == update_mqttsn; + Gw == update_coap; + Gw == update_lwm2m; + Gw == update_exproto +-> "update_" ++ GwStr = atom_to_list(Gw), Gw1 = list_to_existing_atom(GwStr), remove_listener_and_authn(emqx_gateway_schema:fields(Gw1)); - -fields(Listener) when Listener == tcp_listener; - Listener == ssl_listener; - Listener == udp_listener; - Listener == dtls_listener -> - [ {id, - mk(binary(), - #{ required => false - , desc => <<"Listener ID">>})} - , {type, - mk(hoconsc:union([tcp, ssl, udp, dtls]), - #{ desc => <<"Listener type">>})} - , {name, - mk(binary(), - #{ desc => <<"Listener Name">>})} - , {running, - mk(boolean(), - #{ required => false - , desc => <<"Listener running status">>})} +fields(Listener) when + Listener == tcp_listener; + Listener == ssl_listener; + Listener == udp_listener; + Listener == dtls_listener +-> + [ + {id, + mk( + binary(), + #{ + required => false, + desc => <<"Listener ID">> + } + )}, + {type, + mk( + hoconsc:union([tcp, ssl, udp, dtls]), + #{desc => <<"Listener type">>} + )}, + {name, + mk( + binary(), + #{desc => <<"Listener Name">>} + )}, + {running, + mk( + boolean(), + #{ + required => false, + desc => <<"Listener running status">> + } + )} ] ++ emqx_gateway_schema:fields(Listener); - fields(gateway_stats) -> [{key, mk(binary(), #{})}]. schema_update_gateways_conf() -> emqx_dashboard_swagger:schema_with_examples( - hoconsc:union([ref(?MODULE, update_stomp), - ref(?MODULE, update_mqttsn), - ref(?MODULE, update_coap), - ref(?MODULE, update_lwm2m), - ref(?MODULE, update_exproto)]), - examples_update_gateway_confs() - ). + hoconsc:union([ + ref(?MODULE, update_stomp), + ref(?MODULE, update_mqttsn), + ref(?MODULE, update_coap), + ref(?MODULE, update_lwm2m), + ref(?MODULE, update_exproto) + ]), + examples_update_gateway_confs() + ). schema_gateways_conf() -> emqx_dashboard_swagger:schema_with_examples( - hoconsc:union([ref(?MODULE, stomp), ref(?MODULE, mqttsn), - ref(?MODULE, coap), ref(?MODULE, lwm2m), - ref(?MODULE, exproto)]), - examples_gateway_confs() - ). + hoconsc:union([ + ref(?MODULE, stomp), + ref(?MODULE, mqttsn), + ref(?MODULE, coap), + ref(?MODULE, lwm2m), + ref(?MODULE, exproto) + ]), + examples_gateway_confs() + ). convert_listener_struct(Schema) -> - {value, {listeners, - #{type := Type}}, Schema1} = lists:keytake(listeners, 1, Schema), - ListenerSchema = hoconsc:mk(listeners_schema(Type), - #{ required => {false, recursively} - , desc => <<"The gateway listeners">> - }), + {value, {listeners, #{type := Type}}, Schema1} = lists:keytake(listeners, 1, Schema), + ListenerSchema = hoconsc:mk( + listeners_schema(Type), + #{ + required => {false, recursively}, + desc => <<"The gateway listeners">> + } + ), lists:keystore(listeners, 1, Schema1, {listeners, ListenerSchema}). remove_listener_and_authn(Schmea) -> lists:keydelete( - authentication, 1, - lists:keydelete(listeners, 1, Schmea)). + authentication, + 1, + lists:keydelete(listeners, 1, Schmea) + ). listeners_schema(?R_REF(_Mod, tcp_listeners)) -> hoconsc:array(hoconsc:union([ref(tcp_listener), ref(ssl_listener)])); listeners_schema(?R_REF(_Mod, udp_listeners)) -> hoconsc:array(hoconsc:union([ref(udp_listener), ref(dtls_listener)])); listeners_schema(?R_REF(_Mod, udp_tcp_listeners)) -> - hoconsc:array(hoconsc:union([ref(tcp_listener), ref(ssl_listener), - ref(udp_listener), ref(dtls_listener)])). + hoconsc:array( + hoconsc:union([ + ref(tcp_listener), + ref(ssl_listener), + ref(udp_listener), + ref(dtls_listener) + ]) + ). %%-------------------------------------------------------------------- %% examples examples_gateway_overview() -> - [ #{ name => <<"coap">> - , status => <<"unloaded">> - } - , #{ name => <<"exproto">> - , status => <<"unloaded">> - } - , #{ name => <<"lwm2m">> - , status => <<"running">> - , current_connections => 0 - , max_connections => 1024000 - , listeners => - [ #{ id => <<"lwm2m:udp:default">> - , type => <<"udp">> - , name => <<"default">> - , running => true - } - ] - , created_at => <<"2021-12-08T14:41:26.171+08:00">> - , started_at => <<"2021-12-08T14:41:26.202+08:00">> - } - , #{ name => <<"mqttsn">> - , status => <<"stopped">> - , current_connections => 0 - , max_connections => 1024000 - , listeners => - [ #{ id => <<"mqttsn:udp:default">> - , name => <<"default">> - , running => false - , type => <<"udp">> - } - ] - , created_at => <<"2021-12-08T14:41:45.071+08:00">> - , stopped_at => <<"2021-12-08T14:56:35.576+08:00">> - } - , #{ name => <<"stomp">> - , status => <<"running">> - , current_connections => 0 - , max_connections => 1024000 - , listeners => - [ #{ id => <<"stomp:tcp:default">> - , name => <<"default">> - , running => true - , type => <<"tcp">> - } - ] - , created_at => <<"2021-12-08T14:42:15.272+08:00">> - , started_at => <<"2021-12-08T14:42:15.274+08:00">> - } + [ + #{ + name => <<"coap">>, + status => <<"unloaded">> + }, + #{ + name => <<"exproto">>, + status => <<"unloaded">> + }, + #{ + name => <<"lwm2m">>, + status => <<"running">>, + current_connections => 0, + max_connections => 1024000, + listeners => + [ + #{ + id => <<"lwm2m:udp:default">>, + type => <<"udp">>, + name => <<"default">>, + running => true + } + ], + created_at => <<"2021-12-08T14:41:26.171+08:00">>, + started_at => <<"2021-12-08T14:41:26.202+08:00">> + }, + #{ + name => <<"mqttsn">>, + status => <<"stopped">>, + current_connections => 0, + max_connections => 1024000, + listeners => + [ + #{ + id => <<"mqttsn:udp:default">>, + name => <<"default">>, + running => false, + type => <<"udp">> + } + ], + created_at => <<"2021-12-08T14:41:45.071+08:00">>, + stopped_at => <<"2021-12-08T14:56:35.576+08:00">> + }, + #{ + name => <<"stomp">>, + status => <<"running">>, + current_connections => 0, + max_connections => 1024000, + listeners => + [ + #{ + id => <<"stomp:tcp:default">>, + name => <<"default">>, + running => true, + type => <<"tcp">> + } + ], + created_at => <<"2021-12-08T14:42:15.272+08:00">>, + started_at => <<"2021-12-08T14:42:15.274+08:00">> + } ]. examples_gateway_confs() -> - #{ stomp_gateway => - #{ summary => <<"A simple STOMP gateway configs">> - , value => - #{ enable => true - , name => <<"stomp">> - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"stomp/">> - , frame => - #{ max_headers => 10 - , max_headers_length => 1024 - , max_body_length => 65535 - } - , listeners => - [ #{ type => <<"tcp">> - , name => <<"default">> - , bind => <<"61613">> - , max_connections => 1024000 - , max_conn_rate => 1000 - } - ] - } - } - , mqttsn_gateway => - #{ summary => <<"A simple MQTT-SN gateway configs">> - , value => - #{ enable => true - , name => <<"mqttsn">> - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"mqttsn/">> - , gateway_id => 1 - , broadcast => true - , enable_qos3 => true - , predefined => - [ #{ id => <<"1001">> - , topic => <<"pred/1001">> - } - , #{ id => <<"1002">> - , topic => <<"pred/1002">> - } - ] - , listeners => - [ #{ type => <<"udp">> - , name => <<"default">> - , bind => <<"1884">> - , max_connections => 1024000 - , max_conn_rate => 1000 - } - ] - } - } - , coap_gateway => - #{ summary => <<"A simple CoAP gateway configs">> - , value => - #{ enable => true - , name => <<"coap">> - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"coap/">> - , heartbeat => <<"30s">> - , connection_required => false - , notify_type => <<"qos">> - , subscribe_qos => <<"coap">> - , publish_qos => <<"coap">> - , listeners => - [ #{ type => <<"udp">> - , name => <<"default">> - , bind => <<"5683">> - , max_connections => 1024000 - , max_conn_rate => 1000 - } - ] - } - } - , lwm2m_gateway => - #{ summary => <<"A simple LwM2M gateway configs">> - , value => - #{ enable => true - , name => <<"lwm2m">> - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"lwm2m/">> - , xml_dir => <<"etc/lwm2m_xml">> - , lifetime_min => <<"1s">> - , lifetime_max => <<"86400s">> - , qmode_time_window => <<"22s">> - , auto_observe => false - , update_msg_publish_condition => <<"always">> - , translators => - #{ command => #{topic => <<"dn/#">>} - , response => #{topic => <<"up/resp">>} - , notify => #{topic => <<"up/notify">>} - , register => #{topic => <<"up/resp">>} - , update => #{topic => <<"up/resp">>} - } - , listeners => - [ #{ type => <<"udp">> - , name => <<"default">> - , bind => <<"5783">> - , max_connections => 1024000 - , max_conn_rate => 1000 - } - ] - } - } - , exproto_gateway => - #{ summary => <<"A simple ExProto gateway configs">> - , value => - #{ enable => true - , name => <<"exproto">> - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"exproto/">> - , server => - #{ bind => <<"9100">> - } - , handler => - #{ address => <<"http://127.0.0.1:9001">> - } - , listeners => - [ #{ type => <<"tcp">> - , name => <<"default">> - , bind => <<"7993">> - , max_connections => 1024000 - , max_conn_rate => 1000 - } - ] - } - } - }. + #{ + stomp_gateway => + #{ + summary => <<"A simple STOMP gateway configs">>, + value => + #{ + enable => true, + name => <<"stomp">>, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"stomp/">>, + frame => + #{ + max_headers => 10, + max_headers_length => 1024, + max_body_length => 65535 + }, + listeners => + [ + #{ + type => <<"tcp">>, + name => <<"default">>, + bind => <<"61613">>, + max_connections => 1024000, + max_conn_rate => 1000 + } + ] + } + }, + mqttsn_gateway => + #{ + summary => <<"A simple MQTT-SN gateway configs">>, + value => + #{ + enable => true, + name => <<"mqttsn">>, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"mqttsn/">>, + gateway_id => 1, + broadcast => true, + enable_qos3 => true, + predefined => + [ + #{ + id => <<"1001">>, + topic => <<"pred/1001">> + }, + #{ + id => <<"1002">>, + topic => <<"pred/1002">> + } + ], + listeners => + [ + #{ + type => <<"udp">>, + name => <<"default">>, + bind => <<"1884">>, + max_connections => 1024000, + max_conn_rate => 1000 + } + ] + } + }, + coap_gateway => + #{ + summary => <<"A simple CoAP gateway configs">>, + value => + #{ + enable => true, + name => <<"coap">>, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"coap/">>, + heartbeat => <<"30s">>, + connection_required => false, + notify_type => <<"qos">>, + subscribe_qos => <<"coap">>, + publish_qos => <<"coap">>, + listeners => + [ + #{ + type => <<"udp">>, + name => <<"default">>, + bind => <<"5683">>, + max_connections => 1024000, + max_conn_rate => 1000 + } + ] + } + }, + lwm2m_gateway => + #{ + summary => <<"A simple LwM2M gateway configs">>, + value => + #{ + enable => true, + name => <<"lwm2m">>, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"lwm2m/">>, + xml_dir => <<"etc/lwm2m_xml">>, + lifetime_min => <<"1s">>, + lifetime_max => <<"86400s">>, + qmode_time_window => <<"22s">>, + auto_observe => false, + update_msg_publish_condition => <<"always">>, + translators => + #{ + command => #{topic => <<"dn/#">>}, + response => #{topic => <<"up/resp">>}, + notify => #{topic => <<"up/notify">>}, + register => #{topic => <<"up/resp">>}, + update => #{topic => <<"up/resp">>} + }, + listeners => + [ + #{ + type => <<"udp">>, + name => <<"default">>, + bind => <<"5783">>, + max_connections => 1024000, + max_conn_rate => 1000 + } + ] + } + }, + exproto_gateway => + #{ + summary => <<"A simple ExProto gateway configs">>, + value => + #{ + enable => true, + name => <<"exproto">>, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"exproto/">>, + server => + #{bind => <<"9100">>}, + handler => + #{address => <<"http://127.0.0.1:9001">>}, + listeners => + [ + #{ + type => <<"tcp">>, + name => <<"default">>, + bind => <<"7993">>, + max_connections => 1024000, + max_conn_rate => 1000 + } + ] + } + } + }. examples_update_gateway_confs() -> - #{ stomp_gateway => - #{ summary => <<"A simple STOMP gateway configs">> - , value => - #{ enable => true - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"stomp2/">> - , frame => - #{ max_headers => 100 - , max_headers_length => 10240 - , max_body_length => 655350 - } - } - } - , mqttsn_gateway => - #{ summary => <<"A simple MQTT-SN gateway configs">> - , value => - #{ enable => true - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"mqttsn2/">> - , gateway_id => 1 - , broadcast => true - , enable_qos3 => false - , predefined => - [ #{ id => <<"1003">> - , topic => <<"pred/1003">> - } - ] - } - } - , coap_gateway => - #{ summary => <<"A simple CoAP gateway configs">> - , value => - #{ enable => true - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"coap2/">> - , heartbeat => <<"30s">> - , connection_required => false - , notify_type => <<"qos">> - , subscribe_qos => <<"coap">> - , publish_qos => <<"coap">> - } - } - , lwm2m_gateway => - #{ summary => <<"A simple LwM2M gateway configs">> - , value => - #{ enable => true - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"lwm2m2/">> - , xml_dir => <<"etc/lwm2m_xml">> - , lifetime_min => <<"1s">> - , lifetime_max => <<"86400s">> - , qmode_time_window => <<"22s">> - , auto_observe => false - , update_msg_publish_condition => <<"always">> - , translators => - #{ command => #{topic => <<"dn/#">>} - , response => #{topic => <<"up/resp">>} - , notify => #{topic => <<"up/notify">>} - , register => #{topic => <<"up/resp">>} - , update => #{topic => <<"up/resp">>} - } - } - } - , exproto_gateway => - #{ summary => <<"A simple ExProto gateway configs">> - , value => - #{ enable => true - , enable_stats => true - , idle_timeout => <<"30s">> - , mountpoint => <<"exproto2/">> - , server => - #{ bind => <<"9100">> - } - , handler => - #{ address => <<"http://127.0.0.1:9001">> - } - } - } - }. + #{ + stomp_gateway => + #{ + summary => <<"A simple STOMP gateway configs">>, + value => + #{ + enable => true, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"stomp2/">>, + frame => + #{ + max_headers => 100, + max_headers_length => 10240, + max_body_length => 655350 + } + } + }, + mqttsn_gateway => + #{ + summary => <<"A simple MQTT-SN gateway configs">>, + value => + #{ + enable => true, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"mqttsn2/">>, + gateway_id => 1, + broadcast => true, + enable_qos3 => false, + predefined => + [ + #{ + id => <<"1003">>, + topic => <<"pred/1003">> + } + ] + } + }, + coap_gateway => + #{ + summary => <<"A simple CoAP gateway configs">>, + value => + #{ + enable => true, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"coap2/">>, + heartbeat => <<"30s">>, + connection_required => false, + notify_type => <<"qos">>, + subscribe_qos => <<"coap">>, + publish_qos => <<"coap">> + } + }, + lwm2m_gateway => + #{ + summary => <<"A simple LwM2M gateway configs">>, + value => + #{ + enable => true, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"lwm2m2/">>, + xml_dir => <<"etc/lwm2m_xml">>, + lifetime_min => <<"1s">>, + lifetime_max => <<"86400s">>, + qmode_time_window => <<"22s">>, + auto_observe => false, + update_msg_publish_condition => <<"always">>, + translators => + #{ + command => #{topic => <<"dn/#">>}, + response => #{topic => <<"up/resp">>}, + notify => #{topic => <<"up/notify">>}, + register => #{topic => <<"up/resp">>}, + update => #{topic => <<"up/resp">>} + } + } + }, + exproto_gateway => + #{ + summary => <<"A simple ExProto gateway configs">>, + value => + #{ + enable => true, + enable_stats => true, + idle_timeout => <<"30s">>, + mountpoint => <<"exproto2/">>, + server => + #{bind => <<"9100">>}, + handler => + #{address => <<"http://127.0.0.1:9001">>} + } + } + }. diff --git a/apps/emqx_gateway/src/emqx_gateway_api_authn.erl b/apps/emqx_gateway/src/emqx_gateway_api_authn.erl index 08f8672c0..a552e3d86 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api_authn.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api_authn.erl @@ -24,25 +24,30 @@ -import(hoconsc, [mk/2, ref/2]). -import(emqx_dashboard_swagger, [error_codes/2]). --import(emqx_gateway_http, - [ return_http_error/2 - , with_gateway/2 - , with_authn/2 - , checks/2 - ]). +-import( + emqx_gateway_http, + [ + return_http_error/2, + with_gateway/2, + with_authn/2, + checks/2 + ] +). %% minirest/dashbaord_swagger behaviour callbacks --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). %% http handlers --export([ authn/2 - , users/2 - , users_insta/2 - , import_users/2 - ]). +-export([ + authn/2, + users/2, + users_insta/2, + import_users/2 +]). %% internal export for emqx_gateway_api_listeners module -export([schema_authn/0]). @@ -55,10 +60,11 @@ api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/gateway/:name/authentication" - , "/gateway/:name/authentication/users" - , "/gateway/:name/authentication/users/:uid" - , "/gateway/:name/authentication/import_users" + [ + "/gateway/:name/authentication", + "/gateway/:name/authentication/users", + "/gateway/:name/authentication/users/:uid", + "/gateway/:name/authentication/import_users" ]. %%-------------------------------------------------------------------- @@ -66,30 +72,29 @@ paths() -> authn(get, #{bindings := #{name := Name0}}) -> with_gateway(Name0, fun(GwName, _) -> - try - emqx_gateway_http:authn(GwName) - of + try emqx_gateway_http:authn(GwName) of Authn -> {200, Authn} catch - error : {config_not_found, _} -> + error:{config_not_found, _} -> {204} end end); - -authn(put, #{bindings := #{name := Name0}, - body := Body}) -> +authn(put, #{ + bindings := #{name := Name0}, + body := Body +}) -> with_gateway(Name0, fun(GwName, _) -> {ok, Authn} = emqx_gateway_http:update_authn(GwName, Body), {200, Authn} end); - -authn(post, #{bindings := #{name := Name0}, - body := Body}) -> +authn(post, #{ + bindings := #{name := Name0}, + body := Body +}) -> with_gateway(Name0, fun(GwName, _) -> {ok, Authn} = emqx_gateway_http:add_authn(GwName, Body), {201, Authn} end); - authn(delete, #{bindings := #{name := Name0}}) -> with_gateway(Name0, fun(GwName, _) -> ok = emqx_gateway_http:remove_authn(GwName), @@ -97,47 +102,85 @@ authn(delete, #{bindings := #{name := Name0}}) -> end). users(get, #{bindings := #{name := Name0}, query_string := Qs}) -> - with_authn(Name0, fun(_GwName, #{id := AuthId, - chain_name := ChainName}) -> + with_authn(Name0, fun( + _GwName, + #{ + id := AuthId, + chain_name := ChainName + } + ) -> emqx_authn_api:list_users(ChainName, AuthId, parse_qstring(Qs)) end); -users(post, #{bindings := #{name := Name0}, - body := Body}) -> - with_authn(Name0, fun(_GwName, #{id := AuthId, - chain_name := ChainName}) -> +users(post, #{ + bindings := #{name := Name0}, + body := Body +}) -> + with_authn(Name0, fun( + _GwName, + #{ + id := AuthId, + chain_name := ChainName + } + ) -> emqx_authn_api:add_user(ChainName, AuthId, Body) end). users_insta(get, #{bindings := #{name := Name0, uid := UserId}}) -> - with_authn(Name0, fun(_GwName, #{id := AuthId, - chain_name := ChainName}) -> + with_authn(Name0, fun( + _GwName, + #{ + id := AuthId, + chain_name := ChainName + } + ) -> emqx_authn_api:find_user(ChainName, AuthId, UserId) end); -users_insta(put, #{bindings := #{name := Name0, uid := UserId}, - body := Body}) -> - with_authn(Name0, fun(_GwName, #{id := AuthId, - chain_name := ChainName}) -> +users_insta(put, #{ + bindings := #{name := Name0, uid := UserId}, + body := Body +}) -> + with_authn(Name0, fun( + _GwName, + #{ + id := AuthId, + chain_name := ChainName + } + ) -> emqx_authn_api:update_user(ChainName, AuthId, UserId, Body) end); users_insta(delete, #{bindings := #{name := Name0, uid := UserId}}) -> - with_authn(Name0, fun(_GwName, #{id := AuthId, - chain_name := ChainName}) -> + with_authn(Name0, fun( + _GwName, + #{ + id := AuthId, + chain_name := ChainName + } + ) -> emqx_authn_api:delete_user(ChainName, AuthId, UserId) end). -import_users(post, #{bindings := #{name := Name0}, - body := Body}) -> - with_authn(Name0, fun(_GwName, #{id := AuthId, - chain_name := ChainName}) -> +import_users(post, #{ + bindings := #{name := Name0}, + body := Body +}) -> + with_authn(Name0, fun( + _GwName, + #{ + id := AuthId, + chain_name := ChainName + } + ) -> case maps:get(<<"filename">>, Body, undefined) of undefined -> emqx_authn_api:serialize_error({missing_parameter, filename}); Filename -> - case emqx_authentication:import_users( - ChainName, AuthId, Filename) of + case + emqx_authentication:import_users( + ChainName, AuthId, Filename + ) + of ok -> {204}; - {error, Reason} -> - emqx_authn_api:serialize_error(Reason) + {error, Reason} -> emqx_authn_api:serialize_error(Reason) end end end). @@ -146,178 +189,243 @@ import_users(post, #{bindings := #{name := Name0}, %% Utils parse_qstring(Qs) -> - maps:with([ <<"page">> - , <<"limit">> - , <<"like_username">> - , <<"like_clientid">>], Qs). + maps:with( + [ + <<"page">>, + <<"limit">>, + <<"like_username">>, + <<"like_clientid">> + ], + Qs + ). %%-------------------------------------------------------------------- %% Swagger defines %%-------------------------------------------------------------------- - schema("/gateway/:name/authentication") -> - #{ 'operationId' => authn, - get => - #{ desc => <<"Get the gateway authentication">> - , parameters => params_gateway_name_in_path() - , responses => - ?STANDARD_RESP( - #{ 200 => schema_authn() - , 204 => <<"Authentication does not initiated">> - }) - }, - put => - #{ desc => <<"Update authentication for the gateway">> - , parameters => params_gateway_name_in_path() - , 'requestBody' => schema_authn() - , responses => - ?STANDARD_RESP(#{200 => schema_authn()}) - }, - post => - #{ desc => <<"Add authentication for the gateway">> - , parameters => params_gateway_name_in_path() - , 'requestBody' => schema_authn() - , responses => - ?STANDARD_RESP(#{201 => schema_authn()}) - }, - delete => - #{ desc => <<"Remove the gateway authentication">> - , parameters => params_gateway_name_in_path() - , responses => - ?STANDARD_RESP(#{204 => <<"Deleted">>}) - } - }; -schema("/gateway/:name/authentication/users") -> - #{ 'operationId' => users - , get => - #{ desc => <<"Get the users for the authentication">> - , parameters => params_gateway_name_in_path() ++ - params_paging_in_qs() ++ - params_fuzzy_in_qs() - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_users), - emqx_authn_api:response_users_example()) - }) - }, - post => - #{ desc => <<"Add user for the authentication">> - , parameters => params_gateway_name_in_path() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(emqx_authn_api, request_user_create), - emqx_authn_api:request_user_create_examples()) - , responses => - ?STANDARD_RESP( - #{ 201 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_user), - emqx_authn_api:response_user_examples()) - }) - } - }; -schema("/gateway/:name/authentication/users/:uid") -> - #{ 'operationId' => users_insta - , get => - #{ desc => <<"Get user info from the gateway " - "authentication">> - , parameters => params_gateway_name_in_path() ++ - params_userid_in_path() - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_user), - emqx_authn_api:response_user_examples()) - }) - }, + #{ + 'operationId' => authn, + get => + #{ + desc => <<"Get the gateway authentication">>, + parameters => params_gateway_name_in_path(), + responses => + ?STANDARD_RESP( + #{ + 200 => schema_authn(), + 204 => <<"Authentication does not initiated">> + } + ) + }, put => - #{ desc => <<"Update the user info for the gateway " - "authentication">> - , parameters => params_gateway_name_in_path() ++ - params_userid_in_path() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(emqx_authn_api, request_user_update), - emqx_authn_api:request_user_update_examples()) - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_user), - emqx_authn_api:response_user_examples()) - }) - }, + #{ + desc => <<"Update authentication for the gateway">>, + parameters => params_gateway_name_in_path(), + 'requestBody' => schema_authn(), + responses => + ?STANDARD_RESP(#{200 => schema_authn()}) + }, + post => + #{ + desc => <<"Add authentication for the gateway">>, + parameters => params_gateway_name_in_path(), + 'requestBody' => schema_authn(), + responses => + ?STANDARD_RESP(#{201 => schema_authn()}) + }, delete => - #{ desc => <<"Delete the user for the gateway " - "authentication">> - , parameters => params_gateway_name_in_path() ++ - params_userid_in_path() - , responses => - ?STANDARD_RESP(#{204 => <<"User Deleted">>}) - } - }; -schema("/gateway/:name/authentication/import_users") -> - #{ 'operationId' => import_users - , post => - #{ desc => <<"Import users into the gateway authentication">> - , parameters => params_gateway_name_in_path() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(emqx_authn_api, request_import_users), - emqx_authn_api:request_import_users_examples() + #{ + desc => <<"Remove the gateway authentication">>, + parameters => params_gateway_name_in_path(), + responses => + ?STANDARD_RESP(#{204 => <<"Deleted">>}) + } + }; +schema("/gateway/:name/authentication/users") -> + #{ + 'operationId' => users, + get => + #{ + desc => <<"Get the users for the authentication">>, + parameters => params_gateway_name_in_path() ++ + params_paging_in_qs() ++ + params_fuzzy_in_qs(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_users), + emqx_authn_api:response_users_example() ) - , responses => - ?STANDARD_RESP(#{204 => <<"Imported">>}) - } - }. + } + ) + }, + post => + #{ + desc => <<"Add user for the authentication">>, + parameters => params_gateway_name_in_path(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(emqx_authn_api, request_user_create), + emqx_authn_api:request_user_create_examples() + ), + responses => + ?STANDARD_RESP( + #{ + 201 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples() + ) + } + ) + } + }; +schema("/gateway/:name/authentication/users/:uid") -> + #{ + 'operationId' => users_insta, + get => + #{ + desc => << + "Get user info from the gateway " + "authentication" + >>, + parameters => params_gateway_name_in_path() ++ + params_userid_in_path(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples() + ) + } + ) + }, + put => + #{ + desc => << + "Update the user info for the gateway " + "authentication" + >>, + parameters => params_gateway_name_in_path() ++ + params_userid_in_path(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(emqx_authn_api, request_user_update), + emqx_authn_api:request_user_update_examples() + ), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples() + ) + } + ) + }, + delete => + #{ + desc => << + "Delete the user for the gateway " + "authentication" + >>, + parameters => params_gateway_name_in_path() ++ + params_userid_in_path(), + responses => + ?STANDARD_RESP(#{204 => <<"User Deleted">>}) + } + }; +schema("/gateway/:name/authentication/import_users") -> + #{ + 'operationId' => import_users, + post => + #{ + desc => <<"Import users into the gateway authentication">>, + parameters => params_gateway_name_in_path(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(emqx_authn_api, request_import_users), + emqx_authn_api:request_import_users_examples() + ), + responses => + ?STANDARD_RESP(#{204 => <<"Imported">>}) + } + }. %%-------------------------------------------------------------------- %% params defines params_gateway_name_in_path() -> - [{name, - mk(binary(), - #{ in => path - , desc => <<"Gateway Name">> - , example => <<"">> - })} + [ + {name, + mk( + binary(), + #{ + in => path, + desc => <<"Gateway Name">>, + example => <<"">> + } + )} ]. params_userid_in_path() -> - [{uid, mk(binary(), - #{ in => path - , desc => <<"User ID">> - , example => <<"">> - })} + [ + {uid, + mk( + binary(), + #{ + in => path, + desc => <<"User ID">>, + example => <<"">> + } + )} ]. params_paging_in_qs() -> - [{page, mk(integer(), - #{ in => query - , required => false - , desc => <<"Page Index">> - , example => 1 - })}, - {limit, mk(integer(), - #{ in => query - , required => false - , desc => <<"Page Limit">> - , example => 100 - })} + [ + {page, + mk( + integer(), + #{ + in => query, + required => false, + desc => <<"Page Index">>, + example => 1 + } + )}, + {limit, + mk( + integer(), + #{ + in => query, + required => false, + desc => <<"Page Limit">>, + example => 100 + } + )} ]. params_fuzzy_in_qs() -> - [{like_username, - mk(binary(), - #{ in => query - , required => false - , desc => <<"Fuzzy search by username">> - , example => <<"username">> - })}, - {like_clientid, - mk(binary(), - #{ in => query - , required => false - , desc => <<"Fuzzy search by clientid">> - , example => <<"clientid">> - })} + [ + {like_username, + mk( + binary(), + #{ + in => query, + required => false, + desc => <<"Fuzzy search by username">>, + example => <<"username">> + } + )}, + {like_clientid, + mk( + binary(), + #{ + in => query, + required => false, + desc => <<"Fuzzy search by clientid">>, + example => <<"clientid">> + } + )} ]. %%-------------------------------------------------------------------- @@ -325,6 +433,6 @@ params_fuzzy_in_qs() -> schema_authn() -> emqx_dashboard_swagger:schema_with_examples( - emqx_authn_schema:authenticator_type(), - emqx_authn_api:authenticator_examples() - ). + emqx_authn_schema:authenticator_type(), + emqx_authn_api:authenticator_examples() + ). diff --git a/apps/emqx_gateway/src/emqx_gateway_api_clients.erl b/apps/emqx_gateway/src/emqx_gateway_api_clients.erl index 8cce43979..29953cc38 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api_clients.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api_clients.erl @@ -26,31 +26,38 @@ -import(hoconsc, [mk/2, ref/1, ref/2]). --import(emqx_gateway_http, - [ return_http_error/2 - , with_gateway/2 - ]). +-import( + emqx_gateway_http, + [ + return_http_error/2, + with_gateway/2 + ] +). %% minirest/dashbaord_swagger behaviour callbacks --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). --export([ roots/0 - , fields/1 - ]). +-export([ + roots/0, + fields/1 +]). %% http handlers --export([ clients/2 - , clients_insta/2 - , subscriptions/2 - ]). +-export([ + clients/2, + clients_insta/2, + subscriptions/2 +]). %% internal exports (for client query) --export([ query/4 - , format_channel_info/1 - ]). +-export([ + query/4, + format_channel_info/1 +]). %%-------------------------------------------------------------------- %% APIs @@ -60,78 +67,98 @@ api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true, translate_body => true}). paths() -> - [ "/gateway/:name/clients" - , "/gateway/:name/clients/:clientid" - , "/gateway/:name/clients/:clientid/subscriptions" - , "/gateway/:name/clients/:clientid/subscriptions/:topic" + [ + "/gateway/:name/clients", + "/gateway/:name/clients/:clientid", + "/gateway/:name/clients/:clientid/subscriptions", + "/gateway/:name/clients/:clientid/subscriptions/:topic" ]. --define(CLIENT_QSCHEMA, - [ {<<"node">>, atom} - , {<<"clientid">>, binary} - , {<<"username">>, binary} - , {<<"ip_address">>, ip} - , {<<"conn_state">>, atom} - , {<<"clean_start">>, atom} - , {<<"proto_ver">>, binary} - , {<<"like_clientid">>, binary} - , {<<"like_username">>, binary} - , {<<"gte_created_at">>, timestamp} - , {<<"lte_created_at">>, timestamp} - , {<<"gte_connected_at">>, timestamp} - , {<<"lte_connected_at">>, timestamp} +-define(CLIENT_QSCHEMA, [ + {<<"node">>, atom}, + {<<"clientid">>, binary}, + {<<"username">>, binary}, + {<<"ip_address">>, ip}, + {<<"conn_state">>, atom}, + {<<"clean_start">>, atom}, + {<<"proto_ver">>, binary}, + {<<"like_clientid">>, binary}, + {<<"like_username">>, binary}, + {<<"gte_created_at">>, timestamp}, + {<<"lte_created_at">>, timestamp}, + {<<"gte_connected_at">>, timestamp}, + {<<"lte_connected_at">>, timestamp}, %% special keys for lwm2m protocol - , {<<"endpoint_name">>, binary} - , {<<"like_endpoint_name">>, binary} - , {<<"gte_lifetime">>, integer} - , {<<"lte_lifetime">>, integer} - ]). + {<<"endpoint_name">>, binary}, + {<<"like_endpoint_name">>, binary}, + {<<"gte_lifetime">>, integer}, + {<<"lte_lifetime">>, integer} +]). -define(QUERY_FUN, {?MODULE, query}). -clients(get, #{ bindings := #{name := Name0} - , query_string := QString - }) -> +clients(get, #{ + bindings := #{name := Name0}, + query_string := QString +}) -> with_gateway(Name0, fun(GwName, _) -> TabName = emqx_gateway_cm:tabname(info, GwName), case maps:get(<<"node">>, QString, undefined) of undefined -> Response = emqx_mgmt_api:cluster_query( - QString, TabName, - ?CLIENT_QSCHEMA, ?QUERY_FUN), + QString, + TabName, + ?CLIENT_QSCHEMA, + ?QUERY_FUN + ), emqx_mgmt_util:generate_response(Response); Node1 -> Node = binary_to_atom(Node1, utf8), QStringWithoutNode = maps:without([<<"node">>], QString), Response = emqx_mgmt_api:node_query( - Node, QStringWithoutNode, - TabName, ?CLIENT_QSCHEMA, ?QUERY_FUN), + Node, + QStringWithoutNode, + TabName, + ?CLIENT_QSCHEMA, + ?QUERY_FUN + ), emqx_mgmt_util:generate_response(Response) end end). -clients_insta(get, #{ bindings := #{name := Name0, - clientid := ClientId0} - }) -> +clients_insta(get, #{ + bindings := #{ + name := Name0, + clientid := ClientId0 + } +}) -> ClientId = emqx_mgmt_util:urldecode(ClientId0), with_gateway(Name0, fun(GwName, _) -> - case emqx_gateway_http:lookup_client( - GwName, ClientId, - {?MODULE, format_channel_info}) of + case + emqx_gateway_http:lookup_client( + GwName, + ClientId, + {?MODULE, format_channel_info} + ) + of [ClientInfo] -> {200, ClientInfo}; [ClientInfo | _More] -> - ?SLOG(warning, #{ msg => "more_than_one_channel_found" - , clientid => ClientId - }), + ?SLOG(warning, #{ + msg => "more_than_one_channel_found", + clientid => ClientId + }), {200, ClientInfo}; [] -> return_http_error(404, "Client not found") end end); -clients_insta(delete, #{ bindings := #{name := Name0, - clientid := ClientId0} - }) -> +clients_insta(delete, #{ + bindings := #{ + name := Name0, + clientid := ClientId0 + } +}) -> ClientId = emqx_mgmt_util:urldecode(ClientId0), with_gateway(Name0, fun(GwName, _) -> _ = emqx_gateway_http:kickout_client(GwName, ClientId), @@ -141,9 +168,12 @@ clients_insta(delete, #{ bindings := #{name := Name0, %% FIXME: %% List the subscription without mountpoint, but has SubOpts, %% for example, share group ... -subscriptions(get, #{ bindings := #{name := Name0, - clientid := ClientId0} - }) -> +subscriptions(get, #{ + bindings := #{ + name := Name0, + clientid := ClientId0 + } +}) -> ClientId = emqx_mgmt_util:urldecode(ClientId0), with_gateway(Name0, fun(GwName, _) -> case emqx_gateway_http:list_client_subscriptions(GwName, ClientId) of @@ -153,34 +183,40 @@ subscriptions(get, #{ bindings := #{name := Name0, {200, Subs} end end); - %% Create the subscription without mountpoint -subscriptions(post, #{ bindings := #{name := Name0, - clientid := ClientId0}, - body := Body - }) -> +subscriptions(post, #{ + bindings := #{ + name := Name0, + clientid := ClientId0 + }, + body := Body +}) -> ClientId = emqx_mgmt_util:urldecode(ClientId0), with_gateway(Name0, fun(GwName, _) -> case {maps:get(<<"topic">>, Body, undefined), subopts(Body)} of {undefined, _} -> return_http_error(400, "Miss topic property"); {Topic, SubOpts} -> - case emqx_gateway_http:client_subscribe( - GwName, ClientId, Topic, SubOpts) of + case + emqx_gateway_http:client_subscribe( + GwName, ClientId, Topic, SubOpts + ) + of {error, Reason} -> return_http_error(404, Reason); - {ok, {NTopic, NSubOpts}}-> + {ok, {NTopic, NSubOpts}} -> {201, maps:merge(NSubOpts, #{topic => NTopic})} end end end); - %% Remove the subscription without mountpoint -subscriptions(delete, #{ bindings := #{name := Name0, - clientid := ClientId0, - topic := Topic0 - } - }) -> +subscriptions(delete, #{ + bindings := #{ + name := Name0, + clientid := ClientId0, + topic := Topic0 + } +}) -> ClientId = emqx_mgmt_util:urldecode(ClientId0), Topic = emqx_mgmt_util:urldecode(Topic0), with_gateway(Name0, fun(GwName, _) -> @@ -192,11 +228,12 @@ subscriptions(delete, #{ bindings := #{name := Name0, %% Utils subopts(Req) -> - SubOpts = #{ qos => maps:get(<<"qos">>, Req, 0) - , rap => maps:get(<<"rap">>, Req, 0) - , nl => maps:get(<<"nl">>, Req, 0) - , rh => maps:get(<<"rh">>, Req, 1) - }, + SubOpts = #{ + qos => maps:get(<<"qos">>, Req, 0), + rap => maps:get(<<"rap">>, Req, 0), + nl => maps:get(<<"nl">>, Req, 0), + rh => maps:get(<<"rh">>, Req, 1) + }, SubProps = extra_sub_props(maps:get(<<"sub_props">>, Req, #{})), case maps:size(SubProps) of 0 -> SubOpts; @@ -205,24 +242,32 @@ subopts(Req) -> extra_sub_props(Props) -> maps:filter( - fun(_, V) -> V =/= undefined end, - #{subid => maps:get(<<"subid">>, Props, undefined)} - ). + fun(_, V) -> V =/= undefined end, + #{subid => maps:get(<<"subid">>, Props, undefined)} + ). %%-------------------------------------------------------------------- %% query funcs query(Tab, {Qs, []}, Continuation, Limit) -> Ms = qs2ms(Qs), - emqx_mgmt_api:select_table_with_count(Tab, Ms, Continuation, Limit, - fun format_channel_info/1); - + emqx_mgmt_api:select_table_with_count( + Tab, + Ms, + Continuation, + Limit, + fun format_channel_info/1 + ); query(Tab, {Qs, Fuzzy}, Continuation, Limit) -> Ms = qs2ms(Qs), FuzzyFilterFun = fuzzy_filter_fun(Fuzzy), emqx_mgmt_api:select_table_with_count( - Tab, {Ms, FuzzyFilterFun}, Continuation, Limit, - fun format_channel_info/1). + Tab, + {Ms, FuzzyFilterFun}, + Continuation, + Limit, + fun format_channel_info/1 + ). qs2ms(Qs) -> {MtchHead, Conds} = qs2ms(Qs, 2, {#{}, []}), @@ -230,23 +275,27 @@ qs2ms(Qs) -> qs2ms([], _, {MtchHead, Conds}) -> {MtchHead, lists:reverse(Conds)}; - qs2ms([{Key, '=:=', Value} | Rest], N, {MtchHead, Conds}) -> NMtchHead = emqx_mgmt_util:merge_maps(MtchHead, ms(Key, Value)), qs2ms(Rest, N, {NMtchHead, Conds}); qs2ms([Qs | Rest], N, {MtchHead, Conds}) -> Holder = binary_to_atom( - iolist_to_binary(["$", integer_to_list(N)]), utf8), + iolist_to_binary(["$", integer_to_list(N)]), utf8 + ), NMtchHead = emqx_mgmt_util:merge_maps( - MtchHead, ms(element(1, Qs), Holder)), + MtchHead, ms(element(1, Qs), Holder) + ), NConds = put_conds(Qs, Holder, Conds), - qs2ms(Rest, N+1, {NMtchHead, NConds}). + qs2ms(Rest, N + 1, {NMtchHead, NConds}). put_conds({_, Op, V}, Holder, Conds) -> [{Op, Holder, V} | Conds]; put_conds({_, Op1, V1, Op2, V2}, Holder, Conds) -> - [{Op2, Holder, V2}, - {Op1, Holder, V1} | Conds]. + [ + {Op2, Holder, V2}, + {Op1, Holder, V1} + | Conds + ]. ms(clientid, X) -> #{clientinfo => #{clientid => X}}; @@ -275,18 +324,23 @@ ms(lifetime, X) -> fuzzy_filter_fun(Fuzzy) -> fun(MsRaws) when is_list(MsRaws) -> - lists:filter( fun(E) -> run_fuzzy_filter(E, Fuzzy) end - , MsRaws) + lists:filter( + fun(E) -> run_fuzzy_filter(E, Fuzzy) end, + MsRaws + ) end. run_fuzzy_filter(_, []) -> true; -run_fuzzy_filter(E = {_, #{clientinfo := ClientInfo}, _}, - [{Key, like, SubStr} | Fuzzy]) -> - Val = case maps:get(Key, ClientInfo, <<>>) of - undefined -> <<>>; - V -> V - end, +run_fuzzy_filter( + E = {_, #{clientinfo := ClientInfo}, _}, + [{Key, like, SubStr} | Fuzzy] +) -> + Val = + case maps:get(Key, ClientInfo, <<>>) of + undefined -> <<>>; + V -> V + end, binary:match(Val, SubStr) /= nomatch andalso run_fuzzy_filter(E, Fuzzy). %%-------------------------------------------------------------------- @@ -297,60 +351,56 @@ format_channel_info({_, Infos, Stats} = R) -> ClientInfo = maps:get(clientinfo, Infos, #{}), ConnInfo = maps:get(conninfo, Infos, #{}), SessInfo = maps:get(session, Infos, #{}), - FetchX = [ {node, ClientInfo, Node} - , {clientid, ClientInfo} - , {username, ClientInfo} - , {proto_name, ConnInfo} - , {proto_ver, ConnInfo} - , {ip_address, {peername, ConnInfo, fun peer_to_binary_addr/1}} - , {port, {peername, ConnInfo, fun peer_to_port/1}} - , {is_bridge, ClientInfo, false} - , {connected_at, - {connected_at, ConnInfo, - fun emqx_gateway_utils:unix_ts_to_rfc3339/1}} - , {disconnected_at, - {disconnected_at, ConnInfo, - fun emqx_gateway_utils:unix_ts_to_rfc3339/1}} - , {connected, {conn_state, Infos, - fun conn_state_to_connected/1}} - , {keepalive, ClientInfo, 0} - , {clean_start, ConnInfo, true} - , {expiry_interval, ConnInfo, 0} - , {created_at, - {created_at, SessInfo, - fun emqx_gateway_utils:unix_ts_to_rfc3339/1}} - , {subscriptions_cnt, Stats, 0} - , {subscriptions_max, Stats, infinity} - , {inflight_cnt, Stats, 0} - , {inflight_max, Stats, infinity} - , {mqueue_len, Stats, 0} - , {mqueue_max, Stats, infinity} - , {mqueue_dropped, Stats, 0} - , {awaiting_rel_cnt, Stats, 0} - , {awaiting_rel_max, Stats, infinity} - , {recv_oct, Stats, 0} - , {recv_cnt, Stats, 0} - , {recv_pkt, Stats, 0} - , {recv_msg, Stats, 0} - , {send_oct, Stats, 0} - , {send_cnt, Stats, 0} - , {send_pkt, Stats, 0} - , {send_msg, Stats, 0} - , {mailbox_len, Stats, 0} - , {heap_size, Stats, 0} - , {reductions, Stats, 0} - ], + FetchX = [ + {node, ClientInfo, Node}, + {clientid, ClientInfo}, + {username, ClientInfo}, + {proto_name, ConnInfo}, + {proto_ver, ConnInfo}, + {ip_address, {peername, ConnInfo, fun peer_to_binary_addr/1}}, + {port, {peername, ConnInfo, fun peer_to_port/1}}, + {is_bridge, ClientInfo, false}, + {connected_at, {connected_at, ConnInfo, fun emqx_gateway_utils:unix_ts_to_rfc3339/1}}, + {disconnected_at, {disconnected_at, ConnInfo, fun emqx_gateway_utils:unix_ts_to_rfc3339/1}}, + {connected, {conn_state, Infos, fun conn_state_to_connected/1}}, + {keepalive, ClientInfo, 0}, + {clean_start, ConnInfo, true}, + {expiry_interval, ConnInfo, 0}, + {created_at, {created_at, SessInfo, fun emqx_gateway_utils:unix_ts_to_rfc3339/1}}, + {subscriptions_cnt, Stats, 0}, + {subscriptions_max, Stats, infinity}, + {inflight_cnt, Stats, 0}, + {inflight_max, Stats, infinity}, + {mqueue_len, Stats, 0}, + {mqueue_max, Stats, infinity}, + {mqueue_dropped, Stats, 0}, + {awaiting_rel_cnt, Stats, 0}, + {awaiting_rel_max, Stats, infinity}, + {recv_oct, Stats, 0}, + {recv_cnt, Stats, 0}, + {recv_pkt, Stats, 0}, + {recv_msg, Stats, 0}, + {send_oct, Stats, 0}, + {send_cnt, Stats, 0}, + {send_pkt, Stats, 0}, + {send_msg, Stats, 0}, + {mailbox_len, Stats, 0}, + {heap_size, Stats, 0}, + {reductions, Stats, 0} + ], eval(FetchX ++ extra_feilds(R)). extra_feilds({_, Infos, _Stats} = R) -> extra_feilds( - maps:get(protocol, maps:get(clientinfo, Infos)), - R). + maps:get(protocol, maps:get(clientinfo, Infos)), + R + ). extra_feilds(lwm2m, {_, Infos, _Stats}) -> ClientInfo = maps:get(clientinfo, Infos, #{}), - [ {endpoint_name, ClientInfo} - , {lifetime, ClientInfo} + [ + {endpoint_name, ClientInfo}, + {lifetime, ClientInfo} ]; extra_feilds(_, _) -> []. @@ -385,11 +435,11 @@ key_get(K, M) when is_map(M) -> key_get(K, L) when is_list(L) -> proplists:get_value(K, L). --spec(peer_to_binary_addr(emqx_types:peername()) -> binary()). +-spec peer_to_binary_addr(emqx_types:peername()) -> binary(). peer_to_binary_addr({Addr, _}) -> list_to_binary(inet:ntoa(Addr)). --spec(peer_to_port(emqx_types:peername()) -> inet:port_number()). +-spec peer_to_port(emqx_types:peername()) -> inet:port_number(). peer_to_port({_, Port}) -> Port. @@ -401,172 +451,269 @@ conn_state_to_connected(_) -> false. %%-------------------------------------------------------------------- schema("/gateway/:name/clients") -> - #{ 'operationId' => clients - , get => - #{ desc => <<"Get the gateway client list">> - , parameters => params_client_query() - , responses => - ?STANDARD_RESP(#{200 => schema_client_list()}) - } - }; + #{ + 'operationId' => clients, + get => + #{ + desc => <<"Get the gateway client list">>, + parameters => params_client_query(), + responses => + ?STANDARD_RESP(#{200 => schema_client_list()}) + } + }; schema("/gateway/:name/clients/:clientid") -> - #{ 'operationId' => clients_insta - , get => - #{ desc => <<"Get the gateway client information">> - , parameters => params_client_insta() - , responses => - ?STANDARD_RESP(#{200 => schema_client()}) - } - , delete => - #{ desc => <<"Kick out the gateway client">> - , parameters => params_client_insta() - , responses => - ?STANDARD_RESP(#{204 => <<"Kicked">>}) - } - }; + #{ + 'operationId' => clients_insta, + get => + #{ + desc => <<"Get the gateway client information">>, + parameters => params_client_insta(), + responses => + ?STANDARD_RESP(#{200 => schema_client()}) + }, + delete => + #{ + desc => <<"Kick out the gateway client">>, + parameters => params_client_insta(), + responses => + ?STANDARD_RESP(#{204 => <<"Kicked">>}) + } + }; schema("/gateway/:name/clients/:clientid/subscriptions") -> - #{ 'operationId' => subscriptions - , get => - #{ desc => <<"Get the gateway client subscriptions">> - , parameters => params_client_insta() - , responses => - ?STANDARD_RESP( - #{200 => emqx_dashboard_swagger:schema_with_examples( - hoconsc:array(ref(subscription)), - examples_subsctiption_list())}) - } - , post => - #{ desc => <<"Create a subscription membership">> - , parameters => params_client_insta() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(subscription), - examples_subsctiption()) - , responses => - ?STANDARD_RESP( - #{ 201 => emqx_dashboard_swagger:schema_with_examples( - ref(subscription), - examples_subsctiption())}) - } - }; + #{ + 'operationId' => subscriptions, + get => + #{ + desc => <<"Get the gateway client subscriptions">>, + parameters => params_client_insta(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_examples( + hoconsc:array(ref(subscription)), + examples_subsctiption_list() + ) + } + ) + }, + post => + #{ + desc => <<"Create a subscription membership">>, + parameters => params_client_insta(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(subscription), + examples_subsctiption() + ), + responses => + ?STANDARD_RESP( + #{ + 201 => emqx_dashboard_swagger:schema_with_examples( + ref(subscription), + examples_subsctiption() + ) + } + ) + } + }; schema("/gateway/:name/clients/:clientid/subscriptions/:topic") -> - #{ 'operationId' => subscriptions - , delete => - #{ desc => <<"Delete a subscriptions membership">> - , parameters => params_topic_name_in_path() ++ params_client_insta() - , responses => - ?STANDARD_RESP(#{204 => <<"Unsubscribed">>}) - } - }. + #{ + 'operationId' => subscriptions, + delete => + #{ + desc => <<"Delete a subscriptions membership">>, + parameters => params_topic_name_in_path() ++ params_client_insta(), + responses => + ?STANDARD_RESP(#{204 => <<"Unsubscribed">>}) + } + }. params_client_query() -> - params_gateway_name_in_path() - ++ params_client_searching_in_qs() - ++ params_paging(). + params_gateway_name_in_path() ++ + params_client_searching_in_qs() ++ + params_paging(). params_client_insta() -> - params_clientid_in_path() - ++ params_gateway_name_in_path(). + params_clientid_in_path() ++ + params_gateway_name_in_path(). params_client_searching_in_qs() -> M = #{in => query, required => false, example => <<"">>}, - [ {node, - mk(binary(), - M#{desc => <<"Match the client's node name">>})} - , {clientid, - mk(binary(), - M#{desc => <<"Match the client's ID">>})} - , {username, - mk(binary(), - M#{desc => <<"Match the client's Username">>})} - , {ip_address, - mk(binary(), - M#{desc => <<"Match the client's ip address">>})} - , {conn_state, - mk(binary(), - M#{desc => <<"Match the client's connection state">>})} - , {proto_ver, - mk(binary(), - M#{desc => <<"Match the client's protocol version">>})} - , {clean_start, - mk(boolean(), - M#{desc => <<"Match the client's clean start flag">>})} - , {like_clientid, - mk(binary(), - M#{desc => <<"Use sub-string to match client's ID">>})} - , {like_username, - mk(binary(), - M#{desc => <<"Use sub-string to match client's username">>})} - , {gte_created_at, - mk(emqx_datetime:epoch_millisecond(), - M#{desc => <<"Match the session created datetime greater than " - "a certain value">>})} - , {lte_created_at, - mk(emqx_datetime:epoch_millisecond(), - M#{desc => <<"Match the session created datetime less than " - "a certain value">>})} - , {gte_connected_at, - mk(emqx_datetime:epoch_millisecond(), - M#{desc => <<"Match the client socket connected datetime greater " - "than a certain value">>})} - , {lte_connected_at, - mk(emqx_datetime:epoch_millisecond(), - M#{desc => <<"Match the client socket connected datatime less than " - "a certain value">>})} - , {endpoint_name, - mk(binary(), - M#{desc => <<"Match the lwm2m client's endpoint name">>})} - , {like_endpoint_name, - mk(binary(), - M#{desc => <<"Use sub-string to match lwm2m client's endpoint name">>})} - , {gte_lifetime, - mk(binary(), - M#{desc => <<"Match the lwm2m client registered lifetime greater " - "than a certain value">>})} - , {lte_lifetime, - mk(binary(), - M#{desc => <<"Match the lwm2m client registered lifetime less than " - "a certain value">>})} + [ + {node, + mk( + binary(), + M#{desc => <<"Match the client's node name">>} + )}, + {clientid, + mk( + binary(), + M#{desc => <<"Match the client's ID">>} + )}, + {username, + mk( + binary(), + M#{desc => <<"Match the client's Username">>} + )}, + {ip_address, + mk( + binary(), + M#{desc => <<"Match the client's ip address">>} + )}, + {conn_state, + mk( + binary(), + M#{desc => <<"Match the client's connection state">>} + )}, + {proto_ver, + mk( + binary(), + M#{desc => <<"Match the client's protocol version">>} + )}, + {clean_start, + mk( + boolean(), + M#{desc => <<"Match the client's clean start flag">>} + )}, + {like_clientid, + mk( + binary(), + M#{desc => <<"Use sub-string to match client's ID">>} + )}, + {like_username, + mk( + binary(), + M#{desc => <<"Use sub-string to match client's username">>} + )}, + {gte_created_at, + mk( + emqx_datetime:epoch_millisecond(), + M#{ + desc => << + "Match the session created datetime greater than " + "a certain value" + >> + } + )}, + {lte_created_at, + mk( + emqx_datetime:epoch_millisecond(), + M#{ + desc => << + "Match the session created datetime less than " + "a certain value" + >> + } + )}, + {gte_connected_at, + mk( + emqx_datetime:epoch_millisecond(), + M#{ + desc => << + "Match the client socket connected datetime greater " + "than a certain value" + >> + } + )}, + {lte_connected_at, + mk( + emqx_datetime:epoch_millisecond(), + M#{ + desc => << + "Match the client socket connected datatime less than " + "a certain value" + >> + } + )}, + {endpoint_name, + mk( + binary(), + M#{desc => <<"Match the lwm2m client's endpoint name">>} + )}, + {like_endpoint_name, + mk( + binary(), + M#{desc => <<"Use sub-string to match lwm2m client's endpoint name">>} + )}, + {gte_lifetime, + mk( + binary(), + M#{ + desc => << + "Match the lwm2m client registered lifetime greater " + "than a certain value" + >> + } + )}, + {lte_lifetime, + mk( + binary(), + M#{ + desc => << + "Match the lwm2m client registered lifetime less than " + "a certain value" + >> + } + )} ]. params_paging() -> - [ {page, - mk(integer(), - #{ in => query - , required => false - , desc => <<"Page Index">> - , example => 1 - })} - , {limit, - mk(integer(), - #{ in => query - , desc => <<"Page Limit">> - , required => false - , example => 100 - })} + [ + {page, + mk( + integer(), + #{ + in => query, + required => false, + desc => <<"Page Index">>, + example => 1 + } + )}, + {limit, + mk( + integer(), + #{ + in => query, + desc => <<"Page Limit">>, + required => false, + example => 100 + } + )} ]. params_gateway_name_in_path() -> - [{name, - mk(binary(), - #{ in => path - , desc => <<"Gateway Name">> - })} + [ + {name, + mk( + binary(), + #{ + in => path, + desc => <<"Gateway Name">> + } + )} ]. params_clientid_in_path() -> - [{clientid, - mk(binary(), - #{ in => path - , desc => <<"Client ID">> - })} + [ + {clientid, + mk( + binary(), + #{ + in => path, + desc => <<"Client ID">> + } + )} ]. params_topic_name_in_path() -> - [{topic, - mk(binary(), - #{ in => path - , desc => <<"Topic Filter/Name">> - })} + [ + {topic, + mk( + binary(), + #{ + in => path, + desc => <<"Topic Filter/Name">> + } + )} ]. %%-------------------------------------------------------------------- @@ -574,33 +721,36 @@ params_topic_name_in_path() -> schema_client_list() -> emqx_dashboard_swagger:schema_with_examples( - hoconsc:union([hoconsc:array(ref(?MODULE, stomp_client)), - hoconsc:array(ref(?MODULE, mqttsn_client)), - hoconsc:array(ref(?MODULE, coap_client)), - hoconsc:array(ref(?MODULE, lwm2m_client)), - hoconsc:array(ref(?MODULE, exproto_client)) - ]), - examples_client_list() - ). + hoconsc:union([ + hoconsc:array(ref(?MODULE, stomp_client)), + hoconsc:array(ref(?MODULE, mqttsn_client)), + hoconsc:array(ref(?MODULE, coap_client)), + hoconsc:array(ref(?MODULE, lwm2m_client)), + hoconsc:array(ref(?MODULE, exproto_client)) + ]), + examples_client_list() + ). schema_client() -> emqx_dashboard_swagger:schema_with_examples( - hoconsc:union([ref(?MODULE, stomp_client), - ref(?MODULE, mqttsn_client), - ref(?MODULE, coap_client), - ref(?MODULE, lwm2m_client), - ref(?MODULE, exproto_client) - ]), - examples_client() - ). + hoconsc:union([ + ref(?MODULE, stomp_client), + ref(?MODULE, mqttsn_client), + ref(?MODULE, coap_client), + ref(?MODULE, lwm2m_client), + ref(?MODULE, exproto_client) + ]), + examples_client() + ). roots() -> - [ stomp_client - , mqttsn_client - , coap_client - , lwm2m_client - , exproto_client - , subscription + [ + stomp_client, + mqttsn_client, + coap_client, + lwm2m_client, + exproto_client, + subscription ]. fields(stomp_client) -> @@ -610,273 +760,423 @@ fields(mqttsn_client) -> fields(coap_client) -> common_client_props(); fields(lwm2m_client) -> - [ {endpoint_name, - mk(binary(), - #{ desc => <<"The LwM2M client endpoint name">>})} - , {lifetime, - mk(integer(), - #{ desc => <<"Life time">>})} + [ + {endpoint_name, + mk( + binary(), + #{desc => <<"The LwM2M client endpoint name">>} + )}, + {lifetime, + mk( + integer(), + #{desc => <<"Life time">>} + )} ] ++ common_client_props(); fields(exproto_client) -> common_client_props(); - fields(subscription) -> - [ {topic, - mk(binary(), - #{ desc => <<"Topic Fillter">>})} - , {qos, - mk(integer(), - #{ desc => <<"QoS level, enum: 0, 1, 2">>})} - , {nl, - mk(integer(), %% FIXME: why not boolean? - #{ desc => <<"No Local option, enum: 0, 1">>})} - , {rap, - mk(integer(), - #{ desc => <<"Retain as Published option, enum: 0, 1">>})} - , {rh, - mk(integer(), - #{ desc => <<"Retain Handling option, enum: 0, 1, 2">>})} - , {sub_props, - mk(ref(extra_sub_props), - #{desc => <<"Subscription properties">>})} + [ + {topic, + mk( + binary(), + #{desc => <<"Topic Fillter">>} + )}, + {qos, + mk( + integer(), + #{desc => <<"QoS level, enum: 0, 1, 2">>} + )}, + {nl, + %% FIXME: why not boolean? + mk( + integer(), + #{desc => <<"No Local option, enum: 0, 1">>} + )}, + {rap, + mk( + integer(), + #{desc => <<"Retain as Published option, enum: 0, 1">>} + )}, + {rh, + mk( + integer(), + #{desc => <<"Retain Handling option, enum: 0, 1, 2">>} + )}, + {sub_props, + mk( + ref(extra_sub_props), + #{desc => <<"Subscription properties">>} + )} ]; fields(extra_sub_props) -> - [ {subid, - mk(binary(), - #{ desc => <<"Only stomp protocol, a unique identity for " - "the subscription. range: 1-65535.">>})} + [ + {subid, + mk( + binary(), + #{ + desc => << + "Only stomp protocol, a unique identity for " + "the subscription. range: 1-65535." + >> + } + )} ]. common_client_props() -> - [ {node, - mk(binary(), - #{ desc => <<"Name of the node to which the client is " - "connected">>})} - , {clientid, - mk(binary(), - #{ desc => <<"Client identifier">>})} - , {username, - mk(binary(), - #{ desc => <<"Username of client when connecting">>})} - , {proto_name, - mk(binary(), - #{ desc => <<"Client protocol name">>})} - , {proto_ver, - mk(binary(), - #{ desc => <<"Protocol version used by the client">>})} - , {ip_address, - mk(binary(), - #{ desc => <<"Client's IP address">>})} - , {port, - mk(integer(), - #{ desc => <<"Client's port">>})} - , {is_bridge, - mk(boolean(), - #{ desc => <<"Indicates whether the client is connected via " - "bridge">>})} - , {connected_at, - mk(emqx_datetime:epoch_millisecond(), - #{ desc => <<"Client connection time">>})} - , {disconnected_at, - mk(emqx_datetime:epoch_millisecond(), - #{ desc => <<"Client offline time, This field is only valid and " - "returned when connected is false">>})} - , {connected, - mk(boolean(), - #{ desc => <<"Whether the client is connected">>})} - %% FIXME: the will_msg attribute is not a general attribute - %% for every protocol. But it should be returned to frontend if someone - %% want it - %% - %, {will_msg, - % mk(binary(), - % #{ desc => <<"Client will message">>})} - , {keepalive, - mk(integer(), - #{ desc => <<"keepalive time, with the unit of second">>})} - , {clean_start, - mk(boolean(), - #{ desc => <<"Indicate whether the client is using a brand " - "new session">>})} - , {expiry_interval, - mk(integer(), - #{ desc => <<"Session expiration interval, with the unit of " - "second">>})} - , {created_at, - mk(emqx_datetime:epoch_millisecond(), - #{ desc => <<"Session creation time">>})} - , {subscriptions_cnt, - mk(integer(), - #{ desc => <<"Number of subscriptions established by this " - "client">>})} - , {subscriptions_max, - mk(integer(), - #{ desc => <<"Maximum number of subscriptions allowed by this " - "client">>})} - , {inflight_cnt, - mk(integer(), - #{ desc => <<"Current length of inflight">>})} - , {inflight_max, - mk(integer(), - #{ desc => <<"Maximum length of inflight">>})} - , {mqueue_len, - mk(integer(), - #{ desc => <<"Current length of message queue">>})} - , {mqueue_max, - mk(integer(), - #{ desc => <<"Maximum length of message queue">>})} - , {mqueue_dropped, - mk(integer(), - #{ desc => <<"Number of messages dropped by the message queue " - "due to exceeding the length">>})} - , {awaiting_rel_cnt, - mk(integer(), - %% FIXME: PUBREC ?? - #{ desc => <<"Number of awaiting acknowledge packet">>})} - , {awaiting_rel_max, - mk(integer(), - #{ desc => <<"Maximum allowed number of awaiting PUBREC " - "packet">>})} - , {recv_oct, - mk(integer(), - #{ desc => <<"Number of bytes received">>})} - , {recv_cnt, - mk(integer(), - #{ desc => <<"Number of socket packets received">>})} - , {recv_pkt, - mk(integer(), - #{ desc => <<"Number of protocol packets received">>})} - , {recv_msg, - mk(integer(), - #{ desc => <<"Number of message packets received">>})} - , {send_oct, - mk(integer(), - #{ desc => <<"Number of bytes sent">>})} - , {send_cnt, - mk(integer(), - #{ desc => <<"Number of socket packets sent">>})} - , {send_pkt, - mk(integer(), - #{ desc => <<"Number of protocol packets sent">>})} - , {send_msg, - mk(integer(), - #{ desc => <<"Number of message packets sent">>})} - , {mailbox_len, - mk(integer(), - #{ desc => <<"Process mailbox size">>})} - , {heap_size, - mk(integer(), - #{ desc => <<"Process heap size with the unit of byte">>})} - , {reductions, - mk(integer(), - #{ desc => <<"Erlang reduction">>})} + [ + {node, + mk( + binary(), + #{ + desc => << + "Name of the node to which the client is " + "connected" + >> + } + )}, + {clientid, + mk( + binary(), + #{desc => <<"Client identifier">>} + )}, + {username, + mk( + binary(), + #{desc => <<"Username of client when connecting">>} + )}, + {proto_name, + mk( + binary(), + #{desc => <<"Client protocol name">>} + )}, + {proto_ver, + mk( + binary(), + #{desc => <<"Protocol version used by the client">>} + )}, + {ip_address, + mk( + binary(), + #{desc => <<"Client's IP address">>} + )}, + {port, + mk( + integer(), + #{desc => <<"Client's port">>} + )}, + {is_bridge, + mk( + boolean(), + #{ + desc => << + "Indicates whether the client is connected via " + "bridge" + >> + } + )}, + {connected_at, + mk( + emqx_datetime:epoch_millisecond(), + #{desc => <<"Client connection time">>} + )}, + {disconnected_at, + mk( + emqx_datetime:epoch_millisecond(), + #{ + desc => << + "Client offline time, This field is only valid and " + "returned when connected is false" + >> + } + )}, + {connected, + mk( + boolean(), + #{desc => <<"Whether the client is connected">>} + )}, + %% FIXME: the will_msg attribute is not a general attribute + %% for every protocol. But it should be returned to frontend if someone + %% want it + %% + %, {will_msg, + % mk(binary(), + % #{ desc => <<"Client will message">>})} + {keepalive, + mk( + integer(), + #{desc => <<"keepalive time, with the unit of second">>} + )}, + {clean_start, + mk( + boolean(), + #{ + desc => << + "Indicate whether the client is using a brand " + "new session" + >> + } + )}, + {expiry_interval, + mk( + integer(), + #{ + desc => << + "Session expiration interval, with the unit of " + "second" + >> + } + )}, + {created_at, + mk( + emqx_datetime:epoch_millisecond(), + #{desc => <<"Session creation time">>} + )}, + {subscriptions_cnt, + mk( + integer(), + #{ + desc => << + "Number of subscriptions established by this " + "client" + >> + } + )}, + {subscriptions_max, + mk( + integer(), + #{ + desc => << + "Maximum number of subscriptions allowed by this " + "client" + >> + } + )}, + {inflight_cnt, + mk( + integer(), + #{desc => <<"Current length of inflight">>} + )}, + {inflight_max, + mk( + integer(), + #{desc => <<"Maximum length of inflight">>} + )}, + {mqueue_len, + mk( + integer(), + #{desc => <<"Current length of message queue">>} + )}, + {mqueue_max, + mk( + integer(), + #{desc => <<"Maximum length of message queue">>} + )}, + {mqueue_dropped, + mk( + integer(), + #{ + desc => << + "Number of messages dropped by the message queue " + "due to exceeding the length" + >> + } + )}, + {awaiting_rel_cnt, + mk( + integer(), + %% FIXME: PUBREC ?? + #{desc => <<"Number of awaiting acknowledge packet">>} + )}, + {awaiting_rel_max, + mk( + integer(), + #{ + desc => << + "Maximum allowed number of awaiting PUBREC " + "packet" + >> + } + )}, + {recv_oct, + mk( + integer(), + #{desc => <<"Number of bytes received">>} + )}, + {recv_cnt, + mk( + integer(), + #{desc => <<"Number of socket packets received">>} + )}, + {recv_pkt, + mk( + integer(), + #{desc => <<"Number of protocol packets received">>} + )}, + {recv_msg, + mk( + integer(), + #{desc => <<"Number of message packets received">>} + )}, + {send_oct, + mk( + integer(), + #{desc => <<"Number of bytes sent">>} + )}, + {send_cnt, + mk( + integer(), + #{desc => <<"Number of socket packets sent">>} + )}, + {send_pkt, + mk( + integer(), + #{desc => <<"Number of protocol packets sent">>} + )}, + {send_msg, + mk( + integer(), + #{desc => <<"Number of message packets sent">>} + )}, + {mailbox_len, + mk( + integer(), + #{desc => <<"Process mailbox size">>} + )}, + {heap_size, + mk( + integer(), + #{desc => <<"Process heap size with the unit of byte">>} + )}, + {reductions, + mk( + integer(), + #{desc => <<"Erlang reduction">>} + )} ]. %%-------------------------------------------------------------------- %% examples examples_client_list() -> - #{ general_client_list => - #{ summary => <<"General Client List">> - , value => [example_general_client()] - } - , lwm2m_client_list => - #{ summary => <<"LwM2M Client List">> - , value => [example_lwm2m_client()] - } - }. + #{ + general_client_list => + #{ + summary => <<"General Client List">>, + value => [example_general_client()] + }, + lwm2m_client_list => + #{ + summary => <<"LwM2M Client List">>, + value => [example_lwm2m_client()] + } + }. examples_client() -> - #{ general_client => - #{ summary => <<"General Client Info">> - , value => example_general_client() - } - , lwm2m_client => - #{ summary => <<"LwM2M Client Info">> - , value => example_lwm2m_client() - } - }. + #{ + general_client => + #{ + summary => <<"General Client Info">>, + value => example_general_client() + }, + lwm2m_client => + #{ + summary => <<"LwM2M Client Info">>, + value => example_lwm2m_client() + } + }. examples_subsctiption_list() -> - #{ general_subscription_list => - #{ summary => <<"A General Subscription List">> - , value => [example_general_subscription()] - } - , stomp_subscription_list => - #{ summary => <<"The Stomp Subscription List">> - , value => [example_stomp_subscription] - } - }. + #{ + general_subscription_list => + #{ + summary => <<"A General Subscription List">>, + value => [example_general_subscription()] + }, + stomp_subscription_list => + #{ + summary => <<"The Stomp Subscription List">>, + value => [example_stomp_subscription] + } + }. examples_subsctiption() -> - #{ general_subscription => - #{ summary => <<"A General Subscription">> - , value => example_general_subscription() - } - , stomp_subscription => - #{ summary => <<"A Stomp Subscription">> - , value => example_stomp_subscription() - } + #{ + general_subscription => + #{ + summary => <<"A General Subscription">>, + value => example_general_subscription() + }, + stomp_subscription => + #{ + summary => <<"A Stomp Subscription">>, + value => example_stomp_subscription() + } }. example_lwm2m_client() -> maps:merge( - example_general_client(), - #{ proto_name => <<"LwM2M">> - , proto_ver => <<"1.0">> - , endpoint_name => <<"urn:imei:154928475237123">> - , lifetime => 86400 - }). + example_general_client(), + #{ + proto_name => <<"LwM2M">>, + proto_ver => <<"1.0">>, + endpoint_name => <<"urn:imei:154928475237123">>, + lifetime => 86400 + } + ). example_general_client() -> - #{ clientid => <<"MzAyMzEzNTUwNzk1NDA1MzYyMzIwNzUxNjQwMTY1NzQ0NjE">> - , username => <<"guest">> - , node => <<"emqx@127.0.0.1">> - , proto_name => "STOMP" - , proto_ver => <<"1.0">> - , ip_address => <<"127.0.0.1">> - , port => 50675 - , clean_start => true - , connected => true - , is_bridge => false - , keepalive => 0 - , expiry_interval => 0 - , subscriptions_cnt => 0 - , subscriptions_max => <<"infinity">> - , awaiting_rel_cnt => 0 - , awaiting_rel_max => <<"infinity">> - , mqueue_len => 0 - , mqueue_max => <<"infinity">> - , mqueue_dropped => 0 - , inflight_cnt => 0 - , inflight_max => <<"infinity">> - , heap_size => 4185 - , recv_oct => 56 - , recv_cnt => 1 - , recv_pkt => 1 - , recv_msg => 0 - , send_oct => 61 - , send_cnt => 1 - , send_pkt => 1 - , send_msg => 0 - , reductions => 72022 - , mailbox_len => 0 - , created_at => <<"2021-12-07T10:44:02.721+08:00">> - , connected_at => <<"2021-12-07T10:44:02.721+08:00">> - , disconnected_at => null - }. + #{ + clientid => <<"MzAyMzEzNTUwNzk1NDA1MzYyMzIwNzUxNjQwMTY1NzQ0NjE">>, + username => <<"guest">>, + node => <<"emqx@127.0.0.1">>, + proto_name => "STOMP", + proto_ver => <<"1.0">>, + ip_address => <<"127.0.0.1">>, + port => 50675, + clean_start => true, + connected => true, + is_bridge => false, + keepalive => 0, + expiry_interval => 0, + subscriptions_cnt => 0, + subscriptions_max => <<"infinity">>, + awaiting_rel_cnt => 0, + awaiting_rel_max => <<"infinity">>, + mqueue_len => 0, + mqueue_max => <<"infinity">>, + mqueue_dropped => 0, + inflight_cnt => 0, + inflight_max => <<"infinity">>, + heap_size => 4185, + recv_oct => 56, + recv_cnt => 1, + recv_pkt => 1, + recv_msg => 0, + send_oct => 61, + send_cnt => 1, + send_pkt => 1, + send_msg => 0, + reductions => 72022, + mailbox_len => 0, + created_at => <<"2021-12-07T10:44:02.721+08:00">>, + connected_at => <<"2021-12-07T10:44:02.721+08:00">>, + disconnected_at => null + }. example_stomp_subscription() -> maps:merge( - example_general_subscription(), - #{ topic => <<"stomp/topic">> - , sub_props => #{subid => <<"10">>} - }). + example_general_subscription(), + #{ + topic => <<"stomp/topic">>, + sub_props => #{subid => <<"10">>} + } + ). example_general_subscription() -> - #{ topic => <<"test/topic">> - , qos => 1 - , nl => 0 - , rap => 0 - , rh => 0 - }. + #{ + topic => <<"test/topic">>, + qos => 1, + nl => 0, + rap => 0, + rh => 0 + }. diff --git a/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl b/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl index ef1b031d0..d5f2c9f20 100644 --- a/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl +++ b/apps/emqx_gateway/src/emqx_gateway_api_listeners.erl @@ -23,33 +23,39 @@ -import(hoconsc, [mk/2, ref/1, ref/2]). --import(emqx_gateway_http, - [ return_http_error/2 - , with_gateway/2 - , with_listener_authn/3 - , checks/2 - ]). +-import( + emqx_gateway_http, + [ + return_http_error/2, + with_gateway/2, + with_listener_authn/3, + checks/2 + ] +). -import(emqx_gateway_api_authn, [schema_authn/0]). %% minirest/dashboard_swagger behaviour callbacks --export([ api_spec/0 - , paths/0 - , schema/1 - ]). +-export([ + api_spec/0, + paths/0, + schema/1 +]). --export([ roots/0 - , fields/1 - ]). +-export([ + roots/0, + fields/1 +]). %% http handlers --export([ listeners/2 - , listeners_insta/2 - , listeners_insta_authn/2 - , users/2 - , users_insta/2 - , import_users/2 - ]). +-export([ + listeners/2, + listeners_insta/2, + listeners_insta_authn/2, + users/2, + users_insta/2, + import_users/2 +]). %%-------------------------------------------------------------------- %% minirest behaviour callbacks @@ -59,12 +65,13 @@ api_spec() -> emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). paths() -> - [ "/gateway/:name/listeners" - , "/gateway/:name/listeners/:id" - , "/gateway/:name/listeners/:id/authentication" - , "/gateway/:name/listeners/:id/authentication/users" - , "/gateway/:name/listeners/:id/authentication/users/:uid" - , "/gateway/:name/listeners/:id/authentication/import_users" + [ + "/gateway/:name/listeners", + "/gateway/:name/listeners/:id", + "/gateway/:name/listeners/:id/authentication", + "/gateway/:name/listeners/:id/authentication/users", + "/gateway/:name/listeners/:id/authentication/users/:uid", + "/gateway/:name/listeners/:id/authentication/import_users" ]. %%-------------------------------------------------------------------- @@ -74,7 +81,6 @@ listeners(get, #{bindings := #{name := Name0}}) -> with_gateway(Name0, fun(GwName, _) -> {200, emqx_gateway_conf:listeners(GwName)} end); - listeners(post, #{bindings := #{name := Name0}, body := LConf}) -> with_gateway(Name0, fun(GwName, Gateway) -> RunningConf = maps:get(config, Gateway), @@ -88,9 +94,11 @@ listeners(post, #{bindings := #{name := Name0}, body := LConf}) -> case emqx_map_lib:deep_get(Path, RunningConf, undefined) of undefined -> ListenerId = emqx_gateway_utils:listener_id( - GwName, Type, LName), + GwName, Type, LName + ), {ok, RespConf} = emqx_gateway_http:add_listener( - ListenerId, LConf), + ListenerId, LConf + ), {201, RespConf}; _ -> return_http_error(400, "Listener name has occupied") @@ -115,47 +123,63 @@ listeners_insta(get, #{bindings := #{name := Name0, id := ListenerId0}}) -> return_http_error(500, Reason) end end); -listeners_insta(put, #{body := LConf, - bindings := #{name := Name0, id := ListenerId0} - }) -> +listeners_insta(put, #{ + body := LConf, + bindings := #{name := Name0, id := ListenerId0} +}) -> ListenerId = emqx_mgmt_util:urldecode(ListenerId0), with_gateway(Name0, fun(_GwName, _) -> {ok, RespConf} = emqx_gateway_http:update_listener(ListenerId, LConf), {200, RespConf} end). -listeners_insta_authn(get, #{bindings := #{name := Name0, - id := ListenerId0}}) -> +listeners_insta_authn(get, #{ + bindings := #{ + name := Name0, + id := ListenerId0 + } +}) -> ListenerId = emqx_mgmt_util:urldecode(ListenerId0), with_gateway(Name0, fun(GwName, _) -> - try - emqx_gateway_http:authn(GwName, ListenerId) - of + try emqx_gateway_http:authn(GwName, ListenerId) of Authn -> {200, Authn} catch - error : {config_not_found, _} -> + error:{config_not_found, _} -> {204} end end); -listeners_insta_authn(post, #{body := Conf, - bindings := #{name := Name0, - id := ListenerId0}}) -> +listeners_insta_authn(post, #{ + body := Conf, + bindings := #{ + name := Name0, + id := ListenerId0 + } +}) -> ListenerId = emqx_mgmt_util:urldecode(ListenerId0), with_gateway(Name0, fun(GwName, _) -> {ok, Authn} = emqx_gateway_http:add_authn(GwName, ListenerId, Conf), {201, Authn} end); -listeners_insta_authn(put, #{body := Conf, - bindings := #{name := Name0, - id := ListenerId0}}) -> +listeners_insta_authn(put, #{ + body := Conf, + bindings := #{ + name := Name0, + id := ListenerId0 + } +}) -> ListenerId = emqx_mgmt_util:urldecode(ListenerId0), with_gateway(Name0, fun(GwName, _) -> {ok, Authn} = emqx_gateway_http:update_authn( - GwName, ListenerId, Conf), + GwName, ListenerId, Conf + ), {200, Authn} end); -listeners_insta_authn(delete, #{bindings := #{name := Name0, - id := ListenerId0}}) -> +listeners_insta_authn(delete, #{ + bindings := #{ + name := Name0, + id := ListenerId0 + } +}) -> ListenerId = emqx_mgmt_util:urldecode(ListenerId0), with_gateway(Name0, fun(GwName, _) -> ok = emqx_gateway_http:remove_authn(GwName, ListenerId), @@ -163,50 +187,76 @@ listeners_insta_authn(delete, #{bindings := #{name := Name0, end). users(get, #{bindings := #{name := Name0, id := Id}, query_string := Qs}) -> - with_listener_authn(Name0, Id, - fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> - emqx_authn_api:list_users(ChainName, AuthId, page_pramas(Qs)) - end); -users(post, #{bindings := #{name := Name0, id := Id}, - body := Body}) -> - with_listener_authn(Name0, Id, - fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> - emqx_authn_api:add_user(ChainName, AuthId, Body) - end). + with_listener_authn( + Name0, + Id, + fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> + emqx_authn_api:list_users(ChainName, AuthId, page_pramas(Qs)) + end + ); +users(post, #{ + bindings := #{name := Name0, id := Id}, + body := Body +}) -> + with_listener_authn( + Name0, + Id, + fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> + emqx_authn_api:add_user(ChainName, AuthId, Body) + end + ). users_insta(get, #{bindings := #{name := Name0, id := Id, uid := UserId}}) -> - with_listener_authn(Name0, Id, - fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> - emqx_authn_api:find_user(ChainName, AuthId, UserId) - end); -users_insta(put, #{bindings := #{name := Name0, id := Id, uid := UserId}, - body := Body}) -> - with_listener_authn(Name0, Id, - fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> - emqx_authn_api:update_user(ChainName, AuthId, UserId, Body) - end); -users_insta(delete, #{bindings := #{name := Name0, id := Id, uid := UserId}}) -> - with_listener_authn(Name0, Id, - fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> - emqx_authn_api:delete_user(ChainName, AuthId, UserId) - end). - -import_users(post, #{bindings := #{name := Name0, id := Id}, - body := Body}) -> - with_listener_authn(Name0, Id, - fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> - case maps:get(<<"filename">>, Body, undefined) of - undefined -> - emqx_authn_api:serialize_error({missing_parameter, filename}); - Filename -> - case emqx_authentication:import_users( - ChainName, AuthId, Filename) of - ok -> {204}; - {error, Reason} -> - emqx_authn_api:serialize_error(Reason) - end + with_listener_authn( + Name0, + Id, + fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> + emqx_authn_api:find_user(ChainName, AuthId, UserId) end - end). + ); +users_insta(put, #{ + bindings := #{name := Name0, id := Id, uid := UserId}, + body := Body +}) -> + with_listener_authn( + Name0, + Id, + fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> + emqx_authn_api:update_user(ChainName, AuthId, UserId, Body) + end + ); +users_insta(delete, #{bindings := #{name := Name0, id := Id, uid := UserId}}) -> + with_listener_authn( + Name0, + Id, + fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> + emqx_authn_api:delete_user(ChainName, AuthId, UserId) + end + ). + +import_users(post, #{ + bindings := #{name := Name0, id := Id}, + body := Body +}) -> + with_listener_authn( + Name0, + Id, + fun(_GwName, #{id := AuthId, chain_name := ChainName}) -> + case maps:get(<<"filename">>, Body, undefined) of + undefined -> + emqx_authn_api:serialize_error({missing_parameter, filename}); + Filename -> + case + emqx_authentication:import_users( + ChainName, AuthId, Filename + ) + of + ok -> {204}; + {error, Reason} -> emqx_authn_api:serialize_error(Reason) + end + end + end + ). %%-------------------------------------------------------------------- %% Utils @@ -219,307 +269,404 @@ page_pramas(Qs) -> %%-------------------------------------------------------------------- schema("/gateway/:name/listeners") -> - #{ 'operationId' => listeners, - get => - #{ desc => <<"Get the gateway listeners">> - , parameters => params_gateway_name_in_path() - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_example( - hoconsc:array(ref(listener)), - examples_listener_list()) - }) - }, - post => - #{ desc => <<"Create the gateway listener">> - , parameters => params_gateway_name_in_path() - %% XXX: How to distinguish the different listener supported by - %% different types of gateways? - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(listener), - examples_listener()) - , responses => - ?STANDARD_RESP( - #{ 201 => emqx_dashboard_swagger:schema_with_examples( - ref(listener), - examples_listener()) - }) - } - }; -schema("/gateway/:name/listeners/:id") -> - #{ 'operationId' => listeners_insta, - get => - #{ desc => <<"Get the gateway listener configurations">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_examples( - ref(listener), - examples_listener()) - }) - }, - delete => - #{ desc => <<"Delete the gateway listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - ?STANDARD_RESP(#{204 => <<"Deleted">>}) - }, - put => - #{ desc => <<"Update the gateway listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(listener), - examples_listener()) - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_examples( - ref(listener), - examples_listener()) - }) - } - }; -schema("/gateway/:name/listeners/:id/authentication") -> - #{ 'operationId' => listeners_insta_authn, - get => - #{ desc => <<"Get the listener's authentication info">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - ?STANDARD_RESP( - #{ 200 => schema_authn() - , 204 => <<"Authentication or listener does not existed">> - }) - }, - post => - #{ desc => <<"Add authentication for the listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , 'requestBody' => schema_authn() - , responses => - ?STANDARD_RESP(#{201 => schema_authn()}) - }, - put => - #{ desc => <<"Update authentication for the listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , 'requestBody' => schema_authn() - , responses => - ?STANDARD_RESP(#{200 => schema_authn()}) - }, - delete => - #{ desc => <<"Remove authentication for the listener">> - , parameters => params_gateway_name_in_path() - ++ params_listener_id_in_path() - , responses => - ?STANDARD_RESP(#{200 => <<"Deleted">>}) - } - }; -schema("/gateway/:name/listeners/:id/authentication/users") -> - #{ 'operationId' => users - , get => - #{ desc => <<"Get the users for the authentication">> - , parameters => params_gateway_name_in_path() ++ - params_listener_id_in_path() ++ - params_paging_in_qs() - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_user), - emqx_authn_api:response_user_examples()) - }) - }, - post => - #{ desc => <<"Add user for the authentication">> - , parameters => params_gateway_name_in_path() ++ - params_listener_id_in_path() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(emqx_authn_api, request_user_create), - emqx_authn_api:request_user_create_examples()) - , responses => - ?STANDARD_RESP( - #{ 201 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_user), - emqx_authn_api:response_user_examples()) - }) - } - }; -schema("/gateway/:name/listeners/:id/authentication/users/:uid") -> - #{ 'operationId' => users_insta - , get => - #{ desc => <<"Get user info from the gateway " - "authentication">> - , parameters => params_gateway_name_in_path() ++ - params_listener_id_in_path() ++ - params_userid_in_path() - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_user), - emqx_authn_api:response_user_examples()) - }) - }, - put => - #{ desc => <<"Update the user info for the gateway " - "authentication">> - , parameters => params_gateway_name_in_path() ++ - params_listener_id_in_path() ++ - params_userid_in_path() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(emqx_authn_api, request_user_update), - emqx_authn_api:request_user_update_examples()) - , responses => - ?STANDARD_RESP( - #{ 200 => emqx_dashboard_swagger:schema_with_example( - ref(emqx_authn_api, response_user), - emqx_authn_api:response_user_examples()) - }) - }, - delete => - #{ desc => <<"Delete the user for the gateway " - "authentication">> - , parameters => params_gateway_name_in_path() ++ - params_listener_id_in_path() ++ - params_userid_in_path() - , responses => - ?STANDARD_RESP(#{204 => <<"Deleted">>}) - } - }; -schema("/gateway/:name/listeners/:id/authentication/import_users") -> - #{ 'operationId' => import_users - , post => - #{ desc => <<"Import users into the gateway authentication">> - , parameters => params_gateway_name_in_path() ++ - params_listener_id_in_path() - , 'requestBody' => emqx_dashboard_swagger:schema_with_examples( - ref(emqx_authn_api, request_import_users), - emqx_authn_api:request_import_users_examples() + #{ + 'operationId' => listeners, + get => + #{ + desc => <<"Get the gateway listeners">>, + parameters => params_gateway_name_in_path(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + hoconsc:array(ref(listener)), + examples_listener_list() ) - , responses => - ?STANDARD_RESP(#{204 => <<"Imported">>}) - } - }. + } + ) + }, + post => + #{ + desc => <<"Create the gateway listener">>, + parameters => params_gateway_name_in_path(), + %% XXX: How to distinguish the different listener supported by + %% different types of gateways? + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener() + ), + responses => + ?STANDARD_RESP( + #{ + 201 => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener() + ) + } + ) + } + }; +schema("/gateway/:name/listeners/:id") -> + #{ + 'operationId' => listeners_insta, + get => + #{ + desc => <<"Get the gateway listener configurations">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener() + ) + } + ) + }, + delete => + #{ + desc => <<"Delete the gateway listener">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + responses => + ?STANDARD_RESP(#{204 => <<"Deleted">>}) + }, + put => + #{ + desc => <<"Update the gateway listener">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener() + ), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_examples( + ref(listener), + examples_listener() + ) + } + ) + } + }; +schema("/gateway/:name/listeners/:id/authentication") -> + #{ + 'operationId' => listeners_insta_authn, + get => + #{ + desc => <<"Get the listener's authentication info">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + responses => + ?STANDARD_RESP( + #{ + 200 => schema_authn(), + 204 => <<"Authentication or listener does not existed">> + } + ) + }, + post => + #{ + desc => <<"Add authentication for the listener">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + 'requestBody' => schema_authn(), + responses => + ?STANDARD_RESP(#{201 => schema_authn()}) + }, + put => + #{ + desc => <<"Update authentication for the listener">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + 'requestBody' => schema_authn(), + responses => + ?STANDARD_RESP(#{200 => schema_authn()}) + }, + delete => + #{ + desc => <<"Remove authentication for the listener">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + responses => + ?STANDARD_RESP(#{200 => <<"Deleted">>}) + } + }; +schema("/gateway/:name/listeners/:id/authentication/users") -> + #{ + 'operationId' => users, + get => + #{ + desc => <<"Get the users for the authentication">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path() ++ + params_paging_in_qs(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples() + ) + } + ) + }, + post => + #{ + desc => <<"Add user for the authentication">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(emqx_authn_api, request_user_create), + emqx_authn_api:request_user_create_examples() + ), + responses => + ?STANDARD_RESP( + #{ + 201 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples() + ) + } + ) + } + }; +schema("/gateway/:name/listeners/:id/authentication/users/:uid") -> + #{ + 'operationId' => users_insta, + get => + #{ + desc => << + "Get user info from the gateway " + "authentication" + >>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path() ++ + params_userid_in_path(), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples() + ) + } + ) + }, + put => + #{ + desc => << + "Update the user info for the gateway " + "authentication" + >>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path() ++ + params_userid_in_path(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(emqx_authn_api, request_user_update), + emqx_authn_api:request_user_update_examples() + ), + responses => + ?STANDARD_RESP( + #{ + 200 => emqx_dashboard_swagger:schema_with_example( + ref(emqx_authn_api, response_user), + emqx_authn_api:response_user_examples() + ) + } + ) + }, + delete => + #{ + desc => << + "Delete the user for the gateway " + "authentication" + >>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path() ++ + params_userid_in_path(), + responses => + ?STANDARD_RESP(#{204 => <<"Deleted">>}) + } + }; +schema("/gateway/:name/listeners/:id/authentication/import_users") -> + #{ + 'operationId' => import_users, + post => + #{ + desc => <<"Import users into the gateway authentication">>, + parameters => params_gateway_name_in_path() ++ + params_listener_id_in_path(), + 'requestBody' => emqx_dashboard_swagger:schema_with_examples( + ref(emqx_authn_api, request_import_users), + emqx_authn_api:request_import_users_examples() + ), + responses => + ?STANDARD_RESP(#{204 => <<"Imported">>}) + } + }. %%-------------------------------------------------------------------- %% params defines params_gateway_name_in_path() -> - [{name, - mk(binary(), - #{ in => path - , desc => <<"Gateway Name">> - , example => <<"">> - })} + [ + {name, + mk( + binary(), + #{ + in => path, + desc => <<"Gateway Name">>, + example => <<"">> + } + )} ]. params_listener_id_in_path() -> - [{id, - mk(binary(), - #{ in => path - , desc => <<"Listener ID">> - , example => <<"">> - })} + [ + {id, + mk( + binary(), + #{ + in => path, + desc => <<"Listener ID">>, + example => <<"">> + } + )} ]. params_userid_in_path() -> - [{uid, mk(binary(), - #{ in => path - , desc => <<"User ID">> - , example => <<"">> - })} + [ + {uid, + mk( + binary(), + #{ + in => path, + desc => <<"User ID">>, + example => <<"">> + } + )} ]. params_paging_in_qs() -> - [{page, mk(integer(), - #{ in => query - , required => false - , desc => <<"Page Index">> - , example => 1 - })}, - {limit, mk(integer(), - #{ in => query - , required => false - , desc => <<"Page Limit">> - , example => 100 - })} + [ + {page, + mk( + integer(), + #{ + in => query, + required => false, + desc => <<"Page Index">>, + example => 1 + } + )}, + {limit, + mk( + integer(), + #{ + in => query, + required => false, + desc => <<"Page Limit">>, + example => 100 + } + )} ]. %%-------------------------------------------------------------------- %% schemas roots() -> - [ listener - ]. + [listener]. fields(listener) -> common_listener_opts() ++ - [ {tcp, - mk(ref(tcp_listener_opts), - #{ required => {false, recursively} - , desc => <<"The tcp socket options for tcp or ssl listener">> - })} - , {ssl, - mk(ref(ssl_listener_opts), - #{ required => {false, recursively} - , desc => <<"The ssl socket options for ssl listener">> - })} - , {udp, - mk(ref(udp_listener_opts), - #{ required => {false, recursively} - , desc => <<"The udp socket options for udp or dtls listener">> - })} - , {dtls, - mk(ref(dtls_listener_opts), - #{ required => {false, recursively} - , desc => <<"The dtls socket options for dtls listener">> - })} - ]; + [ + {tcp, + mk( + ref(tcp_listener_opts), + #{ + required => {false, recursively}, + desc => <<"The tcp socket options for tcp or ssl listener">> + } + )}, + {ssl, + mk( + ref(ssl_listener_opts), + #{ + required => {false, recursively}, + desc => <<"The ssl socket options for ssl listener">> + } + )}, + {udp, + mk( + ref(udp_listener_opts), + #{ + required => {false, recursively}, + desc => <<"The udp socket options for udp or dtls listener">> + } + )}, + {dtls, + mk( + ref(dtls_listener_opts), + #{ + required => {false, recursively}, + desc => <<"The dtls socket options for dtls listener">> + } + )} + ]; fields(tcp_listener_opts) -> - [ {active_n, mk(integer(), #{})} - , {backlog, mk(integer(), #{})} - , {buffer, mk(binary(), #{})} - , {recbuf, mk(binary(), #{})} - , {sndbuf, mk(binary(), #{})} - , {high_watermark, mk(binary(), #{})} - , {nodelay, mk(boolean(), #{})} - , {reuseaddr, boolean()} - , {send_timeout, binary()} - , {send_timeout_close, boolean()} + [ + {active_n, mk(integer(), #{})}, + {backlog, mk(integer(), #{})}, + {buffer, mk(binary(), #{})}, + {recbuf, mk(binary(), #{})}, + {sndbuf, mk(binary(), #{})}, + {high_watermark, mk(binary(), #{})}, + {nodelay, mk(boolean(), #{})}, + {reuseaddr, boolean()}, + {send_timeout, binary()}, + {send_timeout_close, boolean()} ]; fields(ssl_listener_opts) -> - [ {cacertfile, binary()} - , {certfile, binary()} - , {keyfile, binary()} - , {verify, binary()} - , {fail_if_no_peer_cert, boolean()} - , {depth, integer()} - , {password, binary()} - , {handshake_timeout, binary()} - , {versions, hoconsc:array(binary())} - , {ciphers, hoconsc:array(binary())} - , {user_lookup_fun, binary()} - , {reuse_sessions, boolean()} - , {secure_renegotiate, boolean()} - , {honor_cipher_order, boolean()} - , {dhfile, binary()} + [ + {cacertfile, binary()}, + {certfile, binary()}, + {keyfile, binary()}, + {verify, binary()}, + {fail_if_no_peer_cert, boolean()}, + {depth, integer()}, + {password, binary()}, + {handshake_timeout, binary()}, + {versions, hoconsc:array(binary())}, + {ciphers, hoconsc:array(binary())}, + {user_lookup_fun, binary()}, + {reuse_sessions, boolean()}, + {secure_renegotiate, boolean()}, + {honor_cipher_order, boolean()}, + {dhfile, binary()} ]; fields(udp_listener_opts) -> - [ {active_n, integer()} - , {buffer, binary()} - , {recbuf, binary()} - , {sndbuf, binary()} - , {reuseaddr, boolean()} + [ + {active_n, integer()}, + {buffer, binary()}, + {recbuf, binary()}, + {sndbuf, binary()}, + {reuseaddr, boolean()} ]; fields(dtls_listener_opts) -> Ls = lists_key_without( - [versions,ciphers,handshake_timeout], 1, - fields(ssl_listener_opts) - ), - [ {versions, hoconsc:array(binary())} - , {ciphers, hoconsc:array(binary())} - | Ls]. + [versions, ciphers, handshake_timeout], + 1, + fields(ssl_listener_opts) + ), + [ + {versions, hoconsc:array(binary())}, + {ciphers, hoconsc:array(binary())} + | Ls + ]. lists_key_without([], _N, L) -> L; @@ -527,59 +674,109 @@ lists_key_without([K | Ks], N, L) -> lists_key_without(Ks, N, lists:keydelete(K, N, L)). common_listener_opts() -> - [ {enable, - mk(boolean(), - #{ required => false - , desc => <<"Whether to enable this listener">>})} - , {id, - mk(binary(), - #{ required => false - , desc => <<"Listener Id">>})} - , {name, - mk(binary(), - #{ required => false - , desc => <<"Listener name">>})} - , {type, - mk(hoconsc:enum([tcp, ssl, udp, dtls]), - #{ required => false - , desc => <<"Listener type. Enum: tcp, udp, ssl, dtls">>})} - , {running, - mk(boolean(), - #{ required => false - , desc => <<"Listener running status">>})} - , {bind, - mk(binary(), - #{ required => false - , desc => <<"Listener bind address or port">>})} - , {acceptors, - mk(integer(), - #{ required => false - , desc => <<"Listener acceptors number">>})} - , {access_rules, - mk(hoconsc:array(binary()), - #{ required => false - , desc => <<"Listener Access rules for client">>})} - , {max_conn_rate, - mk(integer(), - #{ required => false - , desc => <<"Max connection rate for the listener">>})} - , {max_connections, - mk(integer(), - #{ required => false - , desc => <<"Max connections for the listener">>})} - , {mountpoint, - mk(binary(), - #{ required => false - , desc => -<<"The Mounpoint for clients of the listener. " - "The gateway-level mountpoint configuration can be overloaded " - "when it is not null or empty string">>})} - %% FIXME: - , {authentication, - mk(emqx_authn_schema:authenticator_type(), - #{ required => {false, recursively} - , desc => <<"The authenticatior for this listener">> - })} + [ + {enable, + mk( + boolean(), + #{ + required => false, + desc => <<"Whether to enable this listener">> + } + )}, + {id, + mk( + binary(), + #{ + required => false, + desc => <<"Listener Id">> + } + )}, + {name, + mk( + binary(), + #{ + required => false, + desc => <<"Listener name">> + } + )}, + {type, + mk( + hoconsc:enum([tcp, ssl, udp, dtls]), + #{ + required => false, + desc => <<"Listener type. Enum: tcp, udp, ssl, dtls">> + } + )}, + {running, + mk( + boolean(), + #{ + required => false, + desc => <<"Listener running status">> + } + )}, + {bind, + mk( + binary(), + #{ + required => false, + desc => <<"Listener bind address or port">> + } + )}, + {acceptors, + mk( + integer(), + #{ + required => false, + desc => <<"Listener acceptors number">> + } + )}, + {access_rules, + mk( + hoconsc:array(binary()), + #{ + required => false, + desc => <<"Listener Access rules for client">> + } + )}, + {max_conn_rate, + mk( + integer(), + #{ + required => false, + desc => <<"Max connection rate for the listener">> + } + )}, + {max_connections, + mk( + integer(), + #{ + required => false, + desc => <<"Max connections for the listener">> + } + )}, + {mountpoint, + mk( + binary(), + #{ + required => false, + desc => + << + "The Mounpoint for clients of the listener. " + "The gateway-level mountpoint configuration can be overloaded " + "when it is not null or empty string" + >> + } + )}, + %% FIXME: + {authentication, + mk( + emqx_authn_schema:authenticator_type(), + #{ + required => {false, recursively}, + desc => <<"The authenticatior for this listener">> + } + )} ] ++ emqx_gateway_schema:proxy_protocol_opts(). %%-------------------------------------------------------------------- @@ -589,133 +786,159 @@ examples_listener_list() -> [Config || #{value := Config} <- maps:values(examples_listener())]. examples_listener() -> - #{ tcp_listener=> - #{ summary => <<"A simple tcp listener example">> - , value => - #{ name => <<"tcp-def">> - , type => <<"tcp">> - , bind => <<"22210">> - , acceptors => 16 - , max_connections => 1024000 - , max_conn_rate => 1000 - , tcp => - #{ active_n => 100 - , backlog => 1024 - , send_timeout => <<"15s">> - , send_timeout_close => true - , recbuf => <<"10KB">> - , sndbuf => <<"10KB">> - , buffer => <<"10KB">> - , high_watermark => <<"1MB">> - , nodelay => false - , reuseaddr => true - } - } - } - , ssl_listener => - #{ summary => <<"A simple ssl listener example">> - , value => - #{ name => <<"ssl-def">> - , type => <<"ssl">> - , bind => <<"22211">> - , acceptors => 16 - , max_connections => 1024000 - , max_conn_rate => 1000 - , access_rules => [<<"allow all">>] - , ssl => - #{ versions => [<<"tlsv1.3">>, <<"tlsv1.2">>, - <<"tlsv1.1">>, <<"tlsv1">>] - , cacertfile => <<"etc/certs/cacert.pem">> - , certfile => <<"etc/certs/cert.pem">> - , keyfile => <<"etc/certs/key.pem">> - , verify => <<"verify_none">> - , fail_if_no_peer_cert => false - } - , tcp => - #{ active_n => 100 - , backlog => 1024 - } - } - } - , udp_listener => - #{ summary => <<"A simple udp listener example">> - , value => - #{ name => <<"udp-def">> - , type => udp - , bind => <<"22212">> - , udp => - #{ active_n => 100 - , recbuf => <<"10KB">> - , sndbuf => <<"10KB">> - , buffer => <<"10KB">> - , reuseaddr => true - } - } - } - , dtls_listener => - #{ summary => <<"A simple dtls listener example">> - , value => - #{ name => <<"dtls-def">> - , type => <<"dtls">> - , bind => <<"22213">> - , acceptors => 16 - , max_connections => 1024000 - , max_conn_rate => 1000 - , access_rules => [<<"allow all">>] - , dtls => - #{ versions => [<<"dtlsv1.2">>, <<"dtlsv1">>] - , cacertfile => <<"etc/certs/cacert.pem">> - , certfile => <<"etc/certs/cert.pem">> - , keyfile => <<"etc/certs/key.pem">> - , verify => <<"verify_none">> - , fail_if_no_peer_cert => false - } - , udp => - #{ active_n => 100 - , backlog => 1024 - } - } - } - , dtls_listener_with_psk_ciphers => - #{ summary => <<"A dtls listener with PSK example">> - , value => - #{ name => <<"dtls-psk">> - , type => <<"dtls">> - , bind => <<"22214">> - , acceptors => 16 - , max_connections => 1024000 - , max_conn_rate => 1000 - , dtls => - #{ versions => [<<"dtlsv1.2">>, <<"dtlsv1">>] - , cacertfile => <<"etc/certs/cacert.pem">> - , certfile => <<"etc/certs/cert.pem">> - , keyfile => <<"etc/certs/key.pem">> - , verify => <<"verify_none">> - , user_lookup_fun => <<"emqx_tls_psk:lookup">> - , ciphers => -<<"RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384,RSA-PSK-AES128-GCM-SHA256," - "RSA-PSK-AES128-CBC-SHA256,RSA-PSK-AES256-CBC-SHA,RSA-PSK-AES128-CBC-SHA">> - , fail_if_no_peer_cert => false - } - } - } - , lisetner_with_authn => - #{ summary => <<"A tcp listener with authentication example">> - , value => - #{ name => <<"tcp-with-authn">> - , type => <<"tcp">> - , bind => <<"22215">> - , acceptors => 16 - , max_connections => 1024000 - , max_conn_rate => 1000 - , authentication => - #{ backend => <<"built_in_database">> - , mechanism => <<"password_based">> - , password_hash_algorithm => - #{ name => <<"sha256">> - } - , user_id_type => <<"username">> - } - } - } - }. + #{ + tcp_listener => + #{ + summary => <<"A simple tcp listener example">>, + value => + #{ + name => <<"tcp-def">>, + type => <<"tcp">>, + bind => <<"22210">>, + acceptors => 16, + max_connections => 1024000, + max_conn_rate => 1000, + tcp => + #{ + active_n => 100, + backlog => 1024, + send_timeout => <<"15s">>, + send_timeout_close => true, + recbuf => <<"10KB">>, + sndbuf => <<"10KB">>, + buffer => <<"10KB">>, + high_watermark => <<"1MB">>, + nodelay => false, + reuseaddr => true + } + } + }, + ssl_listener => + #{ + summary => <<"A simple ssl listener example">>, + value => + #{ + name => <<"ssl-def">>, + type => <<"ssl">>, + bind => <<"22211">>, + acceptors => 16, + max_connections => 1024000, + max_conn_rate => 1000, + access_rules => [<<"allow all">>], + ssl => + #{ + versions => [ + <<"tlsv1.3">>, + <<"tlsv1.2">>, + <<"tlsv1.1">>, + <<"tlsv1">> + ], + cacertfile => <<"etc/certs/cacert.pem">>, + certfile => <<"etc/certs/cert.pem">>, + keyfile => <<"etc/certs/key.pem">>, + verify => <<"verify_none">>, + fail_if_no_peer_cert => false + }, + tcp => + #{ + active_n => 100, + backlog => 1024 + } + } + }, + udp_listener => + #{ + summary => <<"A simple udp listener example">>, + value => + #{ + name => <<"udp-def">>, + type => udp, + bind => <<"22212">>, + udp => + #{ + active_n => 100, + recbuf => <<"10KB">>, + sndbuf => <<"10KB">>, + buffer => <<"10KB">>, + reuseaddr => true + } + } + }, + dtls_listener => + #{ + summary => <<"A simple dtls listener example">>, + value => + #{ + name => <<"dtls-def">>, + type => <<"dtls">>, + bind => <<"22213">>, + acceptors => 16, + max_connections => 1024000, + max_conn_rate => 1000, + access_rules => [<<"allow all">>], + dtls => + #{ + versions => [<<"dtlsv1.2">>, <<"dtlsv1">>], + cacertfile => <<"etc/certs/cacert.pem">>, + certfile => <<"etc/certs/cert.pem">>, + keyfile => <<"etc/certs/key.pem">>, + verify => <<"verify_none">>, + fail_if_no_peer_cert => false + }, + udp => + #{ + active_n => 100, + backlog => 1024 + } + } + }, + dtls_listener_with_psk_ciphers => + #{ + summary => <<"A dtls listener with PSK example">>, + value => + #{ + name => <<"dtls-psk">>, + type => <<"dtls">>, + bind => <<"22214">>, + acceptors => 16, + max_connections => 1024000, + max_conn_rate => 1000, + dtls => + #{ + versions => [<<"dtlsv1.2">>, <<"dtlsv1">>], + cacertfile => <<"etc/certs/cacert.pem">>, + certfile => <<"etc/certs/cert.pem">>, + keyfile => <<"etc/certs/key.pem">>, + verify => <<"verify_none">>, + user_lookup_fun => <<"emqx_tls_psk:lookup">>, + ciphers => + << + "RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384,RSA-PSK-AES128-GCM-SHA256," + "RSA-PSK-AES128-CBC-SHA256,RSA-PSK-AES256-CBC-SHA,RSA-PSK-AES128-CBC-SHA" + >>, + fail_if_no_peer_cert => false + } + } + }, + lisetner_with_authn => + #{ + summary => <<"A tcp listener with authentication example">>, + value => + #{ + name => <<"tcp-with-authn">>, + type => <<"tcp">>, + bind => <<"22215">>, + acceptors => 16, + max_connections => 1024000, + max_conn_rate => 1000, + authentication => + #{ + backend => <<"built_in_database">>, + mechanism => <<"password_based">>, + password_hash_algorithm => + #{name => <<"sha256">>}, + user_id_type => <<"username">> + } + } + } + }. diff --git a/apps/emqx_gateway/src/emqx_gateway_app.erl b/apps/emqx_gateway/src/emqx_gateway_app.erl index 704bd7634..8d774311f 100644 --- a/apps/emqx_gateway/src/emqx_gateway_app.erl +++ b/apps/emqx_gateway/src/emqx_gateway_app.erl @@ -22,6 +22,8 @@ -export([start/2, stop/1]). +-elvis([{elvis_style, invalid_dynamic_call, disable}]). + start(_StartType, _StartArgs) -> {ok, Sup} = emqx_gateway_sup:start_link(), emqx_gateway_cli:load(), @@ -44,22 +46,29 @@ load_default_gateway_applications() -> gateway_type_searching() -> %% FIXME: Hardcoded apps - [emqx_stomp_impl, emqx_sn_impl, emqx_exproto_impl, - emqx_coap_impl, emqx_lwm2m_impl]. + [ + emqx_stomp_impl, + emqx_sn_impl, + emqx_exproto_impl, + emqx_coap_impl, + emqx_lwm2m_impl + ]. reg(Mod) -> try Mod:reg(), - ?SLOG(debug, #{ msg => "register_gateway_succeed" - , callback_module => Mod - }) + ?SLOG(debug, #{ + msg => "register_gateway_succeed", + callback_module => Mod + }) catch - Class : Reason : Stk -> - ?SLOG(error, #{ msg => "failed_to_register_gateway" - , callback_module => Mod - , reason => {Class, Reason} - , stacktrace => Stk - }) + Class:Reason:Stk -> + ?SLOG(error, #{ + msg => "failed_to_register_gateway", + callback_module => Mod, + reason => {Class, Reason}, + stacktrace => Stk + }) end. load_gateway_by_default() -> @@ -67,22 +76,26 @@ load_gateway_by_default() -> load_gateway_by_default([]) -> ok; -load_gateway_by_default([{Type, Confs}|More]) -> +load_gateway_by_default([{Type, Confs} | More]) -> case emqx_gateway_registry:lookup(Type) of undefined -> - ?SLOG(error, #{ msg => "skip_to_load_gateway" - , gateway_name => Type - }); + ?SLOG(error, #{ + msg => "skip_to_load_gateway", + gateway_name => Type + }); _ -> case emqx_gateway:load(Type, Confs) of {ok, _} -> - ?SLOG(debug, #{ msg => "load_gateway_succeed" - , gateway_name => Type - }); + ?SLOG(debug, #{ + msg => "load_gateway_succeed", + gateway_name => Type + }); {error, Reason} -> - ?SLOG(error, #{ msg => "load_gateway_failed" - , gateway_name => Type - , reason => Reason}) + ?SLOG(error, #{ + msg => "load_gateway_failed", + gateway_name => Type, + reason => Reason + }) end end, load_gateway_by_default(More). diff --git a/apps/emqx_gateway/src/emqx_gateway_cli.erl b/apps/emqx_gateway/src/emqx_gateway_cli.erl index 0e51db6b9..699320e4b 100644 --- a/apps/emqx_gateway/src/emqx_gateway_cli.erl +++ b/apps/emqx_gateway/src/emqx_gateway_cli.erl @@ -17,25 +17,30 @@ %% @doc The Command-Line-Interface module for Gateway Application -module(emqx_gateway_cli). --export([ load/0 - , unload/0 - ]). +-export([ + load/0, + unload/0 +]). --export([ gateway/1 - , 'gateway-registry'/1 - , 'gateway-clients'/1 - , 'gateway-metrics'/1 - %, 'gateway-banned'/1 - ]). +-export([ + gateway/1, + 'gateway-registry'/1, + 'gateway-clients'/1, + 'gateway-metrics'/1 + %, 'gateway-banned'/1 +]). -elvis([{elvis_style, function_naming_convention, disable}]). -spec load() -> ok. load() -> Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)], - lists:foreach(fun(Cmd) -> - emqx_ctl:register_command(Cmd, {?MODULE, Cmd}, []) - end, Cmds). + lists:foreach( + fun(Cmd) -> + emqx_ctl:register_command(Cmd, {?MODULE, Cmd}, []) + end, + Cmds + ). -spec unload() -> ok. unload() -> @@ -53,10 +58,11 @@ is_cmd(Fun) -> gateway(["list"]) -> lists:foreach( - fun (GwSummary) -> - print(format_gw_summary(GwSummary)) - end, emqx_gateway_http:gateways(all)); - + fun(GwSummary) -> + print(format_gw_summary(GwSummary)) + end, + emqx_gateway_http:gateways(all) + ); gateway(["lookup", Name]) -> case emqx_gateway:lookup(atom(Name)) of undefined -> @@ -64,18 +70,18 @@ gateway(["lookup", Name]) -> Gateway -> print(format_gateway(Gateway)) end; - gateway(["load", Name, Conf]) -> - case emqx_gateway_conf:load_gateway( - bin(Name), - emqx_json:decode(Conf, [return_maps]) - ) of + case + emqx_gateway_conf:load_gateway( + bin(Name), + emqx_json:decode(Conf, [return_maps]) + ) + of {ok, _} -> print("ok\n"); {error, Reason} -> print("Error: ~ts\n", [format_error(Reason)]) end; - gateway(["unload", Name]) -> case emqx_gateway_conf:unload_gateway(bin(Name)) of ok -> @@ -83,56 +89,51 @@ gateway(["unload", Name]) -> {error, Reason} -> print("Error: ~ts\n", [format_error(Reason)]) end; - gateway(["stop", Name]) -> - case emqx_gateway_conf:update_gateway( - bin(Name), - #{<<"enable">> => <<"false">>} - ) of + case + emqx_gateway_conf:update_gateway( + bin(Name), + #{<<"enable">> => <<"false">>} + ) + of {ok, _} -> print("ok\n"); {error, Reason} -> print("Error: ~ts\n", [format_error(Reason)]) end; - gateway(["start", Name]) -> - case emqx_gateway_conf:update_gateway( - bin(Name), - #{<<"enable">> => <<"true">>} - ) of + case + emqx_gateway_conf:update_gateway( + bin(Name), + #{<<"enable">> => <<"true">>} + ) + of {ok, _} -> print("ok\n"); {error, Reason} -> print("Error: ~ts\n", [format_error(Reason)]) end; - gateway(_) -> emqx_ctl:usage( - [ {"gateway list", - "List all gateway"} - , {"gateway lookup ", - "Lookup a gateway detailed information"} - , {"gateway load ", - "Load a gateway with config"} - , {"gateway unload ", - "Unload the gateway"} - , {"gateway stop ", - "Stop the gateway"} - , {"gateway start ", - "Start the gateway"} - ]). + [ + {"gateway list", "List all gateway"}, + {"gateway lookup ", "Lookup a gateway detailed information"}, + {"gateway load ", "Load a gateway with config"}, + {"gateway unload ", "Unload the gateway"}, + {"gateway stop ", "Stop the gateway"}, + {"gateway start ", "Start the gateway"} + ] + ). 'gateway-registry'(["list"]) -> lists:foreach( - fun({Name, #{cbkmod := CbMod}}) -> - print("Registered Name: ~ts, Callback Module: ~ts\n", [Name, CbMod]) - end, - emqx_gateway_registry:list()); - + fun({Name, #{cbkmod := CbMod}}) -> + print("Registered Name: ~ts, Callback Module: ~ts\n", [Name, CbMod]) + end, + emqx_gateway_registry:list() + ); 'gateway-registry'(_) -> - emqx_ctl:usage([ {"gateway-registry list", - "List all registered gateways"} - ]). + emqx_ctl:usage([{"gateway-registry list", "List all registered gateways"}]). 'gateway-clients'(["list", Name]) -> %% XXX: page me? @@ -143,7 +144,6 @@ gateway(_) -> _ -> dump(InfoTab, client) end; - 'gateway-clients'(["lookup", Name, ClientId]) -> ChanTab = emqx_gateway_cm:tabname(chan, Name), case ets:info(ChanTab) of @@ -151,28 +151,25 @@ gateway(_) -> print("Bad Gateway Name.\n"); _ -> case ets:lookup(ChanTab, bin(ClientId)) of - [] -> print("Not Found.\n"); + [] -> + print("Not Found.\n"); [Chann] -> InfoTab = emqx_gateway_cm:tabname(info, Name), [ChannInfo] = ets:lookup(InfoTab, Chann), print_record({client, ChannInfo}) end end; - 'gateway-clients'(["kick", Name, ClientId]) -> case emqx_gateway_cm:kick_session(Name, bin(ClientId)) of ok -> print("ok\n"); _ -> print("Not Found.\n") end; - 'gateway-clients'(_) -> - emqx_ctl:usage([ {"gateway-clients list ", - "List all clients for a gateway"} - , {"gateway-clients lookup ", - "Lookup the Client Info for specified client"} - , {"gateway-clients kick ", - "Kick out a client"} - ]). + emqx_ctl:usage([ + {"gateway-clients list ", "List all clients for a gateway"}, + {"gateway-clients lookup ", "Lookup the Client Info for specified client"}, + {"gateway-clients kick ", "Kick out a client"} + ]). 'gateway-metrics'([Name]) -> case emqx_gateway_metrics:lookup(atom(Name)) of @@ -180,20 +177,18 @@ gateway(_) -> print("Bad Gateway Name.\n"); Metrics -> lists:foreach( - fun({K, V}) -> print("~-30s: ~w\n", [K, V]) end, - Metrics) + fun({K, V}) -> print("~-30s: ~w\n", [K, V]) end, + Metrics + ) end; - 'gateway-metrics'(_) -> - emqx_ctl:usage([ {"gateway-metrics ", - "List all metrics for a gateway"} - ]). + emqx_ctl:usage([{"gateway-metrics ", "List all metrics for a gateway"}]). atom(Id) -> try list_to_existing_atom(Id) catch - _ : _ -> undefined + _:_ -> undefined end. %%-------------------------------------------------------------------- @@ -207,77 +202,103 @@ dump(Table, Tag) -> dump(_Table, _, '$end_of_table', Result) -> lists:reverse(Result); - dump(Table, Tag, Key, Result) -> PrintValue = [print_record({Tag, Record}) || Record <- ets:lookup(Table, Key)], dump(Table, Tag, ets:next(Table, Key), [PrintValue | Result]). print_record({client, {_, Infos, Stats}}) -> ClientInfo = maps:get(clientinfo, Infos, #{}), - ConnInfo = maps:get(conninfo, Infos, #{}), - _Session = maps:get(session, Infos, #{}), - SafeGet = fun(K, M) -> maps:get(K, M, undefined) end, - StatsGet = fun(K) -> proplists:get_value(K, Stats, 0) end, + ConnInfo = maps:get(conninfo, Infos, #{}), + _Session = maps:get(session, Infos, #{}), + SafeGet = fun(K, M) -> maps:get(K, M, undefined) end, + StatsGet = fun(K) -> proplists:get_value(K, Stats, 0) end, ConnectedAt = SafeGet(connected_at, ConnInfo), - InfoKeys = [clientid, username, peername, clean_start, keepalive, - subscriptions_cnt, send_msg, connected, created_at, connected_at], - Info = #{ clientid => SafeGet(clientid, ClientInfo), - username => SafeGet(username, ClientInfo), - peername => SafeGet(peername, ConnInfo), - clean_start => SafeGet(clean_start, ConnInfo), - keepalive => SafeGet(keepalive, ConnInfo), - subscriptions_cnt => StatsGet(subscriptions_cnt), - send_msg => StatsGet(send_msg), - connected => SafeGet(conn_state, Infos) == connected, - created_at => ConnectedAt, - connected_at => ConnectedAt - }, + InfoKeys = [ + clientid, + username, + peername, + clean_start, + keepalive, + subscriptions_cnt, + send_msg, + connected, + created_at, + connected_at + ], + Info = #{ + clientid => SafeGet(clientid, ClientInfo), + username => SafeGet(username, ClientInfo), + peername => SafeGet(peername, ConnInfo), + clean_start => SafeGet(clean_start, ConnInfo), + keepalive => SafeGet(keepalive, ConnInfo), + subscriptions_cnt => StatsGet(subscriptions_cnt), + send_msg => StatsGet(send_msg), + connected => SafeGet(conn_state, Infos) == connected, + created_at => ConnectedAt, + connected_at => ConnectedAt + }, - print("Client(~ts, username=~ts, peername=~ts, " - "clean_start=~ts, keepalive=~w, " - "subscriptions=~w, delivered_msgs=~w, " - "connected=~ts, created_at=~w, connected_at=~w)\n", - [format(K, maps:get(K, Info)) || K <- InfoKeys]). + print( + "Client(~ts, username=~ts, peername=~ts, " + "clean_start=~ts, keepalive=~w, " + "subscriptions=~w, delivered_msgs=~w, " + "connected=~ts, created_at=~w, connected_at=~w)\n", + [format(K, maps:get(K, Info)) || K <- InfoKeys] + ). print(S) -> emqx_ctl:print(S). print(S, A) -> emqx_ctl:print(S, A). format(_, undefined) -> undefined; - format(peername, {IPAddr, Port}) -> IPStr = emqx_mgmt_util:ntoa(IPAddr), io_lib:format("~ts:~p", [IPStr, Port]); - format(_, Val) -> Val. format_gw_summary(#{name := Name, status := unloaded}) -> io_lib:format("Gateway(name=~ts, status=unloaded)\n", [Name]); +format_gw_summary(#{ + name := Name, + status := stopped, + stopped_at := StoppedAt +}) -> + io_lib:format( + "Gateway(name=~ts, status=stopped, stopped_at=~ts)\n", + [Name, StoppedAt] + ); +format_gw_summary(#{ + name := Name, + status := running, + current_connections := ConnCnt, + started_at := StartedAt +}) -> + io_lib:format( + "Gateway(name=~ts, status=running, clients=~w, " + "started_at=~ts)\n", + [Name, ConnCnt, StartedAt] + ). -format_gw_summary(#{name := Name, status := stopped, - stopped_at := StoppedAt}) -> - io_lib:format("Gateway(name=~ts, status=stopped, stopped_at=~ts)\n", - [Name, StoppedAt]); -format_gw_summary(#{name := Name, status := running, - current_connections := ConnCnt, - started_at := StartedAt}) -> - io_lib:format("Gateway(name=~ts, status=running, clients=~w, " - "started_at=~ts)\n", [Name, ConnCnt, StartedAt]). - -format_gateway(#{name := Name, - status := unloaded}) -> +format_gateway(#{ + name := Name, + status := unloaded +}) -> io_lib:format( "name: ~ts\n" - "status: unloaded\n", [Name]); - -format_gateway(Gw = - #{name := Name, - status := Status, - created_at := CreatedAt, - config := Config - }) -> + "status: unloaded\n", + [Name] + ); +format_gateway( + Gw = + #{ + name := Name, + status := Status, + created_at := CreatedAt, + config := Config + } +) -> {StopOrStart, Timestamp} = case Status of stopped -> {stopped_at, maps:get(stopped_at, Gw)}; @@ -289,10 +310,15 @@ format_gateway(Gw = "created_at: ~ts\n" "~ts: ~ts\n" "config: ~p\n", - [Name, Status, - emqx_gateway_utils:unix_ts_to_rfc3339(CreatedAt), - StopOrStart, emqx_gateway_utils:unix_ts_to_rfc3339(Timestamp), - Config]). + [ + Name, + Status, + emqx_gateway_utils:unix_ts_to_rfc3339(CreatedAt), + StopOrStart, + emqx_gateway_utils:unix_ts_to_rfc3339(Timestamp), + Config + ] + ). format_error(Reason) -> case emqx_gateway_http:reason2msg(Reason) of diff --git a/apps/emqx_gateway/src/emqx_gateway_cm.erl b/apps/emqx_gateway/src/emqx_gateway_cm.erl index 0e33793f8..5a7c0468d 100644 --- a/apps/emqx_gateway/src/emqx_gateway_cm.erl +++ b/apps/emqx_gateway/src/emqx_gateway_cm.erl @@ -30,70 +30,77 @@ %% APIs -export([start_link/1]). --export([ open_session/5 - , open_session/6 - , kick_session/2 - , kick_session/3 - , takeover_session/2 - , register_channel/4 - , unregister_channel/2 - , insert_channel_info/4 - , lookup_by_clientid/2 - , set_chan_info/3 - , set_chan_info/4 - , get_chan_info/2 - , get_chan_info/3 - , set_chan_stats/3 - , set_chan_stats/4 - , get_chan_stats/2 - , get_chan_stats/3 - , connection_closed/2 - ]). +-export([ + open_session/5, + open_session/6, + kick_session/2, + kick_session/3, + takeover_session/2, + register_channel/4, + unregister_channel/2, + insert_channel_info/4, + lookup_by_clientid/2, + set_chan_info/3, + set_chan_info/4, + get_chan_info/2, + get_chan_info/3, + set_chan_stats/3, + set_chan_stats/4, + get_chan_stats/2, + get_chan_stats/3, + connection_closed/2 +]). --export([ call/3 - , call/4 - , cast/3 - ]). +-export([ + call/3, + call/4, + cast/3 +]). --export([ with_channel/3 - , lookup_channels/2 - ]). +-export([ + with_channel/3, + lookup_channels/2 +]). %% Internal funcs for getting tabname by GatewayId -export([cmtabs/1, tabname/2]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). %% RPC targets --export([ do_lookup_by_clientid/2 - , do_get_chan_info/3 - , do_set_chan_info/4 - , do_get_chan_stats/3 - , do_set_chan_stats/4 - , do_kick_session/4 - , do_takeover_session/3 - , do_get_chann_conn_mod/3 - , do_call/4 - , do_call/5 - , do_cast/4 - ]). +-export([ + do_lookup_by_clientid/2, + do_get_chan_info/3, + do_set_chan_info/4, + do_get_chan_stats/3, + do_set_chan_stats/4, + do_kick_session/4, + do_takeover_session/3, + do_get_chann_conn_mod/3, + do_call/4, + do_call/5, + do_cast/4 +]). --export_type([ gateway_name/0 - ]). +-export_type([gateway_name/0]). -record(state, { - gwname :: gateway_name(), %% Gateway Name - locker :: pid(), %% ClientId Locker for CM - registry :: pid(), %% ClientId Registry server - chan_pmon :: emqx_pmon:pmon() - }). + %% Gateway Name + gwname :: gateway_name(), + %% ClientId Locker for CM + locker :: pid(), + %% ClientId Registry server + registry :: pid(), + chan_pmon :: emqx_pmon:pmon() +}). -type option() :: {gwname, gateway_name()}. -type options() :: list(option()). @@ -117,14 +124,16 @@ start_link(Options) -> procname(GwName) -> list_to_atom(lists:concat([emqx_gateway_, GwName, '_cm'])). --spec cmtabs(GwName :: gateway_name()) - -> {ChanTab :: atom(), - ConnTab :: atom(), - ChannInfoTab :: atom()}. +-spec cmtabs(GwName :: gateway_name()) -> + {ChanTab :: atom(), ConnTab :: atom(), ChannInfoTab :: atom()}. cmtabs(GwName) -> - { tabname(chan, GwName) %% Record: {ClientId, Pid} - , tabname(conn, GwName) %% Record: {{ClientId, Pid}, ConnMod} - , tabname(info, GwName) %% Record: {{ClientId, Pid}, Info, Stats} + %% Record: {ClientId, Pid} + { + tabname(chan, GwName), + %% Record: {{ClientId, Pid}, ConnMod} + tabname(conn, GwName), + %% Record: {{ClientId, Pid}, Info, Stats} + tabname(info, GwName) }. tabname(chan, GwName) -> @@ -137,10 +146,12 @@ tabname(info, GwName) -> lockername(GwName) -> list_to_atom(lists:concat([emqx_gateway_, GwName, '_locker'])). --spec register_channel(gateway_name(), - emqx_types:clientid(), - pid(), - emqx_types:conninfo()) -> ok. +-spec register_channel( + gateway_name(), + emqx_types:clientid(), + pid(), + emqx_types:conninfo() +) -> ok. register_channel(GwName, ClientId, ChanPid, #{conn_mod := ConnMod}) when is_pid(ChanPid) -> Chan = {ClientId, ChanPid}, true = ets:insert(tabname(chan, GwName), Chan), @@ -155,31 +166,36 @@ unregister_channel(GwName, ClientId) when is_binary(ClientId) -> ok. %% @doc Insert/Update the channel info and stats --spec insert_channel_info(gateway_name(), - emqx_types:clientid(), - emqx_types:infos(), - emqx_types:stats()) -> ok. +-spec insert_channel_info( + gateway_name(), + emqx_types:clientid(), + emqx_types:infos(), + emqx_types:stats() +) -> ok. insert_channel_info(GwName, ClientId, Info, Stats) -> Chan = {ClientId, self()}, true = ets:insert(tabname(info, GwName), {Chan, Info, Stats}), ok. %% @doc Get info of a channel. --spec get_chan_info(gateway_name(), emqx_types:clientid()) - -> emqx_types:infos() | undefined. +-spec get_chan_info(gateway_name(), emqx_types:clientid()) -> + emqx_types:infos() | undefined. get_chan_info(GwName, ClientId) -> - with_channel(GwName, ClientId, + with_channel( + GwName, + ClientId, fun(ChanPid) -> get_chan_info(GwName, ClientId, ChanPid) - end). + end + ). -spec do_lookup_by_clientid(gateway_name(), emqx_types:clientid()) -> [pid()]. do_lookup_by_clientid(GwName, ClientId) -> ChanTab = emqx_gateway_cm:tabname(chan, GwName), [Pid || {_, Pid} <- ets:lookup(ChanTab, ClientId)]. --spec do_get_chan_info(gateway_name(), emqx_types:clientid(), pid()) - -> emqx_types:infos() | undefined. +-spec do_get_chan_info(gateway_name(), emqx_types:clientid(), pid()) -> + emqx_types:infos() | undefined. do_get_chan_info(GwName, ClientId, ChanPid) -> Chan = {ClientId, ChanPid}, try @@ -189,18 +205,21 @@ do_get_chan_info(GwName, ClientId, ChanPid) -> error:badarg -> undefined end. --spec get_chan_info(gateway_name(), emqx_types:clientid(), pid()) - -> emqx_types:infos() | undefined. +-spec get_chan_info(gateway_name(), emqx_types:clientid(), pid()) -> + emqx_types:infos() | undefined. get_chan_info(GwName, ClientId, ChanPid) -> wrap_rpc( - emqx_gateway_cm_proto_v1:get_chan_info(GwName, ClientId, ChanPid) - ). + emqx_gateway_cm_proto_v1:get_chan_info(GwName, ClientId, ChanPid) + ). -spec lookup_by_clientid(gateway_name(), emqx_types:clientid()) -> [pid()]. lookup_by_clientid(GwName, ClientId) -> Nodes = mria_mnesia:running_nodes(), - case emqx_gateway_cm_proto_v1:lookup_by_clientid( - Nodes, GwName, ClientId) of + case + emqx_gateway_cm_proto_v1:lookup_by_clientid( + Nodes, GwName, ClientId + ) + of {Pids, []} -> lists:append(Pids); {_, _BadNodes} -> @@ -208,74 +227,92 @@ lookup_by_clientid(GwName, ClientId) -> end. %% @doc Update infos of the channel. --spec set_chan_info(gateway_name(), - emqx_types:clientid(), - emqx_types:infos()) -> boolean(). +-spec set_chan_info( + gateway_name(), + emqx_types:clientid(), + emqx_types:infos() +) -> boolean(). set_chan_info(GwName, ClientId, Infos) -> set_chan_info(GwName, ClientId, self(), Infos). --spec do_set_chan_info(gateway_name(), - emqx_types:clientid(), - pid(), - emqx_types:infos()) -> boolean(). +-spec do_set_chan_info( + gateway_name(), + emqx_types:clientid(), + pid(), + emqx_types:infos() +) -> boolean(). do_set_chan_info(GwName, ClientId, ChanPid, Infos) -> Chan = {ClientId, ChanPid}, - try ets:update_element(tabname(info, GwName), Chan, {2, Infos}) + try + ets:update_element(tabname(info, GwName), Chan, {2, Infos}) catch error:badarg -> false end. --spec set_chan_info(gateway_name(), - emqx_types:clientid(), - pid(), - emqx_types:infos()) -> boolean(). +-spec set_chan_info( + gateway_name(), + emqx_types:clientid(), + pid(), + emqx_types:infos() +) -> boolean(). set_chan_info(GwName, ClientId, ChanPid, Infos) -> wrap_rpc(emqx_gateway_cm_proto_v1:set_chan_info(GwName, ClientId, ChanPid, Infos)). %% @doc Get channel's stats. --spec get_chan_stats(gateway_name(), emqx_types:clientid()) - -> emqx_types:stats() | undefined. +-spec get_chan_stats(gateway_name(), emqx_types:clientid()) -> + emqx_types:stats() | undefined. get_chan_stats(GwName, ClientId) -> - with_channel(GwName, ClientId, + with_channel( + GwName, + ClientId, fun(ChanPid) -> get_chan_stats(GwName, ClientId, ChanPid) - end). + end + ). --spec do_get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) - -> emqx_types:stats() | undefined. +-spec do_get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) -> + emqx_types:stats() | undefined. do_get_chan_stats(GwName, ClientId, ChanPid) -> Chan = {ClientId, ChanPid}, - try ets:lookup_element(tabname(info, GwName), Chan, 3) + try + ets:lookup_element(tabname(info, GwName), Chan, 3) catch error:badarg -> undefined end. --spec get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) - -> emqx_types:stats() | undefined. +-spec get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) -> + emqx_types:stats() | undefined. get_chan_stats(GwName, ClientId, ChanPid) -> wrap_rpc(emqx_gateway_cm_proto_v1:get_chan_stats(GwName, ClientId, ChanPid)). --spec set_chan_stats(gateway_name(), - emqx_types:clientid(), - emqx_types:stats()) -> boolean(). +-spec set_chan_stats( + gateway_name(), + emqx_types:clientid(), + emqx_types:stats() +) -> boolean(). set_chan_stats(GwName, ClientId, Stats) -> set_chan_stats(GwName, ClientId, self(), Stats). --spec do_set_chan_stats(gateway_name(), - emqx_types:clientid(), - pid(), - emqx_types:stats()) -> boolean(). +-spec do_set_chan_stats( + gateway_name(), + emqx_types:clientid(), + pid(), + emqx_types:stats() +) -> boolean(). do_set_chan_stats(GwName, ClientId, ChanPid, Stats) -> Chan = {ClientId, ChanPid}, - try ets:update_element(tabname(info, GwName), Chan, {3, Stats}) + try + ets:update_element(tabname(info, GwName), Chan, {3, Stats}) catch error:badarg -> false end. --spec set_chan_stats(gateway_name(), - emqx_types:clientid(), - pid(), - emqx_types:stats()) -> boolean(). +-spec set_chan_stats( + gateway_name(), + emqx_types:clientid(), + pid(), + emqx_types:stats() +) -> boolean(). set_chan_stats(GwName, ClientId, ChanPid, Stats) -> wrap_rpc(emqx_gateway_cm_proto_v1:set_chan_stats(GwName, ClientId, ChanPid, Stats)). @@ -285,18 +322,24 @@ connection_closed(GwName, ClientId) -> Chan = {ClientId, self()}, ets:delete_object(tabname(conn, GwName), Chan). --spec open_session(GwName :: gateway_name(), - CleanStart :: boolean(), - ClientInfo :: emqx_types:clientinfo(), - ConnInfo :: emqx_types:conninfo(), - CreateSessionFun :: fun((emqx_types:clientinfo(), - emqx_types:conninfo()) -> Session - )) - -> {ok, #{session := Session, - present := boolean(), - pendings => list() - }} - | {error, any()}. +-spec open_session( + GwName :: gateway_name(), + CleanStart :: boolean(), + ClientInfo :: emqx_types:clientinfo(), + ConnInfo :: emqx_types:conninfo(), + CreateSessionFun :: fun( + ( + emqx_types:clientinfo(), + emqx_types:conninfo() + ) -> Session + ) +) -> + {ok, #{ + session := Session, + present := boolean(), + pendings => list() + }} + | {error, any()}. open_session(GwName, CleanStart, ClientInfo, ConnInfo, CreateSessionFun) -> open_session(GwName, CleanStart, ClientInfo, ConnInfo, CreateSessionFun, emqx_session). @@ -305,21 +348,26 @@ open_session(GwName, true = _CleanStart, ClientInfo, ConnInfo, CreateSessionFun, Self = self(), ClientId = maps:get(clientid, ClientInfo), Fun = fun(_) -> - _ = discard_session(GwName, ClientId), - Session = create_session(GwName, - ClientInfo, - ConnInfo, - CreateSessionFun, - SessionMod - ), - register_channel(GwName, ClientId, Self, ConnInfo), - {ok, #{session => Session, present => false}} - end, + _ = discard_session(GwName, ClientId), + Session = create_session( + GwName, + ClientInfo, + ConnInfo, + CreateSessionFun, + SessionMod + ), + register_channel(GwName, ClientId, Self, ConnInfo), + {ok, #{session => Session, present => false}} + end, locker_trans(GwName, ClientId, Fun); - -open_session(GwName, false = _CleanStart, - ClientInfo = #{clientid := ClientId}, - ConnInfo, CreateSessionFun, SessionMod) -> +open_session( + GwName, + false = _CleanStart, + ClientInfo = #{clientid := ClientId}, + ConnInfo, + CreateSessionFun, + SessionMod +) -> Self = self(), ResumeStart = @@ -327,79 +375,98 @@ open_session(GwName, false = _CleanStart, CreateSess = fun() -> Session = create_session( - GwName, ClientInfo, ConnInfo, - CreateSessionFun, SessionMod), - register_channel( - GwName, ClientId, Self, ConnInfo), - {ok, #{session => Session, present => false}} + GwName, + ClientInfo, + ConnInfo, + CreateSessionFun, + SessionMod + ), + register_channel( + GwName, ClientId, Self, ConnInfo + ), + {ok, #{session => Session, present => false}} end, - case takeover_session(GwName, ClientId) of - {ok, ConnMod, ChanPid, Session} -> - ok = emqx_session:resume(ClientInfo, Session), - case request_stepdown({takeover, 'end'}, ConnMod, ChanPid) of - {ok, Pendings} -> - register_channel( - GwName, ClientId, Self, ConnInfo), - {ok, #{session => Session, - present => true, - pendings => Pendings}}; - {error, _} -> - CreateSess() - end; - {error, _Reason} -> CreateSess() - end - end, + case takeover_session(GwName, ClientId) of + {ok, ConnMod, ChanPid, Session} -> + ok = emqx_session:resume(ClientInfo, Session), + case request_stepdown({takeover, 'end'}, ConnMod, ChanPid) of + {ok, Pendings} -> + register_channel( + GwName, ClientId, Self, ConnInfo + ), + {ok, #{ + session => Session, + present => true, + pendings => Pendings + }}; + {error, _} -> + CreateSess() + end; + {error, _Reason} -> + CreateSess() + end + end, locker_trans(GwName, ClientId, ResumeStart). %% @private create_session(GwName, ClientInfo, ConnInfo, CreateSessionFun, SessionMod) -> try Session = emqx_gateway_utils:apply( - CreateSessionFun, - [ClientInfo, ConnInfo] - ), + CreateSessionFun, + [ClientInfo, ConnInfo] + ), ok = emqx_gateway_metrics:inc(GwName, 'session.created'), - SessionInfo = case is_tuple(Session) - andalso element(1, Session) == session of - true -> SessionMod:info(Session); - _ -> - case is_map(Session) of - false -> - throw(session_structure_should_be_map); - _ -> - Session - end - end, + SessionInfo = + case + is_tuple(Session) andalso + element(1, Session) == session + of + true -> + SessionMod:info(Session); + _ -> + case is_map(Session) of + false -> + throw(session_structure_should_be_map); + _ -> + Session + end + end, ok = emqx_hooks:run('session.created', [ClientInfo, SessionInfo]), Session catch - Class : Reason : Stk -> - ?SLOG(error, #{ msg => "failed_create_session" - , clientid => maps:get(clientid, ClientInfo, undefined) - , username => maps:get(username, ClientInfo, undefined) - , reason => {Class, Reason} - , stacktrace => Stk - }), - throw(Reason) + Class:Reason:Stk -> + ?SLOG(error, #{ + msg => "failed_create_session", + clientid => maps:get(clientid, ClientInfo, undefined), + username => maps:get(username, ClientInfo, undefined), + reason => {Class, Reason}, + stacktrace => Stk + }), + throw(Reason) end. %% @doc Try to takeover a session. --spec(takeover_session(gateway_name(), emqx_types:clientid()) - -> {error, term()} - | {ok, atom(), pid(), emqx_session:session()}). +-spec takeover_session(gateway_name(), emqx_types:clientid()) -> + {error, term()} + | {ok, atom(), pid(), emqx_session:session()}. takeover_session(GwName, ClientId) -> case lookup_channels(GwName, ClientId) of - [] -> {error, not_found}; + [] -> + {error, not_found}; [ChanPid] -> do_takeover_session(GwName, ClientId, ChanPid); ChanPids -> [ChanPid | StalePids] = lists:reverse(ChanPids), - ?SLOG(warning, #{ msg => "more_than_one_channel_found" - , chan_pids => ChanPids - }), - lists:foreach(fun(StalePid) -> - catch discard_session(GwName, ClientId, StalePid) - end, StalePids), + ?SLOG(warning, #{ + msg => "more_than_one_channel_found", + chan_pids => ChanPids + }), + lists:foreach( + fun(StalePid) -> + catch discard_session(GwName, ClientId, StalePid) + end, + StalePids + ), do_takeover_session(GwName, ClientId, ChanPid) end. @@ -432,17 +499,26 @@ discard_session(GwName, ClientId, ChanPid) -> -spec kick_session(gateway_name(), emqx_types:clientid()) -> ok | {error, not_found}. kick_session(GwName, ClientId) -> case lookup_channels(GwName, ClientId) of - [] -> {error, not_found}; + [] -> + {error, not_found}; ChanPids -> - ChanPids > 1 andalso begin - ?SLOG(warning, #{ msg => "more_than_one_channel_found" - , chan_pids => ChanPids - }, - #{clientid => ClientId}) - end, - lists:foreach(fun(Pid) -> - _ = kick_session(GwName, ClientId, Pid) - end, ChanPids) + ChanPids > 1 andalso + begin + ?SLOG( + warning, + #{ + msg => "more_than_one_channel_found", + chan_pids => ChanPids + }, + #{clientid => ClientId} + ) + end, + lists:foreach( + fun(Pid) -> + _ = kick_session(GwName, ClientId, Pid) + end, + ChanPids + ) end. kick_session(GwName, ClientId, ChanPid) -> @@ -453,27 +529,34 @@ kick_session(GwName, Action, ClientId, ChanPid) -> try wrap_rpc(emqx_gateway_cm_proto_v1:kick_session(GwName, Action, ClientId, ChanPid)) catch - Error : Reason -> + Error:Reason -> %% This should mostly be RPC failures. %% However, if the node is still running the old version %% code (prior to emqx app 4.3.10) some of the RPC handler %% exceptions may get propagated to a new version node - ?SLOG(error, #{ msg => "failed_to_kick_session_on_remote_node" - , node => node(ChanPid) - , action => Action - , error => Error - , reason => Reason - }, - #{clientid => ClientId}) + ?SLOG( + error, + #{ + msg => "failed_to_kick_session_on_remote_node", + node => node(ChanPid), + action => Action, + error => Error, + reason => Reason + }, + #{clientid => ClientId} + ) end. --spec do_kick_session(gateway_name(), - kick | discard, - emqx_types:clientid(), - pid()) -> ok. +-spec do_kick_session( + gateway_name(), + kick | discard, + emqx_types:clientid(), + pid() +) -> ok. do_kick_session(GwName, Action, ClientId, ChanPid) -> case get_chann_conn_mod(GwName, ClientId, ChanPid) of - undefined -> ok; + undefined -> + ok; ConnMod when is_atom(ConnMod) -> ok = request_stepdown(Action, ConnMod, ChanPid) end. @@ -482,56 +565,67 @@ do_kick_session(GwName, Action, ClientId, ChanPid) -> %% If failed to response (e.g. timeout) force a kill. %% Keeping the stale pid around, or returning error or raise an exception %% benefits nobody. --spec request_stepdown(Action, module(), pid()) --> ok - | {ok, emqx_session:session() | list(emqx_type:deliver())} - | {error, term()} - when Action :: kick | discard | {takeover, 'begin'} | {takeover, 'end'}. +-spec request_stepdown(Action, module(), pid()) -> + ok + | {ok, emqx_session:session() | list(emqx_type:deliver())} + | {error, term()} +when + Action :: kick | discard | {takeover, 'begin'} | {takeover, 'end'}. request_stepdown(Action, ConnMod, Pid) -> Timeout = - case Action == kick orelse Action == discard of - true -> ?T_KICK; - _ -> ?T_TAKEOVER - end, + case Action == kick orelse Action == discard of + true -> ?T_KICK; + _ -> ?T_TAKEOVER + end, Return = - %% this is essentailly a gen_server:call implemented in emqx_connection - %% and emqx_ws_connection. - %% the handle_call is implemented in emqx_channel - try apply(ConnMod, call, [Pid, Action, Timeout]) of - ok -> ok; - Reply -> {ok, Reply} - catch - _ : noproc -> % emqx_ws_connection: call - ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), - {error, noproc}; - _ : {noproc, _} -> % emqx_connection: gen_server:call - ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), - {error, noproc}; - _ : Reason = {shutdown, _} -> - ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), - {error, Reason}; - _ : Reason = {{shutdown, _}, _} -> - ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), - {error, Reason}; - _ : {timeout, {gen_server, call, _}} -> - ?tp(warning, "session_stepdown_request_timeout", - #{pid => Pid, - action => Action, - stale_channel => stale_channel_info(Pid) - }), - ok = force_kill(Pid), - {error, timeout}; - _ : Error : St -> - ?tp(error, "session_stepdown_request_exception", - #{pid => Pid, - action => Action, - reason => Error, - stacktrace => St, - stale_channel => stale_channel_info(Pid) - }), - ok = force_kill(Pid), - {error, Error} - end, + %% this is essentailly a gen_server:call implemented in emqx_connection + %% and emqx_ws_connection. + %% the handle_call is implemented in emqx_channel + try apply(ConnMod, call, [Pid, Action, Timeout]) of + ok -> ok; + Reply -> {ok, Reply} + catch + % emqx_ws_connection: call + _:noproc -> + ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), + {error, noproc}; + % emqx_connection: gen_server:call + _:{noproc, _} -> + ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), + {error, noproc}; + _:Reason = {shutdown, _} -> + ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), + {error, Reason}; + _:Reason = {{shutdown, _}, _} -> + ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), + {error, Reason}; + _:{timeout, {gen_server, call, _}} -> + ?tp( + warning, + "session_stepdown_request_timeout", + #{ + pid => Pid, + action => Action, + stale_channel => stale_channel_info(Pid) + } + ), + ok = force_kill(Pid), + {error, timeout}; + _:Error:St -> + ?tp( + error, + "session_stepdown_request_exception", + #{ + pid => Pid, + action => Action, + reason => Error, + stacktrace => St, + stale_channel => stale_channel_info(Pid) + } + ), + ok = force_kill(Pid), + {error, Error} + end, case Action == kick orelse Action == discard of true -> ok; _ -> Return @@ -546,20 +640,22 @@ stale_channel_info(Pid) -> with_channel(GwName, ClientId, Fun) -> case lookup_channels(GwName, ClientId) of - [] -> undefined; + [] -> undefined; [Pid] -> Fun(Pid); - Pids -> Fun(lists:last(Pids)) + Pids -> Fun(lists:last(Pids)) end. %% @doc Lookup channels. --spec(lookup_channels(gateway_name(), emqx_types:clientid()) -> list(pid())). +-spec lookup_channels(gateway_name(), emqx_types:clientid()) -> list(pid()). lookup_channels(GwName, ClientId) -> emqx_gateway_cm_registry:lookup_channels(GwName, ClientId). -spec do_get_chann_conn_mod(gateway_name(), emqx_types:clientid(), pid()) -> atom(). do_get_chann_conn_mod(GwName, ClientId, ChanPid) -> Chan = {ClientId, ChanPid}, - try [ConnMod] = ets:lookup_element(tabname(conn, GwName), Chan, 2), ConnMod + try + [ConnMod] = ets:lookup_element(tabname(conn, GwName), Chan, 2), + ConnMod catch error:badarg -> undefined end. @@ -568,28 +664,33 @@ do_get_chann_conn_mod(GwName, ClientId, ChanPid) -> get_chann_conn_mod(GwName, ClientId, ChanPid) -> wrap_rpc(emqx_gateway_cm_proto_v1:get_chann_conn_mod(GwName, ClientId, ChanPid)). --spec call(gateway_name(), emqx_types:clientid(), term()) - -> undefined | term(). +-spec call(gateway_name(), emqx_types:clientid(), term()) -> + undefined | term(). call(GwName, ClientId, Req) -> with_channel( - GwName, ClientId, - fun(ChanPid) -> - wrap_rpc( - emqx_gateway_cm_proto_v1:call(GwName, ClientId, ChanPid, Req) - ) - end). + GwName, + ClientId, + fun(ChanPid) -> + wrap_rpc( + emqx_gateway_cm_proto_v1:call(GwName, ClientId, ChanPid, Req) + ) + end + ). --spec call(gateway_name(), emqx_types:clientid(), term(), timeout()) - -> undefined | term(). +-spec call(gateway_name(), emqx_types:clientid(), term(), timeout()) -> + undefined | term(). call(GwName, ClientId, Req, Timeout) -> with_channel( - GwName, ClientId, - fun(ChanPid) -> - wrap_rpc( - emqx_gateway_cm_proto_v1:call( - GwName, ClientId, ChanPid, Req, Timeout) - ) - end). + GwName, + ClientId, + fun(ChanPid) -> + wrap_rpc( + emqx_gateway_cm_proto_v1:call( + GwName, ClientId, ChanPid, Req, Timeout + ) + ) + end + ). do_call(GwName, ClientId, ChanPid, Req) -> case do_get_chann_conn_mod(GwName, ClientId, ChanPid) of @@ -606,11 +707,14 @@ do_call(GwName, ClientId, ChanPid, Req, Timeout) -> -spec cast(gateway_name(), emqx_types:clientid(), term()) -> ok. cast(GwName, ClientId, Req) -> with_channel( - GwName, ClientId, - fun(ChanPid) -> - wrap_rpc( - emqx_gateway_cm_proto_v1:cast(GwName, ClientId, ChanPid, Req)) - end), + GwName, + ClientId, + fun(ChanPid) -> + wrap_rpc( + emqx_gateway_cm_proto_v1:cast(GwName, ClientId, ChanPid, Req) + ) + end + ), ok. do_cast(GwName, ClientId, ChanPid, Req) -> @@ -627,7 +731,11 @@ locker_trans(GwName, ClientId, Fun) -> Locker = lockername(GwName), case locker_lock(Locker, ClientId) of {true, Nodes} -> - try Fun(Nodes) after locker_unlock(Locker, ClientId) end; + try + Fun(Nodes) + after + locker_unlock(Locker, ClientId) + end; {false, _Nodes} -> {error, client_id_unavailable} end. @@ -673,10 +781,12 @@ init(Options) -> %% TODO: v0.2 %ok = emqx_stats:update_interval(chan_stats, fun ?MODULE:stats_fun/0), - {ok, #state{gwname = GwName, - locker = Locker, - registry = Registry, - chan_pmon = emqx_pmon:new()}}. + {ok, #state{ + gwname = GwName, + locker = Locker, + registry = Registry, + chan_pmon = emqx_pmon:new() + }}. handle_call(_Request, _From, State) -> Reply = ok, @@ -685,19 +795,19 @@ handle_call(_Request, _From, State) -> handle_cast({registered, {ClientId, ChanPid}}, State = #state{chan_pmon = PMon}) -> PMon1 = emqx_pmon:monitor(ChanPid, ClientId, PMon), {noreply, State#state{chan_pmon = PMon1}}; - handle_cast(_Msg, State) -> {noreply, State}. -handle_info({'DOWN', _MRef, process, Pid, _Reason}, - State = #state{gwname = GwName, chan_pmon = PMon}) -> +handle_info( + {'DOWN', _MRef, process, Pid, _Reason}, + State = #state{gwname = GwName, chan_pmon = PMon} +) -> ChanPids = [Pid | emqx_misc:drain_down(?DEFAULT_BATCH_SIZE)], {Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon), CmTabs = cmtabs(GwName), ok = emqx_pool:async_submit(fun do_unregister_channel_task/3, [Items, GwName, CmTabs]), {noreply, State#state{chan_pmon = PMon1}}; - handle_info(_Info, State) -> {noreply, State}. @@ -711,9 +821,11 @@ code_change(_OldVsn, State, _Extra) -> do_unregister_channel_task(Items, GwName, CmTabs) -> lists:foreach( - fun({ChanPid, ClientId}) -> - do_unregister_channel(GwName, {ClientId, ChanPid}, CmTabs) - end, Items). + fun({ChanPid, ClientId}) -> + do_unregister_channel(GwName, {ClientId, ChanPid}, CmTabs) + end, + Items + ). %%-------------------------------------------------------------------- %% Internal funcs diff --git a/apps/emqx_gateway/src/emqx_gateway_cm_registry.erl b/apps/emqx_gateway/src/emqx_gateway_cm_registry.erl index a81dc6c3b..65532deaa 100644 --- a/apps/emqx_gateway/src/emqx_gateway_cm_registry.erl +++ b/apps/emqx_gateway/src/emqx_gateway_cm_registry.erl @@ -23,22 +23,24 @@ -export([start_link/1]). --export([ register_channel/2 - , unregister_channel/2 - ]). +-export([ + register_channel/2, + unregister_channel/2 +]). -export([lookup_channels/2]). -export([tabname/1]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -define(CM_SHARD, emqx_gateway_cm_shard). -define(LOCK, {?MODULE, cleanup_down}). @@ -46,7 +48,7 @@ -record(channel, {chid, pid}). %% @doc Start the global channel registry for the given gateway name. --spec(start_link(gateway_name()) -> gen_server:startlink_ret()). +-spec start_link(gateway_name()) -> gen_server:startlink_ret(). start_link(Name) -> gen_server:start_link(?MODULE, [Name], []). @@ -63,25 +65,27 @@ tabname(Name) -> -spec register_channel(gateway_name(), binary() | {binary(), pid()}) -> ok. register_channel(Name, ClientId) when is_binary(ClientId) -> register_channel(Name, {ClientId, self()}); - -register_channel(Name, {ClientId, ChanPid}) - when is_binary(ClientId), is_pid(ChanPid) -> +register_channel(Name, {ClientId, ChanPid}) when + is_binary(ClientId), is_pid(ChanPid) +-> mria:dirty_write(tabname(Name), record(ClientId, ChanPid)). %% @doc Unregister a global channel. -spec unregister_channel(gateway_name(), binary() | {binary(), pid()}) -> ok. unregister_channel(Name, ClientId) when is_binary(ClientId) -> unregister_channel(Name, {ClientId, self()}); - -unregister_channel(Name, {ClientId, ChanPid}) - when is_binary(ClientId), is_pid(ChanPid) -> +unregister_channel(Name, {ClientId, ChanPid}) when + is_binary(ClientId), is_pid(ChanPid) +-> mria:dirty_delete_object(tabname(Name), record(ClientId, ChanPid)). %% @doc Lookup the global channels. -spec lookup_channels(gateway_name(), binary()) -> list(pid()). lookup_channels(Name, ClientId) -> - [ChanPid - || #channel{pid = ChanPid} <- mnesia:dirty_read(tabname(Name), ClientId)]. + [ + ChanPid + || #channel{pid = ChanPid} <- mnesia:dirty_read(tabname(Name), ClientId) + ]. record(ClientId, ChanPid) -> #channel{chid = ClientId, pid = ChanPid}. @@ -93,13 +97,18 @@ record(ClientId, ChanPid) -> init([Name]) -> Tab = tabname(Name), ok = mria:create_table(Tab, [ - {type, bag}, - {rlog_shard, ?CM_SHARD}, - {storage, ram_copies}, - {record_name, channel}, - {attributes, record_info(fields, channel)}, - {storage_properties, [{ets, [{read_concurrency, true}, - {write_concurrency, true}]}]}]), + {type, bag}, + {rlog_shard, ?CM_SHARD}, + {storage, ram_copies}, + {record_name, channel}, + {attributes, record_info(fields, channel)}, + {storage_properties, [ + {ets, [ + {read_concurrency, true}, + {write_concurrency, true} + ]} + ]} + ]), ok = mria:wait_for_tables([Tab]), ok = ekka:monitor(membership), {ok, #{name => Name}}. @@ -115,14 +124,11 @@ handle_cast(Msg, State) -> handle_info({membership, {mnesia, down, Node}}, State = #{name := Name}) -> cleanup_channels(Node, Name), {noreply, State}; - handle_info({membership, {node, down, Node}}, State = #{name := Name}) -> cleanup_channels(Node, Name), {noreply, State}; - handle_info({membership, _Event}, State) -> {noreply, State}; - handle_info(Info, State) -> logger:error("Unexpected info: ~p", [Info]), {noreply, State}. @@ -140,13 +146,17 @@ code_change(_OldVsn, State, _Extra) -> cleanup_channels(Node, Name) -> Tab = tabname(Name), global:trans( - {?LOCK, self()}, - fun() -> - mria:transaction(?CM_SHARD, fun do_cleanup_channels/2, [Node, Tab]) - end). + {?LOCK, self()}, + fun() -> + mria:transaction(?CM_SHARD, fun do_cleanup_channels/2, [Node, Tab]) + end + ). do_cleanup_channels(Node, Tab) -> Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}], - lists:foreach(fun(Chan) -> - mnesia:delete_object(Tab, Chan, write) - end, mnesia:select(Tab, Pat, write)). + lists:foreach( + fun(Chan) -> + mnesia:delete_object(Tab, Chan, write) + end, + mnesia:select(Tab, Pat, write) + ). diff --git a/apps/emqx_gateway/src/emqx_gateway_conf.erl b/apps/emqx_gateway/src/emqx_gateway_conf.erl index 52aa204a3..1ac8c50f8 100644 --- a/apps/emqx_gateway/src/emqx_gateway_conf.erl +++ b/apps/emqx_gateway/src/emqx_gateway_conf.erl @@ -20,41 +20,47 @@ -behaviour(emqx_config_handler). %% Load/Unload --export([ load/0 - , unload/0 - ]). +-export([ + load/0, + unload/0 +]). %% APIs --export([ gateway/1 - , load_gateway/2 - , update_gateway/2 - , unload_gateway/1 - ]). +-export([ + gateway/1, + load_gateway/2, + update_gateway/2, + unload_gateway/1 +]). --export([ listeners/1 - , listener/1 - , add_listener/3 - , update_listener/3 - , remove_listener/2 - ]). +-export([ + listeners/1, + listener/1, + add_listener/3, + update_listener/3, + remove_listener/2 +]). --export([ add_authn/2 - , add_authn/3 - , update_authn/2 - , update_authn/3 - , remove_authn/1 - , remove_authn/2 - ]). +-export([ + add_authn/2, + add_authn/3, + update_authn/2, + update_authn/3, + remove_authn/1, + remove_authn/2 +]). %% internal exports --export([ unconvert_listeners/1 - , convert_listeners/2 - ]). +-export([ + unconvert_listeners/1, + convert_listeners/2 +]). %% callbacks for emqx_config_handler --export([ pre_config_update/3 - , post_config_update/5 - ]). +-export([ + pre_config_update/3, + post_config_update/5 +]). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_authentication.hrl"). @@ -63,8 +69,7 @@ -type atom_or_bin() :: atom() | binary(). -type ok_or_err() :: ok | {error, term()}. -type map_or_err() :: {ok, map()} | {error, term()}. --type listener_ref() :: {ListenerType :: atom_or_bin(), - ListenerName :: atom_or_bin()}. +-type listener_ref() :: {ListenerType :: atom_or_bin(), ListenerName :: atom_or_bin()}. %%-------------------------------------------------------------------- %% Load/Unload @@ -83,21 +88,25 @@ unload() -> -spec load_gateway(atom_or_bin(), map()) -> map_or_err(). load_gateway(GwName, Conf) -> - NConf = case maps:take(<<"listeners">>, Conf) of - error -> Conf; - {Ls, Conf1} -> - Conf1#{<<"listeners">> => unconvert_listeners(Ls)} - end, + NConf = + case maps:take(<<"listeners">>, Conf) of + error -> Conf; + {Ls, Conf1} -> Conf1#{<<"listeners">> => unconvert_listeners(Ls)} + end, ret_gw(GwName, update({?FUNCTION_NAME, bin(GwName), NConf})). %% @doc convert listener array to map unconvert_listeners(Ls) when is_list(Ls) -> - lists:foldl(fun(Lis, Acc) -> - %% FIXME: params apperence guard? - {[Type, Name], Lis1} = maps_key_take([<<"type">>, <<"name">>], Lis), - NLis1 = maps:without([<<"id">>], Lis1), - emqx_map_lib:deep_merge(Acc, #{Type => #{Name => NLis1}}) - end, #{}, Ls). + lists:foldl( + fun(Lis, Acc) -> + %% FIXME: params apperence guard? + {[Type, Name], Lis1} = maps_key_take([<<"type">>, <<"name">>], Lis), + NLis1 = maps:without([<<"id">>], Lis1), + emqx_map_lib:deep_merge(Acc, #{Type => #{Name => NLis1}}) + end, + #{}, + Ls + ). maps_key_take(Ks, M) -> maps_key_take(Ks, M, []). @@ -106,8 +115,7 @@ maps_key_take([], M, Acc) -> maps_key_take([K | Ks], M, Acc) -> case maps:take(K, M) of error -> throw(bad_key); - {V, M1} -> - maps_key_take(Ks, M1, [V | Acc]) + {V, M1} -> maps_key_take(Ks, M1, [V | Acc]) end. -spec update_gateway(atom_or_bin(), map()) -> map_or_err(). @@ -131,32 +139,38 @@ gateway(GwName0) -> GwName = bin(GwName0), Path = [<<"gateway">>, GwName], RawConf = emqx_config:fill_defaults( - emqx_config:get_root_raw(Path) - ), + emqx_config:get_root_raw(Path) + ), Confs = emqx_map_lib:jsonable_map( - emqx_map_lib:deep_get(Path, RawConf)), + emqx_map_lib:deep_get(Path, RawConf) + ), LsConf = maps:get(<<"listeners">>, Confs, #{}), Confs#{<<"listeners">> => convert_listeners(GwName, LsConf)}. %% @doc convert listeners map to array convert_listeners(GwName, Ls) when is_map(Ls) -> - lists:append([do_convert_listener(GwName, Type, maps:to_list(Conf)) - || {Type, Conf} <- maps:to_list(Ls)]). + lists:append([ + do_convert_listener(GwName, Type, maps:to_list(Conf)) + || {Type, Conf} <- maps:to_list(Ls) + ]). do_convert_listener(GwName, LType, Conf) -> - [ do_convert_listener2(GwName, LType, LName, LConf) - || {LName, LConf} <- Conf, is_map(LConf)]. + [ + do_convert_listener2(GwName, LType, LName, LConf) + || {LName, LConf} <- Conf, is_map(LConf) + ]. do_convert_listener2(GwName, LType, LName, LConf) -> - ListenerId = emqx_gateway_utils:listener_id(GwName, LType, LName), - Running = emqx_gateway_utils:is_running(ListenerId, LConf), - bind2str( - LConf#{ - id => ListenerId, - type => LType, - name => LName, - running => Running - }). + ListenerId = emqx_gateway_utils:listener_id(GwName, LType, LName), + Running = emqx_gateway_utils:is_running(ListenerId, LConf), + bind2str( + LConf#{ + id => ListenerId, + type => LType, + name => LName, + running => Running + } + ). bind2str(LConf = #{bind := Bind}) when is_integer(Bind) -> maps:put(bind, integer_to_binary(Bind), LConf); @@ -169,48 +183,60 @@ bind2str(LConf = #{<<"bind">> := Bind}) when is_binary(Bind) -> -spec listeners(atom_or_bin()) -> [map()]. listeners(GwName0) -> - GwName = bin(GwName0), - RawConf = emqx_config:fill_defaults( - emqx_config:get_root_raw([<<"gateway">>])), - Listeners = emqx_map_lib:jsonable_map( - emqx_map_lib:deep_get( - [<<"gateway">>, GwName, <<"listeners">>], RawConf)), - convert_listeners(GwName, Listeners). + GwName = bin(GwName0), + RawConf = emqx_config:fill_defaults( + emqx_config:get_root_raw([<<"gateway">>]) + ), + Listeners = emqx_map_lib:jsonable_map( + emqx_map_lib:deep_get( + [<<"gateway">>, GwName, <<"listeners">>], RawConf + ) + ), + convert_listeners(GwName, Listeners). -spec listener(binary()) -> {ok, map()} | {error, not_found} | {error, any()}. listener(ListenerId) -> {GwName, Type, LName} = emqx_gateway_utils:parse_listener_id(ListenerId), RootConf = emqx_config:fill_defaults( - emqx_config:get_root_raw([<<"gateway">>])), + emqx_config:get_root_raw([<<"gateway">>]) + ), try Path = [<<"gateway">>, GwName, <<"listeners">>, Type, LName], LConf = emqx_map_lib:deep_get(Path, RootConf), Running = emqx_gateway_utils:is_running( - binary_to_existing_atom(ListenerId), LConf), - {ok, emqx_map_lib:jsonable_map( - LConf#{ - id => ListenerId, - type => Type, - name => LName, - running => Running})} + binary_to_existing_atom(ListenerId), LConf + ), + {ok, + emqx_map_lib:jsonable_map( + LConf#{ + id => ListenerId, + type => Type, + name => LName, + running => Running + } + )} catch - error : {config_not_found, _} -> + error:{config_not_found, _} -> {error, not_found}; - _Class : Reason -> + _Class:Reason -> {error, Reason} end. -spec add_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err(). add_listener(GwName, ListenerRef, Conf) -> ret_listener_or_err( - GwName, ListenerRef, - update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). + GwName, + ListenerRef, + update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf}) + ). -spec update_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err(). update_listener(GwName, ListenerRef, Conf) -> ret_listener_or_err( - GwName, ListenerRef, - update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). + GwName, + ListenerRef, + update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf}) + ). -spec remove_listener(atom_or_bin(), listener_ref()) -> ok_or_err(). remove_listener(GwName, ListenerRef) -> @@ -223,8 +249,10 @@ add_authn(GwName, Conf) -> -spec add_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err(). add_authn(GwName, ListenerRef, Conf) -> ret_authn( - GwName, ListenerRef, - update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). + GwName, + ListenerRef, + update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf}) + ). -spec update_authn(atom_or_bin(), map()) -> map_or_err(). update_authn(GwName, Conf) -> @@ -233,8 +261,10 @@ update_authn(GwName, Conf) -> -spec update_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err(). update_authn(GwName, ListenerRef, Conf) -> ret_authn( - GwName, ListenerRef, - update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). + GwName, + ListenerRef, + update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf}) + ). -spec remove_authn(atom_or_bin()) -> ok_or_err(). remove_authn(GwName) -> @@ -249,8 +279,8 @@ update(Req) -> res(emqx_conf:update([gateway], Req, #{override_to => cluster})). res({ok, Result}) -> {ok, Result}; -res({error, {pre_config_update,?MODULE,Reason}}) -> {error, Reason}; -res({error, {post_config_update,?MODULE,Reason}}) -> {error, Reason}; +res({error, {pre_config_update, ?MODULE, Reason}}) -> {error, Reason}; +res({error, {post_config_update, ?MODULE, Reason}}) -> {error, Reason}; res({error, Reason}) -> {error, Reason}. bin({LType, LName}) -> @@ -266,38 +296,58 @@ ret_ok_err(Err) -> Err. ret_gw(GwName, {ok, #{raw_config := GwConf}}) -> GwConf1 = emqx_map_lib:deep_get([bin(GwName)], GwConf), LsConf = emqx_map_lib:deep_get( - [bin(GwName), <<"listeners">>], - GwConf, #{}), + [bin(GwName), <<"listeners">>], + GwConf, + #{} + ), NLsConf = - lists:foldl(fun({LType, SubConf}, Acc) -> - NLConfs = - lists:map(fun({LName, LConf}) -> - do_convert_listener2(GwName, LType, LName, LConf) - end, maps:to_list(SubConf)), - [NLConfs | Acc] - end, [], maps:to_list(LsConf)), + lists:foldl( + fun({LType, SubConf}, Acc) -> + NLConfs = + lists:map( + fun({LName, LConf}) -> + do_convert_listener2(GwName, LType, LName, LConf) + end, + maps:to_list(SubConf) + ), + [NLConfs | Acc] + end, + [], + maps:to_list(LsConf) + ), {ok, maps:merge(GwConf1, #{<<"listeners">> => lists:append(NLsConf)})}; -ret_gw(_GwName, Err) -> Err. +ret_gw(_GwName, Err) -> + Err. ret_authn(GwName, {ok, #{raw_config := GwConf}}) -> Authn = emqx_map_lib:deep_get( - [bin(GwName), <<"authentication">>], - GwConf), + [bin(GwName), <<"authentication">>], + GwConf + ), {ok, Authn}; -ret_authn(_GwName, Err) -> Err. +ret_authn(_GwName, Err) -> + Err. ret_authn(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) -> Authn = emqx_map_lib:deep_get( - [bin(GwName), <<"listeners">>, bin(LType), - bin(LName), <<"authentication">>], - GwConf), + [ + bin(GwName), + <<"listeners">>, + bin(LType), + bin(LName), + <<"authentication">> + ], + GwConf + ), {ok, Authn}; -ret_authn(_, _, Err) -> Err. +ret_authn(_, _, Err) -> + Err. ret_listener_or_err(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) -> LConf = emqx_map_lib:deep_get( - [bin(GwName), <<"listeners">>, bin(LType), bin(LName)], - GwConf), + [bin(GwName), <<"listeners">>, bin(LType), bin(LName)], + GwConf + ), {ok, do_convert_listener2(GwName, LType, LName, LConf)}; ret_listener_or_err(_, _, Err) -> Err. @@ -306,9 +356,11 @@ ret_listener_or_err(_, _, Err) -> %% Config Handler %%-------------------------------------------------------------------- --spec pre_config_update(list(atom()), - emqx_config:update_request(), - emqx_config:raw_config()) -> +-spec pre_config_update( + list(atom()), + emqx_config:update_request(), + emqx_config:raw_config() +) -> {ok, emqx_config:update_request()} | {error, term()}. pre_config_update(_, {load_gateway, GwName, Conf}, RawConf) -> case maps:get(GwName, RawConf, undefined) of @@ -327,89 +379,119 @@ pre_config_update(_, {update_gateway, GwName, Conf}, RawConf) -> {ok, emqx_map_lib:deep_merge(RawConf, #{GwName => NConf})} end; pre_config_update(_, {unload_gateway, GwName}, RawConf) -> - _ = tune_gw_certs(fun clear_certs/2, - GwName, - maps:get(GwName, RawConf, #{}) - ), + _ = tune_gw_certs( + fun clear_certs/2, + GwName, + maps:get(GwName, RawConf, #{}) + ), {ok, maps:remove(GwName, RawConf)}; - pre_config_update(_, {add_listener, GwName, {LType, LName}, Conf}, RawConf) -> - case emqx_map_lib:deep_get( - [GwName, <<"listeners">>, LType, LName], RawConf, undefined) of + case + emqx_map_lib:deep_get( + [GwName, <<"listeners">>, LType, LName], RawConf, undefined + ) + of undefined -> NConf = convert_certs(certs_dir(GwName), Conf), NListener = #{LType => #{LName => NConf}}, - {ok, emqx_map_lib:deep_merge( - RawConf, - #{GwName => #{<<"listeners">> => NListener}})}; + {ok, + emqx_map_lib:deep_merge( + RawConf, + #{GwName => #{<<"listeners">> => NListener}} + )}; _ -> badres_listener(already_exist, GwName, LType, LName) end; pre_config_update(_, {update_listener, GwName, {LType, LName}, Conf}, RawConf) -> - case emqx_map_lib:deep_get( - [GwName, <<"listeners">>, LType, LName], RawConf, undefined) of + case + emqx_map_lib:deep_get( + [GwName, <<"listeners">>, LType, LName], RawConf, undefined + ) + of undefined -> badres_listener(not_found, GwName, LType, LName); OldConf -> NConf = convert_certs(certs_dir(GwName), Conf, OldConf), NListener = #{LType => #{LName => NConf}}, - {ok, emqx_map_lib:deep_merge( - RawConf, - #{GwName => #{<<"listeners">> => NListener}})} - + {ok, + emqx_map_lib:deep_merge( + RawConf, + #{GwName => #{<<"listeners">> => NListener}} + )} end; pre_config_update(_, {remove_listener, GwName, {LType, LName}}, RawConf) -> Path = [GwName, <<"listeners">>, LType, LName], case emqx_map_lib:deep_get(Path, RawConf, undefined) of - undefined -> + undefined -> {ok, RawConf}; OldConf -> clear_certs(certs_dir(GwName), OldConf), {ok, emqx_map_lib:deep_remove(Path, RawConf)} end; - pre_config_update(_, {add_authn, GwName, Conf}, RawConf) -> - case emqx_map_lib:deep_get( - [GwName, ?AUTHN_BIN], RawConf, undefined) of + case + emqx_map_lib:deep_get( + [GwName, ?AUTHN_BIN], RawConf, undefined + ) + of undefined -> - {ok, emqx_map_lib:deep_merge( - RawConf, - #{GwName => #{?AUTHN_BIN => Conf}})}; + {ok, + emqx_map_lib:deep_merge( + RawConf, + #{GwName => #{?AUTHN_BIN => Conf}} + )}; _ -> badres_authn(already_exist, GwName) end; pre_config_update(_, {add_authn, GwName, {LType, LName}, Conf}, RawConf) -> - case emqx_map_lib:deep_get( - [GwName, <<"listeners">>, LType, LName], - RawConf, undefined) of + case + emqx_map_lib:deep_get( + [GwName, <<"listeners">>, LType, LName], + RawConf, + undefined + ) + of undefined -> badres_listener(not_found, GwName, LType, LName); Listener -> case maps:get(?AUTHN_BIN, Listener, undefined) of undefined -> NListener = maps:put(?AUTHN_BIN, Conf, Listener), - NGateway = #{GwName => - #{<<"listeners">> => - #{LType => #{LName => NListener}}}}, + NGateway = #{ + GwName => + #{ + <<"listeners">> => + #{LType => #{LName => NListener}} + } + }, {ok, emqx_map_lib:deep_merge(RawConf, NGateway)}; _ -> badres_listener_authn(already_exist, GwName, LType, LName) end end; pre_config_update(_, {update_authn, GwName, Conf}, RawConf) -> - case emqx_map_lib:deep_get( - [GwName, ?AUTHN_BIN], RawConf, undefined) of + case + emqx_map_lib:deep_get( + [GwName, ?AUTHN_BIN], RawConf, undefined + ) + of undefined -> badres_authn(not_found, GwName); _ -> - {ok, emqx_map_lib:deep_merge( - RawConf, - #{GwName => #{?AUTHN_BIN => Conf}})} + {ok, + emqx_map_lib:deep_merge( + RawConf, + #{GwName => #{?AUTHN_BIN => Conf}} + )} end; pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) -> - case emqx_map_lib:deep_get( - [GwName, <<"listeners">>, LType, LName], - RawConf, undefined) of + case + emqx_map_lib:deep_get( + [GwName, <<"listeners">>, LType, LName], + RawConf, + undefined + ) + of undefined -> badres_listener(not_found, GwName, LType, LName); Listener -> @@ -418,76 +500,116 @@ pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) -> badres_listener_authn(not_found, GwName, LType, LName); Auth -> NListener = maps:put( - ?AUTHN_BIN, - emqx_map_lib:deep_merge(Auth, Conf), - Listener - ), - NGateway = #{GwName => - #{<<"listeners">> => - #{LType => #{LName => NListener}}}}, + ?AUTHN_BIN, + emqx_map_lib:deep_merge(Auth, Conf), + Listener + ), + NGateway = #{ + GwName => + #{ + <<"listeners">> => + #{LType => #{LName => NListener}} + } + }, {ok, emqx_map_lib:deep_merge(RawConf, NGateway)} end end; pre_config_update(_, {remove_authn, GwName}, RawConf) -> - {ok, emqx_map_lib:deep_remove( - [GwName, ?AUTHN_BIN], RawConf)}; + {ok, + emqx_map_lib:deep_remove( + [GwName, ?AUTHN_BIN], RawConf + )}; pre_config_update(_, {remove_authn, GwName, {LType, LName}}, RawConf) -> Path = [GwName, <<"listeners">>, LType, LName, ?AUTHN_BIN], {ok, emqx_map_lib:deep_remove(Path, RawConf)}; - pre_config_update(_, UnknownReq, _RawConf) -> logger:error("Unknown configuration update request: ~0p", [UnknownReq]), {error, badreq}. badres_gateway(not_found, GwName) -> - {error, {badres, #{resource => gateway, gateway => GwName, - reason => not_found}}}; + {error, + {badres, #{ + resource => gateway, + gateway => GwName, + reason => not_found + }}}; badres_gateway(already_exist, GwName) -> - {error, {badres, #{resource => gateway, gateway => GwName, - reason => already_exist}}}. + {error, + {badres, #{ + resource => gateway, + gateway => GwName, + reason => already_exist + }}}. badres_listener(not_found, GwName, LType, LName) -> - {error, {badres, #{resource => listener, gateway => GwName, - listener => {GwName, LType, LName}, - reason => not_found}}}; + {error, + {badres, #{ + resource => listener, + gateway => GwName, + listener => {GwName, LType, LName}, + reason => not_found + }}}; badres_listener(already_exist, GwName, LType, LName) -> - {error, {badres, #{resource => listener, gateway => GwName, - listener => {GwName, LType, LName}, - reason => already_exist}}}. + {error, + {badres, #{ + resource => listener, + gateway => GwName, + listener => {GwName, LType, LName}, + reason => already_exist + }}}. badres_authn(not_found, GwName) -> - {error, {badres, #{resource => authn, gateway => GwName, - reason => not_found}}}; + {error, + {badres, #{ + resource => authn, + gateway => GwName, + reason => not_found + }}}; badres_authn(already_exist, GwName) -> - {error, {badres, #{resource => authn, gateway => GwName, - reason => already_exist}}}. + {error, + {badres, #{ + resource => authn, + gateway => GwName, + reason => already_exist + }}}. badres_listener_authn(not_found, GwName, LType, LName) -> - {error, {badres, #{resource => listener_authn, gateway => GwName, - listener => {GwName, LType, LName}, - reason => not_found}}}; + {error, + {badres, #{ + resource => listener_authn, + gateway => GwName, + listener => {GwName, LType, LName}, + reason => not_found + }}}; badres_listener_authn(already_exist, GwName, LType, LName) -> - {error, {badres, #{resource => listener_authn, gateway => GwName, - listener => {GwName, LType, LName}, - reason => already_exist}}}. + {error, + {badres, #{ + resource => listener_authn, + gateway => GwName, + listener => {GwName, LType, LName}, + reason => already_exist + }}}. --spec post_config_update(list(atom()), - emqx_config:update_request(), - emqx_config:config(), - emqx_config:config(), emqx_config:app_envs()) - -> ok | {ok, Result::any()} | {error, Reason::term()}. +-spec post_config_update( + list(atom()), + emqx_config:update_request(), + emqx_config:config(), + emqx_config:config(), + emqx_config:app_envs() +) -> + ok | {ok, Result :: any()} | {error, Reason :: term()}. post_config_update(_, Req, NewConfig, OldConfig, _AppEnvs) when is_tuple(Req) -> [_Tag, GwName0 | _] = tuple_to_list(Req), GwName = binary_to_existing_atom(GwName0), - case {maps:get(GwName, NewConfig, undefined), - maps:get(GwName, OldConfig, undefined)} of + case {maps:get(GwName, NewConfig, undefined), maps:get(GwName, OldConfig, undefined)} of {undefined, undefined} -> - ok; %% nothing to change + %% nothing to change + ok; {undefined, Old} when is_map(Old) -> emqx_gateway:unload(GwName); - {New, undefined} when is_map(New) -> + {New, undefined} when is_map(New) -> emqx_gateway:load(GwName, New); {New, Old} when is_map(New), is_map(Old) -> emqx_gateway:update(GwName, New) @@ -499,29 +621,39 @@ post_config_update(_, _Req, _NewConfig, _OldConfig, _AppEnvs) -> %% Internal funcs %%-------------------------------------------------------------------- - tune_gw_certs(Fun, GwName, Conf) -> SubDir = certs_dir(GwName), case maps:get(<<"listeners">>, Conf, undefined) of - undefined -> Conf; + undefined -> + Conf; Liss -> - maps:put(<<"listeners">>, - maps:map(fun(_, Lis) -> - maps:map(fun(_, LisConf) -> - erlang:apply(Fun, [SubDir, LisConf]) - end, Lis) - end, Liss), - Conf) - end. + maps:put( + <<"listeners">>, + maps:map( + fun(_, Lis) -> + maps:map( + fun(_, LisConf) -> + erlang:apply(Fun, [SubDir, LisConf]) + end, + Lis + ) + end, + Liss + ), + Conf + ) + end. certs_dir(GwName) when is_binary(GwName) -> GwName. convert_certs(SubDir, Conf) -> - case emqx_tls_lib:ensure_ssl_files( - SubDir, - maps:get(<<"ssl">>, Conf, undefined) - ) of + case + emqx_tls_lib:ensure_ssl_files( + SubDir, + maps:get(<<"ssl">>, Conf, undefined) + ) + of {ok, SSL} -> new_ssl_config(Conf, SSL); {error, Reason} -> diff --git a/apps/emqx_gateway/src/emqx_gateway_ctx.erl b/apps/emqx_gateway/src/emqx_gateway_ctx.erl index ab81c1ddb..dfe7c9466 100644 --- a/apps/emqx_gateway/src/emqx_gateway_ctx.erl +++ b/apps/emqx_gateway/src/emqx_gateway_ctx.erl @@ -19,7 +19,6 @@ -include("include/emqx_gateway.hrl"). - %% @doc The running context for a Connection/Channel process. %% %% The `Context` encapsulates a complex structure of contextual information. @@ -27,45 +26,50 @@ %% configuration, register devices and other common operations. %% -type context() :: - #{ %% Gateway Name - gwname := gateway_name() - %% Authentication chains - , auth := [emqx_authentication:chain_name()] - %% The ConnectionManager PID - , cm := pid() - }. + %% Gateway Name + #{ + gwname := gateway_name(), + %% Authentication chains + auth := [emqx_authentication:chain_name()], + %% The ConnectionManager PID + cm := pid() + }. %% Authentication circle --export([ authenticate/2 - , open_session/5 - , open_session/6 - , insert_channel_info/4 - , set_chan_info/3 - , set_chan_stats/3 - , connection_closed/2 - ]). +-export([ + authenticate/2, + open_session/5, + open_session/6, + insert_channel_info/4, + set_chan_info/3, + set_chan_stats/3, + connection_closed/2 +]). %% Message circle --export([ authorize/4 - % Needless for pub/sub - %, publish/3 - %, subscribe/4 - ]). +-export([ + authorize/4 + % Needless for pub/sub + %, publish/3 + %, subscribe/4 +]). %% Metrics & Stats --export([ metrics_inc/2 - , metrics_inc/3 - ]). +-export([ + metrics_inc/2, + metrics_inc/3 +]). %%-------------------------------------------------------------------- %% Authentication circle %% @doc Authenticate whether the client has access to the Broker. --spec authenticate(context(), emqx_types:clientinfo()) - -> {ok, emqx_types:clientinfo()} - | {error, any()}. -authenticate(_Ctx = #{auth := _ChainNames}, ClientInfo0) - when is_list(_ChainNames) -> +-spec authenticate(context(), emqx_types:clientinfo()) -> + {ok, emqx_types:clientinfo()} + | {error, any()}. +authenticate(_Ctx = #{auth := _ChainNames}, ClientInfo0) when + is_list(_ChainNames) +-> ClientInfo = ClientInfo0#{zone => default}, case emqx_access_control:authenticate(ClientInfo) of {ok, _} -> @@ -78,43 +82,74 @@ authenticate(_Ctx = #{auth := _ChainNames}, ClientInfo0) %% %% This function should be called after the client has authenticated %% successfully so that the client can be managed in the cluster. --spec open_session(context(), boolean(), emqx_types:clientinfo(), - emqx_types:conninfo(), - fun((emqx_types:clientinfo(), - emqx_types:conninfo()) -> Session) - ) - -> {ok, #{session := Session, - present := boolean(), - pendings => list() - }} - | {error, any()}. +-spec open_session( + context(), + boolean(), + emqx_types:clientinfo(), + emqx_types:conninfo(), + fun( + ( + emqx_types:clientinfo(), + emqx_types:conninfo() + ) -> Session + ) +) -> + {ok, #{ + session := Session, + present := boolean(), + pendings => list() + }} + | {error, any()}. open_session(Ctx, CleanStart, ClientInfo, ConnInfo, CreateSessionFun) -> - open_session(Ctx, CleanStart, ClientInfo, ConnInfo, - CreateSessionFun, emqx_session). + open_session( + Ctx, + CleanStart, + ClientInfo, + ConnInfo, + CreateSessionFun, + emqx_session + ). -open_session(_Ctx = #{gwname := GwName}, - CleanStart, ClientInfo, ConnInfo, CreateSessionFun, SessionMod) -> - emqx_gateway_cm:open_session(GwName, CleanStart, - ClientInfo, ConnInfo, - CreateSessionFun, SessionMod). +open_session( + _Ctx = #{gwname := GwName}, + CleanStart, + ClientInfo, + ConnInfo, + CreateSessionFun, + SessionMod +) -> + emqx_gateway_cm:open_session( + GwName, + CleanStart, + ClientInfo, + ConnInfo, + CreateSessionFun, + SessionMod + ). --spec insert_channel_info(context(), - emqx_types:clientid(), - emqx_types:infos(), - emqx_types:stats()) -> ok. +-spec insert_channel_info( + context(), + emqx_types:clientid(), + emqx_types:infos(), + emqx_types:stats() +) -> ok. insert_channel_info(_Ctx = #{gwname := GwName}, ClientId, Infos, Stats) -> emqx_gateway_cm:insert_channel_info(GwName, ClientId, Infos, Stats). %% @doc Set the Channel Info to the ConnectionManager for this client --spec set_chan_info(context(), - emqx_types:clientid(), - emqx_types:infos()) -> boolean(). +-spec set_chan_info( + context(), + emqx_types:clientid(), + emqx_types:infos() +) -> boolean(). set_chan_info(_Ctx = #{gwname := GwName}, ClientId, Infos) -> emqx_gateway_cm:set_chan_info(GwName, ClientId, Infos). --spec set_chan_stats(context(), - emqx_types:clientid(), - emqx_types:stats()) -> boolean(). +-spec set_chan_stats( + context(), + emqx_types:clientid(), + emqx_types:stats() +) -> boolean(). set_chan_stats(_Ctx = #{gwname := GwName}, ClientId, Stats) -> emqx_gateway_cm:set_chan_stats(GwName, ClientId, Stats). @@ -122,9 +157,13 @@ set_chan_stats(_Ctx = #{gwname := GwName}, ClientId, Stats) -> connection_closed(_Ctx = #{gwname := GwName}, ClientId) -> emqx_gateway_cm:connection_closed(GwName, ClientId). --spec authorize(context(), emqx_types:clientinfo(), - emqx_types:pubsub(), emqx_types:topic()) - -> allow | deny. +-spec authorize( + context(), + emqx_types:clientinfo(), + emqx_types:pubsub(), + emqx_types:topic() +) -> + allow | deny. authorize(_Ctx, ClientInfo, PubSub, Topic) -> emqx_access_control:authorize(ClientInfo, PubSub, Topic). diff --git a/apps/emqx_gateway/src/emqx_gateway_gw_sup.erl b/apps/emqx_gateway/src/emqx_gateway_gw_sup.erl index 9ca806213..2d91622af 100644 --- a/apps/emqx_gateway/src/emqx_gateway_gw_sup.erl +++ b/apps/emqx_gateway/src/emqx_gateway_gw_sup.erl @@ -27,13 +27,14 @@ -export([start_link/1]). --export([ create_insta/3 - , remove_insta/2 - , update_insta/3 - , start_insta/2 - , stop_insta/2 - , list_insta/1 - ]). +-export([ + create_insta/3, + remove_insta/2, + update_insta/3, + start_insta/2, + stop_insta/2, + list_insta/1 +]). %% Supervisor callbacks -export([init/1]). @@ -48,70 +49,72 @@ start_link(GwName) -> -spec create_insta(pid(), gateway(), map()) -> {ok, GwInstaPid :: pid()} | {error, any()}. create_insta(Sup, Gateway = #{name := Name}, GwDscrptr) -> case emqx_gateway_utils:find_sup_child(Sup, Name) of - {ok, _GwInstaPid} -> {error, alredy_existed}; + {ok, _GwInstaPid} -> + {error, alredy_existed}; false -> Ctx = ctx(Sup, Name), ChildSpec = emqx_gateway_utils:childspec( - Name, - worker, - emqx_gateway_insta_sup, - [Gateway, Ctx, GwDscrptr] - ), + Name, + worker, + emqx_gateway_insta_sup, + [Gateway, Ctx, GwDscrptr] + ), emqx_gateway_utils:supervisor_ret( - supervisor:start_child(Sup, ChildSpec) - ) + supervisor:start_child(Sup, ChildSpec) + ) end. -spec remove_insta(pid(), Name :: gateway_name()) -> ok | {error, any()}. remove_insta(Sup, Name) -> case emqx_gateway_utils:find_sup_child(Sup, Name) of - false -> ok; + false -> + ok; {ok, _GwInstaPid} -> ok = supervisor:terminate_child(Sup, Name), ok = supervisor:delete_child(Sup, Name) end. --spec update_insta(pid(), gateway_name(), emqx_config:config()) - -> ok | {error, any()}. +-spec update_insta(pid(), gateway_name(), emqx_config:config()) -> + ok | {error, any()}. update_insta(Sup, Name, Config) -> case emqx_gateway_utils:find_sup_child(Sup, Name) of false -> {error, not_found}; - {ok, GwInstaPid} -> - emqx_gateway_insta_sup:update(GwInstaPid, Config) + {ok, GwInstaPid} -> emqx_gateway_insta_sup:update(GwInstaPid, Config) end. -spec start_insta(pid(), gateway_name()) -> ok | {error, any()}. start_insta(Sup, Name) -> case emqx_gateway_utils:find_sup_child(Sup, Name) of false -> {error, not_found}; - {ok, GwInstaPid} -> - emqx_gateway_insta_sup:enable(GwInstaPid) + {ok, GwInstaPid} -> emqx_gateway_insta_sup:enable(GwInstaPid) end. -spec stop_insta(pid(), gateway_name()) -> ok | {error, any()}. stop_insta(Sup, Name) -> case emqx_gateway_utils:find_sup_child(Sup, Name) of false -> {error, not_found}; - {ok, GwInstaPid} -> - emqx_gateway_insta_sup:disable(GwInstaPid) + {ok, GwInstaPid} -> emqx_gateway_insta_sup:disable(GwInstaPid) end. -spec list_insta(pid()) -> [gateway()]. list_insta(Sup) -> lists:filtermap( - fun({Name, GwInstaPid, _Type, _Mods}) -> - is_gateway_insta_id(Name) - andalso {true, emqx_gateway_insta_sup:info(GwInstaPid)} - end, supervisor:which_children(Sup)). + fun({Name, GwInstaPid, _Type, _Mods}) -> + is_gateway_insta_id(Name) andalso + {true, emqx_gateway_insta_sup:info(GwInstaPid)} + end, + supervisor:which_children(Sup) + ). %% Supervisor callback %% @doc Initialize Top Supervisor for a Protocol init([GwName]) -> - SupFlags = #{ strategy => one_for_one - , intensity => 10 - , period => 60 - }, + SupFlags = #{ + strategy => one_for_one, + intensity => 10, + period => 60 + }, CmOpts = [{gwname, GwName}], CM = emqx_gateway_utils:childspec(worker, emqx_gateway_cm, [CmOpts]), Metrics = emqx_gateway_utils:childspec(worker, emqx_gateway_metrics, [GwName]), @@ -122,10 +125,11 @@ init([GwName]) -> %%-------------------------------------------------------------------- ctx(Sup, Name) -> - {ok, CM} = emqx_gateway_utils:find_sup_child(Sup, emqx_gateway_cm), - #{ gwname => Name - , cm => CM - }. + {ok, CM} = emqx_gateway_utils:find_sup_child(Sup, emqx_gateway_cm), + #{ + gwname => Name, + cm => CM + }. is_gateway_insta_id(emqx_gateway_cm) -> false; diff --git a/apps/emqx_gateway/src/emqx_gateway_http.erl b/apps/emqx_gateway/src/emqx_gateway_http.erl index b53ccd737..4cf0783a7 100644 --- a/apps/emqx_gateway/src/emqx_gateway_http.erl +++ b/apps/emqx_gateway/src/emqx_gateway_http.erl @@ -26,58 +26,63 @@ -import(emqx_gateway_utils, [listener_id/3]). %% Mgmt APIs - gateway --export([ gateways/1 - ]). +-export([gateways/1]). %% Mgmt APIs --export([ add_listener/2 - , remove_listener/1 - , update_listener/2 - ]). +-export([ + add_listener/2, + remove_listener/1, + update_listener/2 +]). --export([ authn/1 - , authn/2 - , add_authn/2 - , add_authn/3 - , update_authn/2 - , update_authn/3 - , remove_authn/1 - , remove_authn/2 - ]). +-export([ + authn/1, + authn/2, + add_authn/2, + add_authn/3, + update_authn/2, + update_authn/3, + remove_authn/1, + remove_authn/2 +]). %% Mgmt APIs - clients --export([ lookup_client/3 - , kickout_client/2 - , list_client_subscriptions/2 - , client_subscribe/4 - , client_unsubscribe/3 - ]). +-export([ + lookup_client/3, + kickout_client/2, + list_client_subscriptions/2, + client_subscribe/4, + client_unsubscribe/3 +]). %% Utils for http, swagger, etc. --export([ return_http_error/2 - , with_gateway/2 - , with_authn/2 - , with_listener_authn/3 - , checks/2 - , reason2resp/1 - , reason2msg/1 - ]). +-export([ + return_http_error/2, + with_gateway/2, + with_authn/2, + with_listener_authn/3, + checks/2, + reason2resp/1, + reason2msg/1 +]). -type gateway_summary() :: - #{ name := binary() - , status := running | stopped | unloaded - , created_at => binary() - , started_at => binary() - , stopped_at => binary() - , max_connections => integer() - , current_connections => integer() - , listeners => [] - }. + #{ + name := binary(), + status := running | stopped | unloaded, + created_at => binary(), + started_at => binary(), + stopped_at => binary(), + max_connections => integer(), + current_connections => integer(), + listeners => [] + }. --elvis([ {elvis_style, god_modules, disable} - , {elvis_style, no_nested_try_catch, disable} - , {elvis_style, invalid_dynamic_call, disable} - ]). +-elvis([ + {elvis_style, god_modules, disable}, + {elvis_style, no_nested_try_catch, disable}, + {elvis_style, invalid_dynamic_call, disable} +]). -define(DEFAULT_CALL_TIMEOUT, 15000). @@ -85,63 +90,81 @@ %% Mgmt APIs - gateway %%-------------------------------------------------------------------- --spec gateways(Status :: all | running | stopped | unloaded) - -> [gateway_summary()]. +-spec gateways(Status :: all | running | stopped | unloaded) -> + [gateway_summary()]. gateways(Status) -> - Gateways = lists:map(fun({GwName, _}) -> - case emqx_gateway:lookup(GwName) of - undefined -> #{name => GwName, status => unloaded}; - GwInfo = #{config := Config} -> - GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339( - [created_at, started_at, stopped_at], - GwInfo), - GwInfo1 = maps:with([name, - status, - created_at, - started_at, - stopped_at], GwInfo0), - GwInfo1#{ - max_connections => max_connections_count(Config), - current_connections => current_connections_count(GwName), - listeners => get_listeners_status(GwName, Config)} - end - end, emqx_gateway_registry:list()), + Gateways = lists:map( + fun({GwName, _}) -> + case emqx_gateway:lookup(GwName) of + undefined -> + #{name => GwName, status => unloaded}; + GwInfo = #{config := Config} -> + GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339( + [created_at, started_at, stopped_at], + GwInfo + ), + GwInfo1 = maps:with( + [ + name, + status, + created_at, + started_at, + stopped_at + ], + GwInfo0 + ), + GwInfo1#{ + max_connections => max_connections_count(Config), + current_connections => current_connections_count(GwName), + listeners => get_listeners_status(GwName, Config) + } + end + end, + emqx_gateway_registry:list() + ), case Status of all -> Gateways; - _ -> - [Gw || Gw = #{status := S} <- Gateways, S == Status] + _ -> [Gw || Gw = #{status := S} <- Gateways, S == Status] end. %% @private max_connections_count(Config) -> Listeners = emqx_gateway_utils:normalize_config(Config), - lists:foldl(fun({_, _, _, SocketOpts, _}, Acc) -> - Acc + proplists:get_value(max_connections, SocketOpts, 0) - end, 0, Listeners). + lists:foldl( + fun({_, _, _, SocketOpts, _}, Acc) -> + Acc + proplists:get_value(max_connections, SocketOpts, 0) + end, + 0, + Listeners + ). %% @private current_connections_count(GwName) -> try InfoTab = emqx_gateway_cm:tabname(info, GwName), ets:info(InfoTab, size) - catch _ : _ -> - 0 + catch + _:_ -> + 0 end. %% @private get_listeners_status(GwName, Config) -> Listeners = emqx_gateway_utils:normalize_config(Config), - lists:map(fun({Type, LisName, ListenOn, _, _}) -> - Name0 = listener_id(GwName, Type, LisName), - Name = {Name0, ListenOn}, - LisO = #{id => Name0, type => Type, name => LisName}, - case catch esockd:listener(Name) of - _Pid when is_pid(_Pid) -> - LisO#{running => true}; - _ -> - LisO#{running => false} - end - end, Listeners). + lists:map( + fun({Type, LisName, ListenOn, _, _}) -> + Name0 = listener_id(GwName, Type, LisName), + Name = {Name0, ListenOn}, + LisO = #{id => Name0, type => Type, name => LisName}, + case catch esockd:listener(Name) of + _Pid when is_pid(_Pid) -> + LisO#{running => true}; + _ -> + LisO#{running => false} + end + end, + Listeners + ). %%-------------------------------------------------------------------- %% Mgmt APIs - listeners @@ -150,16 +173,30 @@ get_listeners_status(GwName, Config) -> -spec add_listener(atom() | binary(), map()) -> {ok, map()}. add_listener(ListenerId, NewConf0) -> {GwName, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId), - NewConf = maps:without([<<"id">>, <<"name">>, - <<"type">>, <<"running">>], NewConf0), + NewConf = maps:without( + [ + <<"id">>, + <<"name">>, + <<"type">>, + <<"running">> + ], + NewConf0 + ), confexp(emqx_gateway_conf:add_listener(GwName, {Type, Name}, NewConf)). -spec update_listener(atom() | binary(), map()) -> {ok, map()}. update_listener(ListenerId, NewConf0) -> {GwName, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId), - NewConf = maps:without([<<"id">>, <<"name">>, - <<"type">>, <<"running">>], NewConf0), + NewConf = maps:without( + [ + <<"id">>, + <<"name">>, + <<"type">>, + <<"running">> + ], + NewConf0 + ), confexp(emqx_gateway_conf:update_listener(GwName, {Type, Name}, NewConf)). -spec remove_listener(binary()) -> ok. @@ -173,9 +210,9 @@ authn(GwName) -> Path = [gateway, GwName, ?AUTHN], ChainName = emqx_gateway_utils:global_chain(GwName), wrap_chain_name( - ChainName, - emqx_map_lib:jsonable_map(emqx:get_config(Path)) - ). + ChainName, + emqx_map_lib:jsonable_map(emqx:get_config(Path)) + ). -spec authn(gateway_name(), binary()) -> map(). authn(GwName, ListenerId) -> @@ -183,9 +220,9 @@ authn(GwName, ListenerId) -> Path = [gateway, GwName, listeners, Type, Name, ?AUTHN], ChainName = emqx_gateway_utils:listener_chain(GwName, Type, Name), wrap_chain_name( - ChainName, - emqx_map_lib:jsonable_map(emqx:get_config(Path)) - ). + ChainName, + emqx_map_lib:jsonable_map(emqx:get_config(Path)) + ). wrap_chain_name(ChainName, Conf) -> case emqx_authentication:list_authenticators(ChainName) of @@ -230,66 +267,92 @@ confexp({error, Reason}) -> error(Reason). %% Mgmt APIs - clients %%-------------------------------------------------------------------- --spec lookup_client(gateway_name(), - emqx_types:clientid(), {module(), atom()}) -> list(). +-spec lookup_client( + gateway_name(), + emqx_types:clientid(), + {module(), atom()} +) -> list(). lookup_client(GwName, ClientId, {M, F}) -> - [begin - Info = emqx_gateway_cm:get_chan_info(GwName, ClientId, Pid), - Stats = emqx_gateway_cm:get_chan_stats(GwName, ClientId, Pid), - M:F({{ClientId, Pid}, Info, Stats}) - end - || Pid <- emqx_gateway_cm:lookup_by_clientid(GwName, ClientId)]. + [ + begin + Info = emqx_gateway_cm:get_chan_info(GwName, ClientId, Pid), + Stats = emqx_gateway_cm:get_chan_stats(GwName, ClientId, Pid), + M:F({{ClientId, Pid}, Info, Stats}) + end + || Pid <- emqx_gateway_cm:lookup_by_clientid(GwName, ClientId) + ]. --spec kickout_client(gateway_name(), emqx_types:clientid()) - -> {error, any()} - | ok. +-spec kickout_client(gateway_name(), emqx_types:clientid()) -> + {error, any()} + | ok. kickout_client(GwName, ClientId) -> - Results = [emqx_gateway_cm:kick_session(GwName, ClientId, Pid) - || Pid <- emqx_gateway_cm:lookup_by_clientid(GwName, ClientId)], + Results = [ + emqx_gateway_cm:kick_session(GwName, ClientId, Pid) + || Pid <- emqx_gateway_cm:lookup_by_clientid(GwName, ClientId) + ], IsOk = lists:any(fun(Item) -> Item =:= ok end, Results), case {IsOk, Results} of - {true , _ } -> ok; - {_ , []} -> {error, not_found}; - {false, _ } -> lists:last(Results) + {true, _} -> ok; + {_, []} -> {error, not_found}; + {false, _} -> lists:last(Results) end. --spec list_client_subscriptions(gateway_name(), emqx_types:clientid()) - -> {error, any()} - | {ok, list()}. +-spec list_client_subscriptions(gateway_name(), emqx_types:clientid()) -> + {error, any()} + | {ok, list()}. list_client_subscriptions(GwName, ClientId) -> case client_call(GwName, ClientId, subscriptions) of - {error, Reason} -> {error, Reason}; + {error, Reason} -> + {error, Reason}; {ok, Subs} -> - {ok, lists:map(fun({Topic, SubOpts}) -> - SubOpts#{topic => Topic} - end, Subs)} + {ok, + lists:map( + fun({Topic, SubOpts}) -> + SubOpts#{topic => Topic} + end, + Subs + )} end. --spec client_subscribe(gateway_name(), emqx_types:clientid(), - emqx_types:topic(), emqx_types:subopts()) - -> {error, any()} - | {ok, {emqx_types:topic(), emqx_types:subopts()}}. +-spec client_subscribe( + gateway_name(), + emqx_types:clientid(), + emqx_types:topic(), + emqx_types:subopts() +) -> + {error, any()} + | {ok, {emqx_types:topic(), emqx_types:subopts()}}. client_subscribe(GwName, ClientId, Topic, SubOpts) -> client_call(GwName, ClientId, {subscribe, Topic, SubOpts}). --spec client_unsubscribe(gateway_name(), - emqx_types:clientid(), emqx_types:topic()) - -> {error, any()} - | ok. +-spec client_unsubscribe( + gateway_name(), + emqx_types:clientid(), + emqx_types:topic() +) -> + {error, any()} + | ok. client_unsubscribe(GwName, ClientId, Topic) -> client_call(GwName, ClientId, {unsubscribe, Topic}). client_call(GwName, ClientId, Req) -> - try emqx_gateway_cm:call( - GwName, ClientId, - Req, ?DEFAULT_CALL_TIMEOUT) of + try + emqx_gateway_cm:call( + GwName, + ClientId, + Req, + ?DEFAULT_CALL_TIMEOUT + ) + of undefined -> {error, not_found}; - Res -> Res - catch throw : noproc -> - {error, not_found}; - throw : {badrpc, Reason} -> - {error, {badrpc, Reason}} + Res -> + Res + catch + throw:noproc -> + {error, not_found}; + throw:{badrpc, Reason} -> + {error, {badrpc, Reason}} end. %%-------------------------------------------------------------------- @@ -311,54 +374,88 @@ return_http_error(Code, Msg) -> -spec reason2msg({atom(), map()} | any()) -> error | string(). reason2msg({badconf, #{key := Key, value := Value, reason := Reason}}) -> - NValue = case emqx_json:safe_encode(Value) of - {ok, Str} -> Str; - {error, _} -> emqx_gateway_utils:stringfy(Value) - end, - fmtstr("Bad config value '~s' for '~s', reason: ~s", - [NValue, Key, Reason]); -reason2msg({badres, #{resource := gateway, - gateway := GwName, - reason := not_found}}) -> + NValue = + case emqx_json:safe_encode(Value) of + {ok, Str} -> Str; + {error, _} -> emqx_gateway_utils:stringfy(Value) + end, + fmtstr( + "Bad config value '~s' for '~s', reason: ~s", + [NValue, Key, Reason] + ); +reason2msg( + {badres, #{ + resource := gateway, + gateway := GwName, + reason := not_found + }} +) -> fmtstr("The ~s gateway is unloaded", [GwName]); - -reason2msg({badres, #{resource := gateway, - gateway := GwName, - reason := already_exist}}) -> +reason2msg( + {badres, #{ + resource := gateway, + gateway := GwName, + reason := already_exist + }} +) -> fmtstr("The ~s gateway already loaded", [GwName]); - -reason2msg({badres, #{resource := listener, - listener := {GwName, LType, LName}, - reason := not_found}}) -> +reason2msg( + {badres, #{ + resource := listener, + listener := {GwName, LType, LName}, + reason := not_found + }} +) -> fmtstr("Listener ~s not found", [listener_id(GwName, LType, LName)]); - -reason2msg({badres, #{resource := listener, - listener := {GwName, LType, LName}, - reason := already_exist}}) -> - fmtstr("The listener ~s of ~s already exist", - [listener_id(GwName, LType, LName), GwName]); - -reason2msg({badres, #{resource := authn, - gateway := GwName, - reason := not_found}}) -> +reason2msg( + {badres, #{ + resource := listener, + listener := {GwName, LType, LName}, + reason := already_exist + }} +) -> + fmtstr( + "The listener ~s of ~s already exist", + [listener_id(GwName, LType, LName), GwName] + ); +reason2msg( + {badres, #{ + resource := authn, + gateway := GwName, + reason := not_found + }} +) -> fmtstr("The authentication not found on ~s", [GwName]); - -reason2msg({badres, #{resource := authn, - gateway := GwName, - reason := already_exist}}) -> +reason2msg( + {badres, #{ + resource := authn, + gateway := GwName, + reason := already_exist + }} +) -> fmtstr("The authentication already exist on ~s", [GwName]); - -reason2msg({badres, #{resource := listener_authn, - listener := {GwName, LType, LName}, - reason := not_found}}) -> - fmtstr("The authentication not found on ~s", - [listener_id(GwName, LType, LName)]); - -reason2msg({badres, #{resource := listener_authn, - listener := {GwName, LType, LName}, - reason := already_exist}}) -> - fmtstr("The authentication already exist on ~s", - [listener_id(GwName, LType, LName)]); +reason2msg( + {badres, #{ + resource := listener_authn, + listener := {GwName, LType, LName}, + reason := not_found + }} +) -> + fmtstr( + "The authentication not found on ~s", + [listener_id(GwName, LType, LName)] + ); +reason2msg( + {badres, #{ + resource := listener_authn, + listener := {GwName, LType, LName}, + reason := already_exist + }} +) -> + fmtstr( + "The authentication already exist on ~s", + [listener_id(GwName, LType, LName)] + ); reason2msg(_) -> error. @@ -389,10 +486,12 @@ with_listener_authn(GwName0, Id, Fun) -> -spec with_gateway(binary(), function()) -> any(). with_gateway(GwName0, Fun) -> try - GwName = try - binary_to_existing_atom(GwName0) - catch _ : _ -> error(badname) - end, + GwName = + try + binary_to_existing_atom(GwName0) + catch + _:_ -> error(badname) + end, case emqx_gateway:lookup(GwName) of undefined -> return_http_error(404, "Gateway not load"); @@ -400,24 +499,26 @@ with_gateway(GwName0, Fun) -> Fun(GwName, Gateway) end catch - error : badname -> + error:badname -> return_http_error(404, "Bad gateway name"); %% Exceptions from: checks/2 - error : {miss_param, K} -> + error:{miss_param, K} -> return_http_error(400, [K, " is required"]); %% Exceptions from emqx_gateway_utils:parse_listener_id/1 - error : {invalid_listener_id, Id} -> + error:{invalid_listener_id, Id} -> return_http_error(400, ["invalid listener id: ", Id]); %% Exceptions from: emqx:get_config/1 - error : {config_not_found, Path0} -> + error:{config_not_found, Path0} -> Path = lists:concat( - lists:join(".", lists:map(fun to_list/1, Path0))), + lists:join(".", lists:map(fun to_list/1, Path0)) + ), return_http_error(404, "Resource not found. path: " ++ Path); - Class : Reason : Stk -> - ?SLOG(error, #{ msg => "uncatched_error" - , reason => {Class, Reason} - , stacktrace => Stk - }), + Class:Reason:Stk -> + ?SLOG(error, #{ + msg => "uncatched_error", + reason => {Class, Reason}, + stacktrace => Stk + }), reason2resp(Reason) end. @@ -427,8 +528,7 @@ checks([], _) -> checks([K | Ks], Map) -> case maps:is_key(K, Map) of true -> checks(Ks, Map); - false -> - error({miss_param, K}) + false -> error({miss_param, K}) end. to_list(A) when is_atom(A) -> diff --git a/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl b/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl index 2fe087af4..50b7433cf 100644 --- a/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl +++ b/apps/emqx_gateway/src/emqx_gateway_insta_sup.erl @@ -23,34 +23,36 @@ -include_lib("emqx/include/logger.hrl"). %% APIs --export([ start_link/3 - , info/1 - , disable/1 - , enable/1 - , update/2 - ]). +-export([ + start_link/3, + info/1, + disable/1, + enable/1, + update/2 +]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -record(state, { - name :: gateway_name(), - config :: emqx_config:config(), - ctx :: emqx_gateway_ctx:context(), - authns :: [emqx_authentication:chain_name()], - status :: stopped | running, - child_pids :: [pid()], - gw_state :: emqx_gateway_impl:state() | undefined, - created_at :: integer(), - started_at :: integer() | undefined, - stopped_at :: integer() | undefined - }). + name :: gateway_name(), + config :: emqx_config:config(), + ctx :: emqx_gateway_ctx:context(), + authns :: [emqx_authentication:chain_name()], + status :: stopped | running, + child_pids :: [pid()], + gw_state :: emqx_gateway_impl:state() | undefined, + created_at :: integer(), + started_at :: integer() | undefined, + stopped_at :: integer() | undefined +}). -elvis([{elvis_style, invalid_dynamic_call, disable}]). @@ -60,10 +62,10 @@ start_link(Gateway, Ctx, GwDscrptr) -> gen_server:start_link( - ?MODULE, - [Gateway, Ctx, GwDscrptr], - [] - ). + ?MODULE, + [Gateway, Ctx, GwDscrptr], + [] + ). -spec info(pid()) -> gateway(). info(Pid) -> @@ -93,21 +95,22 @@ call(Pid, Req) -> init([Gateway, Ctx, _GwDscrptr]) -> process_flag(trap_exit, true), - #{name := GwName, config := Config } = Gateway, + #{name := GwName, config := Config} = Gateway, State = #state{ - ctx = Ctx, - name = GwName, - authns = [], - config = Config, - child_pids = [], - status = stopped, - created_at = erlang:system_time(millisecond) - }, + ctx = Ctx, + name = GwName, + authns = [], + config = Config, + child_pids = [], + status = stopped, + created_at = erlang:system_time(millisecond) + }, case maps:get(enable, Config, true) of false -> - ?SLOG(info, #{ msg => "skip_to_start_gateway_due_to_disabled" - , gateway_name => GwName - }), + ?SLOG(info, #{ + msg => "skip_to_start_gateway_due_to_disabled", + gateway_name => GwName + }), {ok, State}; true -> case cb_gateway_load(State) of @@ -120,7 +123,6 @@ init([Gateway, Ctx, _GwDscrptr]) -> handle_call(info, _From, State) -> {reply, detailed_gateway_info(State), State}; - handle_call(disable, _From, State = #state{status = Status}) -> case Status of running -> @@ -133,7 +135,6 @@ handle_call(disable, _From, State = #state{status = Status}) -> _ -> {reply, {error, already_stopped}, State} end; - handle_call(enable, _From, State = #state{status = Status}) -> case Status of stopped -> @@ -146,7 +147,6 @@ handle_call(enable, _From, State = #state{status = Status}) -> _ -> {reply, {error, already_started}, State} end; - handle_call({update, Config}, _From, State) -> case do_update_one_by_one(Config, State) of {ok, NState} -> @@ -155,7 +155,6 @@ handle_call({update, Config}, _From, State) -> %% If something wrong, nothing to update {reply, {error, Reason}, State} end; - handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. @@ -163,38 +162,48 @@ handle_call(_Request, _From, State) -> handle_cast(_Msg, State) -> {noreply, State}. -handle_info({'EXIT', Pid, Reason}, State = #state{name = Name, - child_pids = Pids}) -> +handle_info( + {'EXIT', Pid, Reason}, + State = #state{ + name = Name, + child_pids = Pids + } +) -> case lists:member(Pid, Pids) of true -> - ?SLOG(error, #{ msg => "child_process_exited" - , child => Pid - , reason => Reason - }), - case Pids -- [Pid]of + ?SLOG(error, #{ + msg => "child_process_exited", + child => Pid, + reason => Reason + }), + case Pids -- [Pid] of [] -> - ?SLOG(error, #{ msg => "gateway_all_children_process_existed" - , gateway_name => Name - }), - {noreply, State#state{status = stopped, - child_pids = [], - gw_state = undefined}}; + ?SLOG(error, #{ + msg => "gateway_all_children_process_existed", + gateway_name => Name + }), + {noreply, State#state{ + status = stopped, + child_pids = [], + gw_state = undefined + }}; RemainPids -> {noreply, State#state{child_pids = RemainPids}} end; _ -> - ?SLOG(error, #{ msg => "gateway_catch_a_unknown_process_exited" - , child => Pid - , reason => Reason - , gateway_name => Name - }), + ?SLOG(error, #{ + msg => "gateway_catch_a_unknown_process_exited", + child => Pid, + reason => Reason, + gateway_name => Name + }), {noreply, State} end; - handle_info(Info, State) -> - ?SLOG(warning, #{ msg => "unexcepted_info" - , info => Info - }), + ?SLOG(warning, #{ + msg => "unexcepted_info", + info => Info + }), {noreply, State}. terminate(_Reason, State = #state{child_pids = Pids}) -> @@ -207,14 +216,16 @@ code_change(_OldVsn, State, _Extra) -> detailed_gateway_info(State) -> maps:filter( - fun(_, V) -> V =/= undefined end, - #{name => State#state.name, - config => State#state.config, - status => State#state.status, - created_at => State#state.created_at, - started_at => State#state.started_at, - stopped_at => State#state.stopped_at - }). + fun(_, V) -> V =/= undefined end, + #{ + name => State#state.name, + config => State#state.config, + status => State#state.status, + created_at => State#state.created_at, + started_at => State#state.started_at, + stopped_at => State#state.stopped_at + } + ). %%-------------------------------------------------------------------- %% Internal funcs @@ -231,7 +242,7 @@ init_authn(GwName, Config) -> try do_init_authn(Authns, []) catch - throw : Reason = {badauth, _} -> + throw:Reason = {badauth, _} -> do_deinit_authn(proplists:get_keys(Authns)), throw(Reason) end. @@ -250,11 +261,15 @@ do_init_authn([_BadConf | More], Names) -> authns(GwName, Config) -> Listeners = maps:to_list(maps:get(listeners, Config, #{})), lists:append( - [ [{emqx_gateway_utils:listener_chain(GwName, LisType, LisName), - authn_conf(Opts)} - || {LisName, Opts} <- maps:to_list(LisNames) ] - || {LisType, LisNames} <- Listeners]) - ++ [{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}]. + [ + [ + {emqx_gateway_utils:listener_chain(GwName, LisType, LisName), authn_conf(Opts)} + || {LisName, Opts} <- maps:to_list(LisNames) + ] + || {LisType, LisNames} <- Listeners + ] + ) ++ + [{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}]. authn_conf(Conf) -> maps:get(authentication, Conf, #{enable => false}). @@ -263,20 +278,23 @@ do_create_authn_chain(ChainName, AuthConf) -> case ensure_chain(ChainName) of ok -> case emqx_authentication:create_authenticator(ChainName, AuthConf) of - {ok, _} -> ok; + {ok, _} -> + ok; {error, Reason} -> - ?SLOG(error, #{ msg => "failed_to_create_authenticator" - , chain_name => ChainName - , reason => Reason - , config => AuthConf - }), + ?SLOG(error, #{ + msg => "failed_to_create_authenticator", + chain_name => ChainName, + reason => Reason, + config => AuthConf + }), throw({badauth, Reason}) end; {error, Reason} -> - ?SLOG(error, #{ msg => "failed_to_create_authn_chanin" - , chain_name => ChainName - , reason => Reason - }), + ?SLOG(error, #{ + msg => "failed_to_create_authn_chanin", + chain_name => ChainName, + reason => Reason + }), throw({badauth, Reason}) end. @@ -291,22 +309,32 @@ ensure_chain(ChainName) -> end. do_deinit_authn(Names) -> - lists:foreach(fun(ChainName) -> - case emqx_authentication:delete_chain(ChainName) of - ok -> ok; - {error, {not_found, _}} -> ok; - {error, Reason} -> - ?SLOG(error, #{ msg => "failed_to_clean_authn_chain" - , chain_name => ChainName - , reason => Reason - }) - end - end, Names). + lists:foreach( + fun(ChainName) -> + case emqx_authentication:delete_chain(ChainName) of + ok -> + ok; + {error, {not_found, _}} -> + ok; + {error, Reason} -> + ?SLOG(error, #{ + msg => "failed_to_clean_authn_chain", + chain_name => ChainName, + reason => Reason + }) + end + end, + Names + ). -do_update_one_by_one(NCfg, State = #state{ - name = GwName, - config = OCfg, - status = Status}) -> +do_update_one_by_one( + NCfg, + State = #state{ + name = GwName, + config = OCfg, + status = Status + } +) -> NEnable = maps:get(enable, NCfg, true), OAuths = authns(GwName, OCfg), @@ -319,14 +347,16 @@ do_update_one_by_one(NCfg, State = #state{ {stopped, false} -> {ok, State#state{config = NCfg}}; {running, true} -> - NState = case NAuths == OAuths of - true -> State; - false -> - %% Reset Authentication first - _ = do_deinit_authn(State#state.authns), - AuthnNames = init_authn(State#state.name, NCfg), - State#state{authns = AuthnNames} - end, + NState = + case NAuths == OAuths of + true -> + State; + false -> + %% Reset Authentication first + _ = do_deinit_authn(State#state.authns), + AuthnNames = init_authn(State#state.name, NCfg), + State#state{authns = AuthnNames} + end, %% TODO: minimum impact update ??? cb_gateway_update(NCfg, NState); {running, false} -> @@ -338,26 +368,33 @@ do_update_one_by_one(NCfg, State = #state{ throw(nomatch) end. -cb_gateway_unload(State = #state{name = GwName, - gw_state = GwState}) -> +cb_gateway_unload( + State = #state{ + name = GwName, + gw_state = GwState + } +) -> Gateway = detailed_gateway_info(State), try #{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName), CbMod:on_gateway_unload(Gateway, GwState), - {ok, State#state{child_pids = [], - authns = [], - status = stopped, - gw_state = undefined, - started_at = undefined, - stopped_at = erlang:system_time(millisecond)}} + {ok, State#state{ + child_pids = [], + authns = [], + status = stopped, + gw_state = undefined, + started_at = undefined, + stopped_at = erlang:system_time(millisecond) + }} catch - Class : Reason : Stk -> - ?SLOG(error, #{ msg => "unload_gateway_crashed" - , gateway_name => GwName - , inner_state => GwState - , reason => {Class, Reason} - , stacktrace => Stk - }), + Class:Reason:Stk -> + ?SLOG(error, #{ + msg => "unload_gateway_crashed", + gateway_name => GwName, + inner_state => GwState, + reason => {Class, Reason}, + stacktrace => Stk + }), {error, Reason} after _ = do_deinit_authn(State#state.authns) @@ -367,10 +404,13 @@ cb_gateway_unload(State = #state{name = GwName, %% 2. Callback to Mod:on_gateway_load/2 %% %% Notes: If failed, rollback -cb_gateway_load(State = #state{name = GwName, - config = Config, - ctx = Ctx}) -> - +cb_gateway_load( + State = #state{ + name = GwName, + config = Config, + ctx = Ctx + } +) -> Gateway = detailed_gateway_info(State), try AuthnNames = init_authn(GwName, Config), @@ -383,54 +423,62 @@ cb_gateway_load(State = #state{name = GwName, {ok, ChildPidOrSpecs, GwState} -> ChildPids = start_child_process(ChildPidOrSpecs), {ok, State#state{ - ctx = NCtx, - authns = AuthnNames, - status = running, - child_pids = ChildPids, - gw_state = GwState, - stopped_at = undefined, - started_at = erlang:system_time(millisecond) - }} + ctx = NCtx, + authns = AuthnNames, + status = running, + child_pids = ChildPids, + gw_state = GwState, + stopped_at = undefined, + started_at = erlang:system_time(millisecond) + }} end catch - Class : Reason1 : Stk -> - ?SLOG(error, #{ msg => "load_gateway_crashed" - , gateway_name => GwName - , gateway => Gateway - , ctx => Ctx - , reason => {Class, Reason1} - , stacktrace => Stk - }), + Class:Reason1:Stk -> + ?SLOG(error, #{ + msg => "load_gateway_crashed", + gateway_name => GwName, + gateway => Gateway, + ctx => Ctx, + reason => {Class, Reason1}, + stacktrace => Stk + }), {error, Reason1} end. -cb_gateway_update(Config, - State = #state{name = GwName, - gw_state = GwState}) -> +cb_gateway_update( + Config, + State = #state{ + name = GwName, + gw_state = GwState + } +) -> try #{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName), case CbMod:on_gateway_update(Config, detailed_gateway_info(State), GwState) of - {error, Reason} -> {error, Reason}; + {error, Reason} -> + {error, Reason}; {ok, ChildPidOrSpecs, NGwState} -> ChildPids = start_child_process(ChildPidOrSpecs), {ok, State#state{ - config = Config, - child_pids = ChildPids, - gw_state = NGwState - }} + config = Config, + child_pids = ChildPids, + gw_state = NGwState + }} end catch - Class : Reason1 : Stk -> - ?SLOG(error, #{ msg => "update_gateway_crashed" - , gateway_name => GwName - , new_config => Config - , reason => {Class, Reason1} - , stacktrace => Stk - }), + Class:Reason1:Stk -> + ?SLOG(error, #{ + msg => "update_gateway_crashed", + gateway_name => GwName, + new_config => Config, + reason => {Class, Reason1}, + stacktrace => Stk + }), {error, Reason1} end. -start_child_process([]) -> []; +start_child_process([]) -> + []; start_child_process([Indictor | _] = ChildPidOrSpecs) -> case erlang:is_pid(Indictor) of true -> @@ -441,7 +489,6 @@ start_child_process([Indictor | _] = ChildPidOrSpecs) -> do_start_child_process(ChildSpecs) when is_list(ChildSpecs) -> lists:map(fun do_start_child_process/1, ChildSpecs); - do_start_child_process(_ChildSpec = #{start := {M, F, A}}) -> case erlang:apply(M, F, A) of {ok, Pid} -> diff --git a/apps/emqx_gateway/src/emqx_gateway_metrics.erl b/apps/emqx_gateway/src/emqx_gateway_metrics.erl index d2a9d7442..f94ae690f 100644 --- a/apps/emqx_gateway/src/emqx_gateway_metrics.erl +++ b/apps/emqx_gateway/src/emqx_gateway_metrics.erl @@ -23,22 +23,24 @@ %% APIs -export([start_link/1]). --export([ inc/2 - , inc/3 - , dec/2 - , dec/3 - ]). +-export([ + inc/2, + inc/3, + dec/2, + dec/3 +]). -export([lookup/1]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -export([tabname/1]). @@ -68,9 +70,9 @@ dec(GwName, Name) -> dec(GwName, Name, Oct) -> inc(GwName, Name, -Oct). --spec lookup(gateway_name()) - -> undefined - | [{Name :: atom(), integer()}]. +-spec lookup(gateway_name()) -> + undefined + | [{Name :: atom(), integer()}]. lookup(GwName) -> Tab = emqx_gateway_metrics:tabname(GwName), case ets:info(Tab) of @@ -87,7 +89,7 @@ tabname(GwName) -> init([GwName]) -> TabOpts = [public, {write_concurrency, true}], - ok = emqx_tables:new(tabname(GwName), [set|TabOpts]), + ok = emqx_tables:new(tabname(GwName), [set | TabOpts]), {ok, #state{}}. handle_call(_Request, _From, State) -> diff --git a/apps/emqx_gateway/src/emqx_gateway_registry.erl b/apps/emqx_gateway/src/emqx_gateway_registry.erl index 6a2ec3be9..b35cda533 100644 --- a/apps/emqx_gateway/src/emqx_gateway_registry.erl +++ b/apps/emqx_gateway/src/emqx_gateway_registry.erl @@ -22,35 +22,38 @@ -behaviour(gen_server). %% APIs --export([ reg/2 - , unreg/1 - , list/0 - , lookup/1 - ]). +-export([ + reg/2, + unreg/1, + list/0, + lookup/1 +]). %% APIs -export([start_link/0]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -record(state, { - reged = #{} :: #{ gateway_name() => descriptor() } - }). + reged = #{} :: #{gateway_name() => descriptor()} +}). -type registry_options() :: [registry_option()]. -type registry_option() :: {cbkmod, atom()}. --type descriptor() :: #{ cbkmod := atom() - , rgopts := registry_options() - }. +-type descriptor() :: #{ + cbkmod := atom(), + rgopts := registry_options() +}. %%-------------------------------------------------------------------- %% APIs @@ -63,14 +66,15 @@ start_link() -> %% Mgmt %%-------------------------------------------------------------------- --spec reg(gateway_name(), registry_options()) - -> ok - | {error, any()}. +-spec reg(gateway_name(), registry_options()) -> + ok + | {error, any()}. reg(Name, RgOpts) -> CbMod = proplists:get_value(cbkmod, RgOpts, Name), - Dscrptr = #{ cbkmod => CbMod - , rgopts => RgOpts - }, + Dscrptr = #{ + cbkmod => CbMod, + rgopts => RgOpts + }, call({reg, Name, Dscrptr}). -spec unreg(gateway_name()) -> ok | {error, any()}. @@ -110,7 +114,6 @@ handle_call({reg, Name, Dscrptr}, _From, State = #state{reged = Gateways}) -> _ -> {reply, {error, already_existed}, State} end; - handle_call({unreg, Name}, _From, State = #state{reged = Gateways}) -> case maps:get(Name, Gateways, undefined) of undefined -> @@ -119,14 +122,11 @@ handle_call({unreg, Name}, _From, State = #state{reged = Gateways}) -> _ = emqx_gateway_sup:unload_gateway(Name), {reply, ok, State#state{reged = maps:remove(Name, Gateways)}} end; - handle_call(all, _From, State = #state{reged = Gateways}) -> {reply, maps:to_list(Gateways), State}; - handle_call({lookup, Name}, _From, State = #state{reged = Gateways}) -> Reply = maps:get(Name, Gateways, undefined), {reply, Reply, State}; - handle_call(Req, _From, State) -> logger:error("Unexpected call: ~0p", [Req]), {reply, ok, State}. diff --git a/apps/emqx_gateway/src/emqx_gateway_schema.erl b/apps/emqx_gateway/src/emqx_gateway_schema.erl index b1828559d..3c28e5328 100644 --- a/apps/emqx_gateway/src/emqx_gateway_schema.erl +++ b/apps/emqx_gateway/src/emqx_gateway_schema.erl @@ -37,18 +37,18 @@ -typerefl_from_string({duration/0, emqx_schema, to_duration}). -typerefl_from_string({duration_s/0, emqx_schema, to_duration_s}). -typerefl_from_string({bytesize/0, emqx_schema, to_bytesize}). --typerefl_from_string({comma_separated_list/0, emqx_schema, - to_comma_separated_list}). +-typerefl_from_string({comma_separated_list/0, emqx_schema, to_comma_separated_list}). --reflect_type([ duration/0 - , duration_s/0 - , bytesize/0 - , comma_separated_list/0 - , ip_port/0 - ]). +-reflect_type([ + duration/0, + duration_s/0, + bytesize/0, + comma_separated_list/0, + ip_port/0 +]). -elvis([{elvis_style, dont_repeat_yourself, disable}]). --export([namespace/0, roots/0 , fields/1, desc/1]). +-export([namespace/0, roots/0, fields/1, desc/1]). -export([proxy_protocol_opts/0]). @@ -57,390 +57,516 @@ namespace() -> gateway. roots() -> [gateway]. fields(gateway) -> - [{stomp, - sc(ref(stomp), - #{ required => {false, recursively} - , desc => -"The Stomp Gateway configuration.
-This gateway supports v1.2/1.1/1.0" - })}, - {mqttsn, - sc(ref(mqttsn), - #{ required => {false, recursively} - , desc => -"The MQTT-SN Gateway configuration.
-This gateway only supports the v1.2 protocol" - })}, - {coap, - sc(ref(coap), - #{ required => {false, recursively} - , desc => -"The CoAP Gateway configuration.
-This gateway is implemented based on RFC-7252 and -https://core-wg.github.io/coap-pubsub/draft-ietf-core-pubsub.html" - })}, - {lwm2m, - sc(ref(lwm2m), - #{ required => {false, recursively} - , desc => -"The LwM2M Gateway configuration.
-This gateway only supports the v1.0.1 protocol" - })}, - {exproto, - sc(ref(exproto), - #{ required => {false, recursively} - , desc => "The Extension Protocol configuration" - })} + [ + {stomp, + sc( + ref(stomp), + #{ + required => {false, recursively}, + desc => + "The Stomp Gateway configuration.
\n" + "This gateway supports v1.2/1.1/1.0" + } + )}, + {mqttsn, + sc( + ref(mqttsn), + #{ + required => {false, recursively}, + desc => + "The MQTT-SN Gateway configuration.
\n" + "This gateway only supports the v1.2 protocol" + } + )}, + {coap, + sc( + ref(coap), + #{ + required => {false, recursively}, + desc => + "The CoAP Gateway configuration.
\n" + "This gateway is implemented based on RFC-7252 and\n" + "https://core-wg.github.io/coap-pubsub/draft-ietf-core-pubsub.html" + } + )}, + {lwm2m, + sc( + ref(lwm2m), + #{ + required => {false, recursively}, + desc => + "The LwM2M Gateway configuration.
\n" + "This gateway only supports the v1.0.1 protocol" + } + )}, + {exproto, + sc( + ref(exproto), + #{ + required => {false, recursively}, + desc => "The Extension Protocol configuration" + } + )} ]; - fields(stomp) -> - [ {frame, sc(ref(stomp_frame))} - , {listeners, sc(ref(tcp_listeners))} + [ + {frame, sc(ref(stomp_frame))}, + {listeners, sc(ref(tcp_listeners))} ] ++ gateway_common_options(); - fields(stomp_frame) -> - [ {max_headers, - sc(integer(), - #{ default => 10 - , desc => "The maximum number of Header" - })} - , {max_headers_length, - sc(integer(), - #{ default => 1024 - , desc => "The maximum string length of the Header Value" - })} - , {max_body_length, - sc(integer(), - #{ default => 65536 - , desc => "Maximum number of bytes of Body allowed per Stomp packet" - })} + [ + {max_headers, + sc( + integer(), + #{ + default => 10, + desc => "The maximum number of Header" + } + )}, + {max_headers_length, + sc( + integer(), + #{ + default => 1024, + desc => "The maximum string length of the Header Value" + } + )}, + {max_body_length, + sc( + integer(), + #{ + default => 65536, + desc => "Maximum number of bytes of Body allowed per Stomp packet" + } + )} ]; - fields(mqttsn) -> - [ {gateway_id, - sc(integer(), - #{ default => 1 - , required => true - , desc => -"MQTT-SN Gateway ID.
-When the broadcast option is enabled, -the gateway will broadcast ADVERTISE message with this value" - })} - , {broadcast, - sc(boolean(), - #{ default => false - , desc => "Whether to periodically broadcast ADVERTISE messages" - })} - %% TODO: rename - , {enable_qos3, - sc(boolean(), - #{ default => true - , desc => -"Allows connectionless clients to publish messages with a Qos of -1.
-This feature is defined for very simple client implementations -which do not support any other features except this one.
-There is no connection setup nor tear down, no registration nor subscription.
-The client just sends its 'PUBLISH' messages to a GW" - })} - , {subs_resume, - sc(boolean(), - #{ default => false - , desc => -"Whether to initiate all subscribed topic name registration messages to the -client after the Session has been taken over by a new channel." - })} - , {predefined, - sc(hoconsc:array(ref(mqttsn_predefined)), - #{ default => [] - , required => {false, recursively} - , desc => -<<"The pre-defined topic IDs and topic names.
-A 'pre-defined' topic ID is a topic ID whose mapping to a topic name -is known in advance by both the client's application and the gateway">> - })} - , {listeners, sc(ref(udp_listeners))} + [ + {gateway_id, + sc( + integer(), + #{ + default => 1, + required => true, + desc => + "MQTT-SN Gateway ID.
\n" + "When the broadcast option is enabled,\n" + "the gateway will broadcast ADVERTISE message with this value" + } + )}, + {broadcast, + sc( + boolean(), + #{ + default => false, + desc => "Whether to periodically broadcast ADVERTISE messages" + } + )}, + %% TODO: rename + {enable_qos3, + sc( + boolean(), + #{ + default => true, + desc => + "Allows connectionless clients to publish messages with a Qos of -1.
\n" + "This feature is defined for very simple client implementations\n" + "which do not support any other features except this one.
\n" + "There is no connection setup nor tear down, no registration nor subscription.
\n" + "The client just sends its 'PUBLISH' messages to a GW" + } + )}, + {subs_resume, + sc( + boolean(), + #{ + default => false, + desc => + "Whether to initiate all subscribed topic name registration messages to the\n" + "client after the Session has been taken over by a new channel." + } + )}, + {predefined, + sc( + hoconsc:array(ref(mqttsn_predefined)), + #{ + default => [], + required => {false, recursively}, + desc => + << + "The pre-defined topic IDs and topic names.
\n" + "A 'pre-defined' topic ID is a topic ID whose mapping to a topic name\n" + "is known in advance by both the client's application and the gateway" + >> + } + )}, + {listeners, sc(ref(udp_listeners))} ] ++ gateway_common_options(); - fields(mqttsn_predefined) -> - [ {id, sc(integer(), #{desc => "Topic ID.
Range: 1-65535"})} - , {topic, sc(binary(), #{desc => "Topic Name"})} + [ + {id, sc(integer(), #{desc => "Topic ID.
Range: 1-65535"})}, + {topic, sc(binary(), #{desc => "Topic Name"})} ]; - fields(coap) -> - [ {heartbeat, - sc(duration(), - #{ default => <<"30s">> - , desc => -"The gateway server required minimum heartbeat interval.
-When connection mode is enabled, this parameter is used to set the minimum -heartbeat interval for the connection to be alive." - })} - , {connection_required, - sc(boolean(), - #{ default => false - , desc => -"Enable or disable connection mode.
-Connection mode is a feature of non-standard protocols. When connection mode -is enabled, it is necessary to maintain the creation, authentication and alive -of connection resources" - })} - , {notify_type, - sc(hoconsc:union([non, con, qos]), - #{ default => qos - , desc => -"The Notification Message will be delivered to the CoAP client if a new message -received on an observed topic. -The type of delivered coap message can be set to:
-1. non: Non-confirmable;
-2. con: Confirmable;
-3. qos: Mapping from QoS type of received message, QoS0 -> non, QoS1,2 -> con" - })} - , {subscribe_qos, - sc(hoconsc:enum([qos0, qos1, qos2, coap]), - #{ default => coap - , desc => -"The Default QoS Level indicator for subscribe request.
-This option specifies the QoS level for the CoAP Client when establishing a -subscription membership, if the subscribe request is not carried `qos` option. -The indicator can be set to: - - qos0, qos1, qos2: Fixed default QoS level - - coap: Dynamic QoS level by the message type of subscribe request - * qos0: If the subscribe request is non-confirmable - * qos1: If the subscribe request is confirmable" - })} - , {publish_qos, - sc(hoconsc:enum([qos0, qos1, qos2, coap]), - #{ default => coap - , desc => -"The Default QoS Level indicator for publish request.
-This option specifies the QoS level for the CoAP Client when publishing a -message to EMQX PUB/SUB system, if the publish request is not carried `qos` -option. The indicator can be set to: - - qos0, qos1, qos2: Fixed default QoS level - - coap: Dynamic QoS level by the message type of publish request - * qos0: If the publish request is non-confirmable - * qos1: If the publish request is confirmable" - })} - , {listeners, - sc(ref(udp_listeners), - #{ desc =>"Listeners (UDP) for CoAP service" - })} + [ + {heartbeat, + sc( + duration(), + #{ + default => <<"30s">>, + desc => + "The gateway server required minimum heartbeat interval.
\n" + "When connection mode is enabled, this parameter is used to set the minimum\n" + "heartbeat interval for the connection to be alive." + } + )}, + {connection_required, + sc( + boolean(), + #{ + default => false, + desc => + "Enable or disable connection mode.
\n" + "Connection mode is a feature of non-standard protocols. When connection mode\n" + "is enabled, it is necessary to maintain the creation, authentication and alive\n" + "of connection resources" + } + )}, + {notify_type, + sc( + hoconsc:union([non, con, qos]), + #{ + default => qos, + desc => + "The Notification Message will be delivered to the CoAP client if a new message\n" + "received on an observed topic.\n" + "The type of delivered coap message can be set to:
\n" + "1. non: Non-confirmable;
\n" + "2. con: Confirmable;
\n" + "3. qos: Mapping from QoS type of received message, QoS0 -> non, QoS1,2 -> con" + } + )}, + {subscribe_qos, + sc( + hoconsc:enum([qos0, qos1, qos2, coap]), + #{ + default => coap, + desc => + "The Default QoS Level indicator for subscribe request.
\n" + "This option specifies the QoS level for the CoAP Client when establishing a\n" + "subscription membership, if the subscribe request is not carried `qos` option.\n" + "The indicator can be set to:\n" + " - qos0, qos1, qos2: Fixed default QoS level\n" + " - coap: Dynamic QoS level by the message type of subscribe request\n" + " * qos0: If the subscribe request is non-confirmable\n" + " * qos1: If the subscribe request is confirmable" + } + )}, + {publish_qos, + sc( + hoconsc:enum([qos0, qos1, qos2, coap]), + #{ + default => coap, + desc => + "The Default QoS Level indicator for publish request.
\n" + "This option specifies the QoS level for the CoAP Client when publishing a\n" + "message to EMQX PUB/SUB system, if the publish request is not carried `qos`\n" + "option. The indicator can be set to:\n" + " - qos0, qos1, qos2: Fixed default QoS level\n" + " - coap: Dynamic QoS level by the message type of publish request\n" + " * qos0: If the publish request is non-confirmable\n" + " * qos1: If the publish request is confirmable" + } + )}, + {listeners, + sc( + ref(udp_listeners), + #{desc => "Listeners (UDP) for CoAP service"} + )} ] ++ gateway_common_options(); - fields(lwm2m) -> - [ {xml_dir, - sc(binary(), - #{ default =>"etc/lwm2m_xml" - , required => true - , desc => "The Directory for LwM2M Resource definition" - })} - , {lifetime_min, - sc(duration(), - #{ default => "15s" - , desc => "Minimum value of lifetime allowed to be set by the LwM2M client" - })} - , {lifetime_max, - sc(duration(), - #{ default => "86400s" - , desc => "Maximum value of lifetime allowed to be set by the LwM2M client" - })} - , {qmode_time_window, - sc(duration_s(), - #{ default => "22s" - , desc => -"The value of the time window during which the network link is considered -valid by the LwM2M Gateway in QMode mode.
-For example, after receiving an update message from a client, any messages -within this time window are sent directly to the LwM2M client, and all messages -beyond this time window are temporarily stored in memory." - })} - %% TODO: Support config resource path - , {auto_observe, - sc(boolean(), - #{ default => false - , desc => "Automatically observe the object list of REGISTER packet" - })} - %% FIXME: not working now - , {update_msg_publish_condition, - sc(hoconsc:union([always, contains_object_list]), - #{ default => "contains_object_list" - , desc => -"Policy for publishing UPDATE event message to EMQX.
- - always: send update events as long as the UPDATE request is received. - - contains_object_list: send update events only if the UPDATE request carries any Object List." - })} - , {translators, - sc(ref(lwm2m_translators), - #{ required => true - , desc => "Topic configuration for LwM2M's gateway publishing and subscription" - })} - , {listeners, sc(ref(udp_listeners))} + [ + {xml_dir, + sc( + binary(), + #{ + default => "etc/lwm2m_xml", + required => true, + desc => "The Directory for LwM2M Resource definition" + } + )}, + {lifetime_min, + sc( + duration(), + #{ + default => "15s", + desc => "Minimum value of lifetime allowed to be set by the LwM2M client" + } + )}, + {lifetime_max, + sc( + duration(), + #{ + default => "86400s", + desc => "Maximum value of lifetime allowed to be set by the LwM2M client" + } + )}, + {qmode_time_window, + sc( + duration_s(), + #{ + default => "22s", + desc => + "The value of the time window during which the network link is considered\n" + "valid by the LwM2M Gateway in QMode mode.
\n" + "For example, after receiving an update message from a client, any messages\n" + "within this time window are sent directly to the LwM2M client, and all messages\n" + "beyond this time window are temporarily stored in memory." + } + )}, + %% TODO: Support config resource path + {auto_observe, + sc( + boolean(), + #{ + default => false, + desc => "Automatically observe the object list of REGISTER packet" + } + )}, + %% FIXME: not working now + {update_msg_publish_condition, + sc( + hoconsc:union([always, contains_object_list]), + #{ + default => "contains_object_list", + desc => + "Policy for publishing UPDATE event message.
\n" + " - always: send update events as long as the UPDATE request is received.\n" + " - contains_object_list: send update events only if the UPDATE request carries " + "any Object List." + } + )}, + {translators, + sc( + ref(lwm2m_translators), + #{ + required => true, + desc => "Topic configuration for LwM2M's gateway publishing and subscription" + } + )}, + {listeners, sc(ref(udp_listeners))} ] ++ gateway_common_options(); - fields(exproto) -> - [ {server, - sc(ref(exproto_grpc_server), - #{ required => true - , desc => "Configurations for starting the ConnectionAdapter service" - })} - , {handler, - sc(ref(exproto_grpc_handler), - #{ required => true - , desc => "Configurations for request to ConnectionHandler service" - })} - , {listeners, sc(ref(udp_tcp_listeners))} + [ + {server, + sc( + ref(exproto_grpc_server), + #{ + required => true, + desc => "Configurations for starting the ConnectionAdapter service" + } + )}, + {handler, + sc( + ref(exproto_grpc_handler), + #{ + required => true, + desc => "Configurations for request to ConnectionHandler service" + } + )}, + {listeners, sc(ref(udp_tcp_listeners))} ] ++ gateway_common_options(); - fields(exproto_grpc_server) -> - [ {bind, - sc(hoconsc:union([ip_port(), integer()]), - #{ required => true - , desc => "Listening address and port for the gRPC server." - })} - , {ssl, - sc(ref(ssl_server_opts), - #{ required => {false, recursively} - , desc => "SSL configuration for the gRPC server." - })} + [ + {bind, + sc( + hoconsc:union([ip_port(), integer()]), + #{ + required => true, + desc => "Listening address and port for the gRPC server." + } + )}, + {ssl, + sc( + ref(ssl_server_opts), + #{ + required => {false, recursively}, + desc => "SSL configuration for the gRPC server." + } + )} ]; - fields(exproto_grpc_handler) -> - [ {address, sc(binary(), #{required => true, desc => "gRPC server address."})} - , {ssl, - sc(ref(emqx_schema, ssl_client_opts), - #{ required => {false, recursively} - , desc => "SSL configuration for the gRPC client." - })} + [ + {address, sc(binary(), #{required => true, desc => "gRPC server address."})}, + {ssl, + sc( + ref(emqx_schema, ssl_client_opts), + #{ + required => {false, recursively}, + desc => "SSL configuration for the gRPC client." + } + )} ]; - fields(ssl_server_opts) -> emqx_schema:server_ssl_opts_schema( - #{ depth => 10 - , reuse_sessions => true - , versions => tls_all_available - , ciphers => tls_all_available - }, true); - + #{ + depth => 10, + reuse_sessions => true, + versions => tls_all_available, + ciphers => tls_all_available + }, + true + ); fields(clientinfo_override) -> - [ {username, sc(binary(), #{desc => "Template for overriding username."})} - , {password, sc(binary(), #{desc => "Template for overriding password."})} - , {clientid, sc(binary(), #{desc => "Template for overriding clientid."})} + [ + {username, sc(binary(), #{desc => "Template for overriding username."})}, + {password, sc(binary(), #{desc => "Template for overriding password."})}, + {clientid, sc(binary(), #{desc => "Template for overriding clientid."})} ]; - fields(lwm2m_translators) -> - [ {command, - sc(ref(translator), - #{ desc => -"The topic for receiving downstream commands.
-For each new LwM2M client that succeeds in going online, the gateway creates -a subscription relationship to receive downstream commands and send it to -the LwM2M client" - , required => true - })} - , {response, - sc(ref(translator), - #{ desc => -"The topic for gateway to publish the acknowledge events from LwM2M client" - , required => true - })} - , {notify, - sc(ref(translator), - #{ desc => -"The topic for gateway to publish the notify events from LwM2M client.
- After succeed observe a resource of LwM2M client, Gateway will send the - notify events via this topic, if the client reports any resource changes" - , required => true - })} - , {register, - sc(ref(translator), - #{ desc => -"The topic for gateway to publish the register events from LwM2M client.
" - , required => true - })} - , {update, - sc(ref(translator), - #{ desc => -"The topic for gateway to publish the update events from LwM2M client.
" - , required => true - })} + [ + {command, + sc( + ref(translator), + #{ + desc => + "The topic for receiving downstream commands.
\n" + "For each new LwM2M client that succeeds in going online, the gateway creates\n" + "a subscription relationship to receive downstream commands and send it to\n" + "the LwM2M client", + required => true + } + )}, + {response, + sc( + ref(translator), + #{ + desc => + "The topic for gateway to publish the acknowledge events from LwM2M client", + required => true + } + )}, + {notify, + sc( + ref(translator), + #{ + desc => + "The topic for gateway to publish the notify events from LwM2M client.
\n" + " After succeed observe a resource of LwM2M client, Gateway will send the\n" + " notify events via this topic, if the client reports any resource changes", + required => true + } + )}, + {register, + sc( + ref(translator), + #{ + desc => + "The topic for gateway to publish the register events from LwM2M client.
", + required => true + } + )}, + {update, + sc( + ref(translator), + #{ + desc => + "The topic for gateway to publish the update events from LwM2M client.
", + required => true + } + )} ]; - fields(translator) -> - [ {topic, sc(binary(), - #{ required => true - , desc => "Which topic the device's upstream message is published to." - })} - , {qos, sc(emqx_schema:qos(), - #{ default => 0 - , desc => "QoS of the published messages." - })} + [ + {topic, + sc( + binary(), + #{ + required => true, + desc => "Which topic the device's upstream message is published to." + } + )}, + {qos, + sc( + emqx_schema:qos(), + #{ + default => 0, + desc => "QoS of the published messages." + } + )} ]; - fields(udp_listeners) -> - [ {udp, sc(map(name, ref(udp_listener)), #{desc => "UDP configuration."})} - , {dtls, sc(map(name, ref(dtls_listener)), #{desc => "DTLS configuration."})} + [ + {udp, sc(map(name, ref(udp_listener)), #{desc => "UDP configuration."})}, + {dtls, sc(map(name, ref(dtls_listener)), #{desc => "DTLS configuration."})} ]; - fields(tcp_listeners) -> - [ {tcp, sc(map(name, ref(tcp_listener)), #{desc => "TCP configuration."})} - , {ssl, sc(map(name, ref(ssl_listener)), #{desc => "SSL configuration."})} + [ + {tcp, sc(map(name, ref(tcp_listener)), #{desc => "TCP configuration."})}, + {ssl, sc(map(name, ref(ssl_listener)), #{desc => "SSL configuration."})} ]; - fields(udp_tcp_listeners) -> - [ {udp, sc(map(name, ref(udp_listener)), #{desc => "UDP configuration."})} - , {dtls, sc(map(name, ref(dtls_listener)), #{desc => "DTLS configuration."})} - , {tcp, sc(map(name, ref(tcp_listener)), #{desc => "TCP configuration."})} - , {ssl, sc(map(name, ref(ssl_listener)), #{desc => "SSL configuration."})} + [ + {udp, sc(map(name, ref(udp_listener)), #{desc => "UDP configuration."})}, + {dtls, sc(map(name, ref(dtls_listener)), #{desc => "DTLS configuration."})}, + {tcp, sc(map(name, ref(tcp_listener)), #{desc => "TCP configuration."})}, + {ssl, sc(map(name, ref(ssl_listener)), #{desc => "SSL configuration."})} ]; - fields(tcp_listener) -> - [ %% some special configs for tcp listener - {acceptors, sc(integer(), #{default => 16, desc => "Size of the acceptor pool."})} + %% some special configs for tcp listener + [ + {acceptors, sc(integer(), #{default => 16, desc => "Size of the acceptor pool."})} ] ++ - tcp_opts() ++ - proxy_protocol_opts() ++ - common_listener_opts(); - + tcp_opts() ++ + proxy_protocol_opts() ++ + common_listener_opts(); fields(ssl_listener) -> fields(tcp_listener) ++ - [{ssl, - sc(hoconsc:ref(emqx_schema, "listener_ssl_opts"), - #{ desc => "SSL listener options" - })} - ]; - + [ + {ssl, + sc( + hoconsc:ref(emqx_schema, "listener_ssl_opts"), + #{desc => "SSL listener options"} + )} + ]; fields(udp_listener) -> [ - %% some special configs for udp listener + %% some special configs for udp listener ] ++ - udp_opts() ++ - common_listener_opts(); - + udp_opts() ++ + common_listener_opts(); fields(dtls_listener) -> - [ {acceptors, sc(integer(), #{default => 16, desc => "Size of the acceptor pool."})} - ] ++ - fields(udp_listener) ++ - [{dtls, sc(ref(dtls_opts), #{desc => "DTLS listener options"})}]; - + [{acceptors, sc(integer(), #{default => 16, desc => "Size of the acceptor pool."})}] ++ + fields(udp_listener) ++ + [{dtls, sc(ref(dtls_opts), #{desc => "DTLS listener options"})}]; fields(udp_opts) -> - [ {active_n, sc(integer(), - #{ default => 100 - , desc => "Specify the {active, N} option for the socket.
" - "See: https://erlang.org/doc/man/inet.html#setopts-2" - })} - , {recbuf, sc(bytesize(), #{desc => "Size of the kernel-space receive buffer for the socket."})} - , {sndbuf, sc(bytesize(), #{desc => "Size of the kernel-space send buffer for the socket."})} - , {buffer, sc(bytesize(), #{desc => "Size of the user-space buffer for the socket."})} - , {reuseaddr, sc(boolean(), #{default => true, desc => "Allow local reuse of port numbers."})} + [ + {active_n, + sc( + integer(), + #{ + default => 100, + desc => + "Specify the {active, N} option for the socket.
" + "See: https://erlang.org/doc/man/inet.html#setopts-2" + } + )}, + {recbuf, + sc(bytesize(), #{desc => "Size of the kernel-space receive buffer for the socket."})}, + {sndbuf, sc(bytesize(), #{desc => "Size of the kernel-space send buffer for the socket."})}, + {buffer, sc(bytesize(), #{desc => "Size of the user-space buffer for the socket."})}, + {reuseaddr, sc(boolean(), #{default => true, desc => "Allow local reuse of port numbers."})} ]; - fields(dtls_opts) -> emqx_schema:server_ssl_opts_schema( - #{ depth => 10 - , reuse_sessions => true - , versions => dtls_all_available - , ciphers => dtls_all_available - }, false). + #{ + depth => 10, + reuse_sessions => true, + versions => dtls_all_available, + ciphers => dtls_all_available + }, + false + ). desc(gateway) -> "EMQX Gateway configuration root."; @@ -497,94 +623,127 @@ desc(_) -> undefined. authentication_schema() -> - sc(emqx_authn_schema:authenticator_type(), - #{ required => {false, recursively} - , desc => -"Default authentication configs for all the gateway listeners.
-For per-listener overrides see authentication -in listener configs" - }). + sc( + emqx_authn_schema:authenticator_type(), + #{ + required => {false, recursively}, + desc => + "Default authentication configs for all the gateway listeners.
\n" + "For per-listener overrides see authentication\n" + "in listener configs" + } + ). gateway_common_options() -> - [ {enable, - sc(boolean(), - #{ default => true - , desc => "Whether to enable this gateway" - })} - , {enable_stats, - sc(boolean(), - #{ default => true - , desc => "Whether to enable client process statistic" - })} - , {idle_timeout, - sc(duration(), - #{ default => <<"30s">> - , desc => -"The idle time of the client connection process.
-It has two purposes: -1. A newly created client process that does not receive any client requests - after that time will be closed directly. -2. A running client process that does not receive any client requests after - this time will go into hibernation to save resources." - })} - , {mountpoint, - sc(binary(), - #{ default => <<>> - %% TODO: variable support? - , desc => "" - })} - , {clientinfo_override, - sc(ref(clientinfo_override), #{})} - , {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication_schema()} + [ + {enable, + sc( + boolean(), + #{ + default => true, + desc => "Whether to enable this gateway" + } + )}, + {enable_stats, + sc( + boolean(), + #{ + default => true, + desc => "Whether to enable client process statistic" + } + )}, + {idle_timeout, + sc( + duration(), + #{ + default => <<"30s">>, + desc => + "The idle time of the client connection process.
\n" + "It has two purposes:\n" + "1. A newly created client process that does not receive any client requests\n" + " after that time will be closed directly.\n" + "2. A running client process that does not receive any client requests after\n" + " this time will go into hibernation to save resources." + } + )}, + {mountpoint, + sc( + binary(), + #{ + default => <<>>, + %% TODO: variable support? + desc => "" + } + )}, + {clientinfo_override, sc(ref(clientinfo_override), #{})}, + {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication_schema()} ]. common_listener_opts() -> - [ {enable, - sc(boolean(), - #{ default => true - , desc => "Enable the listener." - })} - , {bind, - sc(hoconsc:union([ip_port(), integer()]), - #{ desc => "The IP address and port that the listener will bind." - })} - , {max_connections, - sc(integer(), - #{ default => 1024 - , desc => "Maximum number of concurrent connections." - })} - , {max_conn_rate, - sc(integer(), - #{ default => 1000 - , desc => "Maximum connections per second." - })} - , {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication_schema()} - , {mountpoint, - sc(binary(), - #{ default => undefined - , desc => - "When publishing or subscribing, prefix all topics with a mountpoint string.\n" - " The prefixed string will be removed from the topic name when the message\n" - " is delivered to the subscriber. The mountpoint is a way that users can use\n" - " to implement isolation of message routing between different listeners.\n" - " For example if a client A subscribes to `t` with `listeners.tcp..mountpoint`\n" - " set to `some_tenant`, then the client actually subscribes to the topic\n" - " `some_tenant/t`. Similarly, if another client B (connected to the same listener\n" - " as the client A) sends a message to topic `t`, the message is routed\n" - " to all the clients subscribed `some_tenant/t`, so client A will receive the\n" - " message, with topic name `t`.
\n" - " Set to `\"\"` to disable the feature.
\n" - "\n" - " Variables in mountpoint string:\n" - " - ${clientid}: clientid\n" - " - ${username}: username" - })} - , {access_rules, - sc(hoconsc:array(string()), - #{ default => [] - , desc => "The access control rules for this listener.
" - "See: https://github.com/emqtt/esockd#allowdeny" - })} + [ + {enable, + sc( + boolean(), + #{ + default => true, + desc => "Enable the listener." + } + )}, + {bind, + sc( + hoconsc:union([ip_port(), integer()]), + #{desc => "The IP address and port that the listener will bind."} + )}, + {max_connections, + sc( + integer(), + #{ + default => 1024, + desc => "Maximum number of concurrent connections." + } + )}, + {max_conn_rate, + sc( + integer(), + #{ + default => 1000, + desc => "Maximum connections per second." + } + )}, + {?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM, authentication_schema()}, + {mountpoint, + sc( + binary(), + #{ + default => undefined, + desc => + "When publishing or subscribing, prefix all topics with a mountpoint string.\n" + " The prefixed string will be removed from the topic name when the message\n" + " is delivered to the subscriber. The mountpoint is a way that users can use\n" + " to implement isolation of message routing between different listeners.\n" + " For example if a client A subscribes to `t` with `listeners.tcp..mountpoint`\n" + " set to `some_tenant`, then the client actually subscribes to the topic\n" + " `some_tenant/t`. Similarly, if another client B (connected to the same listener\n" + " as the client A) sends a message to topic `t`, the message is routed\n" + " to all the clients subscribed `some_tenant/t`, so client A will receive the\n" + " message, with topic name `t`.
\n" + " Set to `\"\"` to disable the feature.
\n" + "\n" + " Variables in mountpoint string:\n" + " - ${clientid}: clientid\n" + " - ${username}: username" + } + )}, + {access_rules, + sc( + hoconsc:array(string()), + #{ + default => [], + desc => + "The access control rules for this listener.
" + "See: https://github.com/emqtt/esockd#allowdeny" + } + )} ]. tcp_opts() -> @@ -594,20 +753,29 @@ udp_opts() -> [{udp, sc(ref(udp_opts), #{})}]. proxy_protocol_opts() -> - [ {proxy_protocol, - sc(boolean(), - #{ default => false - , desc => "Enable the Proxy Protocol V1/2 if the EMQX cluster is deployed " - "behind HAProxy or Nginx.
" - "See: https://www.haproxy.com/blog/haproxy/proxy-protocol/" - })} - , {proxy_protocol_timeout, - sc(duration(), - #{ default => "15s" - , desc => "Timeout for proxy protocol.
" - "EMQX will close the TCP connection if proxy protocol packet is not " - "received within the timeout." - })} + [ + {proxy_protocol, + sc( + boolean(), + #{ + default => false, + desc => + "Enable the Proxy Protocol V1/2 if the EMQX cluster is deployed " + "behind HAProxy or Nginx.
" + "See: https://www.haproxy.com/blog/haproxy/proxy-protocol/" + } + )}, + {proxy_protocol_timeout, + sc( + duration(), + #{ + default => "15s", + desc => + "Timeout for proxy protocol.
" + "EMQX will close the TCP connection if proxy protocol packet is not " + "received within the timeout." + } + )} ]. sc(Type) -> diff --git a/apps/emqx_gateway/src/emqx_gateway_sup.erl b/apps/emqx_gateway/src/emqx_gateway_sup.erl index 499132d35..5c42a6d9a 100644 --- a/apps/emqx_gateway/src/emqx_gateway_sup.erl +++ b/apps/emqx_gateway/src/emqx_gateway_sup.erl @@ -23,14 +23,15 @@ -export([start_link/0]). %% Gateway APIs --export([ load_gateway/1 - , unload_gateway/1 - , lookup_gateway/1 - , update_gateway/2 - , start_gateway_insta/1 - , stop_gateway_insta/1 - , list_gateway_insta/0 - ]). +-export([ + load_gateway/1, + unload_gateway/1, + lookup_gateway/1, + update_gateway/2, + start_gateway_insta/1, + stop_gateway_insta/1, + list_gateway_insta/0 +]). %% supervisor callbacks -export([init/1]). @@ -42,23 +43,24 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -spec load_gateway(gateway()) -> {ok, pid()} | {error, any()}. load_gateway(Gateway = #{name := GwName}) -> case emqx_gateway_registry:lookup(GwName) of - undefined -> {error, {unknown_gateway_name, GwName}}; + undefined -> + {error, {unknown_gateway_name, GwName}}; GwDscrptr -> {ok, GwSup} = ensure_gateway_suptree_ready(GwName), emqx_gateway_gw_sup:create_insta(GwSup, Gateway, GwDscrptr) end. --spec unload_gateway(gateway_name()) - -> ok - | {error, not_found} - | {error, any()}. +-spec unload_gateway(gateway_name()) -> + ok + | {error, not_found} + | {error, any()}. unload_gateway(GwName) -> case lists:keyfind(GwName, 1, supervisor:which_children(?MODULE)) of - false -> {error, not_found}; + false -> + {error, not_found}; {_Id, Pid, _Type, _Mods} -> _ = emqx_gateway_gw_sup:remove_insta(Pid, GwName), _ = supervisor:terminate_child(?MODULE, GwName), @@ -75,21 +77,23 @@ lookup_gateway(GwName) -> undefined end. --spec update_gateway(gateway_name(), emqx_config:config()) - -> ok - | {error, any()}. +-spec update_gateway(gateway_name(), emqx_config:config()) -> + ok + | {error, any()}. update_gateway(GwName, Config) -> case emqx_gateway_utils:find_sup_child(?MODULE, GwName) of {ok, GwSup} -> emqx_gateway_gw_sup:update_insta(GwSup, GwName, Config); - _ -> {error, not_found} + _ -> + {error, not_found} end. start_gateway_insta(GwName) -> case search_gateway_insta_proc(GwName) of {ok, {GwSup, _}} -> emqx_gateway_gw_sup:start_insta(GwSup, GwName); - _ -> {error, not_found} + _ -> + {error, not_found} end. -spec stop_gateway_insta(gateway_name()) -> ok | {error, any()}. @@ -97,15 +101,20 @@ stop_gateway_insta(GwName) -> case search_gateway_insta_proc(GwName) of {ok, {GwSup, _}} -> emqx_gateway_gw_sup:stop_insta(GwSup, GwName); - _ -> {error, not_found} + _ -> + {error, not_found} end. -spec list_gateway_insta() -> [gateway()]. list_gateway_insta() -> - lists:append(lists:map( - fun(SupId) -> - emqx_gateway_gw_sup:list_insta(SupId) - end, list_started_gateway())). + lists:append( + lists:map( + fun(SupId) -> + emqx_gateway_gw_sup:list_insta(SupId) + end, + list_started_gateway() + ) + ). -spec list_started_gateway() -> [gateway_name()]. list_started_gateway() -> @@ -114,12 +123,12 @@ list_started_gateway() -> %% Supervisor callback init([]) -> - SupFlags = #{ strategy => one_for_one - , intensity => 10 - , period => 60 - }, - ChildSpecs = [ emqx_gateway_utils:childspec(worker, emqx_gateway_registry) - ], + SupFlags = #{ + strategy => one_for_one, + intensity => 10, + period => 60 + }, + ChildSpecs = [emqx_gateway_utils:childspec(worker, emqx_gateway_registry)], {ok, {SupFlags, ChildSpecs}}. %%-------------------------------------------------------------------- @@ -130,14 +139,14 @@ ensure_gateway_suptree_ready(GwName) -> case lists:keyfind(GwName, 1, supervisor:which_children(?MODULE)) of false -> ChildSpec = emqx_gateway_utils:childspec( - GwName, - supervisor, - emqx_gateway_gw_sup, - [GwName] - ), + GwName, + supervisor, + emqx_gateway_gw_sup, + [GwName] + ), emqx_gateway_utils:supervisor_ret( - supervisor:start_child(?MODULE, ChildSpec) - ); + supervisor:start_child(?MODULE, ChildSpec) + ); {_Id, Pid, _Type, _Mods} -> {ok, Pid} end. @@ -147,24 +156,27 @@ search_gateway_insta_proc(InstaId) -> search_gateway_insta_proc(_InstaId, []) -> {error, not_found}; -search_gateway_insta_proc(InstaId, [SupPid|More]) -> +search_gateway_insta_proc(InstaId, [SupPid | More]) -> case emqx_gateway_utils:find_sup_child(SupPid, InstaId) of {ok, InstaPid} -> {ok, {SupPid, InstaPid}}; - _ -> - search_gateway_insta_proc(InstaId, More) + _ -> search_gateway_insta_proc(InstaId, More) end. started_gateway() -> lists:filtermap( fun({Id, _, _, _}) -> is_a_gateway_id(Id) andalso {true, Id} - end, supervisor:which_children(?MODULE)). + end, + supervisor:which_children(?MODULE) + ). started_gateway_pid() -> lists:filtermap( fun({Id, Pid, _, _}) -> is_a_gateway_id(Id) andalso {true, Pid} - end, supervisor:which_children(?MODULE)). + end, + supervisor:which_children(?MODULE) + ). is_a_gateway_id(Id) -> Id /= emqx_gateway_registry. diff --git a/apps/emqx_gateway/src/emqx_gateway_utils.erl b/apps/emqx_gateway/src/emqx_gateway_utils.erl index e1c29c289..2ad5b6df4 100644 --- a/apps/emqx_gateway/src/emqx_gateway_utils.erl +++ b/apps/emqx_gateway/src/emqx_gateway_utils.erl @@ -20,81 +20,87 @@ -include("emqx_gateway.hrl"). -include_lib("emqx/include/logger.hrl"). --export([ childspec/2 - , childspec/3 - , childspec/4 - , supervisor_ret/1 - , find_sup_child/2 - ]). +-export([ + childspec/2, + childspec/3, + childspec/4, + supervisor_ret/1, + find_sup_child/2 +]). --export([ start_listeners/4 - , start_listener/4 - , stop_listeners/2 - , stop_listener/2 - ]). +-export([ + start_listeners/4, + start_listener/4, + stop_listeners/2, + stop_listener/2 +]). --export([ apply/2 - , format_listenon/1 - , parse_listenon/1 - , unix_ts_to_rfc3339/1 - , unix_ts_to_rfc3339/2 - , listener_id/3 - , parse_listener_id/1 - , is_running/2 - , global_chain/1 - , listener_chain/3 - ]). +-export([ + apply/2, + format_listenon/1, + parse_listenon/1, + unix_ts_to_rfc3339/1, + unix_ts_to_rfc3339/2, + listener_id/3, + parse_listener_id/1, + is_running/2, + global_chain/1, + listener_chain/3 +]). --export([ stringfy/1 - ]). +-export([stringfy/1]). --export([ normalize_config/1 - ]). +-export([normalize_config/1]). %% Common Envs --export([ active_n/1 - , ratelimit/1 - , frame_options/1 - , init_gc_state/1 - , stats_timer/1 - , idle_timeout/1 - , oom_policy/1 - ]). +-export([ + active_n/1, + ratelimit/1, + frame_options/1, + init_gc_state/1, + stats_timer/1, + idle_timeout/1, + oom_policy/1 +]). --export([ default_tcp_options/0 - , default_udp_options/0 - , default_subopts/0 - ]). +-export([ + default_tcp_options/0, + default_udp_options/0, + default_subopts/0 +]). -define(ACTIVE_N, 100). -define(DEFAULT_IDLE_TIMEOUT, 30000). --define(DEFAULT_GC_OPTS, #{count => 1000, bytes => 1024*1024}). --define(DEFAULT_OOM_POLICY, #{max_heap_size => 4194304, - max_message_queue_len => 32000}). +-define(DEFAULT_GC_OPTS, #{count => 1000, bytes => 1024 * 1024}). +-define(DEFAULT_OOM_POLICY, #{ + max_heap_size => 4194304, + max_message_queue_len => 32000 +}). -elvis([{elvis_style, god_modules, disable}]). --spec childspec(supervisor:worker(), Mod :: atom()) - -> supervisor:child_spec(). +-spec childspec(supervisor:worker(), Mod :: atom()) -> + supervisor:child_spec(). childspec(Type, Mod) -> childspec(Mod, Type, Mod, []). --spec childspec(supervisor:worker(), Mod :: atom(), Args :: list()) - -> supervisor:child_spec(). +-spec childspec(supervisor:worker(), Mod :: atom(), Args :: list()) -> + supervisor:child_spec(). childspec(Type, Mod, Args) -> childspec(Mod, Type, Mod, Args). --spec childspec(atom(), supervisor:worker(), Mod :: atom(), Args :: list()) - -> supervisor:child_spec(). +-spec childspec(atom(), supervisor:worker(), Mod :: atom(), Args :: list()) -> + supervisor:child_spec(). childspec(Id, Type, Mod, Args) -> - #{ id => Id - , start => {Mod, start_link, Args} - , type => Type - }. + #{ + id => Id, + start => {Mod, start_link, Args}, + type => Type + }. --spec supervisor_ret(supervisor:startchild_ret()) - -> {ok, pid()} - | {error, supervisor:startchild_err()}. +-spec supervisor_ret(supervisor:startchild_ret()) -> + {ok, pid()} + | {error, supervisor:startchild_err()}. supervisor_ret({ok, Pid, _Info}) -> {ok, Pid}; supervisor_ret({error, {Reason, Child}}) -> @@ -105,9 +111,9 @@ supervisor_ret({error, {Reason, Child}}) -> supervisor_ret(Ret) -> Ret. --spec find_sup_child(Sup :: pid() | atom(), ChildId :: supervisor:child_id()) - -> false - | {ok, pid()}. +-spec find_sup_child(Sup :: pid() | atom(), ChildId :: supervisor:child_id()) -> + false + | {ok, pid()}. find_sup_child(Sup, ChildId) -> case lists:keyfind(ChildId, 1, supervisor:which_children(Sup)) of false -> false; @@ -115,13 +121,16 @@ find_sup_child(Sup, ChildId) -> end. %% @doc start listeners. close all listeners if someone failed --spec start_listeners(Listeners :: list(), - GwName :: atom(), - Ctx :: map(), - ModCfg) - -> {ok, [pid()]} - | {error, term()} - when ModCfg :: #{frame_mod := atom(), chann_mod := atom()}. +-spec start_listeners( + Listeners :: list(), + GwName :: atom(), + Ctx :: map(), + ModCfg +) -> + {ok, [pid()]} + | {error, term()} +when + ModCfg :: #{frame_mod := atom(), chann_mod := atom()}. start_listeners(Listeners, GwName, Ctx, ModCfg) -> start_listeners(Listeners, GwName, Ctx, ModCfg, []). @@ -133,67 +142,94 @@ start_listeners([L | Ls], GwName, Ctx, ModCfg, Acc) -> NAcc = Acc ++ [{listener, {{ListenerId, ListenOn}, Pid}}], start_listeners(Ls, GwName, Ctx, ModCfg, NAcc); {error, Reason} -> - lists:foreach(fun({listener, {{ListenerId, ListenOn}, _}}) -> - esockd:close({ListenerId, ListenOn}) - end, Acc), + lists:foreach( + fun({listener, {{ListenerId, ListenOn}, _}}) -> + esockd:close({ListenerId, ListenOn}) + end, + Acc + ), {error, {Reason, L}} end. --spec start_listener(GwName :: atom(), - Ctx :: emqx_gateway_ctx:context(), - Listener :: tuple(), - ModCfg :: map()) - -> {ok, {ListenerId :: atom(), esockd:listen_on(), pid()}} - | {error, term()}. -start_listener(GwName, Ctx, - {Type, LisName, ListenOn, SocketOpts, Cfg}, ModCfg) -> +-spec start_listener( + GwName :: atom(), + Ctx :: emqx_gateway_ctx:context(), + Listener :: tuple(), + ModCfg :: map() +) -> + {ok, {ListenerId :: atom(), esockd:listen_on(), pid()}} + | {error, term()}. +start_listener( + GwName, + Ctx, + {Type, LisName, ListenOn, SocketOpts, Cfg}, + ModCfg +) -> ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), - ListenerId = emqx_gateway_utils:listener_id(GwName, Type, LisName), + ListenerId = emqx_gateway_utils:listener_id(GwName, Type, LisName), NCfg = maps:merge(Cfg, ModCfg), - case start_listener(GwName, Ctx, Type, - LisName, ListenOn, SocketOpts, NCfg) of + case + start_listener( + GwName, + Ctx, + Type, + LisName, + ListenOn, + SocketOpts, + NCfg + ) + of {ok, Pid} -> - console_print("Gateway ~ts:~ts:~ts on ~ts started.~n", - [GwName, Type, LisName, ListenOnStr]), + console_print( + "Gateway ~ts:~ts:~ts on ~ts started.~n", + [GwName, Type, LisName, ListenOnStr] + ), {ok, {ListenerId, ListenOn, Pid}}; {error, Reason} -> - ?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", - [GwName, Type, LisName, ListenOnStr, Reason]), + ?ELOG( + "Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", + [GwName, Type, LisName, ListenOnStr, Reason] + ), emqx_gateway_utils:supervisor_ret({error, Reason}) end. start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) -> Name = emqx_gateway_utils:listener_id(GwName, Type, LisName), - NCfg = Cfg#{ ctx => Ctx - , listener => {GwName, Type, LisName} - }, + NCfg = Cfg#{ + ctx => Ctx, + listener => {GwName, Type, LisName} + }, NSocketOpts = merge_default(Type, SocketOpts), MFA = {emqx_gateway_conn, start_link, [NCfg]}, do_start_listener(Type, Name, ListenOn, NSocketOpts, MFA). merge_default(Udp, Options) -> - {Key, Default} = case Udp of - udp -> - {udp_options, default_udp_options()}; - dtls -> - {udp_options, default_udp_options()}; - tcp -> - {tcp_options, default_tcp_options()}; - ssl -> - {tcp_options, default_tcp_options()} - end, + {Key, Default} = + case Udp of + udp -> + {udp_options, default_udp_options()}; + dtls -> + {udp_options, default_udp_options()}; + tcp -> + {tcp_options, default_tcp_options()}; + ssl -> + {tcp_options, default_tcp_options()} + end, case lists:keytake(Key, 1, Options) of {value, {Key, TcpOpts}, Options1} -> - [{Key, emqx_misc:merge_opts(Default, TcpOpts)} - | Options1]; + [ + {Key, emqx_misc:merge_opts(Default, TcpOpts)} + | Options1 + ]; false -> [{Key, Default} | Options] end. -do_start_listener(Type, Name, ListenOn, SocketOpts, MFA) - when Type == tcp; - Type == ssl -> +do_start_listener(Type, Name, ListenOn, SocketOpts, MFA) when + Type == tcp; + Type == ssl +-> esockd:open(Name, ListenOn, SocketOpts, MFA); do_start_listener(udp, Name, ListenOn, SocketOpts, MFA) -> esockd:open_udp(Name, ListenOn, SocketOpts, MFA); @@ -210,11 +246,15 @@ stop_listener(GwName, {Type, LisName, ListenOn, SocketOpts, Cfg}) -> ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), case StopRet of ok -> - console_print("Gateway ~ts:~ts:~ts on ~ts stopped.~n", - [GwName, Type, LisName, ListenOnStr]); + console_print( + "Gateway ~ts:~ts:~ts on ~ts stopped.~n", + [GwName, Type, LisName, ListenOnStr] + ); {error, Reason} -> - ?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", - [GwName, Type, LisName, ListenOnStr, Reason]) + ?ELOG( + "Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", + [GwName, Type, LisName, ListenOnStr, Reason] + ) end, StopRet. @@ -228,17 +268,23 @@ console_print(Fmt, Args) -> ?ULOG(Fmt, Args). console_print(_Fmt, _Args) -> ok. -endif. -apply({M, F, A}, A2) when is_atom(M), - is_atom(M), - is_list(A), - is_list(A2) -> +apply({M, F, A}, A2) when + is_atom(M), + is_atom(M), + is_list(A), + is_list(A2) +-> erlang:apply(M, F, A ++ A2); -apply({F, A}, A2) when is_function(F), - is_list(A), - is_list(A2) -> +apply({F, A}, A2) when + is_function(F), + is_list(A), + is_list(A2) +-> erlang:apply(F, A ++ A2); -apply(F, A2) when is_function(F), - is_list(A2) -> +apply(F, A2) when + is_function(F), + is_list(A2) +-> erlang:apply(F, A2). format_listenon(Port) when is_integer(Port) -> @@ -255,21 +301,20 @@ parse_listenon(IpPort) when is_tuple(IpPort) -> parse_listenon(Str) when is_binary(Str) -> parse_listenon(binary_to_list(Str)); parse_listenon(Str) when is_list(Str) -> - try list_to_integer(Str) - catch _ : _ -> - case emqx_schema:to_ip_port(Str) of - {ok, R} -> R; - {error, _} -> - error({invalid_listenon_name, Str}) - end + try + list_to_integer(Str) + catch + _:_ -> + case emqx_schema:to_ip_port(Str) of + {ok, R} -> R; + {error, _} -> error({invalid_listenon_name, Str}) + end end. listener_id(GwName, Type, LisName) -> binary_to_atom( - <<(bin(GwName))/binary, ":", - (bin(Type))/binary, ":", - (bin(LisName))/binary - >>). + <<(bin(GwName))/binary, ":", (bin(Type))/binary, ":", (bin(LisName))/binary>> + ). parse_listener_id(Id) when is_atom(Id) -> parse_listener_id(atom_to_binary(Id)); @@ -278,16 +323,17 @@ parse_listener_id(Id) -> [GwName, Type, Name] = binary:split(bin(Id), <<":">>, [global]), {GwName, Type, Name} catch - _ : _ -> error({invalid_listener_id, Id}) + _:_ -> error({invalid_listener_id, Id}) end. is_running(ListenerId, #{<<"bind">> := ListenOn0}) -> ListenOn = emqx_gateway_utils:parse_listenon(ListenOn0), try esockd:listener({ListenerId, ListenOn}) of - Pid when is_pid(Pid)-> + Pid when is_pid(Pid) -> true - catch _:_ -> - false + catch + _:_ -> + false end. %% same with emqx_authentication:global_chain/1 @@ -315,10 +361,13 @@ unix_ts_to_rfc3339(Keys, Map) when is_list(Keys) -> lists:foldl(fun(K, Acc) -> unix_ts_to_rfc3339(K, Acc) end, Map, Keys); unix_ts_to_rfc3339(Key, Map) -> case maps:get(Key, Map, undefined) of - undefined -> Map; + undefined -> + Map; Ts -> - Map#{Key => - emqx_rule_funcs:unix_ts_to_rfc3339(Ts, <<"millisecond">>)} + Map#{ + Key => + emqx_rule_funcs:unix_ts_to_rfc3339(Ts, <<"millisecond">>) + } end. unix_ts_to_rfc3339(Ts) -> @@ -330,60 +379,104 @@ stringfy(T) when is_list(T); is_binary(T) -> stringfy(T) -> iolist_to_binary(io_lib:format("~0p", [T])). --spec normalize_config(emqx_config:config()) - -> list({ Type :: udp | tcp | ssl | dtls - , Name :: atom() - , ListenOn :: esockd:listen_on() - , SocketOpts :: esockd:option() - , Cfg :: map() - }). +-spec normalize_config(emqx_config:config()) -> + list({ + Type :: udp | tcp | ssl | dtls, + Name :: atom(), + ListenOn :: esockd:listen_on(), + SocketOpts :: esockd:option(), + Cfg :: map() + }). normalize_config(RawConf) -> LisMap = maps:get(listeners, RawConf, #{}), Cfg0 = maps:without([listeners], RawConf), - lists:append(maps:fold(fun(Type, Liss, AccIn1) -> - Listeners = - maps:fold(fun(Name, Confs, AccIn2) -> - ListenOn = maps:get(bind, Confs), - SocketOpts = esockd_opts(Type, Confs), - RemainCfgs = maps:without( - [bind, tcp, ssl, udp, dtls] - ++ proplists:get_keys(SocketOpts), Confs), - Cfg = maps:merge(Cfg0, RemainCfgs), - [{Type, Name, ListenOn, SocketOpts, Cfg} | AccIn2] - end, [], Liss), - [Listeners | AccIn1] - end, [], LisMap)). + lists:append( + maps:fold( + fun(Type, Liss, AccIn1) -> + Listeners = + maps:fold( + fun(Name, Confs, AccIn2) -> + ListenOn = maps:get(bind, Confs), + SocketOpts = esockd_opts(Type, Confs), + RemainCfgs = maps:without( + [bind, tcp, ssl, udp, dtls] ++ + proplists:get_keys(SocketOpts), + Confs + ), + Cfg = maps:merge(Cfg0, RemainCfgs), + [{Type, Name, ListenOn, SocketOpts, Cfg} | AccIn2] + end, + [], + Liss + ), + [Listeners | AccIn1] + end, + [], + LisMap + ) + ). esockd_opts(Type, Opts0) -> - Opts1 = maps:with([acceptors, max_connections, max_conn_rate, - proxy_protocol, proxy_protocol_timeout], Opts0), + Opts1 = maps:with( + [ + acceptors, + max_connections, + max_conn_rate, + proxy_protocol, + proxy_protocol_timeout + ], + Opts0 + ), Opts2 = Opts1#{access_rules => esockd_access_rules(maps:get(access_rules, Opts0, []))}, - maps:to_list(case Type of - tcp -> Opts2#{tcp_options => sock_opts(tcp, Opts0)}; - ssl -> Opts2#{tcp_options => sock_opts(tcp, Opts0), - ssl_options => ssl_opts(ssl, Opts0)}; - udp -> Opts2#{udp_options => sock_opts(udp, Opts0)}; - dtls -> Opts2#{udp_options => sock_opts(udp, Opts0), - dtls_options => ssl_opts(dtls, Opts0)} - end). + maps:to_list( + case Type of + tcp -> + Opts2#{tcp_options => sock_opts(tcp, Opts0)}; + ssl -> + Opts2#{ + tcp_options => sock_opts(tcp, Opts0), + ssl_options => ssl_opts(ssl, Opts0) + }; + udp -> + Opts2#{udp_options => sock_opts(udp, Opts0)}; + dtls -> + Opts2#{ + udp_options => sock_opts(udp, Opts0), + dtls_options => ssl_opts(dtls, Opts0) + } + end + ). esockd_access_rules(StrRules) -> Access = fun(S) -> [A, CIDR] = string:tokens(S, " "), - {list_to_atom(A), case CIDR of "all" -> all; _ -> CIDR end} + { + list_to_atom(A), + case CIDR of + "all" -> all; + _ -> CIDR + end + } end, [Access(R) || R <- StrRules]. ssl_opts(Name, Opts) -> maps:to_list( emqx_tls_lib:drop_tls13_for_old_otp( - maps:without([enable], - maps:get(Name, Opts, #{})))). + maps:without( + [enable], + maps:get(Name, Opts, #{}) + ) + ) + ). sock_opts(Name, Opts) -> maps:to_list( - maps:without([active_n], - maps:get(Name, Opts, #{}))). + maps:without( + [active_n], + maps:get(Name, Opts, #{}) + ) + ). %%-------------------------------------------------------------------- %% Envs @@ -417,7 +510,10 @@ oom_policy(Options) -> -spec stats_timer(map()) -> undefined | disabled. stats_timer(Options) -> - case enable_stats(Options) of true -> undefined; false -> disabled end. + case enable_stats(Options) of + true -> undefined; + false -> disabled + end. -spec enable_stats(map()) -> boolean(). enable_stats(Options) -> @@ -427,16 +523,26 @@ enable_stats(Options) -> %% Envs2 default_tcp_options() -> - [binary, {packet, raw}, {reuseaddr, true}, - {nodelay, true}, {backlog, 512}]. + [ + binary, + {packet, raw}, + {reuseaddr, true}, + {nodelay, true}, + {backlog, 512} + ]. default_udp_options() -> [binary]. default_subopts() -> - #{rh => 1, %% Retain Handling - rap => 0, %% Retain as Publish - nl => 0, %% No Local - qos => 0, %% QoS - is_new => true - }. + %% Retain Handling + #{ + rh => 1, + %% Retain as Publish + rap => 0, + %% No Local + nl => 0, + %% QoS + qos => 0, + is_new => true + }. diff --git a/apps/emqx_gateway/src/exproto/emqx_exproto_channel.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_channel.erl index a5c889516..121439498 100644 --- a/apps/emqx_gateway/src/exproto/emqx_exproto_channel.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_channel.erl @@ -21,63 +21,65 @@ -include_lib("emqx/include/types.hrl"). -include_lib("emqx/include/logger.hrl"). +-export([ + info/1, + info/2, + stats/1 +]). --export([ info/1 - , info/2 - , stats/1 - ]). - --export([ init/2 - , handle_in/2 - , handle_deliver/2 - , handle_timeout/3 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - ]). +-export([ + init/2, + handle_in/2, + handle_deliver/2, + handle_timeout/3, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2 +]). -export_type([channel/0]). -record(channel, { - %% Context - ctx :: emqx_gateway_ctx:context(), - %% gRPC channel options - gcli :: map(), - %% Conn info - conninfo :: emqx_types:conninfo(), - %% Client info from `register` function - clientinfo :: maybe(map()), - %% Connection state - conn_state :: conn_state(), - %% Subscription - subscriptions = #{}, - %% Request queue - rqueue = queue:new(), - %% Inflight function name - inflight = undefined, - %% Keepalive - keepalive :: maybe(emqx_keepalive:keepalive()), - %% Timers - timers :: #{atom() => disabled | maybe(reference())}, - %% Closed reason - closed_reason = undefined - }). + %% Context + ctx :: emqx_gateway_ctx:context(), + %% gRPC channel options + gcli :: map(), + %% Conn info + conninfo :: emqx_types:conninfo(), + %% Client info from `register` function + clientinfo :: maybe(map()), + %% Connection state + conn_state :: conn_state(), + %% Subscription + subscriptions = #{}, + %% Request queue + rqueue = queue:new(), + %% Inflight function name + inflight = undefined, + %% Keepalive + keepalive :: maybe(emqx_keepalive:keepalive()), + %% Timers + timers :: #{atom() => disabled | maybe(reference())}, + %% Closed reason + closed_reason = undefined +}). --opaque(channel() :: #channel{}). +-opaque channel() :: #channel{}. --type(conn_state() :: idle | connecting | connected | disconnected). +-type conn_state() :: idle | connecting | connected | disconnected. --type(reply() :: {outgoing, binary()} - | {outgoing, [binary()]} - | {close, Reason :: atom()}). +-type reply() :: + {outgoing, binary()} + | {outgoing, [binary()]} + | {close, Reason :: atom()}. --type(replies() :: emqx_types:packet() | reply() | [reply()]). +-type replies() :: emqx_types:packet() | reply() | [reply()]. -define(TIMER_TABLE, #{ - alive_timer => keepalive, - force_timer => force_close - }). + alive_timer => keepalive, + force_timer => force_close +}). -define(INFO_KEYS, [conninfo, conn_state, clientinfo, session, will_msg]). @@ -90,7 +92,7 @@ info(Channel) -> maps:from_list(info(?INFO_KEYS, Channel)). --spec info(list(atom())|atom(), channel()) -> term(). +-spec info(list(atom()) | atom(), channel()) -> term(). info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; info(conninfo, #channel{conninfo = ConnInfo}) -> @@ -99,13 +101,17 @@ info(clientid, #channel{clientinfo = ClientInfo}) -> maps:get(clientid, ClientInfo, undefined); info(clientinfo, #channel{clientinfo = ClientInfo}) -> ClientInfo; -info(session, #channel{subscriptions = Subs, - conninfo = ConnInfo}) -> - #{subscriptions => Subs, - upgrade_qos => false, - retry_interval => 0, - await_rel_timeout => 0, - created_at => maps:get(connected_at, ConnInfo)}; +info(session, #channel{ + subscriptions = Subs, + conninfo = ConnInfo +}) -> + #{ + subscriptions => Subs, + upgrade_qos => false, + retry_interval => 0, + await_rel_timeout => 0, + created_at => maps:get(connected_at, ConnInfo) + }; info(conn_state, #channel{conn_state = ConnState}) -> ConnState; info(will_msg, _) -> @@ -115,62 +121,80 @@ info(ctx, #channel{ctx = Ctx}) -> -spec stats(channel()) -> emqx_types:stats(). stats(#channel{subscriptions = Subs}) -> - [{subscriptions_cnt, maps:size(Subs)}, - {subscriptions_max, 0}, - {inflight_cnt, 0}, - {inflight_max, 0}, - {mqueue_len, 0}, - {mqueue_max, 0}, - {mqueue_dropped, 0}, - {next_pkt_id, 0}, - {awaiting_rel_cnt, 0}, - {awaiting_rel_max, 0}]. + [ + {subscriptions_cnt, maps:size(Subs)}, + {subscriptions_max, 0}, + {inflight_cnt, 0}, + {inflight_max, 0}, + {mqueue_len, 0}, + {mqueue_max, 0}, + {mqueue_dropped, 0}, + {next_pkt_id, 0}, + {awaiting_rel_cnt, 0}, + {awaiting_rel_max, 0} + ]. %%-------------------------------------------------------------------- %% Init the channel %%-------------------------------------------------------------------- -spec init(emqx_exproto_types:conninfo(), map()) -> channel(). -init(ConnInfo = #{socktype := Socktype, - peername := Peername, - sockname := Sockname, - peercert := Peercert}, Options) -> +init( + ConnInfo = #{ + socktype := Socktype, + peername := Peername, + sockname := Sockname, + peercert := Peercert + }, + Options +) -> Ctx = maps:get(ctx, Options), GRpcChann = maps:get(handler, Options), PoolName = maps:get(pool_name, Options), NConnInfo = default_conninfo(ConnInfo), - ListenerId = case maps:get(listener, Options, undefined) of - undefined -> undefined; - {GwName, Type, LisName} -> - emqx_gateway_utils:listener_id(GwName, Type, LisName) - end, + ListenerId = + case maps:get(listener, Options, undefined) of + undefined -> undefined; + {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName) + end, ClientInfo = maps:put(listener, ListenerId, default_clientinfo(ConnInfo)), Channel = #channel{ - ctx = Ctx, - gcli = #{channel => GRpcChann, pool_name => PoolName}, - conninfo = NConnInfo, - clientinfo = ClientInfo, - conn_state = connecting, - timers = #{} - }, + ctx = Ctx, + gcli = #{channel => GRpcChann, pool_name => PoolName}, + conninfo = NConnInfo, + clientinfo = ClientInfo, + conn_state = connecting, + timers = #{} + }, - Req = #{conninfo => - peercert(Peercert, - #{socktype => socktype(Socktype), - peername => address(Peername), - sockname => address(Sockname)})}, + Req = #{ + conninfo => + peercert( + Peercert, + #{ + socktype => socktype(Socktype), + peername => address(Peername), + sockname => address(Sockname) + } + ) + }, try_dispatch(on_socket_created, wrap(Req), Channel). %% @private -peercert(NoSsl, ConnInfo) when NoSsl == nossl; - NoSsl == undefined -> +peercert(NoSsl, ConnInfo) when + NoSsl == nossl; + NoSsl == undefined +-> ConnInfo; peercert(Peercert, ConnInfo) -> Fn = fun(_, V) -> V =/= undefined end, - Infos = maps:filter(Fn, - #{cn => esockd_peercert:common_name(Peercert), - dn => esockd_peercert:subject(Peercert)} - ), + Infos = maps:filter( + Fn, + #{ + cn => esockd_peercert:common_name(Peercert), + dn => esockd_peercert:subject(Peercert) + } + ), case maps:size(Infos) of 0 -> ConnInfo; @@ -192,46 +216,64 @@ address({Host, Port}) -> %% Handle incoming packet %%-------------------------------------------------------------------- --spec handle_in(binary(), channel()) - -> {ok, channel()} - | {shutdown, Reason :: term(), channel()}. +-spec handle_in(binary(), channel()) -> + {ok, channel()} + | {shutdown, Reason :: term(), channel()}. handle_in(Data, Channel) -> Req = #{bytes => Data}, {ok, try_dispatch(on_received_bytes, wrap(Req), Channel)}. --spec handle_deliver(list(emqx_types:deliver()), channel()) - -> {ok, channel()} - | {shutdown, Reason :: term(), channel()}. -handle_deliver(Delivers, Channel = #channel{ctx = Ctx, - clientinfo = ClientInfo}) -> +-spec handle_deliver(list(emqx_types:deliver()), channel()) -> + {ok, channel()} + | {shutdown, Reason :: term(), channel()}. +handle_deliver( + Delivers, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> %% XXX: ?? Nack delivers from shared subscriptions Mountpoint = maps:get(mountpoint, ClientInfo), NodeStr = atom_to_binary(node(), utf8), - Msgs = lists:map(fun({_, _, Msg}) -> - ok = metrics_inc(Ctx, 'messages.delivered'), - Msg1 = emqx_hooks:run_fold('message.delivered', - [ClientInfo], Msg), - NMsg = emqx_mountpoint:unmount(Mountpoint, Msg1), - #{node => NodeStr, - id => emqx_guid:to_hexstr(emqx_message:id(NMsg)), - qos => emqx_message:qos(NMsg), - from => fmt_from(emqx_message:from(NMsg)), - topic => emqx_message:topic(NMsg), - payload => emqx_message:payload(NMsg), - timestamp => emqx_message:timestamp(NMsg) - } - end, Delivers), + Msgs = lists:map( + fun({_, _, Msg}) -> + ok = metrics_inc(Ctx, 'messages.delivered'), + Msg1 = emqx_hooks:run_fold( + 'message.delivered', + [ClientInfo], + Msg + ), + NMsg = emqx_mountpoint:unmount(Mountpoint, Msg1), + #{ + node => NodeStr, + id => emqx_guid:to_hexstr(emqx_message:id(NMsg)), + qos => emqx_message:qos(NMsg), + from => fmt_from(emqx_message:from(NMsg)), + topic => emqx_message:topic(NMsg), + payload => emqx_message:payload(NMsg), + timestamp => emqx_message:timestamp(NMsg) + } + end, + Delivers + ), Req = #{messages => Msgs}, {ok, try_dispatch(on_received_messages, wrap(Req), Channel)}. --spec handle_timeout(reference(), Msg :: term(), channel()) - -> {ok, channel()} - | {shutdown, Reason :: term(), channel()}. -handle_timeout(_TRef, {keepalive, _StatVal}, - Channel = #channel{keepalive = undefined}) -> +-spec handle_timeout(reference(), Msg :: term(), channel()) -> + {ok, channel()} + | {shutdown, Reason :: term(), channel()}. +handle_timeout( + _TRef, + {keepalive, _StatVal}, + Channel = #channel{keepalive = undefined} +) -> {ok, Channel}; -handle_timeout(_TRef, {keepalive, StatVal}, - Channel = #channel{keepalive = Keepalive}) -> +handle_timeout( + _TRef, + {keepalive, StatVal}, + Channel = #channel{keepalive = Keepalive} +) -> case emqx_keepalive:check(StatVal, Keepalive) of {ok, NKeepalive} -> NChannel = Channel#channel{keepalive = NKeepalive}, @@ -240,97 +282,119 @@ handle_timeout(_TRef, {keepalive, StatVal}, Req = #{type => 'KEEPALIVE'}, {ok, try_dispatch(on_timer_timeout, wrap(Req), Channel)} end; - handle_timeout(_TRef, force_close, Channel = #channel{closed_reason = Reason}) -> {shutdown, {error, {force_close, Reason}}, Channel}; - handle_timeout(_TRef, Msg, Channel) -> - ?SLOG(warning, #{msg => "unexpected_timeout_signal", - signal => Msg}), + ?SLOG(warning, #{ + msg => "unexpected_timeout_signal", + signal => Msg + }), {ok, Channel}. --spec handle_call(Req :: any(), From :: any(), channel()) - -> {reply, Reply :: term(), channel()} - | {reply, Reply :: term(), replies(), channel()} - | {shutdown, Reason :: term(), Reply :: term(), channel()}. +-spec handle_call(Req :: any(), From :: any(), channel()) -> + {reply, Reply :: term(), channel()} + | {reply, Reply :: term(), replies(), channel()} + | {shutdown, Reason :: term(), Reply :: term(), channel()}. handle_call({send, Data}, _From, Channel) -> {reply, ok, [{outgoing, Data}], Channel}; - handle_call(close, _From, Channel = #channel{conn_state = connected}) -> {reply, ok, [{event, disconnected}, {close, normal}], Channel}; handle_call(close, _From, Channel) -> {reply, ok, [{close, normal}], Channel}; - -handle_call({auth, ClientInfo, _Password}, _From, - Channel = #channel{conn_state = connected}) -> - ?SLOG(warning, #{ msg => "ingore_duplicated_authorized_command" - , request_clientinfo => ClientInfo - }), +handle_call( + {auth, ClientInfo, _Password}, + _From, + Channel = #channel{conn_state = connected} +) -> + ?SLOG(warning, #{ + msg => "ingore_duplicated_authorized_command", + request_clientinfo => ClientInfo + }), {reply, {error, ?RESP_PERMISSION_DENY, <<"Duplicated authenticate command">>}, Channel}; -handle_call({auth, ClientInfo0, Password}, _From, - Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +handle_call( + {auth, ClientInfo0, Password}, + _From, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> ClientInfo1 = enrich_clientinfo(ClientInfo0, ClientInfo), ConnInfo1 = enrich_conninfo(ClientInfo0, ConnInfo), - Channel1 = Channel#channel{conninfo = ConnInfo1, - clientinfo = ClientInfo1}, + Channel1 = Channel#channel{ + conninfo = ConnInfo1, + clientinfo = ClientInfo1 + }, #{clientid := ClientId, username := Username} = ClientInfo1, - case emqx_gateway_ctx:authenticate( - Ctx, ClientInfo1#{password => Password}) of + case + emqx_gateway_ctx:authenticate( + Ctx, ClientInfo1#{password => Password} + ) + of {ok, NClientInfo} -> SessFun = fun(_, _) -> #{} end, emqx_logger:set_metadata_clientid(ClientId), - case emqx_gateway_ctx:open_session( - Ctx, - true, - NClientInfo, - ConnInfo1, - SessFun - ) of + case + emqx_gateway_ctx:open_session( + Ctx, + true, + NClientInfo, + ConnInfo1, + SessFun + ) + of {ok, _Session} -> - ?SLOG(debug, #{ msg => "client_login_succeed" - , clientid => ClientId - , username => Username - }), + ?SLOG(debug, #{ + msg => "client_login_succeed", + clientid => ClientId, + username => Username + }), {reply, ok, [{event, connected}], - ensure_connected(Channel1#channel{clientinfo = NClientInfo})}; + ensure_connected(Channel1#channel{clientinfo = NClientInfo})}; {error, Reason} -> - ?SLOG(warning, #{ msg => "client_login_failed" - , clientid => ClientId - , username => Username - , reason => Reason - }), + ?SLOG(warning, #{ + msg => "client_login_failed", + clientid => ClientId, + username => Username, + reason => Reason + }), {reply, {error, ?RESP_PERMISSION_DENY, Reason}, Channel} end; {error, Reason} -> - ?SLOG(warning, #{ msg => "client_login_failed" - , clientid => ClientId - , username => Username - , reason => Reason}), + ?SLOG(warning, #{ + msg => "client_login_failed", + clientid => ClientId, + username => Username, + reason => Reason + }), {reply, {error, ?RESP_PERMISSION_DENY, Reason}, Channel} end; - -handle_call({start_timer, keepalive, Interval}, _From, - Channel = #channel{ - conninfo = ConnInfo, - clientinfo = ClientInfo - }) -> +handle_call( + {start_timer, keepalive, Interval}, + _From, + Channel = #channel{ + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{keepalive => Interval}, NClientInfo = ClientInfo#{keepalive => Interval}, NChannel = Channel#channel{conninfo = NConnInfo, clientinfo = NClientInfo}, {reply, ok, ensure_keepalive(NChannel)}; - -handle_call({subscribe_from_client, TopicFilter, Qos}, _From, - Channel = #channel{ - ctx = Ctx, - conn_state = connected, - clientinfo = ClientInfo}) -> +handle_call( + {subscribe_from_client, TopicFilter, Qos}, + _From, + Channel = #channel{ + ctx = Ctx, + conn_state = connected, + clientinfo = ClientInfo + } +) -> case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, TopicFilter) of deny -> {reply, {error, ?RESP_PERMISSION_DENY, <<"Authorization deny">>}, Channel}; @@ -338,31 +402,35 @@ handle_call({subscribe_from_client, TopicFilter, Qos}, _From, {ok, _, NChannel} = do_subscribe([{TopicFilter, #{qos => Qos}}], Channel), {reply, ok, NChannel} end; - handle_call({subscribe, Topic, SubOpts}, _From, Channel) -> - {ok, - [{NTopicFilter, NSubOpts}], NChannel} = do_subscribe([{Topic, SubOpts}], Channel), + {ok, [{NTopicFilter, NSubOpts}], NChannel} = do_subscribe([{Topic, SubOpts}], Channel), {reply, {ok, {NTopicFilter, NSubOpts}}, NChannel}; - -handle_call({unsubscribe_from_client, TopicFilter}, _From, - Channel = #channel{conn_state = connected}) -> +handle_call( + {unsubscribe_from_client, TopicFilter}, + _From, + Channel = #channel{conn_state = connected} +) -> {ok, NChannel} = do_unsubscribe([{TopicFilter, #{}}], Channel), {reply, ok, NChannel}; - handle_call({unsubscribe, Topic}, _From, Channel) -> {ok, NChannel} = do_unsubscribe([Topic], Channel), {reply, ok, NChannel}; - handle_call(subscriptions, _From, Channel = #channel{subscriptions = Subs}) -> {reply, {ok, maps:to_list(Subs)}, Channel}; - -handle_call({publish, Topic, Qos, Payload}, _From, - Channel = #channel{ - ctx = Ctx, - conn_state = connected, - clientinfo = ClientInfo - = #{clientid := From, - mountpoint := Mountpoint}}) -> +handle_call( + {publish, Topic, Qos, Payload}, + _From, + Channel = #channel{ + ctx = Ctx, + conn_state = connected, + clientinfo = + ClientInfo = + #{ + clientid := From, + mountpoint := Mountpoint + } + } +) -> case emqx_gateway_ctx:authorize(Ctx, ClientInfo, publish, Topic) of deny -> {reply, {error, ?RESP_PERMISSION_DENY, <<"Authorization deny">>}, Channel}; @@ -372,36 +440,39 @@ handle_call({publish, Topic, Qos, Payload}, _From, _ = emqx:publish(NMsg), {reply, ok, Channel} end; - handle_call(kick, _From, Channel) -> {shutdown, kicked, ok, ensure_disconnected(kicked, Channel)}; - handle_call(discard, _From, Channel) -> {shutdown, discarded, ok, Channel}; - handle_call(Req, _From, Channel) -> - ?SLOG(warning, #{ msg => "unexpected_call" - , call => Req - }), + ?SLOG(warning, #{ + msg => "unexpected_call", + call => Req + }), {reply, {error, unexpected_call}, Channel}. --spec handle_cast(any(), channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()}. +-spec handle_cast(any(), channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()}. handle_cast(Req, Channel) -> - ?SLOG(warning, #{ msg => "unexpected_call" - , call => Req - }), + ?SLOG(warning, #{ + msg => "unexpected_call", + call => Req + }), {ok, Channel}. --spec handle_info(any(), channel()) - -> {ok, channel()} - | {shutdown, Reason :: term(), channel()}. -handle_info({sock_closed, Reason}, - Channel = #channel{rqueue = Queue, inflight = Inflight}) -> - case queue:len(Queue) =:= 0 - andalso Inflight =:= undefined of +-spec handle_info(any(), channel()) -> + {ok, channel()} + | {shutdown, Reason :: term(), channel()}. +handle_info( + {sock_closed, Reason}, + Channel = #channel{rqueue = Queue, inflight = Inflight} +) -> + case + queue:len(Queue) =:= 0 andalso + Inflight =:= undefined + of true -> Channel1 = ensure_disconnected({sock_closed, Reason}, Channel), {shutdown, Reason, Channel1}; @@ -411,25 +482,24 @@ handle_info({sock_closed, Reason}, Channel2 = ensure_timer(force_timer, Channel1), {ok, ensure_disconnected({sock_closed, Reason}, Channel2)} end; - handle_info({hreply, on_socket_created, ok}, Channel) -> dispatch_or_close_process(Channel#channel{inflight = undefined}); -handle_info({hreply, FunName, ok}, Channel) - when FunName == on_socket_closed; - FunName == on_received_bytes; - FunName == on_received_messages; - FunName == on_timer_timeout -> +handle_info({hreply, FunName, ok}, Channel) when + FunName == on_socket_closed; + FunName == on_received_bytes; + FunName == on_received_messages; + FunName == on_timer_timeout +-> dispatch_or_close_process(Channel#channel{inflight = undefined}); handle_info({hreply, FunName, {error, Reason}}, Channel) -> {shutdown, {error, {FunName, Reason}}, Channel}; - handle_info({subscribe, _}, Channel) -> {ok, Channel}; - handle_info(Info, Channel) -> - ?SLOG(warning, #{ msg => "unexpected_info" - , info => Info - }), + ?SLOG(warning, #{ + msg => "unexpected_info", + info => Info + }), {ok, Channel}. -spec terminate(any(), channel()) -> channel(). @@ -446,13 +516,22 @@ do_subscribe(TopicFilters, Channel) -> fun({TopicFilter, SubOpts}, {MadeSubs, ChannelAcc}) -> {Sub, Channel1} = do_subscribe(TopicFilter, SubOpts, ChannelAcc), {MadeSubs ++ [Sub], Channel1} - end, {[], Channel}, parse_topic_filters(TopicFilters)), + end, + {[], Channel}, + parse_topic_filters(TopicFilters) + ), {ok, MadeSubs, NChannel}. %% @private -do_subscribe(TopicFilter, SubOpts, Channel = - #channel{clientinfo = ClientInfo = #{mountpoint := Mountpoint}, - subscriptions = Subs}) -> +do_subscribe( + TopicFilter, + SubOpts, + Channel = + #channel{ + clientinfo = ClientInfo = #{mountpoint := Mountpoint}, + subscriptions = Subs + } +) -> %% Mountpoint first NTopicFilter = emqx_mountpoint:mount(Mountpoint, TopicFilter), NSubOpts = maps:merge(emqx_gateway_utils:default_subopts(), SubOpts), @@ -462,34 +541,49 @@ do_subscribe(TopicFilter, SubOpts, Channel = case IsNew of true -> ok = emqx:subscribe(NTopicFilter, SubId, NSubOpts), - ok = emqx_hooks:run('session.subscribed', - [ClientInfo, NTopicFilter, NSubOpts#{is_new => IsNew}]), - {{NTopicFilter, NSubOpts}, - Channel#channel{subscriptions = Subs#{NTopicFilter => NSubOpts}}}; + ok = emqx_hooks:run( + 'session.subscribed', + [ClientInfo, NTopicFilter, NSubOpts#{is_new => IsNew}] + ), + {{NTopicFilter, NSubOpts}, Channel#channel{ + subscriptions = Subs#{NTopicFilter => NSubOpts} + }}; _ -> %% Update subopts ok = emqx:subscribe(NTopicFilter, SubId, NSubOpts), - {{NTopicFilter, NSubOpts}, - Channel#channel{subscriptions = Subs#{NTopicFilter => NSubOpts}}} + {{NTopicFilter, NSubOpts}, Channel#channel{ + subscriptions = Subs#{NTopicFilter => NSubOpts} + }} end. do_unsubscribe(TopicFilters, Channel) -> NChannel = lists:foldl( fun({TopicFilter, SubOpts}, ChannelAcc) -> do_unsubscribe(TopicFilter, SubOpts, ChannelAcc) - end, Channel, parse_topic_filters(TopicFilters)), + end, + Channel, + parse_topic_filters(TopicFilters) + ), {ok, NChannel}. %% @private -do_unsubscribe(TopicFilter, UnSubOpts, Channel = - #channel{clientinfo = ClientInfo = #{mountpoint := Mountpoint}, - subscriptions = Subs}) -> +do_unsubscribe( + TopicFilter, + UnSubOpts, + Channel = + #channel{ + clientinfo = ClientInfo = #{mountpoint := Mountpoint}, + subscriptions = Subs + } +) -> NTopicFilter = emqx_mountpoint:mount(Mountpoint, TopicFilter), case maps:find(NTopicFilter, Subs) of {ok, SubOpts} -> ok = emqx:unsubscribe(NTopicFilter), - ok = emqx_hooks:run('session.unsubscribed', - [ClientInfo, TopicFilter, maps:merge(SubOpts, UnSubOpts)]), + ok = emqx_hooks:run( + 'session.unsubscribed', + [ClientInfo, TopicFilter, maps:merge(SubOpts, UnSubOpts)] + ), Channel#channel{subscriptions = maps:remove(NTopicFilter, Subs)}; _ -> Channel @@ -503,25 +597,32 @@ parse_topic_filters(TopicFilters) -> %% Ensure & Hooks %%-------------------------------------------------------------------- -ensure_connected(Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_connected( + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)}, ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), - Channel#channel{conninfo = NConnInfo, - conn_state = connected - }. + Channel#channel{ + conninfo = NConnInfo, + conn_state = connected + }. -ensure_disconnected(Reason, Channel = #channel{ - ctx = Ctx, - conn_state = connected, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_disconnected( + Reason, + Channel = #channel{ + ctx = Ctx, + conn_state = connected, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, ok = run_hooks(Ctx, 'client.disconnected', [ClientInfo, Reason, NConnInfo]), Channel#channel{conninfo = NConnInfo, conn_state = disconnected}; - ensure_disconnected(_Reason, Channel = #channel{conninfo = ConnInfo}) -> NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, Channel#channel{conninfo = NConnInfo, conn_state = disconnected}. @@ -549,8 +650,9 @@ ensure_timer(Name, Channel = #channel{timers = Timers}) -> TRef = maps:get(Name, Timers, undefined), Time = interval(Name, Channel), case TRef == undefined andalso Time > 0 of - true -> ensure_timer(Name, Time, Channel); - false -> Channel %% Timer disabled or exists + true -> ensure_timer(Name, Time, Channel); + %% Timer disabled or exists + false -> Channel end. ensure_timer(Name, Time, Channel = #channel{timers = Timers}) -> @@ -574,12 +676,15 @@ interval(alive_timer, #channel{keepalive = Keepalive}) -> %%-------------------------------------------------------------------- wrap(Req) -> - Req#{conn => base64:encode(term_to_binary(self()))}. + Req#{conn => base64:encode(term_to_binary(self()))}. -dispatch_or_close_process(Channel = #channel{ - rqueue = Queue, - inflight = undefined, - gcli = GClient}) -> +dispatch_or_close_process( + Channel = #channel{ + rqueue = Queue, + inflight = undefined, + gcli = GClient + } +) -> case queue:out(Queue) of {empty, _} -> case Channel#channel.conn_state of @@ -613,30 +718,36 @@ enrich_clientinfo(InClientInfo = #{proto_name := ProtoName}, ClientInfo) -> NClientInfo#{protocol => proto_name_to_protocol(ProtoName)}. default_conninfo(ConnInfo) -> - ConnInfo#{clean_start => true, - clientid => undefined, - username => undefined, - conn_mod => undefined, - conn_props => #{}, - connected => true, - proto_name => <<"exproto">>, - proto_ver => <<"1.0">>, - connected_at => erlang:system_time(millisecond), - keepalive => 0, - receive_maximum => 0, - expiry_interval => 0}. + ConnInfo#{ + clean_start => true, + clientid => undefined, + username => undefined, + conn_mod => undefined, + conn_props => #{}, + connected => true, + proto_name => <<"exproto">>, + proto_ver => <<"1.0">>, + connected_at => erlang:system_time(millisecond), + keepalive => 0, + receive_maximum => 0, + expiry_interval => 0 + }. -default_clientinfo(#{peername := {PeerHost, _}, - sockname := {_, SockPort}}) -> - #{zone => default, - protocol => exproto, - peerhost => PeerHost, - sockport => SockPort, - clientid => undefined, - username => undefined, - is_bridge => false, - is_superuser => false, - mountpoint => undefined}. +default_clientinfo(#{ + peername := {PeerHost, _}, + sockname := {_, SockPort} +}) -> + #{ + zone => default, + protocol => exproto, + peerhost => PeerHost, + sockport => SockPort, + clientid => undefined, + username => undefined, + is_bridge => false, + is_superuser => false, + mountpoint => undefined + }. stringfy(Reason) -> unicode:characters_to_binary((io_lib:format("~0p", [Reason]))). diff --git a/apps/emqx_gateway/src/exproto/emqx_exproto_frame.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_frame.erl index 3b5d693e8..2d22db727 100644 --- a/apps/emqx_gateway/src/exproto/emqx_exproto_frame.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_frame.erl @@ -20,14 +20,15 @@ -behaviour(emqx_gateway_frame). --export([ initial_parse_state/1 - , serialize_opts/0 - , parse/2 - , serialize_pkt/2 - , format/1 - , is_message/1 - , type/1 - ]). +-export([ + initial_parse_state/1, + serialize_opts/0, + parse/2, + serialize_pkt/2, + format/1, + is_message/1, + type/1 +]). initial_parse_state(_) -> #{}. @@ -47,4 +48,3 @@ format(Data) -> is_message(_) -> true. type(_) -> unknown. - diff --git a/apps/emqx_gateway/src/exproto/emqx_exproto_gcli.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_gcli.erl index 4017ef3f7..d1bf4ba94 100644 --- a/apps/emqx_gateway/src/exproto/emqx_exproto_gcli.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_gcli.erl @@ -21,26 +21,26 @@ -include_lib("emqx/include/logger.hrl"). - %% APIs -export([async_call/3]). -export([start_link/2]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -record(state, { - pool, - id, - streams - }). + pool, + id, + streams +}). -define(CONN_ADAPTER_MOD, emqx_exproto_v_1_connection_handler_client). @@ -49,11 +49,18 @@ %%-------------------------------------------------------------------- start_link(Pool, Id) -> - gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)}, - ?MODULE, [Pool, Id], []). + gen_server:start_link( + {local, emqx_misc:proc_name(?MODULE, Id)}, + ?MODULE, + [Pool, Id], + [] + ). -async_call(FunName, Req = #{conn := Conn}, - Options = #{pool_name := PoolName}) -> +async_call( + FunName, + Req = #{conn := Conn}, + Options = #{pool_name := PoolName} +) -> cast(pick(PoolName, Conn), {rpc, FunName, Req, Options, self()}). %%-------------------------------------------------------------------- @@ -82,42 +89,49 @@ handle_call(_Request, _From, State) -> handle_cast({rpc, Fun, Req, Options, From}, State = #state{streams = Streams}) -> case ensure_stream_opened(Fun, Options, Streams) of {error, Reason} -> - ?SLOG(error, #{ msg => "request_grpc_server_failed" - , function => {?CONN_ADAPTER_MOD, Fun, Options} - , reason => Reason}), + ?SLOG(error, #{ + msg => "request_grpc_server_failed", + function => {?CONN_ADAPTER_MOD, Fun, Options}, + reason => Reason + }), reply(From, Fun, {error, Reason}), {noreply, State#state{streams = Streams#{Fun => undefined}}}; {ok, Stream} -> case catch grpc_client:send(Stream, Req) of ok -> - ?SLOG(debug, #{ msg => "send_grpc_request_succeed" - , function => {?CONN_ADAPTER_MOD, Fun} - , request => Req - }), + ?SLOG(debug, #{ + msg => "send_grpc_request_succeed", + function => {?CONN_ADAPTER_MOD, Fun}, + request => Req + }), reply(From, Fun, ok), {noreply, State#state{streams = Streams#{Fun => Stream}}}; {'EXIT', {not_found, _Stk}} -> %% Not found the stream, reopen it - ?SLOG(info, #{ msg => "cannt_find_old_stream_ref" - , function => {?CONN_ADAPTER_MOD, Fun} - }), + ?SLOG(info, #{ + msg => "cannt_find_old_stream_ref", + function => {?CONN_ADAPTER_MOD, Fun} + }), handle_cast( - {rpc, Fun, Req, Options, From}, - State#state{streams = maps:remove(Fun, Streams)}); + {rpc, Fun, Req, Options, From}, + State#state{streams = maps:remove(Fun, Streams)} + ); {'EXIT', {timeout, _Stk}} -> - ?SLOG(error, #{ msg => "send_grpc_request_timeout" - , function => {?CONN_ADAPTER_MOD, Fun} - , request => Req - }), + ?SLOG(error, #{ + msg => "send_grpc_request_timeout", + function => {?CONN_ADAPTER_MOD, Fun}, + request => Req + }), reply(From, Fun, {error, timeout}), {noreply, State#state{streams = Streams#{Fun => Stream}}}; {'EXIT', {Reason1, Stk}} -> - ?SLOG(error, #{ msg => "send_grpc_request_failed" - , function => {?CONN_ADAPTER_MOD, Fun} - , request => Req - , error => Reason1 - , stacktrace => Stk - }), + ?SLOG(error, #{ + msg => "send_grpc_request_failed", + function => {?CONN_ADAPTER_MOD, Fun}, + request => Req, + error => Reason1, + stacktrace => Stk + }), reply(From, Fun, {error, Reason1}), {noreply, State#state{streams = Streams#{Fun => undefined}}} end @@ -147,5 +161,6 @@ ensure_stream_opened(Fun, Options, Streams) -> {ok, Stream} -> {ok, Stream}; {error, Reason} -> {error, Reason} end; - Stream -> {ok, Stream} + Stream -> + {ok, Stream} end. diff --git a/apps/emqx_gateway/src/exproto/emqx_exproto_gsvr.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_gsvr.erl index 50765e0ab..5a1932b2b 100644 --- a/apps/emqx_gateway/src/exproto/emqx_exproto_gsvr.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_gsvr.erl @@ -27,49 +27,58 @@ -define(DEFAULT_CALL_TIMEOUT, 5000). %% gRPC server callbacks --export([ send/2 - , close/2 - , authenticate/2 - , start_timer/2 - , publish/2 - , subscribe/2 - , unsubscribe/2 - ]). +-export([ + send/2, + close/2, + authenticate/2, + start_timer/2, + publish/2, + subscribe/2, + unsubscribe/2 +]). %%-------------------------------------------------------------------- %% gRPC ConnectionAdapter service %%-------------------------------------------------------------------- --spec send(emqx_exproto_pb:send_bytes_request(), grpc:metadata()) - -> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. +-spec send(emqx_exproto_pb:send_bytes_request(), grpc:metadata()) -> + {ok, emqx_exproto_pb:code_response(), grpc:metadata()} + | {error, grpc_cowboy_h:error_response()}. send(Req = #{conn := Conn, bytes := Bytes}, Md) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response(call(Conn, {send, Bytes})), Md}. --spec close(emqx_exproto_pb:close_socket_request(), grpc:metadata()) - -> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. +-spec close(emqx_exproto_pb:close_socket_request(), grpc:metadata()) -> + {ok, emqx_exproto_pb:code_response(), grpc:metadata()} + | {error, grpc_cowboy_h:error_response()}. close(Req = #{conn := Conn}, Md) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response(call(Conn, close)), Md}. --spec authenticate(emqx_exproto_pb:authenticate_request(), grpc:metadata()) - -> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -authenticate(Req = #{conn := Conn, - password := Password, - clientinfo := ClientInfo}, Md) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), +-spec authenticate(emqx_exproto_pb:authenticate_request(), grpc:metadata()) -> + {ok, emqx_exproto_pb:code_response(), grpc:metadata()} + | {error, grpc_cowboy_h:error_response()}. +authenticate( + Req = #{ + conn := Conn, + password := Password, + clientinfo := ClientInfo + }, + Md +) -> + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), case validate(clientinfo, ClientInfo) of false -> {ok, response({error, ?RESP_REQUIRED_PARAMS_MISSED}), Md}; @@ -77,70 +86,78 @@ authenticate(Req = #{conn := Conn, {ok, response(call(Conn, {auth, ClientInfo, Password})), Md} end. --spec start_timer(emqx_exproto_pb:timer_request(), grpc:metadata()) - -> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -start_timer(Req = #{conn := Conn, type := Type, interval := Interval}, Md) - when Type =:= 'KEEPALIVE' andalso Interval > 0 -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), +-spec start_timer(emqx_exproto_pb:timer_request(), grpc:metadata()) -> + {ok, emqx_exproto_pb:code_response(), grpc:metadata()} + | {error, grpc_cowboy_h:error_response()}. +start_timer(Req = #{conn := Conn, type := Type, interval := Interval}, Md) when + Type =:= 'KEEPALIVE' andalso Interval > 0 +-> + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response(call(Conn, {start_timer, keepalive, Interval})), Md}; start_timer(Req, Md) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}. --spec publish(emqx_exproto_pb:publish_request(), grpc:metadata()) - -> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -publish(Req = #{conn := Conn, topic := Topic, qos := Qos, payload := Payload}, Md) - when ?IS_QOS(Qos) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), +-spec publish(emqx_exproto_pb:publish_request(), grpc:metadata()) -> + {ok, emqx_exproto_pb:code_response(), grpc:metadata()} + | {error, grpc_cowboy_h:error_response()}. +publish(Req = #{conn := Conn, topic := Topic, qos := Qos, payload := Payload}, Md) when + ?IS_QOS(Qos) +-> + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response(call(Conn, {publish, Topic, Qos, Payload})), Md}; - publish(Req, Md) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}. --spec subscribe(emqx_exproto_pb:subscribe_request(), grpc:metadata()) - -> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -subscribe(Req = #{conn := Conn, topic := Topic, qos := Qos}, Md) - when ?IS_QOS(Qos) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), +-spec subscribe(emqx_exproto_pb:subscribe_request(), grpc:metadata()) -> + {ok, emqx_exproto_pb:code_response(), grpc:metadata()} + | {error, grpc_cowboy_h:error_response()}. +subscribe(Req = #{conn := Conn, topic := Topic, qos := Qos}, Md) when + ?IS_QOS(Qos) +-> + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response(call(Conn, {subscribe_from_client, Topic, Qos})), Md}; - subscribe(Req, Md) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}. --spec unsubscribe(emqx_exproto_pb:unsubscribe_request(), grpc:metadata()) - -> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. +-spec unsubscribe(emqx_exproto_pb:unsubscribe_request(), grpc:metadata()) -> + {ok, emqx_exproto_pb:code_response(), grpc:metadata()} + | {error, grpc_cowboy_h:error_response()}. unsubscribe(Req = #{conn := Conn, topic := Topic}, Md) -> - ?SLOG(debug, #{ msg => "recv_grpc_function_call" - , function => ?FUNCTION_NAME - , request => Req - }), + ?SLOG(debug, #{ + msg => "recv_grpc_function_call", + function => ?FUNCTION_NAME, + request => Req + }), {ok, response(call(Conn, {unsubscribe_from_client, Topic})), Md}. %%-------------------------------------------------------------------- @@ -155,20 +172,20 @@ call(ConnStr, Req) -> Pid = to_pid(ConnStr), emqx_gateway_conn:call(Pid, Req, ?DEFAULT_CALL_TIMEOUT) catch - exit : badarg -> + exit:badarg -> {error, ?RESP_PARAMS_TYPE_ERROR, <<"The conn type error">>}; - exit : noproc -> - {error, ?RESP_CONN_PROCESS_NOT_ALIVE, - <<"Connection process is not alive">>}; - exit : timeout -> + exit:noproc -> + {error, ?RESP_CONN_PROCESS_NOT_ALIVE, <<"Connection process is not alive">>}; + exit:timeout -> {error, ?RESP_UNKNOWN, <<"Connection is not answered">>}; - Class : Reason : Stk-> - ?SLOG(error, #{ msg => "call_conn_process_crashed" - , request => Req - , conn_str=> ConnStr - , reason => {Class, Reason} - , stacktrace => Stk - }), + Class:Reason:Stk -> + ?SLOG(error, #{ + msg => "call_conn_process_crashed", + request => Req, + conn_str => ConnStr, + reason => {Class, Reason}, + stacktrace => Stk + }), {error, ?RESP_UNKNOWN, <<"Unknown crashes">>} end. @@ -184,11 +201,13 @@ validate(clientinfo, M) -> response(ok) -> #{code => ?RESP_SUCCESS}; -response({error, Code, Reason}) - when ?IS_GRPC_RESULT_CODE(Code) -> +response({error, Code, Reason}) when + ?IS_GRPC_RESULT_CODE(Code) +-> #{code => Code, message => stringfy(Reason)}; -response({error, Code}) - when ?IS_GRPC_RESULT_CODE(Code) -> +response({error, Code}) when + ?IS_GRPC_RESULT_CODE(Code) +-> #{code => Code}; response(Other) -> #{code => ?RESP_UNKNOWN, message => stringfy(Other)}. diff --git a/apps/emqx_gateway/src/exproto/emqx_exproto_impl.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_impl.erl index 5c9967182..2c89d32c7 100644 --- a/apps/emqx_gateway/src/exproto/emqx_exproto_impl.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_impl.erl @@ -21,29 +21,33 @@ -include_lib("emqx/include/logger.hrl"). --import(emqx_gateway_utils, - [ normalize_config/1 - , start_listeners/4 - , stop_listeners/2 - ]). +-import( + emqx_gateway_utils, + [ + normalize_config/1, + start_listeners/4, + stop_listeners/2 + ] +). %% APIs --export([ reg/0 - , unreg/0 - ]). +-export([ + reg/0, + unreg/0 +]). --export([ on_gateway_load/2 - , on_gateway_update/3 - , on_gateway_unload/2 - ]). +-export([ + on_gateway_load/2, + on_gateway_update/3, + on_gateway_unload/2 +]). %%-------------------------------------------------------------------- %% APIs %%-------------------------------------------------------------------- reg() -> - RegistryOptions = [ {cbkmod, ?MODULE} - ], + RegistryOptions = [{cbkmod, ?MODULE}], emqx_gateway_registry:reg(exproto, RegistryOptions). unreg() -> @@ -53,13 +57,18 @@ unreg() -> %% emqx_gateway_registry callbacks %%-------------------------------------------------------------------- -on_gateway_load(_Gateway = #{ name := GwName, - config := Config - }, Ctx) -> +on_gateway_load( + _Gateway = #{ + name := GwName, + config := Config + }, + Ctx +) -> %% XXX: How to monitor it ? - _ = start_grpc_client_channel(GwName, - maps:get(handler, Config, undefined) - ), + _ = start_grpc_client_channel( + GwName, + maps:get(handler, Config, undefined) + ), %% XXX: How to monitor it ? _ = start_grpc_server(GwName, maps:get(server, Config, undefined)), @@ -67,29 +76,39 @@ on_gateway_load(_Gateway = #{ name := GwName, PoolName = pool_name(GwName), PoolSize = emqx_vm:schedulers() * 2, {ok, PoolSup} = emqx_pool_sup:start_link( - PoolName, hash, PoolSize, - {emqx_exproto_gcli, start_link, []}), + PoolName, + hash, + PoolSize, + {emqx_exproto_gcli, start_link, []} + ), NConfig = maps:without( - [server, handler], - Config#{pool_name => PoolName} - ), + [server, handler], + Config#{pool_name => PoolName} + ), Listeners = emqx_gateway_utils:normalize_config( - NConfig#{handler => GwName} - ), + NConfig#{handler => GwName} + ), - ModCfg = #{frame_mod => emqx_exproto_frame, - chann_mod => emqx_exproto_channel - }, - case start_listeners( - Listeners, GwName, Ctx, ModCfg) of + ModCfg = #{ + frame_mod => emqx_exproto_frame, + chann_mod => emqx_exproto_channel + }, + case + start_listeners( + Listeners, GwName, Ctx, ModCfg + ) + of {ok, ListenerPids} -> {ok, ListenerPids, _GwState = #{ctx => Ctx, pool => PoolSup}}; {error, {Reason, Listener}} -> - throw({badconf, #{ key => listeners - , vallue => Listener - , reason => Reason - }}) + throw( + {badconf, #{ + key => listeners, + vallue => Listener, + reason => Reason + }} + ) end. on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> @@ -100,16 +119,22 @@ on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> on_gateway_unload(Gateway, GwState), on_gateway_load(Gateway#{config => Config}, Ctx) catch - Class : Reason : Stk -> - logger:error("Failed to update ~ts; " - "reason: {~0p, ~0p} stacktrace: ~0p", - [GwName, Class, Reason, Stk]), + Class:Reason:Stk -> + logger:error( + "Failed to update ~ts; " + "reason: {~0p, ~0p} stacktrace: ~0p", + [GwName, Class, Reason, Stk] + ), {error, Reason} end. -on_gateway_unload(_Gateway = #{ name := GwName, - config := Config - }, _GwState = #{pool := PoolSup}) -> +on_gateway_unload( + _Gateway = #{ + name := GwName, + config := Config + }, + _GwState = #{pool := PoolSup} +) -> Listeners = emqx_gateway_utils:normalize_config(Config), %% Stop funcs??? exit(PoolSup, kill), @@ -124,29 +149,42 @@ on_gateway_unload(_Gateway = #{ name := GwName, start_grpc_server(_GwName, undefined) -> undefined; start_grpc_server(GwName, Options = #{bind := ListenOn}) -> - Services = #{protos => [emqx_exproto_pb], - services => #{ - 'emqx.exproto.v1.ConnectionAdapter' => emqx_exproto_gsvr} - }, - SvrOptions = case emqx_map_lib:deep_get([ssl, enable], Options, false) of - false -> []; - true -> - [{ssl_options, + Services = #{ + protos => [emqx_exproto_pb], + services => #{ + 'emqx.exproto.v1.ConnectionAdapter' => emqx_exproto_gsvr + } + }, + SvrOptions = + case emqx_map_lib:deep_get([ssl, enable], Options, false) of + false -> + []; + true -> + [ + {ssl_options, maps:to_list( - maps:without([enable], maps:get(ssl, Options, #{})) - ) - }] - end, + maps:without([enable], maps:get(ssl, Options, #{})) + )} + ] + end, case grpc:start_server(GwName, ListenOn, Services, SvrOptions) of {ok, _SvrPid} -> - console_print("Start ~ts gRPC server on ~p successfully.~n", - [GwName, ListenOn]); + console_print( + "Start ~ts gRPC server on ~p successfully.~n", + [GwName, ListenOn] + ); {error, Reason} -> - ?ELOG("Failed to start ~ts gRPC server on ~p, reason: ~0p", - [GwName, ListenOn, Reason]), - throw({badconf, #{key => server, - value => Options, - reason => illegal_grpc_server_confs}}) + ?ELOG( + "Failed to start ~ts gRPC server on ~p, reason: ~0p", + [GwName, ListenOn, Reason] + ), + throw( + {badconf, #{ + key => server, + value => Options, + reason => illegal_grpc_server_confs + }} + ) end. stop_grpc_server(GwName) -> @@ -158,13 +196,16 @@ start_grpc_client_channel(_GwName, undefined) -> start_grpc_client_channel(GwName, Options = #{address := Address}) -> #{host := Host, port := Port} = case emqx_http_lib:uri_parse(Address) of - {ok, URIMap0} -> URIMap0; + {ok, URIMap0} -> + URIMap0; {error, _Reason} -> - throw({badconf, #{key => address, - value => Address, - reason => illegal_grpc_address - }}) - + throw( + {badconf, #{ + key => address, + value => Address, + reason => illegal_grpc_address + }} + ) end, case emqx_map_lib:deep_get([ssl, enable], Options, false) of false -> @@ -172,9 +213,13 @@ start_grpc_client_channel(GwName, Options = #{address := Address}) -> grpc_client_sup:create_channel_pool(GwName, SvrAddr, #{}); true -> SslOpts = maps:to_list(maps:get(ssl, Options, #{})), - ClientOpts = #{gun_opts => - #{transport => ssl, - transport_opts => SslOpts}}, + ClientOpts = #{ + gun_opts => + #{ + transport => ssl, + transport_opts => SslOpts + } + }, SvrAddr = compose_http_uri(https, Host, Port), grpc_client_sup:create_channel_pool(GwName, SvrAddr, ClientOpts) @@ -182,8 +227,10 @@ start_grpc_client_channel(GwName, Options = #{address := Address}) -> compose_http_uri(Scheme, Host, Port) -> lists:flatten( - io_lib:format( - "~s://~s:~w", [Scheme, inet:ntoa(Host), Port])). + io_lib:format( + "~s://~s:~w", [Scheme, inet:ntoa(Host), Port] + ) + ). stop_grpc_client_channel(GwName) -> _ = grpc_client_sup:stop_channel_pool(GwName), diff --git a/apps/emqx_gateway/src/exproto/include/emqx_exproto.hrl b/apps/emqx_gateway/src/exproto/include/emqx_exproto.hrl index 877afb6af..e4c32c7ff 100644 --- a/apps/emqx_gateway/src/exproto/include/emqx_exproto.hrl +++ b/apps/emqx_gateway/src/exproto/include/emqx_exproto.hrl @@ -16,8 +16,13 @@ -define(APP, emqx_exproto). --define(TCP_SOCKOPTS, [binary, {packet, raw}, {reuseaddr, true}, - {backlog, 512}, {nodelay, true}]). +-define(TCP_SOCKOPTS, [ + binary, + {packet, raw}, + {reuseaddr, true}, + {backlog, 512}, + {nodelay, true} +]). -define(UDP_SOCKOPTS, []). @@ -30,7 +35,9 @@ -define(RESP_PARAMS_TYPE_ERROR, 'PARAMS_TYPE_ERROR'). -define(RESP_REQUIRED_PARAMS_MISSED, 'REQUIRED_PARAMS_MISSED'). -define(RESP_PERMISSION_DENY, 'PERMISSION_DENY'). --define(IS_GRPC_RESULT_CODE(C), ( C =:= ?RESP_SUCCESS - orelse C =:= ?RESP_CONN_PROCESS_NOT_ALIVE - orelse C =:= ?RESP_REQUIRED_PARAMS_MISSED - orelse C =:= ?RESP_PERMISSION_DENY)). +-define(IS_GRPC_RESULT_CODE(C), + (C =:= ?RESP_SUCCESS orelse + C =:= ?RESP_CONN_PROCESS_NOT_ALIVE orelse + C =:= ?RESP_REQUIRED_PARAMS_MISSED orelse + C =:= ?RESP_PERMISSION_DENY) +). diff --git a/apps/emqx_gateway/src/lwm2m/binary_util.erl b/apps/emqx_gateway/src/lwm2m/binary_util.erl index d91f0b8f5..eabdb9c5f 100644 --- a/apps/emqx_gateway/src/lwm2m/binary_util.erl +++ b/apps/emqx_gateway/src/lwm2m/binary_util.erl @@ -3,58 +3,57 @@ %% copied from https://github.com/arcusfelis/binary2 %% Bytes --export([ reverse/1 - , join/2 - , duplicate/2 - , suffix/2 - , prefix/2 - ]). +-export([ + reverse/1, + join/2, + duplicate/2, + suffix/2, + prefix/2 +]). %% Bits --export([ union/2 - , subtract/2 - , intersection/2 - , inverse/1 - ]). +-export([ + union/2, + subtract/2, + intersection/2, + inverse/1 +]). %% Trimming --export([ rtrim/1 - , rtrim/2 - , ltrim/1 - , ltrim/2 - , trim/1 - , trim/2 - ]). +-export([ + rtrim/1, + rtrim/2, + ltrim/1, + ltrim/2, + trim/1, + trim/2 +]). %% Parsing --export([ bin_to_int/1]). +-export([bin_to_int/1]). %% Matching --export([ optimize_patterns/1]). +-export([optimize_patterns/1]). %% CoAP --export([ join_path/1]). +-export([join_path/1]). - -trim(B) -> trim(B, 0). +trim(B) -> trim(B, 0). ltrim(B) -> ltrim(B, 0). rtrim(B) -> rtrim(B, 0). - rtrim(B, X) when is_binary(B), is_integer(X) -> S = byte_size(B), do_rtrim(S, B, X); -rtrim(B, [_|_]=Xs) when is_binary(B) -> +rtrim(B, [_ | _] = Xs) when is_binary(B) -> S = byte_size(B), do_mrtrim(S, B, Xs). - ltrim(B, X) when is_binary(B), is_integer(X) -> do_ltrim(B, X); -ltrim(B, [_|_]=Xs) when is_binary(B) -> +ltrim(B, [_ | _] = Xs) when is_binary(B) -> do_mltrim(B, Xs). - %% @doc The second element is a single integer element or an ordset of elements. trim(B, X) when is_binary(B), is_integer(X) -> From = ltrimc(B, X, 0), @@ -65,7 +64,7 @@ trim(B, X) when is_binary(B), is_integer(X) -> To = do_rtrimc(S, B, X), binary:part(B, From, To - From) end; -trim(B, [_|_]=Xs) when is_binary(B) -> +trim(B, [_ | _] = Xs) when is_binary(B) -> From = mltrimc(B, Xs, 0), case byte_size(B) of From -> @@ -83,7 +82,7 @@ do_ltrim(B, _X) -> %% multi, left trimming. do_mltrim(<> = XB, Xs) -> case ordsets:is_element(X, Xs) of - true -> do_mltrim(B, Xs); + true -> do_mltrim(B, Xs); false -> XB end; do_mltrim(<<>>, _Xs) -> @@ -105,20 +104,19 @@ do_mrtrim(S, B, Xs) -> S2 = S - 1, X = binary:at(B, S2), case ordsets:is_element(X, Xs) of - true -> do_mrtrim(S2, B, Xs); + true -> do_mrtrim(S2, B, Xs); false -> binary_part(B, 0, S) end. - ltrimc(<>, X, C) -> - ltrimc(B, X, C+1); + ltrimc(B, X, C + 1); ltrimc(_B, _X, C) -> C. %% multi, left trimming, returns a count of matched bytes from the left. mltrimc(<>, Xs, C) -> case ordsets:is_element(X, Xs) of - true -> mltrimc(B, Xs, C+1); + true -> mltrimc(B, Xs, C + 1); false -> C end; mltrimc(<<>>, _Xs, C) -> @@ -138,7 +136,7 @@ do_mrtrimc(S, B, Xs) -> S2 = S - 1, X = binary:at(B, S2), case ordsets:is_element(X, Xs) of - true -> do_mrtrimc(S2, B, Xs); + true -> do_mrtrimc(S2, B, Xs); false -> S end. @@ -148,13 +146,12 @@ reverse(Bin) when is_binary(Bin) -> <> = Bin, <>. -join([B|Bs], Sep) when is_binary(Sep) -> - iolist_to_binary([B|add_separator(Bs, Sep)]); - +join([B | Bs], Sep) when is_binary(Sep) -> + iolist_to_binary([B | add_separator(Bs, Sep)]); join([], _Sep) -> <<>>. -add_separator([B|Bs], Sep) -> +add_separator([B | Bs], Sep) -> [Sep, B | add_separator(Bs, Sep)]; add_separator([], _) -> []. @@ -168,8 +165,7 @@ prefix(B, L) when is_binary(B), is_integer(L), L > 0 -> suffix(B, L) when is_binary(B), is_integer(L), L > 0 -> S = byte_size(B), - binary:part(B, S-L, L). - + binary:part(B, S - L, L). union(B1, B2) -> S = bit_size(B1), @@ -203,7 +199,7 @@ bin_to_int(Bin) -> bin_to_int(Bin, 0). bin_to_int(<>, X) when $0 =< H, H =< $9 -> - bin_to_int(T, (X*10)+(H-$0)); + bin_to_int(T, (X * 10) + (H - $0)); bin_to_int(Bin, X) -> {X, Bin}. @@ -213,10 +209,10 @@ optimize_patterns(Patterns) -> Sorted = lists:usort(Patterns), remove_long_duplicates(Sorted). -remove_long_duplicates([H|T]) -> +remove_long_duplicates([H | T]) -> %% match(Subject, Pattern) DedupT = [X || X <- T, binary:match(X, H) =:= nomatch], - [H|remove_long_duplicates(DedupT)]; + [H | remove_long_duplicates(DedupT)]; remove_long_duplicates([]) -> []. @@ -224,7 +220,5 @@ join_path(PathList) -> join_path(PathList, <<>>). join_path([], Result) -> Result; -join_path([<<>> | PathList], Result) -> - join_path(PathList, Result); -join_path([Path | PathList], Result) -> - join_path(PathList, <>). +join_path([<<>> | PathList], Result) -> join_path(PathList, Result); +join_path([Path | PathList], Result) -> join_path(PathList, <>). diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_api.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_api.erl index 3bc7f1950..d053a8b06 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_api.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_api.erl @@ -23,7 +23,7 @@ -export([lookup_cmd/2, observe/2, read/2, write/2]). --define(PATH(Suffix), "/gateway/lwm2m/clients/:clientid"Suffix). +-define(PATH(Suffix), "/gateway/lwm2m/clients/:clientid" Suffix). -define(DATA_TYPE, ['Integer', 'Float', 'Time', 'String', 'Boolean', 'Opaque', 'Objlnk']). -import(hoconsc, [mk/2, ref/1, ref/2]). @@ -104,8 +104,11 @@ schema(?PATH("/write")) -> parameters => [ {clientid, mk(binary(), #{in => path, example => "urn:oma:lwm2m:oma:2"})}, {path, mk(binary(), #{in => query, required => true, example => "/3/0/7"})}, - {type, mk(hoconsc:enum(?DATA_TYPE), - #{in => query, required => true, example => 'Integer'})}, + {type, + mk( + hoconsc:enum(?DATA_TYPE), + #{in => query, required => true, example => 'Integer'} + )}, {value, mk(binary(), #{in => query, required => true, example => 123})} ], responses => #{ @@ -118,18 +121,23 @@ schema(?PATH("/write")) -> fields(resource) -> [ {operations, mk(binary(), #{desc => <<"Resource Operations">>, example => "E"})}, - {'dataType', mk(hoconsc:enum(?DATA_TYPE), #{desc => <<"Data Type">>, - example => 'Integer'})}, - {path, mk(binary(), #{desc => <<"Resource Path">>, example => "urn:oma:lwm2m:oma:2"})}, - {name, mk(binary(), #{desc => <<"Resource Name">>, example => "lwm2m-test"})} + {'dataType', + mk(hoconsc:enum(?DATA_TYPE), #{ + desc => <<"Data Type">>, + example => 'Integer' + })}, + {path, mk(binary(), #{desc => <<"Resource Path">>, example => "urn:oma:lwm2m:oma:2"})}, + {name, mk(binary(), #{desc => <<"Resource Name">>, example => "lwm2m-test"})} ]. lookup_cmd(get, #{bindings := Bindings, query_string := QS}) -> ClientId = maps:get(clientid, Bindings), case emqx_gateway_cm_registry:lookup_channels(lwm2m, ClientId) of [Channel | _] -> - #{<<"path">> := Path, - <<"action">> := Action} = QS, + #{ + <<"path">> := Path, + <<"action">> := Action + } = QS, {ok, Result} = emqx_lwm2m_channel:lookup_cmd(Channel, Path, Action), lookup_cmd_return(Result, ClientId, Action, Path); _ -> @@ -137,63 +145,73 @@ lookup_cmd(get, #{bindings := Bindings, query_string := QS}) -> end. lookup_cmd_return(undefined, ClientId, Action, Path) -> - {200, - #{clientid => ClientId, - action => Action, - code => <<"6.01">>, - codeMsg => <<"reply_not_received">>, - path => Path}}; - + {200, #{ + clientid => ClientId, + action => Action, + code => <<"6.01">>, + codeMsg => <<"reply_not_received">>, + path => Path + }}; lookup_cmd_return({Code, CodeMsg, Content}, ClientId, Action, Path) -> {200, - format_cmd_content(Content, - Action, - #{clientid => ClientId, - action => Action, - code => Code, - codeMsg => CodeMsg, - path => Path})}. + format_cmd_content( + Content, + Action, + #{ + clientid => ClientId, + action => Action, + code => Code, + codeMsg => CodeMsg, + path => Path + } + )}. format_cmd_content(undefined, _MsgType, Result) -> Result; - format_cmd_content(Content, <<"discover">>, Result) -> [H | Content1] = Content, {_, [HObjId]} = emqx_lwm2m_session:parse_object_list(H), - [ObjId | _]= path_list(HObjId), + [ObjId | _] = path_list(HObjId), ObjectList = case Content1 of [Content2 | _] -> {_, ObjL} = emqx_lwm2m_session:parse_object_list(Content2), ObjL; - [] -> [] + [] -> + [] end, - R = case emqx_lwm2m_xml_object:get_obj_def(binary_to_integer(ObjId), true) of + R = + case emqx_lwm2m_xml_object:get_obj_def(binary_to_integer(ObjId), true) of {error, _} -> lists:map(fun(Object) -> #{Object => Object} end, ObjectList); ObjDefinition -> lists:map(fun(Obj) -> to_operations(Obj, ObjDefinition) end, ObjectList) end, Result#{content => R}; - format_cmd_content(Content, _, Result) -> Result#{content => Content}. to_operations(Obj, ObjDefinition) -> - [_, _, RawResId| _] = path_list(Obj), + [_, _, RawResId | _] = path_list(Obj), ResId = binary_to_integer(RawResId), Operations = case emqx_lwm2m_xml_object:get_resource_operations(ResId, ObjDefinition) of - "E" -> #{operations => <<"E">>}; + "E" -> + #{operations => <<"E">>}; Oper -> - #{'dataType' => - list_to_binary(emqx_lwm2m_xml_object:get_resource_type(ResId, ObjDefinition)), + #{ + 'dataType' => + list_to_binary( + emqx_lwm2m_xml_object:get_resource_type(ResId, ObjDefinition) + ), operations => list_to_binary(Oper) } end, - Operations#{path => Obj, - name => list_to_binary(emqx_lwm2m_xml_object:get_resource_name(ResId, ObjDefinition))}. + Operations#{ + path => Obj, + name => list_to_binary(emqx_lwm2m_xml_object:get_resource_name(ResId, ObjDefinition)) + }. path_list(Path) -> case binary:split(binary_util:trim(Path, $/), [<<$/>>], [global]) of @@ -203,35 +221,42 @@ path_list(Path) -> [ObjId] -> [ObjId] end. -observe(post, #{bindings := #{clientid := ClientId}, - query_string := #{<<"path">> := Path, <<"enable">> := Enable}}) -> - MsgType = case Enable of - true -> <<"observe">>; - _ -> <<"cancel-observe">> - end, +observe(post, #{ + bindings := #{clientid := ClientId}, + query_string := #{<<"path">> := Path, <<"enable">> := Enable} +}) -> + MsgType = + case Enable of + true -> <<"observe">>; + _ -> <<"cancel-observe">> + end, - Cmd = #{<<"msgType">> => MsgType, - <<"data">> => #{<<"path">> => Path} - }, + Cmd = #{ + <<"msgType">> => MsgType, + <<"data">> => #{<<"path">> => Path} + }, send_cmd(ClientId, Cmd). - -read(post, #{bindings := #{clientid := ClientId}, - query_string := Qs}) -> - - Cmd = #{<<"msgType">> => <<"read">>, - <<"data">> => Qs - }, +read(post, #{ + bindings := #{clientid := ClientId}, + query_string := Qs +}) -> + Cmd = #{ + <<"msgType">> => <<"read">>, + <<"data">> => Qs + }, send_cmd(ClientId, Cmd). -write(post, #{bindings := #{clientid := ClientId}, - query_string := Qs}) -> - - Cmd = #{<<"msgType">> => <<"write">>, - <<"data">> => Qs - }, +write(post, #{ + bindings := #{clientid := ClientId}, + query_string := Qs +}) -> + Cmd = #{ + <<"msgType">> => <<"write">>, + <<"data">> => Qs + }, send_cmd(ClientId, Cmd). diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_channel.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_channel.erl index 0ff3dad27..15175c39f 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_channel.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_channel.erl @@ -21,62 +21,69 @@ -include("src/lwm2m/include/emqx_lwm2m.hrl"). %% API --export([ info/1 - , info/2 - , stats/1 - , with_context/2 - , do_takeover/3 - , lookup_cmd/3 - , send_cmd/2 - ]). +-export([ + info/1, + info/2, + stats/1, + with_context/2, + do_takeover/3, + lookup_cmd/3, + send_cmd/2 +]). --export([ init/2 - , handle_in/2 - , handle_deliver/2 - , handle_timeout/3 - , terminate/2 - ]). +-export([ + init/2, + handle_in/2, + handle_deliver/2, + handle_timeout/3, + terminate/2 +]). --export([ handle_call/3 - , handle_cast/2 - , handle_info/2 - ]). +-export([ + handle_call/3, + handle_cast/2, + handle_info/2 +]). -record(channel, { - %% Context - ctx :: emqx_gateway_ctx:context(), - %% Connection Info - conninfo :: emqx_types:conninfo(), - %% Client Info - clientinfo :: emqx_types:clientinfo(), - %% Session - session :: emqx_lwm2m_session:session() | undefined, - %% Channel State - %% TODO: is there need - conn_state :: conn_state(), - %% Timer - timers :: #{atom() => disable | undefined | reference()}, - %% FIXME: don't store anonymous func - with_context :: function() - }). + %% Context + ctx :: emqx_gateway_ctx:context(), + %% Connection Info + conninfo :: emqx_types:conninfo(), + %% Client Info + clientinfo :: emqx_types:clientinfo(), + %% Session + session :: emqx_lwm2m_session:session() | undefined, + %% Channel State + %% TODO: is there need + conn_state :: conn_state(), + %% Timer + timers :: #{atom() => disable | undefined | reference()}, + %% FIXME: don't store anonymous func + with_context :: function() +}). -type channel() :: #channel{}. -type conn_state() :: idle | connecting | connected | disconnected. --type reply() :: {outgoing, coap_message()} - | {outgoing, [coap_message()]} - | {event, conn_state()|updated} - | {close, Reason :: atom()}. +-type reply() :: + {outgoing, coap_message()} + | {outgoing, [coap_message()]} + | {event, conn_state() | updated} + | {close, Reason :: atom()}. -type replies() :: reply() | [reply()]. %% TODO: -define(DEFAULT_OVERRIDE, - #{ clientid => <<"">> %% Generate clientid by default - , username => <<"${Packet.uri_query.ep}">> - , password => <<"">> - }). + %% Generate clientid by default + #{ + clientid => <<"">>, + username => <<"${Packet.uri_query.ep}">>, + password => <<"">> + } +). -define(INFO_KEYS, [conninfo, conn_state, clientinfo, session]). @@ -91,7 +98,6 @@ info(Channel) -> info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; - info(conninfo, #channel{conninfo = ConnInfo}) -> ConnInfo; info(conn_state, #channel{conn_state = ConnState}) -> @@ -108,39 +114,45 @@ info(ctx, #channel{ctx = Ctx}) -> stats(_) -> []. -init(ConnInfo = #{peername := {PeerHost, _}, - sockname := {_, SockPort}}, - #{ctx := Ctx} = Config) -> +init( + ConnInfo = #{ + peername := {PeerHost, _}, + sockname := {_, SockPort} + }, + #{ctx := Ctx} = Config +) -> Peercert = maps:get(peercert, ConnInfo, undefined), Mountpoint = maps:get(mountpoint, Config, undefined), - ListenerId = case maps:get(listener, Config, undefined) of - undefined -> undefined; - {GwName, Type, LisName} -> - emqx_gateway_utils:listener_id(GwName, Type, LisName) - end, + ListenerId = + case maps:get(listener, Config, undefined) of + undefined -> undefined; + {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName) + end, ClientInfo = set_peercert_infos( - Peercert, - #{ zone => default - , listener => ListenerId - , protocol => lwm2m - , peerhost => PeerHost - , sockport => SockPort - , username => undefined - , clientid => undefined - , is_bridge => false - , is_superuser => false - , mountpoint => Mountpoint - } - ), + Peercert, + #{ + zone => default, + listener => ListenerId, + protocol => lwm2m, + peerhost => PeerHost, + sockport => SockPort, + username => undefined, + clientid => undefined, + is_bridge => false, + is_superuser => false, + mountpoint => Mountpoint + } + ), - #channel{ ctx = Ctx - , conninfo = ConnInfo - , clientinfo = ClientInfo - , timers = #{} - , session = emqx_lwm2m_session:new() - , conn_state = idle - , with_context = with_context(Ctx, ClientInfo) - }. + #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo, + timers = #{}, + session = emqx_lwm2m_session:new(), + conn_state = idle, + with_context = with_context(Ctx, ClientInfo) + }. lookup_cmd(Channel, Path, Action) -> gen_server:call(Channel, {?FUNCTION_NAME, Path, Action}). @@ -152,11 +164,11 @@ send_cmd(Channel, Cmd) -> %% Handle incoming packet %%-------------------------------------------------------------------- --spec handle_in(coap_message() | {frame_error, any()}, channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()} - | {shutdown, Reason :: term(), replies(), channel()}. +-spec handle_in(coap_message() | {frame_error, any()}, channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), replies(), channel()}. handle_in(Msg, Channle) -> NChannel = update_life_timer(Channle), call_session(handle_coap_in, Msg, NChannel). @@ -170,18 +182,21 @@ handle_deliver(Delivers, Channel) -> %%-------------------------------------------------------------------- %% Handle timeout %%-------------------------------------------------------------------- -handle_timeout(_, lifetime, #channel{ctx = Ctx, - clientinfo = ClientInfo, - conninfo = ConnInfo} = Channel) -> +handle_timeout( + _, + lifetime, + #channel{ + ctx = Ctx, + clientinfo = ClientInfo, + conninfo = ConnInfo + } = Channel +) -> ok = run_hooks(Ctx, 'client.disconnected', [ClientInfo, timeout, ConnInfo]), {shutdown, timeout, Channel}; - handle_timeout(_, {transport, _} = Msg, Channel) -> call_session(timeout, Msg, Channel); - handle_timeout(_, disconnect, Channel) -> {shutdown, normal, Channel}; - handle_timeout(_, _, Channel) -> {ok, Channel}. @@ -189,63 +204,78 @@ handle_timeout(_, _, Channel) -> %% Handle call %%-------------------------------------------------------------------- -handle_call({lookup_cmd, Path, Type}, _From, - Channel = #channel{session = Session}) -> +handle_call( + {lookup_cmd, Path, Type}, + _From, + Channel = #channel{session = Session} +) -> Result = emqx_lwm2m_session:find_cmd_record(Path, Type, Session), {reply, {ok, Result}, Channel}; - handle_call({send_cmd, Cmd}, _From, Channel) -> {ok, Outs, Channel2} = call_session(send_cmd, Cmd, Channel), {reply, ok, Outs, Channel2}; - -handle_call({subscribe, Topic, SubOpts}, _From, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - = #{clientid := ClientId, - mountpoint := Mountpoint}, - session = Session}) -> +handle_call( + {subscribe, Topic, SubOpts}, + _From, + Channel = #channel{ + ctx = Ctx, + clientinfo = + ClientInfo = + #{ + clientid := ClientId, + mountpoint := Mountpoint + }, + session = Session + } +) -> NSubOpts = maps:merge( - emqx_gateway_utils:default_subopts(), - SubOpts), + emqx_gateway_utils:default_subopts(), + SubOpts + ), MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), _ = emqx_broker:subscribe(MountedTopic, ClientId, NSubOpts), - _ = run_hooks(Ctx, 'session.subscribed', - [ClientInfo, MountedTopic, NSubOpts]), + _ = run_hooks( + Ctx, + 'session.subscribed', + [ClientInfo, MountedTopic, NSubOpts] + ), %% modify session state Subs = emqx_lwm2m_session:info(subscriptions, Session), NSubs = maps:put(MountedTopic, NSubOpts, Subs), NSession = emqx_lwm2m_session:set_subscriptions(NSubs, Session), {reply, {ok, {MountedTopic, NSubOpts}}, Channel#channel{session = NSession}}; - -handle_call({unsubscribe, Topic}, _From, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - = #{mountpoint := Mountpoint}, - session = Session}) -> +handle_call( + {unsubscribe, Topic}, + _From, + Channel = #channel{ + ctx = Ctx, + clientinfo = + ClientInfo = + #{mountpoint := Mountpoint}, + session = Session + } +) -> MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), ok = emqx_broker:unsubscribe(MountedTopic), - _ = run_hooks(Ctx, 'session.unsubscribe', - [ClientInfo, MountedTopic, #{}]), + _ = run_hooks( + Ctx, + 'session.unsubscribe', + [ClientInfo, MountedTopic, #{}] + ), %% modify session state Subs = emqx_lwm2m_session:info(subscriptions, Session), NSubs = maps:remove(MountedTopic, Subs), NSession = emqx_lwm2m_session:set_subscriptions(NSubs, Session), {reply, ok, Channel#channel{session = NSession}}; - handle_call(subscriptions, _From, Channel = #channel{session = Session}) -> Subs = maps:to_list(emqx_lwm2m_session:info(subscriptions, Session)), {reply, {ok, Subs}, Channel}; - handle_call(kick, _From, Channel) -> NChannel = ensure_disconnected(kicked, Channel), shutdown_and_reply(kicked, ok, NChannel); - handle_call(discard, _From, Channel) -> shutdown_and_reply(discarded, ok, Channel); - %% TODO: No Session Takeover %handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) -> % reply(Session, Channel#channel{takeover = true}); @@ -259,18 +289,20 @@ handle_call(discard, _From, Channel) -> % shutdown_and_reply(takenover, AllPendings, Channel); handle_call(Req, _From, Channel) -> - ?SLOG(error, #{ msg => "unexpected_call" - , call => Req - }), + ?SLOG(error, #{ + msg => "unexpected_call", + call => Req + }), {reply, ignored, Channel}. %%-------------------------------------------------------------------- %% Handle Cast %%-------------------------------------------------------------------- handle_cast(Req, Channel) -> - ?SLOG(error, #{ msg => "unexpected_cast" - , cast => Req - }), + ?SLOG(error, #{ + msg => "unexpected_cast", + cast => Req + }), {ok, Channel}. %%-------------------------------------------------------------------- @@ -279,19 +311,21 @@ handle_cast(Req, Channel) -> handle_info({subscribe, _AutoSubs}, Channel) -> %% not need handle this message {ok, Channel}; - handle_info(Info, Channel) -> - ?SLOG(error, #{ msg => "unexpected_info" - , info => Info - }), + ?SLOG(error, #{ + msg => "unexpected_info", + info => Info + }), {ok, Channel}. %%-------------------------------------------------------------------- %% Terminate %%-------------------------------------------------------------------- -terminate(Reason, #channel{ctx = Ctx, - clientinfo = ClientInfo, - session = Session}) -> +terminate(Reason, #channel{ + ctx = Ctx, + clientinfo = ClientInfo, + session = Session +}) -> MountedTopic = emqx_lwm2m_session:on_close(Session), _ = run_hooks(Ctx, 'session.unsubscribe', [ClientInfo, MountedTopic, #{}]), run_hooks(Ctx, 'session.terminated', [ClientInfo, Reason, Session]). @@ -303,29 +337,39 @@ terminate(Reason, #channel{ctx = Ctx, %%-------------------------------------------------------------------- %% Ensure connected -ensure_connected(Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_connected( + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> _ = run_hooks(Ctx, 'client.connack', [ConnInfo, connection_accepted, []]), NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)}, ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), Channel#channel{ - conninfo = NConnInfo, - conn_state = connected - }. + conninfo = NConnInfo, + conn_state = connected + }. %%-------------------------------------------------------------------- %% Ensure disconnected -ensure_disconnected(Reason, Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_disconnected( + Reason, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, - ok = run_hooks(Ctx, 'client.disconnected', - [ClientInfo, Reason, NConnInfo]), + ok = run_hooks( + Ctx, + 'client.disconnected', + [ClientInfo, Reason, NConnInfo] + ), Channel#channel{conninfo = NConnInfo, conn_state = disconnected}. shutdown_and_reply(Reason, Reply, Channel) -> @@ -334,13 +378,13 @@ shutdown_and_reply(Reason, Reply, Channel) -> %shutdown_and_reply(Reason, Reply, OutPkt, Channel) -> % {shutdown, Reason, Reply, OutPkt, Channel}. -set_peercert_infos(NoSSL, ClientInfo) - when NoSSL =:= nossl; - NoSSL =:= undefined -> +set_peercert_infos(NoSSL, ClientInfo) when + NoSSL =:= nossl; + NoSSL =:= undefined +-> ClientInfo; set_peercert_infos(Peercert, ClientInfo) -> - {DN, CN} = {esockd_peercert:subject(Peercert), - esockd_peercert:common_name(Peercert)}, + {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)}, ClientInfo#{dn => DN, cn => CN}. make_timer(Name, Time, Msg, Channel = #channel{timers = Timers}) -> @@ -349,7 +393,8 @@ make_timer(Name, Time, Msg, Channel = #channel{timers = Timers}) -> update_life_timer(#channel{session = Session, timers = Timers} = Channel) -> LifeTime = emqx_lwm2m_session:info(lifetime, Session), - _ = case maps:get(lifetime, Timers, undefined) of + _ = + case maps:get(lifetime, Timers, undefined) of undefined -> ok; Ref -> erlang:cancel_timer(Ref) end, @@ -365,18 +410,25 @@ do_takeover(_DesireId, Msg, Channel) -> call_session(handle_out, Reset, Channel). do_connect(Req, Result, Channel, Iter) -> - case emqx_misc:pipeline( - [ fun check_lwm2m_version/2 - , fun enrich_conninfo/2 - , fun run_conn_hooks/2 - , fun enrich_clientinfo/2 - , fun set_log_meta/2 - , fun auth_connect/2 - ], - Req, - Channel) of - {ok, _Input, #channel{session = Session, - with_context = WithContext} = NChannel} -> + case + emqx_misc:pipeline( + [ + fun check_lwm2m_version/2, + fun enrich_conninfo/2, + fun run_conn_hooks/2, + fun enrich_clientinfo/2, + fun set_log_meta/2, + fun auth_connect/2 + ], + Req, + Channel + ) + of + {ok, _Input, + #channel{ + session = Session, + with_context = WithContext + } = NChannel} -> case emqx_lwm2m_session:info(reg_info, Session) of undefined -> process_connect(ensure_connected(NChannel), Req, Result, Iter); @@ -387,66 +439,84 @@ do_connect(Req, Result, Channel, Iter) -> {error, ReasonCode, NChannel} -> ErrMsg = io_lib:format("Login Failed: ~ts", [ReasonCode]), Payload = erlang:list_to_binary(lists:flatten(ErrMsg)), - iter(Iter, - reply({error, bad_request}, Payload, Req, Result), - NChannel) + iter( + Iter, + reply({error, bad_request}, Payload, Req, Result), + NChannel + ) end. -check_lwm2m_version(#coap_message{options = Opts}, - #channel{conninfo = ConnInfo} = Channel) -> +check_lwm2m_version( + #coap_message{options = Opts}, + #channel{conninfo = ConnInfo} = Channel +) -> Ver = gets([uri_query, <<"lwm2m">>], Opts), - IsValid = case Ver of - <<"1.0">> -> - true; - <<"1">> -> - true; - <<"1.1">> -> - true; - _ -> - false - end, - if IsValid -> - NConnInfo = ConnInfo#{ connected_at => erlang:system_time(millisecond) - , proto_ver => Ver - }, + IsValid = + case Ver of + <<"1.0">> -> + true; + <<"1">> -> + true; + <<"1.1">> -> + true; + _ -> + false + end, + if + IsValid -> + NConnInfo = ConnInfo#{ + connected_at => erlang:system_time(millisecond), + proto_ver => Ver + }, {ok, Channel#channel{conninfo = NConnInfo}}; - true -> - ?SLOG(error, #{ msg => "reject_REGISTRE_request" - , reason => {unsupported_version, Ver} - }), + true -> + ?SLOG(error, #{ + msg => "reject_REGISTRE_request", + reason => {unsupported_version, Ver} + }), {error, "invalid lwm2m version", Channel} end. -run_conn_hooks(Input, Channel = #channel{ctx = Ctx, - conninfo = ConnInfo}) -> +run_conn_hooks( + Input, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo + } +) -> ConnProps = #{}, case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of Error = {error, _Reason} -> Error; - _NConnProps -> - {ok, Input, Channel} + _NConnProps -> {ok, Input, Channel} end. -enrich_conninfo(#coap_message{options = Options}, - Channel = #channel{ - conninfo = ConnInfo}) -> +enrich_conninfo( + #coap_message{options = Options}, + Channel = #channel{ + conninfo = ConnInfo + } +) -> Query = maps:get(uri_query, Options, #{}), case Query of #{<<"ep">> := Epn, <<"lt">> := Lifetime} -> ClientId = maps:get(<<"device_id">>, Query, Epn), - NConnInfo = ConnInfo#{ clientid => ClientId - , proto_name => <<"LwM2M">> - , proto_ver => <<"1.0.1">> - , clean_start => true - , keepalive => binary_to_integer(Lifetime) - , expiry_interval => 0 - }, + NConnInfo = ConnInfo#{ + clientid => ClientId, + proto_name => <<"LwM2M">>, + proto_ver => <<"1.0.1">>, + clean_start => true, + keepalive => binary_to_integer(Lifetime), + expiry_interval => 0 + }, {ok, Channel#channel{conninfo = NConnInfo}}; _ -> {error, "invalid queries", Channel} end. -enrich_clientinfo(#coap_message{options = Options} = Msg, - Channel = #channel{clientinfo = ClientInfo0}) -> +enrich_clientinfo( + #coap_message{options = Options} = Msg, + Channel = #channel{clientinfo = ClientInfo0} +) -> Query = maps:get(uri_query, Options, #{}), case Query of #{<<"ep">> := Epn, <<"lt">> := Lifetime} -> @@ -455,17 +525,20 @@ enrich_clientinfo(#coap_message{options = Options} = Msg, Password = maps:get(<<"password">>, Query, undefined), ClientId = maps:get(<<"device_id">>, Query, Epn), ClientInfo = - ClientInfo0#{endpoint_name => Epn, - lifetime => binary_to_integer(Lifetime), - username => Username, - password => Password, - clientid => ClientId}, + ClientInfo0#{ + endpoint_name => Epn, + lifetime => binary_to_integer(Lifetime), + username => Username, + password => Password, + clientid => ClientId + }, {ok, NClientInfo} = fix_mountpoint(Msg, ClientInfo), {ok, Channel#channel{clientinfo = NClientInfo}}; _ -> - ?SLOG(error, #{ msg => "reject_REGISTER_request" - , reason => {wrong_paramters, Query} - }), + ?SLOG(error, #{ + msg => "reject_REGISTER_request", + reason => {wrong_paramters, Query} + }), {error, "invalid queries", Channel} end. @@ -473,19 +546,27 @@ set_log_meta(_Input, #channel{clientinfo = #{clientid := ClientId}}) -> emqx_logger:set_metadata_clientid(ClientId), ok. -auth_connect(_Input, Channel = #channel{ctx = Ctx, - clientinfo = ClientInfo}) -> +auth_connect( + _Input, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> #{clientid := ClientId, username := Username} = ClientInfo, case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of {ok, NClientInfo} -> - {ok, Channel#channel{clientinfo = NClientInfo, - with_context = with_context(Ctx, ClientInfo)}}; + {ok, Channel#channel{ + clientinfo = NClientInfo, + with_context = with_context(Ctx, ClientInfo) + }}; {error, Reason} -> - ?SLOG(warning, #{ msg => "client_login_failed" - , clientid => ClientId - , username => Username - , reason => Reason - }), + ?SLOG(warning, #{ + msg => "client_login_failed", + clientid => ClientId, + username => Username, + reason => Reason + }), {error, Reason} end. @@ -495,36 +576,45 @@ fix_mountpoint(_Packet, ClientInfo = #{mountpoint := Mountpoint}) -> Mountpoint1 = emqx_mountpoint:replvar(Mountpoint, ClientInfo), {ok, ClientInfo#{mountpoint := Mountpoint1}}. -process_connect(Channel = #channel{ctx = Ctx, - session = Session, - conninfo = ConnInfo, - clientinfo = ClientInfo, - with_context = WithContext}, - Msg, Result, Iter) -> +process_connect( + Channel = #channel{ + ctx = Ctx, + session = Session, + conninfo = ConnInfo, + clientinfo = ClientInfo, + with_context = WithContext + }, + Msg, + Result, + Iter +) -> %% inherit the old session - SessFun = fun(_,_) -> #{} end, - case emqx_gateway_ctx:open_session( - Ctx, - true, - ClientInfo, - ConnInfo, - SessFun, - emqx_lwm2m_session - ) of + SessFun = fun(_, _) -> #{} end, + case + emqx_gateway_ctx:open_session( + Ctx, + true, + ClientInfo, + ConnInfo, + SessFun, + emqx_lwm2m_session + ) + of {ok, _} -> Mountpoint = maps:get(mountpoint, ClientInfo, <<>>), NewResult0 = emqx_lwm2m_session:init( - Msg, - Mountpoint, - WithContext, - Session - ), + Msg, + Mountpoint, + WithContext, + Session + ), NewResult1 = NewResult0#{events => [{event, connected}]}, iter(Iter, maps:merge(Result, NewResult1), Channel); {error, Reason} -> - ?SLOG(error, #{ msg => "falied_to_open_session" - , reason => Reason - }), + ?SLOG(error, #{ + msg => "falied_to_open_session", + reason => Reason + }), iter(Iter, reply({error, bad_request}, Msg, Result), Channel) end. @@ -557,52 +647,68 @@ with_context(publish, [Topic, Msg], Ctx, ClientInfo) -> _ = emqx_broker:publish(Msg), ok; _ -> - ?SLOG(error, #{ msg => "publish_denied" - , topic => Topic - }), + ?SLOG(error, #{ + msg => "publish_denied", + topic => Topic + }), {error, deny} end; - with_context(subscribe, [Topic, Opts], Ctx, ClientInfo) -> - #{clientid := ClientId, - endpoint_name := EndpointName} = ClientInfo, + #{ + clientid := ClientId, + endpoint_name := EndpointName + } = ClientInfo, case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, Topic) of allow -> run_hooks(Ctx, 'session.subscribed', [ClientInfo, Topic, Opts]), - ?SLOG(debug, #{ msg => "subscribe_topic_succeed" - , topic => Topic - , clientid => ClientId - , endpoint_name => EndpointName - }), + ?SLOG(debug, #{ + msg => "subscribe_topic_succeed", + topic => Topic, + clientid => ClientId, + endpoint_name => EndpointName + }), emqx_broker:subscribe(Topic, ClientId, Opts), ok; _ -> - ?SLOG(error, #{ msg => "subscribe_denied" - , topic => Topic - }), + ?SLOG(error, #{ + msg => "subscribe_denied", + topic => Topic + }), {error, deny} end; - with_context(metrics, Name, Ctx, _ClientInfo) -> emqx_gateway_ctx:metrics_inc(Ctx, Name). %%-------------------------------------------------------------------- %% Call Chain %%-------------------------------------------------------------------- -call_session(Fun, - Msg, - #channel{session = Session, - with_context = WithContext} = Channel) -> - iter([ session, fun process_session/4 - , proto, fun process_protocol/4 - , return, fun process_return/4 - , lifetime, fun process_lifetime/4 - , reply, fun process_reply/4 - , out, fun process_out/4 - , fun process_nothing/3 - ], - emqx_lwm2m_session:Fun(Msg, WithContext, Session), - Channel). +call_session( + Fun, + Msg, + #channel{ + session = Session, + with_context = WithContext + } = Channel +) -> + iter( + [ + session, + fun process_session/4, + proto, + fun process_protocol/4, + return, + fun process_return/4, + lifetime, + fun process_lifetime/4, + reply, + fun process_reply/4, + out, + fun process_out/4, + fun process_nothing/3 + ], + emqx_lwm2m_session:Fun(Msg, WithContext, Session), + Channel + ). process_session(Session, Result, Channel, Iter) -> iter(Iter, Result, Channel#channel{session = Session}). @@ -610,14 +716,22 @@ process_session(Session, Result, Channel, Iter) -> process_protocol({request, Msg}, Result, Channel, Iter) -> #coap_message{method = Method} = Msg, handle_request_protocol(Method, Msg, Result, Channel, Iter); - -process_protocol(Msg, Result, - #channel{with_context = WithContext, session = Session} = Channel, Iter) -> +process_protocol( + Msg, + Result, + #channel{with_context = WithContext, session = Session} = Channel, + Iter +) -> ProtoResult = emqx_lwm2m_session:handle_protocol_in(Msg, WithContext, Session), iter(Iter, maps:merge(Result, ProtoResult), Channel). -handle_request_protocol(post, #coap_message{options = Opts} = Msg, - Result, Channel, Iter) -> +handle_request_protocol( + post, + #coap_message{options = Opts} = Msg, + Result, + Channel, + Iter +) -> case Opts of #{uri_path := [?REG_PREFIX]} -> do_connect(Msg, Result, Channel, Iter); @@ -626,9 +740,13 @@ handle_request_protocol(post, #coap_message{options = Opts} = Msg, _ -> iter(Iter, reply({error, not_found}, Msg, Result), Channel) end; - -handle_request_protocol(delete, #coap_message{options = Opts} = Msg, - Result, Channel, Iter) -> +handle_request_protocol( + delete, + #coap_message{options = Opts} = Msg, + Result, + Channel, + Iter +) -> case Opts of #{uri_path := Location} -> case check_location(Location, Channel) of @@ -642,8 +760,13 @@ handle_request_protocol(delete, #coap_message{options = Opts} = Msg, iter(Iter, reply({error, bad_request}, Msg, Result), Channel) end. -do_update(Location, Msg, Result, - #channel{session = Session, with_context = WithContext} = Channel, Iter) -> +do_update( + Location, + Msg, + Result, + #channel{session = Session, with_context = WithContext} = Channel, + Iter +) -> case check_location(Location, Channel) of true -> NewResult = emqx_lwm2m_session:update(Msg, WithContext, Session), @@ -654,18 +777,21 @@ do_update(Location, Msg, Result, process_return({Outs, Session}, Result, Channel, Iter) -> OldOuts = maps:get(out, Result, []), - iter(Iter, - Result#{out => Outs ++ OldOuts}, - Channel#channel{session = Session}). + iter( + Iter, + Result#{out => Outs ++ OldOuts}, + Channel#channel{session = Session} + ). process_out(Outs, Result, Channel, _) -> Outs2 = lists:reverse(Outs), - Outs3 = case maps:get(reply, Result, undefined) of - undefined -> - Outs2; - Reply -> - [Reply | Outs2] - end, + Outs3 = + case maps:get(reply, Result, undefined) of + undefined -> + Outs2; + Reply -> + [Reply | Outs2] + end, Events = maps:get(events, Result, []), {ok, [{outgoing, Outs3}] ++ Events, Channel}. diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd.erl index cd692ddfb..46f7534f1 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd.erl @@ -20,11 +20,12 @@ -include("src/coap/include/emqx_coap.hrl"). -include("src/lwm2m/include/emqx_lwm2m.hrl"). --export([ mqtt_to_coap/2 - , coap_to_mqtt/4 - , empty_ack_to_mqtt/1 - , coap_failure_to_mqtt/2 - ]). +-export([ + mqtt_to_coap/2, + coap_to_mqtt/4, + empty_ack_to_mqtt/1, + coap_failure_to_mqtt/2 +]). -export([path_list/1, extract_path/1]). @@ -54,26 +55,47 @@ mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"create">>, <<"data" FullPathList = add_alternate_path_prefix(AlternatePath, PathList), TlvData = emqx_lwm2m_message:json_to_tlv(PathList, maps:get(<<"content">>, Data)), Payload = emqx_lwm2m_tlv:encode(TlvData), - CoapRequest = emqx_coap_message:request(con, post, Payload, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {content_format, <<"application/vnd.oma.lwm2m+tlv">>}]), + CoapRequest = emqx_coap_message:request( + con, + post, + Payload, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {content_format, <<"application/vnd.oma.lwm2m+tlv">>} + ] + ), {CoapRequest, InputCmd}; - mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"delete">>, <<"data">> := Data}) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), FullPathList = add_alternate_path_prefix(AlternatePath, PathList), - {emqx_coap_message:request(con, delete, <<>>, - [{uri_path, FullPathList}, - {uri_query, QueryList}]), InputCmd}; - + { + emqx_coap_message:request( + con, + delete, + <<>>, + [ + {uri_path, FullPathList}, + {uri_query, QueryList} + ] + ), + InputCmd + }; mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"read">>, <<"data">> := Data}) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), FullPathList = add_alternate_path_prefix(AlternatePath, PathList), - {emqx_coap_message:request(con, get, <<>>, - [{uri_path, FullPathList}, - {uri_query, QueryList}]), InputCmd}; - + { + emqx_coap_message:request( + con, + get, + <<>>, + [ + {uri_path, FullPathList}, + {uri_query, QueryList} + ] + ), + InputCmd + }; mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"write">>, <<"data">> := Data}) -> CoapRequest = case maps:get(<<"basePath">>, Data, <<"/">>) of @@ -83,7 +105,6 @@ mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"write">>, <<"data"> batch_write_request(AlternatePath, BasePath, maps:get(<<"content">>, Data)) end, {CoapRequest, InputCmd}; - mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"execute">>, <<"data">> := Data}) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), FullPathList = add_alternate_path_prefix(AlternatePath, PathList), @@ -93,85 +114,122 @@ mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"execute">>, <<"data undefined -> <<>>; Arg1 -> Arg1 end, - {emqx_coap_message:request(con, post, Args, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {content_format, <<"text/plain">>}]), InputCmd}; - + { + emqx_coap_message:request( + con, + post, + Args, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {content_format, <<"text/plain">>} + ] + ), + InputCmd + }; mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"discover">>, <<"data">> := Data}) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), FullPathList = add_alternate_path_prefix(AlternatePath, PathList), - {emqx_coap_message:request(con, get, <<>>, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {'accept', ?LWM2M_FORMAT_LINK}]), InputCmd}; - + { + emqx_coap_message:request( + con, + get, + <<>>, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {'accept', ?LWM2M_FORMAT_LINK} + ] + ), + InputCmd + }; mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"write-attr">>, <<"data">> := Data}) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), FullPathList = add_alternate_path_prefix(AlternatePath, PathList), Query = attr_query_list(Data), - {emqx_coap_message:request(con, put, <<>>, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {uri_query, Query}]), InputCmd}; - + { + emqx_coap_message:request( + con, + put, + <<>>, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {uri_query, Query} + ] + ), + InputCmd + }; mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"observe">>, <<"data">> := Data}) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), FullPathList = add_alternate_path_prefix(AlternatePath, PathList), - {emqx_coap_message:request(con, get, <<>>, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {observe, 0}]), InputCmd}; - -mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"cancel-observe">>, <<"data">> := Data}) -> + { + emqx_coap_message:request( + con, + get, + <<>>, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {observe, 0} + ] + ), + InputCmd + }; +mqtt_to_coap( + AlternatePath, InputCmd = #{<<"msgType">> := <<"cancel-observe">>, <<"data">> := Data} +) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), FullPathList = add_alternate_path_prefix(AlternatePath, PathList), - {emqx_coap_message:request(con, get, <<>>, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {observe, 1}]), InputCmd}. + { + emqx_coap_message:request( + con, + get, + <<>>, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {observe, 1} + ] + ), + InputCmd + }. -coap_to_mqtt(_Method = {_, Code}, _CoapPayload, _Options, Ref=#{<<"msgType">> := <<"create">>}) -> +coap_to_mqtt(_Method = {_, Code}, _CoapPayload, _Options, Ref = #{<<"msgType">> := <<"create">>}) -> make_response(Code, Ref); - -coap_to_mqtt(_Method = {_, Code}, _CoapPayload, _Options, Ref=#{<<"msgType">> := <<"delete">>}) -> +coap_to_mqtt(_Method = {_, Code}, _CoapPayload, _Options, Ref = #{<<"msgType">> := <<"delete">>}) -> make_response(Code, Ref); - -coap_to_mqtt(Method, CoapPayload, Options, Ref=#{<<"msgType">> := <<"read">>}) -> +coap_to_mqtt(Method, CoapPayload, Options, Ref = #{<<"msgType">> := <<"read">>}) -> read_resp_to_mqtt(Method, CoapPayload, data_format(Options), Ref); - -coap_to_mqtt(Method, CoapPayload, _Options, Ref=#{<<"msgType">> := <<"write">>}) -> +coap_to_mqtt(Method, CoapPayload, _Options, Ref = #{<<"msgType">> := <<"write">>}) -> write_resp_to_mqtt(Method, CoapPayload, Ref); - -coap_to_mqtt(Method, _CoapPayload, _Options, Ref=#{<<"msgType">> := <<"execute">>}) -> +coap_to_mqtt(Method, _CoapPayload, _Options, Ref = #{<<"msgType">> := <<"execute">>}) -> execute_resp_to_mqtt(Method, Ref); - -coap_to_mqtt(Method, CoapPayload, _Options, Ref=#{<<"msgType">> := <<"discover">>}) -> +coap_to_mqtt(Method, CoapPayload, _Options, Ref = #{<<"msgType">> := <<"discover">>}) -> discover_resp_to_mqtt(Method, CoapPayload, Ref); - -coap_to_mqtt(Method, CoapPayload, _Options, Ref=#{<<"msgType">> := <<"write-attr">>}) -> +coap_to_mqtt(Method, CoapPayload, _Options, Ref = #{<<"msgType">> := <<"write-attr">>}) -> writeattr_resp_to_mqtt(Method, CoapPayload, Ref); - -coap_to_mqtt(Method, CoapPayload, Options, Ref=#{<<"msgType">> := <<"observe">>}) -> +coap_to_mqtt(Method, CoapPayload, Options, Ref = #{<<"msgType">> := <<"observe">>}) -> observe_resp_to_mqtt(Method, CoapPayload, data_format(Options), observe_seq(Options), Ref); - -coap_to_mqtt(Method, CoapPayload, Options, Ref=#{<<"msgType">> := <<"cancel-observe">>}) -> +coap_to_mqtt(Method, CoapPayload, Options, Ref = #{<<"msgType">> := <<"cancel-observe">>}) -> cancel_observe_resp_to_mqtt(Method, CoapPayload, data_format(Options), Ref). read_resp_to_mqtt({error, ErrorCode}, _CoapPayload, _Format, Ref) -> make_response(ErrorCode, Ref); - read_resp_to_mqtt({ok, SuccessCode}, CoapPayload, Format, Ref) -> try Result = content_to_mqtt(CoapPayload, Format, Ref), make_response(SuccessCode, Ref, Format, Result) catch - error:not_implemented -> make_response(not_implemented, Ref); + error:not_implemented -> + make_response(not_implemented, Ref); _:Ex:_ST -> - ?SLOG(error, #{ msg => "bad_payload_format" - , payload => CoapPayload - , reason => Ex - , stacktrace => _ST}), + ?SLOG(error, #{ + msg => "bad_payload_format", + payload => CoapPayload, + reason => Ex, + stacktrace => _ST + }), make_response(bad_request, Ref) end. @@ -183,67 +241,55 @@ coap_failure_to_mqtt(Ref, MsgType) -> content_to_mqtt(CoapPayload, <<"text/plain">>, Ref) -> emqx_lwm2m_message:text_to_json(extract_path(Ref), CoapPayload); - content_to_mqtt(CoapPayload, <<"application/octet-stream">>, Ref) -> emqx_lwm2m_message:opaque_to_json(extract_path(Ref), CoapPayload); - content_to_mqtt(CoapPayload, <<"application/vnd.oma.lwm2m+tlv">>, Ref) -> emqx_lwm2m_message:tlv_to_json(extract_path(Ref), CoapPayload); - content_to_mqtt(CoapPayload, <<"application/vnd.oma.lwm2m+json">>, _Ref) -> emqx_lwm2m_message:translate_json(CoapPayload). write_resp_to_mqtt({ok, changed}, _CoapPayload, Ref) -> make_response(changed, Ref); - write_resp_to_mqtt({ok, content}, CoapPayload, Ref) when CoapPayload =:= <<>> -> make_response(method_not_allowed, Ref); - write_resp_to_mqtt({ok, content}, _CoapPayload, Ref) -> make_response(changed, Ref); - write_resp_to_mqtt({error, Error}, _CoapPayload, Ref) -> make_response(Error, Ref). execute_resp_to_mqtt({ok, changed}, Ref) -> make_response(changed, Ref); - execute_resp_to_mqtt({error, Error}, Ref) -> make_response(Error, Ref). discover_resp_to_mqtt({ok, content}, CoapPayload, Ref) -> Links = binary:split(CoapPayload, <<",">>, [global]), make_response(content, Ref, <<"application/link-format">>, Links); - discover_resp_to_mqtt({error, Error}, _CoapPayload, Ref) -> make_response(Error, Ref). writeattr_resp_to_mqtt({ok, changed}, _CoapPayload, Ref) -> make_response(changed, Ref); - writeattr_resp_to_mqtt({error, Error}, _CoapPayload, Ref) -> make_response(Error, Ref). observe_resp_to_mqtt({error, Error}, _CoapPayload, _Format, _ObserveSeqNum, Ref) -> make_response(Error, Ref); - observe_resp_to_mqtt({ok, content}, CoapPayload, Format, 0, Ref) -> read_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref); - observe_resp_to_mqtt({ok, content}, CoapPayload, Format, ObserveSeqNum, Ref) -> read_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref#{<<"seqNum">> => ObserveSeqNum}). cancel_observe_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref) -> read_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref); - cancel_observe_resp_to_mqtt({error, Error}, _CoapPayload, _Format, Ref) -> make_response(Error, Ref). -make_response(Code, Ref=#{}) -> +make_response(Code, Ref = #{}) -> BaseRsp = make_base_response(Ref), make_data_response(BaseRsp, Code). -make_response(Code, Ref=#{}, _Format, Result) -> +make_response(Code, Ref = #{}, _Format, Result) -> BaseRsp = make_base_response(Ref), make_data_response(BaseRsp, Code, _Format, Result). @@ -258,7 +304,7 @@ make_response(Code, Ref=#{}, _Format, Result) -> %% <<"msgType">> => maps:get(<<"msgType">>, Ref, null) %% } -make_base_response(Ref=#{}) -> +make_base_response(Ref = #{}) -> remove_tmp_fields(Ref). make_data_response(BaseRsp, Code) -> @@ -273,18 +319,18 @@ make_data_response(BaseRsp, Code) -> make_data_response(BaseRsp, Code, _Format, Result) -> BaseRsp#{ <<"data">> => - #{ - <<"reqPath">> => extract_path(BaseRsp), - <<"code">> => code(Code), - <<"codeMsg">> => Code, - <<"content">> => Result - } + #{ + <<"reqPath">> => extract_path(BaseRsp), + <<"code">> => code(Code), + <<"codeMsg">> => Code, + <<"content">> => Result + } }. remove_tmp_fields(Ref) -> maps:remove(observe_type, Ref). --spec path_list(Path::binary()) -> {[PathWord::binary()], [Query::binary()]}. +-spec path_list(Path :: binary()) -> {[PathWord :: binary()], [Query :: binary()]}. path_list(Path) -> case binary:split(binary_util:trim(Path, $/), [<<$/>>], [global]) of [ObjId, ObjInsId, ResId, LastPart] -> @@ -304,8 +350,7 @@ path_list(Path) -> query_list(PathWithQuery) -> case binary:split(PathWithQuery, [<<$?>>], []) of [Path] -> {Path, []}; - [Path, Querys] -> - {Path, binary:split(Querys, [<<$&>>], [global])} + [Path, Querys] -> {Path, binary:split(Querys, [<<$&>>], [global])} end. attr_query_list(Data) -> @@ -314,7 +359,8 @@ attr_query_list(Data) -> attr_query_list(QueryJson = #{}, ValidAttrKeys, QueryList) -> maps:fold( fun - (_K, null, Acc) -> Acc; + (_K, null, Acc) -> + Acc; (K, V, Acc) -> case lists:member(K, ValidAttrKeys) of true -> @@ -323,7 +369,10 @@ attr_query_list(QueryJson = #{}, ValidAttrKeys, QueryList) -> false -> Acc end - end, QueryList, QueryJson). + end, + QueryList, + QueryJson + ). valid_attr_keys() -> [<<"pmin">>, <<"pmax">>, <<"gt">>, <<"lt">>, <<"st">>]. @@ -332,11 +381,10 @@ data_format(Options) -> maps:get(content_format, Options, <<"text/plain">>). observe_seq(Options) -> - maps:get(observe, Options, rand:uniform(1000000) + 1 ). + maps:get(observe, Options, rand:uniform(1000000) + 1). add_alternate_path_prefix(<<"/">>, PathList) -> PathList; - add_alternate_path_prefix(AlternatePath, PathList) -> [binary_util:trim(AlternatePath, $/) | PathList]. @@ -350,22 +398,29 @@ extract_path(Ref = #{}) -> end; #{<<"path">> := Path} -> Path - end). - + end + ). batch_write_request(AlternatePath, BasePath, Content) -> {PathList, QueryList} = path_list(BasePath), - Method = case length(PathList) of - 2 -> post; - 3 -> put - end, + Method = + case length(PathList) of + 2 -> post; + 3 -> put + end, FullPathList = add_alternate_path_prefix(AlternatePath, PathList), TlvData = emqx_lwm2m_message:json_to_tlv(PathList, Content), Payload = emqx_lwm2m_tlv:encode(TlvData), - emqx_coap_message:request(con, Method, Payload, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {content_format, <<"application/vnd.oma.lwm2m+tlv">>}]). + emqx_coap_message:request( + con, + Method, + Payload, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {content_format, <<"application/vnd.oma.lwm2m+tlv">>} + ] + ). single_write_request(AlternatePath, Data) -> {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), @@ -373,10 +428,16 @@ single_write_request(AlternatePath, Data) -> %% TO DO: handle write to resource instance, e.g. /4/0/1/0 TlvData = emqx_lwm2m_message:json_to_tlv(PathList, [Data]), Payload = emqx_lwm2m_tlv:encode(TlvData), - emqx_coap_message:request(con, put, Payload, - [{uri_path, FullPathList}, - {uri_query, QueryList}, - {content_format, <<"application/vnd.oma.lwm2m+tlv">>}]). + emqx_coap_message:request( + con, + put, + Payload, + [ + {uri_path, FullPathList}, + {uri_query, QueryList}, + {content_format, <<"application/vnd.oma.lwm2m+tlv">>} + ] + ). drop_query(Path) -> case binary:split(Path, [<<$?>>]) of diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_impl.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_impl.erl index 0cfb512b5..ade160b25 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_impl.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_impl.erl @@ -22,57 +22,72 @@ -include_lib("emqx/include/logger.hrl"). %% APIs --export([ reg/0 - , unreg/0 - ]). +-export([ + reg/0, + unreg/0 +]). --export([ on_gateway_load/2 - , on_gateway_update/3 - , on_gateway_unload/2 - ]). +-export([ + on_gateway_load/2, + on_gateway_update/3, + on_gateway_unload/2 +]). %%-------------------------------------------------------------------- %% APIs %%-------------------------------------------------------------------- reg() -> - RegistryOptions = [ {cbkmod, ?MODULE} - ], + RegistryOptions = [{cbkmod, ?MODULE}], emqx_gateway_registry:reg(lwm2m, RegistryOptions). unreg() -> - emqx_gateway_registry:unreg(lwm2m). + emqx_gateway_registry:unreg(lwm2m). %%-------------------------------------------------------------------- %% emqx_gateway_registry callbacks %%-------------------------------------------------------------------- -on_gateway_load(_Gateway = #{ name := GwName, - config := Config - }, Ctx) -> +on_gateway_load( + _Gateway = #{ + name := GwName, + config := Config + }, + Ctx +) -> XmlDir = maps:get(xml_dir, Config), case emqx_lwm2m_xml_object_db:start_link(XmlDir) of {ok, RegPid} -> Listeners = emqx_gateway_utils:normalize_config(Config), - ModCfg = #{frame_mod => emqx_coap_frame, - chann_mod => emqx_lwm2m_channel - }, - case emqx_gateway_utils:start_listeners( - Listeners, GwName, Ctx, ModCfg) of + ModCfg = #{ + frame_mod => emqx_coap_frame, + chann_mod => emqx_lwm2m_channel + }, + case + emqx_gateway_utils:start_listeners( + Listeners, GwName, Ctx, ModCfg + ) + of {ok, ListenerPids} -> {ok, ListenerPids, #{ctx => Ctx, registry => RegPid}}; {error, {Reason, Listener}} -> _ = emqx_lwm2m_xml_object_db:stop(), - throw({badconf, #{ key => listeners - , vallue => Listener - , reason => Reason - }}) + throw( + {badconf, #{ + key => listeners, + vallue => Listener, + reason => Reason + }} + ) end; {error, Reason} -> - throw({badconf, #{ key => xml_dir - , value => XmlDir - , reason => Reason - }}) + throw( + {badconf, #{ + key => xml_dir, + value => XmlDir, + reason => Reason + }} + ) end. on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> @@ -83,16 +98,27 @@ on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> on_gateway_unload(Gateway, GwState), on_gateway_load(Gateway#{config => Config}, Ctx) catch - Class : Reason : Stk -> - logger:error("Failed to update ~ts; " - "reason: {~0p, ~0p} stacktrace: ~0p", - [GwName, Class, Reason, Stk]), + Class:Reason:Stk -> + logger:error( + "Failed to update ~ts; " + "reason: {~0p, ~0p} stacktrace: ~0p", + [GwName, Class, Reason, Stk] + ), {error, Reason} end. -on_gateway_unload(_Gateway = #{ name := GwName, - config := Config - }, _GwState = #{registry := _RegPid}) -> - _ = try emqx_lwm2m_xml_object_db:stop() catch _ : _ -> ok end, +on_gateway_unload( + _Gateway = #{ + name := GwName, + config := Config + }, + _GwState = #{registry := _RegPid} +) -> + _ = + try + emqx_lwm2m_xml_object_db:stop() + catch + _:_ -> ok + end, Listeners = emqx_gateway_utils:normalize_config(Config), emqx_gateway_utils:stop_listeners(GwName, Listeners). diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_message.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_message.erl index b7d764344..4b13015f5 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_message.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_message.erl @@ -16,12 +16,13 @@ -module(emqx_lwm2m_message). --export([ tlv_to_json/2 - , json_to_tlv/2 - , text_to_json/2 - , opaque_to_json/2 - , translate_json/1 - ]). +-export([ + tlv_to_json/2, + json_to_tlv/2, + text_to_json/2, + opaque_to_json/2, + translate_json/1 +]). -include("src/lwm2m/include/emqx_lwm2m.hrl"). @@ -30,58 +31,86 @@ tlv_to_json(BaseName, TlvData) -> ObjectId = object_id(BaseName), ObjDefinition = emqx_lwm2m_xml_object:get_obj_def(ObjectId, true), case DecodedTlv of - [#{tlv_resource_with_value:=Id, value:=Value}] -> + [#{tlv_resource_with_value := Id, value := Value}] -> TrueBaseName = basename(BaseName, undefined, undefined, Id, 3), tlv_single_resource(TrueBaseName, Id, Value, ObjDefinition); - List1 = [#{tlv_resource_with_value:=_Id}, _|_] -> + List1 = [#{tlv_resource_with_value := _Id}, _ | _] -> TrueBaseName = basename(BaseName, undefined, undefined, undefined, 2), tlv_level2(TrueBaseName, List1, ObjDefinition, []); - List2 = [#{tlv_multiple_resource:=_Id}|_] -> + List2 = [#{tlv_multiple_resource := _Id} | _] -> TrueBaseName = basename(BaseName, undefined, undefined, undefined, 2), tlv_level2(TrueBaseName, List2, ObjDefinition, []); - [#{tlv_object_instance:=Id, value:=Value}] -> + [#{tlv_object_instance := Id, value := Value}] -> TrueBaseName = basename(BaseName, undefined, Id, undefined, 2), tlv_level2(TrueBaseName, Value, ObjDefinition, []); - List3=[#{tlv_object_instance:=_Id}, _|_] -> + List3 = [#{tlv_object_instance := _Id}, _ | _] -> tlv_level1(integer_to_binary(ObjectId), List3, ObjDefinition, []) end. - tlv_level1(_Path, [], _ObjDefinition, Acc) -> Acc; -tlv_level1(Path, [#{tlv_object_instance:=Id, value:=Value}|T], ObjDefinition, Acc) -> +tlv_level1(Path, [#{tlv_object_instance := Id, value := Value} | T], ObjDefinition, Acc) -> New = tlv_level2(make_path(Path, Id), Value, ObjDefinition, []), - tlv_level1(Path, T, ObjDefinition, Acc++New). + tlv_level1(Path, T, ObjDefinition, Acc ++ New). tlv_level2(_, [], _, Acc) -> Acc; -tlv_level2(RelativePath, [#{tlv_resource_with_value:=ResourceId, value:=Value}|T], ObjDefinition, Acc) -> +tlv_level2( + RelativePath, [#{tlv_resource_with_value := ResourceId, value := Value} | T], ObjDefinition, Acc +) -> Val = value(Value, ResourceId, ObjDefinition), - New = #{path => make_path(RelativePath, ResourceId), - value=>Val}, - tlv_level2(RelativePath, T, ObjDefinition, Acc++[New]); -tlv_level2(RelativePath, [#{tlv_multiple_resource:=ResourceId, value:=Value}|T], ObjDefinition, Acc) -> - SubList = tlv_level3(make_path(RelativePath, ResourceId), - Value, ResourceId, ObjDefinition, []), - tlv_level2(RelativePath, T, ObjDefinition, Acc++SubList). + New = #{ + path => make_path(RelativePath, ResourceId), + value => Val + }, + tlv_level2(RelativePath, T, ObjDefinition, Acc ++ [New]); +tlv_level2( + RelativePath, [#{tlv_multiple_resource := ResourceId, value := Value} | T], ObjDefinition, Acc +) -> + SubList = tlv_level3( + make_path(RelativePath, ResourceId), + Value, + ResourceId, + ObjDefinition, + [] + ), + tlv_level2(RelativePath, T, ObjDefinition, Acc ++ SubList). tlv_level3(_RelativePath, [], _Id, _ObjDefinition, Acc) -> lists:reverse(Acc); -tlv_level3(RelativePath, [#{tlv_resource_instance:=InsId, value:=Value}|T], ResourceId, ObjDefinition, Acc) -> +tlv_level3( + RelativePath, + [#{tlv_resource_instance := InsId, value := Value} | T], + ResourceId, + ObjDefinition, + Acc +) -> Val = value(Value, ResourceId, ObjDefinition), - New = #{path => make_path(RelativePath, InsId), - value=>Val}, - tlv_level3(RelativePath, T, ResourceId, ObjDefinition, [New|Acc]). + New = #{ + path => make_path(RelativePath, InsId), + value => Val + }, + tlv_level3(RelativePath, T, ResourceId, ObjDefinition, [New | Acc]). tlv_single_resource(BaseName, Id, Value, ObjDefinition) -> Val = value(Value, Id, ObjDefinition), - [#{path=>BaseName, value=>Val}]. + [#{path => BaseName, value => Val}]. basename(OldBaseName, _ObjectId, ObjectInstanceId, ResourceId, 3) -> case binary:split(binary_util:trim(OldBaseName, $/), [<<$/>>], [global]) of - [ObjId, ObjInsId, ResId] -> <<$/, ObjId/binary, $/, ObjInsId/binary, $/, ResId/binary>>; - [ObjId, ObjInsId] -> <<$/, ObjId/binary, $/, ObjInsId/binary, $/, (integer_to_binary(ResourceId))/binary>>; - [ObjId] -> <<$/, ObjId/binary, $/, (integer_to_binary(ObjectInstanceId))/binary, $/, (integer_to_binary(ResourceId))/binary>> + [ObjId, ObjInsId, ResId] -> + <<$/, ObjId/binary, $/, ObjInsId/binary, $/, ResId/binary>>; + [ObjId, ObjInsId] -> + <<$/, ObjId/binary, $/, ObjInsId/binary, $/, (integer_to_binary(ResourceId))/binary>>; + [ObjId] -> + << + $/, + ObjId/binary, + $/, + (integer_to_binary(ObjectInstanceId))/binary, + $/, + (integer_to_binary(ResourceId))/binary + >> end; basename(OldBaseName, _ObjectId, ObjectInstanceId, _ResourceId, 2) -> case binary:split(binary_util:trim(OldBaseName, $/), [<<$/>>], [global]) of @@ -101,16 +130,18 @@ make_path(RelativePath, Id) -> object_id(BaseName) -> case binary:split(binary_util:trim(BaseName, $/), [<<$/>>], [global]) of - [ObjId] -> binary_to_integer(ObjId); - [ObjId, _] -> binary_to_integer(ObjId); + [ObjId] -> binary_to_integer(ObjId); + [ObjId, _] -> binary_to_integer(ObjId); [ObjId, _, _] -> binary_to_integer(ObjId); [ObjId, _, _, _] -> binary_to_integer(ObjId) end. object_resource_id(BaseName) -> case binary:split(binary_util:trim(BaseName, $/), [<<$/>>], [global]) of - [_ObjIdBin1] -> error({invalid_basename, BaseName}); - [_ObjIdBin2, _] -> error({invalid_basename, BaseName}); + [_ObjIdBin1] -> + error({invalid_basename, BaseName}); + [_ObjIdBin2, _] -> + error({invalid_basename, BaseName}); [ObjIdBin3, _, ResourceId3] -> {binary_to_integer(ObjIdBin3), binary_to_integer(ResourceId3)}; [ObjIdBin3, _, ResourceId3, _] -> @@ -121,17 +152,19 @@ object_resource_id(BaseName) -> value(Value, ResourceId, ObjDefinition) -> case emqx_lwm2m_xml_object:get_resource_type(ResourceId, ObjDefinition) of "String" -> - Value; % keep binary type since it is same as a string for jsx + % keep binary type since it is same as a string for jsx + Value; "Integer" -> - Size = byte_size(Value)*8, + Size = byte_size(Value) * 8, <> = Value, IntResult; "Float" -> - Size = byte_size(Value)*8, + Size = byte_size(Value) * 8, <> = Value, FloatResult; "Boolean" -> - B = case Value of + B = + case Value of <<0>> -> false; <<1>> -> true end, @@ -139,7 +172,7 @@ value(Value, ResourceId, ObjDefinition) -> "Opaque" -> base64:encode(Value); "Time" -> - Size = byte_size(Value)*8, + Size = byte_size(Value) * 8, <> = Value, IntResult; "Objlnk" -> @@ -149,8 +182,12 @@ value(Value, ResourceId, ObjDefinition) -> json_to_tlv([_ObjectId, _ObjectInstanceId, ResourceId], ResourceArray) -> case length(ResourceArray) of - 1 -> element_single_resource(integer(ResourceId), ResourceArray); - _ -> element_loop_level4(ResourceArray, [#{tlv_multiple_resource=>integer(ResourceId), value=>[]}]) + 1 -> + element_single_resource(integer(ResourceId), ResourceArray); + _ -> + element_loop_level4(ResourceArray, [ + #{tlv_multiple_resource => integer(ResourceId), value => []} + ]) end; json_to_tlv([_ObjectId, _ObjectInstanceId], ResourceArray) -> element_loop_level3(ResourceArray, []); @@ -159,23 +196,23 @@ json_to_tlv([_ObjectId], ResourceArray) -> element_single_resource(ResourceId, [#{<<"type">> := Type, <<"value">> := Value}]) -> BinaryValue = value_ex(Type, Value), - [#{tlv_resource_with_value=>integer(ResourceId), value=>BinaryValue}]. + [#{tlv_resource_with_value => integer(ResourceId), value => BinaryValue}]. element_loop_level2([], Acc) -> Acc; -element_loop_level2([H|T], Acc) -> +element_loop_level2([H | T], Acc) -> NewAcc = insert(object, H, Acc), element_loop_level2(T, NewAcc). element_loop_level3([], Acc) -> Acc; -element_loop_level3([H|T], Acc) -> +element_loop_level3([H | T], Acc) -> NewAcc = insert(object_instance, H, Acc), element_loop_level3(T, NewAcc). element_loop_level4([], Acc) -> Acc; -element_loop_level4([H|T], Acc) -> +element_loop_level4([H | T], Acc) -> NewAcc = insert(resource, H, Acc), element_loop_level4(T, NewAcc). @@ -183,15 +220,14 @@ insert(Level, #{<<"path">> := EleName, <<"type">> := Type, <<"value">> := Value} BinaryValue = value_ex(Type, Value), Path = split_path(EleName), case Level of - object -> insert_resource_into_object(Path, BinaryValue, Acc); + object -> insert_resource_into_object(Path, BinaryValue, Acc); object_instance -> insert_resource_into_object_instance(Path, BinaryValue, Acc); - resource -> insert_resource_instance_into_resource(hd(Path), BinaryValue, Acc) + resource -> insert_resource_instance_into_resource(hd(Path), BinaryValue, Acc) end. % json text to TLV binary value_ex(K, Value) when K =:= <<"Integer">>; K =:= <<"Float">>; K =:= <<"Time">> -> encode_number(Value); - value_ex(K, Value) when K =:= <<"String">> -> Value; value_ex(K, Value) when K =:= <<"Opaque">> -> @@ -201,34 +237,33 @@ value_ex(K, Value) when K =:= <<"Opaque">> -> base64:decode(Value); value_ex(K, <<"true">>) when K =:= <<"Boolean">> -> <<1>>; value_ex(K, <<"false">>) when K =:= <<"Boolean">> -> <<0>>; - value_ex(K, Value) when K =:= <<"Objlnk">>; K =:= ov -> [P1, P2] = binary:split(Value, [<<$:>>], [global]), <<(binary_to_integer(P1)):16, (binary_to_integer(P2)):16>>. -insert_resource_into_object([ObjectInstanceId|OtherIds], Value, Acc) -> +insert_resource_into_object([ObjectInstanceId | OtherIds], Value, Acc) -> case find_obj_instance(ObjectInstanceId, Acc) of undefined -> NewList = insert_resource_into_object_instance(OtherIds, Value, []), - Acc ++ [#{tlv_object_instance=>integer(ObjectInstanceId), value=>NewList}]; - ObjectInstance = #{value:=List} -> + Acc ++ [#{tlv_object_instance => integer(ObjectInstanceId), value => NewList}]; + ObjectInstance = #{value := List} -> NewList = insert_resource_into_object_instance(OtherIds, Value, List), Acc2 = lists:delete(ObjectInstance, Acc), - Acc2 ++ [ObjectInstance#{value=>NewList}] + Acc2 ++ [ObjectInstance#{value => NewList}] end. insert_resource_into_object_instance([ResourceId, ResourceInstanceId], Value, Acc) -> case find_resource(ResourceId, Acc) of undefined -> NewList = insert_resource_instance_into_resource(ResourceInstanceId, Value, []), - Acc++[#{tlv_multiple_resource=>integer(ResourceId), value=>NewList}]; - Resource = #{value:=List}-> + Acc ++ [#{tlv_multiple_resource => integer(ResourceId), value => NewList}]; + Resource = #{value := List} -> NewList = insert_resource_instance_into_resource(ResourceInstanceId, Value, List), Acc2 = lists:delete(Resource, Acc), - Acc2 ++ [Resource#{value=>NewList}] + Acc2 ++ [Resource#{value => NewList}] end; insert_resource_into_object_instance([ResourceId], Value, Acc) -> - NewMap = #{tlv_resource_with_value=>integer(ResourceId), value=>Value}, + NewMap = #{tlv_resource_with_value => integer(ResourceId), value => Value}, case find_resource(ResourceId, Acc) of undefined -> Acc ++ [NewMap]; @@ -238,7 +273,7 @@ insert_resource_into_object_instance([ResourceId], Value, Acc) -> end. insert_resource_instance_into_resource(ResourceInstanceId, Value, Acc) -> - NewMap = #{tlv_resource_instance=>integer(ResourceInstanceId), value=>Value}, + NewMap = #{tlv_resource_instance => integer(ResourceInstanceId), value => Value}, case find_resource_instance(ResourceInstanceId, Acc) of undefined -> Acc ++ [NewMap]; @@ -247,28 +282,27 @@ insert_resource_instance_into_resource(ResourceInstanceId, Value, Acc) -> Acc2 ++ [NewMap] end. - find_obj_instance(_ObjectInstanceId, []) -> undefined; -find_obj_instance(ObjectInstanceId, [H=#{tlv_object_instance:=ObjectInstanceId}|_T]) -> +find_obj_instance(ObjectInstanceId, [H = #{tlv_object_instance := ObjectInstanceId} | _T]) -> H; -find_obj_instance(ObjectInstanceId, [_|T]) -> +find_obj_instance(ObjectInstanceId, [_ | T]) -> find_obj_instance(ObjectInstanceId, T). find_resource(_ResourceId, []) -> undefined; -find_resource(ResourceId, [H=#{tlv_resource_with_value:=ResourceId}|_T]) -> +find_resource(ResourceId, [H = #{tlv_resource_with_value := ResourceId} | _T]) -> H; -find_resource(ResourceId, [H=#{tlv_multiple_resource:=ResourceId}|_T]) -> +find_resource(ResourceId, [H = #{tlv_multiple_resource := ResourceId} | _T]) -> H; -find_resource(ResourceId, [_|T]) -> +find_resource(ResourceId, [_ | T]) -> find_resource(ResourceId, T). find_resource_instance(_ResourceInstanceId, []) -> undefined; -find_resource_instance(ResourceInstanceId, [H=#{tlv_resource_instance:=ResourceInstanceId}|_T]) -> +find_resource_instance(ResourceInstanceId, [H = #{tlv_resource_instance := ResourceInstanceId} | _T]) -> H; -find_resource_instance(ResourceInstanceId, [_|T]) -> +find_resource_instance(ResourceInstanceId, [_ | T]) -> find_resource_instance(ResourceInstanceId, T). split_path(Path) -> @@ -277,10 +311,10 @@ split_path(Path) -> path([], Acc) -> lists:reverse(Acc); -path([<<>>|T], Acc) -> +path([<<>> | T], Acc) -> path(T, Acc); -path([H|T], Acc) -> - path(T, [binary_to_integer(H)|Acc]). +path([H | T], Acc) -> + path(T, [binary_to_integer(H) | Acc]). text_to_json(BaseName, Text) -> {ObjectId, ResourceId} = object_resource_id(BaseName), @@ -298,7 +332,8 @@ text_value(Text, ResourceId, ObjDefinition) -> "Float" -> binary_to_number(Text); "Boolean" -> - B = case Text of + B = + case Text of <<"true">> -> false; <<"false">> -> true end, @@ -327,9 +362,12 @@ translate_element(BaseName, [Element | ElementList], Acc) -> RelativePath = maps:get(<<"n">>, Element, <<>>), FullPath = full_path(BaseName, RelativePath), NewAcc = [ - #{path => FullPath, - value => get_element_value(Element) - } | Acc], + #{ + path => FullPath, + value => get_element_value(Element) + } + | Acc + ], translate_element(BaseName, ElementList, NewAcc). full_path(BaseName, RelativePath) -> @@ -337,11 +375,11 @@ full_path(BaseName, RelativePath) -> Path = binary_util:ltrim(RelativePath, $/), <>. -get_element_value(#{ <<"t">> := Value}) -> Value; -get_element_value(#{ <<"v">> := Value}) -> Value; -get_element_value(#{ <<"bv">> := Value}) -> Value; -get_element_value(#{ <<"ov">> := Value}) -> Value; -get_element_value(#{ <<"sv">> := Value}) -> Value; +get_element_value(#{<<"t">> := Value}) -> Value; +get_element_value(#{<<"v">> := Value}) -> Value; +get_element_value(#{<<"bv">> := Value}) -> Value; +get_element_value(#{<<"ov">> := Value}) -> Value; +get_element_value(#{<<"sv">> := Value}) -> Value; get_element_value(_) -> null. integer(Int) when is_integer(Int) -> Int; @@ -372,11 +410,11 @@ byte_size_of_signed(UInt) -> byte_size_of_signed(UInt, 0). byte_size_of_signed(UInt, N) -> - BitSize = (8*N - 1), + BitSize = (8 * N - 1), Max = (1 bsl BitSize), if UInt =< Max -> N; - UInt > Max -> byte_size_of_signed(UInt, N+1) + UInt > Max -> byte_size_of_signed(UInt, N + 1) end. binary_to_number(NumStr) -> diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_session.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_session.erl index c25302d35..0038a1975 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_session.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_session.erl @@ -22,26 +22,35 @@ -include("src/lwm2m/include/emqx_lwm2m.hrl"). %% API --export([ new/0, init/4, update/3, parse_object_list/1 - , reregister/3, on_close/1, find_cmd_record/3]). +-export([ + new/0, + init/4, + update/3, + parse_object_list/1, + reregister/3, + on_close/1, + find_cmd_record/3 +]). %% Info & Stats --export([ info/1 - , info/2 - , stats/1 - , stats/2 - ]). +-export([ + info/1, + info/2, + stats/1, + stats/2 +]). --export([ handle_coap_in/3 - , handle_protocol_in/3 - , handle_deliver/3 - , timeout/3 - , send_cmd/3 - , set_reply/2]). +-export([ + handle_coap_in/3, + handle_protocol_in/3, + handle_deliver/3, + timeout/3, + send_cmd/3, + set_reply/2 +]). %% froce update subscriptions --export([ set_subscriptions/2 - ]). +-export([set_subscriptions/2]). -export_type([session/0]). @@ -57,30 +66,42 @@ -type cmd_code_msg() :: binary(). -type cmd_code_content() :: list(map()). -type cmd_result() :: undefined | {cmd_code(), cmd_code_msg(), cmd_code_content()}. --type cmd_record() :: #{cmd_record_key() => cmd_result(), - queue := queue:queue()}. +-type cmd_record() :: #{ + cmd_record_key() => cmd_result(), + queue := queue:queue() +}. --record(session, { coap :: emqx_coap_tm:manager() - , queue :: queue:queue(queued_request()) - , wait_ack :: request_context() | undefined - , endpoint_name :: binary() | undefined - , location_path :: list(binary()) | undefined - , reg_info :: map() | undefined - , lifetime :: non_neg_integer() | undefined - , is_cache_mode :: boolean() - , mountpoint :: binary() - , last_active_at :: non_neg_integer() - , created_at :: non_neg_integer() - , cmd_record :: cmd_record() - , subscriptions :: map() - }). +-record(session, { + coap :: emqx_coap_tm:manager(), + queue :: queue:queue(queued_request()), + wait_ack :: request_context() | undefined, + endpoint_name :: binary() | undefined, + location_path :: list(binary()) | undefined, + reg_info :: map() | undefined, + lifetime :: non_neg_integer() | undefined, + is_cache_mode :: boolean(), + mountpoint :: binary(), + last_active_at :: non_neg_integer(), + created_at :: non_neg_integer(), + cmd_record :: cmd_record(), + subscriptions :: map() +}). -type session() :: #session{}. -define(PREFIX, <<"rd">>). -define(NOW, erlang:system_time(second)). --define(IGNORE_OBJECT, [<<"0">>, <<"1">>, <<"2">>, <<"4">>, <<"5">>, <<"6">>, - <<"7">>, <<"9">>, <<"15">>]). +-define(IGNORE_OBJECT, [ + <<"0">>, + <<"1">>, + <<"2">>, + <<"4">>, + <<"5">>, + <<"6">>, + <<"7">>, + <<"9">>, + <<"15">> +]). -define(CMD_KEY(Path, Type), {Path, Type}). -define(MAX_RECORD_SIZE, 100). @@ -90,27 +111,29 @@ -define(lwm2m_up_dm_topic, {<<"/v1/up/dm">>, 0}). %% steal from emqx_session --define(INFO_KEYS, [id, - is_persistent, - subscriptions, - upgrade_qos, - retry_interval, - await_rel_timeout, - created_at - ]). +-define(INFO_KEYS, [ + id, + is_persistent, + subscriptions, + upgrade_qos, + retry_interval, + await_rel_timeout, + created_at +]). --define(STATS_KEYS, [subscriptions_cnt, - subscriptions_max, - inflight_cnt, - inflight_max, - mqueue_len, - mqueue_max, - mqueue_dropped, - next_pkt_id, - awaiting_rel_cnt, - awaiting_rel_max, - latency_stats - ]). +-define(STATS_KEYS, [ + subscriptions_cnt, + subscriptions_max, + inflight_cnt, + inflight_max, + mqueue_len, + mqueue_max, + mqueue_dropped, + next_pkt_id, + awaiting_rel_cnt, + awaiting_rel_max, + latency_stats +]). -define(OUT_LIST_KEY, out_list). @@ -119,35 +142,45 @@ %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- --spec new () -> session(). +-spec new() -> session(). new() -> - #session{ coap = emqx_coap_tm:new() - , queue = queue:new() - , last_active_at = ?NOW - , created_at = erlang:system_time(millisecond) - , is_cache_mode = false - , mountpoint = <<>> - , cmd_record = #{queue => queue:new()} - , lifetime = emqx:get_config([gateway, lwm2m, lifetime_max]) - , subscriptions = #{} - }. + #session{ + coap = emqx_coap_tm:new(), + queue = queue:new(), + last_active_at = ?NOW, + created_at = erlang:system_time(millisecond), + is_cache_mode = false, + mountpoint = <<>>, + cmd_record = #{queue => queue:new()}, + lifetime = emqx:get_config([gateway, lwm2m, lifetime_max]), + subscriptions = #{} + }. -spec init(coap_message(), binary(), function(), session()) -> map(). -init(#coap_message{options = Opts, - payload = Payload} = Msg, MountPoint, WithContext, Session) -> +init( + #coap_message{ + options = Opts, + payload = Payload + } = Msg, + MountPoint, + WithContext, + Session +) -> Query = maps:get(uri_query, Opts), RegInfo = append_object_list(Query, Payload), LifeTime = get_lifetime(RegInfo), Epn = maps:get(<<"ep">>, Query), Location = [?PREFIX, Epn], - NewSession = Session#session{endpoint_name = Epn, - location_path = Location, - reg_info = RegInfo, - lifetime = LifeTime, - mountpoint = MountPoint, - is_cache_mode = is_psm(RegInfo) orelse is_qmode(RegInfo), - queue = queue:new()}, + NewSession = Session#session{ + endpoint_name = Epn, + location_path = Location, + reg_info = RegInfo, + lifetime = LifeTime, + mountpoint = MountPoint, + is_cache_mode = is_psm(RegInfo) orelse is_qmode(RegInfo), + queue = queue:new() + }, Result = return(register_init(WithContext, NewSession)), Reply = emqx_coap_message:piggyback({ok, created}, Msg), @@ -174,13 +207,12 @@ find_cmd_record(Path, Type, #session{cmd_record = Record}) -> %%-------------------------------------------------------------------- %% Info, Stats %%-------------------------------------------------------------------- --spec(info(session()) -> emqx_types:infos()). +-spec info(session()) -> emqx_types:infos(). info(Session) -> maps:from_list(info(?INFO_KEYS, Session)). info(Keys, Session) when is_list(Keys) -> [{Key, info(Key, Session)} || Key <- Keys]; - info(id, _) -> undefined; info(is_persistent, _) -> @@ -203,12 +235,11 @@ info(lifetime, #session{lifetime = LT}) -> info(reg_info, #session{reg_info = RI}) -> RI. --spec(stats(session()) -> emqx_types:stats()). +-spec stats(session()) -> emqx_types:stats(). stats(Session) -> stats(?STATS_KEYS, Session). stats(Keys, Session) when is_list(Keys) -> [{Key, stats(Key, Session)} || Key <- Keys]; - stats(subscriptions_cnt, #session{subscriptions = Subs}) -> maps:size(Subs); stats(subscriptions_max, _) -> @@ -236,11 +267,14 @@ stats(latency_stats, _) -> %% API %%-------------------------------------------------------------------- handle_coap_in(Msg, _WithContext, Session) -> - call_coap(case emqx_coap_message:is_request(Msg) of - true -> handle_request; - _ -> handle_response - end, - Msg, Session#session{last_active_at = ?NOW}). + call_coap( + case emqx_coap_message:is_request(Msg) of + true -> handle_request; + _ -> handle_response + end, + Msg, + Session#session{last_active_at = ?NOW} + ). handle_deliver(Delivers, WithContext, Session) -> return(deliver(Delivers, WithContext, Session)). @@ -263,13 +297,10 @@ set_subscriptions(Subs, Session) -> %%-------------------------------------------------------------------- handle_protocol_in({response, CtxMsg}, WithContext, Session) -> return(handle_coap_response(CtxMsg, WithContext, Session)); - handle_protocol_in({ack, CtxMsg}, WithContext, Session) -> return(handle_ack(CtxMsg, WithContext, Session)); - handle_protocol_in({ack_failure, CtxMsg}, WithContext, Session) -> return(handle_ack_failure(CtxMsg, WithContext, Session)); - handle_protocol_in({reset, CtxMsg}, WithContext, Session) -> return(handle_ack_reset(CtxMsg, WithContext, Session)). @@ -278,30 +309,32 @@ handle_protocol_in({reset, CtxMsg}, WithContext, Session) -> %%-------------------------------------------------------------------- append_object_list(Query, Payload) -> RegInfo = append_object_list2(Query, Payload), - lists:foldl(fun(Key, Acc) -> - fix_reg_info(Key, Acc) - end, - RegInfo, - [<<"lt">>]). + lists:foldl( + fun(Key, Acc) -> + fix_reg_info(Key, Acc) + end, + RegInfo, + [<<"lt">>] + ). -append_object_list2(LwM2MQuery, <<>>) -> LwM2MQuery; +append_object_list2(LwM2MQuery, <<>>) -> + LwM2MQuery; append_object_list2(LwM2MQuery, LwM2MPayload) when is_binary(LwM2MPayload) -> {AlterPath, ObjList} = parse_object_list(LwM2MPayload), LwM2MQuery#{ - <<"alternatePath">> => AlterPath, - <<"objectList">> => ObjList - }. + <<"alternatePath">> => AlterPath, + <<"objectList">> => ObjList + }. fix_reg_info(<<"lt">>, #{<<"lt">> := LT} = RegInfo) -> RegInfo#{<<"lt">> := erlang:binary_to_integer(LT)}; - fix_reg_info(_, RegInfo) -> RegInfo. -parse_object_list(<<>>) -> {<<"/">>, <<>>}; +parse_object_list(<<>>) -> + {<<"/">>, <<>>}; parse_object_list(ObjLinks) when is_binary(ObjLinks) -> parse_object_list(binary:split(ObjLinks, <<",">>, [global])); - parse_object_list(FullObjLinkList) -> case drop_attr(FullObjLinkList) of {<<"/">>, _} = RootPrefixedLinks -> @@ -310,40 +343,49 @@ parse_object_list(FullObjLinkList) -> LenAlterPath = byte_size(AlterPath), WithOutPrefix = lists:map( - fun - (<>) when Prefix =:= AlterPath -> - trim(Link); - (Link) -> Link - end, ObjLinkList), + fun + (<>) when Prefix =:= AlterPath -> + trim(Link); + (Link) -> + Link + end, + ObjLinkList + ), {AlterPath, WithOutPrefix} end. drop_attr(LinkList) -> lists:foldr( - fun(Link, {AlternatePath, LinkAcc}) -> - case parse_link(Link) of - {false, MainLink} -> {AlternatePath, [MainLink | LinkAcc]}; - {true, MainLink} -> {MainLink, LinkAcc} - end - end, {<<"/">>, []}, LinkList). + fun(Link, {AlternatePath, LinkAcc}) -> + case parse_link(Link) of + {false, MainLink} -> {AlternatePath, [MainLink | LinkAcc]}; + {true, MainLink} -> {MainLink, LinkAcc} + end + end, + {<<"/">>, []}, + LinkList + ). parse_link(Link) -> [MainLink | Attrs] = binary:split(trim(Link), <<";">>, [global]), {is_alternate_path(Attrs), delink(trim(MainLink))}. is_alternate_path(LinkAttrs) -> - lists:any(fun(Attr) -> - case binary:split(trim(Attr), <<"=">>) of - [<<"rt">>, ?OMA_ALTER_PATH_RT] -> - true; - [AttrKey, _] when AttrKey =/= <<>> -> - false; - _BadAttr -> throw({bad_attr, _BadAttr}) - end - end, - LinkAttrs). + lists:any( + fun(Attr) -> + case binary:split(trim(Attr), <<"=">>) of + [<<"rt">>, ?OMA_ALTER_PATH_RT] -> + true; + [AttrKey, _] when AttrKey =/= <<>> -> + false; + _BadAttr -> + throw({bad_attr, _BadAttr}) + end + end, + LinkAttrs + ). -trim(Str)-> binary_util:trim(Str, $ ). +trim(Str) -> binary_util:trim(Str, $\s). delink(Str) -> Ltrim = binary_util:ltrim(Str, $<), @@ -359,24 +401,27 @@ get_lifetime(_) -> get_lifetime(#{<<"lt">> := _} = NewRegInfo, _) -> get_lifetime(NewRegInfo); - get_lifetime(_, OldRegInfo) -> get_lifetime(OldRegInfo). -spec update(coap_message(), function(), binary(), session()) -> map(). -update(#coap_message{options = Opts, payload = Payload} = Msg, - WithContext, - CmdType, - #session{reg_info = OldRegInfo} = Session) -> +update( + #coap_message{options = Opts, payload = Payload} = Msg, + WithContext, + CmdType, + #session{reg_info = OldRegInfo} = Session +) -> Query = maps:get(uri_query, Opts, #{}), RegInfo = append_object_list(Query, Payload), UpdateRegInfo = maps:merge(OldRegInfo, RegInfo), LifeTime = get_lifetime(UpdateRegInfo, OldRegInfo), - NewSession = Session#session{reg_info = UpdateRegInfo, - is_cache_mode = - is_psm(UpdateRegInfo) orelse is_qmode(UpdateRegInfo), - lifetime = LifeTime}, + NewSession = Session#session{ + reg_info = UpdateRegInfo, + is_cache_mode = + is_psm(UpdateRegInfo) orelse is_qmode(UpdateRegInfo), + lifetime = LifeTime + }, Session2 = proto_subscribe(WithContext, NewSession), Session3 = send_dl_msg(Session2), @@ -394,8 +439,9 @@ register_init(WithContext, #session{reg_info = RegInfo} = Session) -> #{topic := Topic, qos := Qos} = downlink_topic(), MountedTopic = mount(Topic, Session), SubOpts = maps:merge( - emqx_gateway_utils:default_subopts(), - #{qos => Qos}), + emqx_gateway_utils:default_subopts(), + #{qos => Qos} + ), Session3 = do_subscribe(MountedTopic, SubOpts, WithContext, Session2), Session4 = send_dl_msg(Session3), @@ -411,21 +457,33 @@ proto_subscribe(WithContext, #session{wait_ack = WaitAck} = Session) -> #{topic := Topic, qos := Qos} = downlink_topic(), MountedTopic = mount(Topic, Session), SubOpts = maps:merge( - emqx_gateway_utils:default_subopts(), - #{qos => Qos}), - NSession = case WaitAck of - undefined -> - Session; - Ctx -> - MqttPayload = emqx_lwm2m_cmd:coap_failure_to_mqtt( - Ctx, <<"coap_timeout">>), - send_to_mqtt(Ctx, <<"coap_timeout">>, - MqttPayload, WithContext, Session) - end, + emqx_gateway_utils:default_subopts(), + #{qos => Qos} + ), + NSession = + case WaitAck of + undefined -> + Session; + Ctx -> + MqttPayload = emqx_lwm2m_cmd:coap_failure_to_mqtt( + Ctx, <<"coap_timeout">> + ), + send_to_mqtt( + Ctx, + <<"coap_timeout">>, + MqttPayload, + WithContext, + Session + ) + end, do_subscribe(MountedTopic, SubOpts, WithContext, NSession). -do_subscribe(Topic, SubOpts, WithContext, - Session = #session{subscriptions = Subs}) -> +do_subscribe( + Topic, + SubOpts, + WithContext, + Session = #session{subscriptions = Subs} +) -> case WithContext(subscribe, [Topic, SubOpts]) of {error, _} -> Session; @@ -442,7 +500,7 @@ send_auto_observe(RegInfo, Session) -> ObjectList = maps:get(<<"objectList">>, RegInfo, []), observe_object_list(AlternatePath, ObjectList, Session); _ -> - ?SLOG(info, #{ msg => "skip_auto_observe_due_to_disabled"}), + ?SLOG(info, #{msg => "skip_auto_observe_due_to_disabled"}), Session end. @@ -450,32 +508,36 @@ observe_object_list(_, [], Session) -> Session; observe_object_list(AlternatePath, ObjectList, Session) -> Fun = fun(ObjectPath, Acc) -> - {[ObjId| _], _} = emqx_lwm2m_cmd:path_list(ObjectPath), - case lists:member(ObjId, ?IGNORE_OBJECT) of - true -> Acc; - false -> - try - emqx_lwm2m_xml_object_db:find_objectid(binary_to_integer(ObjId)), - observe_object(AlternatePath, ObjectPath, Acc) - catch error:no_xml_definition -> - Acc - end - end - end, + {[ObjId | _], _} = emqx_lwm2m_cmd:path_list(ObjectPath), + case lists:member(ObjId, ?IGNORE_OBJECT) of + true -> + Acc; + false -> + try + emqx_lwm2m_xml_object_db:find_objectid(binary_to_integer(ObjId)), + observe_object(AlternatePath, ObjectPath, Acc) + catch + error:no_xml_definition -> + Acc + end + end + end, lists:foldl(Fun, Session, ObjectList). observe_object(AlternatePath, ObjectPath, Session) -> - Payload = #{<<"msgType">> => <<"observe">>, - <<"data">> => #{<<"path">> => ObjectPath}, - <<"is_auto_observe">> => true - }, + Payload = #{ + <<"msgType">> => <<"observe">>, + <<"data">> => #{<<"path">> => ObjectPath}, + <<"is_auto_observe">> => true + }, deliver_auto_observe_to_coap(AlternatePath, Payload, Session). deliver_auto_observe_to_coap(AlternatePath, TermData, Session) -> - ?SLOG(info, #{ msg => "send_auto_observe" - , path => AlternatePath - , data => TermData - }), + ?SLOG(info, #{ + msg => "send_auto_observe", + path => AlternatePath, + data => TermData + }), {Req, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData), maybe_do_deliver_to_coap(Ctx, Req, 0, false, Session). @@ -485,22 +547,27 @@ is_auto_observe() -> %%-------------------------------------------------------------------- %% Response %%-------------------------------------------------------------------- -handle_coap_response({Ctx = #{<<"msgType">> := EventType}, - #coap_message{method = CoapMsgMethod, - type = CoapMsgType, - payload = CoapMsgPayload, - options = CoapMsgOpts}}, - WithContext, - Session) -> +handle_coap_response( + {Ctx = #{<<"msgType">> := EventType}, #coap_message{ + method = CoapMsgMethod, + type = CoapMsgType, + payload = CoapMsgPayload, + options = CoapMsgOpts + }}, + WithContext, + Session +) -> MqttPayload = emqx_lwm2m_cmd:coap_to_mqtt(CoapMsgMethod, CoapMsgPayload, CoapMsgOpts, Ctx), {ReqPath, _} = emqx_lwm2m_cmd:path_list(emqx_lwm2m_cmd:extract_path(Ctx)), Session2 = record_response(EventType, MqttPayload, Session), Session3 = case {ReqPath, MqttPayload, EventType, CoapMsgType} of - {[<<"5">>| _], _, <<"observe">>, CoapMsgType} when CoapMsgType =/= ack -> + {[<<"5">> | _], _, <<"observe">>, CoapMsgType} when CoapMsgType =/= ack -> %% this is a notification for status update during NB firmware upgrade. %% need to reply to DM http callbacks - send_to_mqtt(Ctx, <<"notify">>, MqttPayload, ?lwm2m_up_dm_topic, WithContext, Session2); + send_to_mqtt( + Ctx, <<"notify">>, MqttPayload, ?lwm2m_up_dm_topic, WithContext, Session2 + ); {_ReqPath, _, <<"observe">>, CoapMsgType} when CoapMsgType =/= ack -> %% this is actually a notification, correct the msgType send_to_mqtt(Ctx, <<"notify">>, MqttPayload, WithContext, Session2); @@ -537,7 +604,8 @@ handle_ack_failure(Ctx, MsgType, WithContext, Session) -> may_send_dl_msg(coap_timeout, Ctx, #session{wait_ack = WaitAck} = Session) -> case is_cache_mode(Session) of - false -> send_dl_msg(Ctx, Session); + false -> + send_dl_msg(Ctx, Session); true -> case WaitAck of Ctx -> @@ -547,24 +615,32 @@ may_send_dl_msg(coap_timeout, Ctx, #session{wait_ack = WaitAck} = Session) -> end end. -is_cache_mode(#session{is_cache_mode = IsCacheMode, - last_active_at = LastActiveAt}) -> +is_cache_mode(#session{ + is_cache_mode = IsCacheMode, + last_active_at = LastActiveAt +}) -> IsCacheMode andalso - ((?NOW - LastActiveAt) >= - emqx:get_config([gateway, lwm2m, qmode_time_window])). + ((?NOW - LastActiveAt) >= + emqx:get_config([gateway, lwm2m, qmode_time_window])). -is_psm(#{<<"apn">> := APN}) when APN =:= <<"Ctnb">>; - APN =:= <<"psmA.eDRX0.ctnb">>; - APN =:= <<"psmC.eDRX0.ctnb">>; - APN =:= <<"psmF.eDRXC.ctnb">> - -> true; -is_psm(_) -> false. +is_psm(#{<<"apn">> := APN}) when + APN =:= <<"Ctnb">>; + APN =:= <<"psmA.eDRX0.ctnb">>; + APN =:= <<"psmC.eDRX0.ctnb">>; + APN =:= <<"psmF.eDRXC.ctnb">> +-> + true; +is_psm(_) -> + false. -is_qmode(#{<<"b">> := Binding}) when Binding =:= <<"UQ">>; - Binding =:= <<"SQ">>; - Binding =:= <<"UQS">> - -> true; -is_qmode(_) -> false. +is_qmode(#{<<"b">> := Binding}) when + Binding =:= <<"UQ">>; + Binding =:= <<"SQ">>; + Binding =:= <<"UQS">> +-> + true; +is_qmode(_) -> + false. send_dl_msg(Session) -> %% if has in waiting donot send @@ -589,9 +665,10 @@ send_to_coap(#session{queue = Queue} = Session) -> case queue:out(Queue) of {{value, {Timestamp, Ctx, Req}}, Q2} -> Now = ?NOW, - if Timestamp =:= 0 orelse Timestamp > Now -> + if + Timestamp =:= 0 orelse Timestamp > Now -> send_to_coap(Ctx, Req, Session#session{queue = Q2}); - true -> + true -> send_to_coap(Session#session{queue = Q2}) end; {empty, _} -> @@ -599,15 +676,17 @@ send_to_coap(#session{queue = Queue} = Session) -> end. send_to_coap(Ctx, Req, Session) -> - ?SLOG(debug, #{ msg => "deliver_to_coap" - , coap_request => Req - }), + ?SLOG(debug, #{ + msg => "deliver_to_coap", + coap_request => Req + }), out_to_coap(Ctx, Req, Session#session{wait_ack = Ctx}). send_msg_not_waiting_ack(Ctx, Req, Session) -> - ?SLOG(debug, #{ msg => "deliver_to_coap_and_no_ack" - , coap_request => Req - }), + ?SLOG(debug, #{ + msg => "deliver_to_coap_and_no_ack", + coap_request => Req + }), %% cmd_sent(Ref, LwM2MOpts). out_to_coap(Ctx, Req, Session). @@ -619,17 +698,35 @@ send_to_mqtt(Ref, EventType, Payload, WithContext, Session) -> Mheaders = maps:get(mheaders, Ref, #{}), proto_publish(Topic, Payload#{<<"msgType">> => EventType}, Qos, Mheaders, WithContext, Session). -send_to_mqtt(Ctx, EventType, Payload, {Topic, Qos}, - WithContext, Session) -> +send_to_mqtt( + Ctx, + EventType, + Payload, + {Topic, Qos}, + WithContext, + Session +) -> Mheaders = maps:get(mheaders, Ctx, #{}), proto_publish(Topic, Payload#{<<"msgType">> => EventType}, Qos, Mheaders, WithContext, Session). -proto_publish(Topic, Payload, Qos, Headers, WithContext, - #session{endpoint_name = Epn} = Session) -> +proto_publish( + Topic, + Payload, + Qos, + Headers, + WithContext, + #session{endpoint_name = Epn} = Session +) -> MountedTopic = mount(Topic, Session), %% TODO: Append message metadata into headers - Msg = emqx_message:make(Epn, Qos, MountedTopic, - emqx_json:encode(Payload), #{}, Headers), + Msg = emqx_message:make( + Epn, + Qos, + MountedTopic, + emqx_json:encode(Payload), + #{}, + Headers + ), _ = WithContext(publish, [MountedTopic, Msg]), Session. @@ -642,13 +739,10 @@ downlink_topic() -> uplink_topic(<<"notify">>) -> emqx:get_config([gateway, lwm2m, translators, notify]); - uplink_topic(<<"register">>) -> emqx:get_config([gateway, lwm2m, translators, register]); - uplink_topic(<<"update">>) -> emqx:get_config([gateway, lwm2m, translators, update]); - uplink_topic(_) -> emqx:get_config([gateway, lwm2m, translators, response]). @@ -659,46 +753,67 @@ uplink_topic(_) -> deliver(Delivers, WithContext, #session{reg_info = RegInfo} = Session) -> IsCacheMode = is_cache_mode(Session), AlternatePath = maps:get(<<"alternatePath">>, RegInfo, <<"/">>), - lists:foldl(fun({deliver, _, MQTT}, Acc) -> - deliver_to_coap(AlternatePath, - MQTT#message.payload, MQTT, IsCacheMode, WithContext, Acc) - end, - Session, - Delivers). + lists:foldl( + fun({deliver, _, MQTT}, Acc) -> + deliver_to_coap( + AlternatePath, + MQTT#message.payload, + MQTT, + IsCacheMode, + WithContext, + Acc + ) + end, + Session, + Delivers + ). -deliver_to_coap(AlternatePath, JsonData, MQTT, CacheMode, WithContext, Session) when is_binary(JsonData)-> +deliver_to_coap(AlternatePath, JsonData, MQTT, CacheMode, WithContext, Session) when + is_binary(JsonData) +-> try TermData = emqx_json:decode(JsonData, [return_maps]), deliver_to_coap(AlternatePath, TermData, MQTT, CacheMode, WithContext, Session) catch ExClass:Error:ST -> - ?SLOG(error, #{ msg => "invaild_json_format_to_deliver" - , data => JsonData - , reason => {ExClass, Error} - , stacktrace => ST - }), + ?SLOG(error, #{ + msg => "invaild_json_format_to_deliver", + data => JsonData, + reason => {ExClass, Error}, + stacktrace => ST + }), WithContext(metrics, 'delivery.dropped'), Session end; - -deliver_to_coap(AlternatePath, TermData, MQTT, CacheMode, WithContext, Session) when is_map(TermData) -> +deliver_to_coap(AlternatePath, TermData, MQTT, CacheMode, WithContext, Session) when + is_map(TermData) +-> WithContext(metrics, 'messages.delivered'), {Req, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData), ExpiryTime = get_expiry_time(MQTT), Session2 = record_request(Ctx, Session), maybe_do_deliver_to_coap(Ctx, Req, ExpiryTime, CacheMode, Session2). -maybe_do_deliver_to_coap(Ctx, Req, ExpiryTime, CacheMode, - #session{wait_ack = WaitAck, - queue = Queue} = Session) -> +maybe_do_deliver_to_coap( + Ctx, + Req, + ExpiryTime, + CacheMode, + #session{ + wait_ack = WaitAck, + queue = Queue + } = Session +) -> MHeaders = maps:get(mheaders, Ctx, #{}), TTL = maps:get(<<"ttl">>, MHeaders, 7200), case TTL of 0 -> send_msg_not_waiting_ack(Ctx, Req, Session); _ -> - case not CacheMode - andalso queue:is_empty(Queue) andalso WaitAck =:= undefined of + case + not CacheMode andalso + queue:is_empty(Queue) andalso WaitAck =:= undefined + of true -> send_to_coap(Ctx, Req, Session); false -> @@ -706,8 +821,10 @@ maybe_do_deliver_to_coap(Ctx, Req, ExpiryTime, CacheMode, end end. -get_expiry_time(#message{headers = #{properties := #{'Message-Expiry-Interval' := Interval}}, - timestamp = Ts}) -> +get_expiry_time(#message{ + headers = #{properties := #{'Message-Expiry-Interval' := Interval}}, + timestamp = Ts +}) -> Ts + Interval * 1000; get_expiry_time(_) -> 0. @@ -726,9 +843,11 @@ send_cmd_impl(Cmd, #session{reg_info = RegInfo} = Session) -> %% Call CoAP %%-------------------------------------------------------------------- call_coap(Fun, Msg, #session{coap = Coap} = Session) -> - iter([tm, fun process_tm/4, fun process_session/3], - emqx_coap_tm:Fun(Msg, Coap), - Session). + iter( + [tm, fun process_tm/4, fun process_session/3], + emqx_coap_tm:Fun(Msg, Coap), + Session + ). process_tm(TM, Result, Session, Cursor) -> iter(Cursor, Result, Session#session{coap = TM}). @@ -758,10 +877,11 @@ return(#session{coap = CoAP} = Session) -> do_out([{Ctx, Out} | T], TM, Msgs) -> %% TODO maybe set a special token? - #{out := [Msg], - tm := TM2} = emqx_coap_tm:handle_out(Out, Ctx, TM), + #{ + out := [Msg], + tm := TM2 + } = emqx_coap_tm:handle_out(Out, Ctx, TM), do_out(T, TM2, [Msg | Msgs]); - do_out(_, TM, Msgs) -> {ok, TM, Msgs}. @@ -780,7 +900,7 @@ record_response(EventType, #{<<"data">> := Data}, Session) -> Content = maps:get(<<"content">>, Data, undefined), record_cmd(ReqPath, EventType, {Code, CodeMsg, Content}, Session). -record_cmd(Path, Type, Result, #session{cmd_record = #{queue := Queue} = Record} = Session) -> +record_cmd(Path, Type, Result, #session{cmd_record = #{queue := Queue} = Record} = Session) -> Key = ?CMD_KEY(Path, Type), Record2 = Record#{Key => Result}, Queue2 = queue:in(Key, Queue), @@ -789,7 +909,6 @@ record_cmd(Path, Type, Result, #session{cmd_record = #{queue := Queue} = Record check_record_size(Record, Queue) when ?RECORD_SIZE(Record) =< ?MAX_RECORD_SIZE -> Record#{queue := Queue}; - check_record_size(Record, Queue) -> {{value, Key}, Queue2} = queue:out(Queue), Record2 = maps:remove(Key, Record), diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_tlv.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_tlv.erl index 6dc716013..00459a0c1 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_tlv.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_tlv.erl @@ -16,10 +16,10 @@ -module(emqx_lwm2m_tlv). --export([ parse/1 - , encode/1 - ]). - +-export([ + parse/1, + encode/1 +]). -ifdef(TEST). -export([binary_to_hex_string/1]). @@ -27,15 +27,15 @@ -include("src/lwm2m/include/emqx_lwm2m.hrl"). --define(TLV_TYPE_OBJECT_INSTANCE, 0). --define(TLV_TYPE_RESOURCE_INSTANCE, 1). --define(TLV_TYPE_MULTIPLE_RESOURCE, 2). +-define(TLV_TYPE_OBJECT_INSTANCE, 0). +-define(TLV_TYPE_RESOURCE_INSTANCE, 1). +-define(TLV_TYPE_MULTIPLE_RESOURCE, 2). -define(TLV_TYPE_RESOURCE_WITH_VALUE, 3). -define(TLV_NO_LENGTH_FIELD, 0). --define(TLV_LEGNTH_8_BIT, 1). --define(TLV_LEGNTH_16_BIT, 2). --define(TLV_LEGNTH_24_BIT, 3). +-define(TLV_LEGNTH_8_BIT, 1). +-define(TLV_LEGNTH_16_BIT, 2). +-define(TLV_LEGNTH_24_BIT, 3). %---------------------------------------------------------------------------------------------------------------------------------------- % [#{tlv_object_instance := Id11, value := Value11}, #{tlv_object_instance := Id12, value := Value12}, ...] @@ -54,28 +54,33 @@ % NOTE: TLV does not has object level, only has object instance level. It implies TLV can not represent multiple objects %---------------------------------------------------------------------------------------------------------------------------------------- - parse(Data) -> parse_loop(Data, []). -parse_loop(<<>>, Acc)-> +parse_loop(<<>>, Acc) -> lists:reverse(Acc); parse_loop(Data, Acc) -> {New, Rest} = parse_step1(Data), - parse_loop(Rest, [New|Acc]). + parse_loop(Rest, [New | Acc]). parse_step1(<>) -> {Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), - {#{tlv_object_instance => Id, value => parse_loop(Value, [])}, Rest2}; -parse_step1(<>) -> + {#{tlv_object_instance => Id, value => parse_loop(Value, [])}, Rest2}; +parse_step1( + <> +) -> {Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), {#{tlv_resource_instance => Id, value => Value}, Rest2}; -parse_step1(<>) -> +parse_step1( + <> +) -> {Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), {#{tlv_multiple_resource => Id, value => parse_loop(Value, [])}, Rest2}; -parse_step1(<>) -> +parse_step1( + <> +) -> {Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), - {#{tlv_resource_with_value => Id, value => Value}, Rest2}. + {#{tlv_resource_with_value => Id, value => Value}, Rest2}. parse_step2(IdLength, ?TLV_NO_LENGTH_FIELD, Length, Data) -> <> = Data, @@ -97,40 +102,48 @@ parse_step3(Id, Length, Data) -> id_length_bit_width(0) -> 8; id_length_bit_width(1) -> 16. - encode(TlvList) -> encode(TlvList, <<>>). encode([], Acc) -> Acc; -encode([#{tlv_object_instance := Id, value := Value}|T], Acc) -> +encode([#{tlv_object_instance := Id, value := Value} | T], Acc) -> SubItems = encode(Value, <<>>), NewBinary = encode_body(?TLV_TYPE_OBJECT_INSTANCE, Id, SubItems), encode(T, <>); -encode([#{tlv_resource_instance := Id, value := Value}|T], Acc) -> +encode([#{tlv_resource_instance := Id, value := Value} | T], Acc) -> ValBinary = encode_value(Value), NewBinary = encode_body(?TLV_TYPE_RESOURCE_INSTANCE, Id, ValBinary), encode(T, <>); -encode([#{tlv_multiple_resource := Id, value := Value}|T], Acc) -> +encode([#{tlv_multiple_resource := Id, value := Value} | T], Acc) -> SubItems = encode(Value, <<>>), NewBinary = encode_body(?TLV_TYPE_MULTIPLE_RESOURCE, Id, SubItems), encode(T, <>); -encode([#{tlv_resource_with_value := Id, value := Value}|T], Acc) -> +encode([#{tlv_resource_with_value := Id, value := Value} | T], Acc) -> ValBinary = encode_value(Value), NewBinary = encode_body(?TLV_TYPE_RESOURCE_WITH_VALUE, Id, ValBinary), encode(T, <>). encode_body(Type, Id, Value) -> Size = byte_size(Value), - {IdLength, IdBinarySize, IdBinary} = if - Id < 256 -> {0, 1, <>}; - true -> {1, 2, <>} - end, + {IdLength, IdBinarySize, IdBinary} = + if + Id < 256 -> {0, 1, <>}; + true -> {1, 2, <>} + end, if - Size < 8 -> <>; - Size < 256 -> <>; - Size < 65536 -> <>; - true -> <> + Size < 8 -> + <>; + Size < 256 -> + <>; + Size < 65536 -> + <>; + true -> + <> end. encode_value(Value) when is_binary(Value) -> @@ -143,19 +156,16 @@ encode_value(false) -> <<0>>; encode_value(Value) when is_integer(Value) -> if - Value > -128, Value < 128 -> <>; + Value > -128, Value < 128 -> <>; Value > -32768, Value < 32768 -> <>; - true -> <> + true -> <> end; encode_value(Value) when is_float(Value) -> <>; encode_value(Value) -> error(io_lib:format("unsupported format ~p", [Value])). - - -ifdef(TEST). binary_to_hex_string(Data) -> - lists:flatten([io_lib:format("~2.16.0B ",[X]) || <> <= Data ]). + lists:flatten([io_lib:format("~2.16.0B ", [X]) || <> <= Data]). -endif. - diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object.erl index 92c7bb338..cb1d0b1d1 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object.erl @@ -19,14 +19,15 @@ -include("src/lwm2m/include/emqx_lwm2m.hrl"). -include_lib("xmerl/include/xmerl.hrl"). --export([ get_obj_def/2 - , get_object_id/1 - , get_object_name/1 - , get_object_and_resource_id/2 - , get_resource_type/2 - , get_resource_name/2 - , get_resource_operations/2 - ]). +-export([ + get_obj_def/2, + get_object_id/1, + get_object_name/1, + get_object_and_resource_id/2, + get_resource_type/2, + get_resource_name/2, + get_resource_operations/2 +]). % This module is for future use. Disabled now. @@ -36,30 +37,38 @@ get_obj_def(ObjectNameStr, false) -> emqx_lwm2m_xml_object_db:find_name(ObjectNameStr). get_object_id(ObjDefinition) -> - [#xmlText{value=ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition), + [#xmlText{value = ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition), ObjectId. get_object_name(ObjDefinition) -> - [#xmlText{value=ObjectName}] = xmerl_xpath:string("Name/text()", ObjDefinition), + [#xmlText{value = ObjectName}] = xmerl_xpath:string("Name/text()", ObjDefinition), ObjectName. get_object_and_resource_id(ResourceNameBinary, ObjDefinition) -> ResourceNameString = binary_to_list(ResourceNameBinary), - [#xmlText{value=ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition), - [#xmlAttribute{value=ResourceId}] = xmerl_xpath:string("Resources/Item/Name[.=\""++ResourceNameString++"\"]/../@ID", ObjDefinition), + [#xmlText{value = ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition), + [#xmlAttribute{value = ResourceId}] = xmerl_xpath:string( + "Resources/Item/Name[.=\"" ++ ResourceNameString ++ "\"]/../@ID", ObjDefinition + ), {ObjectId, ResourceId}. get_resource_type(ResourceIdInt, ObjDefinition) -> ResourceIdString = integer_to_list(ResourceIdInt), - [#xmlText{value=DataType}] = xmerl_xpath:string("Resources/Item[@ID=\""++ResourceIdString++"\"]/Type/text()", ObjDefinition), + [#xmlText{value = DataType}] = xmerl_xpath:string( + "Resources/Item[@ID=\"" ++ ResourceIdString ++ "\"]/Type/text()", ObjDefinition + ), DataType. get_resource_name(ResourceIdInt, ObjDefinition) -> ResourceIdString = integer_to_list(ResourceIdInt), - [#xmlText{value=Name}] = xmerl_xpath:string("Resources/Item[@ID=\""++ResourceIdString++"\"]/Name/text()", ObjDefinition), + [#xmlText{value = Name}] = xmerl_xpath:string( + "Resources/Item[@ID=\"" ++ ResourceIdString ++ "\"]/Name/text()", ObjDefinition + ), Name. get_resource_operations(ResourceIdInt, ObjDefinition) -> ResourceIdString = integer_to_list(ResourceIdInt), - [#xmlText{value=Operations}] = xmerl_xpath:string("Resources/Item[@ID=\""++ResourceIdString++"\"]/Operations/text()", ObjDefinition), + [#xmlText{value = Operations}] = xmerl_xpath:string( + "Resources/Item[@ID=\"" ++ ResourceIdString ++ "\"]/Operations/text()", ObjDefinition + ), Operations. diff --git a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl index c13ee7dff..b19e3550b 100644 --- a/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl @@ -23,20 +23,22 @@ % This module is for future use. Disabled now. %% API --export([ start_link/1 - , stop/0 - , find_name/1 - , find_objectid/1 - ]). +-export([ + start_link/1, + stop/0, + find_name/1, + find_objectid/1 +]). %% gen_server. --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -define(LWM2M_OBJECT_DEF_TAB, lwm2m_object_def_tab). -define(LWM2M_OBJECT_NAME_TO_ID_TAB, lwm2m_object_name_to_id_tab). @@ -47,29 +49,31 @@ %% API Function Definitions %% ------------------------------------------------------------------ --spec start_link(string()) - -> {ok, pid()} - | ignore - | {error, no_xml_files_found} - | {error, term()}. +-spec start_link(string()) -> + {ok, pid()} + | ignore + | {error, no_xml_files_found} + | {error, term()}. start_link(XmlDir) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [XmlDir], []). find_objectid(ObjectId) -> - ObjectIdInt = case is_list(ObjectId) of - true -> list_to_integer(ObjectId); - false -> ObjectId - end, + ObjectIdInt = + case is_list(ObjectId) of + true -> list_to_integer(ObjectId); + false -> ObjectId + end, case ets:lookup(?LWM2M_OBJECT_DEF_TAB, ObjectIdInt) of [] -> {error, no_xml_definition}; [{ObjectId, Xml}] -> Xml end. find_name(Name) -> - NameBinary = case is_list(Name) of - true -> list_to_binary(Name); - false -> Name - end, + NameBinary = + case is_list(Name) of + true -> list_to_binary(Name); + false -> Name + end, case ets:lookup(?LWM2M_OBJECT_NAME_TO_ID_TAB, NameBinary) of [] -> undefined; @@ -93,7 +97,8 @@ init([XmlDir]) -> case load(XmlDir) of ok -> {ok, #state{}}; - {error, Reason} -> {stop, Reason} + {error, Reason} -> + {stop, Reason} end. handle_call(_Request, _From, State) -> @@ -118,11 +123,13 @@ code_change(_OldVsn, State, _Extra) -> %%-------------------------------------------------------------------- load(BaseDir) -> Wild = filename:join(BaseDir, "*.xml"), - Wild2 = if is_binary(Wild) -> - erlang:binary_to_list(Wild); - true -> - Wild - end, + Wild2 = + if + is_binary(Wild) -> + erlang:binary_to_list(Wild); + true -> + Wild + end, case filelib:wildcard(Wild2) of [] -> {error, no_xml_files_found}; AllXmlFiles -> load_loop(AllXmlFiles) @@ -130,17 +137,18 @@ load(BaseDir) -> load_loop([]) -> ok; -load_loop([FileName|T]) -> +load_loop([FileName | T]) -> ObjectXml = load_xml(FileName), - [#xmlText{value=ObjectIdString}] = xmerl_xpath:string("ObjectID/text()", ObjectXml), - [#xmlText{value=Name}] = xmerl_xpath:string("Name/text()", ObjectXml), + [#xmlText{value = ObjectIdString}] = xmerl_xpath:string("ObjectID/text()", ObjectXml), + [#xmlText{value = Name}] = xmerl_xpath:string("Name/text()", ObjectXml), ObjectId = list_to_integer(ObjectIdString), NameBinary = list_to_binary(Name), - ?SLOG(debug, #{ msg => "load_object_succeed" - , filename => FileName - , object_id => ObjectId - , object_name => NameBinary - }), + ?SLOG(debug, #{ + msg => "load_object_succeed", + filename => FileName, + object_id => ObjectId, + object_name => NameBinary + }), ets:insert(?LWM2M_OBJECT_DEF_TAB, {ObjectId, ObjectXml}), ets:insert(?LWM2M_OBJECT_NAME_TO_ID_TAB, {NameBinary, ObjectId}), load_loop(T). diff --git a/apps/emqx_gateway/src/lwm2m/include/emqx_lwm2m.hrl b/apps/emqx_gateway/src/lwm2m/include/emqx_lwm2m.hrl index 6a5f44acc..dfb5c5bc2 100644 --- a/apps/emqx_gateway/src/lwm2m/include/emqx_lwm2m.hrl +++ b/apps/emqx_gateway/src/lwm2m/include/emqx_lwm2m.hrl @@ -16,29 +16,28 @@ -define(LWAPP, emqx_lwm2m). - -define(OMA_ALTER_PATH_RT, <<"\"oma.lwm2m\"">>). --define(MQ_COMMAND_ID, <<"CmdID">>). --define(MQ_COMMAND, <<"requestID">>). --define(MQ_BASENAME, <<"BaseName">>). --define(MQ_ARGS, <<"Arguments">>). +-define(MQ_COMMAND_ID, <<"CmdID">>). +-define(MQ_COMMAND, <<"requestID">>). +-define(MQ_BASENAME, <<"BaseName">>). +-define(MQ_ARGS, <<"Arguments">>). --define(MQ_VALUE_TYPE, <<"ValueType">>). --define(MQ_VALUE, <<"Value">>). --define(MQ_ERROR, <<"Error">>). --define(MQ_RESULT, <<"Result">>). +-define(MQ_VALUE_TYPE, <<"ValueType">>). +-define(MQ_VALUE, <<"Value">>). +-define(MQ_ERROR, <<"Error">>). +-define(MQ_RESULT, <<"Result">>). --define(ERR_NO_XML, <<"No XML Definition">>). --define(ERR_NOT_ACCEPTABLE, <<"Not Acceptable">>). +-define(ERR_NO_XML, <<"No XML Definition">>). +-define(ERR_NOT_ACCEPTABLE, <<"Not Acceptable">>). -define(ERR_METHOD_NOT_ALLOWED, <<"Method Not Allowed">>). --define(ERR_NOT_FOUND, <<"Not Found">>). --define(ERR_UNAUTHORIZED, <<"Unauthorized">>). --define(ERR_BAD_REQUEST, <<"Bad Request">>). +-define(ERR_NOT_FOUND, <<"Not Found">>). +-define(ERR_UNAUTHORIZED, <<"Unauthorized">>). +-define(ERR_BAD_REQUEST, <<"Bad Request">>). -define(REG_PREFIX, <<"rd">>). -define(LWM2M_FORMAT_PLAIN_TEXT, 0). --define(LWM2M_FORMAT_LINK, 40). --define(LWM2M_FORMAT_OPAQUE, 42). --define(LWM2M_FORMAT_TLV, 11542). --define(LWMWM_FORMAT_JSON, 11543). +-define(LWM2M_FORMAT_LINK, 40). +-define(LWM2M_FORMAT_OPAQUE, 42). +-define(LWM2M_FORMAT_TLV, 11542). +-define(LWMWM_FORMAT_JSON, 11543). diff --git a/apps/emqx_gateway/src/mqttsn/emqx_sn_broadcast.erl b/apps/emqx_gateway/src/mqttsn/emqx_sn_broadcast.erl index 1f2e43882..ecde268ba 100644 --- a/apps/emqx_gateway/src/mqttsn/emqx_sn_broadcast.erl +++ b/apps/emqx_gateway/src/mqttsn/emqx_sn_broadcast.erl @@ -21,28 +21,35 @@ -include("src/mqttsn/include/emqx_sn.hrl"). -include_lib("emqx/include/logger.hrl"). --export([ start_link/2 - , stop/0 - ]). +-export([ + start_link/2, + stop/0 +]). %% gen_server --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -record(state, {gwid, sock, port, addrs, duration, tref}). --define(DEFAULT_DURATION, 15*60*1000). +-define(DEFAULT_DURATION, 15 * 60 * 1000). %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- --spec(start_link(pos_integer(), inet:port_number()) - -> {ok, pid()} | {error, term()}). +-spec start_link(pos_integer(), inet:port_number()) -> + {ok, pid()} | {error, term()}. start_link(GwId, Port) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [GwId, Port], []). + gen_server:start_link({local, ?MODULE}, ?MODULE, [GwId, Port], []). --spec(stop() -> ok). +-spec stop() -> ok. stop() -> gen_server:stop(?MODULE, nomal, infinity). @@ -54,36 +61,44 @@ init([GwId, Port]) -> %% FIXME: Duration = application:get_env(emqx_sn, advertise_duration, ?DEFAULT_DURATION), {ok, Sock} = gen_udp:open(0, [binary, {broadcast, true}]), - {ok, ensure_advertise(#state{gwid = GwId, addrs = boradcast_addrs(), - sock = Sock, port = Port, duration = Duration})}. + {ok, + ensure_advertise(#state{ + gwid = GwId, + addrs = boradcast_addrs(), + sock = Sock, + port = Port, + duration = Duration + })}. handle_call(Req, _From, State) -> - ?SLOG(error, #{ msg => "unexpected_call" - , call => Req - }), - {reply, ignored, State}. + ?SLOG(error, #{ + msg => "unexpected_call", + call => Req + }), + {reply, ignored, State}. handle_cast(Msg, State) -> - ?SLOG(error, #{ msg => "unexpected_cast" - , cast => Msg - }), - {noreply, State}. + ?SLOG(error, #{ + msg => "unexpected_cast", + cast => Msg + }), + {noreply, State}. handle_info(broadcast_advertise, State) -> {noreply, ensure_advertise(State), hibernate}; - handle_info(Info, State) -> - ?SLOG(error, #{ msg => "unexpected_info" - , info => Info - }), - {noreply, State}. + ?SLOG(error, #{ + msg => "unexpected_info", + info => Info + }), + {noreply, State}. terminate(_Reason, #state{tref = Timer}) -> _ = erlang:cancel_timer(Timer), ok. code_change(_OldVsn, State, _Extra) -> - {ok, State}. + {ok, State}. %%-------------------------------------------------------------------- %% Internal Functions @@ -93,17 +108,29 @@ ensure_advertise(State = #state{duration = Duration}) -> send_advertise(State), State#state{tref = erlang:send_after(Duration, self(), broadcast_advertise)}. -send_advertise(#state{gwid = GwId, sock = Sock, port = Port, - addrs = Addrs, duration = Duration}) -> +send_advertise(#state{ + gwid = GwId, + sock = Sock, + port = Port, + addrs = Addrs, + duration = Duration +}) -> Data = emqx_sn_frame:serialize_pkt(?SN_ADVERTISE_MSG(GwId, Duration), #{}), - lists:foreach(fun(Addr) -> - ?SLOG(debug, #{ msg => "send_ADVERTISE_msg" - , address => Addr - }), - gen_udp:send(Sock, Addr, Port, Data) - end, Addrs). + lists:foreach( + fun(Addr) -> + ?SLOG(debug, #{ + msg => "send_ADVERTISE_msg", + address => Addr + }), + gen_udp:send(Sock, Addr, Port, Data) + end, + Addrs + ). boradcast_addrs() -> - lists:usort([Addr || {ok, IfList} <- [inet:getiflist()], If <- IfList, - {ok, [{broadaddr, Addr}]} <- [inet:ifget(If, [broadaddr])]]). - + lists:usort([ + Addr + || {ok, IfList} <- [inet:getiflist()], + If <- IfList, + {ok, [{broadaddr, Addr}]} <- [inet:ifget(If, [broadaddr])] + ]). diff --git a/apps/emqx_gateway/src/mqttsn/emqx_sn_channel.erl b/apps/emqx_gateway/src/mqttsn/emqx_sn_channel.erl index 4a6c9a9f8..804b5c265 100644 --- a/apps/emqx_gateway/src/mqttsn/emqx_sn_channel.erl +++ b/apps/emqx_gateway/src/mqttsn/emqx_sn_channel.erl @@ -25,171 +25,190 @@ -include_lib("emqx/include/logger.hrl"). %% API --export([ info/1 - , info/2 - , stats/1 - ]). +-export([ + info/1, + info/2, + stats/1 +]). --export([ init/2 - , handle_in/2 - , handle_out/3 - , handle_deliver/2 - , handle_timeout/3 - , terminate/2 - , set_conn_state/2 - ]). +-export([ + init/2, + handle_in/2, + handle_out/3, + handle_deliver/2, + handle_timeout/3, + terminate/2, + set_conn_state/2 +]). --export([ handle_call/3 - , handle_cast/2 - , handle_info/2 - ]). +-export([ + handle_call/3, + handle_cast/2, + handle_info/2 +]). -record(channel, { - %% Context - ctx :: emqx_gateway_ctx:context(), - %% Registry - registry :: emqx_sn_registry:registry(), - %% Gateway Id - gateway_id :: integer(), - %% Enable QoS3 - enable_qos3 :: boolean(), %% XXX: Get confs from ctx ? - %% MQTT-SN Connection Info - conninfo :: emqx_types:conninfo(), - %% MQTT-SN Client Info - clientinfo :: emqx_types:clientinfo(), - %% Session - session :: emqx_session:session() | undefined, - %% Keepalive - keepalive :: emqx_keepalive:keepalive() | undefined, - %% Will Msg - will_msg :: emqx_types:message() | undefined, - %% ClientInfo override specs - clientinfo_override :: map(), - %% Connection State - conn_state :: conn_state(), - %% Inflight register message queue - register_inflight :: maybe(term()), - %% Topics list for awaiting to register to client - register_awaiting_queue :: list(), - %% Duration for asleep - asleep_timer_duration :: integer() | undefined, - %% Timer - timers :: #{atom() => disable | undefined | reference()}, - %%% Takeover - takeover :: boolean(), - %% Resume - resuming :: boolean(), - %% Pending delivers when takeovering - pendings :: list() - }). + %% Context + ctx :: emqx_gateway_ctx:context(), + %% Registry + registry :: emqx_sn_registry:registry(), + %% Gateway Id + gateway_id :: integer(), + %% Enable QoS3 + + %% XXX: Get confs from ctx ? + enable_qos3 :: boolean(), + %% MQTT-SN Connection Info + conninfo :: emqx_types:conninfo(), + %% MQTT-SN Client Info + clientinfo :: emqx_types:clientinfo(), + %% Session + session :: emqx_session:session() | undefined, + %% Keepalive + keepalive :: emqx_keepalive:keepalive() | undefined, + %% Will Msg + will_msg :: emqx_types:message() | undefined, + %% ClientInfo override specs + clientinfo_override :: map(), + %% Connection State + conn_state :: conn_state(), + %% Inflight register message queue + register_inflight :: maybe(term()), + %% Topics list for awaiting to register to client + register_awaiting_queue :: list(), + %% Duration for asleep + asleep_timer_duration :: integer() | undefined, + %% Timer + timers :: #{atom() => disable | undefined | reference()}, + %%% Takeover + takeover :: boolean(), + %% Resume + resuming :: boolean(), + %% Pending delivers when takeovering + pendings :: list() +}). -type channel() :: #channel{}. --type conn_state() :: idle | connecting | connected | asleep | awake - | disconnected. +-type conn_state() :: + idle + | connecting + | connected + | asleep + | awake + | disconnected. --type reply() :: {outgoing, mqtt_sn_message()} - | {outgoing, [mqtt_sn_message()]} - | {event, conn_state()|updated} - | {close, Reason :: atom()}. +-type reply() :: + {outgoing, mqtt_sn_message()} + | {outgoing, [mqtt_sn_message()]} + | {event, conn_state() | updated} + | {close, Reason :: atom()}. -type replies() :: reply() | [reply()]. -define(TIMER_TABLE, #{ - alive_timer => keepalive, - retry_timer => retry_delivery, - await_timer => expire_awaiting_rel, - expire_timer => expire_session, - asleep_timer => expire_asleep, - register_timer => retry_register - }). + alive_timer => keepalive, + retry_timer => retry_delivery, + await_timer => expire_awaiting_rel, + expire_timer => expire_session, + asleep_timer => expire_asleep, + register_timer => retry_register +}). --define(DEFAULT_OVERRIDE, - #{ clientid => <<"${ConnInfo.clientid}">> - %, username => <<"${ConnInfo.clientid}">> - %, password => <<"${Packet.headers.passcode}">> - }). +-define(DEFAULT_OVERRIDE, #{ + clientid => <<"${ConnInfo.clientid}">> + %, username => <<"${ConnInfo.clientid}">> + %, password => <<"${Packet.headers.passcode}">> +}). -define(INFO_KEYS, [conninfo, conn_state, clientinfo, session, will_msg]). -define(NEG_QOS_CLIENT_ID, <<"NegQoS-Client">>). --define(REGISTER_INFLIGHT(TopicId, TopicName), - #channel{register_inflight = {TopicId, _, TopicName}}). +-define(REGISTER_INFLIGHT(TopicId, TopicName), #channel{register_inflight = {TopicId, _, TopicName}}). -define(MAX_RETRY_TIMES, 3). --define(REGISTER_TIMEOUT, 5000). % 5s --define(DEFAULT_SESSION_EXPIRY, 7200000). %% 2h +% 5s +-define(REGISTER_TIMEOUT, 5000). +%% 2h +-define(DEFAULT_SESSION_EXPIRY, 7200000). %%-------------------------------------------------------------------- %% Init the channel %%-------------------------------------------------------------------- %% @doc Init protocol -init(ConnInfo = #{peername := {PeerHost, _}, - sockname := {_, SockPort}}, Option) -> +init( + ConnInfo = #{ + peername := {PeerHost, _}, + sockname := {_, SockPort} + }, + Option +) -> Peercert = maps:get(peercert, ConnInfo, undefined), Mountpoint = maps:get(mountpoint, Option, undefined), Registry = maps:get(registry, Option), GwId = maps:get(gateway_id, Option), EnableQoS3 = maps:get(enable_qos3, Option, true), - ListenerId = case maps:get(listener, Option, undefined) of - undefined -> undefined; - {GwName, Type, LisName} -> - emqx_gateway_utils:listener_id(GwName, Type, LisName) - end, + ListenerId = + case maps:get(listener, Option, undefined) of + undefined -> undefined; + {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName) + end, ClientInfo = set_peercert_infos( - Peercert, - #{ zone => default - , listener => ListenerId - , protocol => 'mqtt-sn' - , peerhost => PeerHost - , sockport => SockPort - , clientid => undefined - , username => undefined - , is_bridge => false - , is_superuser => false - , mountpoint => Mountpoint - } - ), + Peercert, + #{ + zone => default, + listener => ListenerId, + protocol => 'mqtt-sn', + peerhost => PeerHost, + sockport => SockPort, + clientid => undefined, + username => undefined, + is_bridge => false, + is_superuser => false, + mountpoint => Mountpoint + } + ), Ctx = maps:get(ctx, Option), - Override = maps:merge(?DEFAULT_OVERRIDE, - maps:get(clientinfo_override, Option, #{}) - ), - #channel{ ctx = Ctx - , registry = Registry - , gateway_id = GwId - , enable_qos3 = EnableQoS3 - , conninfo = ConnInfo - , clientinfo = ClientInfo - , clientinfo_override = Override - , conn_state = idle - , register_awaiting_queue = [] - , timers = #{} - , takeover = false - , resuming = false - , pendings = [] - }. + Override = maps:merge( + ?DEFAULT_OVERRIDE, + maps:get(clientinfo_override, Option, #{}) + ), + #channel{ + ctx = Ctx, + registry = Registry, + gateway_id = GwId, + enable_qos3 = EnableQoS3, + conninfo = ConnInfo, + clientinfo = ClientInfo, + clientinfo_override = Override, + conn_state = idle, + register_awaiting_queue = [], + timers = #{}, + takeover = false, + resuming = false, + pendings = [] + }. -set_peercert_infos(NoSSL, ClientInfo) - when NoSSL =:= nossl; - NoSSL =:= undefined -> +set_peercert_infos(NoSSL, ClientInfo) when + NoSSL =:= nossl; + NoSSL =:= undefined +-> ClientInfo; set_peercert_infos(Peercert, ClientInfo) -> - {DN, CN} = {esockd_peercert:subject(Peercert), - esockd_peercert:common_name(Peercert)}, + {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)}, ClientInfo#{dn => DN, cn => CN}. -spec info(channel()) -> emqx_types:infos(). info(Channel) -> maps:from_list(info(?INFO_KEYS, Channel)). --spec info(list(atom())|atom(), channel()) -> term(). +-spec info(list(atom()) | atom(), channel()) -> term(). info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; - info(conninfo, #channel{conninfo = ConnInfo}) -> ConnInfo; info(conn_state, #channel{conn_state = ConnState}) -> @@ -205,25 +224,28 @@ info(clientid, #channel{clientinfo = #{clientid := ClientId}}) -> info(ctx, #channel{ctx = Ctx}) -> Ctx. --spec(stats(channel()) -> emqx_types:stats()). -stats(#channel{session = undefined})-> +-spec stats(channel()) -> emqx_types:stats(). +stats(#channel{session = undefined}) -> []; -stats(#channel{session = Session})-> +stats(#channel{session = Session}) -> emqx_session:stats(Session). set_conn_state(ConnState, Channel) -> Channel#channel{conn_state = ConnState}. -enrich_conninfo(?SN_CONNECT_MSG(Flags, _ProtoId, Duration, ClientId), - Channel = #channel{conninfo = ConnInfo}) -> +enrich_conninfo( + ?SN_CONNECT_MSG(Flags, _ProtoId, Duration, ClientId), + Channel = #channel{conninfo = ConnInfo} +) -> CleanStart = Flags#mqtt_sn_flags.clean_start, - NConnInfo = ConnInfo#{ clientid => ClientId - , proto_name => <<"MQTT-SN">> - , proto_ver => <<"1.2">> - , clean_start => CleanStart - , keepalive => Duration - , expiry_interval => expiry_interval(Flags) - }, + NConnInfo = ConnInfo#{ + clientid => ClientId, + proto_name => <<"MQTT-SN">>, + proto_ver => <<"1.2">>, + clean_start => CleanStart, + keepalive => Duration, + expiry_interval => expiry_interval(Flags) + }, {ok, Channel#channel{conninfo = NConnInfo}}. expiry_interval(#mqtt_sn_flags{clean_start = false}) -> @@ -232,42 +254,56 @@ expiry_interval(#mqtt_sn_flags{clean_start = false}) -> expiry_interval(#mqtt_sn_flags{clean_start = true}) -> 0. -run_conn_hooks(Packet, Channel = #channel{ctx = Ctx, - conninfo = ConnInfo}) -> +run_conn_hooks( + Packet, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo + } +) -> %% XXX: Assign headers of Packet to ConnProps ConnProps = #{}, case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of Error = {error, _Reason} -> Error; - _NConnProps -> - {ok, Packet, Channel} + _NConnProps -> {ok, Packet, Channel} end. -enrich_clientinfo(Packet, - Channel = #channel{ - conninfo = ConnInfo, - clientinfo = ClientInfo0, - clientinfo_override = Override}) -> +enrich_clientinfo( + Packet, + Channel = #channel{ + conninfo = ConnInfo, + clientinfo = ClientInfo0, + clientinfo_override = Override + } +) -> ClientInfo = write_clientinfo( - feedvar(Override, Packet, ConnInfo, ClientInfo0), - ClientInfo0 - ), + feedvar(Override, Packet, ConnInfo, ClientInfo0), + ClientInfo0 + ), {ok, NPacket, NClientInfo} = emqx_misc:pipeline( - [ fun maybe_assign_clientid/2 - %% FIXME: CALL After authentication successfully - , fun fix_mountpoint/2 - ], Packet, ClientInfo - ), + [ + fun maybe_assign_clientid/2, + %% FIXME: CALL After authentication successfully + fun fix_mountpoint/2 + ], + Packet, + ClientInfo + ), {ok, NPacket, Channel#channel{clientinfo = NClientInfo}}. feedvar(Override, Packet, ConnInfo, ClientInfo) -> - Envs = #{ 'ConnInfo' => ConnInfo - , 'ClientInfo' => ClientInfo - , 'Packet' => connect_packet_to_map(Packet) - }, - maps:map(fun(_K, V) -> - Tokens = emqx_plugin_libs_rule:preproc_tmpl(V), - emqx_plugin_libs_rule:proc_tmpl(Tokens, Envs) - end, Override). + Envs = #{ + 'ConnInfo' => ConnInfo, + 'ClientInfo' => ClientInfo, + 'Packet' => connect_packet_to_map(Packet) + }, + maps:map( + fun(_K, V) -> + Tokens = emqx_plugin_libs_rule:preproc_tmpl(V), + emqx_plugin_libs_rule:proc_tmpl(Tokens, Envs) + end, + Override + ). connect_packet_to_map(#mqtt_sn_message{}) -> %% XXX: Empty now @@ -277,15 +313,16 @@ write_clientinfo(Override, ClientInfo) -> Override1 = maps:with([username, password, clientid], Override), maps:merge(ClientInfo, Override1). -maybe_assign_clientid(_Packet, ClientInfo = #{clientid := ClientId}) - when ClientId == undefined; - ClientId == <<>> -> +maybe_assign_clientid(_Packet, ClientInfo = #{clientid := ClientId}) when + ClientId == undefined; + ClientId == <<>> +-> {ok, ClientInfo#{clientid => emqx_guid:to_base62(emqx_guid:gen())}}; - maybe_assign_clientid(_Packet, ClientInfo) -> {ok, ClientInfo}. -fix_mountpoint(_Packet, #{mountpoint := undefined}) -> ok; +fix_mountpoint(_Packet, #{mountpoint := undefined}) -> + ok; fix_mountpoint(_Packet, ClientInfo = #{mountpoint := Mountpoint}) -> %% TODO: Enrich the variable replacement???? %% i.e: ${ClientInfo.auth_result.productKey} @@ -305,61 +342,84 @@ maybe_require_will_msg(?SN_CONNECT_MSG(Flags, _, _, _), Channel) -> ok end. -auth_connect(_Packet, Channel = #channel{ctx = Ctx, - clientinfo = ClientInfo}) -> - #{clientid := ClientId, - username := Username} = ClientInfo, +auth_connect( + _Packet, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> + #{ + clientid := ClientId, + username := Username + } = ClientInfo, case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of {ok, NClientInfo} -> {ok, Channel#channel{clientinfo = NClientInfo}}; {error, Reason} -> - ?SLOG(warning, #{ msg => "client_login_failed" - , clientid => ClientId - , username => Username - , reason => Reason - }), + ?SLOG(warning, #{ + msg => "client_login_failed", + clientid => ClientId, + username => Username, + reason => Reason + }), %% FIXME: ReasonCode? {error, Reason} end. -ensure_connected(Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_connected( + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)}, ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), - Channel#channel{conninfo = NConnInfo, - conn_state = connected - }. + Channel#channel{ + conninfo = NConnInfo, + conn_state = connected + }. -process_connect(Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo = #{clean_start := CleanStart}, - clientinfo = ClientInfo - }) -> - SessFun = fun(_,_) -> emqx_session:init(#{max_inflight => 1}) end, - case emqx_gateway_ctx:open_session( - Ctx, - CleanStart, - ClientInfo, - ConnInfo, - SessFun - ) of - {ok, #{session := Session, - present := false}} -> - handle_out(connack, ?SN_RC_ACCEPTED, - Channel#channel{session = Session}); +process_connect( + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo = #{clean_start := CleanStart}, + clientinfo = ClientInfo + } +) -> + SessFun = fun(_, _) -> emqx_session:init(#{max_inflight => 1}) end, + case + emqx_gateway_ctx:open_session( + Ctx, + CleanStart, + ClientInfo, + ConnInfo, + SessFun + ) + of + {ok, #{ + session := Session, + present := false + }} -> + handle_out( + connack, + ?SN_RC_ACCEPTED, + Channel#channel{session = Session} + ); {ok, #{session := Session, present := true, pendings := Pendings}} -> Pendings1 = lists:usort(lists:append(Pendings, emqx_misc:drain_deliver())), - NChannel = Channel#channel{session = Session, - resuming = true, - pendings = Pendings1 - }, + NChannel = Channel#channel{ + session = Session, + resuming = true, + pendings = Pendings1 + }, handle_out(connack, ?SN_RC_ACCEPTED, NChannel); {error, Reason} -> - ?SLOG(error, #{ msg => "failed_to_open_session" - , reason => Reason - }), + ?SLOG(error, #{ + msg => "failed_to_open_session", + reason => Reason + }), handle_out(connack, ?SN_RC2_FAILED_SESSION, Channel) end. @@ -369,7 +429,8 @@ process_connect(Channel = #channel{ ensure_keepalive(Channel = #channel{conninfo = ConnInfo}) -> ensure_keepalive_timer(maps:get(keepalive, ConnInfo), Channel). -ensure_keepalive_timer(0, Channel) -> Channel; +ensure_keepalive_timer(0, Channel) -> + Channel; ensure_keepalive_timer(Interval, Channel) -> Keepalive = emqx_keepalive:init(round(timer:seconds(Interval))), ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}). @@ -378,75 +439,90 @@ ensure_keepalive_timer(Interval, Channel) -> %% Handle incoming packet %%-------------------------------------------------------------------- --spec handle_in(emqx_types:packet() | {frame_error, any()}, channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()} - | {shutdown, Reason :: term(), replies(), channel()}. +-spec handle_in(emqx_types:packet() | {frame_error, any()}, channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), replies(), channel()}. %% SEARCHGW, GWINFO -handle_in(?SN_SEARCHGW_MSG(_Radius), - Channel = #channel{gateway_id = GwId}) -> +handle_in( + ?SN_SEARCHGW_MSG(_Radius), + Channel = #channel{gateway_id = GwId} +) -> {ok, {outgoing, ?SN_GWINFO_MSG(GwId, <<>>)}, Channel}; - handle_in(?SN_ADVERTISE_MSG(_GwId, _Radius), Channel) -> % ignore shutdown(normal, Channel); - -handle_in(?SN_PUBLISH_MSG(#mqtt_sn_flags{qos = ?QOS_NEG1, - topic_id_type = TopicIdType - }, - TopicId, _MsgId, Data), - Channel = #channel{conn_state = idle, registry = Registry}) -> +handle_in( + ?SN_PUBLISH_MSG( + #mqtt_sn_flags{ + qos = ?QOS_NEG1, + topic_id_type = TopicIdType + }, + TopicId, + _MsgId, + Data + ), + Channel = #channel{conn_state = idle, registry = Registry} +) -> %% FIXME: check enable_qos3 ?? - TopicName = case (TopicIdType =:= ?SN_SHORT_TOPIC) of - true -> - <>; - false -> - emqx_sn_registry:lookup_topic( - Registry, - ?NEG_QOS_CLIENT_ID, - TopicId - ) - end, - _ = case TopicName =/= undefined of - true -> - Msg = emqx_message:make( + TopicName = + case (TopicIdType =:= ?SN_SHORT_TOPIC) of + true -> + <>; + false -> + emqx_sn_registry:lookup_topic( + Registry, + ?NEG_QOS_CLIENT_ID, + TopicId + ) + end, + _ = + case TopicName =/= undefined of + true -> + Msg = emqx_message:make( ?NEG_QOS_CLIENT_ID, ?QOS_0, TopicName, Data - ), - emqx_broker:publish(Msg); - false -> - ok - end, - ?SLOG(debug, #{ msg => "receive_qo3_message_in_idle_mode" - , topic => TopicName - , data => Data - }), + ), + emqx_broker:publish(Msg); + false -> + ok + end, + ?SLOG(debug, #{ + msg => "receive_qo3_message_in_idle_mode", + topic => TopicName, + data => Data + }), {ok, Channel}; - -handle_in(Pkt = #mqtt_sn_message{type = Type}, - Channel = #channel{conn_state = idle}) - when Type /= ?SN_CONNECT -> - ?SLOG(warning, #{ msg => "receive_unknown_packet_in_idle_state" - , packet => Pkt - }), +handle_in( + Pkt = #mqtt_sn_message{type = Type}, + Channel = #channel{conn_state = idle} +) when + Type /= ?SN_CONNECT +-> + ?SLOG(warning, #{ + msg => "receive_unknown_packet_in_idle_state", + packet => Pkt + }), shutdown(normal, Channel); - -handle_in(?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId), - Channel = #channel{conn_state = connecting}) -> - ?SLOG(warning, #{ msg => "receive_connect_packet_in_connecting_state" - }), +handle_in( + ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId), + Channel = #channel{conn_state = connecting} +) -> + ?SLOG(warning, #{msg => "receive_connect_packet_in_connecting_state"}), {ok, Channel}; - -handle_in(?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId), - Channel = #channel{conn_state = connected}) -> +handle_in( + ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId), + Channel = #channel{conn_state = connected} +) -> {error, unexpected_connect, Channel}; - -handle_in(?SN_WILLTOPIC_EMPTY_MSG, - Channel = #channel{conn_state = connecting}) -> +handle_in( + ?SN_WILLTOPIC_EMPTY_MSG, + Channel = #channel{conn_state = connecting} +) -> %% 6.3: %% Note that if a client wants to delete only its Will data at %% connection setup, it could send a CONNECT message with @@ -458,19 +534,25 @@ handle_in(?SN_WILLTOPIC_EMPTY_MSG, {error, ReasonCode} -> handle_out(connack, ReasonCode, Channel) end; - -handle_in(?SN_WILLTOPIC_MSG(Flags, Topic), - Channel = #channel{conn_state = connecting, - clientinfo = #{clientid := ClientId}}) -> +handle_in( + ?SN_WILLTOPIC_MSG(Flags, Topic), + Channel = #channel{ + conn_state = connecting, + clientinfo = #{clientid := ClientId} + } +) -> #mqtt_sn_flags{qos = QoS, retain = Retain} = Flags, WillMsg0 = emqx_message:make(ClientId, QoS, Topic, <<>>), WillMsg = emqx_message:set_flag(retain, Retain, WillMsg0), NChannel = Channel#channel{will_msg = WillMsg}, {ok, {outgoing, ?SN_WILLMSGREQ_MSG()}, NChannel}; - -handle_in(?SN_WILLMSG_MSG(Payload), - Channel = #channel{conn_state = connecting, - will_msg = WillMsg}) -> +handle_in( + ?SN_WILLMSG_MSG(Payload), + Channel = #channel{ + conn_state = connecting, + will_msg = WillMsg + } +) -> NWillMsg = WillMsg#message{payload = Payload}, case auth_connect(fake_packet, Channel#channel{will_msg = NWillMsg}) of {ok, NChannel} -> @@ -478,36 +560,50 @@ handle_in(?SN_WILLMSG_MSG(Payload), {error, ReasonCode} -> handle_out(connack, ReasonCode, Channel) end; - %% TODO: takeover ??? -handle_in(?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, ClientId), - Channel = #channel{ - clientinfo = #{clientid := ClientId}, - conn_state = ConnState}) - when ConnState == asleep; - ConnState == awake -> +handle_in( + ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, ClientId), + Channel = #channel{ + clientinfo = #{clientid := ClientId}, + conn_state = ConnState + } +) when + ConnState == asleep; + ConnState == awake +-> %% From the asleep or awake state a client can return either to the %% active state by sending a CONNECT message [6.14] - ?SLOG(info, #{ msg => "goto_connected_state" - , previous_state => ConnState - , clientid => ClientId - }), - handle_out(connack, ?SN_RC_ACCEPTED, - Channel#channel{conn_state = connected}); - + ?SLOG(info, #{ + msg => "goto_connected_state", + previous_state => ConnState, + clientid => ClientId + }), + handle_out( + connack, + ?SN_RC_ACCEPTED, + Channel#channel{conn_state = connected} + ); %% new connection -handle_in(Packet = ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId), - Channel = #channel{conn_state = idle}) -> - case emqx_misc:pipeline( - [ fun enrich_conninfo/2 - , fun run_conn_hooks/2 - , fun enrich_clientinfo/2 - , fun set_log_meta/2 - %% TODO: How to implement the banned in the gateway instance? - %, fun check_banned/2 - , fun maybe_require_will_msg/2 - , fun auth_connect/2 - ], Packet, Channel#channel{conn_state = connecting}) of +handle_in( + Packet = ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId), + Channel = #channel{conn_state = idle} +) -> + case + emqx_misc:pipeline( + [ + fun enrich_conninfo/2, + fun run_conn_hooks/2, + fun enrich_clientinfo/2, + fun set_log_meta/2, + %% TODO: How to implement the banned in the gateway instance? + %, fun check_banned/2 + fun maybe_require_will_msg/2, + fun auth_connect/2 + ], + Packet, + Channel#channel{conn_state = connecting} + ) + of {ok, _NPacket, NChannel} -> process_connect(ensure_connected(NChannel)); {error, need_will_msg, NChannel} -> @@ -515,55 +611,65 @@ handle_in(Packet = ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId), {error, ReasonCode, NChannel} -> handle_out(connack, ReasonCode, NChannel) end; - -handle_in(?SN_REGISTER_MSG(_TopicId, MsgId, TopicName), - Channel = #channel{registry = Registry, - clientinfo = #{clientid := ClientId}}) -> +handle_in( + ?SN_REGISTER_MSG(_TopicId, MsgId, TopicName), + Channel = #channel{ + registry = Registry, + clientinfo = #{clientid := ClientId} + } +) -> case emqx_sn_registry:register_topic(Registry, ClientId, TopicName) of TopicId when is_integer(TopicId) -> - ?SLOG(debug, #{ msg => "registered_topic_name" - , topic_name => TopicName - , topic_id => TopicId - }), + ?SLOG(debug, #{ + msg => "registered_topic_name", + topic_name => TopicName, + topic_id => TopicId + }), AckPacket = ?SN_REGACK_MSG(TopicId, MsgId, ?SN_RC_ACCEPTED), {ok, {outgoing, AckPacket}, Channel}; {error, too_large} -> - ?SLOG(error, #{ msg => "register_topic_failed" - , topic_name => TopicName - , reason => topic_id_fulled - }), + ?SLOG(error, #{ + msg => "register_topic_failed", + topic_name => TopicName, + reason => topic_id_fulled + }), AckPacket = ?SN_REGACK_MSG( - ?SN_INVALID_TOPIC_ID, - MsgId, - ?SN_RC_NOT_SUPPORTED - ), + ?SN_INVALID_TOPIC_ID, + MsgId, + ?SN_RC_NOT_SUPPORTED + ), {ok, {outgoing, AckPacket}, Channel}; {error, wildcard_topic} -> - ?SLOG(error, #{ msg => "register_topic_failed" - , topic_name => TopicName - , reason => not_support_wildcard_topic - }), + ?SLOG(error, #{ + msg => "register_topic_failed", + topic_name => TopicName, + reason => not_support_wildcard_topic + }), AckPacket = ?SN_REGACK_MSG( - ?SN_INVALID_TOPIC_ID, - MsgId, - ?SN_RC_NOT_SUPPORTED - ), + ?SN_INVALID_TOPIC_ID, + MsgId, + ?SN_RC_NOT_SUPPORTED + ), {ok, {outgoing, AckPacket}, Channel} end; - -handle_in(?SN_REGACK_MSG(TopicId, _MsgId, ?SN_RC_ACCEPTED), - Channel = ?REGISTER_INFLIGHT(TopicId, TopicName)) -> - ?SLOG(debug, #{ msg => "register_topic_name_to_client_succesfully" - , topic_id => TopicId - , topic_name => TopicName - }), +handle_in( + ?SN_REGACK_MSG(TopicId, _MsgId, ?SN_RC_ACCEPTED), + Channel = ?REGISTER_INFLIGHT(TopicId, TopicName) +) -> + ?SLOG(debug, #{ + msg => "register_topic_name_to_client_succesfully", + topic_id => TopicId, + topic_name => TopicName + }), NChannel = cancel_timer( - register_timer, - Channel#channel{register_inflight = undefined}), + register_timer, + Channel#channel{register_inflight = undefined} + ), send_next_register_or_replay_publish(TopicName, NChannel); - -handle_in(?SN_REGACK_MSG(TopicId, _MsgId, Reason), - Channel = ?REGISTER_INFLIGHT(TopicId, TopicName)) -> +handle_in( + ?SN_REGACK_MSG(TopicId, _MsgId, Reason), + Channel = ?REGISTER_INFLIGHT(TopicId, TopicName) +) -> case Reason of ?SN_RC_CONGESTION -> %% TODO: a or b? @@ -573,76 +679,95 @@ handle_in(?SN_REGACK_MSG(TopicId, _MsgId, Reason), _ -> %% skip this topic-name register, if the reason is %% ?SN_RC_NOT_SUPPORTED, ?SN_RC_INVALID_TOPIC_ID, etc. - ?SLOG(warning, #{ msg => "skipp_register_topic_name_to_client" - , topic_id => TopicId - , topic_name => TopicName - }), + ?SLOG(warning, #{ + msg => "skipp_register_topic_name_to_client", + topic_id => TopicId, + topic_name => TopicName + }), NChannel = cancel_timer( - register_timer, - Channel#channel{register_inflight = undefined}), + register_timer, + Channel#channel{register_inflight = undefined} + ), send_next_register_or_replay_publish(TopicName, NChannel) end; - -handle_in(?SN_REGACK_MSG(TopicId, MsgId, Reason), - Channel = #channel{register_inflight = Inflight}) -> - ?SLOG(error, #{ msg => "unexpected_regack_msg" - , acked_msg_id => MsgId - , acked_topic_id => TopicId - , acked_reason => Reason - , current_inflight => Inflight - }), +handle_in( + ?SN_REGACK_MSG(TopicId, MsgId, Reason), + Channel = #channel{register_inflight = Inflight} +) -> + ?SLOG(error, #{ + msg => "unexpected_regack_msg", + acked_msg_id => MsgId, + acked_topic_id => TopicId, + acked_reason => Reason, + current_inflight => Inflight + }), {ok, Channel}; - handle_in(PubPkt = ?SN_PUBLISH_MSG(_Flags, TopicId0, MsgId, _Data), Channel) -> - TopicId = case is_integer(TopicId0) of - true -> TopicId0; - _ -> <> = TopicId0, Id - end, - case emqx_misc:pipeline( - [ fun check_qos3_enable/2 - , fun preproc_pub_pkt/2 - , fun convert_topic_id_to_name/2 - , fun check_pub_authz/2 - , fun convert_pub_to_msg/2 - ], PubPkt, Channel) of + TopicId = + case is_integer(TopicId0) of + true -> + TopicId0; + _ -> + <> = TopicId0, + Id + end, + case + emqx_misc:pipeline( + [ + fun check_qos3_enable/2, + fun preproc_pub_pkt/2, + fun convert_topic_id_to_name/2, + fun check_pub_authz/2, + fun convert_pub_to_msg/2 + ], + PubPkt, + Channel + ) + of {ok, Msg, NChannel} -> do_publish(TopicId, MsgId, Msg, NChannel); {error, ReturnCode, NChannel} -> handle_out(puback, {TopicId, MsgId, ReturnCode}, NChannel) end; - -handle_in(?SN_PUBACK_MSG(TopicId, MsgId, ReturnCode), - Channel = #channel{ - ctx = Ctx, - registry = Registry, - session = Session, - clientinfo = ClientInfo = #{clientid := ClientId}}) -> +handle_in( + ?SN_PUBACK_MSG(TopicId, MsgId, ReturnCode), + Channel = #channel{ + ctx = Ctx, + registry = Registry, + session = Session, + clientinfo = ClientInfo = #{clientid := ClientId} + } +) -> case ReturnCode of ?SN_RC_ACCEPTED -> case emqx_session:puback(ClientInfo, MsgId, Session) of {ok, Msg, NSession} -> ok = after_message_acked(ClientInfo, Msg, Channel), {Replies, NChannel} = goto_asleep_if_buffered_msgs_sent( - Channel#channel{session = NSession} - ), + Channel#channel{session = NSession} + ), {ok, Replies, NChannel}; {ok, Msg, Publishes, NSession} -> ok = after_message_acked(ClientInfo, Msg, Channel), - handle_out(publish, - Publishes, - Channel#channel{session = NSession}); + handle_out( + publish, + Publishes, + Channel#channel{session = NSession} + ); {error, ?RC_PACKET_IDENTIFIER_IN_USE} -> - ?SLOG(warning, #{ msg => "commit_puback_failed" - , msg_id => MsgId - , reason => msg_id_inused - }), + ?SLOG(warning, #{ + msg => "commit_puback_failed", + msg_id => MsgId, + reason => msg_id_inused + }), ok = metrics_inc(Ctx, 'packets.puback.inuse'), {ok, Channel}; {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> - ?SLOG(warning, #{ msg => "commit_puback_failed" - , msg_id => MsgId - , reason => not_found - }), + ?SLOG(warning, #{ + msg => "commit_puback_failed", + msg_id => MsgId, + reason => not_found + }), ok = metrics_inc(Ctx, 'packets.puback.missed'), {ok, Channel} end; @@ -658,151 +783,192 @@ handle_in(?SN_PUBACK_MSG(TopicId, MsgId, ReturnCode), handle_out(register, {TopicId, TopicName}, Channel) end; _ -> - ?SLOG(error, #{ msg => "cannt_handle_PUBACK" - , return_code => ReturnCode - }), + ?SLOG(error, #{ + msg => "cannt_handle_PUBACK", + return_code => ReturnCode + }), {ok, Channel} end; - -handle_in(?SN_PUBREC_MSG(?SN_PUBREC, MsgId), - Channel = #channel{ctx = Ctx, - session = Session, - clientinfo = ClientInfo}) -> +handle_in( + ?SN_PUBREC_MSG(?SN_PUBREC, MsgId), + Channel = #channel{ + ctx = Ctx, + session = Session, + clientinfo = ClientInfo + } +) -> case emqx_session:pubrec(ClientInfo, MsgId, Session) of {ok, Msg, NSession} -> ok = after_message_acked(ClientInfo, Msg, Channel), NChannel = Channel#channel{session = NSession}, handle_out(pubrel, MsgId, NChannel); {error, ?RC_PACKET_IDENTIFIER_IN_USE} -> - ?SLOG(warning, #{ msg => "commit_PUBREC_failed" - , msg_id => MsgId - , reason => msg_id_inused - }), + ?SLOG(warning, #{ + msg => "commit_PUBREC_failed", + msg_id => MsgId, + reason => msg_id_inused + }), ok = metrics_inc(Ctx, 'packets.pubrec.inuse'), handle_out(pubrel, MsgId, Channel); {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> - ?SLOG(warning, #{ msg => "commit_PUBREC_failed" - , msg_id => MsgId - , reason => not_found - }), + ?SLOG(warning, #{ + msg => "commit_PUBREC_failed", + msg_id => MsgId, + reason => not_found + }), ok = metrics_inc(Ctx, 'packets.pubrec.missed'), handle_out(pubrel, MsgId, Channel) end; - -handle_in(?SN_PUBREC_MSG(?SN_PUBREL, MsgId), - Channel = #channel{ctx = Ctx, session = Session, clientinfo = ClientInfo}) -> +handle_in( + ?SN_PUBREC_MSG(?SN_PUBREL, MsgId), + Channel = #channel{ctx = Ctx, session = Session, clientinfo = ClientInfo} +) -> case emqx_session:pubrel(ClientInfo, MsgId, Session) of {ok, NSession} -> NChannel = Channel#channel{session = NSession}, handle_out(pubcomp, MsgId, NChannel); {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> - ?SLOG(warning, #{ msg => "commit_PUBREL_failed" - , msg_id => MsgId - , reason => not_found - }), + ?SLOG(warning, #{ + msg => "commit_PUBREL_failed", + msg_id => MsgId, + reason => not_found + }), ok = metrics_inc(Ctx, 'packets.pubrel.missed'), handle_out(pubcomp, MsgId, Channel) end; - -handle_in(?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId), - Channel = #channel{ctx = Ctx, session = Session, clientinfo = ClientInfo}) -> +handle_in( + ?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId), + Channel = #channel{ctx = Ctx, session = Session, clientinfo = ClientInfo} +) -> case emqx_session:pubcomp(ClientInfo, MsgId, Session) of {ok, NSession} -> {Replies, NChannel} = goto_asleep_if_buffered_msgs_sent( - Channel#channel{session = NSession} - ), + Channel#channel{session = NSession} + ), {ok, Replies, NChannel}; {ok, Publishes, NSession} -> - handle_out(publish, Publishes, - Channel#channel{session = NSession}); + handle_out( + publish, + Publishes, + Channel#channel{session = NSession} + ); {error, ?RC_PACKET_IDENTIFIER_IN_USE} -> - ?SLOG(warning, #{ msg => "commit_PUBCOMP_failed" - , msg_id => MsgId - , reason => msg_id_inused - }), + ?SLOG(warning, #{ + msg => "commit_PUBCOMP_failed", + msg_id => MsgId, + reason => msg_id_inused + }), ok = metrics_inc(Ctx, 'packets.pubcomp.inuse'), {ok, Channel}; {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> - ?SLOG(warning, #{ msg => "commit_PUBCOMP_failed" - , msg_id => MsgId - , reason => not_found - }), + ?SLOG(warning, #{ + msg => "commit_PUBCOMP_failed", + msg_id => MsgId, + reason => not_found + }), ok = metrics_inc(Ctx, 'packets.pubcomp.missed'), {ok, Channel} end; - handle_in(SubPkt = ?SN_SUBSCRIBE_MSG(_, MsgId, _), Channel) -> - case emqx_misc:pipeline( - [ fun preproc_subs_type/2 - , fun check_subscribe_authz/2 - , fun run_client_subs_hook/2 - , fun do_subscribe/2 - ], SubPkt, Channel) of + case + emqx_misc:pipeline( + [ + fun preproc_subs_type/2, + fun check_subscribe_authz/2, + fun run_client_subs_hook/2, + fun do_subscribe/2 + ], + SubPkt, + Channel + ) + of {ok, {TopicId, _TopicName, SubOpts}, NChannel} -> GrantedQoS = maps:get(qos, SubOpts), - SubAck = ?SN_SUBACK_MSG(#mqtt_sn_flags{qos = GrantedQoS}, - TopicId, MsgId, ?SN_RC_ACCEPTED), + SubAck = ?SN_SUBACK_MSG( + #mqtt_sn_flags{qos = GrantedQoS}, + TopicId, + MsgId, + ?SN_RC_ACCEPTED + ), {ok, outgoing_and_update(SubAck), NChannel}; {error, ReturnCode, NChannel} -> - SubAck = ?SN_SUBACK_MSG(#mqtt_sn_flags{}, - ?SN_INVALID_TOPIC_ID, - MsgId, - ReturnCode), + SubAck = ?SN_SUBACK_MSG( + #mqtt_sn_flags{}, + ?SN_INVALID_TOPIC_ID, + MsgId, + ReturnCode + ), {ok, {outgoing, SubAck}, NChannel} end; - -handle_in(UnsubPkt = ?SN_UNSUBSCRIBE_MSG(_, MsgId, TopicIdOrName), - Channel) -> - case emqx_misc:pipeline( - [ fun preproc_unsub_type/2 - , fun run_client_unsub_hook/2 - , fun do_unsubscribe/2 - ], UnsubPkt, Channel) of +handle_in( + UnsubPkt = ?SN_UNSUBSCRIBE_MSG(_, MsgId, TopicIdOrName), + Channel +) -> + case + emqx_misc:pipeline( + [ + fun preproc_unsub_type/2, + fun run_client_unsub_hook/2, + fun do_unsubscribe/2 + ], + UnsubPkt, + Channel + ) + of {ok, _TopicName, NChannel} -> UnsubAck = ?SN_UNSUBACK_MSG(MsgId), {ok, outgoing_and_update(UnsubAck), NChannel}; {error, Reason, NChannel} -> - ?SLOG(warning, #{ msg => "unsubscribe_failed" - , topic => TopicIdOrName - , reason => Reason - }), + ?SLOG(warning, #{ + msg => "unsubscribe_failed", + topic => TopicIdOrName, + reason => Reason + }), %% XXX: Even if it fails, the reply is successful. UnsubAck = ?SN_UNSUBACK_MSG(MsgId), {ok, {outgoing, UnsubAck}, NChannel} end; - -handle_in(?SN_PINGREQ_MSG(ClientId), Channel) - when ClientId == undefined; - ClientId == <<>> -> +handle_in(?SN_PINGREQ_MSG(ClientId), Channel) when + ClientId == undefined; + ClientId == <<>> +-> {ok, {outgoing, ?SN_PINGRESP_MSG()}, Channel}; - -handle_in(?SN_PINGREQ_MSG(ReqClientId), - Channel = #channel{clientinfo = #{clientid := ClientId}}) - when ReqClientId =/= ClientId -> - ?SLOG(warning, #{ msg => "awake_pingreq_clientid_not_match" - , clientid => ClientId - , request_clientid => ReqClientId - }), +handle_in( + ?SN_PINGREQ_MSG(ReqClientId), + Channel = #channel{clientinfo = #{clientid := ClientId}} +) when + ReqClientId =/= ClientId +-> + ?SLOG(warning, #{ + msg => "awake_pingreq_clientid_not_match", + clientid => ClientId, + request_clientid => ReqClientId + }), %% FIXME: takeover_and_awake.. {ok, Channel}; - -handle_in(?SN_PINGREQ_MSG(ClientId), - Channel = #channel{conn_state = ConnState}) - when ConnState == idle; ConnState == asleep; ConnState == awake -> +handle_in( + ?SN_PINGREQ_MSG(ClientId), + Channel = #channel{conn_state = ConnState} +) when + ConnState == idle; ConnState == asleep; ConnState == awake +-> awake(ClientId, Channel); - -handle_in(?SN_PINGREQ_MSG(ClientId), - Channel = #channel{ - conn_state = connected, - clientinfo = #{clientid := ClientId}}) -> +handle_in( + ?SN_PINGREQ_MSG(ClientId), + Channel = #channel{ + conn_state = connected, + clientinfo = #{clientid := ClientId} + } +) -> {ok, {outgoing, ?SN_PINGRESP_MSG()}, Channel}; - handle_in(?SN_DISCONNECT_MSG(_Duration = undefined), Channel) -> handle_out(disconnect, normal, Channel); - -handle_in(?SN_DISCONNECT_MSG(Duration), - Channel = #channel{conn_state = ConnState}) - when ConnState == connected; ConnState == asleep -> +handle_in( + ?SN_DISCONNECT_MSG(Duration), + Channel = #channel{conn_state = ConnState} +) when + ConnState == connected; ConnState == asleep +-> %% A DISCONNECT message with a Duration field is sent by a client %% when it wants to go to the “asleep” state. The receipt of this %% message is also acknowledged by the gateway by means of a @@ -810,72 +976,89 @@ handle_in(?SN_DISCONNECT_MSG(Duration), %% AckPkt = ?SN_DISCONNECT_MSG(undefined), {ok, [{outgoing, AckPkt}, {event, asleep}], asleep(Duration, Channel)}; - -handle_in(?SN_WILLTOPICUPD_MSG(Flags, Topic), - Channel = #channel{will_msg = WillMsg, - clientinfo = #{clientid := ClientId}}) -> - NWillMsg = case Topic of - undefined -> undefined; - _ -> - update_will_topic(WillMsg, Flags, Topic, ClientId) - end, +handle_in( + ?SN_WILLTOPICUPD_MSG(Flags, Topic), + Channel = #channel{ + will_msg = WillMsg, + clientinfo = #{clientid := ClientId} + } +) -> + NWillMsg = + case Topic of + undefined -> undefined; + _ -> update_will_topic(WillMsg, Flags, Topic, ClientId) + end, AckPkt = ?SN_WILLTOPICRESP_MSG(?SN_RC_ACCEPTED), {ok, {outgoing, AckPkt}, Channel#channel{will_msg = NWillMsg}}; - -handle_in(?SN_WILLMSGUPD_MSG(Payload), - Channel = #channel{will_msg = WillMsg}) -> +handle_in( + ?SN_WILLMSGUPD_MSG(Payload), + Channel = #channel{will_msg = WillMsg} +) -> AckPkt = ?SN_WILLMSGRESP_MSG(?SN_RC_ACCEPTED), NWillMsg = update_will_msg(WillMsg, Payload), {ok, {outgoing, AckPkt}, Channel#channel{will_msg = NWillMsg}}; - -handle_in({frame_error, Reason}, - Channel = #channel{conn_state = _ConnState}) -> - ?SLOG(error, #{ msg => "unexpected_frame_error" - , reason => Reason - }), +handle_in( + {frame_error, Reason}, + Channel = #channel{conn_state = _ConnState} +) -> + ?SLOG(error, #{ + msg => "unexpected_frame_error", + reason => Reason + }), shutdown(Reason, Channel). after_message_acked(ClientInfo, Msg, #channel{ctx = Ctx}) -> ok = metrics_inc(Ctx, 'messages.acked'), - run_hooks_without_metrics(Ctx, + run_hooks_without_metrics( + Ctx, 'message.acked', - [ClientInfo, emqx_message:set_header(puback_props, #{}, Msg)]). + [ClientInfo, emqx_message:set_header(puback_props, #{}, Msg)] + ). outgoing_and_update(Pkt) -> [{outgoing, Pkt}, {event, update}]. send_next_register_or_replay_publish( - _TopicName, - Channel = #channel{ register_awaiting_queue = []}) -> + _TopicName, + Channel = #channel{register_awaiting_queue = []} +) -> {Outgoing, NChannel} = resume_or_replay_messages(Channel), {ok, Outgoing, NChannel}; - send_next_register_or_replay_publish( - _TopicName, - Channel = #channel{register_awaiting_queue = RAQueue}) -> + _TopicName, + Channel = #channel{register_awaiting_queue = RAQueue} +) -> [RegisterReq | NRAQueue] = RAQueue, - handle_out(register, RegisterReq, - Channel#channel{register_awaiting_queue = NRAQueue}). + handle_out( + register, + RegisterReq, + Channel#channel{register_awaiting_queue = NRAQueue} + ). %%-------------------------------------------------------------------- %% Handle Publish -check_qos3_enable(?SN_PUBLISH_MSG(Flags, TopicId, _MsgId, Data), - #channel{enable_qos3 = EnableQoS3}) -> +check_qos3_enable( + ?SN_PUBLISH_MSG(Flags, TopicId, _MsgId, Data), + #channel{enable_qos3 = EnableQoS3} +) -> #mqtt_sn_flags{qos = QoS} = Flags, case EnableQoS3 =:= false andalso QoS =:= ?QOS_NEG1 of - true -> - ?SLOG(debug, #{ msg => "ignore_msg_due_to_qos3_disabled" - , topic_id => TopicId - , data => Data - }), + true -> + ?SLOG(debug, #{ + msg => "ignore_msg_due_to_qos3_disabled", + topic_id => TopicId, + data => Data + }), {error, ?SN_RC_NOT_SUPPORTED}; false -> ok end. -preproc_pub_pkt(?SN_PUBLISH_MSG(Flags, Topic0, _MsgId, Data), - Channel) -> +preproc_pub_pkt( + ?SN_PUBLISH_MSG(Flags, Topic0, _MsgId, Data), + Channel +) -> #mqtt_sn_flags{topic_id_type = TopicIdType} = Flags, case TopicIdType of ?SN_NORMAL_TOPIC -> @@ -897,12 +1080,13 @@ preproc_pub_pkt(?SN_PUBLISH_MSG(Flags, Topic0, _MsgId, Data), convert_topic_id_to_name({{name, TopicName}, Flags, Data}, Channel) -> {ok, {TopicName, Flags, Data}, Channel}; - -convert_topic_id_to_name({{id, TopicId}, Flags, Data}, - Channel = #channel{ - registry = Registry, - clientinfo = #{clientid := ClientId}} - ) -> +convert_topic_id_to_name( + {{id, TopicId}, Flags, Data}, + Channel = #channel{ + registry = Registry, + clientinfo = #{clientid := ClientId} + } +) -> case emqx_sn_registry:lookup_topic(Registry, ClientId, TopicId) of undefined -> {error, ?SN_RC_INVALID_TOPIC_ID}; @@ -910,34 +1094,51 @@ convert_topic_id_to_name({{id, TopicId}, Flags, Data}, {ok, {TopicName, Flags, Data}, Channel} end. -check_pub_authz({TopicName, _Flags, _Data}, - #channel{ctx = Ctx, clientinfo = ClientInfo}) -> +check_pub_authz( + {TopicName, _Flags, _Data}, + #channel{ctx = Ctx, clientinfo = ClientInfo} +) -> case emqx_gateway_ctx:authorize(Ctx, ClientInfo, publish, TopicName) of allow -> ok; - deny -> {error, ?SN_RC2_NOT_AUTHORIZE} + deny -> {error, ?SN_RC2_NOT_AUTHORIZE} end. -convert_pub_to_msg({TopicName, Flags, Data}, - Channel = #channel{clientinfo = #{clientid := ClientId}}) -> +convert_pub_to_msg( + {TopicName, Flags, Data}, + Channel = #channel{clientinfo = #{clientid := ClientId}} +) -> #mqtt_sn_flags{qos = QoS, dup = Dup, retain = Retain} = Flags, NewQoS = get_corrected_qos(QoS), Message = put_message_headers( - emqx_message:make( - ClientId, NewQoS, TopicName, Data, - #{dup => Dup, retain => Retain}, #{}), Channel), + emqx_message:make( + ClientId, + NewQoS, + TopicName, + Data, + #{dup => Dup, retain => Retain}, + #{} + ), + Channel + ), {ok, Message, Channel}. put_message_headers(Msg, #channel{ - conninfo = #{proto_ver := ProtoVer}, - clientinfo = #{ - protocol := Protocol, - username := Username, - peerhost := PeerHost}}) -> + conninfo = #{proto_ver := ProtoVer}, + clientinfo = #{ + protocol := Protocol, + username := Username, + peerhost := PeerHost + } +}) -> emqx_message:set_headers( - #{proto_ver => ProtoVer, - protocol => Protocol, - username => Username, - peerhost => PeerHost}, Msg). + #{ + proto_ver => ProtoVer, + protocol => Protocol, + username => Username, + peerhost => PeerHost + }, + Msg + ). get_corrected_qos(?QOS_NEG1) -> ?QOS_0; get_corrected_qos(QoS) -> QoS. @@ -945,28 +1146,35 @@ get_corrected_qos(QoS) -> QoS. do_publish(_TopicId, _MsgId, Msg = #message{qos = ?QOS_0}, Channel) -> _ = emqx_broker:publish(Msg), {ok, Channel}; - do_publish(TopicId, MsgId, Msg = #message{qos = ?QOS_1}, Channel) -> _ = emqx_broker:publish(Msg), handle_out(puback, {TopicId, MsgId, ?SN_RC_ACCEPTED}, Channel); - -do_publish(TopicId, MsgId, Msg = #message{qos = ?QOS_2}, - Channel = #channel{ctx = Ctx, session = Session, clientinfo = ClientInfo}) -> +do_publish( + TopicId, + MsgId, + Msg = #message{qos = ?QOS_2}, + Channel = #channel{ctx = Ctx, session = Session, clientinfo = ClientInfo} +) -> case emqx_session:publish(ClientInfo, MsgId, Msg, Session) of {ok, _PubRes, NSession} -> - NChannel1 = ensure_timer(await_timer, - Channel#channel{session = NSession} - ), + NChannel1 = ensure_timer( + await_timer, + Channel#channel{session = NSession} + ), handle_out(pubrec, MsgId, NChannel1); {error, ?RC_PACKET_IDENTIFIER_IN_USE} -> ok = metrics_inc(Ctx, 'packets.publish.inuse'), %% XXX: Use PUBACK to reply a PUBLISH Error Code - handle_out(puback , {TopicId, MsgId, ?SN_RC_NOT_SUPPORTED}, - Channel); + handle_out( + puback, + {TopicId, MsgId, ?SN_RC_NOT_SUPPORTED}, + Channel + ); {error, ?RC_RECEIVE_MAXIMUM_EXCEEDED} -> - ?SLOG(warning, #{ msg => "dropped_the_qos2_packet_due_to_awaiting_rel_full" - , msg_id => MsgId - }), + ?SLOG(warning, #{ + msg => "dropped_the_qos2_packet_due_to_awaiting_rel_full", + msg_id => MsgId + }), ok = metrics_inc(Ctx, 'packets.publish.dropped'), handle_out(puback, {TopicId, MsgId, ?SN_RC_CONGESTION}, Channel) end. @@ -974,12 +1182,17 @@ do_publish(TopicId, MsgId, Msg = #message{qos = ?QOS_2}, %%-------------------------------------------------------------------- %% Handle Susbscribe -preproc_subs_type(?SN_SUBSCRIBE_MSG_TYPE(?SN_NORMAL_TOPIC, - TopicName, QoS), - Channel = #channel{ - registry = Registry, - clientinfo = #{clientid := ClientId} - }) -> +preproc_subs_type( + ?SN_SUBSCRIBE_MSG_TYPE( + ?SN_NORMAL_TOPIC, + TopicName, + QoS + ), + Channel = #channel{ + registry = Registry, + clientinfo = #{clientid := ClientId} + } +) -> %% If the gateway is able accept the subscription, %% it assigns a topic id to the received topic name %% and returns it within a SUBACK message @@ -997,37 +1210,54 @@ preproc_subs_type(?SN_SUBSCRIBE_MSG_TYPE(?SN_NORMAL_TOPIC, TopicId when is_integer(TopicId) -> {ok, {TopicId, TopicName, QoS}, Channel} end; - -preproc_subs_type(?SN_SUBSCRIBE_MSG_TYPE(?SN_PREDEFINED_TOPIC, - TopicId, QoS), - Channel = #channel{ - registry = Registry, - clientinfo = #{clientid := ClientId} - }) -> - case emqx_sn_registry:lookup_topic(Registry, - ClientId, TopicId) of +preproc_subs_type( + ?SN_SUBSCRIBE_MSG_TYPE( + ?SN_PREDEFINED_TOPIC, + TopicId, + QoS + ), + Channel = #channel{ + registry = Registry, + clientinfo = #{clientid := ClientId} + } +) -> + case + emqx_sn_registry:lookup_topic( + Registry, + ClientId, + TopicId + ) + of undefined -> {error, ?SN_RC_INVALID_TOPIC_ID}; TopicName -> {ok, {TopicId, TopicName, QoS}, Channel} end; - -preproc_subs_type(?SN_SUBSCRIBE_MSG_TYPE(?SN_SHORT_TOPIC, - TopicId, QoS), - Channel) -> - TopicName = case is_binary(TopicId) of - true -> TopicId; - false -> <> - end, +preproc_subs_type( + ?SN_SUBSCRIBE_MSG_TYPE( + ?SN_SHORT_TOPIC, + TopicId, + QoS + ), + Channel +) -> + TopicName = + case is_binary(TopicId) of + true -> TopicId; + false -> <> + end, %% XXX: ?SN_INVALID_TOPIC_ID ??? {ok, {?SN_INVALID_TOPIC_ID, TopicName, QoS}, Channel}; - -preproc_subs_type(?SN_SUBSCRIBE_MSG_TYPE(_Reserved, _TopicId, _QoS), - _Channel) -> +preproc_subs_type( + ?SN_SUBSCRIBE_MSG_TYPE(_Reserved, _TopicId, _QoS), + _Channel +) -> {error, ?SN_RC_NOT_SUPPORTED}. -check_subscribe_authz({_TopicId, TopicName, _QoS}, - Channel = #channel{ctx = Ctx, clientinfo = ClientInfo}) -> +check_subscribe_authz( + {_TopicId, TopicName, _QoS}, + Channel = #channel{ctx = Ctx, clientinfo = ClientInfo} +) -> case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, TopicName) of allow -> {ok, Channel}; @@ -1035,164 +1265,232 @@ check_subscribe_authz({_TopicId, TopicName, _QoS}, {error, ?SN_RC2_NOT_AUTHORIZE} end. -run_client_subs_hook({TopicId, TopicName, QoS}, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo}) -> +run_client_subs_hook( + {TopicId, TopicName, QoS}, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> {TopicName1, SubOpts0} = emqx_topic:parse(TopicName), TopicFilters = [{TopicName1, SubOpts0#{qos => QoS}}], - case run_hooks(Ctx, 'client.subscribe', - [ClientInfo, #{}], TopicFilters) of + case + run_hooks( + Ctx, + 'client.subscribe', + [ClientInfo, #{}], + TopicFilters + ) + of [] -> - ?SLOG(warning, #{ msg => "skip_to_subscribe" - , topic_name => TopicName - , reason => "'client.subscribe' filtered it" - }), + ?SLOG(warning, #{ + msg => "skip_to_subscribe", + topic_name => TopicName, + reason => "'client.subscribe' filtered it" + }), {error, ?SN_RC2_EXCEED_LIMITATION}; - [{NTopicName, NSubOpts}|_] -> + [{NTopicName, NSubOpts} | _] -> {ok, {TopicId, NTopicName, NSubOpts}, Channel} end. -do_subscribe({TopicId, TopicName, SubOpts}, - Channel = #channel{ - session = Session, - clientinfo = ClientInfo - = #{mountpoint := Mountpoint}}) -> +do_subscribe( + {TopicId, TopicName, SubOpts}, + Channel = #channel{ + session = Session, + clientinfo = + ClientInfo = + #{mountpoint := Mountpoint} + } +) -> NTopicName = emqx_mountpoint:mount(Mountpoint, TopicName), NSubOpts = maps:merge(emqx_gateway_utils:default_subopts(), SubOpts), case emqx_session:subscribe(ClientInfo, NTopicName, NSubOpts, Session) of {ok, NSession} -> - {ok, {TopicId, NTopicName, NSubOpts}, - Channel#channel{session = NSession}}; + {ok, {TopicId, NTopicName, NSubOpts}, Channel#channel{session = NSession}}; {error, ?RC_QUOTA_EXCEEDED} -> - ?SLOG(warning, #{ msg => "cannt_subscribe_due_to_quota_exceeded" - , topic_name => TopicName - , reason => emqx_reason_codes:text(?RC_QUOTA_EXCEEDED) - }), + ?SLOG(warning, #{ + msg => "cannt_subscribe_due_to_quota_exceeded", + topic_name => TopicName, + reason => emqx_reason_codes:text(?RC_QUOTA_EXCEEDED) + }), {error, ?SN_RC2_EXCEED_LIMITATION} end. %%-------------------------------------------------------------------- %% Handle Unsubscribe -preproc_unsub_type(?SN_UNSUBSCRIBE_MSG_TYPE(?SN_NORMAL_TOPIC, - TopicName), - Channel) -> +preproc_unsub_type( + ?SN_UNSUBSCRIBE_MSG_TYPE( + ?SN_NORMAL_TOPIC, + TopicName + ), + Channel +) -> {ok, TopicName, Channel}; -preproc_unsub_type(?SN_UNSUBSCRIBE_MSG_TYPE(?SN_PREDEFINED_TOPIC, - TopicId), - Channel = #channel{ - registry = Registry, - clientinfo = #{clientid := ClientId} - }) -> - case emqx_sn_registry:lookup_topic(Registry, ClientId, - TopicId) of +preproc_unsub_type( + ?SN_UNSUBSCRIBE_MSG_TYPE( + ?SN_PREDEFINED_TOPIC, + TopicId + ), + Channel = #channel{ + registry = Registry, + clientinfo = #{clientid := ClientId} + } +) -> + case + emqx_sn_registry:lookup_topic( + Registry, + ClientId, + TopicId + ) + of undefined -> {error, not_found}; TopicName -> {ok, TopicName, Channel} end; -preproc_unsub_type(?SN_UNSUBSCRIBE_MSG_TYPE(?SN_SHORT_TOPIC, - TopicId), - Channel) -> - TopicName = case is_binary(TopicId) of - true -> TopicId; - false -> <> - end, +preproc_unsub_type( + ?SN_UNSUBSCRIBE_MSG_TYPE( + ?SN_SHORT_TOPIC, + TopicId + ), + Channel +) -> + TopicName = + case is_binary(TopicId) of + true -> TopicId; + false -> <> + end, {ok, TopicName, Channel}. -run_client_unsub_hook(TopicName, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - }) -> +run_client_unsub_hook( + TopicName, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> TopicFilters = [emqx_topic:parse(TopicName)], - case run_hooks(Ctx, 'client.unsubscribe', - [ClientInfo, #{}], TopicFilters) of + case + run_hooks( + Ctx, + 'client.unsubscribe', + [ClientInfo, #{}], + TopicFilters + ) + of [] -> {ok, [], Channel}; NTopicFilters -> {ok, NTopicFilters, Channel} end. -do_unsubscribe(TopicFilters, - Channel = #channel{ - session = Session, - clientinfo = ClientInfo - = #{mountpoint := Mountpoint}}) -> +do_unsubscribe( + TopicFilters, + Channel = #channel{ + session = Session, + clientinfo = + ClientInfo = + #{mountpoint := Mountpoint} + } +) -> NChannel = - lists:foldl(fun({TopicName, SubOpts}, ChannAcc) -> - NTopicName = emqx_mountpoint:mount(Mountpoint, TopicName), - NSubOpts = maps:merge( - emqx_gateway_utils:default_subopts(), - SubOpts - ), - case emqx_session:unsubscribe(ClientInfo, NTopicName, - NSubOpts, Session) of - {ok, NSession} -> - ChannAcc#channel{session = NSession}; - {error, ?RC_NO_SUBSCRIPTION_EXISTED} -> - ChannAcc - end - end, Channel, TopicFilters), + lists:foldl( + fun({TopicName, SubOpts}, ChannAcc) -> + NTopicName = emqx_mountpoint:mount(Mountpoint, TopicName), + NSubOpts = maps:merge( + emqx_gateway_utils:default_subopts(), + SubOpts + ), + case + emqx_session:unsubscribe( + ClientInfo, + NTopicName, + NSubOpts, + Session + ) + of + {ok, NSession} -> + ChannAcc#channel{session = NSession}; + {error, ?RC_NO_SUBSCRIPTION_EXISTED} -> + ChannAcc + end + end, + Channel, + TopicFilters + ), {ok, TopicFilters, NChannel}. %%-------------------------------------------------------------------- %% Awake & Asleep awake(ClientId, Channel = #channel{conn_state = idle}) -> - ?SLOG(warning, #{ msg => "awake_pingreq_in_idle_state" - , clientid => ClientId - }), + ?SLOG(warning, #{ + msg => "awake_pingreq_in_idle_state", + clientid => ClientId + }), %% TODO: takeover and awake? %% 1. Query emqx_cm_registry to get the session state? %% 2. Takeover it and goto awake state {ok, {outgoing, ?SN_PINGRESP_MSG()}, Channel}; - -awake(ClientId, Channel = #channel{ - conn_state = ConnState, - session = Session, - clientinfo = ClientInfo = #{clientid := ClientId}}) - when ConnState == asleep; ConnState == awake -> - ?SLOG(info, #{ msg => "goto_awake_state" - , clientid => ClientId - , previous_state => ConnState - }), +awake( + ClientId, + Channel = #channel{ + conn_state = ConnState, + session = Session, + clientinfo = ClientInfo = #{clientid := ClientId} + } +) when + ConnState == asleep; ConnState == awake +-> + ?SLOG(info, #{ + msg => "goto_awake_state", + clientid => ClientId, + previous_state => ConnState + }), {ok, Publishes, Session1} = emqx_session:replay(ClientInfo, Session), - {NPublishes, NSession} = case emqx_session:deliver(ClientInfo, [], Session1) of - {ok, Session2} -> - {Publishes, Session2}; - {ok, More, Session2} -> - {lists:append(Publishes, More), Session2} - end, + {NPublishes, NSession} = + case emqx_session:deliver(ClientInfo, [], Session1) of + {ok, Session2} -> + {Publishes, Session2}; + {ok, More, Session2} -> + {lists:append(Publishes, More), Session2} + end, Channel1 = cancel_timer(asleep_timer, Channel), {Replies0, NChannel0} = outgoing_deliver_and_register( - do_deliver( - NPublishes, - Channel1#channel{ - conn_state = awake, session = NSession} - ) - ), + do_deliver( + NPublishes, + Channel1#channel{ + conn_state = awake, session = NSession + } + ) + ), Replies1 = [{event, awake} | Replies0], {Replies2, NChannel} = goto_asleep_if_buffered_msgs_sent(NChannel0), {ok, Replies1 ++ Replies2, NChannel}. goto_asleep_if_buffered_msgs_sent( - Channel = #channel{ - conn_state = awake, - session = Session, - asleep_timer_duration = Duration}) -> - case emqx_mqueue:is_empty(emqx_session:info(mqueue, Session)) andalso - emqx_inflight:is_empty(emqx_session:info(inflight, Session)) of + Channel = #channel{ + conn_state = awake, + session = Session, + asleep_timer_duration = Duration + } +) -> + case + emqx_mqueue:is_empty(emqx_session:info(mqueue, Session)) andalso + emqx_inflight:is_empty(emqx_session:info(inflight, Session)) + of true -> - ?SLOG(info, #{ msg => "goto_asleep_state" - , reason => buffered_messages_sent - , duration => Duration - }), - Replies = [ {outgoing, ?SN_PINGRESP_MSG()} - , {event, asleep} - ], + ?SLOG(info, #{ + msg => "goto_asleep_state", + reason => buffered_messages_sent, + duration => Duration + }), + Replies = [ + {outgoing, ?SN_PINGRESP_MSG()}, + {event, asleep} + ], {Replies, ensure_asleep_timer(Channel#channel{conn_state = asleep})}; false -> {[], Channel} @@ -1206,101 +1504,119 @@ asleep(Duration, Channel = #channel{conn_state = asleep}) -> %% the sleep duration %% %% XXX: Do we need to limit the maximum of Duration? - ?SLOG(debug, #{ msg => "update_asleep_timer" - , new_duration => Duration - }), + ?SLOG(debug, #{ + msg => "update_asleep_timer", + new_duration => Duration + }), ensure_asleep_timer(Duration, cancel_timer(asleep_timer, Channel)); - asleep(Duration, Channel = #channel{conn_state = connected}) -> - ?SLOG(info, #{ msg => "goto_asleep_state" - , duration => Duration - }), + ?SLOG(info, #{ + msg => "goto_asleep_state", + duration => Duration + }), ensure_asleep_timer(Duration, Channel#channel{conn_state = asleep}). %%-------------------------------------------------------------------- %% Handle outgoing packet %%-------------------------------------------------------------------- --spec handle_out(atom(), term(), channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()} - | {shutdown, Reason :: term(), replies(), channel()}. +-spec handle_out(atom(), term(), channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), replies(), channel()}. -handle_out(connack, ?SN_RC_ACCEPTED, - Channel = #channel{ctx = Ctx, conninfo = ConnInfo}) -> - _ = run_hooks(Ctx, 'client.connack', - [ConnInfo, returncode_name(?SN_RC_ACCEPTED)], - #{} - ), - return_connack(?SN_CONNACK_MSG(?SN_RC_ACCEPTED), - ensure_keepalive(Channel)); - -handle_out(connack, ReasonCode, - Channel = #channel{ctx = Ctx, conninfo = ConnInfo}) -> +handle_out( + connack, + ?SN_RC_ACCEPTED, + Channel = #channel{ctx = Ctx, conninfo = ConnInfo} +) -> + _ = run_hooks( + Ctx, + 'client.connack', + [ConnInfo, returncode_name(?SN_RC_ACCEPTED)], + #{} + ), + return_connack( + ?SN_CONNACK_MSG(?SN_RC_ACCEPTED), + ensure_keepalive(Channel) + ); +handle_out( + connack, + ReasonCode, + Channel = #channel{ctx = Ctx, conninfo = ConnInfo} +) -> Reason = returncode_name(ReasonCode), _ = run_hooks(Ctx, 'client.connack', [ConnInfo, Reason], #{}), AckPacket = ?SN_CONNACK_MSG(ReasonCode), shutdown(Reason, AckPacket, Channel); - handle_out(publish, Publishes, Channel) -> {Replies1, NChannel} = outgoing_deliver_and_register( - do_deliver(Publishes, Channel) - ), + do_deliver(Publishes, Channel) + ), {Replies2, NChannel2} = goto_asleep_if_buffered_msgs_sent(NChannel), {ok, Replies1 ++ Replies2, NChannel2}; - handle_out(puback, {TopicId, MsgId, Rc}, Channel) -> {ok, {outgoing, ?SN_PUBACK_MSG(TopicId, MsgId, Rc)}, Channel}; - handle_out(pubrec, MsgId, Channel) -> {ok, {outgoing, ?SN_PUBREC_MSG(?SN_PUBREC, MsgId)}, Channel}; - handle_out(pubrel, MsgId, Channel) -> {ok, {outgoing, ?SN_PUBREC_MSG(?SN_PUBREL, MsgId)}, Channel}; - handle_out(pubcomp, MsgId, Channel) -> {ok, {outgoing, ?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId)}, Channel}; - -handle_out(register, {TopicId, TopicName}, - Channel = #channel{session = Session, - register_inflight = undefined}) -> +handle_out( + register, + {TopicId, TopicName}, + Channel = #channel{ + session = Session, + register_inflight = undefined + } +) -> {MsgId, NSession} = emqx_session:obtain_next_pkt_id(Session), Outgoing = {outgoing, ?SN_REGISTER_MSG(TopicId, MsgId, TopicName)}, NChannel = Channel#channel{ - session = NSession, - register_inflight = {TopicId, MsgId, TopicName}}, + session = NSession, + register_inflight = {TopicId, MsgId, TopicName} + }, {ok, Outgoing, ensure_register_timer(NChannel)}; - -handle_out(register, {TopicId, TopicName}, - Channel = #channel{register_inflight = Inflight, - register_awaiting_queue = RAQueue}) -> +handle_out( + register, + {TopicId, TopicName}, + Channel = #channel{ + register_inflight = Inflight, + register_awaiting_queue = RAQueue + } +) -> case enqueue_register_request({TopicId, TopicName}, Inflight, RAQueue) of ignore -> - ?SLOG(debug, #{ msg => "ingore_register_request_to_client" - , register_request => - #{ topic_id => TopicId - , topic_name => TopicName - } - }), + ?SLOG(debug, #{ + msg => "ingore_register_request_to_client", + register_request => + #{ + topic_id => TopicId, + topic_name => TopicName + } + }), {ok, Channel}; NRAQueue -> - ?SLOG(debug, #{ msg => "put_register_msg_into_awaiting_queue" - , register_request => - #{ topic_id => TopicId - , topic_name => TopicName - } - , register_awaiting_queue_size => length(NRAQueue) - }), + ?SLOG(debug, #{ + msg => "put_register_msg_into_awaiting_queue", + register_request => + #{ + topic_id => TopicId, + topic_name => TopicName + }, + register_awaiting_queue_size => length(NRAQueue) + }), {ok, Channel#channel{register_awaiting_queue = NRAQueue}} end; - handle_out(disconnect, RC, Channel) -> DisPkt = ?SN_DISCONNECT_MSG(undefined), - Reason = case is_atom(RC) of - true -> RC; - false -> returncode_name(RC) - end, + Reason = + case is_atom(RC) of + true -> RC; + false -> returncode_name(RC) + end, {ok, [{outgoing, DisPkt}, {close, Reason}], Channel}. enqueue_register_request({_, TopicName}, {_, _, TopicName}, _RAQueue) -> @@ -1326,24 +1642,30 @@ return_connack(AckPacket, Channel) -> maybe_resume_session(Channel = #channel{resuming = false}) -> {[], Channel}; -maybe_resume_session(Channel = #channel{session = Session, - resuming = true}) -> +maybe_resume_session( + Channel = #channel{ + session = Session, + resuming = true + } +) -> Subs = emqx_session:info(subscriptions, Session), case subs_resume() andalso map_size(Subs) =/= 0 of true -> - TopicNames = lists:filter(fun(T) -> not emqx_topic:wildcard(T) - end, maps:keys(Subs)), + TopicNames = lists:filter(fun(T) -> not emqx_topic:wildcard(T) end, maps:keys(Subs)), Registers = lists:map(fun(T) -> {register, T} end, TopicNames), {Registers, Channel}; false -> resume_or_replay_messages(Channel) end. -resume_or_replay_messages(Channel = #channel{ - resuming = Resuming, - pendings = Pendings, - session = Session, - clientinfo = ClientInfo}) -> +resume_or_replay_messages( + Channel = #channel{ + resuming = Resuming, + pendings = Pendings, + session = Session, + clientinfo = ClientInfo + } +) -> {NPendings, NChannel} = case Resuming of true -> @@ -1360,7 +1682,8 @@ resume_or_replay_messages(Channel = #channel{ {lists:append(Publishes, More), Session2} end, outgoing_deliver_and_register( - do_deliver(NPublishes, NChannel#channel{session = NSession})). + do_deliver(NPublishes, NChannel#channel{session = NSession}) + ). subs_resume() -> emqx:get_config([gateway, mqttsn, subs_resume]). @@ -1371,56 +1694,71 @@ subs_resume() -> do_deliver({pubrel, MsgId}, Channel) -> {[?SN_PUBREC_MSG(?SN_PUBREL, MsgId)], Channel}; - -do_deliver({MsgId, Msg}, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - = #{mountpoint := Mountpoint}}) -> +do_deliver( + {MsgId, Msg}, + Channel = #channel{ + ctx = Ctx, + clientinfo = + ClientInfo = + #{mountpoint := Mountpoint} + } +) -> metrics_inc(Ctx, 'messages.delivered'), Msg1 = run_hooks_without_metrics( - Ctx, - 'message.delivered', - [ClientInfo], - emqx_message:update_expiry(Msg) - ), + Ctx, + 'message.delivered', + [ClientInfo], + emqx_message:update_expiry(Msg) + ), Msg2 = emqx_mountpoint:unmount(Mountpoint, Msg1), Packet = message_to_packet(MsgId, Msg2, Channel), {[Packet], Channel}; - do_deliver([Publish], Channel) -> do_deliver(Publish, Channel); - do_deliver(Publishes, Channel) when is_list(Publishes) -> {Packets, NChannel} = - lists:foldl(fun(Publish, {Acc, Chann}) -> - {Packets, NChann} = do_deliver(Publish, Chann), - {Packets ++ Acc, NChann} - end, {[], Channel}, Publishes), + lists:foldl( + fun(Publish, {Acc, Chann}) -> + {Packets, NChann} = do_deliver(Publish, Chann), + {Packets ++ Acc, NChann} + end, + {[], Channel}, + Publishes + ), {lists:reverse(Packets), NChannel}. outgoing_deliver_and_register({Packets, Channel}) -> {NPackets, NRegisters} = - lists:foldl(fun(P, {Acc0, Acc1}) -> - case P of - {register, _} -> - {Acc0, [P|Acc1]}; - _ -> - {[P|Acc0], Acc1} - end - end, {[], []}, Packets), + lists:foldl( + fun(P, {Acc0, Acc1}) -> + case P of + {register, _} -> + {Acc0, [P | Acc1]}; + _ -> + {[P | Acc0], Acc1} + end + end, + {[], []}, + Packets + ), {[{outgoing, lists:reverse(NPackets)}] ++ lists:reverse(NRegisters), Channel}. -message_to_packet(MsgId, Message, - #channel{registry = Registry, - clientinfo = #{clientid := ClientId}}) -> - QoS = emqx_message:qos(Message), +message_to_packet( + MsgId, + Message, + #channel{ + registry = Registry, + clientinfo = #{clientid := ClientId} + } +) -> + QoS = emqx_message:qos(Message), Topic = emqx_message:topic(Message), Payload = emqx_message:payload(Message), - NMsgId = case QoS of - ?QOS_0 -> 0; - _ -> MsgId - end, + NMsgId = + case QoS of + ?QOS_0 -> 0; + _ -> MsgId + end, case emqx_sn_registry:lookup_topic_id(Registry, ClientId, Topic) of {predef, PredefTopicId} -> Flags = #mqtt_sn_flags{qos = QoS, topic_id_type = ?SN_PREDEFINED_TOPIC}, @@ -1439,12 +1777,11 @@ message_to_packet(MsgId, Message, %% Handle call %%-------------------------------------------------------------------- --spec handle_call(Req :: term(), From :: term(), channel()) - -> {reply, Reply :: term(), channel()} - | {reply, Reply :: term(), replies(), channel()} - | {shutdown, Reason :: term(), Reply :: term(), channel()} - | {shutdown, Reason :: term(), Reply :: term(), - emqx_types:packet(), channel()}. +-spec handle_call(Req :: term(), From :: term(), channel()) -> + {reply, Reply :: term(), channel()} + | {reply, Reply :: term(), replies(), channel()} + | {shutdown, Reason :: term(), Reply :: term(), channel()} + | {shutdown, Reason :: term(), Reply :: term(), emqx_types:packet(), channel()}. handle_call({subscribe, Topic, SubOpts}, _From, Channel) -> %% XXX: Only support short_topic_name SubProps = maps:get(sub_props, SubOpts, #{}), @@ -1452,8 +1789,7 @@ handle_call({subscribe, Topic, SubOpts}, _From, Channel) -> short_topic_name -> case byte_size(Topic) of 2 -> - case do_subscribe({?SN_INVALID_TOPIC_ID, - Topic, SubOpts}, Channel) of + case do_subscribe({?SN_INVALID_TOPIC_ID, Topic, SubOpts}, Channel) of {ok, {_, NTopicName, NSubOpts}, NChannel} -> reply({ok, {NTopicName, NSubOpts}}, NChannel); {error, ?SN_RC2_EXCEED_LIMITATION} -> @@ -1462,27 +1798,22 @@ handle_call({subscribe, Topic, SubOpts}, _From, Channel) -> _ -> reply({error, bad_topic_name}, Channel) end; - predefined_topic_id -> + predefined_topic_id -> reply({error, only_support_short_name_topic}, Channel); _ -> reply({error, only_support_short_name_topic}, Channel) end; - handle_call({unsubscribe, Topic}, _From, Channel) -> TopicFilters = [emqx_topic:parse(Topic)], {ok, _, NChannel} = do_unsubscribe(TopicFilters, Channel), reply(ok, NChannel); - handle_call(subscriptions, _From, Channel = #channel{session = Session}) -> reply({ok, maps:to_list(emqx_session:info(subscriptions, Session))}, Channel); - handle_call(kick, _From, Channel) -> NChannel = ensure_disconnected(kicked, Channel), shutdown_and_reply(kicked, ok, NChannel); - handle_call(discard, _From, Channel) -> shutdown_and_reply(discarded, ok, Channel); - handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) -> %% In MQTT-SN the meaning of a “clean session” is extended to the Will %% feature, i.e. not only the subscriptions are persistent, but also the @@ -1490,15 +1821,19 @@ handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) - %% %% FIXME: We need to reply WillMsg and Session reply(Session, Channel#channel{takeover = true}); - -handle_call({takeover, 'end'}, _From, Channel = #channel{session = Session, - pendings = Pendings}) -> +handle_call( + {takeover, 'end'}, + _From, + Channel = #channel{ + session = Session, + pendings = Pendings + } +) -> ok = emqx_session:takeover(Session), %% TODO: Should not drain deliver here (side effect) Delivers = emqx_misc:drain_deliver(), AllPendings = lists:append(Delivers, Pendings), shutdown_and_reply(takenover, AllPendings, Channel); - %handle_call(list_authz_cache, _From, Channel) -> % {reply, emqx_authz_cache:list_authz_cache(), Channel}; @@ -1509,17 +1844,18 @@ handle_call({takeover, 'end'}, _From, Channel = #channel{session = Session, % reply(ok, Channel#channel{quota = Quota}); handle_call(Req, _From, Channel) -> - ?SLOG(error, #{ msg => "unexpected_call" - , call => Req - }), + ?SLOG(error, #{ + msg => "unexpected_call", + call => Req + }), reply(ignored, Channel). %%-------------------------------------------------------------------- %% Handle Cast %%-------------------------------------------------------------------- --spec handle_cast(Req :: term(), channel()) - -> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. +-spec handle_cast(Req :: term(), channel()) -> + ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. handle_cast(_Req, Channel) -> {ok, Channel}. @@ -1527,20 +1863,26 @@ handle_cast(_Req, Channel) -> %% Handle Info %%-------------------------------------------------------------------- --spec handle_info(Info :: term(), channel()) - -> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. +-spec handle_info(Info :: term(), channel()) -> + ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = idle}) -> +handle_info( + {sock_closed, Reason}, + Channel = #channel{conn_state = idle} +) -> shutdown(Reason, Channel); - -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = connecting}) -> +handle_info( + {sock_closed, Reason}, + Channel = #channel{conn_state = connecting} +) -> shutdown(Reason, Channel); - -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = connected, - clientinfo = _ClientInfo}) -> +handle_info( + {sock_closed, Reason}, + Channel = #channel{ + conn_state = connected, + clientinfo = _ClientInfo + } +) -> %% XXX: Flapping detect ??? %% How to get the flapping detect policy ??? %emqx_zone:enable_flapping_detect(Zone) @@ -1550,49 +1892,53 @@ handle_info({sock_closed, Reason}, {ok, NChannel1} -> {ok, {event, disconnected}, NChannel1}; Shutdown -> Shutdown end; - -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = disconnected}) -> - ?SLOG(error, #{ msg => "unexpected_sock_closed" - , reason => Reason - }), +handle_info( + {sock_closed, Reason}, + Channel = #channel{conn_state = disconnected} +) -> + ?SLOG(error, #{ + msg => "unexpected_sock_closed", + reason => Reason + }), {ok, Channel}; - handle_info(clean_authz_cache, Channel) -> ok = emqx_authz_cache:empty_authz_cache(), {ok, Channel}; - handle_info({subscribe, _}, Channel) -> - {ok, Channel}; - + {ok, Channel}; handle_info({register, TopicName}, Channel) -> case ensure_registered_topic_name(TopicName, Channel) of {error, Reason} -> - ?SLOG(error, #{ msg => "register_topic_failed" - , topic_name => TopicName - , reason => Reason - }), + ?SLOG(error, #{ + msg => "register_topic_failed", + topic_name => TopicName, + reason => Reason + }), {ok, Channel}; {ok, TopicId} -> handle_out(register, {TopicId, TopicName}, Channel) end; - handle_info(Info, Channel) -> - ?SLOG(error, #{ msg => "unexpected_info" - , info => Info - }), + ?SLOG(error, #{ + msg => "unexpected_info", + info => Info + }), {ok, Channel}. maybe_shutdown(Reason, Channel = #channel{conninfo = ConnInfo}) -> case maps:get(expiry_interval, ConnInfo) of - ?UINT_MAX -> {ok, Channel}; + ?UINT_MAX -> + {ok, Channel}; I when I > 0 -> {ok, ensure_timer(expire_timer, I, Channel)}; - _ -> shutdown(Reason, Channel) + _ -> + shutdown(Reason, Channel) end. -ensure_registered_topic_name(TopicName, - Channel = #channel{registry = Registry}) -> +ensure_registered_topic_name( + TopicName, + Channel = #channel{registry = Registry} +) -> ClientId = clientid(Channel), case emqx_sn_registry:lookup_topic_id(Registry, ClientId, TopicName) of undefined -> @@ -1607,13 +1953,20 @@ ensure_registered_topic_name(TopicName, %%-------------------------------------------------------------------- %% Ensure disconnected -ensure_disconnected(Reason, Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_disconnected( + Reason, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, - ok = run_hooks(Ctx, 'client.disconnected', - [ClientInfo, Reason, NConnInfo]), + ok = run_hooks( + Ctx, + 'client.disconnected', + [ClientInfo, Reason, NConnInfo] + ), Channel#channel{conninfo = NConnInfo, conn_state = disconnected}. mabye_publish_will_msg(Channel = #channel{will_msg = undefined}) -> @@ -1630,94 +1983,126 @@ publish_will_msg(Msg) -> %% Handle Delivers from broker to client %%-------------------------------------------------------------------- --spec handle_deliver(list(emqx_types:deliver()), channel()) - -> {ok, channel()} - | {ok, replies(), channel()}. -handle_deliver(Delivers, Channel = #channel{ - ctx = Ctx, - conn_state = ConnState, - session = Session, - clientinfo = ClientInfo = #{clientid := ClientId}}) - when ConnState =:= disconnected; - ConnState =:= asleep -> - NSession = emqx_session:enqueue(ClientInfo, - ignore_local(maybe_nack(Delivers), ClientId, Session, Ctx), - Session - ), +-spec handle_deliver(list(emqx_types:deliver()), channel()) -> + {ok, channel()} + | {ok, replies(), channel()}. +handle_deliver( + Delivers, + Channel = #channel{ + ctx = Ctx, + conn_state = ConnState, + session = Session, + clientinfo = ClientInfo = #{clientid := ClientId} + } +) when + ConnState =:= disconnected; + ConnState =:= asleep +-> + NSession = emqx_session:enqueue( + ClientInfo, + ignore_local(maybe_nack(Delivers), ClientId, Session, Ctx), + Session + ), {ok, Channel#channel{session = NSession}}; - %% There are two scenarios need to cache delivering messages: %% 1. it is being takeover by other channel %% 2. it is being resume registered topic-names -handle_deliver(Delivers, Channel = #channel{ - ctx = Ctx, - takeover = Takeover, - pendings = Pendings, - session = Session, - resuming = Resuming, - clientinfo = #{clientid := ClientId}}) - when Takeover == true; Resuming == true -> +handle_deliver( + Delivers, + Channel = #channel{ + ctx = Ctx, + takeover = Takeover, + pendings = Pendings, + session = Session, + resuming = Resuming, + clientinfo = #{clientid := ClientId} + } +) when + Takeover == true; Resuming == true +-> NPendings = lists:append( - Pendings, - ignore_local(maybe_nack(Delivers), ClientId, Session, Ctx) - ), + Pendings, + ignore_local(maybe_nack(Delivers), ClientId, Session, Ctx) + ), {ok, Channel#channel{pendings = NPendings}}; - -handle_deliver(Delivers, Channel = #channel{ - ctx = Ctx, - session = Session, - clientinfo = ClientInfo = #{clientid := ClientId}}) -> - case emqx_session:deliver(ClientInfo, - ignore_local(Delivers, ClientId, Session, Ctx), - Session - ) of +handle_deliver( + Delivers, + Channel = #channel{ + ctx = Ctx, + session = Session, + clientinfo = ClientInfo = #{clientid := ClientId} + } +) -> + case + emqx_session:deliver( + ClientInfo, + ignore_local(Delivers, ClientId, Session, Ctx), + Session + ) + of {ok, Publishes, NSession} -> NChannel = Channel#channel{session = NSession}, - handle_out(publish, Publishes, - ensure_timer(retry_timer, NChannel)); + handle_out( + publish, + Publishes, + ensure_timer(retry_timer, NChannel) + ); {ok, NSession} -> {ok, Channel#channel{session = NSession}} end. ignore_local(Delivers, Subscriber, Session, Ctx) -> Subs = emqx_session:info(subscriptions, Session), - lists:dropwhile(fun({deliver, Topic, #message{from = Publisher}}) -> - case maps:find(Topic, Subs) of - {ok, #{nl := 1}} when Subscriber =:= Publisher -> - ok = metrics_inc(Ctx, 'delivery.dropped'), - ok = metrics_inc(Ctx, 'delivery.dropped.no_local'), - true; - _ -> - false - end - end, Delivers). + lists:dropwhile( + fun({deliver, Topic, #message{from = Publisher}}) -> + case maps:find(Topic, Subs) of + {ok, #{nl := 1}} when Subscriber =:= Publisher -> + ok = metrics_inc(Ctx, 'delivery.dropped'), + ok = metrics_inc(Ctx, 'delivery.dropped.no_local'), + true; + _ -> + false + end + end, + Delivers + ). %% Nack delivers from shared subscription maybe_nack(Delivers) -> lists:filter(fun not_nacked/1, Delivers). not_nacked({deliver, _Topic, Msg}) -> - not (emqx_shared_sub:is_ack_required(Msg) - andalso (ok == emqx_shared_sub:nack_no_connection(Msg))). + not (emqx_shared_sub:is_ack_required(Msg) andalso + (ok == emqx_shared_sub:nack_no_connection(Msg))). %%-------------------------------------------------------------------- %% Handle timeout %%-------------------------------------------------------------------- --spec handle_timeout(reference(), Msg :: term(), channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()}. -handle_timeout(_TRef, {keepalive, _StatVal}, - Channel = #channel{keepalive = undefined}) -> +-spec handle_timeout(reference(), Msg :: term(), channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()}. +handle_timeout( + _TRef, + {keepalive, _StatVal}, + Channel = #channel{keepalive = undefined} +) -> {ok, Channel}; -handle_timeout(_TRef, {keepalive, _StatVal}, - Channel = #channel{conn_state = ConnState}) - when ConnState =:= disconnected; - ConnState =:= asleep -> +handle_timeout( + _TRef, + {keepalive, _StatVal}, + Channel = #channel{conn_state = ConnState} +) when + ConnState =:= disconnected; + ConnState =:= asleep +-> {ok, Channel}; -handle_timeout(_TRef, {keepalive, StatVal}, - Channel = #channel{keepalive = Keepalive}) -> +handle_timeout( + _TRef, + {keepalive, StatVal}, + Channel = #channel{keepalive = Keepalive} +) -> case emqx_keepalive:check(StatVal, Keepalive) of {ok, NKeepalive} -> NChannel = Channel#channel{keepalive = NKeepalive}, @@ -1725,15 +2110,23 @@ handle_timeout(_TRef, {keepalive, StatVal}, {error, timeout} -> handle_out(disconnect, ?SN_RC2_KEEPALIVE_TIMEOUT, Channel) end; - -handle_timeout(_TRef, retry_delivery, - Channel = #channel{conn_state = disconnected}) -> +handle_timeout( + _TRef, + retry_delivery, + Channel = #channel{conn_state = disconnected} +) -> {ok, Channel}; -handle_timeout(_TRef, retry_delivery, - Channel = #channel{conn_state = asleep}) -> +handle_timeout( + _TRef, + retry_delivery, + Channel = #channel{conn_state = asleep} +) -> {ok, reset_timer(retry_timer, Channel)}; -handle_timeout(_TRef, retry_delivery, - Channel = #channel{session = Session, clientinfo = ClientInfo}) -> +handle_timeout( + _TRef, + retry_delivery, + Channel = #channel{session = Session, clientinfo = ClientInfo} +) -> case emqx_session:retry(ClientInfo, Session) of {ok, NSession} -> {ok, clean_timer(retry_timer, Channel#channel{session = NSession})}; @@ -1742,49 +2135,59 @@ handle_timeout(_TRef, retry_delivery, %% XXX: These replay messages should awaiting register acked? handle_out(publish, Publishes, reset_timer(retry_timer, Timeout, NChannel)) end; - -handle_timeout(_TRef, expire_awaiting_rel, - Channel = #channel{conn_state = disconnected}) -> +handle_timeout( + _TRef, + expire_awaiting_rel, + Channel = #channel{conn_state = disconnected} +) -> {ok, Channel}; -handle_timeout(_TRef, expire_awaiting_rel, - Channel = #channel{conn_state = asleep}) -> +handle_timeout( + _TRef, + expire_awaiting_rel, + Channel = #channel{conn_state = asleep} +) -> {ok, reset_timer(await_timer, Channel)}; -handle_timeout(_TRef, expire_awaiting_rel, - Channel = #channel{session = Session, clientinfo = ClientInfo}) -> +handle_timeout( + _TRef, + expire_awaiting_rel, + Channel = #channel{session = Session, clientinfo = ClientInfo} +) -> case emqx_session:expire(ClientInfo, awaiting_rel, Session) of {ok, NSession} -> {ok, clean_timer(await_timer, Channel#channel{session = NSession})}; {ok, Timeout, NSession} -> {ok, reset_timer(await_timer, Timeout, Channel#channel{session = NSession})} end; - -handle_timeout(_TRef, {retry_register, RetryTimes}, - Channel = #channel{register_inflight = {TopicId, MsgId, TopicName}}) -> +handle_timeout( + _TRef, + {retry_register, RetryTimes}, + Channel = #channel{register_inflight = {TopicId, MsgId, TopicName}} +) -> case RetryTimes < ?MAX_RETRY_TIMES of true -> Outgoing = {outgoing, ?SN_REGISTER_MSG(TopicId, MsgId, TopicName)}, {ok, Outgoing, ensure_register_timer(RetryTimes + 1, Channel)}; false -> - ?SLOG(error, #{ msg => "register_topic_reached_max_retry_times" - , register_request => - #{ topic_id => TopicId - , msg_id => MsgId - , topic_name => TopicName - } - }), + ?SLOG(error, #{ + msg => "register_topic_reached_max_retry_times", + register_request => + #{ + topic_id => TopicId, + msg_id => MsgId, + topic_name => TopicName + } + }), handle_out(disconnect, ?SN_RC2_REACHED_MAX_RETRY, Channel) end; - handle_timeout(_TRef, expire_session, Channel) -> shutdown(expired, Channel); - handle_timeout(_TRef, expire_asleep, Channel) -> shutdown(asleep_timeout, Channel); - handle_timeout(_TRef, Msg, Channel) -> - ?SLOG(error, #{ msg => "unexpected_timeout" - , timeout_msg => Msg - }), + ?SLOG(error, #{ + msg => "unexpected_timeout", + timeout_msg => Msg + }), {ok, Channel}. %%-------------------------------------------------------------------- @@ -1809,14 +2212,25 @@ shutdown_and_reply(Reason, Reply, Channel) -> %%-------------------------------------------------------------------- %% Will -update_will_topic(undefined, #mqtt_sn_flags{qos = QoS, retain = Retain}, - Topic, ClientId) -> +update_will_topic( + undefined, + #mqtt_sn_flags{qos = QoS, retain = Retain}, + Topic, + ClientId +) -> WillMsg0 = emqx_message:make(ClientId, QoS, Topic, <<>>), emqx_message:set_flag(retain, Retain, WillMsg0); -update_will_topic(Will, #mqtt_sn_flags{qos = QoS, retain = Retain}, - Topic, _ClientId) -> - emqx_message:set_flag(retain, Retain, - Will#message{qos = QoS, topic = Topic}). +update_will_topic( + Will, + #mqtt_sn_flags{qos = QoS, retain = Retain}, + Topic, + _ClientId +) -> + emqx_message:set_flag( + retain, + Retain, + Will#message{qos = QoS, topic = Topic} + ). update_will_msg(Will, Payload) -> Will#message{payload = Payload}. @@ -1824,13 +2238,17 @@ update_will_msg(Will, Payload) -> %%-------------------------------------------------------------------- %% Timer -ensure_asleep_timer(Channel = #channel{asleep_timer_duration = Duration}) - when is_integer(Duration) -> +ensure_asleep_timer(Channel = #channel{asleep_timer_duration = Duration}) when + is_integer(Duration) +-> ensure_asleep_timer(Duration, Channel). ensure_asleep_timer(Durtion, Channel) -> - ensure_timer(asleep_timer, timer:seconds(Durtion), - Channel#channel{asleep_timer_duration = Durtion}). + ensure_timer( + asleep_timer, + timer:seconds(Durtion), + Channel#channel{asleep_timer_duration = Durtion} + ). ensure_register_timer(Channel) -> ensure_register_timer(0, Channel). @@ -1853,8 +2271,9 @@ ensure_timer(Name, Channel = #channel{timers = Timers}) -> TRef = maps:get(Name, Timers, undefined), Time = interval(Name, Channel), case TRef == undefined andalso is_integer(Time) andalso Time > 0 of - true -> ensure_timer(Name, Time, Channel); - false -> Channel %% Timer disabled or exists + true -> ensure_timer(Name, Time, Channel); + %% Timer disabled or exists + false -> Channel end. ensure_timer(Name, Time, Channel = #channel{timers = Timers}) -> diff --git a/apps/emqx_gateway/src/mqttsn/emqx_sn_frame.erl b/apps/emqx_gateway/src/mqttsn/emqx_sn_frame.erl index 15f9c5288..4313fb952 100644 --- a/apps/emqx_gateway/src/mqttsn/emqx_sn_frame.erl +++ b/apps/emqx_gateway/src/mqttsn/emqx_sn_frame.erl @@ -22,26 +22,28 @@ -include("src/mqttsn/include/emqx_sn.hrl"). --export([ initial_parse_state/1 - , serialize_opts/0 - , parse/2 - , serialize_pkt/2 - , message_type/1 - , format/1 - , type/1 - , is_message/1 - ]). +-export([ + initial_parse_state/1, + serialize_opts/0, + parse/2, + serialize_pkt/2, + message_type/1, + format/1, + type/1, + is_message/1 +]). --define(flag, 1/binary). --define(byte, 8/big-integer). --define(short, 16/big-integer). +-define(flag, 1 / binary). +-define(byte, 8 / big - integer). +-define(short, 16 / big - integer). -type parse_state() :: #{}. -type serialize_opts() :: #{}. --export_type([ parse_state/0 - , serialize_opts/0 - ]). +-export_type([ + parse_state/0, + serialize_opts/0 +]). %%-------------------------------------------------------------------- %% Initial @@ -95,9 +97,13 @@ parse_var(?SN_PUBLISH, <>) -> {TopicId, MsgId, ReturnCode}; -parse_var(PubRec, <>) when PubRec == ?SN_PUBREC; PubRec == ?SN_PUBREL; PubRec == ?SN_PUBCOMP -> +parse_var(PubRec, <>) when + PubRec == ?SN_PUBREC; PubRec == ?SN_PUBREL; PubRec == ?SN_PUBCOMP +-> MsgId; -parse_var(Sub, <>) when Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE -> +parse_var(Sub, <>) when + Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE +-> #mqtt_sn_flags{topic_id_type = IdType} = Flags = parse_flags(Sub, FlagsBin), {Flags, MsgId, parse_topic(IdType, Topic)}; parse_var(?SN_SUBACK, <>) -> @@ -131,8 +137,9 @@ parse_flags(?SN_WILLTOPIC, <<_D:1, QoS:2, Retain:1, _Will:1, _C:1, _:2>>) -> #mqtt_sn_flags{qos = QoS, retain = bool(Retain)}; parse_flags(?SN_PUBLISH, <>) -> #mqtt_sn_flags{dup = bool(Dup), qos = QoS, retain = bool(Retain), topic_id_type = IdType}; -parse_flags(Sub, <>) - when Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE -> +parse_flags(Sub, <>) when + Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE +-> #mqtt_sn_flags{dup = bool(Dup), qos = QoS, topic_id_type = IdType}; parse_flags(?SN_SUBACK, <<_D:1, QoS:2, _R:1, _W:1, _C:1, _Id:2>>) -> #mqtt_sn_flags{qos = QoS}; @@ -141,17 +148,18 @@ parse_flags(?SN_WILLTOPICUPD, <<_D:1, QoS:2, Retain:1, _W:1, _C:1, _Id:2>>) -> parse_flags(_Type, _) -> error(malformed_message_flags). -parse_topic(2#00, Topic) -> Topic; +parse_topic(2#00, Topic) -> Topic; parse_topic(2#01, <>) -> Id; -parse_topic(2#10, Topic) -> Topic; -parse_topic(2#11, Topic) -> Topic. +parse_topic(2#10, Topic) -> Topic; +parse_topic(2#11, Topic) -> Topic. %%-------------------------------------------------------------------- %% Serialize MQTT-SN Message %%-------------------------------------------------------------------- serialize_pkt(#mqtt_sn_message{type = Type, variable = Var}, Opts) -> - VarBin = serialize(Type, Var, Opts), VarLen = size(VarBin), + VarBin = serialize(Type, Var, Opts), + VarLen = size(VarBin), if VarLen < 254 -> <<(VarLen + 2), Type, VarBin/binary>>; true -> <<16#01, (VarLen + 4):?short, Type, VarBin/binary>> @@ -182,18 +190,33 @@ serialize(?SN_REGISTER, {TopicId, MsgId, TopicName}, _Opts) -> <>; serialize(?SN_REGACK, {TopicId, MsgId, ReturnCode}, _Opts) -> <>; -serialize(?SN_PUBLISH, {Flags=#mqtt_sn_flags{topic_id_type = ?SN_NORMAL_TOPIC}, TopicId, MsgId, Data}, _Opts) -> +serialize( + ?SN_PUBLISH, + {Flags = #mqtt_sn_flags{topic_id_type = ?SN_NORMAL_TOPIC}, TopicId, MsgId, Data}, + _Opts +) -> <<(serialize_flags(Flags))/binary, TopicId:?short, MsgId:?short, Data/binary>>; -serialize(?SN_PUBLISH, {Flags=#mqtt_sn_flags{topic_id_type = ?SN_PREDEFINED_TOPIC}, TopicId, MsgId, Data}, _Opts) -> +serialize( + ?SN_PUBLISH, + {Flags = #mqtt_sn_flags{topic_id_type = ?SN_PREDEFINED_TOPIC}, TopicId, MsgId, Data}, + _Opts +) -> <<(serialize_flags(Flags))/binary, TopicId:?short, MsgId:?short, Data/binary>>; -serialize(?SN_PUBLISH, {Flags=#mqtt_sn_flags{topic_id_type = ?SN_SHORT_TOPIC}, STopicName, MsgId, Data}, _Opts) -> +serialize( + ?SN_PUBLISH, + {Flags = #mqtt_sn_flags{topic_id_type = ?SN_SHORT_TOPIC}, STopicName, MsgId, Data}, + _Opts +) -> <<(serialize_flags(Flags))/binary, STopicName:2/binary, MsgId:?short, Data/binary>>; serialize(?SN_PUBACK, {TopicId, MsgId, ReturnCode}, _Opts) -> <>; -serialize(PubRec, MsgId, _Opts) when PubRec == ?SN_PUBREC; PubRec == ?SN_PUBREL; PubRec == ?SN_PUBCOMP -> +serialize(PubRec, MsgId, _Opts) when + PubRec == ?SN_PUBREC; PubRec == ?SN_PUBREL; PubRec == ?SN_PUBCOMP +-> <>; -serialize(Sub, {Flags = #mqtt_sn_flags{topic_id_type = IdType}, MsgId, Topic}, _Opts) - when Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE -> +serialize(Sub, {Flags = #mqtt_sn_flags{topic_id_type = IdType}, MsgId, Topic}, _Opts) when + Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE +-> <<(serialize_flags(Flags))/binary, MsgId:16, (serialize_topic(IdType, Topic))/binary>>; serialize(?SN_SUBACK, {Flags, TopicId, MsgId, ReturnCode}, _Opts) -> <<(serialize_flags(Flags))/binary, TopicId:?short, MsgId:?short, ReturnCode>>; @@ -216,19 +239,32 @@ serialize(?SN_DISCONNECT, undefined, _Opts) -> serialize(?SN_DISCONNECT, Duration, _Opts) -> <>. -serialize_flags(#mqtt_sn_flags{dup = Dup, qos = QoS, retain = Retain, will = Will, - clean_start = CleanStart, topic_id_type = IdType}) -> - <<(bool(Dup)):1, (i(QoS)):2, (bool(Retain)):1, (bool(Will)):1, (bool(CleanStart)):1, (i(IdType)):2>>. +serialize_flags(#mqtt_sn_flags{ + dup = Dup, + qos = QoS, + retain = Retain, + will = Will, + clean_start = CleanStart, + topic_id_type = IdType +}) -> + << + (bool(Dup)):1, + (i(QoS)):2, + (bool(Retain)):1, + (bool(Will)):1, + (bool(CleanStart)):1, + (i(IdType)):2 + >>. serialize_topic(2#00, Topic) -> Topic; -serialize_topic(2#01, Id) -> <>; +serialize_topic(2#01, Id) -> <>; serialize_topic(2#10, Topic) -> Topic; serialize_topic(2#11, Topic) -> Topic. bool(0) -> false; bool(1) -> true; bool(false) -> 0; -bool(true) -> 1; +bool(true) -> 1; bool(undefined) -> 0. i(undefined) -> 0; @@ -293,22 +329,33 @@ message_type(Type) -> format(?SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId)) -> #mqtt_sn_flags{ - will = Will, - clean_start = CleanStart} = Flags, - io_lib:format("SN_CONNECT(W~w, C~w, ProtocolId=~w, Duration=~w, " - "ClientId=~ts)", - [bool(Will), bool(CleanStart), - ProtocolId, Duration, ClientId]); + will = Will, + clean_start = CleanStart + } = Flags, + io_lib:format( + "SN_CONNECT(W~w, C~w, ProtocolId=~w, Duration=~w, " + "ClientId=~ts)", + [ + bool(Will), + bool(CleanStart), + ProtocolId, + Duration, + ClientId + ] + ); format(?SN_CONNACK_MSG(ReturnCode)) -> io_lib:format("SN_CONNACK(ReturnCode=~w)", [ReturnCode]); format(?SN_WILLTOPICREQ_MSG()) -> "SN_WILLTOPICREQ()"; format(?SN_WILLTOPIC_MSG(Flags, Topic)) -> #mqtt_sn_flags{ - qos = QoS, - retain = Retain} = Flags, - io_lib:format("SN_WILLTOPIC(Q~w, R~w, Topic=~s)", - [QoS, bool(Retain), Topic]); + qos = QoS, + retain = Retain + } = Flags, + io_lib:format( + "SN_WILLTOPIC(Q~w, R~w, Topic=~s)", + [QoS, bool(Retain), Topic] + ); format(?SN_WILLTOPIC_EMPTY_MSG) -> "SN_WILLTOPIC(_)"; format(?SN_WILLMSGREQ_MSG()) -> @@ -317,17 +364,29 @@ format(?SN_WILLMSG_MSG(Msg)) -> io_lib:format("SN_WILLMSG_MSG(Msg=~p)", [Msg]); format(?SN_PUBLISH_MSG(Flags, TopicId, MsgId, Data)) -> #mqtt_sn_flags{ - dup = Dup, - qos = QoS, - retain = Retain, - topic_id_type = TopicIdType} = Flags, - io_lib:format("SN_PUBLISH(D~w, Q~w, R~w, TopicIdType=~w, TopicId=~w, " - "MsgId=~w, Payload=~p)", - [bool(Dup), QoS, bool(Retain), - TopicIdType, TopicId, MsgId, Data]); + dup = Dup, + qos = QoS, + retain = Retain, + topic_id_type = TopicIdType + } = Flags, + io_lib:format( + "SN_PUBLISH(D~w, Q~w, R~w, TopicIdType=~w, TopicId=~w, " + "MsgId=~w, Payload=~p)", + [ + bool(Dup), + QoS, + bool(Retain), + TopicIdType, + TopicId, + MsgId, + Data + ] + ); format(?SN_PUBACK_MSG(TopicId, MsgId, ReturnCode)) -> - io_lib:format("SN_PUBACK(TopicId=~w, MsgId=~w, ReturnCode=~w)", - [TopicId, MsgId, ReturnCode]); + io_lib:format( + "SN_PUBACK(TopicId=~w, MsgId=~w, ReturnCode=~w)", + [TopicId, MsgId, ReturnCode] + ); format(?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId)) -> io_lib:format("SN_PUBCOMP(MsgId=~w)", [MsgId]); format(?SN_PUBREC_MSG(?SN_PUBREC, MsgId)) -> @@ -336,72 +395,112 @@ format(?SN_PUBREC_MSG(?SN_PUBREL, MsgId)) -> io_lib:format("SN_PUBREL(MsgId=~w)", [MsgId]); format(?SN_SUBSCRIBE_MSG(Flags, Msgid, Topic)) -> #mqtt_sn_flags{ - dup = Dup, - qos = QoS, - topic_id_type = TopicIdType} = Flags, - io_lib:format("SN_SUBSCRIBE(D~w, Q~w, TopicIdType=~w, MsgId=~w, " - "TopicId=~w)", - [bool(Dup), QoS, TopicIdType, Msgid, Topic]); + dup = Dup, + qos = QoS, + topic_id_type = TopicIdType + } = Flags, + io_lib:format( + "SN_SUBSCRIBE(D~w, Q~w, TopicIdType=~w, MsgId=~w, " + "TopicId=~w)", + [bool(Dup), QoS, TopicIdType, Msgid, Topic] + ); format(?SN_SUBACK_MSG(Flags, TopicId, MsgId, ReturnCode)) -> #mqtt_sn_flags{qos = QoS} = Flags, - io_lib:format("SN_SUBACK(GrantedQoS=~w, MsgId=~w, TopicId=~w, " - "ReturnCode=~w)", - [QoS, MsgId, TopicId, ReturnCode]); + io_lib:format( + "SN_SUBACK(GrantedQoS=~w, MsgId=~w, TopicId=~w, " + "ReturnCode=~w)", + [QoS, MsgId, TopicId, ReturnCode] + ); format(?SN_UNSUBSCRIBE_MSG(Flags, Msgid, Topic)) -> #mqtt_sn_flags{topic_id_type = TopicIdType} = Flags, - io_lib:format("SN_UNSUBSCRIBE(TopicIdType=~w, MsgId=~w, TopicId=~w)", - [TopicIdType, Msgid, Topic]); + io_lib:format( + "SN_UNSUBSCRIBE(TopicIdType=~w, MsgId=~w, TopicId=~w)", + [TopicIdType, Msgid, Topic] + ); format(?SN_UNSUBACK_MSG(MsgId)) -> io_lib:format("SN_UNSUBACK(MsgId=~w)", [MsgId]); format(?SN_REGISTER_MSG(TopicId, MsgId, TopicName)) -> - io_lib:format("SN_REGISTER(TopicId=~w, MsgId=~w, TopicName=~s)", - [TopicId, MsgId, TopicName]); + io_lib:format( + "SN_REGISTER(TopicId=~w, MsgId=~w, TopicName=~s)", + [TopicId, MsgId, TopicName] + ); format(?SN_REGACK_MSG(TopicId, MsgId, ReturnCode)) -> - io_lib:format("SN_REGACK(TopicId=~w, MsgId=~w, ReturnCode=~w)", - [TopicId, MsgId, ReturnCode]); + io_lib:format( + "SN_REGACK(TopicId=~w, MsgId=~w, ReturnCode=~w)", + [TopicId, MsgId, ReturnCode] + ); format(?SN_PINGREQ_MSG(ClientId)) -> io_lib:format("SN_PINGREQ(ClientId=~s)", [ClientId]); format(?SN_PINGRESP_MSG()) -> "SN_PINGRESP()"; format(?SN_DISCONNECT_MSG(Duration)) -> io_lib:format("SN_DISCONNECT(Duration=~w)", [Duration]); - format(#mqtt_sn_message{type = Type, variable = Var}) -> - io_lib:format("mqtt_sn_message(type=~s, Var=~w)", - [emqx_sn_frame:message_type(Type), Var]). + io_lib:format( + "mqtt_sn_message(type=~s, Var=~w)", + [emqx_sn_frame:message_type(Type), Var] + ). -is_message(#mqtt_sn_message{type = Type}) - when Type == ?SN_PUBLISH -> +is_message(#mqtt_sn_message{type = Type}) when + Type == ?SN_PUBLISH +-> true; is_message(_) -> false. type(#mqtt_sn_message{type = Type}) -> type(Type); -type(?SN_ADVERTISE) -> advertise; -type(?SN_SEARCHGW) -> serachgw; -type(?SN_GWINFO) -> gwinfo; -type(?SN_CONNECT) -> connect; -type(?SN_CONNACK) -> connack; -type(?SN_WILLTOPICREQ) -> willtopicreq; -type(?SN_WILLTOPIC) -> willtopic; -type(?SN_WILLMSGREQ) -> willmsgreq; -type(?SN_WILLMSG) -> willmsg; -type(?SN_REGISTER) -> register; -type(?SN_REGACK) -> regack; -type(?SN_PUBLISH) -> publish; -type(?SN_PUBACK) -> puback; -type(?SN_PUBCOMP) -> pubcomp; -type(?SN_PUBREC) -> pubrec; -type(?SN_PUBREL) -> pubrel; -type(?SN_SUBSCRIBE) -> subscribe; -type(?SN_SUBACK) -> suback; -type(?SN_UNSUBSCRIBE) -> unsubscribe; -type(?SN_UNSUBACK) -> unsuback; -type(?SN_PINGREQ) -> pingreq; -type(?SN_PINGRESP) -> pingresp; -type(?SN_DISCONNECT) -> disconnect; -type(?SN_WILLTOPICUPD) -> willtopicupd; -type(?SN_WILLTOPICRESP) -> willtopicresp; -type(?SN_WILLMSGUPD) -> willmsgupd; -type(?SN_WILLMSGRESP) -> willmsgresp. +type(?SN_ADVERTISE) -> + advertise; +type(?SN_SEARCHGW) -> + serachgw; +type(?SN_GWINFO) -> + gwinfo; +type(?SN_CONNECT) -> + connect; +type(?SN_CONNACK) -> + connack; +type(?SN_WILLTOPICREQ) -> + willtopicreq; +type(?SN_WILLTOPIC) -> + willtopic; +type(?SN_WILLMSGREQ) -> + willmsgreq; +type(?SN_WILLMSG) -> + willmsg; +type(?SN_REGISTER) -> + register; +type(?SN_REGACK) -> + regack; +type(?SN_PUBLISH) -> + publish; +type(?SN_PUBACK) -> + puback; +type(?SN_PUBCOMP) -> + pubcomp; +type(?SN_PUBREC) -> + pubrec; +type(?SN_PUBREL) -> + pubrel; +type(?SN_SUBSCRIBE) -> + subscribe; +type(?SN_SUBACK) -> + suback; +type(?SN_UNSUBSCRIBE) -> + unsubscribe; +type(?SN_UNSUBACK) -> + unsuback; +type(?SN_PINGREQ) -> + pingreq; +type(?SN_PINGRESP) -> + pingresp; +type(?SN_DISCONNECT) -> + disconnect; +type(?SN_WILLTOPICUPD) -> + willtopicupd; +type(?SN_WILLTOPICRESP) -> + willtopicresp; +type(?SN_WILLMSGUPD) -> + willmsgupd; +type(?SN_WILLMSGRESP) -> + willmsgresp. diff --git a/apps/emqx_gateway/src/mqttsn/emqx_sn_impl.erl b/apps/emqx_gateway/src/mqttsn/emqx_sn_impl.erl index a99f81f0a..a51d2f855 100644 --- a/apps/emqx_gateway/src/mqttsn/emqx_sn_impl.erl +++ b/apps/emqx_gateway/src/mqttsn/emqx_sn_impl.erl @@ -21,29 +21,33 @@ -include_lib("emqx/include/logger.hrl"). --import(emqx_gateway_utils, - [ normalize_config/1 - , start_listeners/4 - , stop_listeners/2 - ]). +-import( + emqx_gateway_utils, + [ + normalize_config/1, + start_listeners/4, + stop_listeners/2 + ] +). %% APIs --export([ reg/0 - , unreg/0 - ]). +-export([ + reg/0, + unreg/0 +]). --export([ on_gateway_load/2 - , on_gateway_update/3 - , on_gateway_unload/2 - ]). +-export([ + on_gateway_load/2, + on_gateway_update/3, + on_gateway_unload/2 +]). %%-------------------------------------------------------------------- %% APIs %%-------------------------------------------------------------------- reg() -> - RegistryOptions = [ {cbkmod, ?MODULE} - ], + RegistryOptions = [{cbkmod, ?MODULE}], emqx_gateway_registry:reg(mqttsn, RegistryOptions). unreg() -> @@ -53,10 +57,13 @@ unreg() -> %% emqx_gateway_registry callbacks %%-------------------------------------------------------------------- -on_gateway_load(_Gateway = #{ name := GwName, - config := Config - }, Ctx) -> - +on_gateway_load( + _Gateway = #{ + name := GwName, + config := Config + }, + Ctx +) -> %% We Also need to start `emqx_sn_broadcast` & %% `emqx_sn_registry` process case maps:get(broadcast, Config, false) of @@ -66,32 +73,40 @@ on_gateway_load(_Gateway = #{ name := GwName, %% FIXME: Port = 1884, SnGwId = maps:get(gateway_id, Config, undefined), - _ = emqx_sn_broadcast:start_link(SnGwId, Port), ok + _ = emqx_sn_broadcast:start_link(SnGwId, Port), + ok end, PredefTopics = maps:get(predefined, Config, []), {ok, RegistrySvr} = emqx_sn_registry:start_link(GwName, PredefTopics), NConfig = maps:without( - [broadcast, predefined], - Config#{registry => emqx_sn_registry:lookup_name(RegistrySvr)} - ), + [broadcast, predefined], + Config#{registry => emqx_sn_registry:lookup_name(RegistrySvr)} + ), Listeners = emqx_gateway_utils:normalize_config(NConfig), - ModCfg = #{frame_mod => emqx_sn_frame, - chann_mod => emqx_sn_channel - }, + ModCfg = #{ + frame_mod => emqx_sn_frame, + chann_mod => emqx_sn_channel + }, - case start_listeners( - Listeners, GwName, Ctx, ModCfg) of + case + start_listeners( + Listeners, GwName, Ctx, ModCfg + ) + of {ok, ListenerPids} -> {ok, ListenerPids, _GwState = #{ctx => Ctx}}; {error, {Reason, Listener}} -> - throw({badconf, #{ key => listeners - , value => Listener - , reason => Reason - }}) + throw( + {badconf, #{ + key => listeners, + value => Listener, + reason => Reason + }} + ) end. on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> @@ -102,15 +117,21 @@ on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> on_gateway_unload(Gateway, GwState), on_gateway_load(Gateway#{config => Config}, Ctx) catch - Class : Reason : Stk -> - logger:error("Failed to update ~ts; " - "reason: {~0p, ~0p} stacktrace: ~0p", - [GwName, Class, Reason, Stk]), + Class:Reason:Stk -> + logger:error( + "Failed to update ~ts; " + "reason: {~0p, ~0p} stacktrace: ~0p", + [GwName, Class, Reason, Stk] + ), {error, Reason} end. -on_gateway_unload(_Gateway = #{ name := GwName, - config := Config - }, _GwState) -> +on_gateway_unload( + _Gateway = #{ + name := GwName, + config := Config + }, + _GwState +) -> Listeners = normalize_config(Config), stop_listeners(GwName, Listeners). diff --git a/apps/emqx_gateway/src/mqttsn/emqx_sn_registry.erl b/apps/emqx_gateway/src/mqttsn/emqx_sn_registry.erl index 667e3e305..6086ec7d6 100644 --- a/apps/emqx_gateway/src/mqttsn/emqx_sn_registry.erl +++ b/apps/emqx_gateway/src/mqttsn/emqx_sn_registry.erl @@ -24,25 +24,27 @@ -include("src/mqttsn/include/emqx_sn.hrl"). -include_lib("emqx/include/logger.hrl"). --export([ start_link/2 - ]). +-export([start_link/2]). --export([ register_topic/3 - , unregister_topic/2 - ]). +-export([ + register_topic/3, + unregister_topic/2 +]). --export([ lookup_topic/3 - , lookup_topic_id/3 - ]). +-export([ + lookup_topic/3, + lookup_topic_id/3 +]). %% gen_server callbacks --export([ init/1 - , handle_call/3 - , handle_cast/2 - , handle_info/2 - , terminate/2 - , code_change/3 - ]). +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 +]). -export([lookup_name/1]). @@ -52,21 +54,20 @@ -record(emqx_sn_registry, {key, value}). --type registry() :: {Tab :: atom(), - RegistryPid :: pid()}. +-type registry() :: {Tab :: atom(), RegistryPid :: pid()}. %%----------------------------------------------------------------------------- --spec start_link(atom(), list()) - -> ignore - | {ok, pid()} - | {error, Reason :: term()}. +-spec start_link(atom(), list()) -> + ignore + | {ok, pid()} + | {error, Reason :: term()}. start_link(InstaId, PredefTopics) -> gen_server:start_link(?MODULE, [InstaId, PredefTopics], []). --spec register_topic(registry(), emqx_types:clientid(), emqx_types:topic()) - -> integer() - | {error, term()}. +-spec register_topic(registry(), emqx_types:clientid(), emqx_types:topic()) -> + integer() + | {error, term()}. register_topic({_, Pid}, ClientId, TopicName) when is_binary(TopicName) -> case emqx_topic:wildcard(TopicName) of false -> @@ -75,23 +76,25 @@ register_topic({_, Pid}, ClientId, TopicName) when is_binary(TopicName) -> %% id by the gateway when sending PUBLISH messages to the client (not %% relevant in case of subscriptions to a short topic name or to a topic %% name which contains wildcard characters) - true -> {error, wildcard_topic} + true -> + {error, wildcard_topic} end. --spec lookup_topic(registry(), emqx_types:clientid(), pos_integer()) - -> undefined - | binary(). +-spec lookup_topic(registry(), emqx_types:clientid(), pos_integer()) -> + undefined + | binary(). lookup_topic({Tab, _}, ClientId, TopicId) when is_integer(TopicId) -> case lookup_element(Tab, {predef, TopicId}, 3) of undefined -> lookup_element(Tab, {ClientId, TopicId}, 3); - Topic -> Topic + Topic -> + Topic end. --spec lookup_topic_id(registry(), emqx_types:clientid(), emqx_types:topic()) - -> undefined - | pos_integer() - | {predef, integer()}. +-spec lookup_topic_id(registry(), emqx_types:clientid(), emqx_types:topic()) -> + undefined + | pos_integer() + | {predef, integer()}. lookup_topic_id({Tab, _}, ClientId, TopicName) when is_binary(TopicName) -> case lookup_element(Tab, {predef, TopicName}, 3) of undefined -> @@ -102,7 +105,11 @@ lookup_topic_id({Tab, _}, ClientId, TopicName) when is_binary(TopicName) -> %% @private lookup_element(Tab, Key, Pos) -> - try ets:lookup_element(Tab, Key, Pos) catch error:badarg -> undefined end. + try + ets:lookup_element(Tab, Key, Pos) + catch + error:badarg -> undefined + end. -spec unregister_topic(registry(), emqx_types:clientid()) -> ok. unregister_topic({_, Pid}, ClientId) -> @@ -123,32 +130,41 @@ init([InstaId, PredefTopics]) -> %% {ClientId, TopicName} -> TopicId Tab = name(InstaId), ok = mria:create_table(Tab, [ - {storage, ram_copies}, - {record_name, emqx_sn_registry}, - {attributes, record_info(fields, emqx_sn_registry)}, - {storage_properties, [{ets, [{read_concurrency, true}]}]}, - {rlog_shard, ?SN_SHARD} - ]), + {storage, ram_copies}, + {record_name, emqx_sn_registry}, + {attributes, record_info(fields, emqx_sn_registry)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]}, + {rlog_shard, ?SN_SHARD} + ]), ok = mria:wait_for_tables([Tab]), MaxPredefId = lists:foldl( - fun(#{id := TopicId, topic := TopicName0}, AccId) -> - TopicName = iolist_to_binary(TopicName0), - mria:dirty_write(Tab, #emqx_sn_registry{ - key = {predef, TopicId}, - value = TopicName} - ), - mria:dirty_write(Tab, #emqx_sn_registry{ - key = {predef, TopicName}, - value = TopicId} - ), - if TopicId > AccId -> TopicId; true -> AccId end - end, 0, PredefTopics), + fun(#{id := TopicId, topic := TopicName0}, AccId) -> + TopicName = iolist_to_binary(TopicName0), + mria:dirty_write(Tab, #emqx_sn_registry{ + key = {predef, TopicId}, + value = TopicName + }), + mria:dirty_write(Tab, #emqx_sn_registry{ + key = {predef, TopicName}, + value = TopicId + }), + if + TopicId > AccId -> TopicId; + true -> AccId + end + end, + 0, + PredefTopics + ), {ok, #state{tabname = Tab, max_predef_topic_id = MaxPredefId}}. -handle_call({register, ClientId, TopicName}, _From, - State = #state{tabname = Tab, max_predef_topic_id = PredefId}) -> +handle_call( + {register, ClientId, TopicName}, + _From, + State = #state{tabname = Tab, max_predef_topic_id = PredefId} +) -> case lookup_topic_id({Tab, self()}, ClientId, TopicName) of - {predef, PredefTopicId} when is_integer(PredefTopicId) -> + {predef, PredefTopicId} when is_integer(PredefTopicId) -> {reply, PredefTopicId, State}; TopicId when is_integer(TopicId) -> {reply, TopicId, State}; @@ -158,15 +174,30 @@ handle_call({register, ClientId, TopicName}, _From, {reply, {error, too_large}, State}; TopicId -> Fun = fun() -> - mnesia:write(Tab, #emqx_sn_registry{ - key = {ClientId, next_topic_id}, - value = TopicId + 1}, write), - mnesia:write(Tab, #emqx_sn_registry{ - key = {ClientId, TopicName}, - value = TopicId}, write), - mnesia:write(Tab, #emqx_sn_registry{ - key = {ClientId, TopicId}, - value = TopicName}, write) + mnesia:write( + Tab, + #emqx_sn_registry{ + key = {ClientId, next_topic_id}, + value = TopicId + 1 + }, + write + ), + mnesia:write( + Tab, + #emqx_sn_registry{ + key = {ClientId, TopicName}, + value = TopicId + }, + write + ), + mnesia:write( + Tab, + #emqx_sn_registry{ + key = {ClientId, TopicId}, + value = TopicName + }, + write + ) end, case mria:transaction(?SN_SHARD, Fun) of {atomic, ok} -> @@ -176,36 +207,39 @@ handle_call({register, ClientId, TopicName}, _From, end end end; - handle_call({unregister, ClientId}, _From, State = #state{tabname = Tab}) -> Registry = mnesia:dirty_match_object( - Tab, - {emqx_sn_registry, {ClientId, '_'}, '_'} - ), - lists:foreach(fun(R) -> - mria:dirty_delete_object(Tab, R) - end, Registry), + Tab, + {emqx_sn_registry, {ClientId, '_'}, '_'} + ), + lists:foreach( + fun(R) -> + mria:dirty_delete_object(Tab, R) + end, + Registry + ), {reply, ok, State}; - handle_call(name, _From, State = #state{tabname = Tab}) -> {reply, {Tab, self()}, State}; - handle_call(Req, _From, State) -> - ?SLOG(error, #{ msg => "unexpected_call" - , call => Req - }), + ?SLOG(error, #{ + msg => "unexpected_call", + call => Req + }), {reply, ignored, State}. handle_cast(Msg, State) -> - ?SLOG(error, #{ msg => "unexpected_cast" - , cast => Msg - }), + ?SLOG(error, #{ + msg => "unexpected_cast", + cast => Msg + }), {noreply, State}. handle_info(Info, State) -> - ?SLOG(error, #{ msg => "unexpected_info" - , info => Info - }), + ?SLOG(error, #{ + msg => "unexpected_info", + info => Info + }), {noreply, State}. terminate(_Reason, _State) -> @@ -219,5 +253,5 @@ code_change(_OldVsn, State, _Extra) -> next_topic_id(Tab, PredefId, ClientId) -> case mnesia:dirty_read(Tab, {ClientId, next_topic_id}) of [#emqx_sn_registry{value = Id}] -> Id; - [] -> PredefId + 1 + [] -> PredefId + 1 end. diff --git a/apps/emqx_gateway/src/mqttsn/include/emqx_sn.hrl b/apps/emqx_gateway/src/mqttsn/include/emqx_sn.hrl index 016fc1231..b636cf453 100644 --- a/apps/emqx_gateway/src/mqttsn/include/emqx_sn.hrl +++ b/apps/emqx_gateway/src/mqttsn/include/emqx_sn.hrl @@ -14,189 +14,213 @@ %% limitations under the License. %%-------------------------------------------------------------------- - %%-------------------------------------------------------------------- %% MQTT-SN Types %%-------------------------------------------------------------------- --define(SN_ADVERTISE, 16#00). --define(SN_SEARCHGW, 16#01). --define(SN_GWINFO, 16#02). --define(SN_CONNECT, 16#04). --define(SN_CONNACK, 16#05). --define(SN_WILLTOPICREQ, 16#06). --define(SN_WILLTOPIC, 16#07). --define(SN_WILLMSGREQ, 16#08). --define(SN_WILLMSG, 16#09). --define(SN_REGISTER, 16#0A). --define(SN_REGACK, 16#0B). --define(SN_PUBLISH, 16#0C). --define(SN_PUBACK, 16#0D). --define(SN_PUBCOMP, 16#0E). --define(SN_PUBREC, 16#0F). --define(SN_PUBREL, 16#10). --define(SN_SUBSCRIBE, 16#12). --define(SN_SUBACK, 16#13). --define(SN_UNSUBSCRIBE, 16#14). --define(SN_UNSUBACK, 16#15). --define(SN_PINGREQ, 16#16). --define(SN_PINGRESP, 16#17). --define(SN_DISCONNECT, 16#18). --define(SN_WILLTOPICUPD, 16#1A). +-define(SN_ADVERTISE, 16#00). +-define(SN_SEARCHGW, 16#01). +-define(SN_GWINFO, 16#02). +-define(SN_CONNECT, 16#04). +-define(SN_CONNACK, 16#05). +-define(SN_WILLTOPICREQ, 16#06). +-define(SN_WILLTOPIC, 16#07). +-define(SN_WILLMSGREQ, 16#08). +-define(SN_WILLMSG, 16#09). +-define(SN_REGISTER, 16#0A). +-define(SN_REGACK, 16#0B). +-define(SN_PUBLISH, 16#0C). +-define(SN_PUBACK, 16#0D). +-define(SN_PUBCOMP, 16#0E). +-define(SN_PUBREC, 16#0F). +-define(SN_PUBREL, 16#10). +-define(SN_SUBSCRIBE, 16#12). +-define(SN_SUBACK, 16#13). +-define(SN_UNSUBSCRIBE, 16#14). +-define(SN_UNSUBACK, 16#15). +-define(SN_PINGREQ, 16#16). +-define(SN_PINGRESP, 16#17). +-define(SN_DISCONNECT, 16#18). +-define(SN_WILLTOPICUPD, 16#1A). -define(SN_WILLTOPICRESP, 16#1B). --define(SN_WILLMSGUPD, 16#1C). --define(SN_WILLMSGRESP, 16#1D). +-define(SN_WILLMSGUPD, 16#1C). +-define(SN_WILLMSGRESP, 16#1D). --type(mqtt_sn_type() :: ?SN_ADVERTISE..?SN_WILLMSGRESP). +-type mqtt_sn_type() :: ?SN_ADVERTISE..?SN_WILLMSGRESP. --define(SN_RC_ACCEPTED, 16#00). --define(SN_RC_CONGESTION, 16#01). +-define(SN_RC_ACCEPTED, 16#00). +-define(SN_RC_CONGESTION, 16#01). -define(SN_RC_INVALID_TOPIC_ID, 16#02). --define(SN_RC_NOT_SUPPORTED, 16#03). +-define(SN_RC_NOT_SUPPORTED, 16#03). %% Custom Reason code by emqx --define(SN_RC2_NOT_AUTHORIZE, 16#80). --define(SN_RC2_FAILED_SESSION, 16#81). +-define(SN_RC2_NOT_AUTHORIZE, 16#80). +-define(SN_RC2_FAILED_SESSION, 16#81). -define(SN_RC2_KEEPALIVE_TIMEOUT, 16#82). -define(SN_RC2_EXCEED_LIMITATION, 16#83). -define(SN_RC2_REACHED_MAX_RETRY, 16#84). -define(QOS_NEG1, 3). --type(mqtt_sn_return_code() :: ?SN_RC_ACCEPTED .. ?SN_RC2_EXCEED_LIMITATION). +-type mqtt_sn_return_code() :: ?SN_RC_ACCEPTED..?SN_RC2_EXCEED_LIMITATION. %%-------------------------------------------------------------------- %% MQTT-SN Message %%-------------------------------------------------------------------- --record(mqtt_sn_flags, {dup = false, - qos = 0, - retain = false, - will = false, - clean_start = false, - topic_id_type = 0}). +-record(mqtt_sn_flags, { + dup = false, + qos = 0, + retain = false, + will = false, + clean_start = false, + topic_id_type = 0 +}). --type(mqtt_sn_flags() :: #mqtt_sn_flags{}). +-type mqtt_sn_flags() :: #mqtt_sn_flags{}. --type(mqtt_sn_variable() :: undefined | integer() | binary() | tuple()). +-type mqtt_sn_variable() :: undefined | integer() | binary() | tuple(). --record(mqtt_sn_message, {type :: mqtt_sn_type(), - variable :: mqtt_sn_variable()}). +-record(mqtt_sn_message, { + type :: mqtt_sn_type(), + variable :: mqtt_sn_variable() +}). --type(mqtt_sn_message() :: #mqtt_sn_message{}). +-type mqtt_sn_message() :: #mqtt_sn_message{}. --define(SN_ADVERTISE_MSG(GwId, Duration), - #mqtt_sn_message{type = ?SN_ADVERTISE, - variable = {GwId, Duration}}). +-define(SN_ADVERTISE_MSG(GwId, Duration), #mqtt_sn_message{ + type = ?SN_ADVERTISE, + variable = {GwId, Duration} +}). --define(SN_SEARCHGW_MSG(Radius), - #mqtt_sn_message{type = ?SN_SEARCHGW, - variable = Radius}). +-define(SN_SEARCHGW_MSG(Radius), #mqtt_sn_message{ + type = ?SN_SEARCHGW, + variable = Radius +}). --define(SN_GWINFO_MSG(GwId, GwAddr), - #mqtt_sn_message{type = ?SN_GWINFO, - variable = {GwId, GwAddr}}). +-define(SN_GWINFO_MSG(GwId, GwAddr), #mqtt_sn_message{ + type = ?SN_GWINFO, + variable = {GwId, GwAddr} +}). --define(SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId), - #mqtt_sn_message{type = ?SN_CONNECT, - variable = {Flags, ProtocolId, Duration, ClientId}}). +-define(SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId), #mqtt_sn_message{ + type = ?SN_CONNECT, + variable = {Flags, ProtocolId, Duration, ClientId} +}). --define(SN_CONNACK_MSG(ReturnCode), - #mqtt_sn_message{type = ?SN_CONNACK, variable = ReturnCode}). +-define(SN_CONNACK_MSG(ReturnCode), #mqtt_sn_message{type = ?SN_CONNACK, variable = ReturnCode}). --define(SN_WILLTOPICREQ_MSG(), - #mqtt_sn_message{type = ?SN_WILLTOPICREQ}). +-define(SN_WILLTOPICREQ_MSG(), #mqtt_sn_message{type = ?SN_WILLTOPICREQ}). --define(SN_WILLTOPIC_MSG(Flags, Topic), - #mqtt_sn_message{type = ?SN_WILLTOPIC, - variable = {Flags, Topic}}). +-define(SN_WILLTOPIC_MSG(Flags, Topic), #mqtt_sn_message{ + type = ?SN_WILLTOPIC, + variable = {Flags, Topic} +}). --define(SN_WILLTOPIC_EMPTY_MSG, - #mqtt_sn_message{type = ?SN_WILLTOPIC, - variable = undefined}). +-define(SN_WILLTOPIC_EMPTY_MSG, #mqtt_sn_message{ + type = ?SN_WILLTOPIC, + variable = undefined +}). --define(SN_WILLMSGREQ_MSG(), - #mqtt_sn_message{type = ?SN_WILLMSGREQ}). +-define(SN_WILLMSGREQ_MSG(), #mqtt_sn_message{type = ?SN_WILLMSGREQ}). --define(SN_WILLMSG_MSG(Msg), - #mqtt_sn_message{type = ?SN_WILLMSG, variable = Msg}). +-define(SN_WILLMSG_MSG(Msg), #mqtt_sn_message{type = ?SN_WILLMSG, variable = Msg}). --define(SN_REGISTER_MSG(TopicId, MsgId, TopicName), - #mqtt_sn_message{type = ?SN_REGISTER, - variable = {TopicId, MsgId, TopicName}}). +-define(SN_REGISTER_MSG(TopicId, MsgId, TopicName), #mqtt_sn_message{ + type = ?SN_REGISTER, + variable = {TopicId, MsgId, TopicName} +}). --define(SN_REGACK_MSG(TopicId, MsgId, ReturnCode), - #mqtt_sn_message{type = ?SN_REGACK, - variable = {TopicId, MsgId, ReturnCode}}). +-define(SN_REGACK_MSG(TopicId, MsgId, ReturnCode), #mqtt_sn_message{ + type = ?SN_REGACK, + variable = {TopicId, MsgId, ReturnCode} +}). --define(SN_PUBLISH_MSG(Flags, TopicId, MsgId, Data), - #mqtt_sn_message{type = ?SN_PUBLISH, - variable = {Flags, TopicId, MsgId, Data}}). +-define(SN_PUBLISH_MSG(Flags, TopicId, MsgId, Data), #mqtt_sn_message{ + type = ?SN_PUBLISH, + variable = {Flags, TopicId, MsgId, Data} +}). --define(SN_PUBACK_MSG(TopicId, MsgId, ReturnCode), - #mqtt_sn_message{type = ?SN_PUBACK, - variable = {TopicId, MsgId, ReturnCode}}). +-define(SN_PUBACK_MSG(TopicId, MsgId, ReturnCode), #mqtt_sn_message{ + type = ?SN_PUBACK, + variable = {TopicId, MsgId, ReturnCode} +}). %% Type: SN_PUBREC | SN_PUBREL | SN_PUBCOMP --define(SN_PUBREC_MSG(Type, MsgId), - #mqtt_sn_message{type = Type, - variable = MsgId}). +-define(SN_PUBREC_MSG(Type, MsgId), #mqtt_sn_message{ + type = Type, + variable = MsgId +}). --define(SN_SUBSCRIBE_MSG(Flags, MsgId, Topic), - #mqtt_sn_message{type = ?SN_SUBSCRIBE, - variable = {Flags, MsgId, Topic}}). +-define(SN_SUBSCRIBE_MSG(Flags, MsgId, Topic), #mqtt_sn_message{ + type = ?SN_SUBSCRIBE, + variable = {Flags, MsgId, Topic} +}). --define(SN_SUBSCRIBE_MSG_TYPE(Type, Topic, QoS), - #mqtt_sn_message{type = ?SN_SUBSCRIBE, - variable = { - #mqtt_sn_flags{qos = QoS, topic_id_type = Type}, - _, Topic}}). +-define(SN_SUBSCRIBE_MSG_TYPE(Type, Topic, QoS), #mqtt_sn_message{ + type = ?SN_SUBSCRIBE, + variable = { + #mqtt_sn_flags{qos = QoS, topic_id_type = Type}, + _, + Topic + } +}). --define(SN_SUBACK_MSG(Flags, TopicId, MsgId, ReturnCode), - #mqtt_sn_message{type = ?SN_SUBACK, - variable = {Flags, TopicId, MsgId, ReturnCode}}). +-define(SN_SUBACK_MSG(Flags, TopicId, MsgId, ReturnCode), #mqtt_sn_message{ + type = ?SN_SUBACK, + variable = {Flags, TopicId, MsgId, ReturnCode} +}). --define(SN_UNSUBSCRIBE_MSG(Flags, MsgId, Topic), - #mqtt_sn_message{type = ?SN_UNSUBSCRIBE, - variable = {Flags, MsgId, Topic}}). +-define(SN_UNSUBSCRIBE_MSG(Flags, MsgId, Topic), #mqtt_sn_message{ + type = ?SN_UNSUBSCRIBE, + variable = {Flags, MsgId, Topic} +}). --define(SN_UNSUBSCRIBE_MSG_TYPE(Type, Topic), - #mqtt_sn_message{type = ?SN_UNSUBSCRIBE, - variable = { - #mqtt_sn_flags{topic_id_type = Type}, - _, Topic}}). +-define(SN_UNSUBSCRIBE_MSG_TYPE(Type, Topic), #mqtt_sn_message{ + type = ?SN_UNSUBSCRIBE, + variable = { + #mqtt_sn_flags{topic_id_type = Type}, + _, + Topic + } +}). --define(SN_UNSUBACK_MSG(MsgId), - #mqtt_sn_message{type = ?SN_UNSUBACK, - variable = MsgId}). +-define(SN_UNSUBACK_MSG(MsgId), #mqtt_sn_message{ + type = ?SN_UNSUBACK, + variable = MsgId +}). --define(SN_PINGREQ_MSG(ClientId), - #mqtt_sn_message{type = ?SN_PINGREQ, variable = ClientId}). +-define(SN_PINGREQ_MSG(ClientId), #mqtt_sn_message{type = ?SN_PINGREQ, variable = ClientId}). -define(SN_PINGRESP_MSG(), #mqtt_sn_message{type = ?SN_PINGRESP}). --define(SN_DISCONNECT_MSG(Duration), - #mqtt_sn_message{type = ?SN_DISCONNECT, - variable = Duration}). +-define(SN_DISCONNECT_MSG(Duration), #mqtt_sn_message{ + type = ?SN_DISCONNECT, + variable = Duration +}). --define(SN_WILLTOPICUPD_MSG(Flags, Topic), - #mqtt_sn_message{type = ?SN_WILLTOPICUPD, - variable = {Flags, Topic}}). +-define(SN_WILLTOPICUPD_MSG(Flags, Topic), #mqtt_sn_message{ + type = ?SN_WILLTOPICUPD, + variable = {Flags, Topic} +}). --define(SN_WILLTOPICRESP_MSG(ReturnCode), - #mqtt_sn_message{type = ?SN_WILLTOPICRESP, - variable = ReturnCode}). +-define(SN_WILLTOPICRESP_MSG(ReturnCode), #mqtt_sn_message{ + type = ?SN_WILLTOPICRESP, + variable = ReturnCode +}). --define(SN_WILLMSGUPD_MSG(Msg), - #mqtt_sn_message{type = ?SN_WILLMSGUPD, - variable = Msg}). +-define(SN_WILLMSGUPD_MSG(Msg), #mqtt_sn_message{ + type = ?SN_WILLMSGUPD, + variable = Msg +}). --define(SN_WILLMSGRESP_MSG(ReturnCode), - #mqtt_sn_message{type = ?SN_WILLMSGRESP, - variable = ReturnCode}). +-define(SN_WILLMSGRESP_MSG(ReturnCode), #mqtt_sn_message{ + type = ?SN_WILLMSGRESP, + variable = ReturnCode +}). --define(SN_NORMAL_TOPIC, 0). +-define(SN_NORMAL_TOPIC, 0). -define(SN_PREDEFINED_TOPIC, 1). --define(SN_SHORT_TOPIC, 2). --define(SN_RESERVED_TOPIC, 3). +-define(SN_SHORT_TOPIC, 2). +-define(SN_RESERVED_TOPIC, 3). -define(SN_INVALID_TOPIC_ID, 0). diff --git a/apps/emqx_gateway/src/proto/emqx_gateway_cm_proto_v1.erl b/apps/emqx_gateway/src/proto/emqx_gateway_cm_proto_v1.erl index 08c713b14..546a8493c 100644 --- a/apps/emqx_gateway/src/proto/emqx_gateway_cm_proto_v1.erl +++ b/apps/emqx_gateway/src/proto/emqx_gateway_cm_proto_v1.erl @@ -18,20 +18,21 @@ -behaviour(emqx_bpapi). --export([ introduced_in/0 +-export([ + introduced_in/0, - , get_chan_info/3 - , set_chan_info/4 - , get_chan_stats/3 - , set_chan_stats/4 - , kick_session/4 - , get_chann_conn_mod/3 - , lookup_by_clientid/3 - , takeover_session/3 - , call/4 - , call/5 - , cast/4 - ]). + get_chan_info/3, + set_chan_info/4, + get_chan_stats/3, + set_chan_stats/4, + kick_session/4, + get_chann_conn_mod/3, + lookup_by_clientid/3, + takeover_session/3, + call/4, + call/5, + cast/4 +]). -include_lib("emqx/include/bpapi.hrl"). @@ -39,75 +40,107 @@ introduced_in() -> "5.0.0". -spec lookup_by_clientid([node()], emqx_gateway_cm:gateway_name(), emqx_types:clientid()) -> - emqx_rpc:multicall_return([pid()]). + emqx_rpc:multicall_return([pid()]). lookup_by_clientid(Nodes, GwName, ClientId) -> rpc:multicall(Nodes, emqx_gateway_cm, do_lookup_by_clientid, [GwName, ClientId]). --spec get_chan_info(emqx_gateway_cm:gateway_name(), emqx_types:clientid(), pid()) - -> emqx_types:infos() | undefined | {badrpc, _}. +-spec get_chan_info(emqx_gateway_cm:gateway_name(), emqx_types:clientid(), pid()) -> + emqx_types:infos() | undefined | {badrpc, _}. get_chan_info(GwName, ClientId, ChanPid) -> rpc:call(node(ChanPid), emqx_gateway_cm, do_get_chan_info, [GwName, ClientId, ChanPid]). --spec set_chan_info(emqx_gateway_cm:gateway_name(), - emqx_types:clientid(), - pid(), - emqx_types:infos()) -> boolean() | {badrpc, _}. +-spec set_chan_info( + emqx_gateway_cm:gateway_name(), + emqx_types:clientid(), + pid(), + emqx_types:infos() +) -> boolean() | {badrpc, _}. set_chan_info(GwName, ClientId, ChanPid, Infos) -> rpc:call(node(ChanPid), emqx_gateway_cm, do_set_chan_info, [GwName, ClientId, ChanPid, Infos]). --spec get_chan_stats(emqx_gateway_cm:gateway_name(), emqx_types:clientid(), pid()) - -> emqx_types:stats() | undefined | {badrpc, _}. +-spec get_chan_stats(emqx_gateway_cm:gateway_name(), emqx_types:clientid(), pid()) -> + emqx_types:stats() | undefined | {badrpc, _}. get_chan_stats(GwName, ClientId, ChanPid) -> rpc:call(node(ChanPid), emqx_gateway_cm, do_get_chan_stats, [GwName, ClientId, ChanPid]). --spec set_chan_stats(emqx_gateway_cm:gateway_name(), - emqx_types:clientid(), - pid(), - emqx_types:stats()) -> boolean() | {badrpc, _}. +-spec set_chan_stats( + emqx_gateway_cm:gateway_name(), + emqx_types:clientid(), + pid(), + emqx_types:stats() +) -> boolean() | {badrpc, _}. set_chan_stats(GwName, ClientId, ChanPid, Stats) -> rpc:call(node(ChanPid), emqx_gateway_cm, do_set_chan_stats, [GwName, ClientId, ChanPid, Stats]). --spec kick_session(emqx_gateway_cm:gateway_name(), - kick | discard, - emqx_types:clientid(), pid()) -> _. +-spec kick_session( + emqx_gateway_cm:gateway_name(), + kick | discard, + emqx_types:clientid(), + pid() +) -> _. kick_session(GwName, Action, ClientId, ChanPid) -> - rpc:call(node(ChanPid), - emqx_gateway_cm, do_kick_session, - [GwName, Action, ClientId, ChanPid]). + rpc:call( + node(ChanPid), + emqx_gateway_cm, + do_kick_session, + [GwName, Action, ClientId, ChanPid] + ). --spec get_chann_conn_mod(emqx_gateway_cm:gateway_name(), - emqx_types:clientid(), - pid()) -> atom() | {badrpc, _}. +-spec get_chann_conn_mod( + emqx_gateway_cm:gateway_name(), + emqx_types:clientid(), + pid() +) -> atom() | {badrpc, _}. get_chann_conn_mod(GwName, ClientId, ChanPid) -> - rpc:call(node(ChanPid), emqx_gateway_cm, do_get_chann_conn_mod, - [GwName, ClientId, ChanPid]). + rpc:call( + node(ChanPid), + emqx_gateway_cm, + do_get_chann_conn_mod, + [GwName, ClientId, ChanPid] + ). --spec takeover_session(emqx_gateway_cm:gateway_name(), - emqx_types:clientid(), - pid()) -> boolean() | {badrpc, _}. +-spec takeover_session( + emqx_gateway_cm:gateway_name(), + emqx_types:clientid(), + pid() +) -> boolean() | {badrpc, _}. takeover_session(GwName, ClientId, ChanPid) -> rpc:call(node(ChanPid), emqx_gateway_cm, do_takeover_session, [GwName, ClientId, ChanPid]). --spec call(emqx_gateway_cm:gateway_name(), - emqx_types:clientid(), - pid(), - term(), - timeout()) -> term() | {badrpc, _}. +-spec call( + emqx_gateway_cm:gateway_name(), + emqx_types:clientid(), + pid(), + term(), + timeout() +) -> term() | {badrpc, _}. call(GwName, ClientId, ChanPid, Req, Timeout) -> - rpc:call(node(ChanPid), emqx_gateway_cm, do_call, - [GwName, ClientId, ChanPid, Req, Timeout]). + rpc:call( + node(ChanPid), + emqx_gateway_cm, + do_call, + [GwName, ClientId, ChanPid, Req, Timeout] + ). --spec call(emqx_gateway_cm:gateway_name(), - emqx_types:clientid(), - pid(), - term()) -> term() | {badrpc, _}. +-spec call( + emqx_gateway_cm:gateway_name(), + emqx_types:clientid(), + pid(), + term() +) -> term() | {badrpc, _}. call(GwName, ClientId, ChanPid, Req) -> - rpc:call(node(ChanPid), emqx_gateway_cm, do_call, - [GwName, ClientId, ChanPid, Req]). + rpc:call( + node(ChanPid), + emqx_gateway_cm, + do_call, + [GwName, ClientId, ChanPid, Req] + ). --spec cast(emqx_gateway_cm:gateway_name(), - emqx_types:clientid(), - pid(), - term()) -> term() | {badrpc, _}. +-spec cast( + emqx_gateway_cm:gateway_name(), + emqx_types:clientid(), + pid(), + term() +) -> term() | {badrpc, _}. cast(GwName, ClientId, ChanPid, Req) -> rpc:call(node(ChanPid), emqx_gateway_cm, do_cast, [GwName, ClientId, ChanPid, Req]). diff --git a/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl b/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl index 1ac660d4b..d137d7eab 100644 --- a/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl +++ b/apps/emqx_gateway/src/stomp/emqx_stomp_channel.erl @@ -25,78 +25,86 @@ -import(proplists, [get_value/2, get_value/3]). %% API --export([ info/1 - , info/2 - , stats/1 - ]). +-export([ + info/1, + info/2, + stats/1 +]). --export([ init/2 - , handle_in/2 - , handle_out/3 - , handle_deliver/2 - , handle_timeout/3 - , terminate/2 - , set_conn_state/2 - ]). +-export([ + init/2, + handle_in/2, + handle_out/3, + handle_deliver/2, + handle_timeout/3, + terminate/2, + set_conn_state/2 +]). --export([ handle_call/3 - , handle_cast/2 - , handle_info/2 - ]). +-export([ + handle_call/3, + handle_cast/2, + handle_info/2 +]). %% for trans callback --export([ handle_recv_send_frame/2 - , handle_recv_ack_frame/2 - , handle_recv_nack_frame/2 - ]). +-export([ + handle_recv_send_frame/2, + handle_recv_ack_frame/2, + handle_recv_nack_frame/2 +]). -record(channel, { - %% Context - ctx :: emqx_gateway_ctx:context(), - %% Stomp Connection Info - conninfo :: emqx_types:conninfo(), - %% Stomp Client Info - clientinfo :: emqx_types:clientinfo(), - %% Session - session :: undefined | map(), - %% ClientInfo override specs - clientinfo_override :: map(), - %% Channel State - conn_state :: conn_state(), - %% Heartbeat - heartbeat :: emqx_stomp_heartbeat:heartbeat(), - %% Subscriptions - subscriptions = [], - %% Timer - timers :: #{atom() => disable | undefined | reference()}, - %% Transaction - transaction :: #{binary() => list()} - }). + %% Context + ctx :: emqx_gateway_ctx:context(), + %% Stomp Connection Info + conninfo :: emqx_types:conninfo(), + %% Stomp Client Info + clientinfo :: emqx_types:clientinfo(), + %% Session + session :: undefined | map(), + %% ClientInfo override specs + clientinfo_override :: map(), + %% Channel State + conn_state :: conn_state(), + %% Heartbeat + heartbeat :: emqx_stomp_heartbeat:heartbeat(), + %% Subscriptions + subscriptions = [], + %% Timer + timers :: #{atom() => disable | undefined | reference()}, + %% Transaction + transaction :: #{binary() => list()} +}). -type channel() :: #channel{}. -type conn_state() :: idle | connecting | connected | disconnected. --type reply() :: {outgoing, stomp_frame()} - | {outgoing, [stomp_frame()]} - | {event, conn_state()|updated} - | {close, Reason :: atom()}. +-type reply() :: + {outgoing, stomp_frame()} + | {outgoing, [stomp_frame()]} + | {event, conn_state() | updated} + | {close, Reason :: atom()}. -type replies() :: reply() | [reply()]. -define(TIMER_TABLE, #{ - incoming_timer => keepalive, - outgoing_timer => keepalive_send, - clean_trans_timer => clean_trans - }). + incoming_timer => keepalive, + outgoing_timer => keepalive_send, + clean_trans_timer => clean_trans +}). -define(TRANS_TIMEOUT, 60000). -define(DEFAULT_OVERRIDE, - #{ clientid => <<"">> %% Generate clientid by default - , username => <<"${Packet.headers.login}">> - , password => <<"${Packet.headers.passcode}">> - }). + %% Generate clientid by default + #{ + clientid => <<"">>, + username => <<"${Packet.headers.login}">>, + password => <<"${Packet.headers.passcode}">> + } +). -define(INFO_KEYS, [conninfo, conn_state, clientinfo, session, will_msg]). @@ -105,60 +113,67 @@ %%-------------------------------------------------------------------- %% @doc Init protocol -init(ConnInfo = #{peername := {PeerHost, _}, - sockname := {_, SockPort}}, Option) -> +init( + ConnInfo = #{ + peername := {PeerHost, _}, + sockname := {_, SockPort} + }, + Option +) -> Peercert = maps:get(peercert, ConnInfo, undefined), Mountpoint = maps:get(mountpoint, Option, undefined), - ListenerId = case maps:get(listener, Option, undefined) of - undefined -> undefined; - {GwName, Type, LisName} -> - emqx_gateway_utils:listener_id(GwName, Type, LisName) - end, + ListenerId = + case maps:get(listener, Option, undefined) of + undefined -> undefined; + {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName) + end, ClientInfo = setting_peercert_infos( - Peercert, - #{ zone => default - , listener => ListenerId - , protocol => stomp - , peerhost => PeerHost - , sockport => SockPort - , clientid => undefined - , username => undefined - , is_bridge => false - , is_superuser => false - , mountpoint => Mountpoint - } - ), + Peercert, + #{ + zone => default, + listener => ListenerId, + protocol => stomp, + peerhost => PeerHost, + sockport => SockPort, + clientid => undefined, + username => undefined, + is_bridge => false, + is_superuser => false, + mountpoint => Mountpoint + } + ), Ctx = maps:get(ctx, Option), - Override = maps:merge(?DEFAULT_OVERRIDE, - maps:get(clientinfo_override, Option, #{}) - ), - #channel{ ctx = Ctx - , conninfo = ConnInfo - , clientinfo = ClientInfo - , clientinfo_override = Override - , timers = #{} - , transaction = #{} - , conn_state = idle - }. + Override = maps:merge( + ?DEFAULT_OVERRIDE, + maps:get(clientinfo_override, Option, #{}) + ), + #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo, + clientinfo_override = Override, + timers = #{}, + transaction = #{}, + conn_state = idle + }. -setting_peercert_infos(NoSSL, ClientInfo) - when NoSSL =:= nossl; - NoSSL =:= undefined -> +setting_peercert_infos(NoSSL, ClientInfo) when + NoSSL =:= nossl; + NoSSL =:= undefined +-> ClientInfo; setting_peercert_infos(Peercert, ClientInfo) -> - {DN, CN} = {esockd_peercert:subject(Peercert), - esockd_peercert:common_name(Peercert)}, + {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)}, ClientInfo#{dn => DN, cn => CN}. -spec info(channel()) -> emqx_types:infos(). info(Channel) -> maps:from_list(info(?INFO_KEYS, Channel)). --spec info(list(atom())|atom(), channel()) -> term(). +-spec info(list(atom()) | atom(), channel()) -> term(). info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; - info(conninfo, #channel{conninfo = ConnInfo}) -> ConnInfo; info(conn_state, #channel{conn_state = ConnState}) -> @@ -181,66 +196,85 @@ stats(#channel{subscriptions = Subs}) -> set_conn_state(ConnState, Channel) -> Channel#channel{conn_state = ConnState}. -enrich_conninfo(_Packet, - Channel = #channel{conninfo = ConnInfo}) -> +enrich_conninfo( + _Packet, + Channel = #channel{conninfo = ConnInfo} +) -> %% XXX: How enrich more infos? - NConnInfo = ConnInfo#{ proto_name => <<"STOMP">> - , proto_ver => <<"1.2">> - , clean_start => true - , keepalive => 0 - , expiry_interval => 0 - , conn_props => #{} - , receive_maximum => 0 - }, + NConnInfo = ConnInfo#{ + proto_name => <<"STOMP">>, + proto_ver => <<"1.2">>, + clean_start => true, + keepalive => 0, + expiry_interval => 0, + conn_props => #{}, + receive_maximum => 0 + }, {ok, Channel#channel{conninfo = NConnInfo}}. -run_conn_hooks(Packet, Channel = #channel{ctx = Ctx, - conninfo = ConnInfo}) -> +run_conn_hooks( + Packet, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo + } +) -> %% XXX: Assign headers of Packet to ConnProps ConnProps = #{}, case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of Error = {error, _Reason} -> Error; - _NConnProps -> - {ok, Packet, Channel} + _NConnProps -> {ok, Packet, Channel} end. -negotiate_version(#stomp_frame{headers = Headers}, - Channel = #channel{conninfo = ConnInfo}) -> +negotiate_version( + #stomp_frame{headers = Headers}, + Channel = #channel{conninfo = ConnInfo} +) -> %% XXX: case do_negotiate_version(header(<<"accept-version">>, Headers)) of {ok, Version} -> {ok, Channel#channel{conninfo = ConnInfo#{proto_ver => Version}}}; - {error, Reason}-> + {error, Reason} -> {error, Reason} end. -enrich_clientinfo(Packet, - Channel = #channel{ - conninfo = ConnInfo, - clientinfo = ClientInfo0, - clientinfo_override = Override}) -> +enrich_clientinfo( + Packet, + Channel = #channel{ + conninfo = ConnInfo, + clientinfo = ClientInfo0, + clientinfo_override = Override + } +) -> ClientInfo = write_clientinfo( - feedvar(Override, Packet, ConnInfo, ClientInfo0), - ClientInfo0 - ), + feedvar(Override, Packet, ConnInfo, ClientInfo0), + ClientInfo0 + ), {ok, NPacket, NClientInfo} = emqx_misc:pipeline( - [ fun maybe_assign_clientid/2 - , fun parse_heartbeat/2 - %% FIXME: CALL After authentication successfully - , fun fix_mountpoint/2 - ], Packet, ClientInfo - ), + [ + fun maybe_assign_clientid/2, + fun parse_heartbeat/2, + %% FIXME: CALL After authentication successfully + fun fix_mountpoint/2 + ], + Packet, + ClientInfo + ), {ok, NPacket, Channel#channel{clientinfo = NClientInfo}}. feedvar(Override, Packet, ConnInfo, ClientInfo) -> - Envs = #{ 'ConnInfo' => ConnInfo - , 'ClientInfo' => ClientInfo - , 'Packet' => connect_packet_to_map(Packet) - }, - maps:map(fun(_K, V) -> - Tokens = emqx_plugin_libs_rule:preproc_tmpl(V), - emqx_plugin_libs_rule:proc_tmpl(Tokens, Envs) - end, Override). + Envs = #{ + 'ConnInfo' => ConnInfo, + 'ClientInfo' => ClientInfo, + 'Packet' => connect_packet_to_map(Packet) + }, + maps:map( + fun(_K, V) -> + Tokens = emqx_plugin_libs_rule:preproc_tmpl(V), + emqx_plugin_libs_rule:proc_tmpl(Tokens, Envs) + end, + Override + ). connect_packet_to_map(#stomp_frame{headers = Headers}) -> #{headers => maps:from_list(Headers)}. @@ -249,11 +283,11 @@ write_clientinfo(Override, ClientInfo) -> Override1 = maps:with([username, password, clientid], Override), maps:merge(ClientInfo, Override1). -maybe_assign_clientid(_Packet, ClientInfo = #{clientid := ClientId}) - when ClientId == undefined; - ClientId == <<>> -> +maybe_assign_clientid(_Packet, ClientInfo = #{clientid := ClientId}) when + ClientId == undefined; + ClientId == <<>> +-> {ok, ClientInfo#{clientid => emqx_guid:to_base62(emqx_guid:gen())}}; - maybe_assign_clientid(_Packet, ClientInfo) -> {ok, ClientInfo}. @@ -263,7 +297,8 @@ parse_heartbeat(#stomp_frame{headers = Headers}, ClientInfo) -> Heartbeat = list_to_tuple([list_to_integer(S) || S <- CxCy]), {ok, ClientInfo#{heartbeat => Heartbeat}}. -fix_mountpoint(_Packet, #{mountpoint := undefined}) -> ok; +fix_mountpoint(_Packet, #{mountpoint := undefined}) -> + ok; fix_mountpoint(_Packet, ClientInfo = #{mountpoint := Mountpoint}) -> %% TODO: Enrich the variable replacement???? %% i.e: ${ClientInfo.auth_result.productKey} @@ -274,57 +309,78 @@ set_log_meta(_Packet, #channel{clientinfo = #{clientid := ClientId}}) -> emqx_logger:set_metadata_clientid(ClientId), ok. -auth_connect(_Packet, Channel = #channel{ctx = Ctx, - clientinfo = ClientInfo}) -> - #{clientid := ClientId, - username := Username} = ClientInfo, +auth_connect( + _Packet, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> + #{ + clientid := ClientId, + username := Username + } = ClientInfo, case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of {ok, NClientInfo} -> {ok, Channel#channel{clientinfo = NClientInfo}}; {error, Reason} -> - ?SLOG(warning, #{ msg => "client_login_failed" - , clientid => ClientId - , username => Username - , reason => Reason - }), + ?SLOG(warning, #{ + msg => "client_login_failed", + clientid => ClientId, + username => Username, + reason => Reason + }), {error, Reason} end. -ensure_connected(Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_connected( + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)}, ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), Channel#channel{ - conninfo = NConnInfo, - conn_state = connected}. + conninfo = NConnInfo, + conn_state = connected + }. -process_connect(Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo - }) -> - SessFun = fun(_,_) -> #{} end, - case emqx_gateway_ctx:open_session( - Ctx, - true, - ClientInfo, - ConnInfo, - SessFun - ) of +process_connect( + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> + SessFun = fun(_, _) -> #{} end, + case + emqx_gateway_ctx:open_session( + Ctx, + true, + ClientInfo, + ConnInfo, + SessFun + ) + of {ok, #{session := Session}} -> #{proto_ver := Version} = ConnInfo, #{heartbeat := Heartbeat} = ClientInfo, - Headers = [{<<"version">>, Version}, - {<<"heart-beat">>, reverse_heartbeats(Heartbeat)}], + Headers = [ + {<<"version">>, Version}, + {<<"heart-beat">>, reverse_heartbeats(Heartbeat)} + ], handle_out(connected, Headers, Channel#channel{session = Session}); {error, Reason} -> - ?SLOG(error, #{ msg => "failed_to_open_session" - , reason => Reason - }), - Headers = [{<<"version">>, <<"1.0,1.1,1.2">>}, - {<<"content-type">>, <<"text/plain">>}], + ?SLOG(error, #{ + msg => "failed_to_open_session", + reason => Reason + }), + Headers = [ + {<<"version">>, <<"1.0,1.1,1.2">>}, + {<<"content-type">>, <<"text/plain">>} + ], handle_out(connerr, {Headers, undefined, <<"Not Authenticated">>}, Channel) end. @@ -332,42 +388,49 @@ process_connect(Channel = #channel{ %% Handle incoming packet %%-------------------------------------------------------------------- --spec handle_in(stomp_frame() | {frame_error, any()}, channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()} - | {shutdown, Reason :: term(), replies(), channel()}. +-spec handle_in(stomp_frame() | {frame_error, any()}, channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), replies(), channel()}. handle_in(Frame = ?PACKET(?CMD_STOMP), Channel) -> handle_in(Frame#stomp_frame{command = <<"CONNECT">>}, Channel); - -handle_in(?PACKET(?CMD_CONNECT), - Channel = #channel{conn_state = connected}) -> +handle_in( + ?PACKET(?CMD_CONNECT), + Channel = #channel{conn_state = connected} +) -> {error, unexpected_connect, Channel}; - handle_in(Packet = ?PACKET(?CMD_CONNECT), Channel) -> - case emqx_misc:pipeline( - [ fun enrich_conninfo/2 - , fun run_conn_hooks/2 - , fun negotiate_version/2 - , fun enrich_clientinfo/2 - , fun set_log_meta/2 - %% TODO: How to implement the banned in the gateway instance? - %, fun check_banned/2 - , fun auth_connect/2 - ], Packet, Channel#channel{conn_state = connecting}) of + case + emqx_misc:pipeline( + [ + fun enrich_conninfo/2, + fun run_conn_hooks/2, + fun negotiate_version/2, + fun enrich_clientinfo/2, + fun set_log_meta/2, + %% TODO: How to implement the banned in the gateway instance? + %, fun check_banned/2 + fun auth_connect/2 + ], + Packet, + Channel#channel{conn_state = connecting} + ) + of {ok, _NPacket, NChannel} -> process_connect(ensure_connected(NChannel)); {error, ReasonCode, NChannel} -> ErrMsg = io_lib:format("Login Failed: ~ts", [ReasonCode]), handle_out(connerr, {[], undefined, ErrMsg}, NChannel) end; - -handle_in(Frame = ?PACKET(?CMD_SEND, Headers), - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - }) -> +handle_in( + Frame = ?PACKET(?CMD_SEND, Headers), + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> Topic = header(<<"destination">>, Headers), case emqx_gateway_ctx:authorize(Ctx, ClientInfo, publish, Topic) of deny -> @@ -377,76 +440,104 @@ handle_in(Frame = ?PACKET(?CMD_SEND, Headers), undefined -> handle_recv_send_frame(Frame, Channel); TxId -> - add_action(TxId, {fun ?MODULE:handle_recv_send_frame/2, [Frame]}, receipt_id(Headers), Channel) + add_action( + TxId, + {fun ?MODULE:handle_recv_send_frame/2, [Frame]}, + receipt_id(Headers), + Channel + ) end end; - -handle_in(?PACKET(?CMD_SUBSCRIBE, Headers), - Channel = #channel{ - ctx = Ctx, - subscriptions = Subs, - clientinfo = ClientInfo - }) -> - +handle_in( + ?PACKET(?CMD_SUBSCRIBE, Headers), + Channel = #channel{ + ctx = Ctx, + subscriptions = Subs, + clientinfo = ClientInfo + } +) -> SubId = header(<<"id">>, Headers), Topic = header(<<"destination">>, Headers), - Ack = header(<<"ack">>, Headers, <<"auto">>), - case emqx_misc:pipeline( - [ fun parse_topic_filter/2 - , fun check_subscribed_status/2 - , fun check_sub_acl/2 - ], {SubId, Topic}, Channel) of + Ack = header(<<"ack">>, Headers, <<"auto">>), + case + emqx_misc:pipeline( + [ + fun parse_topic_filter/2, + fun check_subscribed_status/2, + fun check_sub_acl/2 + ], + {SubId, Topic}, + Channel + ) + of {ok, {_, TopicFilter}, NChannel} -> TopicFilters = [TopicFilter], - NTopicFilters = run_hooks(Ctx, 'client.subscribe', - [ClientInfo, #{}], TopicFilters), + NTopicFilters = run_hooks( + Ctx, + 'client.subscribe', + [ClientInfo, #{}], + TopicFilters + ), case do_subscribe(NTopicFilters, NChannel) of [] -> ErrMsg = "Permission denied", handle_out(error, {receipt_id(Headers), ErrMsg}, Channel); - [{MountedTopic, SubOpts}|_] -> - NSubs = [{SubId, MountedTopic, Ack, SubOpts}|Subs], + [{MountedTopic, SubOpts} | _] -> + NSubs = [{SubId, MountedTopic, Ack, SubOpts} | Subs], NChannel1 = NChannel#channel{subscriptions = NSubs}, handle_out(receipt, receipt_id(Headers), NChannel1) end; {error, ErrMsg, NChannel} -> - ?SLOG(error, #{ msg => "failed_top_subscribe_topic" - , topic => Topic - , reason => ErrMsg - }), + ?SLOG(error, #{ + msg => "failed_top_subscribe_topic", + topic => Topic, + reason => ErrMsg + }), handle_out(error, {receipt_id(Headers), ErrMsg}, NChannel) end; - -handle_in(?PACKET(?CMD_UNSUBSCRIBE, Headers), - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - = #{mountpoint := Mountpoint}, - subscriptions = Subs}) -> +handle_in( + ?PACKET(?CMD_UNSUBSCRIBE, Headers), + Channel = #channel{ + ctx = Ctx, + clientinfo = + ClientInfo = + #{mountpoint := Mountpoint}, + subscriptions = Subs + } +) -> SubId = header(<<"id">>, Headers), {ok, NChannel} = case lists:keyfind(SubId, 1, Subs) of {SubId, MountedTopic, _Ack, _SubOpts} -> Topic = emqx_mountpoint:unmount(Mountpoint, MountedTopic), %% XXX: eval the return topics? - _ = run_hooks(Ctx, 'client.unsubscribe', - [ClientInfo, #{}], [{Topic, #{}}]), + _ = run_hooks( + Ctx, + 'client.unsubscribe', + [ClientInfo, #{}], + [{Topic, #{}}] + ), ok = emqx_broker:unsubscribe(MountedTopic), - _ = run_hooks(Ctx, 'session.unsubscribe', - [ClientInfo, MountedTopic, #{}]), + _ = run_hooks( + Ctx, + 'session.unsubscribe', + [ClientInfo, MountedTopic, #{}] + ), {ok, Channel#channel{subscriptions = lists:keydelete(SubId, 1, Subs)}}; - false -> - {ok, Channel} + false -> + {ok, Channel} end, handle_out(receipt, receipt_id(Headers), NChannel); - %% XXX: How to ack a frame ??? handle_in(Frame = ?PACKET(?CMD_ACK, Headers), Channel) -> case header(<<"transaction">>, Headers) of - undefined -> handle_recv_ack_frame(Frame, Channel); - TxId -> add_action(TxId, {fun ?MODULE:handle_recv_ack_frame/2, [Frame]}, receipt_id(Headers), Channel) + undefined -> + handle_recv_ack_frame(Frame, Channel); + TxId -> + add_action( + TxId, {fun ?MODULE:handle_recv_ack_frame/2, [Frame]}, receipt_id(Headers), Channel + ) end; - %% NACK %% id:12345 %% transaction:tx1 @@ -454,10 +545,13 @@ handle_in(Frame = ?PACKET(?CMD_ACK, Headers), Channel) -> %% ^@ handle_in(Frame = ?PACKET(?CMD_NACK, Headers), Channel) -> case header(<<"transaction">>, Headers) of - undefined -> handle_recv_nack_frame(Frame, Channel); - TxId -> add_action(TxId, {fun ?MODULE:handle_recv_nack_frame/2, [Frame]}, receipt_id(Headers), Channel) + undefined -> + handle_recv_nack_frame(Frame, Channel); + TxId -> + add_action( + TxId, {fun ?MODULE:handle_recv_nack_frame/2, [Frame]}, receipt_id(Headers), Channel + ) end; - %% The transaction header is REQUIRED, and the transaction identifier %% will be used for SEND, COMMIT, ABORT, ACK, and NACK frames to bind %% them to the named transaction. @@ -466,22 +560,24 @@ handle_in(Frame = ?PACKET(?CMD_NACK, Headers), Channel) -> %% transaction:tx1 %% %% ^@ -handle_in(?PACKET(?CMD_BEGIN, Headers), - Channel = #channel{transaction = Trans}) -> +handle_in( + ?PACKET(?CMD_BEGIN, Headers), + Channel = #channel{transaction = Trans} +) -> TxId = header(<<"transaction">>, Headers), case maps:get(TxId, Trans, undefined) of undefined -> StartedAt = erlang:system_time(millisecond), NChannel = ensure_clean_trans_timer( - Channel#channel{ - transaction = Trans#{TxId => {StartedAt, []}}} - ), + Channel#channel{ + transaction = Trans#{TxId => {StartedAt, []}} + } + ), handle_out(receipt, receipt_id(Headers), NChannel); _ -> ErrMsg = ["Transaction ", TxId, " already started"], handle_out(error, {receipt_id(Headers), ErrMsg}, Channel) end; - %% COMMIT %% transaction:tx1 %% @@ -494,38 +590,45 @@ handle_in(?PACKET(?CMD_COMMIT, Headers), Channel) -> maybe_outgoing_receipt(receipt_id(Headers), Outgoings, Chann1); {error, Reason, Chann1} -> %% FIXME: atomic for transaction ?? - ErrMsg = io_lib:format("Execute transaction ~ts failed: ~0p", - [TxId, Reason] - ), + ErrMsg = io_lib:format( + "Execute transaction ~ts failed: ~0p", + [TxId, Reason] + ), handle_out(error, {receipt_id(Headers), ErrMsg}, Chann1) end end); - %% ABORT %% transaction:tx1 %% %% ^@ -handle_in(?PACKET(?CMD_ABORT, Headers), - Channel = #channel{transaction = Trans}) -> +handle_in( + ?PACKET(?CMD_ABORT, Headers), + Channel = #channel{transaction = Trans} +) -> with_transaction(Headers, Channel, fun(Id, _Actions) -> NChannel = Channel#channel{transaction = maps:remove(Id, Trans)}, handle_out(receipt, receipt_id(Headers), NChannel) end); - -handle_in(?PACKET(?CMD_DISCONNECT, Headers), - Channel = #channel{conn_state = connected}) -> - Outgoings = case receipt_id(Headers) of - undefined -> [{close, normal}]; - ReceiptId -> - [{outgoing, receipt_frame(ReceiptId)}, - {close, normal}] - end, +handle_in( + ?PACKET(?CMD_DISCONNECT, Headers), + Channel = #channel{conn_state = connected} +) -> + Outgoings = + case receipt_id(Headers) of + undefined -> + [{close, normal}]; + ReceiptId -> + [ + {outgoing, receipt_frame(ReceiptId)}, + {close, normal} + ] + end, {ok, Outgoings, Channel}; - handle_in({frame_error, Reason}, Channel = #channel{conn_state = _ConnState}) -> - ?SLOG(error, #{ msg => "unexpected_frame_error" - , reason => Reason - }), + ?SLOG(error, #{ + msg => "unexpected_frame_error", + reason => Reason + }), shutdown(Reason, Channel). with_transaction(Headers, Channel = #channel{transaction = Trans}, Fun) -> @@ -535,7 +638,7 @@ with_transaction(Headers, Channel = #channel{transaction = Trans}, Fun) -> {_, Actions} -> Fun(Id, Actions); _ -> - ErrMsg = ["Transaction ", Id, " not found"], + ErrMsg = ["Transaction ", Id, " not found"], handle_out(error, {ReceiptId, ErrMsg}, Channel) end. @@ -544,8 +647,7 @@ remove_trans(Id, Channel = #channel{transaction = Trans}) -> trans_pipeline([], Outgoings, Channel) -> {ok, Outgoings, Channel}; - -trans_pipeline([{Func, Args}|More], Outgoings, Channel) -> +trans_pipeline([{Func, Args} | More], Outgoings, Channel) -> case erlang:apply(Func, Args ++ [Channel]) of {ok, NChannel} -> trans_pipeline(More, Outgoings, NChannel); @@ -563,11 +665,13 @@ parse_topic_filter({SubId, Topic}, Channel) -> NSubOpts = SubOpts#{sub_props => #{subid => SubId}}, {ok, {SubId, {ParsedTopic, NSubOpts}}, Channel}. -check_subscribed_status({SubId, {ParsedTopic, _SubOpts}}, - #channel{ - subscriptions = Subs, - clientinfo = #{mountpoint := Mountpoint} - }) -> +check_subscribed_status( + {SubId, {ParsedTopic, _SubOpts}}, + #channel{ + subscriptions = Subs, + clientinfo = #{mountpoint := Mountpoint} + } +) -> MountedTopic = emqx_mountpoint:mount(Mountpoint, ParsedTopic), case lists:keyfind(SubId, 1, Subs) of {SubId, MountedTopic, _Ack, _} -> @@ -578,10 +682,13 @@ check_subscribed_status({SubId, {ParsedTopic, _SubOpts}}, ok end. -check_sub_acl({_SubId, {ParsedTopic, _SubOpts}}, - #channel{ - ctx = Ctx, - clientinfo = ClientInfo}) -> +check_sub_acl( + {_SubId, {ParsedTopic, _SubOpts}}, + #channel{ + ctx = Ctx, + clientinfo = ClientInfo + } +) -> case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, ParsedTopic) of deny -> {error, "ACL Deny"}; allow -> ok @@ -592,47 +699,56 @@ do_subscribe(TopicFilters, Channel) -> do_subscribe([], _Channel, Acc) -> lists:reverse(Acc); -do_subscribe([{ParsedTopic, SubOpts0}|More], - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo - = #{clientid := ClientId, - mountpoint := Mountpoint}}, Acc) -> +do_subscribe( + [{ParsedTopic, SubOpts0} | More], + Channel = #channel{ + ctx = Ctx, + clientinfo = + ClientInfo = + #{ + clientid := ClientId, + mountpoint := Mountpoint + } + }, + Acc +) -> SubOpts = maps:merge(emqx_gateway_utils:default_subopts(), SubOpts0), MountedTopic = emqx_mountpoint:mount(Mountpoint, ParsedTopic), _ = emqx_broker:subscribe(MountedTopic, ClientId, SubOpts), run_hooks(Ctx, 'session.subscribed', [ClientInfo, MountedTopic, SubOpts]), - do_subscribe(More, Channel, [{MountedTopic, SubOpts}|Acc]). + do_subscribe(More, Channel, [{MountedTopic, SubOpts} | Acc]). %%-------------------------------------------------------------------- %% Handle outgoing packet %%-------------------------------------------------------------------- --spec(handle_out(atom(), term(), channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()} - | {shutdown, Reason :: term(), replies(), channel()}). +-spec handle_out(atom(), term(), channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), replies(), channel()}. handle_out(connerr, {Headers, ReceiptId, ErrMsg}, Channel) -> Frame = error_frame(Headers, ReceiptId, ErrMsg), shutdown(ErrMsg, Frame, Channel); - handle_out(error, {ReceiptId, ErrMsg}, Channel) -> Frame = error_frame(ReceiptId, ErrMsg), {ok, {outgoing, Frame}, Channel}; - -handle_out(connected, Headers, Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo - }) -> +handle_out( + connected, + Headers, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo + } +) -> %% XXX: connection_accepted is not defined by stomp protocol _ = run_hooks(Ctx, 'client.connack', [ConnInfo, connection_accepted, []]), - Replies = [{outgoing, connected_frame(Headers)}, - {event, connected} - ], + Replies = [ + {outgoing, connected_frame(Headers)}, + {event, connected} + ], {ok, Replies, ensure_heartbeart_timer(Channel)}; - handle_out(receipt, undefined, Channel) -> {ok, Channel}; handle_out(receipt, ReceiptId, Channel) -> @@ -643,75 +759,94 @@ handle_out(receipt, ReceiptId, Channel) -> %% Handle call %%-------------------------------------------------------------------- --spec(handle_call(Req :: term(), From :: term(), channel()) - -> {reply, Reply :: term(), channel()} - | {reply, Reply :: term(), replies(), channel()} - | {shutdown, Reason :: term(), Reply :: term(), channel()} - | {shutdown, Reason :: term(), Reply :: term(), stomp_frame(), channel()}). -handle_call({subscribe, Topic, SubOpts}, _From, - Channel = #channel{ - subscriptions = Subs - }) -> - case maps:get(subid, - maps:get(sub_props, SubOpts, #{}), - undefined) of +-spec handle_call(Req :: term(), From :: term(), channel()) -> + {reply, Reply :: term(), channel()} + | {reply, Reply :: term(), replies(), channel()} + | {shutdown, Reason :: term(), Reply :: term(), channel()} + | {shutdown, Reason :: term(), Reply :: term(), stomp_frame(), channel()}. +handle_call( + {subscribe, Topic, SubOpts}, + _From, + Channel = #channel{ + subscriptions = Subs + } +) -> + case + maps:get( + subid, + maps:get(sub_props, SubOpts, #{}), + undefined + ) + of undefined -> reply({error, no_subid}, Channel); SubId -> - case emqx_misc:pipeline( - [ fun parse_topic_filter/2 - , fun check_subscribed_status/2 - ], {SubId, {Topic, SubOpts}}, Channel) of + case + emqx_misc:pipeline( + [ + fun parse_topic_filter/2, + fun check_subscribed_status/2 + ], + {SubId, {Topic, SubOpts}}, + Channel + ) + of {ok, {_, TopicFilter}, NChannel} -> [{MountedTopic, NSubOpts}] = do_subscribe( - [TopicFilter], - NChannel - ), - NSubs = [{SubId, MountedTopic, <<"auto">>, NSubOpts}|Subs], + [TopicFilter], + NChannel + ), + NSubs = [{SubId, MountedTopic, <<"auto">>, NSubOpts} | Subs], NChannel1 = NChannel#channel{subscriptions = NSubs}, reply({ok, {MountedTopic, NSubOpts}}, NChannel1); {error, ErrMsg, NChannel} -> - ?SLOG(error, #{ msg => "failed_to_subscribe_topic" - , topic => Topic - , reason => ErrMsg - }), + ?SLOG(error, #{ + msg => "failed_to_subscribe_topic", + topic => Topic, + reason => ErrMsg + }), reply({error, ErrMsg}, NChannel) end end; - -handle_call({unsubscribe, Topic}, _From, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo = #{mountpoint := Mountpoint}, - subscriptions = Subs - }) -> +handle_call( + {unsubscribe, Topic}, + _From, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo = #{mountpoint := Mountpoint}, + subscriptions = Subs + } +) -> {ParsedTopic, _SubOpts} = emqx_topic:parse(Topic), MountedTopic = emqx_mountpoint:mount(Mountpoint, ParsedTopic), ok = emqx_broker:unsubscribe(MountedTopic), - _ = run_hooks(Ctx, 'session.unsubscribe', - [ClientInfo, MountedTopic, #{}]), - reply(ok, - Channel#channel{ - subscriptions = lists:keydelete(MountedTopic, 2, Subs)} - ); - + _ = run_hooks( + Ctx, + 'session.unsubscribe', + [ClientInfo, MountedTopic, #{}] + ), + reply( + ok, + Channel#channel{ + subscriptions = lists:keydelete(MountedTopic, 2, Subs) + } + ); %% Reply :: [{emqx_types:topic(), emqx_types:subopts()}] handle_call(subscriptions, _From, Channel = #channel{subscriptions = Subs}) -> NSubs = lists:map( - fun({_SubId, Topic, _Ack, SubOpts}) -> - {Topic, SubOpts} - end, Subs), + fun({_SubId, Topic, _Ack, SubOpts}) -> + {Topic, SubOpts} + end, + Subs + ), reply({ok, NSubs}, Channel); - handle_call(kick, _From, Channel) -> NChannel = ensure_disconnected(kicked, Channel), Frame = error_frame(undefined, <<"Kicked out">>), shutdown_and_reply(kicked, ok, Frame, NChannel); - handle_call(discard, _From, Channel) -> Frame = error_frame(undefined, <<"Discarded">>), shutdown_and_reply(discarded, ok, Frame, Channel); - %% XXX: No Session Takeover %handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) -> % reply(Session, Channel#channel{takeover = true}); @@ -727,7 +862,6 @@ handle_call(discard, _From, Channel) -> handle_call(list_authz_cache, _From, Channel) -> %% This won't work {reply, emqx_authz_cache:list_authz_cache(), Channel}; - %% XXX: No Quota Now % handle_call({quota, Policy}, Channel) -> % Zone = info(zone, Channel), @@ -735,17 +869,18 @@ handle_call(list_authz_cache, _From, Channel) -> % reply(ok, Channel#channel{quota = Quota}); handle_call(Req, _From, Channel) -> - ?SLOG(error, #{ msg => "unexpected_call" - , call => Req - }), + ?SLOG(error, #{ + msg => "unexpected_call", + call => Req + }), reply(ignored, Channel). %%-------------------------------------------------------------------- %% Handle cast %%-------------------------------------------------------------------- --spec handle_cast(Req :: term(), channel()) - -> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. +-spec handle_cast(Req :: term(), channel()) -> + ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. handle_cast(_Req, Channel) -> {ok, Channel}. @@ -753,159 +888,195 @@ handle_cast(_Req, Channel) -> %% Handle Info %%-------------------------------------------------------------------- --spec(handle_info(Info :: term(), channel()) - -> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}). +-spec handle_info(Info :: term(), channel()) -> + ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = idle}) -> +handle_info( + {sock_closed, Reason}, + Channel = #channel{conn_state = idle} +) -> shutdown(Reason, Channel); - -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = connecting}) -> +handle_info( + {sock_closed, Reason}, + Channel = #channel{conn_state = connecting} +) -> shutdown(Reason, Channel); - -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = connected, - clientinfo = _ClientInfo}) -> +handle_info( + {sock_closed, Reason}, + Channel = #channel{ + conn_state = connected, + clientinfo = _ClientInfo + } +) -> %% XXX: Flapping detect ??? %% How to get the flapping detect policy ??? %emqx_zone:enable_flapping_detect(Zone) % andalso emqx_flapping:detect(ClientInfo), NChannel = ensure_disconnected(Reason, Channel), shutdown(Reason, NChannel); - -handle_info({sock_closed, Reason}, - Channel = #channel{conn_state = disconnected}) -> - ?SLOG(error, #{ msg => "unexpected_sock_closed" - , reason => Reason - }), +handle_info( + {sock_closed, Reason}, + Channel = #channel{conn_state = disconnected} +) -> + ?SLOG(error, #{ + msg => "unexpected_sock_closed", + reason => Reason + }), {ok, Channel}; - handle_info(clean_authz_cache, Channel) -> ok = emqx_authz_cache:empty_authz_cache(), {ok, Channel}; - handle_info({subscribe, _}, Channel) -> {ok, Channel}; - handle_info(Info, Channel) -> - ?SLOG(error, #{ msg => "unexpected_info" - , info => Info - }), + ?SLOG(error, #{ + msg => "unexpected_info", + info => Info + }), {ok, Channel}. %%-------------------------------------------------------------------- %% Ensure disconnected -ensure_disconnected(Reason, Channel = #channel{ - ctx = Ctx, - conninfo = ConnInfo, - clientinfo = ClientInfo}) -> +ensure_disconnected( + Reason, + Channel = #channel{ + ctx = Ctx, + conninfo = ConnInfo, + clientinfo = ClientInfo + } +) -> NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, - ok = run_hooks(Ctx, 'client.disconnected', - [ClientInfo, Reason, NConnInfo]), + ok = run_hooks( + Ctx, + 'client.disconnected', + [ClientInfo, Reason, NConnInfo] + ), Channel#channel{conninfo = NConnInfo, conn_state = disconnected}. %%-------------------------------------------------------------------- %% Handle Delivers from broker to client %%-------------------------------------------------------------------- --spec(handle_deliver(list(emqx_types:deliver()), channel()) - -> {ok, channel()} - | {ok, replies(), channel()}). - -handle_deliver(Delivers, - Channel = #channel{ - ctx = Ctx, - clientinfo = ClientInfo, - subscriptions = Subs - }) -> +-spec handle_deliver(list(emqx_types:deliver()), channel()) -> + {ok, channel()} + | {ok, replies(), channel()}. +handle_deliver( + Delivers, + Channel = #channel{ + ctx = Ctx, + clientinfo = ClientInfo, + subscriptions = Subs + } +) -> %% TODO: Re-deliver ??? %% Shared-subscription support ??? - Frames0 = lists:foldl(fun({_, _, Message}, Acc) -> - Topic0 = emqx_message:topic(Message), - case lists:keyfind(Topic0, 2, Subs) of - {Id, Topic, Ack, _SubOpts} -> - %% XXX: refactor later - metrics_inc('messages.delivered', Channel), - NMessage = run_hooks_without_metrics( - Ctx, - 'message.delivered', - [ClientInfo], - Message - ), - Topic = emqx_message:topic(NMessage), - Headers = emqx_message:get_headers(NMessage), - Payload = emqx_message:payload(NMessage), - Headers0 = [{<<"subscription">>, Id}, - {<<"message-id">>, next_msgid()}, - {<<"destination">>, Topic}, - {<<"content-type">>, <<"text/plain">>}], - Headers1 = case Ack of - _ when Ack =:= <<"client">>; - Ack =:= <<"client-individual">> -> - Headers0 ++ [{<<"ack">>, next_ackid()}]; - _ -> - Headers0 - end, - Frame = #stomp_frame{command = <<"MESSAGE">>, - headers = Headers1 ++ maps:get(stomp_headers, Headers, []), - body = Payload - }, - [Frame|Acc]; - false -> - ?SLOG(error, #{ msg => "dropped_message_due_to_subscription_not_found" - , message => Message - , topic => emqx_message:topic(Message) - }), - metrics_inc('delivery.dropped', Channel), - metrics_inc('delivery.dropped.no_subid', Channel), - Acc - end - end, [], Delivers), + Frames0 = lists:foldl( + fun({_, _, Message}, Acc) -> + Topic0 = emqx_message:topic(Message), + case lists:keyfind(Topic0, 2, Subs) of + {Id, Topic, Ack, _SubOpts} -> + %% XXX: refactor later + metrics_inc('messages.delivered', Channel), + NMessage = run_hooks_without_metrics( + Ctx, + 'message.delivered', + [ClientInfo], + Message + ), + Topic = emqx_message:topic(NMessage), + Headers = emqx_message:get_headers(NMessage), + Payload = emqx_message:payload(NMessage), + Headers0 = [ + {<<"subscription">>, Id}, + {<<"message-id">>, next_msgid()}, + {<<"destination">>, Topic}, + {<<"content-type">>, <<"text/plain">>} + ], + Headers1 = + case Ack of + _ when + Ack =:= <<"client">>; + Ack =:= <<"client-individual">> + -> + Headers0 ++ [{<<"ack">>, next_ackid()}]; + _ -> + Headers0 + end, + Frame = #stomp_frame{ + command = <<"MESSAGE">>, + headers = Headers1 ++ maps:get(stomp_headers, Headers, []), + body = Payload + }, + [Frame | Acc]; + false -> + ?SLOG(error, #{ + msg => "dropped_message_due_to_subscription_not_found", + message => Message, + topic => emqx_message:topic(Message) + }), + metrics_inc('delivery.dropped', Channel), + metrics_inc('delivery.dropped.no_subid', Channel), + Acc + end + end, + [], + Delivers + ), {ok, [{outgoing, lists:reverse(Frames0)}], Channel}. %%-------------------------------------------------------------------- %% Handle timeout %%-------------------------------------------------------------------- --spec(handle_timeout(reference(), Msg :: term(), channel()) - -> {ok, channel()} - | {ok, replies(), channel()} - | {shutdown, Reason :: term(), channel()}). +-spec handle_timeout(reference(), Msg :: term(), channel()) -> + {ok, channel()} + | {ok, replies(), channel()} + | {shutdown, Reason :: term(), channel()}. -handle_timeout(_TRef, {keepalive, NewVal}, - Channel = #channel{heartbeat = HrtBt}) -> +handle_timeout( + _TRef, + {keepalive, NewVal}, + Channel = #channel{heartbeat = HrtBt} +) -> case emqx_stomp_heartbeat:check(incoming, NewVal, HrtBt) of {error, timeout} -> shutdown(heartbeat_timeout, Channel); {ok, NHrtBt} -> - {ok, reset_timer(incoming_timer, - Channel#channel{heartbeat = NHrtBt} - )} + {ok, + reset_timer( + incoming_timer, + Channel#channel{heartbeat = NHrtBt} + )} end; - -handle_timeout(_TRef, {keepalive_send, NewVal}, - Channel = #channel{heartbeat = HrtBt}) -> +handle_timeout( + _TRef, + {keepalive_send, NewVal}, + Channel = #channel{heartbeat = HrtBt} +) -> case emqx_stomp_heartbeat:check(outgoing, NewVal, HrtBt) of {error, timeout} -> NHrtBt = emqx_stomp_heartbeat:reset(outgoing, NewVal, HrtBt), NChannel = Channel#channel{heartbeat = NHrtBt}, {ok, {outgoing, emqx_stomp_frame:make(?CMD_HEARTBEAT)}, - reset_timer(outgoing_timer, NChannel)}; + reset_timer(outgoing_timer, NChannel)}; {ok, NHrtBt} -> - {ok, reset_timer(outgoing_timer, - Channel#channel{heartbeat = NHrtBt} - )} + {ok, + reset_timer( + outgoing_timer, + Channel#channel{heartbeat = NHrtBt} + )} end; - handle_timeout(_TRef, clean_trans, Channel = #channel{transaction = Trans}) -> Now = erlang:system_time(millisecond), - NTrans = maps:filter(fun(_, {StartedAt, _}) -> - StartedAt + ?TRANS_TIMEOUT < Now - end, Trans), + NTrans = maps:filter( + fun(_, {StartedAt, _}) -> + StartedAt + ?TRANS_TIMEOUT < Now + end, + Trans + ), {ok, ensure_clean_trans_timer(Channel#channel{transaction = NTrans})}. %%-------------------------------------------------------------------- @@ -913,9 +1084,10 @@ handle_timeout(_TRef, clean_trans, Channel = #channel{transaction = Trans}) -> %%-------------------------------------------------------------------- terminate(Reason, #channel{ - ctx = Ctx, - session = Session, - clientinfo = ClientInfo}) -> + ctx = Ctx, + session = Session, + clientinfo = ClientInfo +}) -> run_hooks(Ctx, 'session.terminated', [ClientInfo, Reason, Session]). reply(Reply, Channel) -> @@ -932,18 +1104,17 @@ shutdown_and_reply(Reason, Reply, OutPkt, Channel) -> do_negotiate_version(undefined) -> {ok, <<"1.0">>}; - do_negotiate_version(Accepts) -> - do_negotiate_version( - ?STOMP_VER, - lists:reverse(lists:sort(binary:split(Accepts, <<",">>, [global]))) - ). + do_negotiate_version( + ?STOMP_VER, + lists:reverse(lists:sort(binary:split(Accepts, <<",">>, [global]))) + ). do_negotiate_version(Ver, []) -> {error, <<"Supported protocol versions < ", Ver/binary>>}; -do_negotiate_version(Ver, [AcceptVer|_]) when Ver >= AcceptVer -> +do_negotiate_version(Ver, [AcceptVer | _]) when Ver >= AcceptVer -> {ok, AcceptVer}; -do_negotiate_version(Ver, [_|T]) -> +do_negotiate_version(Ver, [_ | T]) -> do_negotiate_version(Ver, T). header(Name, Headers) -> @@ -966,50 +1137,62 @@ error_frame(Headers, ReceiptId, Msg) -> emqx_stomp_frame:make(<<"ERROR">>, [{<<"receipt-id">>, ReceiptId} | Headers], Msg). next_msgid() -> - MsgId = case get(msgid) of - undefined -> 1; - I -> I - end, + MsgId = + case get(msgid) of + undefined -> 1; + I -> I + end, put(msgid, MsgId + 1), MsgId. next_ackid() -> - AckId = case get(ackid) of - undefined -> 1; - I -> I - end, + AckId = + case get(ackid) of + undefined -> 1; + I -> I + end, put(ackid, AckId + 1), AckId. -frame2message(?PACKET(?CMD_SEND, Headers, Body), - #channel{ - conninfo = #{proto_ver := ProtoVer}, - clientinfo = #{ - protocol := Protocol, - clientid := ClientId, - username := Username, - peerhost := PeerHost, - mountpoint := Mountpoint - }}) -> +frame2message( + ?PACKET(?CMD_SEND, Headers, Body), + #channel{ + conninfo = #{proto_ver := ProtoVer}, + clientinfo = #{ + protocol := Protocol, + clientid := ClientId, + username := Username, + peerhost := PeerHost, + mountpoint := Mountpoint + } + } +) -> Topic = header(<<"destination">>, Headers), Msg = emqx_message:make(ClientId, Topic, Body), StompHeaders = lists:foldl( - fun(Key, Headers0) -> - proplists:delete(Key, Headers0) - end, Headers, - [<<"destination">>, - <<"content-length">>, - <<"content-type">>, - <<"transaction">>, - <<"receipt">> - ]), + fun(Key, Headers0) -> + proplists:delete(Key, Headers0) + end, + Headers, + [ + <<"destination">>, + <<"content-length">>, + <<"content-type">>, + <<"transaction">>, + <<"receipt">> + ] + ), %% Pass-through of custom headers on the sending side - NMsg = emqx_message:set_headers(#{proto_ver => ProtoVer, - protocol => Protocol, - username => Username, - peerhost => PeerHost, - stomp_headers => StompHeaders - }, Msg), + NMsg = emqx_message:set_headers( + #{ + proto_ver => ProtoVer, + protocol => Protocol, + username => Username, + peerhost => PeerHost, + stomp_headers => StompHeaders + }, + Msg + ), emqx_mountpoint:mount(Mountpoint, NMsg). receipt_id(Headers) -> @@ -1021,11 +1204,13 @@ receipt_id(Headers) -> add_action(TxId, Action, ReceiptId, Channel = #channel{transaction = Trans}) -> case maps:get(TxId, Trans, undefined) of {_StartedAt, Actions} -> - NTrans = Trans#{TxId => {_StartedAt, [Action|Actions]}}, + NTrans = Trans#{TxId => {_StartedAt, [Action | Actions]}}, {ok, Channel#channel{transaction = NTrans}}; _ -> - ErrFrame = error_frame(ReceiptId, - ["Transaction ", TxId, " not found"]), + ErrFrame = error_frame( + ReceiptId, + ["Transaction ", TxId, " not found"] + ), {ok, {outgoing, ErrFrame}, Channel} end. @@ -1051,7 +1236,7 @@ maybe_outgoing_receipt(ReceiptId, Channel) -> maybe_outgoing_receipt(undefined, Outgoings, Channel) -> {ok, Outgoings, Channel}; maybe_outgoing_receipt(ReceiptId, Outgoings, Channel) -> - {ok, lists:reverse([receipt_frame(ReceiptId)|Outgoings]), Channel}. + {ok, lists:reverse([receipt_frame(ReceiptId) | Outgoings]), Channel}. ensure_clean_trans_timer(Channel = #channel{transaction = Trans}) -> case maps:size(Trans) of @@ -1068,8 +1253,9 @@ reverse_heartbeats({Cx, Cy}) -> ensure_heartbeart_timer(Channel = #channel{clientinfo = ClientInfo}) -> Heartbeat = maps:get(heartbeat, ClientInfo), ensure_timer( - [incoming_timer, outgoing_timer], - Channel#channel{heartbeat = emqx_stomp_heartbeat:init(Heartbeat)}). + [incoming_timer, outgoing_timer], + Channel#channel{heartbeat = emqx_stomp_heartbeat:init(Heartbeat)} + ). %%-------------------------------------------------------------------- %% Timer @@ -1078,13 +1264,13 @@ ensure_timer([Name], Channel) -> ensure_timer(Name, Channel); ensure_timer([Name | Rest], Channel) -> ensure_timer(Rest, ensure_timer(Name, Channel)); - ensure_timer(Name, Channel = #channel{timers = Timers}) -> TRef = maps:get(Name, Timers, undefined), Time = interval(Name, Channel), case TRef == undefined andalso is_integer(Time) andalso Time > 0 of - true -> ensure_timer(Name, Time, Channel); - false -> Channel %% Timer disabled or exists + true -> ensure_timer(Name, Time, Channel); + %% Timer disabled or exists + false -> Channel end. ensure_timer(Name, Time, Channel = #channel{timers = Timers}) -> diff --git a/apps/emqx_gateway/src/stomp/emqx_stomp_frame.erl b/apps/emqx_gateway/src/stomp/emqx_stomp_frame.erl index 7158cbcdb..b2e4de15d 100644 --- a/apps/emqx_gateway/src/stomp/emqx_stomp_frame.erl +++ b/apps/emqx_gateway/src/stomp/emqx_stomp_frame.erl @@ -72,46 +72,53 @@ -include("src/stomp/include/emqx_stomp.hrl"). --export([ initial_parse_state/1 - , parse/2 - , serialize_opts/0 - , serialize_pkt/2 - ]). +-export([ + initial_parse_state/1, + parse/2, + serialize_opts/0, + serialize_pkt/2 +]). --export([ make/1 - , make/2 - , make/3 - , format/1 - ]). +-export([ + make/1, + make/2, + make/3, + format/1 +]). --export([ type/1 - , is_message/1 - ]). +-export([ + type/1, + is_message/1 +]). --define(NULL, 0). --define(CR, $\r). --define(LF, $\n). --define(BSL, $\\). +-define(NULL, 0). +-define(CR, $\r). +-define(LF, $\n). +-define(BSL, $\\). -define(COLON, $:). -define(IS_ESC(Ch), Ch == ?CR; Ch == ?LF; Ch == ?BSL; Ch == ?COLON). --record(parser_state, {cmd, - headers = [], - hdname, - acc = <<>> :: binary(), - limit}). +-record(parser_state, { + cmd, + headers = [], + hdname, + acc = <<>> :: binary(), + limit +}). -record(frame_limit, {max_header_num, max_header_length, max_body_length}). --type(parse_result() :: {ok, stomp_frame(), binary(), parse_state()} - | {more, parse_state()}). +-type parse_result() :: + {ok, stomp_frame(), binary(), parse_state()} + | {more, parse_state()}. --type(parse_state() :: - #{phase := none | command | headers | hdname | hdvalue | body, +-type parse_state() :: + #{ + phase := none | command | headers | hdname | hdvalue | body, pre => binary(), state := #parser_state{} - }). + }. %-dialyzer({nowarn_function, [serialize_pkt/2,make/1]}). @@ -122,10 +129,10 @@ initial_parse_state(Opts) -> limit(Opts) -> #frame_limit{ - max_header_num = g(max_header_num, Opts, ?MAX_HEADER_NUM), - max_header_length = g(max_header_length, Opts, ?MAX_HEADER_LENGTH), - max_body_length = g(max_body_length, Opts, ?MAX_BODY_LENGTH) - }. + max_header_num = g(max_header_num, Opts, ?MAX_HEADER_NUM), + max_header_length = g(max_header_length, Opts, ?MAX_HEADER_LENGTH), + max_body_length = g(max_body_length, Opts, ?MAX_BODY_LENGTH) + }. g(Key, Opts, Val) -> maps:get(Key, Opts, Val). @@ -133,14 +140,12 @@ g(Key, Opts, Val) -> -spec parse(binary(), parse_state()) -> parse_result(). parse(<<>>, Parser) -> {more, Parser}; - parse(Bytes, #{phase := body, length := Len, state := State}) -> parse(body, Bytes, State, Len); parse(<>, #{phase := hdname, state := State}) -> parse(body, Bytes, State, content_len(State)); parse(Bytes, #{phase := Phase, state := State}) when Phase =/= none -> parse(Phase, Bytes, State); - parse(Bytes, Parser = #{pre := Pre}) -> parse(<
>, maps:without([pre], Parser));
 parse(<>, #{phase := Phase, state := State}) ->
@@ -149,17 +154,21 @@ parse(<>, Parser) ->
     {more, Parser#{pre => <>}};
 parse(<>, _Parser) ->
     error(linefeed_expected);
-
-parse(<>, Parser = #{phase := Phase}) when Phase =:= hdname;
-                                                 Phase =:= hdvalue ->
+parse(<>, Parser = #{phase := Phase}) when
+    Phase =:= hdname;
+    Phase =:= hdvalue
+->
     {more, Parser#{pre => <>}};
-parse(<>,
-      #{phase := Phase, state := State}) when Phase =:= hdname;
-                                              Phase =:= hdvalue ->
+parse(
+    <>,
+    #{phase := Phase, state := State}
+) when
+    Phase =:= hdname;
+    Phase =:= hdvalue
+->
     parse(Phase, Rest, acc(unescape(Ch), State));
-
 parse(<>, Parser = #{phase := none}) ->
-    {more,  Parser};
+    {more, Parser};
 parse(Bytes, #{phase := none, state := State}) ->
     parse(command, Bytes, State).
 
@@ -170,12 +179,10 @@ parse(command, <>, State) ->
     parse(command, Rest, acc(Ch, State));
 parse(command, <<>>, State) ->
     {more, #{phase => command, state => State}};
-
 parse(headers, <>, State) ->
     parse(body, Rest, State, content_len(State#parser_state{acc = <<>>}));
 parse(headers, Bin, State) ->
     parse(hdname, Bin, State);
-
 parse(hdname, <>, _State) ->
     error(unexpected_linefeed);
 parse(hdname, <>, State = #parser_state{acc = Acc}) ->
@@ -184,13 +191,16 @@ parse(hdname, <>, State) ->
     parse(hdname, Rest, acc(Ch, State));
 parse(hdname, <<>>, State) ->
     {more, #{phase => hdname, state => State}};
-
-parse(hdvalue, <>,
-      State = #parser_state{headers = Headers, hdname = Name, acc = Acc}) ->
-    NState = State#parser_state{headers = add_header(Name, Acc, Headers),
-                                hdname = undefined,
-                                acc = <<>>
-                               },
+parse(
+    hdvalue,
+    <>,
+    State = #parser_state{headers = Headers, hdname = Name, acc = Acc}
+) ->
+    NState = State#parser_state{
+        headers = add_header(Name, Acc, Headers),
+        hdname = undefined,
+        acc = <<>>
+    },
     parse(headers, Rest, NState);
 parse(hdvalue, <>, State) ->
     parse(hdvalue, Rest, acc(Ch, State));
@@ -205,28 +215,32 @@ parse(body, Bin, State, none) ->
         [Chunk, Rest] ->
             {ok, new_frame(acc(Chunk, State)), Rest, new_state(State)};
         [Chunk] ->
-            {more, #{phase => body,
-                     length => none,
-                     state => acc(Chunk, State)}}
+            {more, #{
+                phase => body,
+                length => none,
+                state => acc(Chunk, State)
+            }}
     end;
-parse(body, Bin, State, Len) when byte_size(Bin) >= (Len+1) ->
+parse(body, Bin, State, Len) when byte_size(Bin) >= (Len + 1) ->
     <> = Bin,
     {ok, new_frame(acc(Chunk, State)), Rest, new_state(State)};
 parse(body, Bin, State, Len) ->
-    {more, #{phase => body,
-             length => Len - byte_size(Bin),
-             state => acc(Bin, State)}}.
+    {more, #{
+        phase => body,
+        length => Len - byte_size(Bin),
+        state => acc(Bin, State)
+    }}.
 
 add_header(Name, Value, Headers) ->
     case lists:keyfind(Name, 1, Headers) of
         {Name, _} -> Headers;
-        false     -> [{Name, Value} | Headers]
+        false -> [{Name, Value} | Headers]
     end.
 
 content_len(#parser_state{headers = Headers}) ->
     case lists:keyfind(<<"content-length">>, 1, Headers) of
         {_, Val} -> list_to_integer(binary_to_list(Val));
-        false    -> none
+        false -> none
     end.
 
 new_frame(#parser_state{cmd = Cmd, headers = Headers, acc = Acc}) ->
@@ -241,9 +255,9 @@ acc(Ch, State = #parser_state{acc = Acc}) ->
 %% \n (octet 92 and 110) translates to line feed (octet 10)
 %% \c (octet 92 and 99) translates to : (octet 58)
 %% \\ (octet 92 and 92) translates to \ (octet 92)
-unescape($r)  -> ?CR;
-unescape($n)  -> ?LF;
-unescape($c)  -> ?COLON;
+unescape($r) -> ?CR;
+unescape($n) -> ?LF;
+unescape($c) -> ?COLON;
 unescape($\\) -> ?BSL;
 unescape(_Ch) -> error(cannnot_unescape).
 
@@ -256,31 +270,41 @@ serialize_opts() ->
 
 serialize_pkt(#stomp_frame{command = ?CMD_HEARTBEAT}, _SerializeOpts) ->
     <<$\n>>;
-
-serialize_pkt(#stomp_frame{command = Cmd, headers = Headers, body = Body},
-             _SerializeOpts) ->
+serialize_pkt(
+    #stomp_frame{command = Cmd, headers = Headers, body = Body},
+    _SerializeOpts
+) ->
     Headers1 = lists:keydelete(<<"content-length">>, 1, Headers),
     Headers2 =
-    case iolist_size(Body) of
-        0   -> Headers1;
-        Len -> Headers1 ++ [{<<"content-length">>, Len}]
-    end,
-    [Cmd,
-     ?LF, [serialize_pkt(header, Header) || Header <- Headers2],
-     ?LF, Body, 0];
-
+        case iolist_size(Body) of
+            0 -> Headers1;
+            Len -> Headers1 ++ [{<<"content-length">>, Len}]
+        end,
+    [
+        Cmd,
+        ?LF,
+        [serialize_pkt(header, Header) || Header <- Headers2],
+        ?LF,
+        Body,
+        0
+    ];
 serialize_pkt(header, {Name, Val}) when is_integer(Val) ->
     [escape(Name), ?COLON, integer_to_list(Val), ?LF];
 serialize_pkt(header, {Name, Val}) ->
     [escape(Name), ?COLON, escape(Val), ?LF].
 
 escape(Bin) when is_binary(Bin) ->
-    << <<(escape(Ch))/binary>> || <> <= Bin >>;
-escape(?CR)    -> <>;
-escape(?LF)    -> <>;
-escape(?BSL)   -> <>;
-escape(?COLON) -> <>;
-escape(Ch)     -> <>.
+    <<<<(escape(Ch))/binary>> || <> <= Bin>>;
+escape(?CR) ->
+    <>;
+escape(?LF) ->
+    <>;
+escape(?BSL) ->
+    <>;
+escape(?COLON) ->
+    <>;
+escape(Ch) ->
+    <>.
 
 new_state(#parser_state{limit = Limit}) ->
     #{phase => none, state => #parser_state{limit = Limit}}.
@@ -295,9 +319,10 @@ make(?CMD_HEARTBEAT) ->
     #stomp_frame{command = ?CMD_HEARTBEAT}.
 
 make(<<"CONNECTED">>, Headers) ->
-    #stomp_frame{command = <<"CONNECTED">>,
-                 headers = [{<<"server">>, ?STOMP_SERVER} | Headers]};
-
+    #stomp_frame{
+        command = <<"CONNECTED">>,
+        headers = [{<<"server">>, ?STOMP_SERVER} | Headers]
+    };
 make(Command, Headers) ->
     #stomp_frame{command = Command, headers = Headers}.
 
@@ -307,27 +332,45 @@ make(Command, Headers, Body) ->
 %% @doc Format a frame
 format(Frame) -> serialize_pkt(Frame, #{}).
 
-is_message(#stomp_frame{command = CMD})
-    when CMD == ?CMD_SEND;
-         CMD == ?CMD_MESSAGE ->
+is_message(#stomp_frame{command = CMD}) when
+    CMD == ?CMD_SEND;
+    CMD == ?CMD_MESSAGE
+->
     true;
-is_message(_) -> false.
+is_message(_) ->
+    false.
 
 type(#stomp_frame{command = CMD}) ->
     type(CMD);
-type(?CMD_STOMP)       -> connect;
-type(?CMD_CONNECT)     -> connect;
-type(?CMD_SEND)        -> send;
-type(?CMD_SUBSCRIBE)   -> subscribe;
-type(?CMD_UNSUBSCRIBE) -> unsubscribe;
-type(?CMD_BEGIN)       -> 'begin';
-type(?CMD_COMMIT)      -> commit;
-type(?CMD_ABORT)       -> abort;
-type(?CMD_ACK)         -> ack;
-type(?CMD_NACK)        -> nack;
-type(?CMD_DISCONNECT)  -> disconnect;
-type(?CMD_CONNECTED)   -> connected;
-type(?CMD_MESSAGE)     -> message;
-type(?CMD_RECEIPT)     -> receipt;
-type(?CMD_ERROR)       -> error;
-type(?CMD_HEARTBEAT)   -> heartbeat.
+type(?CMD_STOMP) ->
+    connect;
+type(?CMD_CONNECT) ->
+    connect;
+type(?CMD_SEND) ->
+    send;
+type(?CMD_SUBSCRIBE) ->
+    subscribe;
+type(?CMD_UNSUBSCRIBE) ->
+    unsubscribe;
+type(?CMD_BEGIN) ->
+    'begin';
+type(?CMD_COMMIT) ->
+    commit;
+type(?CMD_ABORT) ->
+    abort;
+type(?CMD_ACK) ->
+    ack;
+type(?CMD_NACK) ->
+    nack;
+type(?CMD_DISCONNECT) ->
+    disconnect;
+type(?CMD_CONNECTED) ->
+    connected;
+type(?CMD_MESSAGE) ->
+    message;
+type(?CMD_RECEIPT) ->
+    receipt;
+type(?CMD_ERROR) ->
+    error;
+type(?CMD_HEARTBEAT) ->
+    heartbeat.
diff --git a/apps/emqx_gateway/src/stomp/emqx_stomp_heartbeat.erl b/apps/emqx_gateway/src/stomp/emqx_stomp_heartbeat.erl
index 2d26b47a3..41021d490 100644
--- a/apps/emqx_gateway/src/stomp/emqx_stomp_heartbeat.erl
+++ b/apps/emqx_gateway/src/stomp/emqx_stomp_heartbeat.erl
@@ -19,20 +19,22 @@
 
 -include("src/stomp/include/emqx_stomp.hrl").
 
--export([ init/1
-        , check/3
-        , reset/3
-        , info/1
-        , interval/2
-        ]).
+-export([
+    init/1,
+    check/3,
+    reset/3,
+    info/1,
+    interval/2
+]).
 
 -record(heartbeater, {interval, statval, repeat}).
 
 -type name() :: incoming | outgoing.
 
--type heartbeat() :: #{incoming => #heartbeater{},
-                       outgoing => #heartbeater{}
-                      }.
+-type heartbeat() :: #{
+    incoming => #heartbeater{},
+    outgoing => #heartbeater{}
+}.
 
 %%--------------------------------------------------------------------
 %% APIs
@@ -42,43 +44,51 @@
 init({0, 0}) ->
     #{};
 init({Cx, Cy}) ->
-    maps:filter(fun(_, V) -> V /= undefined end,
-      #{incoming => heartbeater(Cx),
-        outgoing => heartbeater(Cy)
-       }).
+    maps:filter(
+        fun(_, V) -> V /= undefined end,
+        #{
+            incoming => heartbeater(Cx),
+            outgoing => heartbeater(Cy)
+        }
+    ).
 
 heartbeater(0) ->
     undefined;
 heartbeater(I) ->
     #heartbeater{
-       interval = I,
-       statval = 0,
-       repeat = 0
-      }.
+        interval = I,
+        statval = 0,
+        repeat = 0
+    }.
 
--spec check(name(), pos_integer(), heartbeat())
-    -> {ok, heartbeat()}
-     | {error, timeout}.
+-spec check(name(), pos_integer(), heartbeat()) ->
+    {ok, heartbeat()}
+    | {error, timeout}.
 check(Name, NewVal, HrtBt) ->
     HrtBter = maps:get(Name, HrtBt),
     case check(NewVal, HrtBter) of
         {error, _} = R -> R;
-        {ok, NHrtBter} ->
-            {ok, HrtBt#{Name => NHrtBter}}
+        {ok, NHrtBter} -> {ok, HrtBt#{Name => NHrtBter}}
     end.
 
-check(NewVal, HrtBter = #heartbeater{statval = OldVal,
-                                     repeat = Repeat}) ->
+check(
+    NewVal,
+    HrtBter = #heartbeater{
+        statval = OldVal,
+        repeat = Repeat
+    }
+) ->
     if
         NewVal =/= OldVal ->
             {ok, HrtBter#heartbeater{statval = NewVal, repeat = 0}};
         Repeat < 1 ->
             {ok, HrtBter#heartbeater{repeat = Repeat + 1}};
-        true -> {error, timeout}
+        true ->
+            {error, timeout}
     end.
 
--spec reset(name(), pos_integer(), heartbeat())
-    -> heartbeat().
+-spec reset(name(), pos_integer(), heartbeat()) ->
+    heartbeat().
 reset(Name, NewVal, HrtBt) ->
     HrtBter = maps:get(Name, HrtBt),
     HrtBt#{Name => reset(NewVal, HrtBter)}.
@@ -88,11 +98,19 @@ reset(NewVal, HrtBter) ->
 
 -spec info(heartbeat()) -> map().
 info(HrtBt) ->
-    maps:map(fun(_, #heartbeater{interval = Intv,
-                                 statval = Val,
-                                 repeat = Repeat}) ->
+    maps:map(
+        fun(
+            _,
+            #heartbeater{
+                interval = Intv,
+                statval = Val,
+                repeat = Repeat
+            }
+        ) ->
             #{interval => Intv, statval => Val, repeat => Repeat}
-             end, HrtBt).
+        end,
+        HrtBt
+    ).
 
 interval(Type, HrtBt) ->
     case maps:get(Type, HrtBt, undefined) of
diff --git a/apps/emqx_gateway/src/stomp/emqx_stomp_impl.erl b/apps/emqx_gateway/src/stomp/emqx_stomp_impl.erl
index 47d1899c9..4a71399a4 100644
--- a/apps/emqx_gateway/src/stomp/emqx_stomp_impl.erl
+++ b/apps/emqx_gateway/src/stomp/emqx_stomp_impl.erl
@@ -21,21 +21,26 @@
 -include_lib("emqx/include/logger.hrl").
 -include_lib("emqx_gateway/include/emqx_gateway.hrl").
 
--import(emqx_gateway_utils,
-        [ normalize_config/1
-        , start_listeners/4
-        , stop_listeners/2
-        ]).
+-import(
+    emqx_gateway_utils,
+    [
+        normalize_config/1,
+        start_listeners/4,
+        stop_listeners/2
+    ]
+).
 
 %% APIs
--export([ reg/0
-        , unreg/0
-        ]).
+-export([
+    reg/0,
+    unreg/0
+]).
 
--export([ on_gateway_load/2
-        , on_gateway_update/3
-        , on_gateway_unload/2
-        ]).
+-export([
+    on_gateway_load/2,
+    on_gateway_update/3,
+    on_gateway_unload/2
+]).
 
 %%--------------------------------------------------------------------
 %% APIs
@@ -43,8 +48,7 @@
 
 -spec reg() -> ok | {error, any()}.
 reg() ->
-    RegistryOptions = [ {cbkmod, ?MODULE}
-                      ],
+    RegistryOptions = [{cbkmod, ?MODULE}],
     emqx_gateway_registry:reg(stomp, RegistryOptions).
 
 -spec unreg() -> ok | {error, any()}.
@@ -55,24 +59,35 @@ unreg() ->
 %% emqx_gateway_registry callbacks
 %%--------------------------------------------------------------------
 
-on_gateway_load(_Gateway = #{ name := GwName,
-                              config := Config
-                            }, Ctx) ->
+on_gateway_load(
+    _Gateway = #{
+        name := GwName,
+        config := Config
+    },
+    Ctx
+) ->
     Listeners = normalize_config(Config),
-    ModCfg = #{frame_mod => emqx_stomp_frame,
-               chann_mod => emqx_stomp_channel
-              },
-    case start_listeners(
-           Listeners, GwName, Ctx, ModCfg) of
+    ModCfg = #{
+        frame_mod => emqx_stomp_frame,
+        chann_mod => emqx_stomp_channel
+    },
+    case
+        start_listeners(
+            Listeners, GwName, Ctx, ModCfg
+        )
+    of
         {ok, ListenerPids} ->
             %% FIXME: How to throw an exception to interrupt the restart logic ?
             %% FIXME: Assign ctx to GwState
             {ok, ListenerPids, _GwState = #{ctx => Ctx}};
         {error, {Reason, Listener}} ->
-            throw({badconf, #{ key => listeners
-                             , vallue => Listener
-                             , reason => Reason
-                             }})
+            throw(
+                {badconf, #{
+                    key => listeners,
+                    vallue => Listener,
+                    reason => Reason
+                }}
+            )
     end.
 
 on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) ->
@@ -83,15 +98,21 @@ on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) ->
         on_gateway_unload(Gateway, GwState),
         on_gateway_load(Gateway#{config => Config}, Ctx)
     catch
-        Class : Reason : Stk ->
-            logger:error("Failed to update ~ts; "
-                         "reason: {~0p, ~0p} stacktrace: ~0p",
-                         [GwName, Class, Reason, Stk]),
+        Class:Reason:Stk ->
+            logger:error(
+                "Failed to update ~ts; "
+                "reason: {~0p, ~0p} stacktrace: ~0p",
+                [GwName, Class, Reason, Stk]
+            ),
             {error, Reason}
     end.
 
-on_gateway_unload(_Gateway = #{ name := GwName,
-                                config := Config
-                              }, _GwState) ->
+on_gateway_unload(
+    _Gateway = #{
+        name := GwName,
+        config := Config
+    },
+    _GwState
+) ->
     Listeners = normalize_config(Config),
     stop_listeners(GwName, Listeners).
diff --git a/apps/emqx_gateway/src/stomp/include/emqx_stomp.hrl b/apps/emqx_gateway/src/stomp/include/emqx_stomp.hrl
index 94c1767fd..6c81b5909 100644
--- a/apps/emqx_gateway/src/stomp/include/emqx_stomp.hrl
+++ b/apps/emqx_gateway/src/stomp/include/emqx_stomp.hrl
@@ -26,23 +26,23 @@
 %%--------------------------------------------------------------------
 
 %% client command
--define(CMD_STOMP,       <<"STOMP">>).
--define(CMD_CONNECT,     <<"CONNECT">>).
--define(CMD_SEND,        <<"SEND">>).
--define(CMD_SUBSCRIBE,   <<"SUBSCRIBE">>).
+-define(CMD_STOMP, <<"STOMP">>).
+-define(CMD_CONNECT, <<"CONNECT">>).
+-define(CMD_SEND, <<"SEND">>).
+-define(CMD_SUBSCRIBE, <<"SUBSCRIBE">>).
 -define(CMD_UNSUBSCRIBE, <<"UNSUBSCRIBE">>).
--define(CMD_BEGIN,       <<"BEGIN">>).
--define(CMD_COMMIT,      <<"COMMIT">>).
--define(CMD_ABORT,       <<"ABORT">>).
--define(CMD_ACK,         <<"ACK">>).
--define(CMD_NACK,        <<"NACK">>).
--define(CMD_DISCONNECT,  <<"DISCONNECT">>).
+-define(CMD_BEGIN, <<"BEGIN">>).
+-define(CMD_COMMIT, <<"COMMIT">>).
+-define(CMD_ABORT, <<"ABORT">>).
+-define(CMD_ACK, <<"ACK">>).
+-define(CMD_NACK, <<"NACK">>).
+-define(CMD_DISCONNECT, <<"DISCONNECT">>).
 
 %% server command
 -define(CMD_CONNECTED, <<"CONNECTED">>).
--define(CMD_MESSAGE,   <<"MESSAGE">>).
--define(CMD_RECEIPT,   <<"RECEIPT">>).
--define(CMD_ERROR,     <<"ERROR">>).
+-define(CMD_MESSAGE, <<"MESSAGE">>).
+-define(CMD_RECEIPT, <<"RECEIPT">>).
+-define(CMD_ERROR, <<"ERROR">>).
 -define(CMD_HEARTBEAT, <<"HEARTBEAT">>).
 
 %-type client_command() :: ?CMD_SEND | ?CMD_SUBSCRIBE | ?CMD_UNSUBSCRIBE
@@ -57,10 +57,10 @@
 -type server_command() :: binary().
 
 -record(stomp_frame, {
-          command :: client_command() | server_command(),
-          headers = [],
-          body = <<>> :: iodata()}
-       ).
+    command :: client_command() | server_command(),
+    headers = [],
+    body = <<>> :: iodata()
+}).
 
 -type stomp_frame() :: #stomp_frame{}.
 
@@ -68,10 +68,11 @@
 
 -define(PACKET(CMD, Headers), #stomp_frame{command = CMD, headers = Headers}).
 
--define(PACKET(CMD, Headers, Body), #stomp_frame{command = CMD,
-                                                 headers = Headers,
-                                                 body    = Body
-                                                }).
+-define(PACKET(CMD, Headers, Body), #stomp_frame{
+    command = CMD,
+    headers = Headers,
+    body = Body
+}).
 
 %%--------------------------------------------------------------------
 %% Frame Size Limits
@@ -87,8 +88,8 @@
 %% and then close the connection.
 %%--------------------------------------------------------------------
 
--define(MAX_HEADER_NUM,    10).
+-define(MAX_HEADER_NUM, 10).
 -define(MAX_HEADER_LENGTH, 1024).
--define(MAX_BODY_LENGTH,   65536).
+-define(MAX_BODY_LENGTH, 65536).
 
 -endif.
diff --git a/apps/emqx_gateway/test/emqx_coap_SUITE.erl b/apps/emqx_gateway/test/emqx_coap_SUITE.erl
index ba3d77329..d3f4f8014 100644
--- a/apps/emqx_gateway/test/emqx_coap_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_coap_SUITE.erl
@@ -19,37 +19,40 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--import(emqx_gateway_test_utils,
-        [ request/2
-        , request/3
-        ]).
+-import(
+    emqx_gateway_test_utils,
+    [
+        request/2,
+        request/3
+    ]
+).
 
 -include_lib("er_coap_client/include/coap.hrl").
 -include_lib("emqx/include/emqx.hrl").
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
 
--define(CONF_DEFAULT, <<"
-gateway.coap
-{
-    idle_timeout = 30s
-    enable_stats = false
-    mountpoint = \"\"
-    notify_type = qos
-    connection_required = true
-    subscribe_qos = qos1
-    publish_qos = qos1
-
-    listeners.udp.default
-    {bind = 5683}
-}
-">>).
+-define(CONF_DEFAULT, <<
+    "\n"
+    "gateway.coap\n"
+    "{\n"
+    "    idle_timeout = 30s\n"
+    "    enable_stats = false\n"
+    "    mountpoint = \"\"\n"
+    "    notify_type = qos\n"
+    "    connection_required = true\n"
+    "    subscribe_qos = qos1\n"
+    "    publish_qos = qos1\n"
+    "\n"
+    "    listeners.udp.default\n"
+    "    {bind = 5683}\n"
+    "}\n"
+>>).
 
 -define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)).
 -define(PS_PREFIX, "coap://127.0.0.1/ps").
 -define(MQTT_PREFIX, "coap://127.0.0.1/mqtt").
 
-
 all() -> emqx_common_test_helpers:all(?MODULE).
 
 init_per_suite(Config) ->
@@ -58,7 +61,7 @@ init_per_suite(Config) ->
     Config.
 
 end_per_suite(_) ->
-    {ok, _} = emqx:remove_config([<<"gateway">>,<<"coap">>]),
+    {ok, _} = emqx:remove_config([<<"gateway">>, <<"coap">>]),
     emqx_mgmt_api_test_util:end_suite([emqx_gateway]).
 
 %%--------------------------------------------------------------------
@@ -72,13 +75,15 @@ t_connection(_) ->
 
         timer:sleep(100),
         ?assertNotEqual(
-           [],
-           emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)),
+            [],
+            emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)
+        ),
 
         %% heartbeat
-        HeartURI = ?MQTT_PREFIX ++
-                   "/connection?clientid=client1&token=" ++
-                   Token,
+        HeartURI =
+            ?MQTT_PREFIX ++
+                "/connection?clientid=client1&token=" ++
+                Token,
 
         ?LOGT("send heartbeat request:~ts~n", [HeartURI]),
         {ok, changed, _} = er_coap_client:request(put, HeartURI),
@@ -87,8 +92,9 @@ t_connection(_) ->
 
         timer:sleep(100),
         ?assertEqual(
-           [],
-           emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>))
+            [],
+            emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)
+        )
     end,
     do(Action).
 
@@ -110,9 +116,8 @@ t_publish(_) ->
             {deliver, Topic, Msg} ->
                 ?assertEqual(Topic, Msg#message.topic),
                 ?assertEqual(Payload, Msg#message.payload)
-        after
-            500 ->
-                ?assert(false)
+        after 500 ->
+            ?assert(false)
         end
     end,
     with_connection(Action).
@@ -168,7 +173,6 @@ t_subscribe(_) ->
 
     ?assertEqual([], emqx:subscribers(Topic)).
 
-
 t_un_subscribe(_) ->
     Topic = <<"/abc">>,
     Fun = fun(Channel, Token) ->
@@ -232,11 +236,17 @@ t_clients_api(_) ->
         #{clientid := ClientId} = Client1,
         %% searching
         {200, #{data := [Client2]}} =
-            request(get, "/gateway/coap/clients",
-                    [{<<"clientid">>, ClientId}]),
+            request(
+                get,
+                "/gateway/coap/clients",
+                [{<<"clientid">>, ClientId}]
+            ),
         {200, #{data := [Client3]}} =
-            request(get, "/gateway/coap/clients",
-                    [{<<"like_clientid">>, <<"cli">>}]),
+            request(
+                get,
+                "/gateway/coap/clients",
+                [{<<"like_clientid">>, <<"cli">>}]
+            ),
         %% lookup
         {200, Client4} =
             request(get, "/gateway/coap/clients/client1"),
@@ -255,18 +265,20 @@ t_clients_subscription_api(_) ->
         %% list
         {200, []} = request(get, Path),
         %% create
-        SubReq = #{ topic => <<"tx">>
-                  , qos => 0
-                  , nl => 0
-                  , rap => 0
-                  , rh => 0
-                  },
+        SubReq = #{
+            topic => <<"tx">>,
+            qos => 0,
+            nl => 0,
+            rap => 0,
+            rh => 0
+        },
 
         {201, SubsResp} = request(post, Path, SubReq),
         {200, [SubsResp2]} = request(get, Path),
         ?assertEqual(
-           maps:get(topic, SubsResp),
-           maps:get(topic, SubsResp2)),
+            maps:get(topic, SubsResp),
+            maps:get(topic, SubsResp2)
+        ),
 
         {204, _} = request(delete, Path ++ "/tx"),
 
@@ -276,57 +288,58 @@ t_clients_subscription_api(_) ->
 
 t_clients_get_subscription_api(_) ->
     Fun = fun(Channel, Token) ->
-                  Path = "/gateway/coap/clients/client1/subscriptions",
-                  %% list
-                  {200, []} = request(get, Path),
+        Path = "/gateway/coap/clients/client1/subscriptions",
+        %% list
+        {200, []} = request(get, Path),
 
-                  observe(Channel, Token, true),
+        observe(Channel, Token, true),
 
-                  {200, [Subs]} = request(get, Path),
+        {200, [Subs]} = request(get, Path),
 
-                  ?assertEqual(<<"/coap/observe">>, maps:get(topic, Subs)),
+        ?assertEqual(<<"/coap/observe">>, maps:get(topic, Subs)),
 
-                  observe(Channel, Token, false),
+        observe(Channel, Token, false),
 
-                  {200, []} = request(get, Path)
-          end,
+        {200, []} = request(get, Path)
+    end,
     with_connection(Fun).
 
 t_on_offline_event(_) ->
     Fun = fun(Channel) ->
-                  emqx_hooks:add('client.connected', {emqx_sys, on_client_connected, []}),
-                  emqx_hooks:add('client.disconnected', {emqx_sys, on_client_disconnected, []}),
+        emqx_hooks:add('client.connected', {emqx_sys, on_client_connected, []}),
+        emqx_hooks:add('client.disconnected', {emqx_sys, on_client_disconnected, []}),
 
-                  ConnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/connected">>,
-                  emqx_broker:subscribe(ConnectedSub),
-                  timer:sleep(100),
+        ConnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/connected">>,
+        emqx_broker:subscribe(ConnectedSub),
+        timer:sleep(100),
 
-                  Token = connection(Channel),
-                  ?assertMatch(#message{}, receive_deliver(500)),
+        Token = connection(Channel),
+        ?assertMatch(#message{}, receive_deliver(500)),
 
-                  DisconnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/disconnected">>,
-                  emqx_broker:subscribe(DisconnectedSub),
-                  timer:sleep(100),
+        DisconnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/disconnected">>,
+        emqx_broker:subscribe(DisconnectedSub),
+        timer:sleep(100),
 
-                  disconnection(Channel, Token),
+        disconnection(Channel, Token),
 
-                  ?assertMatch(#message{}, receive_deliver(500)),
+        ?assertMatch(#message{}, receive_deliver(500)),
 
-                  emqx_broker:unsubscribe(ConnectedSub),
-                  emqx_broker:unsubscribe(DisconnectedSub),
+        emqx_broker:unsubscribe(ConnectedSub),
+        emqx_broker:unsubscribe(DisconnectedSub),
 
-                  emqx_hooks:del('client.connected', {emqx_sys, on_client_connected}),
-                  emqx_hooks:del('client.disconnected', {emqx_sys, on_client_disconnected}),
-                  timer:sleep(500)
-          end,
+        emqx_hooks:del('client.connected', {emqx_sys, on_client_connected}),
+        emqx_hooks:del('client.disconnected', {emqx_sys, on_client_disconnected}),
+        timer:sleep(500)
+    end,
     do(Fun).
 
 %%--------------------------------------------------------------------
 %% helpers
 
 connection(Channel) ->
-    URI = ?MQTT_PREFIX ++
-        "/connection?clientid=client1&username=admin&password=public",
+    URI =
+        ?MQTT_PREFIX ++
+            "/connection?clientid=client1&username=admin&password=public",
     Req = make_req(post),
     {ok, created, Data} = do_request(Channel, URI, Req),
     #coap_content{payload = BinToken} = Data,
@@ -343,7 +356,6 @@ observe(Channel, Token, true) ->
     Req = make_req(get, <<>>, [{observe, 0}]),
     {ok, content, _Data} = do_request(Channel, URI, Req),
     ok;
-
 observe(Channel, Token, false) ->
     URI = ?PS_PREFIX ++ "/coap/observe?clientid=client1&token=" ++ Token,
     Req = make_req(get, <<>>, [{observe, 1}]),
@@ -360,7 +372,6 @@ make_req(Method, Payload, Opts) ->
     er_coap_message:request(con, Method, Payload, Opts).
 
 do_request(Channel, URI, #coap_message{options = Opts} = Req) ->
-
     {_, _, Path, Query} = er_coap_client:resolve_uri(URI),
     Opts2 = [{uri_path, Path}, {uri_query, Query} | Opts],
     Req2 = Req#coap_message{options = Opts2},
@@ -371,18 +382,17 @@ do_request(Channel, URI, #coap_message{options = Opts} = Req) ->
 
 with_response(Channel) ->
     receive
-        {coap_response, _ChId, Channel,
-         _Ref, Message=#coap_message{method=Code}} ->
+        {coap_response, _ChId, Channel, _Ref, Message = #coap_message{method = Code}} ->
             return_response(Code, Message);
         {coap_error, _ChId, Channel, _Ref, reset} ->
             {error, reset}
     after 2000 ->
-            {error, timeout}
+        {error, timeout}
     end.
 
 return_response({ok, Code}, Message) ->
     {ok, Code, er_coap_message:get_content(Message)};
-return_response({error, Code}, #coap_message{payload= <<>>}) ->
+return_response({error, Code}, #coap_message{payload = <<>>}) ->
     {error, Code};
 return_response({error, Code}, Message) ->
     {error, Code, er_coap_message:get_content(Message)}.
@@ -413,5 +423,5 @@ receive_deliver(Wait) ->
         {deliver, _, Msg} ->
             Msg
     after Wait ->
-            {error, timeout}
+        {error, timeout}
     end.
diff --git a/apps/emqx_gateway/test/emqx_coap_api_SUITE.erl b/apps/emqx_gateway/test/emqx_coap_api_SUITE.erl
index 54a90175f..7a7843c64 100644
--- a/apps/emqx_gateway/test/emqx_coap_api_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_coap_api_SUITE.erl
@@ -23,26 +23,28 @@
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
 
--define(CONF_DEFAULT, <<"
-gateway.coap {
-  idle_timeout = 30s
-  enable_stats = false
-  mountpoint = \"\"
-  notify_type = qos
-  connection_required = true
-  subscribe_qos = qos1
-  publish_qos = qos1
-  listeners.udp.default {
-    bind = 5683
-  }
-}
-">>).
+-define(CONF_DEFAULT, <<
+    "\n"
+    "gateway.coap {\n"
+    "  idle_timeout = 30s\n"
+    "  enable_stats = false\n"
+    "  mountpoint = \"\"\n"
+    "  notify_type = qos\n"
+    "  connection_required = true\n"
+    "  subscribe_qos = qos1\n"
+    "  publish_qos = qos1\n"
+    "  listeners.udp.default {\n"
+    "    bind = 5683\n"
+    "  }\n"
+    "}\n"
+>>).
 
 -define(HOST, "127.0.0.1").
 -define(PORT, 5683).
 -define(CONN_URI,
-          "coap://127.0.0.1/mqtt/connection?clientid=client1&"
-          "username=admin&password=public").
+    "coap://127.0.0.1/mqtt/connection?clientid=client1&"
+    "username=admin&password=public"
+).
 
 -define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)).
 
@@ -59,7 +61,7 @@ init_per_suite(Config) ->
     Config.
 
 end_per_suite(Config) ->
-    {ok, _} = emqx:remove_config([<<"gateway">>,<<"coap">>]),
+    {ok, _} = emqx:remove_config([<<"gateway">>, <<"coap">>]),
     emqx_mgmt_api_test_util:end_suite([emqx_gateway]),
     Config.
 
@@ -72,18 +74,21 @@ t_send_request_api(_) ->
     Path = emqx_mgmt_api_test_util:api_path(["gateway/coap/clients/client1/request"]),
     Token = <<"atoken">>,
     Payload = <<"simple echo this">>,
-    Req = #{token => Token,
-            payload => Payload,
-            timeout => <<"10s">>,
-            content_type => <<"text/plain">>,
-            method => <<"get">>},
+    Req = #{
+        token => Token,
+        payload => Payload,
+        timeout => <<"10s">>,
+        content_type => <<"text/plain">>,
+        method => <<"get">>
+    },
     Auth = emqx_mgmt_api_test_util:auth_header_(),
-    {ok, Response} = emqx_mgmt_api_test_util:request_api(post,
-                                                         Path,
-                                                         "method=get",
-                                                         Auth,
-                                                         Req
-                                                        ),
+    {ok, Response} = emqx_mgmt_api_test_util:request_api(
+        post,
+        Path,
+        "method=get",
+        Auth,
+        Req
+    ),
     #{<<"token">> := RToken, <<"payload">> := RPayload} =
         emqx_json:decode(Response, [return_maps]),
     ?assertEqual(Token, RToken),
@@ -113,34 +118,55 @@ test_send_coap_request(UdpSock, Method, Content, Options, MsgId) ->
     is_list(Options) orelse error("Options must be a list"),
     case resolve_uri(?CONN_URI) of
         {coap, {IpAddr, Port}, Path, Query} ->
-            Request0 = emqx_coap_message:request(con, Method, Content,
-                                                 [{uri_path, Path},
-                                                  {uri_query, Query} | Options]),
+            Request0 = emqx_coap_message:request(
+                con,
+                Method,
+                Content,
+                [
+                    {uri_path, Path},
+                    {uri_query, Query}
+                    | Options
+                ]
+            ),
             Request = Request0#coap_message{id = MsgId},
             ?LOGT("send_coap_request Request=~p", [Request]),
             RequestBinary = emqx_coap_frame:serialize_pkt(Request, undefined),
-            ?LOGT("test udp socket send to ~p:~p, data=~p",
-                  [IpAddr, Port, RequestBinary]),
+            ?LOGT(
+                "test udp socket send to ~p:~p, data=~p",
+                [IpAddr, Port, RequestBinary]
+            ),
             ok = gen_udp:send(UdpSock, IpAddr, Port, RequestBinary);
         {SchemeDiff, ChIdDiff, _, _} ->
             error(
-              lists:flatten(
-                io_lib:format(
-                    "scheme ~ts or ChId ~ts does not match with socket",
-                    [SchemeDiff, ChIdDiff])
-               ))
+                lists:flatten(
+                    io_lib:format(
+                        "scheme ~ts or ChId ~ts does not match with socket",
+                        [SchemeDiff, ChIdDiff]
+                    )
+                )
+            )
     end.
 
 test_recv_coap_response(UdpSock) ->
     {ok, {Address, Port, Packet}} = gen_udp:recv(UdpSock, 0, 2000),
     {ok, Response, _, _} = emqx_coap_frame:parse(Packet, undefined),
-    ?LOGT("test udp receive from ~p:~p, data1=~p, Response=~p",
-          [Address, Port, Packet, Response]),
+    ?LOGT(
+        "test udp receive from ~p:~p, data1=~p, Response=~p",
+        [Address, Port, Packet, Response]
+    ),
     #coap_message{
-       type = ack, method = Method, id = Id,
-       token = Token, options = Options, payload = Payload} = Response,
-    ?LOGT("receive coap response Method=~p, Id=~p, Token=~p, "
-          "Options=~p, Payload=~p", [Method, Id, Token, Options, Payload]),
+        type = ack,
+        method = Method,
+        id = Id,
+        token = Token,
+        options = Options,
+        payload = Payload
+    } = Response,
+    ?LOGT(
+        "receive coap response Method=~p, Id=~p, Token=~p, "
+        "Options=~p, Payload=~p",
+        [Method, Id, Token, Options, Payload]
+    ),
     Response.
 
 test_recv_coap_request(UdpSock) ->
@@ -148,11 +174,18 @@ test_recv_coap_request(UdpSock) ->
         {ok, {_Address, _Port, Packet}} ->
             {ok, Request, _, _} = emqx_coap_frame:parse(Packet, undefined),
             #coap_message{
-               type = con, method = Method, id = Id,
-               token = Token, payload = Payload, options = Options} = Request,
-            ?LOGT("receive coap request Method=~p, Id=~p, "
-                  "Token=~p, Options=~p, Payload=~p",
-                  [Method, Id, Token, Options, Payload]),
+                type = con,
+                method = Method,
+                id = Id,
+                token = Token,
+                payload = Payload,
+                options = Options
+            } = Request,
+            ?LOGT(
+                "receive coap request Method=~p, Id=~p, "
+                "Token=~p, Options=~p, Payload=~p",
+                [Method, Id, Token, Options, Payload]
+            ),
             Request;
         {error, Reason} ->
             ?LOGT("test_recv_coap_request failed, Reason=~p", [Reason]),
@@ -168,10 +201,13 @@ test_send_coap_response(UdpSock, Host, Port, Code, Content, Request) ->
     ok = gen_udp:send(UdpSock, IpAddr, Port, Binary).
 
 resolve_uri(Uri) ->
-    {ok, #{scheme := Scheme,
-           host := Host,
-           port := PortNo,
-           path := Path} = URIMap} = emqx_http_lib:uri_parse(Uri),
+    {ok,
+        #{
+            scheme := Scheme,
+            host := Host,
+            port := PortNo,
+            path := Path
+        } = URIMap} = emqx_http_lib:uri_parse(Uri),
     Query = maps:get(query, URIMap, undefined),
     {ok, PeerIP} = inet:getaddr(Host, inet),
     {Scheme, {PeerIP, PortNo}, split_path(Path), split_query(Query)}.
@@ -181,16 +217,18 @@ split_path([$/]) -> [];
 split_path([$/ | Path]) -> split_segments(Path, $/, []).
 
 split_query(undefined) -> #{};
-split_query(Path) ->
-    split_segments(Path, $&, []).
+split_query(Path) -> split_segments(Path, $&, []).
 
 split_segments(Path, Char, Acc) ->
     case string:rchr(Path, Char) of
         0 ->
             [make_segment(Path) | Acc];
         N when N > 0 ->
-            split_segments(string:substr(Path, 1, N-1), Char,
-                [make_segment(string:substr(Path, N+1)) | Acc])
+            split_segments(
+                string:substr(Path, 1, N - 1),
+                Char,
+                [make_segment(string:substr(Path, N + 1)) | Acc]
+            )
     end.
 
 make_segment(Seg) ->
@@ -199,17 +237,15 @@ make_segment(Seg) ->
 get_path([], Acc) ->
     %?LOGT("get_path Acc=~p", [Acc]),
     Acc;
-get_path([{uri_path, Path1}|T], Acc) ->
+get_path([{uri_path, Path1} | T], Acc) ->
     %?LOGT("Path=~p, Acc=~p", [Path1, Acc]),
     get_path(T, join_path(Path1, Acc));
-get_path([{_, _}|T], Acc) ->
+get_path([{_, _} | T], Acc) ->
     get_path(T, Acc).
 
 join_path([], Acc) -> Acc;
-join_path([<<"/">>|T], Acc) ->
-    join_path(T, Acc);
-join_path([H|T], Acc) ->
-    join_path(T, <>).
+join_path([<<"/">> | T], Acc) -> join_path(T, Acc);
+join_path([H | T], Acc) -> join_path(T, <>).
 
 sprintf(Format, Args) ->
     lists:flatten(io_lib:format(Format, Args)).
diff --git a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl b/apps/emqx_gateway/test/emqx_exproto_SUITE.erl
index 62334b6ab..5ca1ae52f 100644
--- a/apps/emqx_gateway/test/emqx_exproto_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_exproto_SUITE.erl
@@ -19,17 +19,20 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--import(emqx_exproto_echo_svr,
-        [ frame_connect/2
-        , frame_connack/1
-        , frame_publish/3
-        , frame_puback/1
-        , frame_subscribe/2
-        , frame_suback/1
-        , frame_unsubscribe/1
-        , frame_unsuback/1
-        , frame_disconnect/0
-        ]).
+-import(
+    emqx_exproto_echo_svr,
+    [
+        frame_connect/2,
+        frame_connack/1,
+        frame_publish/3,
+        frame_puback/1,
+        frame_subscribe/2,
+        frame_suback/1,
+        frame_unsubscribe/1,
+        frame_unsuback/1,
+        frame_disconnect/0
+    ]
+).
 
 -include_lib("emqx/include/emqx.hrl").
 -include_lib("emqx/include/emqx_mqtt.hrl").
@@ -42,7 +45,7 @@
 %%--------------------------------------------------------------------
 
 all() ->
-    [{group, Name} || Name  <- metrics()].
+    [{group, Name} || Name <- metrics()].
 
 groups() ->
     Cases = emqx_common_test_helpers:all(?MODULE),
@@ -67,11 +70,13 @@ end_per_group(_, Cfg) ->
 set_special_cfg(emqx_gateway) ->
     LisType = get(grpname),
     emqx_config:put(
-      [gateway, exproto],
-      #{server => #{bind => 9100},
-        handler => #{address => "http://127.0.0.1:9001"},
-        listeners => listener_confs(LisType)
-       });
+        [gateway, exproto],
+        #{
+            server => #{bind => 9100},
+            handler => #{address => "http://127.0.0.1:9001"},
+            listeners => listener_confs(LisType)
+        }
+    );
 set_special_cfg(_App) ->
     ok.
 
@@ -90,11 +95,12 @@ t_mountpoint_echo(Cfg) ->
     SockType = proplists:get_value(listener_type, Cfg),
     Sock = open(SockType),
 
-    Client = #{proto_name => <<"demo">>,
-               proto_ver => <<"v0.1">>,
-               clientid => <<"test_client_1">>,
-               mountpoint => <<"ct/">>
-              },
+    Client = #{
+        proto_name => <<"demo">>,
+        proto_ver => <<"v0.1">>,
+        clientid => <<"test_client_1">>,
+        mountpoint => <<"ct/">>
+    },
     Password = <<"123456">>,
 
     ConnBin = frame_connect(Client, Password),
@@ -124,7 +130,7 @@ t_mountpoint_echo(Cfg) ->
     receive
         {deliver, _, _} -> ok
     after 1000 ->
-          error(echo_not_running)
+        error(echo_not_running)
     end,
     close(Sock).
 
@@ -132,15 +138,19 @@ t_auth_deny(Cfg) ->
     SockType = proplists:get_value(listener_type, Cfg),
     Sock = open(SockType),
 
-    Client = #{proto_name => <<"demo">>,
-               proto_ver => <<"v0.1">>,
-               clientid => <<"test_client_1">>
-              },
+    Client = #{
+        proto_name => <<"demo">>,
+        proto_ver => <<"v0.1">>,
+        clientid => <<"test_client_1">>
+    },
     Password = <<"123456">>,
 
     ok = meck:new(emqx_gateway_ctx, [passthrough, no_history, no_link]),
-    ok = meck:expect(emqx_gateway_ctx, authenticate,
-                     fun(_, _) -> {error, ?RC_NOT_AUTHORIZED} end),
+    ok = meck:expect(
+        emqx_gateway_ctx,
+        authenticate,
+        fun(_, _) -> {error, ?RC_NOT_AUTHORIZED} end
+    ),
 
     ConnBin = frame_connect(Client, Password),
     ConnAckBin = frame_connack(1),
@@ -148,19 +158,21 @@ t_auth_deny(Cfg) ->
     send(Sock, ConnBin),
     {ok, ConnAckBin} = recv(Sock, 5000),
 
-    SockType =/= udp andalso begin
-        {error, closed} = recv(Sock, 5000)
-    end,
+    SockType =/= udp andalso
+        begin
+            {error, closed} = recv(Sock, 5000)
+        end,
     meck:unload([emqx_gateway_ctx]).
 
 t_acl_deny(Cfg) ->
     SockType = proplists:get_value(listener_type, Cfg),
     Sock = open(SockType),
 
-    Client = #{proto_name => <<"demo">>,
-               proto_ver => <<"v0.1">>,
-               clientid => <<"test_client_1">>
-              },
+    Client = #{
+        proto_name => <<"demo">>,
+        proto_ver => <<"v0.1">>,
+        clientid => <<"test_client_1">>
+    },
     Password = <<"123456">>,
 
     ok = meck:new(emqx_gateway_ctx, [passthrough, no_history, no_link]),
@@ -197,11 +209,12 @@ t_keepalive_timeout(Cfg) ->
     SockType = proplists:get_value(listener_type, Cfg),
     Sock = open(SockType),
 
-    Client = #{proto_name => <<"demo">>,
-               proto_ver => <<"v0.1">>,
-               clientid => <<"test_client_1">>,
-               keepalive => 2
-              },
+    Client = #{
+        proto_name => <<"demo">>,
+        proto_ver => <<"v0.1">>,
+        clientid => <<"test_client_1">>,
+        keepalive => 2
+    },
     Password = <<"123456">>,
 
     ConnBin = frame_connect(Client, Password),
@@ -213,18 +226,21 @@ t_keepalive_timeout(Cfg) ->
     DisconnectBin = frame_disconnect(),
     {ok, DisconnectBin} = recv(Sock, 10000),
 
-    SockType =/= udp andalso begin
-        {error, closed} = recv(Sock, 5000)
-    end, ok.
+    SockType =/= udp andalso
+        begin
+            {error, closed} = recv(Sock, 5000)
+        end,
+    ok.
 
 t_hook_connected_disconnected(Cfg) ->
     SockType = proplists:get_value(listener_type, Cfg),
     Sock = open(SockType),
 
-    Client = #{proto_name => <<"demo">>,
-               proto_ver => <<"v0.1">>,
-               clientid => <<"test_client_1">>
-              },
+    Client = #{
+        proto_name => <<"demo">>,
+        proto_ver => <<"v0.1">>,
+        clientid => <<"test_client_1">>
+    },
     Password = <<"123456">>,
 
     ConnBin = frame_connect(Client, Password),
@@ -232,7 +248,7 @@ t_hook_connected_disconnected(Cfg) ->
 
     Parent = self(),
     emqx:hook('client.connected', {?MODULE, hook_fun1, [Parent]}),
-    emqx:hook('client.disconnected',{?MODULE, hook_fun2, [Parent]}),
+    emqx:hook('client.disconnected', {?MODULE, hook_fun2, [Parent]}),
 
     send(Sock, ConnBin),
     {ok, ConnAckBin} = recv(Sock, 5000),
@@ -252,9 +268,10 @@ t_hook_connected_disconnected(Cfg) ->
         error(hook_is_not_running)
     end,
 
-    SockType =/= udp andalso begin
-        {error, closed} = recv(Sock, 5000)
-    end,
+    SockType =/= udp andalso
+        begin
+            {error, closed} = recv(Sock, 5000)
+        end,
     emqx:unhook('client.connected', {?MODULE, hook_fun1}),
     emqx:unhook('client.disconnected', {?MODULE, hook_fun2}).
 
@@ -262,10 +279,11 @@ t_hook_session_subscribed_unsubscribed(Cfg) ->
     SockType = proplists:get_value(listener_type, Cfg),
     Sock = open(SockType),
 
-    Client = #{proto_name => <<"demo">>,
-               proto_ver => <<"v0.1">>,
-               clientid => <<"test_client_1">>
-              },
+    Client = #{
+        proto_name => <<"demo">>,
+        proto_ver => <<"v0.1">>,
+        clientid => <<"test_client_1">>
+    },
     Password = <<"123456">>,
 
     ConnBin = frame_connect(Client, Password),
@@ -310,10 +328,11 @@ t_hook_message_delivered(Cfg) ->
     SockType = proplists:get_value(listener_type, Cfg),
     Sock = open(SockType),
 
-    Client = #{proto_name => <<"demo">>,
-               proto_ver => <<"v0.1">>,
-               clientid => <<"test_client_1">>
-              },
+    Client = #{
+        proto_name => <<"demo">>,
+        proto_ver => <<"v0.1">>,
+        clientid => <<"test_client_1">>
+    },
     Password = <<"123456">>,
 
     ConnBin = frame_connect(Client, Password),
@@ -340,11 +359,19 @@ t_hook_message_delivered(Cfg) ->
 %%--------------------------------------------------------------------
 %% Utils
 
-hook_fun1(_, _, Parent) -> Parent ! connected, ok.
-hook_fun2(_, _, _, Parent) -> Parent ! disconnected, ok.
+hook_fun1(_, _, Parent) ->
+    Parent ! connected,
+    ok.
+hook_fun2(_, _, _, Parent) ->
+    Parent ! disconnected,
+    ok.
 
-hook_fun3(_, _, _, Parent) -> Parent ! subscribed, ok.
-hook_fun4(_, _, _, Parent) -> Parent ! unsubscribed, ok.
+hook_fun3(_, _, _, Parent) ->
+    Parent ! subscribed,
+    ok.
+hook_fun4(_, _, _, Parent) ->
+    Parent ! unsubscribed,
+    ok.
 
 hook_fun5(_, Msg) -> {ok, Msg#message{payload = <<"2">>}}.
 
@@ -403,41 +430,51 @@ close({dtls, Sock}) ->
 socketopts(tcp) ->
     #{tcp => tcp_opts()};
 socketopts(ssl) ->
-    #{tcp => tcp_opts(),
-      ssl => ssl_opts()};
+    #{
+        tcp => tcp_opts(),
+        ssl => ssl_opts()
+    };
 socketopts(udp) ->
     #{udp => udp_opts()};
 socketopts(dtls) ->
-    #{udp => udp_opts(),
-      dtls => dtls_opts()}.
+    #{
+        udp => udp_opts(),
+        dtls => dtls_opts()
+    }.
 
 tcp_opts() ->
     maps:merge(
-      udp_opts(),
-      #{send_timeout => 15000,
-        send_timeout_close => true,
-        backlog => 100,
-        nodelay => true}
+        udp_opts(),
+        #{
+            send_timeout => 15000,
+            send_timeout_close => true,
+            backlog => 100,
+            nodelay => true
+        }
     ).
 
 udp_opts() ->
-    #{recbuf => 1024,
-      sndbuf => 1024,
-      buffer => 1024,
-      reuseaddr => true}.
+    #{
+        recbuf => 1024,
+        sndbuf => 1024,
+        buffer => 1024,
+        reuseaddr => true
+    }.
 
 ssl_opts() ->
     Certs = certs("key.pem", "cert.pem", "cacert.pem"),
     maps:merge(
-      Certs,
-      #{versions => emqx_tls_lib:default_versions(),
-        ciphers => emqx_tls_lib:default_ciphers(),
-        verify => verify_peer,
-        fail_if_no_peer_cert => true,
-        secure_renegotiate => false,
-        reuse_sessions => true,
-        honor_cipher_order => true}
-     ).
+        Certs,
+        #{
+            versions => emqx_tls_lib:default_versions(),
+            ciphers => emqx_tls_lib:default_ciphers(),
+            verify => verify_peer,
+            fail_if_no_peer_cert => true,
+            secure_renegotiate => false,
+            reuse_sessions => true,
+            honor_cipher_order => true
+        }
+    ).
 
 dtls_opts() ->
     maps:merge(ssl_opts(), #{versions => ['dtlsv1.2', 'dtlsv1']}).
@@ -450,7 +487,8 @@ client_ssl_opts() ->
 
 certs(Key, Cert, CACert) ->
     CertsPath = emqx_common_test_helpers:deps_path(emqx, "etc/certs"),
-    #{keyfile    => filename:join([ CertsPath, Key   ]),
-      certfile   => filename:join([ CertsPath, Cert  ]),
-      cacertfile => filename:join([ CertsPath, CACert])}.
-
+    #{
+        keyfile => filename:join([CertsPath, Key]),
+        certfile => filename:join([CertsPath, Cert]),
+        cacertfile => filename:join([CertsPath, CACert])
+    }.
diff --git a/apps/emqx_gateway/test/emqx_exproto_echo_svr.erl b/apps/emqx_gateway/test/emqx_exproto_echo_svr.erl
index 8c64a8a4c..ecc8999ce 100644
--- a/apps/emqx_gateway/test/emqx_exproto_echo_svr.erl
+++ b/apps/emqx_gateway/test/emqx_exproto_echo_svr.erl
@@ -18,76 +18,87 @@
 
 -behaviour(emqx_exproto_v_1_connection_handler_bhvr).
 
--export([ start/0
-        , stop/1
-        ]).
+-export([
+    start/0,
+    stop/1
+]).
 
--export([ frame_connect/2
-        , frame_connack/1
-        , frame_publish/3
-        , frame_puback/1
-        , frame_subscribe/2
-        , frame_suback/1
-        , frame_unsubscribe/1
-        , frame_unsuback/1
-        , frame_disconnect/0
-        ]).
+-export([
+    frame_connect/2,
+    frame_connack/1,
+    frame_publish/3,
+    frame_puback/1,
+    frame_subscribe/2,
+    frame_suback/1,
+    frame_unsubscribe/1,
+    frame_unsuback/1,
+    frame_disconnect/0
+]).
 
--export([ on_socket_created/2
-        , on_received_bytes/2
-        , on_socket_closed/2
-        , on_timer_timeout/2
-        , on_received_messages/2
-        ]).
+-export([
+    on_socket_created/2,
+    on_received_bytes/2,
+    on_socket_closed/2,
+    on_timer_timeout/2,
+    on_received_messages/2
+]).
 
 -define(LOG(Fmt, Args), ct:pal(Fmt, Args)).
 
--define(HTTP, #{grpc_opts => #{service_protos => [emqx_exproto_pb],
-                               services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}},
-                listen_opts => #{port => 9001,
-                                 socket_options => []},
-                pool_opts => #{size => 8},
-                transport_opts => #{ssl => false}}).
+-define(HTTP, #{
+    grpc_opts => #{
+        service_protos => [emqx_exproto_pb],
+        services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}
+    },
+    listen_opts => #{
+        port => 9001,
+        socket_options => []
+    },
+    pool_opts => #{size => 8},
+    transport_opts => #{ssl => false}
+}).
 
 -define(CLIENT, emqx_exproto_v_1_connection_adapter_client).
 
--define(send(Req),         ?CLIENT:send(Req, #{channel => ct_test_channel})).
--define(close(Req),        ?CLIENT:close(Req, #{channel => ct_test_channel})).
+-define(send(Req), ?CLIENT:send(Req, #{channel => ct_test_channel})).
+-define(close(Req), ?CLIENT:close(Req, #{channel => ct_test_channel})).
 -define(authenticate(Req), ?CLIENT:authenticate(Req, #{channel => ct_test_channel})).
--define(start_timer(Req),  ?CLIENT:start_timer(Req, #{channel => ct_test_channel})).
--define(publish(Req),      ?CLIENT:publish(Req, #{channel => ct_test_channel})).
--define(subscribe(Req),    ?CLIENT:subscribe(Req, #{channel => ct_test_channel})).
--define(unsubscribe(Req),  ?CLIENT:unsubscribe(Req, #{channel => ct_test_channel})).
+-define(start_timer(Req), ?CLIENT:start_timer(Req, #{channel => ct_test_channel})).
+-define(publish(Req), ?CLIENT:publish(Req, #{channel => ct_test_channel})).
+-define(subscribe(Req), ?CLIENT:subscribe(Req, #{channel => ct_test_channel})).
+-define(unsubscribe(Req), ?CLIENT:unsubscribe(Req, #{channel => ct_test_channel})).
 
--define(TYPE_CONNECT,     1).
--define(TYPE_CONNACK,     2).
--define(TYPE_PUBLISH,     3).
--define(TYPE_PUBACK,      4).
--define(TYPE_SUBSCRIBE,   5).
--define(TYPE_SUBACK,      6).
+-define(TYPE_CONNECT, 1).
+-define(TYPE_CONNACK, 2).
+-define(TYPE_PUBLISH, 3).
+-define(TYPE_PUBACK, 4).
+-define(TYPE_SUBSCRIBE, 5).
+-define(TYPE_SUBACK, 6).
 -define(TYPE_UNSUBSCRIBE, 7).
--define(TYPE_UNSUBACK,    8).
--define(TYPE_DISCONNECT,  9).
+-define(TYPE_UNSUBACK, 8).
+-define(TYPE_DISCONNECT, 9).
 
 -define(loop_recv_and_reply_empty_success(Stream),
-        ?loop_recv_and_reply_empty_success(Stream, fun(_) -> ok end)).
+    ?loop_recv_and_reply_empty_success(Stream, fun(_) -> ok end)
+).
 
--define(loop_recv_and_reply_empty_success(Stream, Fun),
-        begin
-            LoopRecv = fun _Lp(_St) ->
-                case grpc_stream:recv(_St) of
-                    {more, _Reqs, _NSt} ->
-                        ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]),
-                        Fun(_Reqs), _Lp(_NSt);
-                    {eos, _Reqs, _NSt} ->
-                        ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]),
-                        Fun(_Reqs), _NSt
-                end
-            end,
-            NStream  = LoopRecv(Stream),
-            grpc_stream:reply(NStream, #{}),
-            {ok, NStream}
-        end).
+-define(loop_recv_and_reply_empty_success(Stream, Fun), begin
+    LoopRecv = fun _Lp(_St) ->
+        case grpc_stream:recv(_St) of
+            {more, _Reqs, _NSt} ->
+                ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]),
+                Fun(_Reqs),
+                _Lp(_NSt);
+            {eos, _Reqs, _NSt} ->
+                ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]),
+                Fun(_Reqs),
+                _NSt
+        end
+    end,
+    NStream = LoopRecv(Stream),
+    grpc_stream:reply(NStream, #{}),
+    {ok, NStream}
+end).
 
 %%--------------------------------------------------------------------
 %% APIs
@@ -101,9 +112,10 @@ start_channel() ->
     grpc_client_sup:create_channel_pool(ct_test_channel, "http://127.0.0.1:9100", #{}).
 
 start_server() ->
-    Services = #{protos => [emqx_exproto_pb],
-                 services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}
-                },
+    Services = #{
+        protos => [emqx_exproto_pb],
+        services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}
+    },
     Options = [],
     grpc:start_server(?MODULE, 9001, Services, Options).
 
@@ -115,53 +127,68 @@ stop([_ChannPid, _SvrPid]) ->
 %% Protocol Adapter callbacks
 %%--------------------------------------------------------------------
 
--spec on_socket_created(grpc_stream:stream(), grpc:metadata())
-    -> {ok, grpc_stream:stream()}.
+-spec on_socket_created(grpc_stream:stream(), grpc:metadata()) ->
+    {ok, grpc_stream:stream()}.
 on_socket_created(Stream, _Md) ->
     ?loop_recv_and_reply_empty_success(Stream).
 
--spec on_socket_closed(grpc_stream:stream(), grpc:metadata())
-    -> {ok, grpc_stream:stream()}.
+-spec on_socket_closed(grpc_stream:stream(), grpc:metadata()) ->
+    {ok, grpc_stream:stream()}.
 on_socket_closed(Stream, _Md) ->
     ?loop_recv_and_reply_empty_success(Stream).
 
--spec on_received_bytes(grpc_stream:stream(), grpc:metadata())
-    -> {ok, grpc_stream:stream()}.
+-spec on_received_bytes(grpc_stream:stream(), grpc:metadata()) ->
+    {ok, grpc_stream:stream()}.
 on_received_bytes(Stream, _Md) ->
-    ?loop_recv_and_reply_empty_success(Stream,
-      fun(Reqs) ->
-        lists:foreach(
-          fun(#{conn := Conn, bytes := Bytes}) ->
-            #{<<"type">> := Type} = Params = emqx_json:decode(Bytes, [return_maps]),
-            _ = handle_in(Conn, Type, Params)
-          end, Reqs)
-      end).
+    ?loop_recv_and_reply_empty_success(
+        Stream,
+        fun(Reqs) ->
+            lists:foreach(
+                fun(#{conn := Conn, bytes := Bytes}) ->
+                    #{<<"type">> := Type} = Params = emqx_json:decode(Bytes, [return_maps]),
+                    _ = handle_in(Conn, Type, Params)
+                end,
+                Reqs
+            )
+        end
+    ).
 
--spec on_timer_timeout(grpc_stream:stream(), grpc:metadata())
-    -> {ok, grpc_stream:stream()}.
+-spec on_timer_timeout(grpc_stream:stream(), grpc:metadata()) ->
+    {ok, grpc_stream:stream()}.
 on_timer_timeout(Stream, _Md) ->
-    ?loop_recv_and_reply_empty_success(Stream,
-      fun(Reqs) ->
-        lists:foreach(
-          fun(#{conn := Conn, type := 'KEEPALIVE'}) ->
-            ?LOG("Close this connection ~p due to keepalive timeout", [Conn]),
-            handle_out(Conn, ?TYPE_DISCONNECT),
-            ?close(#{conn => Conn})
-          end, Reqs)
-      end).
+    ?loop_recv_and_reply_empty_success(
+        Stream,
+        fun(Reqs) ->
+            lists:foreach(
+                fun(#{conn := Conn, type := 'KEEPALIVE'}) ->
+                    ?LOG("Close this connection ~p due to keepalive timeout", [Conn]),
+                    handle_out(Conn, ?TYPE_DISCONNECT),
+                    ?close(#{conn => Conn})
+                end,
+                Reqs
+            )
+        end
+    ).
 
--spec on_received_messages(grpc_stream:stream(), grpc:metadata())
-    -> {ok, grpc_stream:stream()}.
+-spec on_received_messages(grpc_stream:stream(), grpc:metadata()) ->
+    {ok, grpc_stream:stream()}.
 on_received_messages(Stream, _Md) ->
-    ?loop_recv_and_reply_empty_success(Stream,
-      fun(Reqs) ->
-        lists:foreach(
-          fun(#{conn := Conn, messages := Messages}) ->
-            lists:foreach(fun(Message) ->
-                handle_out(Conn, ?TYPE_PUBLISH, Message)
-            end, Messages)
-          end, Reqs)
-      end).
+    ?loop_recv_and_reply_empty_success(
+        Stream,
+        fun(Reqs) ->
+            lists:foreach(
+                fun(#{conn := Conn, messages := Messages}) ->
+                    lists:foreach(
+                        fun(Message) ->
+                            handle_out(Conn, ?TYPE_PUBLISH, Message)
+                        end,
+                        Messages
+                    )
+                end,
+                Reqs
+            )
+        end
+    ).
 
 %%--------------------------------------------------------------------
 %% The Protocol Example:
@@ -189,12 +216,16 @@ on_received_messages(Stream, _Md) ->
 
 handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">> := Password}) ->
     NClientInfo = maps:from_list(
-                    [{binary_to_atom(K, utf8), V}
-                     || {K, V} <- maps:to_list(ClientInfo)]),
+        [
+            {binary_to_atom(K, utf8), V}
+         || {K, V} <- maps:to_list(ClientInfo)
+        ]
+    ),
     case ?authenticate(#{conn => Conn, clientinfo => NClientInfo, password => Password}) of
         {ok, #{code := 'SUCCESS'}, _} ->
             case maps:get(keepalive, NClientInfo, 0) of
-                0 -> ok;
+                0 ->
+                    ok;
                 Intv ->
                     ?LOG("Try call start_timer with ~ps", [Intv]),
                     ?start_timer(#{conn => Conn, type => 'KEEPALIVE', interval => Intv})
@@ -204,9 +235,11 @@ handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">>
             handle_out(Conn, ?TYPE_CONNACK, 1),
             ?close(#{conn => Conn})
     end;
-handle_in(Conn, ?TYPE_PUBLISH, #{<<"topic">> := Topic,
-                                 <<"qos">> := Qos,
-                                 <<"payload">> := Payload}) ->
+handle_in(Conn, ?TYPE_PUBLISH, #{
+    <<"topic">> := Topic,
+    <<"qos">> := Qos,
+    <<"payload">> := Payload
+}) ->
     case ?publish(#{conn => Conn, topic => Topic, qos => Qos, payload => Payload}) of
         {ok, #{code := 'SUCCESS'}, _} ->
             handle_out(Conn, ?TYPE_PUBACK, 0);
@@ -227,7 +260,6 @@ handle_in(Conn, ?TYPE_UNSUBSCRIBE, #{<<"topic">> := Topic}) ->
         _ ->
             handle_out(Conn, ?TYPE_UNSUBACK, 1)
     end;
-
 handle_in(Conn, ?TYPE_DISCONNECT, _) ->
     ?close(#{conn => Conn}).
 
@@ -249,17 +281,21 @@ handle_out(Conn, ?TYPE_DISCONNECT) ->
 %% Frame
 
 frame_connect(ClientInfo, Password) ->
-    emqx_json:encode(#{type => ?TYPE_CONNECT,
-                       clientinfo => ClientInfo,
-                       password => Password}).
+    emqx_json:encode(#{
+        type => ?TYPE_CONNECT,
+        clientinfo => ClientInfo,
+        password => Password
+    }).
 frame_connack(Code) ->
     emqx_json:encode(#{type => ?TYPE_CONNACK, code => Code}).
 
 frame_publish(Topic, Qos, Payload) ->
-    emqx_json:encode(#{type => ?TYPE_PUBLISH,
-                       topic => Topic,
-                       qos => Qos,
-                       payload => Payload}).
+    emqx_json:encode(#{
+        type => ?TYPE_PUBLISH,
+        topic => Topic,
+        qos => Qos,
+        payload => Payload
+    }).
 
 frame_puback(Code) ->
     emqx_json:encode(#{type => ?TYPE_PUBACK, code => Code}).
diff --git a/apps/emqx_gateway/test/emqx_gateway_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_SUITE.erl
index dbe17da42..fa8ea66d0 100644
--- a/apps/emqx_gateway/test/emqx_gateway_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_SUITE.erl
@@ -44,25 +44,30 @@ end_per_suite(_Conf) ->
 %%--------------------------------------------------------------------
 
 t_registered_gateway(_) ->
-    [{coap, #{cbkmod := emqx_coap_impl}},
-     {exproto, #{cbkmod := emqx_exproto_impl}},
-     {lwm2m, #{cbkmod := emqx_lwm2m_impl}},
-     {mqttsn, #{cbkmod := emqx_sn_impl}},
-     {stomp, #{cbkmod := emqx_stomp_impl}}] =  emqx_gateway:registered_gateway().
+    [
+        {coap, #{cbkmod := emqx_coap_impl}},
+        {exproto, #{cbkmod := emqx_exproto_impl}},
+        {lwm2m, #{cbkmod := emqx_lwm2m_impl}},
+        {mqttsn, #{cbkmod := emqx_sn_impl}},
+        {stomp, #{cbkmod := emqx_stomp_impl}}
+    ] = emqx_gateway:registered_gateway().
 
 t_load_unload_list_lookup(_) ->
     {ok, _} = emqx_gateway:load(?GWNAME, #{idle_timeout => 1000}),
     ?assertEqual(
-       {error, alredy_existed},
-       emqx_gateway:load(?GWNAME, #{})),
+        {error, alredy_existed},
+        emqx_gateway:load(?GWNAME, #{})
+    ),
     ?assertEqual(
-       {error, {unknown_gateway_name, bad_gw_name}},
-       emqx_gateway:load(bad_gw_name, #{})),
+        {error, {unknown_gateway_name, bad_gw_name}},
+        emqx_gateway:load(bad_gw_name, #{})
+    ),
 
     ?assertEqual(1, length(emqx_gateway:list())),
     ?assertEqual(
-       emqx_gateway:lookup(?GWNAME),
-       lists:nth(1, emqx_gateway:list())),
+        emqx_gateway:lookup(?GWNAME),
+        lists:nth(1, emqx_gateway:list())
+    ),
 
     ?assertEqual(ok, emqx_gateway:unload(?GWNAME)),
     ?assertEqual({error, not_found}, emqx_gateway:unload(?GWNAME)).
@@ -78,23 +83,34 @@ t_start_stop_update(_) ->
     #{status := stopped} = emqx_gateway:lookup(?GWNAME),
 
     ok = emqx_gateway:update(
-           ?GWNAME, #{enable => false, idle_timeout => 2000}),
-    #{status := stopped,
-      config := #{idle_timeout := 2000}} = emqx_gateway:lookup(?GWNAME),
+        ?GWNAME, #{enable => false, idle_timeout => 2000}
+    ),
+    #{
+        status := stopped,
+        config := #{idle_timeout := 2000}
+    } = emqx_gateway:lookup(?GWNAME),
 
     ok = emqx_gateway:update(
-           ?GWNAME, #{enable => true, idle_timeout => 3000}),
-    #{status := running,
-      config := #{idle_timeout := 3000}} = emqx_gateway:lookup(?GWNAME),
+        ?GWNAME, #{enable => true, idle_timeout => 3000}
+    ),
+    #{
+        status := running,
+        config := #{idle_timeout := 3000}
+    } = emqx_gateway:lookup(?GWNAME),
 
     ok = emqx_gateway:update(
-           ?GWNAME, #{enable => false, idle_timeout => 4000}),
-    #{status := stopped,
-      config := #{idle_timeout := 4000}} = emqx_gateway:lookup(?GWNAME),
+        ?GWNAME, #{enable => false, idle_timeout => 4000}
+    ),
+    #{
+        status := stopped,
+        config := #{idle_timeout := 4000}
+    } = emqx_gateway:lookup(?GWNAME),
 
     ok = emqx_gateway:start(?GWNAME),
-    #{status := running,
-      config := #{idle_timeout := 4000}} = emqx_gateway:lookup(?GWNAME),
+    #{
+        status := running,
+        config := #{idle_timeout := 4000}
+    } = emqx_gateway:lookup(?GWNAME),
 
     {error, already_started} = emqx_gateway:start(?GWNAME),
     ok.
diff --git a/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl
index dab5962df..f2080dc8c 100644
--- a/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_api_SUITE.erl
@@ -19,12 +19,15 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--import(emqx_gateway_test_utils,
-        [ assert_confs/2
-        , assert_feilds_apperence/2
-        , request/2
-        , request/3
-        ]).
+-import(
+    emqx_gateway_test_utils,
+    [
+        assert_confs/2,
+        assert_feilds_apperence/2,
+        request/2,
+        request/3
+    ]
+).
 
 -include_lib("eunit/include/eunit.hrl").
 
@@ -53,14 +56,16 @@ end_per_suite(Conf) ->
 %%--------------------------------------------------------------------
 
 t_gateway(_) ->
-    {200, Gateways}= request(get, "/gateway"),
+    {200, Gateways} = request(get, "/gateway"),
     lists:foreach(fun assert_gw_unloaded/1, Gateways),
     {400, BadReq} = request(get, "/gateway/uname_gateway"),
     assert_bad_request(BadReq),
     {201, _} = request(post, "/gateway", #{name => <<"stomp">>}),
     {200, StompGw1} = request(get, "/gateway/stomp"),
-    assert_feilds_apperence([name, status, enable, created_at, started_at],
-                            StompGw1),
+    assert_feilds_apperence(
+        [name, status, enable, created_at, started_at],
+        StompGw1
+    ),
     {204, _} = request(delete, "/gateway/stomp"),
     {200, StompGw2} = request(get, "/gateway/stomp"),
     assert_gw_unloaded(StompGw2),
@@ -70,15 +75,17 @@ t_gateway_stomp(_) ->
     {200, Gw} = request(get, "/gateway/stomp"),
     assert_gw_unloaded(Gw),
     %% post
-    GwConf = #{name => <<"stomp">>,
-               frame => #{max_headers => 5,
-                          max_headers_length => 100,
-                          max_body_length => 100
-                         },
-               listeners => [
-                  #{name => <<"def">>, type => <<"tcp">>, bind => <<"61613">>}
-                ]
-              },
+    GwConf = #{
+        name => <<"stomp">>,
+        frame => #{
+            max_headers => 5,
+            max_headers_length => 100,
+            max_body_length => 100
+        },
+        listeners => [
+            #{name => <<"def">>, type => <<"tcp">>, bind => <<"61613">>}
+        ]
+    },
     {201, _} = request(post, "/gateway", GwConf),
     {200, ConfResp} = request(get, "/gateway/stomp"),
     assert_confs(GwConf, ConfResp),
@@ -93,15 +100,16 @@ t_gateway_mqttsn(_) ->
     {200, Gw} = request(get, "/gateway/mqttsn"),
     assert_gw_unloaded(Gw),
     %% post
-    GwConf = #{name => <<"mqttsn">>,
-               gateway_id => 1,
-               broadcast => true,
-               predefined => [#{id => 1, topic => <<"t/a">>}],
-               enable_qos3 => true,
-               listeners => [
-                  #{name => <<"def">>, type => <<"udp">>, bind => <<"1884">>}
-                ]
-              },
+    GwConf = #{
+        name => <<"mqttsn">>,
+        gateway_id => 1,
+        broadcast => true,
+        predefined => [#{id => 1, topic => <<"t/a">>}],
+        enable_qos3 => true,
+        listeners => [
+            #{name => <<"def">>, type => <<"udp">>, bind => <<"1884">>}
+        ]
+    },
     {201, _} = request(post, "/gateway", GwConf),
     {200, ConfResp} = request(get, "/gateway/mqttsn"),
     assert_confs(GwConf, ConfResp),
@@ -116,13 +124,14 @@ t_gateway_coap(_) ->
     {200, Gw} = request(get, "/gateway/coap"),
     assert_gw_unloaded(Gw),
     %% post
-    GwConf = #{name => <<"coap">>,
-               heartbeat => <<"60s">>,
-               connection_required => true,
-               listeners => [
-                  #{name => <<"def">>, type => <<"udp">>, bind => <<"5683">>}
-                ]
-              },
+    GwConf = #{
+        name => <<"coap">>,
+        heartbeat => <<"60s">>,
+        connection_required => true,
+        listeners => [
+            #{name => <<"def">>, type => <<"udp">>, bind => <<"5683">>}
+        ]
+    },
     {201, _} = request(post, "/gateway", GwConf),
     {200, ConfResp} = request(get, "/gateway/coap"),
     assert_confs(GwConf, ConfResp),
@@ -137,23 +146,24 @@ t_gateway_lwm2m(_) ->
     {200, Gw} = request(get, "/gateway/lwm2m"),
     assert_gw_unloaded(Gw),
     %% post
-    GwConf = #{name => <<"lwm2m">>,
-               xml_dir => <<"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml">>,
-               lifetime_min => <<"1s">>,
-               lifetime_max => <<"1000s">>,
-               qmode_time_window => <<"30s">>,
-               auto_observe => true,
-               translators => #{
-                 command =>  #{ topic => <<"dn/#">>},
-                 response => #{ topic => <<"up/resp">>},
-                 notify =>   #{ topic => <<"up/resp">>},
-                 register => #{ topic => <<"up/resp">>},
-                 update =>   #{ topic => <<"up/resp">>}
-               },
-               listeners => [
-                  #{name => <<"def">>, type => <<"udp">>, bind => <<"5783">>}
-                ]
-              },
+    GwConf = #{
+        name => <<"lwm2m">>,
+        xml_dir => <<"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml">>,
+        lifetime_min => <<"1s">>,
+        lifetime_max => <<"1000s">>,
+        qmode_time_window => <<"30s">>,
+        auto_observe => true,
+        translators => #{
+            command => #{topic => <<"dn/#">>},
+            response => #{topic => <<"up/resp">>},
+            notify => #{topic => <<"up/resp">>},
+            register => #{topic => <<"up/resp">>},
+            update => #{topic => <<"up/resp">>}
+        },
+        listeners => [
+            #{name => <<"def">>, type => <<"udp">>, bind => <<"5783">>}
+        ]
+    },
     {201, _} = request(post, "/gateway", GwConf),
     {200, ConfResp} = request(get, "/gateway/lwm2m"),
     assert_confs(GwConf, ConfResp),
@@ -168,13 +178,14 @@ t_gateway_exproto(_) ->
     {200, Gw} = request(get, "/gateway/exproto"),
     assert_gw_unloaded(Gw),
     %% post
-    GwConf = #{name => <<"exproto">>,
-               server => #{bind => <<"9100">>},
-               handler => #{address => <<"http://127.0.0.1:9001">>},
-               listeners => [
-                  #{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>}
-                ]
-              },
+    GwConf = #{
+        name => <<"exproto">>,
+        server => #{bind => <<"9100">>},
+        handler => #{address => <<"http://127.0.0.1:9001">>},
+        listeners => [
+            #{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>}
+        ]
+    },
     {201, _} = request(post, "/gateway", GwConf),
     {200, ConfResp} = request(get, "/gateway/exproto"),
     assert_confs(GwConf, ConfResp),
@@ -190,10 +201,11 @@ t_authn(_) ->
     {201, _} = request(post, "/gateway", GwConf),
     {204, _} = request(get, "/gateway/stomp/authentication"),
 
-    AuthConf = #{mechanism => <<"password_based">>,
-                 backend => <<"built_in_database">>,
-                 user_id_type => <<"clientid">>
-                },
+    AuthConf = #{
+        mechanism => <<"password_based">>,
+        backend => <<"built_in_database">>,
+        user_id_type => <<"clientid">>
+    },
     {201, _} = request(post, "/gateway/stomp/authentication", AuthConf),
     {200, ConfResp} = request(get, "/gateway/stomp/authentication"),
     assert_confs(AuthConf, ConfResp),
@@ -213,40 +225,52 @@ t_authn_data_mgmt(_) ->
     {201, _} = request(post, "/gateway", GwConf),
     {204, _} = request(get, "/gateway/stomp/authentication"),
 
-    AuthConf = #{mechanism => <<"password_based">>,
-                 backend => <<"built_in_database">>,
-                 user_id_type => <<"clientid">>
-                },
+    AuthConf = #{
+        mechanism => <<"password_based">>,
+        backend => <<"built_in_database">>,
+        user_id_type => <<"clientid">>
+    },
     {201, _} = request(post, "/gateway/stomp/authentication", AuthConf),
     {200, ConfResp} = request(get, "/gateway/stomp/authentication"),
     assert_confs(AuthConf, ConfResp),
 
-    User1 = #{ user_id => <<"test">>
-             , password => <<"123456">>
-             , is_superuser => false
-             },
+    User1 = #{
+        user_id => <<"test">>,
+        password => <<"123456">>,
+        is_superuser => false
+    },
     {201, _} = request(post, "/gateway/stomp/authentication/users", User1),
     {200, #{data := [UserRespd1]}} = request(get, "/gateway/stomp/authentication/users"),
     assert_confs(UserRespd1, User1),
 
-    {200, UserRespd2} = request(get,
-                                "/gateway/stomp/authentication/users/test"),
+    {200, UserRespd2} = request(
+        get,
+        "/gateway/stomp/authentication/users/test"
+    ),
     assert_confs(UserRespd2, User1),
 
-    {200, UserRespd3} = request(put,
-                                "/gateway/stomp/authentication/users/test",
-                                #{password => <<"654321">>,
-                                  is_superuser => true}),
+    {200, UserRespd3} = request(
+        put,
+        "/gateway/stomp/authentication/users/test",
+        #{
+            password => <<"654321">>,
+            is_superuser => true
+        }
+    ),
     assert_confs(UserRespd3, User1#{is_superuser => true}),
 
-    {200, UserRespd4} = request(get,
-                                "/gateway/stomp/authentication/users/test"),
+    {200, UserRespd4} = request(
+        get,
+        "/gateway/stomp/authentication/users/test"
+    ),
     assert_confs(UserRespd4, User1#{is_superuser => true}),
 
     {204, _} = request(delete, "/gateway/stomp/authentication/users/test"),
 
-    {200, #{data := []}} = request(get,
-                                   "/gateway/stomp/authentication/users"),
+    {200, #{data := []}} = request(
+        get,
+        "/gateway/stomp/authentication/users"
+    ),
 
     {204, _} = request(delete, "/gateway/stomp/authentication"),
     {204, _} = request(get, "/gateway/stomp/authentication"),
@@ -256,10 +280,11 @@ t_listeners(_) ->
     GwConf = #{name => <<"stomp">>},
     {201, _} = request(post, "/gateway", GwConf),
     {404, _} = request(get, "/gateway/stomp/listeners"),
-    LisConf = #{name => <<"def">>,
-                type => <<"tcp">>,
-                bind => <<"61613">>
-               },
+    LisConf = #{
+        name => <<"def">>,
+        type => <<"tcp">>,
+        bind => <<"61613">>
+    },
     {201, _} = request(post, "/gateway/stomp/listeners", LisConf),
     {200, ConfResp} = request(get, "/gateway/stomp/listeners"),
     assert_confs([LisConf], ConfResp),
@@ -268,10 +293,10 @@ t_listeners(_) ->
 
     LisConf2 = maps:merge(LisConf, #{bind => <<"61614">>}),
     {200, _} = request(
-                 put,
-                 "/gateway/stomp/listeners/stomp:tcp:def",
-                 LisConf2
-                ),
+        put,
+        "/gateway/stomp/listeners/stomp:tcp:def",
+        LisConf2
+    ),
 
     {200, ConfResp2} = request(get, "/gateway/stomp/listeners/stomp:tcp:def"),
     assert_confs(LisConf2, ConfResp2),
@@ -281,20 +306,25 @@ t_listeners(_) ->
     {204, _} = request(delete, "/gateway/stomp").
 
 t_listeners_authn(_) ->
-    GwConf = #{name => <<"stomp">>,
-               listeners => [
-                 #{name => <<"def">>,
-                   type => <<"tcp">>,
-                   bind => <<"61613">>
-                  }]},
+    GwConf = #{
+        name => <<"stomp">>,
+        listeners => [
+            #{
+                name => <<"def">>,
+                type => <<"tcp">>,
+                bind => <<"61613">>
+            }
+        ]
+    },
     {201, _} = request(post, "/gateway", GwConf),
     {200, ConfResp} = request(get, "/gateway/stomp"),
     assert_confs(GwConf, ConfResp),
 
-    AuthConf = #{mechanism => <<"password_based">>,
-                 backend => <<"built_in_database">>,
-                 user_id_type => <<"clientid">>
-                },
+    AuthConf = #{
+        mechanism => <<"password_based">>,
+        backend => <<"built_in_database">>,
+        user_id_type => <<"clientid">>
+    },
     Path = "/gateway/stomp/listeners/stomp:tcp:def/authentication",
     {201, _} = request(post, Path, AuthConf),
     {200, ConfResp2} = request(get, Path),
@@ -312,62 +342,75 @@ t_listeners_authn(_) ->
     {204, _} = request(delete, "/gateway/stomp").
 
 t_listeners_authn_data_mgmt(_) ->
-    GwConf = #{name => <<"stomp">>,
-               listeners => [
-                 #{name => <<"def">>,
-                   type => <<"tcp">>,
-                   bind => <<"61613">>
-                  }]},
+    GwConf = #{
+        name => <<"stomp">>,
+        listeners => [
+            #{
+                name => <<"def">>,
+                type => <<"tcp">>,
+                bind => <<"61613">>
+            }
+        ]
+    },
     {201, _} = request(post, "/gateway", GwConf),
     {200, ConfResp} = request(get, "/gateway/stomp"),
     assert_confs(GwConf, ConfResp),
 
-    AuthConf = #{mechanism => <<"password_based">>,
-                 backend => <<"built_in_database">>,
-                 user_id_type => <<"clientid">>
-                },
+    AuthConf = #{
+        mechanism => <<"password_based">>,
+        backend => <<"built_in_database">>,
+        user_id_type => <<"clientid">>
+    },
     Path = "/gateway/stomp/listeners/stomp:tcp:def/authentication",
     {201, _} = request(post, Path, AuthConf),
     {200, ConfResp2} = request(get, Path),
     assert_confs(AuthConf, ConfResp2),
 
-    User1 = #{ user_id => <<"test">>
-             , password => <<"123456">>
-             , is_superuser => false
-             },
-    {201, _} = request(post,
-                       "/gateway/stomp/listeners/stomp:tcp:def/authentication/users",
-                       User1),
+    User1 = #{
+        user_id => <<"test">>,
+        password => <<"123456">>,
+        is_superuser => false
+    },
+    {201, _} = request(
+        post,
+        "/gateway/stomp/listeners/stomp:tcp:def/authentication/users",
+        User1
+    ),
 
-    {200,
-     #{data := [UserRespd1]} } = request(
-                                   get,
-                                   Path ++ "/users"),
+    {200, #{data := [UserRespd1]}} = request(
+        get,
+        Path ++ "/users"
+    ),
     assert_confs(UserRespd1, User1),
 
     {200, UserRespd2} = request(
-                          get,
-                          Path ++ "/users/test"),
+        get,
+        Path ++ "/users/test"
+    ),
     assert_confs(UserRespd2, User1),
 
     {200, UserRespd3} = request(
-                          put,
-                          Path ++ "/users/test",
-                          #{password => <<"654321">>, is_superuser => true}),
+        put,
+        Path ++ "/users/test",
+        #{password => <<"654321">>, is_superuser => true}
+    ),
     assert_confs(UserRespd3, User1#{is_superuser => true}),
 
     {200, UserRespd4} = request(
-                          get,
-                          Path ++ "/users/test"),
+        get,
+        Path ++ "/users/test"
+    ),
     assert_confs(UserRespd4, User1#{is_superuser => true}),
 
     {204, _} = request(
-                 delete,
-                 Path ++ "/users/test"),
+        delete,
+        Path ++ "/users/test"
+    ),
 
     {200, #{data := []}} = request(
-                             get,
-                             Path ++ "/users"),
+        get,
+        Path ++ "/users"
+    ),
     {204, _} = request(delete, "/gateway/stomp").
 
 %%--------------------------------------------------------------------
diff --git a/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl
index bf1108c61..c63760224 100644
--- a/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_cli_SUITE.erl
@@ -21,30 +21,38 @@
 
 -include_lib("eunit/include/eunit.hrl").
 
--define(GP(S), begin S, receive {fmt, P} -> P; O -> O end end).
+-define(GP(S), begin
+    S,
+    receive
+        {fmt, P} -> P;
+        O -> O
+    end
+end).
 
 %% this parses to #{}, will not cause config cleanup
 %% so we will need call emqx_config:erase
--define(CONF_DEFAULT, <<"
-gateway {}
-">>).
+-define(CONF_DEFAULT, <<
+    "\n"
+    "gateway {}\n"
+>>).
 
 %% The config with json format for mqtt-sn gateway
--define(CONF_MQTTSN, "
-{\"idle_timeout\": \"30s\",
- \"enable_stats\": true,
- \"mountpoint\": \"mqttsn/\",
- \"gateway_id\": 1,
- \"broadcast\": true,
- \"enable_qos3\": true,
- \"predefined\": [{\"id\": 1001, \"topic\": \"pred/a\"}],
- \"listeners\":
-    [{\"type\": \"udp\",
-      \"name\": \"ct\",
-      \"bind\": \"1884\"
-    }]
-}
-").
+-define(CONF_MQTTSN,
+    "\n"
+    "{\"idle_timeout\": \"30s\",\n"
+    " \"enable_stats\": true,\n"
+    " \"mountpoint\": \"mqttsn/\",\n"
+    " \"gateway_id\": 1,\n"
+    " \"broadcast\": true,\n"
+    " \"enable_qos3\": true,\n"
+    " \"predefined\": [{\"id\": 1001, \"topic\": \"pred/a\"}],\n"
+    " \"listeners\":\n"
+    "    [{\"type\": \"udp\",\n"
+    "      \"name\": \"ct\",\n"
+    "      \"bind\": \"1884\"\n"
+    "    }]\n"
+    "}\n"
+).
 
 %%--------------------------------------------------------------------
 %% Setup
@@ -65,16 +73,25 @@ end_per_suite(Conf) ->
 init_per_testcase(_, Conf) ->
     Self = self(),
     ok = meck:new(emqx_ctl, [passthrough, no_history, no_link]),
-    ok = meck:expect(emqx_ctl, usage,
-                     fun(L) -> emqx_ctl:format_usage(L) end),
-    ok = meck:expect(emqx_ctl, print,
-                     fun(Fmt) ->
-                        Self ! {fmt, emqx_ctl:format(Fmt, [])}
-                     end),
-    ok = meck:expect(emqx_ctl, print,
-                     fun(Fmt, Args) ->
-                        Self ! {fmt, emqx_ctl:format(Fmt, Args)}
-                     end),
+    ok = meck:expect(
+        emqx_ctl,
+        usage,
+        fun(L) -> emqx_ctl:format_usage(L) end
+    ),
+    ok = meck:expect(
+        emqx_ctl,
+        print,
+        fun(Fmt) ->
+            Self ! {fmt, emqx_ctl:format(Fmt, [])}
+        end
+    ),
+    ok = meck:expect(
+        emqx_ctl,
+        print,
+        fun(Fmt, Args) ->
+            Self ! {fmt, emqx_ctl:format(Fmt, Args)}
+        end
+    ),
     Conf.
 
 end_per_testcase(_, _) ->
@@ -92,39 +109,44 @@ t_load_unload(_) ->
 
 t_gateway_registry_usage(_) ->
     ?assertEqual(
-       ["gateway-registry list # List all registered gateways\n"],
-       emqx_gateway_cli:'gateway-registry'(usage)).
+        ["gateway-registry list # List all registered gateways\n"],
+        emqx_gateway_cli:'gateway-registry'(usage)
+    ).
 
 t_gateway_registry_list(_) ->
     emqx_gateway_cli:'gateway-registry'(["list"]),
     ?assertEqual(
-       "Registered Name: coap, Callback Module: emqx_coap_impl\n"
-       "Registered Name: exproto, Callback Module: emqx_exproto_impl\n"
-       "Registered Name: lwm2m, Callback Module: emqx_lwm2m_impl\n"
-       "Registered Name: mqttsn, Callback Module: emqx_sn_impl\n"
-       "Registered Name: stomp, Callback Module: emqx_stomp_impl\n"
-       , acc_print()).
+        "Registered Name: coap, Callback Module: emqx_coap_impl\n"
+        "Registered Name: exproto, Callback Module: emqx_exproto_impl\n"
+        "Registered Name: lwm2m, Callback Module: emqx_lwm2m_impl\n"
+        "Registered Name: mqttsn, Callback Module: emqx_sn_impl\n"
+        "Registered Name: stomp, Callback Module: emqx_stomp_impl\n",
+        acc_print()
+    ).
 
 t_gateway_usage(_) ->
     ?assertEqual(
-       ["gateway list                     # List all gateway\n",
-        "gateway lookup             # Lookup a gateway detailed information\n",
-        "gateway load     # Load a gateway with config\n",
-        "gateway unload             # Unload the gateway\n",
-        "gateway stop               # Stop the gateway\n",
-        "gateway start              # Start the gateway\n"],
-       emqx_gateway_cli:gateway(usage)
-     ).
+        [
+            "gateway list                     # List all gateway\n",
+            "gateway lookup             # Lookup a gateway detailed information\n",
+            "gateway load     # Load a gateway with config\n",
+            "gateway unload             # Unload the gateway\n",
+            "gateway stop               # Stop the gateway\n",
+            "gateway start              # Start the gateway\n"
+        ],
+        emqx_gateway_cli:gateway(usage)
+    ).
 
 t_gateway_list(_) ->
     emqx_gateway_cli:gateway(["list"]),
     ?assertEqual(
-      "Gateway(name=coap, status=unloaded)\n"
-      "Gateway(name=exproto, status=unloaded)\n"
-      "Gateway(name=lwm2m, status=unloaded)\n"
-      "Gateway(name=mqttsn, status=unloaded)\n"
-      "Gateway(name=stomp, status=unloaded)\n"
-      , acc_print()),
+        "Gateway(name=coap, status=unloaded)\n"
+        "Gateway(name=exproto, status=unloaded)\n"
+        "Gateway(name=lwm2m, status=unloaded)\n"
+        "Gateway(name=mqttsn, status=unloaded)\n"
+        "Gateway(name=stomp, status=unloaded)\n",
+        acc_print()
+    ),
 
     emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]),
     ?assertEqual("ok\n", acc_print()),
@@ -158,8 +180,9 @@ t_gateway_load_unload_lookup(_) ->
 
     emqx_gateway_cli:gateway(["load", "mqttsn", "{}"]),
     ?assertEqual(
-        "Error: The mqttsn gateway already loaded\n"
-        , acc_print()),
+        "Error: The mqttsn gateway already loaded\n",
+        acc_print()
+    ),
 
     emqx_gateway_cli:gateway(["load", "bad-gw-name", "{}"]),
     %% TODO: assert it. for example:
@@ -196,14 +219,16 @@ t_gateway_start_stop(_) ->
 
 t_gateway_clients_usage(_) ->
     ?assertEqual(
-       ["gateway-clients list               "
+        [
+            "gateway-clients list               "
             "# List all clients for a gateway\n",
-        "gateway-clients lookup   "
+            "gateway-clients lookup   "
             "# Lookup the Client Info for specified client\n",
-        "gateway-clients kick     "
-            "# Kick out a client\n"],
-       emqx_gateway_cli:'gateway-clients'(usage)
-     ).
+            "gateway-clients kick     "
+            "# Kick out a client\n"
+        ],
+        emqx_gateway_cli:'gateway-clients'(usage)
+    ).
 
 t_gateway_clients(_) ->
     emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]),
@@ -258,10 +283,12 @@ t_gateway_clients_kick(_) ->
 
 t_gateway_metrcis_usage(_) ->
     ?assertEqual(
-       [ "gateway-metrics  "
-            "# List all metrics for a gateway\n"],
-       emqx_gateway_cli:'gateway-metrics'(usage)
-     ).
+        [
+            "gateway-metrics  "
+            "# List all metrics for a gateway\n"
+        ],
+        emqx_gateway_cli:'gateway-metrics'(usage)
+    ).
 
 t_gateway_metrcis(_) ->
     ok.
@@ -271,7 +298,7 @@ acc_print() ->
 
 acc_print(Acc) ->
     receive
-        {fmt, S} -> acc_print([S|Acc])
+        {fmt, S} -> acc_print([S | Acc])
     after 200 ->
         Acc
     end.
@@ -279,10 +306,13 @@ acc_print(Acc) ->
 sn_client_connect(ClientId) ->
     {ok, Socket} = gen_udp:open(0, [binary]),
     _ = emqx_sn_protocol_SUITE:send_connect_msg(Socket, ClientId),
-    ?assertEqual(<<3, 16#05, 0>>,
-                 emqx_sn_protocol_SUITE:receive_response(Socket)),
+    ?assertEqual(
+        <<3, 16#05, 0>>,
+        emqx_sn_protocol_SUITE:receive_response(Socket)
+    ),
     Socket.
 
 sn_client_disconnect(Socket) ->
     _ = emqx_sn_protocol_SUITE:send_disconnect_msg(Socket, undefined),
-    gen_udp:close(Socket), ok.
+    gen_udp:close(Socket),
+    ok.
diff --git a/apps/emqx_gateway/test/emqx_gateway_cm_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_cm_SUITE.erl
index dabea7c95..6aa3533df 100644
--- a/apps/emqx_gateway/test/emqx_gateway_cm_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_cm_SUITE.erl
@@ -61,40 +61,70 @@ end_per_testcase(_TestCase, Conf) ->
 %%--------------------------------------------------------------------
 
 t_open_session(_) ->
-    {ok, #{present := false,
-           session := #{}}} = emqx_gateway_cm:open_session(
-                                ?GWNAME, false, clientinfo(), conninfo(),
-                                fun(_, _) -> #{} end),
+    {ok, #{
+        present := false,
+        session := #{}
+    }} = emqx_gateway_cm:open_session(
+        ?GWNAME,
+        false,
+        clientinfo(),
+        conninfo(),
+        fun(_, _) -> #{} end
+    ),
 
     {ok, SessionRes} = emqx_gateway_cm:open_session(
-                         ?GWNAME, true, clientinfo(), conninfo(),
-                         fun(_, _) -> #{no => 1} end),
-    ?assertEqual(#{present => false,
-                   session => #{no => 1}}, SessionRes),
+        ?GWNAME,
+        true,
+        clientinfo(),
+        conninfo(),
+        fun(_, _) -> #{no => 1} end
+    ),
+    ?assertEqual(
+        #{
+            present => false,
+            session => #{no => 1}
+        },
+        SessionRes
+    ),
 
     %% assert1. check channel infos in ets table
     Chann = {?CLIENTID, self()},
     ?assertEqual(
-       [Chann],
-       ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))),
+        [Chann],
+        ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))
+    ),
     ?assertEqual(
-       [{Chann, ?MODULE}],
-       ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))),
+        [{Chann, ?MODULE}],
+        ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))
+    ),
 
     %% assert2. discard the presented session
 
     {ok, SessionRes2} = emqx_gateway_cm:open_session(
-                          ?GWNAME, true, clientinfo(), conninfo(),
-                          fun(_, _) -> #{no => 2} end),
-    ?assertEqual(#{present => false,
-                   session => #{no => 2}}, SessionRes2),
+        ?GWNAME,
+        true,
+        clientinfo(),
+        conninfo(),
+        fun(_, _) -> #{no => 2} end
+    ),
+    ?assertEqual(
+        #{
+            present => false,
+            session => #{no => 2}
+        },
+        SessionRes2
+    ),
 
     emqx_gateway_cm:insert_channel_info(
-      ?GWNAME, ?CLIENTID,
-      #{clientinfo => clientinfo(), conninfo => conninfo()}, []),
+        ?GWNAME,
+        ?CLIENTID,
+        #{clientinfo => clientinfo(), conninfo => conninfo()},
+        []
+    ),
     ?assertEqual(
-       1,
-       ets:info(emqx_gateway_cm:tabname(info, ?GWNAME), size)),
+        1,
+        ets:info(emqx_gateway_cm:tabname(info, ?GWNAME), size)
+    ),
 
     receive
         discard ->
@@ -106,43 +136,62 @@ t_open_session(_) ->
 
     %% assert3. no channel infos in ets table
     ?assertEqual(
-       [],
-       ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))),
+        [],
+        ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))
+    ),
     ?assertEqual(
-       [],
-       ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))),
+        [],
+        ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))
+    ),
     ?assertEqual(
-       [],
-       ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))).
+        [],
+        ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))
+    ).
 
 t_get_set_chan_info_stats(_) ->
     {ok, SessionRes} = emqx_gateway_cm:open_session(
-                         ?GWNAME, true, clientinfo(), conninfo(),
-                         fun(_, _) -> #{no => 1} end),
-    ?assertEqual(#{present => false,
-                   session => #{no => 1}}, SessionRes),
+        ?GWNAME,
+        true,
+        clientinfo(),
+        conninfo(),
+        fun(_, _) -> #{no => 1} end
+    ),
+    ?assertEqual(
+        #{
+            present => false,
+            session => #{no => 1}
+        },
+        SessionRes
+    ),
     emqx_gateway_cm:insert_channel_info(
-      ?GWNAME, ?CLIENTID,
-      #{clientinfo => clientinfo(), conninfo => conninfo()}, []),
+        ?GWNAME,
+        ?CLIENTID,
+        #{clientinfo => clientinfo(), conninfo => conninfo()},
+        []
+    ),
 
     %% Info: get/set
     NInfo = #{newinfo => true, node => node()},
     emqx_gateway_cm:set_chan_info(?GWNAME, ?CLIENTID, NInfo),
     ?assertEqual(
-       NInfo,
-       emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID)),
+        NInfo,
+        emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID)
+    ),
     ?assertEqual(
-       NInfo,
-       emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID, self())),
+        NInfo,
+        emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID, self())
+    ),
     %% Stats: get/set
     NStats = [{newstats, true}],
     emqx_gateway_cm:set_chan_stats(?GWNAME, ?CLIENTID, NStats),
     ?assertEqual(
-       NStats,
-       emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID)),
+        NStats,
+        emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID)
+    ),
     ?assertEqual(
-       NStats,
-       emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID, self())),
+        NStats,
+        emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID, self())
+    ),
 
     emqx_gateway_cm:connection_closed(?GWNAME, ?CLIENTID),
     emqx_gateway_cm:unregister_channel(?GWNAME, ?CLIENTID).
@@ -151,55 +200,83 @@ t_handle_process_down(Conf) ->
     Pid = proplists:get_value(cm, Conf),
 
     {ok, SessionRes} = emqx_gateway_cm:open_session(
-                         ?GWNAME, true, clientinfo(), conninfo(),
-                         fun(_, _) -> #{no => 1} end),
-    ?assertEqual(#{present => false,
-                   session => #{no => 1}}, SessionRes),
+        ?GWNAME,
+        true,
+        clientinfo(),
+        conninfo(),
+        fun(_, _) -> #{no => 1} end
+    ),
+    ?assertEqual(
+        #{
+            present => false,
+            session => #{no => 1}
+        },
+        SessionRes
+    ),
     emqx_gateway_cm:insert_channel_info(
-      ?GWNAME, ?CLIENTID,
-      #{clientinfo => clientinfo(), conninfo => conninfo()}, []),
+        ?GWNAME,
+        ?CLIENTID,
+        #{clientinfo => clientinfo(), conninfo => conninfo()},
+        []
+    ),
 
     _ = Pid ! {'DOWN', mref, process, self(), normal},
 
-    timer:sleep(200), %% wait the async clear task
+    %% wait the async clear task
+    timer:sleep(200),
     ?assertEqual(
-       [],
-       ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))),
+        [],
+        ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))
+    ),
     ?assertEqual(
-       [],
-       ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))),
+        [],
+        ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))
+    ),
     ?assertEqual(
-       [],
-       ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))).
+        [],
+        ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))
+    ).
 
 t_kick_session(_) ->
     %% session1
     {ok, _} = emqx_gateway_cm:open_session(
-                ?GWNAME, true, clientinfo(), conninfo(),
-                fun(_, _) -> #{no => 1} end),
+        ?GWNAME,
+        true,
+        clientinfo(),
+        conninfo(),
+        fun(_, _) -> #{no => 1} end
+    ),
     emqx_gateway_cm:insert_channel_info(
-      ?GWNAME, ?CLIENTID,
-      #{clientinfo => clientinfo(), conninfo => conninfo()}, []),
+        ?GWNAME,
+        ?CLIENTID,
+        #{clientinfo => clientinfo(), conninfo => conninfo()},
+        []
+    ),
 
     %% meck `lookup_channels`
     Self = self(),
-    ok = meck:new(emqx_gateway_cm_registry,
-                  [passthrough, no_history, no_link]),
-    ok = meck:expect(emqx_gateway_cm_registry, lookup_channels,
-                     fun(_, ?CLIENTID) -> [Self, Self] end),
+    ok = meck:new(
+        emqx_gateway_cm_registry,
+        [passthrough, no_history, no_link]
+    ),
+    ok = meck:expect(
+        emqx_gateway_cm_registry,
+        lookup_channels,
+        fun(_, ?CLIENTID) -> [Self, Self] end
+    ),
 
     ok = emqx_gateway_cm:kick_session(?GWNAME, ?CLIENTID),
 
-    receive kick -> ok
+    receive
+        kick -> ok
     after 100 -> ?assert(false, "waiting discard msg timeout")
     end,
     receive
         kick ->
             emqx_gateway_cm:connection_closed(?GWNAME, ?CLIENTID),
             emqx_gateway_cm:unregister_channel(?GWNAME, ?CLIENTID)
-    after
-        100 ->
-            ?assert(false, "waiting kick msg timeout")
+    after 100 ->
+        ?assert(false, "waiting kick msg timeout")
     end,
     ?assertMatch({error, not_found}, emqx_gateway_http:kickout_client(?GWNAME, <<"i-dont-exist">>)),
     meck:unload(emqx_gateway_cm_registry).
@@ -214,37 +291,41 @@ t_unexpected_handle(Conf) ->
 %% helpers
 
 clientinfo() ->
-    #{ clientid => ?CLIENTID
-     , is_bridge => false
-     , is_superuser => false
-     , listener => 'mqttsn:udp:default'
-     , mountpoint => <<"mqttsn/">>
-     , peerhost => {127, 0, 0, 1}
-     , protocol => 'mqtt-sn'
-     , sockport => 1884
-     , username => undefined
-     , zone => default
-     }.
+    #{
+        clientid => ?CLIENTID,
+        is_bridge => false,
+        is_superuser => false,
+        listener => 'mqttsn:udp:default',
+        mountpoint => <<"mqttsn/">>,
+        peerhost => {127, 0, 0, 1},
+        protocol => 'mqtt-sn',
+        sockport => 1884,
+        username => undefined,
+        zone => default
+    }.
 
 conninfo() ->
-    #{ clean_start => true
-     , clientid => ?CLIENTID
-     , conn_mod => ?MODULE
-     , connected_at => 1641805544652
-     , expiry_interval => 0
-     , keepalive => 10
-     , peercert => nossl
-     , peername => {{127, 0, 0, 1}, 64810}
-     , proto_name => <<"MQTT-SN">>
-     , proto_ver => <<"1.2">>
-     , sockname => {{0, 0, 0, 0}, 1884}
-     , socktype => udp
-     }.
+    #{
+        clean_start => true,
+        clientid => ?CLIENTID,
+        conn_mod => ?MODULE,
+        connected_at => 1641805544652,
+        expiry_interval => 0,
+        keepalive => 10,
+        peercert => nossl,
+        peername => {{127, 0, 0, 1}, 64810},
+        proto_name => <<"MQTT-SN">>,
+        proto_ver => <<"1.2">>,
+        sockname => {{0, 0, 0, 0}, 1884},
+        socktype => udp
+    }.
 
 %%--------------------------------------------------------------------
 %% connection module mock
 
 call(ConnPid, discard, _) ->
-    ConnPid ! discard, ok;
+    ConnPid ! discard,
+    ok;
 call(ConnPid, kick, _) ->
-    ConnPid ! kick, ok.
+    ConnPid ! kick,
+    ok.
diff --git a/apps/emqx_gateway/test/emqx_gateway_cm_registry_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_cm_registry_SUITE.erl
index 628a66c0a..1cfe40a3b 100644
--- a/apps/emqx_gateway/test/emqx_gateway_cm_registry_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_cm_registry_SUITE.erl
@@ -56,51 +56,60 @@ end_per_testcase(_TestCase, Conf) ->
 
 t_tabname(_) ->
     ?assertEqual(
-       emqx_gateway_gw_name_channel_registry,
-       emqx_gateway_cm_registry:tabname(gw_name)).
+        emqx_gateway_gw_name_channel_registry,
+        emqx_gateway_cm_registry:tabname(gw_name)
+    ).
 
 t_register_unregister_channel(_) ->
     ok = emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID),
     ?assertEqual(
-       [{channel, ?CLIENTID, self()}],
-       ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))),
+        [{channel, ?CLIENTID, self()}],
+        ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))
+    ),
 
     ?assertEqual(
-       [self()],
-       emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)),
+        [self()],
+        emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
+    ),
 
     ok = emqx_gateway_cm_registry:unregister_channel(?GWNAME, ?CLIENTID),
 
     ?assertEqual(
-       [],
-       ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))),
+        [],
+        ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))
+    ),
     ?assertEqual(
-       [],
-       emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)).
+        [],
+        emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
+    ).
 
 t_cleanup_channels_mnesia_down(Conf) ->
     Pid = proplists:get_value(registry, Conf),
     emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID),
     ?assertEqual(
-       [self()],
-       emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)),
+        [self()],
+        emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
+    ),
     Pid ! {membership, {mnesia, down, node()}},
     ct:sleep(100),
     ?assertEqual(
-       [],
-       emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)).
+        [],
+        emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
+    ).
 
 t_cleanup_channels_node_down(Conf) ->
     Pid = proplists:get_value(registry, Conf),
     emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID),
     ?assertEqual(
-       [self()],
-       emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)),
+        [self()],
+        emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
+    ),
     Pid ! {membership, {node, down, node()}},
     ct:sleep(100),
     ?assertEqual(
-       [],
-       emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)).
+        [],
+        emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
+    ).
 
 t_handle_unexpected_msg(Conf) ->
     Pid = proplists:get_value(registry, Conf),
diff --git a/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl
index bc34da123..e3de574e0 100644
--- a/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_conf_SUITE.erl
@@ -19,10 +19,13 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--import(emqx_gateway_test_utils,
-        [ assert_confs/2
-        , maybe_unconvert_listeners/1
-        ]).
+-import(
+    emqx_gateway_test_utils,
+    [
+        assert_confs/2,
+        maybe_unconvert_listeners/1
+    ]
+).
 
 -include_lib("eunit/include/eunit.hrl").
 
@@ -49,205 +52,210 @@ init_per_testcase(_CaseName, Conf) ->
 %% Cases
 %%--------------------------------------------------------------------
 
--define(SVR_CA,
-<<"-----BEGIN CERTIFICATE-----
-MIIDUTCCAjmgAwIBAgIJAPPYCjTmxdt/MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV
-BAYTAkNOMREwDwYDVQQIDAhoYW5nemhvdTEMMAoGA1UECgwDRU1RMQ8wDQYDVQQD
-DAZSb290Q0EwHhcNMjAwNTA4MDgwNjUyWhcNMzAwNTA2MDgwNjUyWjA/MQswCQYD
-VQQGEwJDTjERMA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UE
-AwwGUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcgVLex1
-EZ9ON64EX8v+wcSjzOZpiEOsAOuSXOEN3wb8FKUxCdsGrsJYB7a5VM/Jot25Mod2
-juS3OBMg6r85k2TWjdxUoUs+HiUB/pP/ARaaW6VntpAEokpij/przWMPgJnBF3Ur
-MjtbLayH9hGmpQrI5c2vmHQ2reRZnSFbY+2b8SXZ+3lZZgz9+BaQYWdQWfaUWEHZ
-uDaNiViVO0OT8DRjCuiDp3yYDj3iLWbTA/gDL6Tf5XuHuEwcOQUrd+h0hyIphO8D
-tsrsHZ14j4AWYLk1CPA6pq1HIUvEl2rANx2lVUNv+nt64K/Mr3RnVQd9s8bK+TXQ
-KGHd2Lv/PALYuwIDAQABo1AwTjAdBgNVHQ4EFgQUGBmW+iDzxctWAWxmhgdlE8Pj
-EbQwHwYDVR0jBBgwFoAUGBmW+iDzxctWAWxmhgdlE8PjEbQwDAYDVR0TBAUwAwEB
-/zANBgkqhkiG9w0BAQsFAAOCAQEAGbhRUjpIred4cFAFJ7bbYD9hKu/yzWPWkMRa
-ErlCKHmuYsYk+5d16JQhJaFy6MGXfLgo3KV2itl0d+OWNH0U9ULXcglTxy6+njo5
-CFqdUBPwN1jxhzo9yteDMKF4+AHIxbvCAJa17qcwUKR5MKNvv09C6pvQDJLzid7y
-E2dkgSuggik3oa0427KvctFf8uhOV94RvEDyqvT5+pgNYZ2Yfga9pD/jjpoHEUlo
-88IGU8/wJCx3Ds2yc8+oBg/ynxG8f/HmCC1ET6EHHoe2jlo8FpU/SgGtghS1YL30
-IWxNsPrUP+XsZpBJy/mvOhE5QXo6Y35zDqqj8tI7AGmAWu22jg==
------END CERTIFICATE-----
-">>).
+-define(SVR_CA, <<
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIDUTCCAjmgAwIBAgIJAPPYCjTmxdt/MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV\n"
+    "BAYTAkNOMREwDwYDVQQIDAhoYW5nemhvdTEMMAoGA1UECgwDRU1RMQ8wDQYDVQQD\n"
+    "DAZSb290Q0EwHhcNMjAwNTA4MDgwNjUyWhcNMzAwNTA2MDgwNjUyWjA/MQswCQYD\n"
+    "VQQGEwJDTjERMA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UE\n"
+    "AwwGUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcgVLex1\n"
+    "EZ9ON64EX8v+wcSjzOZpiEOsAOuSXOEN3wb8FKUxCdsGrsJYB7a5VM/Jot25Mod2\n"
+    "juS3OBMg6r85k2TWjdxUoUs+HiUB/pP/ARaaW6VntpAEokpij/przWMPgJnBF3Ur\n"
+    "MjtbLayH9hGmpQrI5c2vmHQ2reRZnSFbY+2b8SXZ+3lZZgz9+BaQYWdQWfaUWEHZ\n"
+    "uDaNiViVO0OT8DRjCuiDp3yYDj3iLWbTA/gDL6Tf5XuHuEwcOQUrd+h0hyIphO8D\n"
+    "tsrsHZ14j4AWYLk1CPA6pq1HIUvEl2rANx2lVUNv+nt64K/Mr3RnVQd9s8bK+TXQ\n"
+    "KGHd2Lv/PALYuwIDAQABo1AwTjAdBgNVHQ4EFgQUGBmW+iDzxctWAWxmhgdlE8Pj\n"
+    "EbQwHwYDVR0jBBgwFoAUGBmW+iDzxctWAWxmhgdlE8PjEbQwDAYDVR0TBAUwAwEB\n"
+    "/zANBgkqhkiG9w0BAQsFAAOCAQEAGbhRUjpIred4cFAFJ7bbYD9hKu/yzWPWkMRa\n"
+    "ErlCKHmuYsYk+5d16JQhJaFy6MGXfLgo3KV2itl0d+OWNH0U9ULXcglTxy6+njo5\n"
+    "CFqdUBPwN1jxhzo9yteDMKF4+AHIxbvCAJa17qcwUKR5MKNvv09C6pvQDJLzid7y\n"
+    "E2dkgSuggik3oa0427KvctFf8uhOV94RvEDyqvT5+pgNYZ2Yfga9pD/jjpoHEUlo\n"
+    "88IGU8/wJCx3Ds2yc8+oBg/ynxG8f/HmCC1ET6EHHoe2jlo8FpU/SgGtghS1YL30\n"
+    "IWxNsPrUP+XsZpBJy/mvOhE5QXo6Y35zDqqj8tI7AGmAWu22jg==\n"
+    "-----END CERTIFICATE-----\n"
+>>).
 
--define(SVR_CERT,
-<<"-----BEGIN CERTIFICATE-----
-MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER
-MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB
-MB4XDTIwMDUwODA4MDcwNVoXDTMwMDUwNjA4MDcwNVowPzELMAkGA1UEBhMCQ04x
-ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBlNlcnZl
-cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNeWT3pE+QFfiRJzKmn
-AMUrWo3K2j/Tm3+Xnl6WLz67/0rcYrJbbKvS3uyRP/stXyXEKw9CepyQ1ViBVFkW
-Aoy8qQEOWFDsZc/5UzhXUnb6LXr3qTkFEjNmhj+7uzv/lbBxlUG1NlYzSeOB6/RT
-8zH/lhOeKhLnWYPXdXKsa1FL6ij4X8DeDO1kY7fvAGmBn/THh1uTpDizM4YmeI+7
-4dmayA5xXvARte5h4Vu5SIze7iC057N+vymToMk2Jgk+ZZFpyXrnq+yo6RaD3ANc
-lrc4FbeUQZ5a5s5Sxgs9a0Y3WMG+7c5VnVXcbjBRz/aq2NtOnQQjikKKQA8GF080
-BQkCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL
-BQADggEBAJefnMZpaRDHQSNUIEL3iwGXE9c6PmIsQVE2ustr+CakBp3TZ4l0enLt
-iGMfEVFju69cO4oyokWv+hl5eCMkHBf14Kv51vj448jowYnF1zmzn7SEzm5Uzlsa
-sqjtAprnLyof69WtLU1j5rYWBuFX86yOTwRAFNjm9fvhAcrEONBsQtqipBWkMROp
-iUYMkRqbKcQMdwxov+lHBYKq9zbWRoqLROAn54SRqgQk6c15JdEfgOOjShbsOkIH
-UhqcwRkQic7n1zwHVGVDgNIZVgmJ2IdIWBlPEC7oLrRrBD/X1iEEXtKab6p5o22n
-KB5mN+iQaE+Oe2cpGKZJiJRdM+IqDDQ=
------END CERTIFICATE-----
-">>).
+-define(SVR_CERT, <<
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER\n"
+    "MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB\n"
+    "MB4XDTIwMDUwODA4MDcwNVoXDTMwMDUwNjA4MDcwNVowPzELMAkGA1UEBhMCQ04x\n"
+    "ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBlNlcnZl\n"
+    "cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNeWT3pE+QFfiRJzKmn\n"
+    "AMUrWo3K2j/Tm3+Xnl6WLz67/0rcYrJbbKvS3uyRP/stXyXEKw9CepyQ1ViBVFkW\n"
+    "Aoy8qQEOWFDsZc/5UzhXUnb6LXr3qTkFEjNmhj+7uzv/lbBxlUG1NlYzSeOB6/RT\n"
+    "8zH/lhOeKhLnWYPXdXKsa1FL6ij4X8DeDO1kY7fvAGmBn/THh1uTpDizM4YmeI+7\n"
+    "4dmayA5xXvARte5h4Vu5SIze7iC057N+vymToMk2Jgk+ZZFpyXrnq+yo6RaD3ANc\n"
+    "lrc4FbeUQZ5a5s5Sxgs9a0Y3WMG+7c5VnVXcbjBRz/aq2NtOnQQjikKKQA8GF080\n"
+    "BQkCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL\n"
+    "BQADggEBAJefnMZpaRDHQSNUIEL3iwGXE9c6PmIsQVE2ustr+CakBp3TZ4l0enLt\n"
+    "iGMfEVFju69cO4oyokWv+hl5eCMkHBf14Kv51vj448jowYnF1zmzn7SEzm5Uzlsa\n"
+    "sqjtAprnLyof69WtLU1j5rYWBuFX86yOTwRAFNjm9fvhAcrEONBsQtqipBWkMROp\n"
+    "iUYMkRqbKcQMdwxov+lHBYKq9zbWRoqLROAn54SRqgQk6c15JdEfgOOjShbsOkIH\n"
+    "UhqcwRkQic7n1zwHVGVDgNIZVgmJ2IdIWBlPEC7oLrRrBD/X1iEEXtKab6p5o22n\n"
+    "KB5mN+iQaE+Oe2cpGKZJiJRdM+IqDDQ=\n"
+    "-----END CERTIFICATE-----\n"
+>>).
 
--define(SVR_KEY,
-<<"-----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAs15ZPekT5AV+JEnMqacAxStajcraP9Obf5eeXpYvPrv/Stxi
-sltsq9Le7JE/+y1fJcQrD0J6nJDVWIFUWRYCjLypAQ5YUOxlz/lTOFdSdvotevep
-OQUSM2aGP7u7O/+VsHGVQbU2VjNJ44Hr9FPzMf+WE54qEudZg9d1cqxrUUvqKPhf
-wN4M7WRjt+8AaYGf9MeHW5OkOLMzhiZ4j7vh2ZrIDnFe8BG17mHhW7lIjN7uILTn
-s36/KZOgyTYmCT5lkWnJeuer7KjpFoPcA1yWtzgVt5RBnlrmzlLGCz1rRjdYwb7t
-zlWdVdxuMFHP9qrY206dBCOKQopADwYXTzQFCQIDAQABAoIBAQCuvCbr7Pd3lvI/
-n7VFQG+7pHRe1VKwAxDkx2t8cYos7y/QWcm8Ptwqtw58HzPZGWYrgGMCRpzzkRSF
-V9g3wP1S5Scu5C6dBu5YIGc157tqNGXB+SpdZddJQ4Nc6yGHXYERllT04ffBGc3N
-WG/oYS/1cSteiSIrsDy/91FvGRCi7FPxH3wIgHssY/tw69s1Cfvaq5lr2NTFzxIG
-xCvpJKEdSfVfS9I7LYiymVjst3IOR/w76/ZFY9cRa8ZtmQSWWsm0TUpRC1jdcbkm
-ZoJptYWlP+gSwx/fpMYftrkJFGOJhHJHQhwxT5X/ajAISeqjjwkWSEJLwnHQd11C
-Zy2+29lBAoGBANlEAIK4VxCqyPXNKfoOOi5dS64NfvyH4A1v2+KaHWc7lqaqPN49
-ezfN2n3X+KWx4cviDD914Yc2JQ1vVJjSaHci7yivocDo2OfZDmjBqzaMp/y+rX1R
-/f3MmiTqMa468rjaxI9RRZu7vDgpTR+za1+OBCgMzjvAng8dJuN/5gjlAoGBANNY
-uYPKtearBmkqdrSV7eTUe49Nhr0XotLaVBH37TCW0Xv9wjO2xmbm5Ga/DCtPIsBb
-yPeYwX9FjoasuadUD7hRvbFu6dBa0HGLmkXRJZTcD7MEX2Lhu4BuC72yDLLFd0r+
-Ep9WP7F5iJyagYqIZtz+4uf7gBvUDdmvXz3sGr1VAoGAdXTD6eeKeiI6PlhKBztF
-zOb3EQOO0SsLv3fnodu7ZaHbUgLaoTMPuB17r2jgrYM7FKQCBxTNdfGZmmfDjlLB
-0xZ5wL8ibU30ZXL8zTlWPElST9sto4B+FYVVF/vcG9sWeUUb2ncPcJ/Po3UAktDG
-jYQTTyuNGtSJHpad/YOZctkCgYBtWRaC7bq3of0rJGFOhdQT9SwItN/lrfj8hyHA
-OjpqTV4NfPmhsAtu6j96OZaeQc+FHvgXwt06cE6Rt4RG4uNPRluTFgO7XYFDfitP
-vCppnoIw6S5BBvHwPP+uIhUX2bsi/dm8vu8tb+gSvo4PkwtFhEr6I9HglBKmcmog
-q6waEQKBgHyecFBeM6Ls11Cd64vborwJPAuxIW7HBAFj/BS99oeG4TjBx4Sz2dFd
-rzUibJt4ndnHIvCN8JQkjNG14i9hJln+H3mRss8fbZ9vQdqG+2vOWADYSzzsNI55
-RFY7JjluKcVkp/zCDeUxTU3O6sS+v6/3VE11Cob6OYQx3lN5wrZ3
------END RSA PRIVATE KEY-----
-">>).
+-define(SVR_KEY, <<
+    "-----BEGIN RSA PRIVATE KEY-----\n"
+    "MIIEowIBAAKCAQEAs15ZPekT5AV+JEnMqacAxStajcraP9Obf5eeXpYvPrv/Stxi\n"
+    "sltsq9Le7JE/+y1fJcQrD0J6nJDVWIFUWRYCjLypAQ5YUOxlz/lTOFdSdvotevep\n"
+    "OQUSM2aGP7u7O/+VsHGVQbU2VjNJ44Hr9FPzMf+WE54qEudZg9d1cqxrUUvqKPhf\n"
+    "wN4M7WRjt+8AaYGf9MeHW5OkOLMzhiZ4j7vh2ZrIDnFe8BG17mHhW7lIjN7uILTn\n"
+    "s36/KZOgyTYmCT5lkWnJeuer7KjpFoPcA1yWtzgVt5RBnlrmzlLGCz1rRjdYwb7t\n"
+    "zlWdVdxuMFHP9qrY206dBCOKQopADwYXTzQFCQIDAQABAoIBAQCuvCbr7Pd3lvI/\n"
+    "n7VFQG+7pHRe1VKwAxDkx2t8cYos7y/QWcm8Ptwqtw58HzPZGWYrgGMCRpzzkRSF\n"
+    "V9g3wP1S5Scu5C6dBu5YIGc157tqNGXB+SpdZddJQ4Nc6yGHXYERllT04ffBGc3N\n"
+    "WG/oYS/1cSteiSIrsDy/91FvGRCi7FPxH3wIgHssY/tw69s1Cfvaq5lr2NTFzxIG\n"
+    "xCvpJKEdSfVfS9I7LYiymVjst3IOR/w76/ZFY9cRa8ZtmQSWWsm0TUpRC1jdcbkm\n"
+    "ZoJptYWlP+gSwx/fpMYftrkJFGOJhHJHQhwxT5X/ajAISeqjjwkWSEJLwnHQd11C\n"
+    "Zy2+29lBAoGBANlEAIK4VxCqyPXNKfoOOi5dS64NfvyH4A1v2+KaHWc7lqaqPN49\n"
+    "ezfN2n3X+KWx4cviDD914Yc2JQ1vVJjSaHci7yivocDo2OfZDmjBqzaMp/y+rX1R\n"
+    "/f3MmiTqMa468rjaxI9RRZu7vDgpTR+za1+OBCgMzjvAng8dJuN/5gjlAoGBANNY\n"
+    "uYPKtearBmkqdrSV7eTUe49Nhr0XotLaVBH37TCW0Xv9wjO2xmbm5Ga/DCtPIsBb\n"
+    "yPeYwX9FjoasuadUD7hRvbFu6dBa0HGLmkXRJZTcD7MEX2Lhu4BuC72yDLLFd0r+\n"
+    "Ep9WP7F5iJyagYqIZtz+4uf7gBvUDdmvXz3sGr1VAoGAdXTD6eeKeiI6PlhKBztF\n"
+    "zOb3EQOO0SsLv3fnodu7ZaHbUgLaoTMPuB17r2jgrYM7FKQCBxTNdfGZmmfDjlLB\n"
+    "0xZ5wL8ibU30ZXL8zTlWPElST9sto4B+FYVVF/vcG9sWeUUb2ncPcJ/Po3UAktDG\n"
+    "jYQTTyuNGtSJHpad/YOZctkCgYBtWRaC7bq3of0rJGFOhdQT9SwItN/lrfj8hyHA\n"
+    "OjpqTV4NfPmhsAtu6j96OZaeQc+FHvgXwt06cE6Rt4RG4uNPRluTFgO7XYFDfitP\n"
+    "vCppnoIw6S5BBvHwPP+uIhUX2bsi/dm8vu8tb+gSvo4PkwtFhEr6I9HglBKmcmog\n"
+    "q6waEQKBgHyecFBeM6Ls11Cd64vborwJPAuxIW7HBAFj/BS99oeG4TjBx4Sz2dFd\n"
+    "rzUibJt4ndnHIvCN8JQkjNG14i9hJln+H3mRss8fbZ9vQdqG+2vOWADYSzzsNI55\n"
+    "RFY7JjluKcVkp/zCDeUxTU3O6sS+v6/3VE11Cob6OYQx3lN5wrZ3\n"
+    "-----END RSA PRIVATE KEY-----\n"
+>>).
 
--define(SVR_CERT2,
-<<"-----BEGIN CERTIFICATE-----
-MIIDEzCCAfugAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER
-MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB
-MB4XDTIwMDUwODA4MDY1N1oXDTMwMDUwNjA4MDY1N1owPzELMAkGA1UEBhMCQ04x
-ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBkNsaWVu
-dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMy4hoksKcZBDbY680u6
-TS25U51nuB1FBcGMlF9B/t057wPOlxF/OcmbxY5MwepS41JDGPgulE1V7fpsXkiW
-1LUimYV/tsqBfymIe0mlY7oORahKji7zKQ2UBIVFhdlvQxunlIDnw6F9popUgyHt
-dMhtlgZK8oqRwHxO5dbfoukYd6J/r+etS5q26sgVkf3C6dt0Td7B25H9qW+f7oLV
-PbcHYCa+i73u9670nrpXsC+Qc7Mygwa2Kq/jwU+ftyLQnOeW07DuzOwsziC/fQZa
-nbxR+8U9FNftgRcC3uP/JMKYUqsiRAuaDokARZxVTV5hUElfpO6z6/NItSDvvh3i
-eikCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL
-BQADggEBABchYxKo0YMma7g1qDswJXsR5s56Czx/I+B41YcpMBMTrRqpUC0nHtLk
-M7/tZp592u/tT8gzEnQjZLKBAhFeZaR3aaKyknLqwiPqJIgg0pgsBGITrAK3Pv4z
-5/YvAJJKgTe5UdeTz6U4lvNEux/4juZ4pmqH4qSFJTOzQS7LmgSmNIdd072rwXBd
-UzcSHzsJgEMb88u/LDLjj1pQ7AtZ4Tta8JZTvcgBFmjB0QUi6fgkHY6oGat/W4kR
-jSRUBlMUbM/drr2PVzRc2dwbFIl3X+ZE6n5Sl3ZwRAC/s92JU6CPMRW02muVu6xl
-goraNgPISnrbpR6KjxLZkVembXzjNNc=
------END CERTIFICATE-----
-">>).
+-define(SVR_CERT2, <<
+    "-----BEGIN CERTIFICATE-----\n"
+    "MIIDEzCCAfugAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER\n"
+    "MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB\n"
+    "MB4XDTIwMDUwODA4MDY1N1oXDTMwMDUwNjA4MDY1N1owPzELMAkGA1UEBhMCQ04x\n"
+    "ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBkNsaWVu\n"
+    "dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMy4hoksKcZBDbY680u6\n"
+    "TS25U51nuB1FBcGMlF9B/t057wPOlxF/OcmbxY5MwepS41JDGPgulE1V7fpsXkiW\n"
+    "1LUimYV/tsqBfymIe0mlY7oORahKji7zKQ2UBIVFhdlvQxunlIDnw6F9popUgyHt\n"
+    "dMhtlgZK8oqRwHxO5dbfoukYd6J/r+etS5q26sgVkf3C6dt0Td7B25H9qW+f7oLV\n"
+    "PbcHYCa+i73u9670nrpXsC+Qc7Mygwa2Kq/jwU+ftyLQnOeW07DuzOwsziC/fQZa\n"
+    "nbxR+8U9FNftgRcC3uP/JMKYUqsiRAuaDokARZxVTV5hUElfpO6z6/NItSDvvh3i\n"
+    "eikCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL\n"
+    "BQADggEBABchYxKo0YMma7g1qDswJXsR5s56Czx/I+B41YcpMBMTrRqpUC0nHtLk\n"
+    "M7/tZp592u/tT8gzEnQjZLKBAhFeZaR3aaKyknLqwiPqJIgg0pgsBGITrAK3Pv4z\n"
+    "5/YvAJJKgTe5UdeTz6U4lvNEux/4juZ4pmqH4qSFJTOzQS7LmgSmNIdd072rwXBd\n"
+    "UzcSHzsJgEMb88u/LDLjj1pQ7AtZ4Tta8JZTvcgBFmjB0QUi6fgkHY6oGat/W4kR\n"
+    "jSRUBlMUbM/drr2PVzRc2dwbFIl3X+ZE6n5Sl3ZwRAC/s92JU6CPMRW02muVu6xl\n"
+    "goraNgPISnrbpR6KjxLZkVembXzjNNc=\n"
+    "-----END CERTIFICATE-----\n"
+>>).
 
--define(SVR_KEY2,
-<<"-----BEGIN RSA PRIVATE KEY-----
-MIIEpAIBAAKCAQEAzLiGiSwpxkENtjrzS7pNLblTnWe4HUUFwYyUX0H+3TnvA86X
-EX85yZvFjkzB6lLjUkMY+C6UTVXt+mxeSJbUtSKZhX+2yoF/KYh7SaVjug5FqEqO
-LvMpDZQEhUWF2W9DG6eUgOfDoX2milSDIe10yG2WBkryipHAfE7l1t+i6Rh3on+v
-561LmrbqyBWR/cLp23RN3sHbkf2pb5/ugtU9twdgJr6Lve73rvSeulewL5BzszKD
-BrYqr+PBT5+3ItCc55bTsO7M7CzOIL99BlqdvFH7xT0U1+2BFwLe4/8kwphSqyJE
-C5oOiQBFnFVNXmFQSV+k7rPr80i1IO++HeJ6KQIDAQABAoIBAGWgvPjfuaU3qizq
-uti/FY07USz0zkuJdkANH6LiSjlchzDmn8wJ0pApCjuIE0PV/g9aS8z4opp5q/gD
-UBLM/a8mC/xf2EhTXOMrY7i9p/I3H5FZ4ZehEqIw9sWKK9YzC6dw26HabB2BGOnW
-5nozPSQ6cp2RGzJ7BIkxSZwPzPnVTgy3OAuPOiJytvK+hGLhsNaT+Y9bNDvplVT2
-ZwYTV8GlHZC+4b2wNROILm0O86v96O+Qd8nn3fXjGHbMsAnONBq10bZS16L4fvkH
-5G+W/1PeSXmtZFppdRRDxIW+DWcXK0D48WRliuxcV4eOOxI+a9N2ZJZZiNLQZGwg
-w3A8+mECgYEA8HuJFrlRvdoBe2U/EwUtG74dcyy30L4yEBnN5QscXmEEikhaQCfX
-Wm6EieMcIB/5I5TQmSw0cmBMeZjSXYoFdoI16/X6yMMuATdxpvhOZGdUGXxhAH+x
-xoTUavWZnEqW3fkUU71kT5E2f2i+0zoatFESXHeslJyz85aAYpP92H0CgYEA2e5A
-Yozt5eaA1Gyhd8SeptkEU4xPirNUnVQHStpMWUb1kzTNXrPmNWccQ7JpfpG6DcYl
-zUF6p6mlzY+zkMiyPQjwEJlhiHM2NlL1QS7td0R8ewgsFoyn8WsBI4RejWrEG9td
-EDniuIw+pBFkcWthnTLHwECHdzgquToyTMjrBB0CgYEA28tdGbrZXhcyAZEhHAZA
-Gzog+pKlkpEzeonLKIuGKzCrEKRecIK5jrqyQsCjhS0T7ZRnL4g6i0s+umiV5M5w
-fcc292pEA1h45L3DD6OlKplSQVTv55/OYS4oY3YEJtf5mfm8vWi9lQeY8sxOlQpn
-O+VZTdBHmTC8PGeTAgZXHZUCgYA6Tyv88lYowB7SN2qQgBQu8jvdGtqhcs/99GCr
-H3N0I69LPsKAR0QeH8OJPXBKhDUywESXAaEOwS5yrLNP1tMRz5Vj65YUCzeDG3kx
-gpvY4IMp7ArX0bSRvJ6mYSFnVxy3k174G3TVCfksrtagHioVBGQ7xUg5ltafjrms
-n8l55QKBgQDVzU8tQvBVqY8/1lnw11Vj4fkE/drZHJ5UkdC1eenOfSWhlSLfUJ8j
-ds7vEWpRPPoVuPZYeR1y78cyxKe1GBx6Wa2lF5c7xjmiu0xbRnrxYeLolce9/ntp
-asClqpnHT8/VJYTD7Kqj0fouTTZf0zkig/y+2XERppd8k+pSKjUCPQ==
------END RSA PRIVATE KEY-----
-">>).
+-define(SVR_KEY2, <<
+    "-----BEGIN RSA PRIVATE KEY-----\n"
+    "MIIEpAIBAAKCAQEAzLiGiSwpxkENtjrzS7pNLblTnWe4HUUFwYyUX0H+3TnvA86X\n"
+    "EX85yZvFjkzB6lLjUkMY+C6UTVXt+mxeSJbUtSKZhX+2yoF/KYh7SaVjug5FqEqO\n"
+    "LvMpDZQEhUWF2W9DG6eUgOfDoX2milSDIe10yG2WBkryipHAfE7l1t+i6Rh3on+v\n"
+    "561LmrbqyBWR/cLp23RN3sHbkf2pb5/ugtU9twdgJr6Lve73rvSeulewL5BzszKD\n"
+    "BrYqr+PBT5+3ItCc55bTsO7M7CzOIL99BlqdvFH7xT0U1+2BFwLe4/8kwphSqyJE\n"
+    "C5oOiQBFnFVNXmFQSV+k7rPr80i1IO++HeJ6KQIDAQABAoIBAGWgvPjfuaU3qizq\n"
+    "uti/FY07USz0zkuJdkANH6LiSjlchzDmn8wJ0pApCjuIE0PV/g9aS8z4opp5q/gD\n"
+    "UBLM/a8mC/xf2EhTXOMrY7i9p/I3H5FZ4ZehEqIw9sWKK9YzC6dw26HabB2BGOnW\n"
+    "5nozPSQ6cp2RGzJ7BIkxSZwPzPnVTgy3OAuPOiJytvK+hGLhsNaT+Y9bNDvplVT2\n"
+    "ZwYTV8GlHZC+4b2wNROILm0O86v96O+Qd8nn3fXjGHbMsAnONBq10bZS16L4fvkH\n"
+    "5G+W/1PeSXmtZFppdRRDxIW+DWcXK0D48WRliuxcV4eOOxI+a9N2ZJZZiNLQZGwg\n"
+    "w3A8+mECgYEA8HuJFrlRvdoBe2U/EwUtG74dcyy30L4yEBnN5QscXmEEikhaQCfX\n"
+    "Wm6EieMcIB/5I5TQmSw0cmBMeZjSXYoFdoI16/X6yMMuATdxpvhOZGdUGXxhAH+x\n"
+    "xoTUavWZnEqW3fkUU71kT5E2f2i+0zoatFESXHeslJyz85aAYpP92H0CgYEA2e5A\n"
+    "Yozt5eaA1Gyhd8SeptkEU4xPirNUnVQHStpMWUb1kzTNXrPmNWccQ7JpfpG6DcYl\n"
+    "zUF6p6mlzY+zkMiyPQjwEJlhiHM2NlL1QS7td0R8ewgsFoyn8WsBI4RejWrEG9td\n"
+    "EDniuIw+pBFkcWthnTLHwECHdzgquToyTMjrBB0CgYEA28tdGbrZXhcyAZEhHAZA\n"
+    "Gzog+pKlkpEzeonLKIuGKzCrEKRecIK5jrqyQsCjhS0T7ZRnL4g6i0s+umiV5M5w\n"
+    "fcc292pEA1h45L3DD6OlKplSQVTv55/OYS4oY3YEJtf5mfm8vWi9lQeY8sxOlQpn\n"
+    "O+VZTdBHmTC8PGeTAgZXHZUCgYA6Tyv88lYowB7SN2qQgBQu8jvdGtqhcs/99GCr\n"
+    "H3N0I69LPsKAR0QeH8OJPXBKhDUywESXAaEOwS5yrLNP1tMRz5Vj65YUCzeDG3kx\n"
+    "gpvY4IMp7ArX0bSRvJ6mYSFnVxy3k174G3TVCfksrtagHioVBGQ7xUg5ltafjrms\n"
+    "n8l55QKBgQDVzU8tQvBVqY8/1lnw11Vj4fkE/drZHJ5UkdC1eenOfSWhlSLfUJ8j\n"
+    "ds7vEWpRPPoVuPZYeR1y78cyxKe1GBx6Wa2lF5c7xjmiu0xbRnrxYeLolce9/ntp\n"
+    "asClqpnHT8/VJYTD7Kqj0fouTTZf0zkig/y+2XERppd8k+pSKjUCPQ==\n"
+    "-----END RSA PRIVATE KEY-----\n"
+>>).
 
--define(CONF_STOMP_BAISC_1,
-        #{ <<"idle_timeout">> => <<"10s">>,
-           <<"mountpoint">> => <<"t/">>,
-           <<"frame">> =>
-           #{ <<"max_headers">> => 20,
-              <<"max_headers_length">> => 2000,
-              <<"max_body_length">> => 2000
-            }
-         }).
--define(CONF_STOMP_BAISC_2,
-        #{ <<"idle_timeout">> => <<"20s">>,
-           <<"mountpoint">> => <<"t2/">>,
-           <<"frame">> =>
-           #{ <<"max_headers">> => 30,
-              <<"max_headers_length">> => 3000,
-              <<"max_body_length">> => 3000
-            }
-         }).
--define(CONF_STOMP_LISTENER_1,
-        #{ <<"bind">> => <<"61613">>
-         }).
--define(CONF_STOMP_LISTENER_2,
-        #{ <<"bind">> => <<"61614">>
-         }).
--define(CONF_STOMP_LISTENER_SSL,
-        #{ <<"bind">> => <<"61614">>,
-           <<"ssl">> =>
-           #{ <<"cacertfile">> => ?SVR_CA,
-              <<"certfile">> => ?SVR_CERT,
-              <<"keyfile">> => ?SVR_KEY
-            }
-         }).
--define(CONF_STOMP_LISTENER_SSL_2,
-        #{ <<"bind">> => <<"61614">>,
-           <<"ssl">> =>
-           #{ <<"cacertfile">> => ?SVR_CA,
-              <<"certfile">> => ?SVR_CERT2,
-              <<"keyfile">> => ?SVR_KEY2
-            }
-         }).
+-define(CONF_STOMP_BAISC_1, #{
+    <<"idle_timeout">> => <<"10s">>,
+    <<"mountpoint">> => <<"t/">>,
+    <<"frame">> =>
+        #{
+            <<"max_headers">> => 20,
+            <<"max_headers_length">> => 2000,
+            <<"max_body_length">> => 2000
+        }
+}).
+-define(CONF_STOMP_BAISC_2, #{
+    <<"idle_timeout">> => <<"20s">>,
+    <<"mountpoint">> => <<"t2/">>,
+    <<"frame">> =>
+        #{
+            <<"max_headers">> => 30,
+            <<"max_headers_length">> => 3000,
+            <<"max_body_length">> => 3000
+        }
+}).
+-define(CONF_STOMP_LISTENER_1, #{<<"bind">> => <<"61613">>}).
+-define(CONF_STOMP_LISTENER_2, #{<<"bind">> => <<"61614">>}).
+-define(CONF_STOMP_LISTENER_SSL, #{
+    <<"bind">> => <<"61614">>,
+    <<"ssl">> =>
+        #{
+            <<"cacertfile">> => ?SVR_CA,
+            <<"certfile">> => ?SVR_CERT,
+            <<"keyfile">> => ?SVR_KEY
+        }
+}).
+-define(CONF_STOMP_LISTENER_SSL_2, #{
+    <<"bind">> => <<"61614">>,
+    <<"ssl">> =>
+        #{
+            <<"cacertfile">> => ?SVR_CA,
+            <<"certfile">> => ?SVR_CERT2,
+            <<"keyfile">> => ?SVR_KEY2
+        }
+}).
 -define(CERTS_PATH(CertName), filename:join(["../../lib/emqx/etc/certs/", CertName])).
--define(CONF_STOMP_LISTENER_SSL_PATH,
-        #{ <<"bind">> => <<"61614">>,
-           <<"ssl">> =>
-           #{ <<"cacertfile">> => ?CERTS_PATH("cacert.pem"),
-              <<"certfile">> => ?CERTS_PATH("cert.pem"),
-              <<"keyfile">> => ?CERTS_PATH("key.pem")
-            }
-         }).
--define(CONF_STOMP_AUTHN_1,
-        #{ <<"mechanism">> => <<"password_based">>,
-           <<"backend">> => <<"built_in_database">>,
-           <<"user_id_type">> => <<"clientid">>
-         }).
--define(CONF_STOMP_AUTHN_2,
-        #{ <<"mechanism">> => <<"password_based">>,
-           <<"backend">> => <<"built_in_database">>,
-           <<"user_id_type">> => <<"username">>
-         }).
+-define(CONF_STOMP_LISTENER_SSL_PATH, #{
+    <<"bind">> => <<"61614">>,
+    <<"ssl">> =>
+        #{
+            <<"cacertfile">> => ?CERTS_PATH("cacert.pem"),
+            <<"certfile">> => ?CERTS_PATH("cert.pem"),
+            <<"keyfile">> => ?CERTS_PATH("key.pem")
+        }
+}).
+-define(CONF_STOMP_AUTHN_1, #{
+    <<"mechanism">> => <<"password_based">>,
+    <<"backend">> => <<"built_in_database">>,
+    <<"user_id_type">> => <<"clientid">>
+}).
+-define(CONF_STOMP_AUTHN_2, #{
+    <<"mechanism">> => <<"password_based">>,
+    <<"backend">> => <<"built_in_database">>,
+    <<"user_id_type">> => <<"username">>
+}).
 
 t_load_unload_gateway(_) ->
-    StompConf1 = compose(?CONF_STOMP_BAISC_1,
-                         ?CONF_STOMP_AUTHN_1,
-                         ?CONF_STOMP_LISTENER_1
-                        ),
-    StompConf2 = compose(?CONF_STOMP_BAISC_2,
-                         ?CONF_STOMP_AUTHN_1,
-                         ?CONF_STOMP_LISTENER_1),
+    StompConf1 = compose(
+        ?CONF_STOMP_BAISC_1,
+        ?CONF_STOMP_AUTHN_1,
+        ?CONF_STOMP_LISTENER_1
+    ),
+    StompConf2 = compose(
+        ?CONF_STOMP_BAISC_2,
+        ?CONF_STOMP_AUTHN_1,
+        ?CONF_STOMP_LISTENER_1
+    ),
     {ok, _} = emqx_gateway_conf:load_gateway(stomp, StompConf1),
     ?assertMatch(
-       {error, {badres, #{reason := already_exist}}},
-       emqx_gateway_conf:load_gateway(stomp, StompConf1)),
+        {error, {badres, #{reason := already_exist}}},
+        emqx_gateway_conf:load_gateway(stomp, StompConf1)
+    ),
     assert_confs(StompConf1, emqx:get_raw_config([gateway, stomp])),
 
     {ok, _} = emqx_gateway_conf:update_gateway(stomp, StompConf2),
@@ -257,11 +265,15 @@ t_load_unload_gateway(_) ->
     ok = emqx_gateway_conf:unload_gateway(stomp),
 
     ?assertMatch(
-       {error, {badres, #{reason := not_found}}},
-       emqx_gateway_conf:update_gateway(stomp, StompConf2)),
+        {error, {badres, #{reason := not_found}}},
+        emqx_gateway_conf:update_gateway(stomp, StompConf2)
+    ),
 
-    ?assertException(error, {config_not_found, [gateway, stomp]},
-                     emqx:get_raw_config([gateway, stomp])),
+    ?assertException(
+        error,
+        {config_not_found, [gateway, stomp]},
+        emqx:get_raw_config([gateway, stomp])
+    ),
     ok.
 
 t_load_remove_authn(_) ->
@@ -272,24 +284,28 @@ t_load_remove_authn(_) ->
 
     {ok, _} = emqx_gateway_conf:add_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_1),
     assert_confs(
-      maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_1, StompConf),
-      emqx:get_raw_config([gateway, stomp])),
+        maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_1, StompConf),
+        emqx:get_raw_config([gateway, stomp])
+    ),
 
     {ok, _} = emqx_gateway_conf:update_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_2),
     assert_confs(
-      maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_2, StompConf),
-      emqx:get_raw_config([gateway, stomp])),
+        maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_2, StompConf),
+        emqx:get_raw_config([gateway, stomp])
+    ),
 
     ok = emqx_gateway_conf:remove_authn(<<"stomp">>),
 
     ?assertMatch(
-       {error, {badres, #{reason := not_found}}},
-       emqx_gateway_conf:update_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_2)),
+        {error, {badres, #{reason := not_found}}},
+        emqx_gateway_conf:update_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_2)
+    ),
 
     ?assertException(
-       error, {config_not_found, [gateway, stomp, authentication]},
-       emqx:get_raw_config([gateway, stomp, authentication])
-      ),
+        error,
+        {config_not_found, [gateway, stomp, authentication]},
+        emqx:get_raw_config([gateway, stomp, authentication])
+    ),
     ok.
 
 t_load_remove_listeners(_) ->
@@ -299,90 +315,109 @@ t_load_remove_listeners(_) ->
     assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
 
     {ok, _} = emqx_gateway_conf:add_listener(
-                <<"stomp">>, {<<"tcp">>, <<"default">>},
-                ?CONF_STOMP_LISTENER_1),
+        <<"stomp">>,
+        {<<"tcp">>, <<"default">>},
+        ?CONF_STOMP_LISTENER_1
+    ),
     assert_confs(
-      maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_1)),
-      emqx:get_raw_config([gateway, stomp])),
+        maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_1)),
+        emqx:get_raw_config([gateway, stomp])
+    ),
 
     {ok, _} = emqx_gateway_conf:update_listener(
-                <<"stomp">>, {<<"tcp">>, <<"default">>},
-                ?CONF_STOMP_LISTENER_2),
+        <<"stomp">>,
+        {<<"tcp">>, <<"default">>},
+        ?CONF_STOMP_LISTENER_2
+    ),
     assert_confs(
-      maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_2)),
-      emqx:get_raw_config([gateway, stomp])),
+        maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_2)),
+        emqx:get_raw_config([gateway, stomp])
+    ),
 
     ok = emqx_gateway_conf:remove_listener(
-           <<"stomp">>, {<<"tcp">>, <<"default">>}),
+        <<"stomp">>, {<<"tcp">>, <<"default">>}
+    ),
 
     ?assertMatch(
-       {error, {badres, #{reason := not_found}}},
-       emqx_gateway_conf:update_listener(
-         <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_LISTENER_2)),
+        {error, {badres, #{reason := not_found}}},
+        emqx_gateway_conf:update_listener(
+            <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_LISTENER_2
+        )
+    ),
 
     ?assertException(
-       error, {config_not_found, [gateway, stomp, listeners, tcp, default]},
-       emqx:get_raw_config([gateway, stomp, listeners, tcp, default])
-      ),
+        error,
+        {config_not_found, [gateway, stomp, listeners, tcp, default]},
+        emqx:get_raw_config([gateway, stomp, listeners, tcp, default])
+    ),
     ok.
 
 t_load_remove_listener_authn(_) ->
-    StompConf  = compose_listener(
-                   ?CONF_STOMP_BAISC_1,
-                   ?CONF_STOMP_LISTENER_1
-                  ),
+    StompConf = compose_listener(
+        ?CONF_STOMP_BAISC_1,
+        ?CONF_STOMP_LISTENER_1
+    ),
     StompConf1 = compose_listener_authn(
-                   ?CONF_STOMP_BAISC_1,
-                   ?CONF_STOMP_LISTENER_1,
-                   ?CONF_STOMP_AUTHN_1
-                  ),
+        ?CONF_STOMP_BAISC_1,
+        ?CONF_STOMP_LISTENER_1,
+        ?CONF_STOMP_AUTHN_1
+    ),
     StompConf2 = compose_listener_authn(
-                   ?CONF_STOMP_BAISC_1,
-                   ?CONF_STOMP_LISTENER_1,
-                   ?CONF_STOMP_AUTHN_2
-                 ),
+        ?CONF_STOMP_BAISC_1,
+        ?CONF_STOMP_LISTENER_1,
+        ?CONF_STOMP_AUTHN_2
+    ),
 
     {ok, _} = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf),
     assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
 
     {ok, _} = emqx_gateway_conf:add_authn(
-                <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_1),
+        <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_1
+    ),
     assert_confs(StompConf1, emqx:get_raw_config([gateway, stomp])),
 
     {ok, _} = emqx_gateway_conf:update_authn(
-                <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_2),
+        <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_2
+    ),
     assert_confs(StompConf2, emqx:get_raw_config([gateway, stomp])),
 
     ok = emqx_gateway_conf:remove_authn(
-           <<"stomp">>, {<<"tcp">>, <<"default">>}),
+        <<"stomp">>, {<<"tcp">>, <<"default">>}
+    ),
 
     ?assertMatch(
-       {error, {badres, #{reason := not_found}}},
-       emqx_gateway_conf:update_authn(
-         <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_2)),
+        {error, {badres, #{reason := not_found}}},
+        emqx_gateway_conf:update_authn(
+            <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_2
+        )
+    ),
 
     Path = [gateway, stomp, listeners, tcp, default, authentication],
     ?assertException(
-       error, {config_not_found, Path},
-       emqx:get_raw_config(Path)
-      ),
+        error,
+        {config_not_found, Path},
+        emqx:get_raw_config(Path)
+    ),
     ok.
 
 t_load_gateway_with_certs_content(_) ->
     StompConf = compose_ssl_listener(
-                  ?CONF_STOMP_BAISC_1,
-                  ?CONF_STOMP_LISTENER_SSL
-                 ),
+        ?CONF_STOMP_BAISC_1,
+        ?CONF_STOMP_LISTENER_SSL
+    ),
     {ok, _} = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf),
     assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
     SslConf = emqx_map_lib:deep_get(
-                [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
-                emqx:get_raw_config([gateway, stomp])
-               ),
+        [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
+        emqx:get_raw_config([gateway, stomp])
+    ),
     ok = emqx_gateway_conf:unload_gateway(<<"stomp">>),
     assert_ssl_confs_files_deleted(SslConf),
-    ?assertException(error, {config_not_found, [gateway, stomp]},
-                     emqx:get_raw_config([gateway, stomp])),
+    ?assertException(
+        error,
+        {config_not_found, [gateway, stomp]},
+        emqx:get_raw_config([gateway, stomp])
+    ),
     ok.
 
 %% TODO: Comment out this test case for now, because emqx_tls_lib
@@ -411,52 +446,66 @@ t_add_listener_with_certs_content(_) ->
     assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
 
     {ok, _} = emqx_gateway_conf:add_listener(
-                <<"stomp">>, {<<"ssl">>, <<"default">>},
-                ?CONF_STOMP_LISTENER_SSL),
+        <<"stomp">>,
+        {<<"ssl">>, <<"default">>},
+        ?CONF_STOMP_LISTENER_SSL
+    ),
     assert_confs(
-      maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL)),
-      emqx:get_raw_config([gateway, stomp])),
+        maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL)),
+        emqx:get_raw_config([gateway, stomp])
+    ),
 
     {ok, _} = emqx_gateway_conf:update_listener(
-                <<"stomp">>, {<<"ssl">>, <<"default">>},
-                ?CONF_STOMP_LISTENER_SSL_2),
+        <<"stomp">>,
+        {<<"ssl">>, <<"default">>},
+        ?CONF_STOMP_LISTENER_SSL_2
+    ),
     assert_confs(
-      maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL_2)),
-      emqx:get_raw_config([gateway, stomp])),
+        maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL_2)),
+        emqx:get_raw_config([gateway, stomp])
+    ),
 
     SslConf = emqx_map_lib:deep_get(
-                [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
-                emqx:get_raw_config([gateway, stomp])
-               ),
+        [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
+        emqx:get_raw_config([gateway, stomp])
+    ),
     ok = emqx_gateway_conf:remove_listener(
-           <<"stomp">>, {<<"ssl">>, <<"default">>}),
+        <<"stomp">>, {<<"ssl">>, <<"default">>}
+    ),
     assert_ssl_confs_files_deleted(SslConf),
 
     ?assertMatch(
-       {error, {badres, #{reason := not_found}}},
-       emqx_gateway_conf:update_listener(
-         <<"stomp">>, {<<"ssl">>, <<"default">>}, ?CONF_STOMP_LISTENER_SSL_2)),
+        {error, {badres, #{reason := not_found}}},
+        emqx_gateway_conf:update_listener(
+            <<"stomp">>, {<<"ssl">>, <<"default">>}, ?CONF_STOMP_LISTENER_SSL_2
+        )
+    ),
 
     ?assertException(
-       error, {config_not_found, [gateway, stomp, listeners, ssl, default]},
-       emqx:get_raw_config([gateway, stomp, listeners, ssl, default])
-      ),
+        error,
+        {config_not_found, [gateway, stomp, listeners, ssl, default]},
+        emqx:get_raw_config([gateway, stomp, listeners, ssl, default])
+    ),
     ok.
 
 assert_ssl_confs_files_deleted(SslConf) when is_map(SslConf) ->
     Ks = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>],
-    lists:foreach(fun(K) ->
-        Path = maps:get(K, SslConf),
-        {error, enoent} = file:read_file(Path)
-    end, Ks).
+    lists:foreach(
+        fun(K) ->
+            Path = maps:get(K, SslConf),
+            {error, enoent} = file:read_file(Path)
+        end,
+        Ks
+    ).
 
 %%--------------------------------------------------------------------
 %% Utils
 
 compose(Basic, Authn, Listener) ->
     maps:merge(
-      maps:merge(Basic, #{<<"authentication">> => Authn}),
-      listener(Listener)).
+        maps:merge(Basic, #{<<"authentication">> => Authn}),
+        listener(Listener)
+    ).
 
 compose_listener(Basic, Listener) ->
     maps:merge(Basic, listener(Listener)).
@@ -469,13 +518,26 @@ compose_authn(Basic, Authn) ->
 
 compose_listener_authn(Basic, Listener, Authn) ->
     maps:merge(
-      Basic,
-      listener(maps:put(<<"authentication">>, Authn, Listener))).
+        Basic,
+        listener(maps:put(<<"authentication">>, Authn, Listener))
+    ).
 
 listener(L) ->
-    #{<<"listeners">> => [L#{<<"type">> => <<"tcp">>,
-                             <<"name">> => <<"default">>}]}.
+    #{
+        <<"listeners">> => [
+            L#{
+                <<"type">> => <<"tcp">>,
+                <<"name">> => <<"default">>
+            }
+        ]
+    }.
 
 ssl_listener(L) ->
-    #{<<"listeners">> => [L#{<<"type">> => <<"ssl">>,
-                             <<"name">> => <<"default">>}]}.
+    #{
+        <<"listeners">> => [
+            L#{
+                <<"type">> => <<"ssl">>,
+                <<"name">> => <<"default">>
+            }
+        ]
+    }.
diff --git a/apps/emqx_gateway/test/emqx_gateway_ctx_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_ctx_SUITE.erl
index e09e5bc1b..f0ca75e24 100644
--- a/apps/emqx_gateway/test/emqx_gateway_ctx_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_ctx_SUITE.erl
@@ -29,11 +29,16 @@ all() -> emqx_common_test_helpers:all(?MODULE).
 
 init_per_suite(Conf) ->
     ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
-    ok = meck:expect(emqx_access_control, authenticate,
-                     fun(#{clientid := bad_client}) ->
-                             {error, bad_username_or_password};
-                        (ClientInfo) -> {ok, ClientInfo}
-                     end),
+    ok = meck:expect(
+        emqx_access_control,
+        authenticate,
+        fun
+            (#{clientid := bad_client}) ->
+                {error, bad_username_or_password};
+            (ClientInfo) ->
+                {ok, ClientInfo}
+        end
+    ),
     Conf.
 
 end_per_suite(_Conf) ->
@@ -45,23 +50,26 @@ end_per_suite(_Conf) ->
 
 t_authenticate(_) ->
     Ctx = #{gwname => mqttsn, auth => [], cm => self()},
-    Info1 = #{ mountpoint => undefined
-             , clientid => <<"user1">>
-             },
+    Info1 = #{
+        mountpoint => undefined,
+        clientid => <<"user1">>
+    },
     NInfo1 = zone(Info1),
     ?assertEqual({ok, NInfo1}, emqx_gateway_ctx:authenticate(Ctx, Info1)),
 
-    Info2 = #{ mountpoint => <<"mqttsn/${clientid}/">> 
-             , clientid => <<"user1">>
-             },
+    Info2 = #{
+        mountpoint => <<"mqttsn/${clientid}/">>,
+        clientid => <<"user1">>
+    },
     NInfo2 = zone(Info2#{mountpoint => <<"mqttsn/user1/">>}),
     ?assertEqual({ok, NInfo2}, emqx_gateway_ctx:authenticate(Ctx, Info2)),
 
-    Info3 = #{ mountpoint => <<"mqttsn/${clientid}/">> 
-             , clientid => bad_client
-             },
-    {error, bad_username_or_password}
-        = emqx_gateway_ctx:authenticate(Ctx, Info3),
+    Info3 = #{
+        mountpoint => <<"mqttsn/${clientid}/">>,
+        clientid => bad_client
+    },
+    {error, bad_username_or_password} =
+        emqx_gateway_ctx:authenticate(Ctx, Info3),
     ok.
 
 zone(Info) -> Info#{zone => default}.
diff --git a/apps/emqx_gateway/test/emqx_gateway_metrics_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_metrics_SUITE.erl
index 0bfa82204..d677c749d 100644
--- a/apps/emqx_gateway/test/emqx_gateway_metrics_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_metrics_SUITE.erl
@@ -58,15 +58,17 @@ t_inc_dec(_) ->
     ok = emqx_gateway_metrics:inc(?GWNAME, ?METRIC),
 
     ?assertEqual(
-      [{?METRIC, 2}],
-      emqx_gateway_metrics:lookup(?GWNAME)),
+        [{?METRIC, 2}],
+        emqx_gateway_metrics:lookup(?GWNAME)
+    ),
 
     ok = emqx_gateway_metrics:dec(?GWNAME, ?METRIC),
     ok = emqx_gateway_metrics:dec(?GWNAME, ?METRIC),
 
     ?assertEqual(
-      [{?METRIC, 0}],
-      emqx_gateway_metrics:lookup(?GWNAME)).
+        [{?METRIC, 0}],
+        emqx_gateway_metrics:lookup(?GWNAME)
+    ).
 
 t_handle_unexpected_msg(Conf) ->
     Pid = proplists:get_value(metrics, Conf),
diff --git a/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl b/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl
index 99a89149b..78a83dca6 100644
--- a/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_registry_SUITE.erl
@@ -21,11 +21,14 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--define(CONF_DEFAULT, <<"""
-gateway: {
-    stomp {}
-}
-""">>).
+-define(CONF_DEFAULT, <<
+    ""
+    "\n"
+    "gateway: {\n"
+    "    stomp {}\n"
+    "}\n"
+    ""
+>>).
 
 all() -> emqx_common_test_helpers:all(?MODULE).
 
@@ -50,10 +53,12 @@ t_load_unload(_) ->
     OldCnt = length(emqx_gateway_registry:list()),
     RgOpts = [{cbkmod, ?MODULE}],
     ok = emqx_gateway_registry:reg(test, RgOpts),
-    ?assertEqual(OldCnt+1, length(emqx_gateway_registry:list())),
+    ?assertEqual(OldCnt + 1, length(emqx_gateway_registry:list())),
 
-    #{cbkmod := ?MODULE,
-      rgopts := RgOpts} = emqx_gateway_registry:lookup(test),
+    #{
+        cbkmod := ?MODULE,
+        rgopts := RgOpts
+    } = emqx_gateway_registry:lookup(test),
 
     {error, already_existed} = emqx_gateway_registry:reg(test, [{cbkmod, ?MODULE}]),
 
diff --git a/apps/emqx_gateway/test/emqx_gateway_test_utils.erl b/apps/emqx_gateway/test/emqx_gateway_test_utils.erl
index c96dee651..e253aed4f 100644
--- a/apps/emqx_gateway/test/emqx_gateway_test_utils.erl
+++ b/apps/emqx_gateway/test/emqx_gateway_test_utils.erl
@@ -23,60 +23,81 @@ assert_confs(Expected0, Effected) ->
     Expected = maybe_unconvert_listeners(Expected0),
     case do_assert_confs(root, Expected, Effected) of
         false ->
-            io:format(standard_error, "Expected config: ~p,\n"
-                                      "Effected config: ~p",
-                                      [Expected, Effected]),
+            io:format(
+                standard_error,
+                "Expected config: ~p,\n"
+                "Effected config: ~p",
+                [Expected, Effected]
+            ),
             exit(conf_not_match);
         true ->
             ok
     end.
 
-do_assert_confs(_Key, Expected, Effected) when is_map(Expected),
-                                               is_map(Effected) ->
+do_assert_confs(_Key, Expected, Effected) when
+    is_map(Expected),
+    is_map(Effected)
+->
     Ks1 = maps:keys(Expected),
-    lists:all(fun(K) ->
-        do_assert_confs(K,
-                        maps:get(K, Expected),
-                        maps:get(K, Effected, undefined))
-    end, Ks1);
-
-do_assert_confs(Key, Expected, Effected) when Key == <<"cacertfile">>;
-                                              Key == <<"certfile">>;
-                                              Key == <<"keyfile">> ->
+    lists:all(
+        fun(K) ->
+            do_assert_confs(
+                K,
+                maps:get(K, Expected),
+                maps:get(K, Effected, undefined)
+            )
+        end,
+        Ks1
+    );
+do_assert_confs(Key, Expected, Effected) when
+    Key == <<"cacertfile">>;
+    Key == <<"certfile">>;
+    Key == <<"keyfile">>
+->
     case Expected == Effected of
-        true -> true;
+        true ->
+            true;
         false ->
             case file:read_file(Effected) of
                 {ok, Content} -> Expected == Content;
                 _ -> false
             end
     end;
-do_assert_confs(Key, [Expected|More1], [Effected|More2]) ->
-    do_assert_confs(Key, Expected, Effected)
-    andalso do_assert_confs(Key, More1, More2);
+do_assert_confs(Key, [Expected | More1], [Effected | More2]) ->
+    do_assert_confs(Key, Expected, Effected) andalso
+        do_assert_confs(Key, More1, More2);
 do_assert_confs(_Key, [], []) ->
     true;
 do_assert_confs(Key, Expected, Effected) ->
     Res = Expected =:= Effected,
     Res == false andalso
-    ct:pal("Errors: ~p value not match, "
-           "expected: ~p, got: ~p~n", [Key, Expected, Effected]),
+        ct:pal(
+            "Errors: ~p value not match, "
+            "expected: ~p, got: ~p~n",
+            [Key, Expected, Effected]
+        ),
     Res.
 
 maybe_unconvert_listeners(Conf) when is_map(Conf) ->
     case maps:take(<<"listeners">>, Conf) of
-        error -> Conf;
+        error ->
+            Conf;
         {Ls, Conf1} ->
-            Conf1#{<<"listeners">> =>
-                   emqx_gateway_conf:unconvert_listeners(Ls)}
+            Conf1#{
+                <<"listeners">> =>
+                    emqx_gateway_conf:unconvert_listeners(Ls)
+            }
     end;
 maybe_unconvert_listeners(Conf) ->
     Conf.
 
 assert_feilds_apperence(Ks, Map) ->
-    lists:foreach(fun(K) ->
-        _ = maps:get(K, Map)
-    end, Ks).
+    lists:foreach(
+        fun(K) ->
+            _ = maps:get(K, Map)
+        end,
+        Ks
+    ).
 
 %%--------------------------------------------------------------------
 %% http
@@ -100,12 +121,15 @@ request(post = Mth, Path, Body) ->
 do_request(Mth, Req) ->
     case httpc:request(Mth, Req, [], [{body_format, binary}]) of
         {ok, {{_Vsn, Code, _Text}, _, Resp}} ->
-            NResp = case Resp of
-                        <<>> -> #{};
-                        _ ->
-                            emqx_map_lib:unsafe_atom_key_map(
-                              emqx_json:decode(Resp, [return_maps]))
-                    end,
+            NResp =
+                case Resp of
+                    <<>> ->
+                        #{};
+                    _ ->
+                        emqx_map_lib:unsafe_atom_key_map(
+                            emqx_json:decode(Resp, [return_maps])
+                        )
+                end,
             {Code, NResp};
         {error, Reason} ->
             error({failed_to_request, Reason})
diff --git a/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl b/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl
index a26b447b3..c32bf7b70 100644
--- a/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_lwm2m_SUITE.erl
@@ -19,10 +19,13 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--import(emqx_gateway_test_utils,
-        [ request/2
-        , request/3
-        ]).
+-import(
+    emqx_gateway_test_utils,
+    [
+        request/2,
+        request/3
+    ]
+).
 
 -define(PORT, 5783).
 
@@ -33,27 +36,28 @@
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
 
--define(CONF_DEFAULT, <<"
-gateway.lwm2m {
-  xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"
-  lifetime_min = 1s
-  lifetime_max = 86400s
-  qmode_time_window = 22
-  auto_observe = false
-  mountpoint = \"lwm2m/${username}\"
-  update_msg_publish_condition = contains_object_list
-  translators {
-    command = {topic = \"/dn/#\", qos = 0}
-    response = {topic = \"/up/resp\", qos = 0}
-    notify = {topic = \"/up/notify\", qos = 0}
-    register = {topic = \"/up/resp\", qos = 0}
-    update = {topic = \"/up/resp\", qos = 0}
-  }
-  listeners.udp.default {
-    bind = 5783
-  }
-}
-">>).
+-define(CONF_DEFAULT, <<
+    "\n"
+    "gateway.lwm2m {\n"
+    "  xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"\n"
+    "  lifetime_min = 1s\n"
+    "  lifetime_max = 86400s\n"
+    "  qmode_time_window = 22\n"
+    "  auto_observe = false\n"
+    "  mountpoint = \"lwm2m/${username}\"\n"
+    "  update_msg_publish_condition = contains_object_list\n"
+    "  translators {\n"
+    "    command = {topic = \"/dn/#\", qos = 0}\n"
+    "    response = {topic = \"/up/resp\", qos = 0}\n"
+    "    notify = {topic = \"/up/notify\", qos = 0}\n"
+    "    register = {topic = \"/up/resp\", qos = 0}\n"
+    "    update = {topic = \"/up/resp\", qos = 0}\n"
+    "  }\n"
+    "  listeners.udp.default {\n"
+    "    bind = 5783\n"
+    "  }\n"
+    "}\n"
+>>).
 
 -record(coap_content, {content_format, payload = <<>>}).
 
@@ -62,18 +66,19 @@ gateway.lwm2m {
 %%--------------------------------------------------------------------
 
 all() ->
-    [ {group, test_grp_0_register}
-    , {group, test_grp_1_read}
-    , {group, test_grp_2_write}
-    , {group, test_grp_create}
-    , {group, test_grp_delete}
-    , {group, test_grp_3_execute}
-    , {group, test_grp_4_discover}
-    , {group, test_grp_5_write_attr}
-    , {group, test_grp_6_observe}
-      %% {group, test_grp_8_object_19}
-    , {group, test_grp_9_psm_queue_mode}
-    , {group, test_grp_10_rest_api}
+    [
+        {group, test_grp_0_register},
+        {group, test_grp_1_read},
+        {group, test_grp_2_write},
+        {group, test_grp_create},
+        {group, test_grp_delete},
+        {group, test_grp_3_execute},
+        {group, test_grp_4_discover},
+        {group, test_grp_5_write_attr},
+        {group, test_grp_6_observe},
+        %% {group, test_grp_8_object_19}
+        {group, test_grp_9_psm_queue_mode},
+        {group, test_grp_10_rest_api}
     ].
 
 suite() -> [{timetrap, {seconds, 90}}].
@@ -81,83 +86,70 @@ suite() -> [{timetrap, {seconds, 90}}].
 groups() ->
     RepeatOpt = {repeat_until_all_ok, 1},
     [
-        {test_grp_0_register, [RepeatOpt],
-         [
-          case01_register,
-          case01_register_additional_opts,
-          %% TODO now we can't handle partial decode packet
-          %% case01_register_incorrect_opts,
-          case01_register_report,
-          case02_update_deregister,
-          case03_register_wrong_version,
-          case04_register_and_lifetime_timeout,
-          case05_register_wrong_epn,
-          %% case06_register_wrong_lifetime, %% now, will ignore wrong lifetime
-          case07_register_alternate_path_01,
-          case07_register_alternate_path_02,
-          case08_reregister
-         ]},
-     {test_grp_1_read, [RepeatOpt],
-      [
-       case10_read,
-       case10_read_separate_ack,
-       case11_read_object_tlv,
-       case11_read_object_json,
-       case12_read_resource_opaque,
-       case13_read_no_xml
-      ]},
-     {test_grp_2_write, [RepeatOpt],
-      [
-       case20_write,
-       case21_write_object,
-       case22_write_error,
-       case20_single_write
-      ]},
-     {test_grp_create, [RepeatOpt],
-      [
-       case_create_basic
-      ]},
-     {test_grp_delete, [RepeatOpt],
-      [
-       case_delete_basic
-      ]},
-     {test_grp_3_execute, [RepeatOpt],
-      [
-       case30_execute, case31_execute_error
-      ]},
-     {test_grp_4_discover, [RepeatOpt],
-      [
-       case40_discover
-      ]},
-     {test_grp_5_write_attr, [RepeatOpt],
-      [
-       case50_write_attribute
-      ]},
-     {test_grp_6_observe, [RepeatOpt],
-      [
-       case60_observe
-      ]},
-     {test_grp_7_block_wize_transfer, [RepeatOpt],
-      [
-       case70_read_large, case70_write_large
-      ]},
-     {test_grp_8_object_19, [RepeatOpt],
-      [
-       case80_specail_object_19_1_0_write,
-       case80_specail_object_19_0_0_notify,
-       case80_specail_object_19_0_0_response,
-       case80_normal_object_19_0_0_read
-       ]},
-     {test_grp_9_psm_queue_mode, [RepeatOpt],
-      [
-       case90_psm_mode,
-       case90_queue_mode
-      ]},
-     {test_grp_10_rest_api, [RepeatOpt],
-      [
-       case100_clients_api,
-       case100_subscription_api
-      ]}
+        {test_grp_0_register, [RepeatOpt], [
+            case01_register,
+            case01_register_additional_opts,
+            %% TODO now we can't handle partial decode packet
+            %% case01_register_incorrect_opts,
+            case01_register_report,
+            case02_update_deregister,
+            case03_register_wrong_version,
+            case04_register_and_lifetime_timeout,
+            case05_register_wrong_epn,
+            %% case06_register_wrong_lifetime, %% now, will ignore wrong lifetime
+            case07_register_alternate_path_01,
+            case07_register_alternate_path_02,
+            case08_reregister
+        ]},
+        {test_grp_1_read, [RepeatOpt], [
+            case10_read,
+            case10_read_separate_ack,
+            case11_read_object_tlv,
+            case11_read_object_json,
+            case12_read_resource_opaque,
+            case13_read_no_xml
+        ]},
+        {test_grp_2_write, [RepeatOpt], [
+            case20_write,
+            case21_write_object,
+            case22_write_error,
+            case20_single_write
+        ]},
+        {test_grp_create, [RepeatOpt], [
+            case_create_basic
+        ]},
+        {test_grp_delete, [RepeatOpt], [
+            case_delete_basic
+        ]},
+        {test_grp_3_execute, [RepeatOpt], [
+            case30_execute, case31_execute_error
+        ]},
+        {test_grp_4_discover, [RepeatOpt], [
+            case40_discover
+        ]},
+        {test_grp_5_write_attr, [RepeatOpt], [
+            case50_write_attribute
+        ]},
+        {test_grp_6_observe, [RepeatOpt], [
+            case60_observe
+        ]},
+        {test_grp_7_block_wize_transfer, [RepeatOpt], [
+            case70_read_large, case70_write_large
+        ]},
+        {test_grp_8_object_19, [RepeatOpt], [
+            case80_specail_object_19_1_0_write,
+            case80_specail_object_19_0_0_notify,
+            case80_specail_object_19_0_0_response,
+            case80_normal_object_19_0_0_read
+        ]},
+        {test_grp_9_psm_queue_mode, [RepeatOpt], [
+            case90_psm_mode,
+            case90_queue_mode
+        ]},
+        {test_grp_10_rest_api, [RepeatOpt], [
+            case100_clients_api,
+            case100_subscription_api
+        ]}
     ].
 
 init_per_suite(Config) ->
@@ -168,7 +160,7 @@ init_per_suite(Config) ->
 
 end_per_suite(Config) ->
     timer:sleep(300),
-    {ok, _} = emqx_conf:remove([<<"gateway">>,<<"lwm2m">>], #{}),
+    {ok, _} = emqx_conf:remove([<<"gateway">>, <<"lwm2m">>], #{}),
     emqx_mgmt_api_test_util:end_suite([emqx_conf]),
     Config.
 
@@ -178,9 +170,11 @@ init_per_testcase(_AllTestCase, Config) ->
     {ok, _} = application:ensure_all_started(emqx_gateway),
     {ok, ClientUdpSock} = gen_udp:open(0, [binary, {active, false}]),
 
-    {ok, C} = emqtt:start_link([{host, "localhost"},
-                                {port, 1883},
-                                {clientid, <<"c1">>}]),
+    {ok, C} = emqtt:start_link([
+        {host, "localhost"},
+        {port, 1883},
+        {clientid, <<"c1">>}
+    ]),
     {ok, _} = emqtt:connect(C),
     timer:sleep(100),
 
@@ -203,16 +197,19 @@ case01_register(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
 
     %% checkpoint 1 - response
     #coap_message{type = Type, method = Method, id = RspId, options = Opts} =
@@ -234,16 +231,19 @@ case01_register(Config) ->
     ?LOGT("start to send DE-REGISTER command", []),
     MsgId3 = 52,
     test_send_coap_request(
-      UdpSock,
-      delete,
-      sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
-      #coap_content{payload = <<>>},
-      [],
-      MsgId3),
-    #coap_message{type = ack,
-                  id = RspId3,
-                  method = Method3} = test_recv_coap_response(UdpSock),
-    {ok,deleted} = Method3,
+        UdpSock,
+        delete,
+        sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
+        #coap_content{payload = <<>>},
+        [],
+        MsgId3
+    ),
+    #coap_message{
+        type = ack,
+        id = RspId3,
+        method = Method3
+    } = test_recv_coap_response(UdpSock),
+    {ok, deleted} = Method3,
     MsgId3 = RspId3,
     timer:sleep(50),
     false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()).
@@ -255,18 +255,22 @@ case01_register_additional_opts(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
 
-    AddOpts = "ep=~ts<=345&lwm2m=1&apn=psmA.eDRX0.ctnb&cust_opt=shawn&"
-              "im=123&ct=1.4&mt=mdm9620&mv=1.2",
+    AddOpts =
+        "ep=~ts<=345&lwm2m=1&apn=psmA.eDRX0.ctnb&cust_opt=shawn&"
+        "im=123&ct=1.4&mt=mdm9620&mv=1.2",
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
 
     %% checkpoint 1 - response
     #coap_message{type = Type, method = Method, id = RspId, options = Opts} =
@@ -288,16 +292,19 @@ case01_register_additional_opts(Config) ->
     ?LOGT("start to send DE-REGISTER command", []),
     MsgId3 = 52,
     test_send_coap_request(
-      UdpSock,
-      delete,
-      sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
-      #coap_content{payload = <<>>},
-      [],
-      MsgId3),
-    #coap_message{type = ack,
-                  id = RspId3,
-                  method = Method3} = test_recv_coap_response(UdpSock),
-    {ok,deleted} = Method3,
+        UdpSock,
+        delete,
+        sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
+        #coap_content{payload = <<>>},
+        [],
+        MsgId3
+    ),
+    #coap_message{
+        type = ack,
+        id = RspId3,
+        method = Method3
+    } = test_recv_coap_response(UdpSock),
+    {ok, deleted} = Method3,
     MsgId3 = RspId3,
     timer:sleep(50),
     false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()).
@@ -310,21 +317,23 @@ case01_register_incorrect_opts(Config) ->
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
 
-
     AddOpts = "ep=~ts<=345&lwm2m=1&incorrect_opt",
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
 
     %% checkpoint 1 - response
     #coap_message{type = ack, method = Method, id = MsgId} =
         test_recv_coap_response(UdpSock),
-    ?assertEqual({error,bad_request}, Method).
+    ?assertEqual({error, bad_request}, Method).
 
 case01_register_report(Config) ->
     %%----------------------------------------
@@ -333,19 +342,22 @@ case01_register_report(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
-    ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
+    ReportTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0),
     timer:sleep(200),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
 
     #coap_message{type = Type, method = Method, id = RspId, options = Opts} =
         test_recv_coap_response(UdpSock),
@@ -359,15 +371,23 @@ case01_register_report(Config) ->
     true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()),
 
     ReadResult = emqx_json:encode(
-                   #{<<"msgType">> => <<"register">>,
-                     <<"data">> => #{
-                         <<"alternatePath">> => <<"/">>,
-                         <<"ep">> => list_to_binary(Epn),
-                         <<"lt">> => 345,
-                         <<"lwm2m">> => <<"1">>,
-                         <<"objectList">> => [<<"/1">>, <<"/2">>,
-                                              <<"/3">>, <<"/4">>, <<"/5">>]
-                     }}),
+        #{
+            <<"msgType">> => <<"register">>,
+            <<"data">> => #{
+                <<"alternatePath">> => <<"/">>,
+                <<"ep">> => list_to_binary(Epn),
+                <<"lt">> => 345,
+                <<"lwm2m">> => <<"1">>,
+                <<"objectList">> => [
+                    <<"/1">>,
+                    <<"/2">>,
+                    <<"/3">>,
+                    <<"/4">>,
+                    <<"/5">>
+                ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)),
 
     %%----------------------------------------
@@ -376,16 +396,19 @@ case01_register_report(Config) ->
     ?LOGT("start to send DE-REGISTER command", []),
     MsgId3 = 52,
     test_send_coap_request(
-      UdpSock,
-      delete,
-      sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
-      #coap_content{payload = <<>>},
-      [],
-      MsgId3),
-    #coap_message{type = ack,
-                  id = RspId3,
-                  method = Method3} = test_recv_coap_response(UdpSock),
-    {ok,deleted} = Method3,
+        UdpSock,
+        delete,
+        sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
+        #coap_content{payload = <<>>},
+        [],
+        MsgId3
+    ),
+    #coap_message{
+        type = ack,
+        id = RspId3,
+        method = Method3
+    } = test_recv_coap_response(UdpSock),
+    {ok, deleted} = Method3,
     MsgId3 = RspId3,
     timer:sleep(50),
     false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()).
@@ -397,37 +420,50 @@ case02_update_deregister(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
-    ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
+    ReportTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0),
     timer:sleep(200),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
     timer:sleep(100),
-    #coap_message{type = ack,
-                  method = Method,
-                  options = Opts} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method),
+    #coap_message{
+        type = ack,
+        method = Method,
+        options = Opts
+    } = test_recv_coap_response(UdpSock),
+    ?assertEqual({ok, created}, Method),
 
     ?LOGT("Options got: ~p", [Opts]),
     Location = maps:get(location_path, Opts),
     Register = emqx_json:encode(
-                 #{<<"msgType">> => <<"register">>,
-                   <<"data">> => #{
-                       <<"alternatePath">> => <<"/">>,
-                       <<"ep">> => list_to_binary(Epn),
-                       <<"lt">> => 345,
-                       <<"lwm2m">> => <<"1">>,
-                       <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>,
-                                            <<"/4">>, <<"/5">>]
-                  }}),
+        #{
+            <<"msgType">> => <<"register">>,
+            <<"data">> => #{
+                <<"alternatePath">> => <<"/">>,
+                <<"ep">> => list_to_binary(Epn),
+                <<"lt">> => 345,
+                <<"lwm2m">> => <<"1">>,
+                <<"objectList">> => [
+                    <<"/1">>,
+                    <<"/2">>,
+                    <<"/3">>,
+                    <<"/4">>,
+                    <<"/5">>
+                ]
+            }
+        }
+    ),
     ?assertEqual(Register, test_recv_mqtt_response(ReportTopic)),
 
     %%----------------------------------------
@@ -436,28 +472,42 @@ case02_update_deregister(Config) ->
     ?LOGT("start to send UPDATE command", []),
     MsgId2 = 27,
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b~ts?lt=789", [?PORT, join_path(Location, <<>>)]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , , ">>},
-      [],
-      MsgId2),
-    #coap_message{type = ack,
-                  id = RspId2,
-                  method = Method2} = test_recv_coap_response(UdpSock),
-    {ok,changed} = Method2,
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b~ts?lt=789", [?PORT, join_path(Location, <<>>)]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , , ">>
+        },
+        [],
+        MsgId2
+    ),
+    #coap_message{
+        type = ack,
+        id = RspId2,
+        method = Method2
+    } = test_recv_coap_response(UdpSock),
+    {ok, changed} = Method2,
     MsgId2 = RspId2,
     Update = emqx_json:encode(
-               #{<<"msgType">> => <<"update">>,
-                 <<"data">> => #{
-                    <<"alternatePath">> => <<"/">>,
-                    <<"ep">> => list_to_binary(Epn),
-                    <<"lt">> => 789,
-                    <<"lwm2m">> => <<"1">>,
-                    <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>,
-                                         <<"/4">>, <<"/5">>, <<"/6">>]
-                }}),
+        #{
+            <<"msgType">> => <<"update">>,
+            <<"data">> => #{
+                <<"alternatePath">> => <<"/">>,
+                <<"ep">> => list_to_binary(Epn),
+                <<"lt">> => 789,
+                <<"lwm2m">> => <<"1">>,
+                <<"objectList">> => [
+                    <<"/1">>,
+                    <<"/2">>,
+                    <<"/3">>,
+                    <<"/4">>,
+                    <<"/5">>,
+                    <<"/6">>
+                ]
+            }
+        }
+    ),
     ?assertEqual(Update, test_recv_mqtt_response(ReportTopic)),
 
     %%----------------------------------------
@@ -466,16 +516,19 @@ case02_update_deregister(Config) ->
     ?LOGT("start to send DE-REGISTER command", []),
     MsgId3 = 52,
     test_send_coap_request(
-      UdpSock,
-      delete,
-      sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
-      #coap_content{payload = <<>>},
-      [],
-      MsgId3),
-    #coap_message{type = ack,
-                  id = RspId3,
-                  method = Method3} = test_recv_coap_response(UdpSock),
-    {ok,deleted} = Method3,
+        UdpSock,
+        delete,
+        sprintf("coap://127.0.0.1:~b~ts", [?PORT, join_path(Location, <<>>)]),
+        #coap_content{payload = <<>>},
+        [],
+        MsgId3
+    ),
+    #coap_message{
+        type = ack,
+        id = RspId3,
+        method = Method3
+    } = test_recv_coap_response(UdpSock),
+    {ok, deleted} = Method3,
     MsgId3 = RspId3,
 
     timer:sleep(50),
@@ -488,15 +541,18 @@ case03_register_wrong_version(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=8.3", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=8.3", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
     #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock),
     ?assertEqual({error, bad_request}, Method),
     timer:sleep(50),
@@ -510,19 +566,22 @@ case04_register_and_lifetime_timeout(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=2&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=2&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
     timer:sleep(100),
     #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method),
+    ?assertEqual({ok, created}, Method),
 
     true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()),
 
@@ -541,15 +600,18 @@ case05_register_wrong_epn(Config) ->
     UdpSock = ?config(sock, Config),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?lt=345&lwm2m=1.0", [?PORT]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?lt=345&lwm2m=1.0", [?PORT]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId
+    ),
     #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock),
-    ?assertEqual({error,bad_request}, Method).
+    ?assertEqual({error, bad_request}, Method).
 
 %% case06_register_wrong_lifetime(Config) ->
 %%     %%----------------------------------------
@@ -580,20 +642,25 @@ case07_register_alternate_path_01(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
-    ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
+    ReportTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0),
     timer:sleep(200),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId
+    ),
     timer:sleep(50),
     true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()).
 
@@ -604,20 +671,25 @@ case07_register_alternate_path_02(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
-    ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
+    ReportTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0),
     timer:sleep(200),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId
+    ),
     timer:sleep(50),
     true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()).
 
@@ -628,45 +700,58 @@ case08_reregister(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId = 12,
-    SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"),
-    ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    SubTopic = list_to_binary("lwm2m/" ++ Epn ++ "/dn/#"),
+    ReportTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0),
     timer:sleep(200),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId
+    ),
     timer:sleep(50),
     true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()),
 
     ReadResult = emqx_json:encode(
-                   #{<<"msgType">> => <<"register">>,
-                     <<"data">> => #{
-                        <<"alternatePath">> => <<"/lwm2m">>,
-                        <<"ep">> => list_to_binary(Epn),
-                        <<"lt">> => 345,
-                        <<"lwm2m">> => <<"1">>,
-                        <<"objectList">> => [<<"/1/0">>, <<"/2/0">>, <<"/3/0">>]
-                     }}),
+        #{
+            <<"msgType">> => <<"register">>,
+            <<"data">> => #{
+                <<"alternatePath">> => <<"/lwm2m">>,
+                <<"ep">> => list_to_binary(Epn),
+                <<"lt">> => 345,
+                <<"lwm2m">> => <<"1">>,
+                <<"objectList">> => [<<"/1/0">>, <<"/2/0">>, <<"/3/0">>]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)),
     timer:sleep(1000),
 
     %% the same lwm2mc client registers to server again
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId + 1),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId + 1
+    ),
 
     %% verify the lwm2m client is still online
     ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)).
@@ -675,41 +760,49 @@ case10_read(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId1 = 15,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
     %% step 1, device register ...
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId1),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId1
+    ),
     #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method1),
+    ?assertEqual({ok, created}, Method1),
     test_recv_mqtt_response(RespTopic),
 
     %% step2,  send a READ command to device
     CmdId = 206,
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
-    Command =   #{
-                  <<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                  <<"msgType">> => <<"read">>,
-                  <<"data">> => #{
-                                  <<"path">> => <<"/3/0/0">>
-                                 }
-                 },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/0">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     ?LOGT("CommandJson=~p", [CommandJson]),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options=Options2,
-                  payload=Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     ?LOGT("LwM2M client got ~p", [Request2]),
 
     ?assertEqual(get, Method2),
@@ -718,28 +811,37 @@ case10_read(Config) ->
     timer:sleep(50),
 
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<"EMQ">>},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<"EMQ">>
+        },
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"read">>,
-                     <<"data">> => #{
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"reqPath">> => <<"/3/0/0">>,
-                        <<"content">> => [#{path => <<"/3/0/0">>,
-                                            value => <<"EMQ">>}
-                                         ]
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"read">>,
+            <<"data">> => #{
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"reqPath">> => <<"/3/0/0">>,
+                <<"content">> => [
+                    #{
+                        path => <<"/3/0/0">>,
+                        value => <<"EMQ">>
+                    }
+                ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case10_read_separate_ack(Config) ->
@@ -747,7 +849,7 @@ case10_read_separate_ack(Config) ->
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId1 = 15,
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
 
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
@@ -759,19 +861,23 @@ case10_read_separate_ack(Config) ->
     CmdId = 206,
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     Command = #{
-                <<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"read">>,
-                <<"data">> => #{
-                                <<"path">> => <<"/3/0/0">>
-                               }
-               },
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/0">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     ?LOGT("CommandJson=~p", [CommandJson]),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2, payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     ?LOGT("LwM2M client got ~p", [Request2]),
 
     ?assertEqual(get, Method2),
@@ -780,35 +886,48 @@ case10_read_separate_ack(Config) ->
 
     test_send_empty_ack(UdpSock, "127.0.0.1", ?PORT, Request2),
     ReadResultACK = emqx_json:encode(
-                      #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                        <<"msgType">> => <<"ack">>,
-                        <<"data">> => #{ <<"path">> => <<"/3/0/0">> }
-                       }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"ack">>,
+            <<"data">> => #{<<"path">> => <<"/3/0/0">>}
+        }
+    ),
     ?assertEqual(ReadResultACK, test_recv_mqtt_response(RespTopic)),
     timer:sleep(100),
 
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<"EMQ">>},
-      Request2,
-      false),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<"EMQ">>
+        },
+        Request2,
+        false
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"read">>,
-                     <<"data">> => #{
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"reqPath">> => <<"/3/0/0">>,
-                        <<"content">> => [#{path => <<"/3/0/0">>,
-                                            value => <<"EMQ">>}]
-                     }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"read">>,
+            <<"data">> => #{
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"reqPath">> => <<"/3/0/0">>,
+                <<"content">> => [
+                    #{
+                        path => <<"/3/0/0">>,
+                        value => <<"EMQ">>
+                    }
+                ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case11_read_object_tlv(Config) ->
@@ -817,7 +936,7 @@ case11_read_object_tlv(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -826,13 +945,14 @@ case11_read_object_tlv(Config) ->
     %% step2,  send a READ command to device
     CmdId = 207,
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
-    Command =   #{
-                  <<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                  <<"msgType">> => <<"read">>,
-                  <<"data">> => #{
-                                  <<"path">> => <<"/3/0">>
-                                 }
-                 },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     ?LOGT("CommandJson=~p", [CommandJson]),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
@@ -844,41 +964,54 @@ case11_read_object_tlv(Config) ->
     ?assertEqual(get, Method2),
     timer:sleep(50),
 
-    Tlv = <<16#08, 16#00, 16#3C, 16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65,
-            16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20,
-            16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8,
-            16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65,
-            16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20,
-            16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09,
-            16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33>>,
+    Tlv =
+        <<16#08, 16#00, 16#3C, 16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F,
+            16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63,
+            16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69,
+            16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65,
+            16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31,
+            16#32, 16#33>>,
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"application/vnd.oma.lwm2m+tlv">>,
-                    payload = Tlv},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"application/vnd.oma.lwm2m+tlv">>,
+            payload = Tlv
+        },
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"read">>,
-                     <<"data">> => #{
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"reqPath">> => <<"/3/0">>,
-                        <<"content">> =>
-                            [#{path => <<"/3/0/0">>,
-                               value => <<"Open Mobile Alliance">>},
-                             #{path => <<"/3/0/1">>,
-                               value => <<"Lightweight M2M Client">>},
-                             #{path => <<"/3/0/2">>,
-                               value => <<"345000123">>}
-                             ]
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"read">>,
+            <<"data">> => #{
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"reqPath">> => <<"/3/0">>,
+                <<"content">> =>
+                    [
+                        #{
+                            path => <<"/3/0/0">>,
+                            value => <<"Open Mobile Alliance">>
+                        },
+                        #{
+                            path => <<"/3/0/1">>,
+                            value => <<"Lightweight M2M Client">>
+                        },
+                        #{
+                            path => <<"/3/0/2">>,
+                            value => <<"345000123">>
+                        }
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case11_read_object_json(Config) ->
@@ -887,7 +1020,7 @@ case11_read_object_json(Config) ->
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId1 = 15,
 
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     ObjectList = <<", , , , ">>,
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
@@ -897,13 +1030,14 @@ case11_read_object_json(Config) ->
     %% step2,  send a READ command to device
     CmdId = 206,
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
-    Command =   #{
-                  <<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                  <<"msgType">> => <<"read">>,
-                  <<"data">> => #{
-                                  <<"path">> => <<"/3/0">>
-                                 }
-                 },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     ?LOGT("CommandJson=~p", [CommandJson]),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
@@ -915,37 +1049,52 @@ case11_read_object_json(Config) ->
     ?assertEqual(get, Method2),
     timer:sleep(50),
 
-    Json = <<"{\"bn\":\"/3/0\",\"e\":[{\"n\":\"0\",\"sv\":\"Open Mobile "
-             "Alliance\"},{\"n\":\"1\",\"sv\":\"Lightweight M2M Client\"},"
-             "{\"n\":\"2\",\"sv\":\"345000123\"}]}">>,
+    Json = <<
+        "{\"bn\":\"/3/0\",\"e\":[{\"n\":\"0\",\"sv\":\"Open Mobile "
+        "Alliance\"},{\"n\":\"1\",\"sv\":\"Lightweight M2M Client\"},"
+        "{\"n\":\"2\",\"sv\":\"345000123\"}]}"
+    >>,
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"application/vnd.oma.lwm2m+json">>,
-                    payload = Json},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"application/vnd.oma.lwm2m+json">>,
+            payload = Json
+        },
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"read">>,
-                     <<"data">> => #{
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"reqPath">> => <<"/3/0">>,
-                        <<"content">> =>
-                            [#{path => <<"/3/0/0">>,
-                               value => <<"Open Mobile Alliance">>},
-                             #{path => <<"/3/0/1">>,
-                               value => <<"Lightweight M2M Client">>},
-                             #{path => <<"/3/0/2">>,
-                               value => <<"345000123">>}
-                            ]
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"read">>,
+            <<"data">> => #{
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"reqPath">> => <<"/3/0">>,
+                <<"content">> =>
+                    [
+                        #{
+                            path => <<"/3/0/0">>,
+                            value => <<"Open Mobile Alliance">>
+                        },
+                        #{
+                            path => <<"/3/0/1">>,
+                            value => <<"Lightweight M2M Client">>
+                        },
+                        #{
+                            path => <<"/3/0/2">>,
+                            value => <<"345000123">>
+                        }
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case12_read_resource_opaque(Config) ->
@@ -954,7 +1103,7 @@ case12_read_resource_opaque(Config) ->
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId1 = 15,
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -963,13 +1112,14 @@ case12_read_resource_opaque(Config) ->
     %% step2,  send a READ command to device
     CmdId = 206,
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
-    Command =   #{
-                  <<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                  <<"msgType">> => <<"read">>,
-                  <<"data">> => #{
-                                  <<"path">> => <<"/3/0/8">>
-                                 }
-                 },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/8">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     ?LOGT("CommandJson=~p", [CommandJson]),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
@@ -983,28 +1133,38 @@ case12_read_resource_opaque(Config) ->
 
     Opaque = <<20, 21, 22, 23>>,
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"application/octet-stream">>,
-                    payload = Opaque},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"application/octet-stream">>,
+            payload = Opaque
+        },
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"read">>,
-                     <<"data">> => #{
-                         <<"code">> => <<"2.05">>,
-                         <<"codeMsg">> => <<"content">>,
-                         <<"reqPath">> => <<"/3/0/8">>,
-                         <<"content">> =>
-                            [#{path => <<"/3/0/8">>,
-                               value => base64:encode(Opaque)}
-                            ]
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"read">>,
+            <<"data">> => #{
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"reqPath">> => <<"/3/0/8">>,
+                <<"content">> =>
+                    [
+                        #{
+                            path => <<"/3/0/8">>,
+                            value => base64:encode(Opaque)
+                        }
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case13_read_no_xml(Config) ->
@@ -1013,7 +1173,7 @@ case13_read_no_xml(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1022,11 +1182,12 @@ case13_read_no_xml(Config) ->
     %% step2,  send a READ command to device
     CmdId = 206,
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
-    Command =   #{<<"requestID">> => CmdId,
-                  <<"cacheID">> => CmdId,
-                  <<"msgType">> => <<"read">>,
-                  <<"data">> => #{ <<"path">> => <<"/9723/0/0">> }
-                 },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{<<"path">> => <<"/9723/0/0">>}
+    },
     CommandJson = emqx_json:encode(Command),
     ?LOGT("CommandJson=~p", [CommandJson]),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
@@ -1039,25 +1200,31 @@ case13_read_no_xml(Config) ->
     timer:sleep(50),
 
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<"EMQ">>},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<"EMQ">>
+        },
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"read">>,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/9723/0/0">>,
-                        <<"code">> => <<"4.00">>,
-                        <<"codeMsg">> => <<"bad_request">>
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"read">>,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/9723/0/0">>,
+                <<"code">> => <<"4.00">>,
+                <<"codeMsg">> => <<"bad_request">>
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case20_single_write(Config) ->
@@ -1066,7 +1233,7 @@ case20_single_write(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1075,20 +1242,25 @@ case20_single_write(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"write">>,
-                <<"data">> => #{
-                                <<"path">> => <<"/3/0/13">>,
-                                <<"type">> => <<"Integer">>,
-                                <<"value">> => <<"12345">>
-                               }
-               },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"write">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/13">>,
+            <<"type">> => <<"Integer">>,
+            <<"value">> => <<"12345">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2, payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(put, Method2),
     ?assertEqual(<<"/3/0/13">>, Path2),
@@ -1096,19 +1268,29 @@ case20_single_write(Config) ->
     ?assertEqual(Tlv_Value, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {ok, changed}, #coap_content{}, Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, changed},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/13">>,
-                        <<"code">> => <<"2.04">>,
-                        <<"codeMsg">> => <<"changed">>},
-                     <<"msgType">> => <<"write">>
-                    }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/13">>,
+                <<"code">> => <<"2.04">>,
+                <<"codeMsg">> => <<"changed">>
+            },
+            <<"msgType">> => <<"write">>
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case20_write(Config) ->
@@ -1117,7 +1299,7 @@ case20_write(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1126,41 +1308,60 @@ case20_write(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId,
-                <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"write">>,
-                <<"data">> => #{
-                    <<"basePath">> => <<"/3/0/13">>,
-                    <<"content">> =>
-                        [#{type => <<"Float">>,
-                           value => <<"12345.0">>}]
-               }},
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"write">>,
+        <<"data">> => #{
+            <<"basePath">> => <<"/3/0/13">>,
+            <<"content">> =>
+                [
+                    #{
+                        type => <<"Float">>,
+                        value => <<"12345.0">>
+                    }
+                ]
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(put, Method2),
     ?assertEqual(<<"/3/0/13">>, Path2),
-    Tlv_Value = <<200, 13, 8, 64,200,28,128,0,0,0,0>>,
+    Tlv_Value = <<200, 13, 8, 64, 200, 28, 128, 0, 0, 0, 0>>,
     ?assertEqual(Tlv_Value, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {ok, changed}, #coap_content{}, Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, changed},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     WriteResult = emqx_json:encode(
-                    #{<<"requestID">> => CmdId,
-                      <<"cacheID">> => CmdId,
-                      <<"data">> => #{
-                          <<"reqPath">> => <<"/3/0/13">>,
-                          <<"code">> => <<"2.04">>,
-                          <<"codeMsg">> => <<"changed">> },
-                      <<"msgType">> => <<"write">>}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/13">>,
+                <<"code">> => <<"2.04">>,
+                <<"codeMsg">> => <<"changed">>
+            },
+            <<"msgType">> => <<"write">>
+        }
+    ),
     ?assertEqual(WriteResult, test_recv_mqtt_response(RespTopic)).
 
 case21_write_object(Config) ->
@@ -1169,7 +1370,7 @@ case21_write_object(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1178,47 +1379,66 @@ case21_write_object(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId,
-                <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"write">>,
-                <<"data">> => #{
-                    <<"basePath">> => <<"/3/0/">>,
-                    <<"content">> =>
-                        [#{path => <<"13">>,
-                           type => <<"Integer">>,
-                           value => <<"12345">>},
-                         #{path => <<"14">>,
-                           type => <<"String">>,
-                           value => <<"87x">>}]
-               }},
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"write">>,
+        <<"data">> => #{
+            <<"basePath">> => <<"/3/0/">>,
+            <<"content">> =>
+                [
+                    #{
+                        path => <<"13">>,
+                        type => <<"Integer">>,
+                        value => <<"12345">>
+                    },
+                    #{
+                        path => <<"14">>,
+                        type => <<"String">>,
+                        value => <<"87x">>
+                    }
+                ]
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload=Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(post, Method2),
     ?assertEqual(<<"/3/0">>, Path2),
-    Tlv_Value = <<3:2, 0:1, 0:2, 2:3, 13, 12345:16,
-                  3:2, 0:1, 0:2, 3:3, 14, "87x">>,
+    Tlv_Value = <<3:2, 0:1, 0:2, 2:3, 13, 12345:16, 3:2, 0:1, 0:2, 3:3, 14, "87x">>,
     ?assertEqual(Tlv_Value, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {ok, changed}, #coap_content{}, Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, changed},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"write">>,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/">>,
-                        <<"code">> => <<"2.04">>,
-                        <<"codeMsg">> => <<"changed">>
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"write">>,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/">>,
+                <<"code">> => <<"2.04">>,
+                <<"codeMsg">> => <<"changed">>
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case22_write_error(Config) ->
@@ -1227,7 +1447,7 @@ case22_write_error(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1236,38 +1456,54 @@ case22_write_error(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"write">>,
-                <<"data">> => #{
-                    <<"basePath">> => <<"/3/0/1">>,
-                    <<"content">> =>
-                        [#{type => <<"Integer">>,
-                           value => <<"12345">>}]
-               }},
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"write">>,
+        <<"data">> => #{
+            <<"basePath">> => <<"/3/0/1">>,
+            <<"content">> =>
+                [
+                    #{
+                        type => <<"Integer">>,
+                        value => <<"12345">>
+                    }
+                ]
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2, options=Options2} = Request2,
+    #coap_message{method = Method2, options = Options2} = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(put, Method2),
     ?assertEqual(<<"/3/0/1">>, Path2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {error, bad_request}, #coap_content{},
-                            Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {error, bad_request},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/1">>,
-                        <<"code">> => <<"4.00">>,
-                        <<"codeMsg">> => <<"bad_request">>},
-                     <<"msgType">> => <<"write">>
-                    }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/1">>,
+                <<"code">> => <<"4.00">>,
+                <<"codeMsg">> => <<"bad_request">>
+            },
+            <<"msgType">> => <<"write">>
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case_create_basic(Config) ->
@@ -1276,7 +1512,7 @@ case_create_basic(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1285,37 +1521,53 @@ case_create_basic(Config) ->
     %% step2, send a CREATE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"msgType">> => <<"create">>,
-                <<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"data">> => #{<<"content">> => [],
-                                <<"basePath">> => <<"/5">>
-                               }},
+    Command = #{
+        <<"msgType">> => <<"create">>,
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"data">> => #{
+            <<"content">> => [],
+            <<"basePath">> => <<"/5">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(post, Method2),
     ?assertEqual(<<"/5">>, Path2),
     ?assertEqual(<<"">>, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {ok, created}, #coap_content{}, Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, created},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/5">>,
-                        <<"code">> => <<"2.01">>,
-                        <<"codeMsg">> => <<"created">>},
-                     <<"msgType">> => <<"create">>
-                    }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/5">>,
+                <<"code">> => <<"2.01">>,
+                <<"codeMsg">> => <<"created">>
+            },
+            <<"msgType">> => <<"create">>
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case_delete_basic(Config) ->
@@ -1324,7 +1576,7 @@ case_delete_basic(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1333,36 +1585,50 @@ case_delete_basic(Config) ->
     %% step2, send a CREATE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"delete">>,
-                <<"data">> => #{ <<"path">> => <<"/5/0">> }
-               },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"delete">>,
+        <<"data">> => #{<<"path">> => <<"/5/0">>}
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(delete, Method2),
     ?assertEqual(<<"/5/0">>, Path2),
     ?assertEqual(<<"">>, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {ok, deleted}, #coap_content{}, Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, deleted},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/5/0">>,
-                        <<"code">> => <<"2.02">>,
-                        <<"codeMsg">> => <<"deleted">>},
-                     <<"msgType">> => <<"delete">>
-                    }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/5/0">>,
+                <<"code">> => <<"2.02">>,
+                <<"codeMsg">> => <<"deleted">>
+            },
+            <<"msgType">> => <<"delete">>
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case30_execute(Config) ->
@@ -1371,7 +1637,7 @@ case30_execute(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1380,41 +1646,55 @@ case30_execute(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId,
-                <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"execute">>,
-                <<"data">> => #{
-                    <<"path">> => <<"/3/0/4">>,
-                    %% "args" should not be present for "/3/0/4", only for
-                    %% testing the encoding here
-                    <<"args">> => <<"2,7">>}
-               },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"execute">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/4">>,
+            %% "args" should not be present for "/3/0/4", only for
+            %% testing the encoding here
+            <<"args">> => <<"2,7">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(post, Method2),
     ?assertEqual(<<"/3/0/4">>, Path2),
     ?assertEqual(<<"2,7">>, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {ok, changed}, #coap_content{}, Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, changed},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/4">>,
-                        <<"code">> => <<"2.04">>,
-                        <<"codeMsg">> => <<"changed">>},
-                     <<"msgType">> => <<"execute">>
-                    }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/4">>,
+                <<"code">> => <<"2.04">>,
+                <<"codeMsg">> => <<"changed">>
+            },
+            <<"msgType">> => <<"execute">>
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case31_execute_error(Config) ->
@@ -1423,7 +1703,7 @@ case31_execute_error(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1432,39 +1712,53 @@ case31_execute_error(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"execute">>,
-                <<"data">> => #{
-                    <<"path">> => <<"/3/0/4">>,
-                    <<"args">> => <<"2,7">>}
-               },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"execute">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/4">>,
+            <<"args">> => <<"2,7">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(post, Method2),
     ?assertEqual(<<"/3/0/4">>, Path2),
     ?assertEqual(<<"2,7">>, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {error, unauthorized}, #coap_content{},
-                            Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {error, unauthorized},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId,
-                     <<"cacheID">> => CmdId,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/4">>,
-                        <<"code">> => <<"4.01">>,
-                        <<"codeMsg">> => <<"unauthorized">>},
-                    <<"msgType">> => <<"execute">>
-                   }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/4">>,
+                <<"code">> => <<"4.01">>,
+                <<"codeMsg">> => <<"unauthorized">>
+            },
+            <<"msgType">> => <<"execute">>
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case40_discover(Config) ->
@@ -1473,7 +1767,7 @@ case40_discover(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1482,18 +1776,23 @@ case40_discover(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"discover">>,
-                <<"data">> => #{
-                                <<"path">> => <<"/3/0/7">>
-                               } },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"discover">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/7">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     ?assertEqual(get, Method2),
     ?assertEqual(<<"/3/0/7">>, Path2),
@@ -1502,27 +1801,36 @@ case40_discover(Config) ->
 
     PayloadDiscover = <<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2,">>,
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"application/link-format">>,
-                    payload = PayloadDiscover},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"application/link-format">>,
+            payload = PayloadDiscover
+        },
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"discover">>,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/7">>,
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"content">> =>
-                            [<<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2">>,
-                             <<"">>]
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"discover">>,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/7">>,
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"content">> =>
+                    [
+                        <<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2">>,
+                        <<"">>
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case50_write_attribute(Config) ->
@@ -1531,7 +1839,7 @@ case50_write_attribute(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -1540,45 +1848,65 @@ case50_write_attribute(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"write-attr">>,
-                <<"data">> => #{
-                                <<"path">> => <<"/3/0/9">>,
-                                <<"pmin">> => <<"1">>,
-                                <<"pmax">> => <<"5">>,
-                                <<"lt">> => <<"5">>
-                               } },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"write-attr">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/9">>,
+            <<"pmin">> => <<"1">>,
+            <<"pmax">> => <<"5">>,
+            <<"lt">> => <<"5">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(100),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     ?LOGT("got options: ~p", [Options2]),
     Path2 = get_coap_path(Options2),
     Query2 = lists:sort(maps:to_list(get_coap_query(Options2))),
     ?assertEqual(put, Method2),
     ?assertEqual(<<"/3/0/9">>, Path2),
-    ?assertEqual(lists:sort([{<<"pmax">>, <<"5">>},
-                             {<<"lt">>, <<"5">>},
-                             {<<"pmin">>,<<"1">>}]), Query2),
+    ?assertEqual(
+        lists:sort([
+            {<<"pmax">>, <<"5">>},
+            {<<"lt">>, <<"5">>},
+            {<<"pmin">>, <<"1">>}
+        ]),
+        Query2
+    ),
     ?assertEqual(<<>>, Payload2),
     timer:sleep(50),
 
-    test_send_coap_response(UdpSock, "127.0.0.1", ?PORT,
-                            {ok, changed}, #coap_content{},
-                            Request2, true),
+    test_send_coap_response(
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, changed},
+        #coap_content{},
+        Request2,
+        true
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/9">>,
-                        <<"code">> => <<"2.04">>,
-                        <<"codeMsg">> => <<"changed">>},
-                     <<"msgType">> => <<"write-attr">>
-                    }),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/9">>,
+                <<"code">> => <<"2.04">>,
+                <<"codeMsg">> => <<"changed">>
+            },
+            <<"msgType">> => <<"write-attr">>
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 case60_observe(Config) ->
@@ -1587,8 +1915,8 @@ case60_observe(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
-    RespTopicAD = list_to_binary("lwm2m/"++Epn++"/up/notify"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
+    RespTopicAD = list_to_binary("lwm2m/" ++ Epn ++ "/up/notify"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     emqtt:subscribe(?config(emqx_c, Config), RespTopicAD, qos0),
     timer:sleep(200),
@@ -1598,17 +1926,21 @@ case60_observe(Config) ->
     %% step2,  send a OBSERVE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"observe">>,
-                <<"data">> => #{<<"path">> => <<"/3/0/10">>}
-               },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"observe">>,
+        <<"data">> => #{<<"path">> => <<"/3/0/10">>}
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50),
     Request2 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method2,
-                  options = Options2,
-                  payload = Payload2} = Request2,
+    #coap_message{
+        method = Method2,
+        options = Options2,
+        payload = Payload2
+    } = Request2,
     Path2 = get_coap_path(Options2),
     Observe = get_coap_observe(Options2),
     ?assertEqual(get, Method2),
@@ -1618,69 +1950,91 @@ case60_observe(Config) ->
     timer:sleep(50),
 
     test_send_coap_observe_ack(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"text/plain">>, payload = <<"2048">>},
-      Request2),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{content_format = <<"text/plain">>, payload = <<"2048">>},
+        Request2
+    ),
     timer:sleep(100),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"observe">>,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/10">>,
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"content">> =>
-                            [#{path => <<"/3/0/10">>,
-                               value => 2048}]
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"observe">>,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/10">>,
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"content">> =>
+                    [
+                        #{
+                            path => <<"/3/0/10">>,
+                            value => 2048
+                        }
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)),
 
     %% step3 the notifications
     timer:sleep(200),
     ObSeq = 3,
     test_send_coap_notif(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      #coap_content{content_format = <<"text/plain">>, payload = <<"4096">>},
-      ObSeq,
-      Request2),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        #coap_content{content_format = <<"text/plain">>, payload = <<"4096">>},
+        ObSeq,
+        Request2
+    ),
     timer:sleep(100),
     #coap_message{} = test_recv_coap_response(UdpSock),
 
     ReadResult2 = emqx_json:encode(
-                    #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                      <<"msgType">> => <<"notify">>,
-                      <<"seqNum">> => ObSeq,
-                      <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/10">>,
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"content">> =>
-                            [#{path => <<"/3/0/10">>,
-                               value => 4096}]
-                     }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"notify">>,
+            <<"seqNum">> => ObSeq,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/10">>,
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"content">> =>
+                    [
+                        #{
+                            path => <<"/3/0/10">>,
+                            value => 4096
+                        }
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult2, test_recv_mqtt_response(RespTopicAD)),
 
     %% Step3. cancel observe
     CmdId3 = 308,
-    Command3 = #{<<"requestID">> => CmdId3, <<"cacheID">> => CmdId3,
-                 <<"msgType">> => <<"cancel-observe">>,
-                 <<"data">> => #{
-                                 <<"path">> => <<"/3/0/10">>
-                                }
-                },
+    Command3 = #{
+        <<"requestID">> => CmdId3,
+        <<"cacheID">> => CmdId3,
+        <<"msgType">> => <<"cancel-observe">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/10">>
+        }
+    },
     CommandJson3 = emqx_json:encode(Command3),
     test_mqtt_broker:publish(CommandTopic, CommandJson3, 0),
     timer:sleep(50),
     Request3 = test_recv_coap_request(UdpSock),
-    #coap_message{method = Method3,
-                  options = Options3,
-                  payload = Payload3} = Request3,
+    #coap_message{
+        method = Method3,
+        options = Options3,
+        payload = Payload3
+    } = Request3,
     Path3 = get_coap_path(Options3),
     Observe3 = get_coap_observe(Options3),
     ?assertEqual(get, Method3),
@@ -1690,26 +2044,34 @@ case60_observe(Config) ->
     timer:sleep(50),
 
     test_send_coap_observe_ack(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"text/plain">>, payload = <<"1150">>},
-      Request3),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{content_format = <<"text/plain">>, payload = <<"1150">>},
+        Request3
+    ),
     timer:sleep(100),
 
     ReadResult3 = emqx_json:encode(
-                    #{<<"requestID">> => CmdId3,
-                      <<"cacheID">> => CmdId3,
-                      <<"msgType">> => <<"cancel-observe">>,
-                      <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/10">>,
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"content">> =>
-                            [#{path => <<"/3/0/10">>,
-                               value => 1150}]
-                     }}),
+        #{
+            <<"requestID">> => CmdId3,
+            <<"cacheID">> => CmdId3,
+            <<"msgType">> => <<"cancel-observe">>,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/10">>,
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"content">> =>
+                    [
+                        #{
+                            path => <<"/3/0/10">>,
+                            value => 1150
+                        }
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult3, test_recv_mqtt_response(RespTopic)).
 
 %% case80_specail_object_19_0_0_notify(Config) ->
@@ -1859,22 +2221,27 @@ server_cache_mode(Config, RegOption) ->
 
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?"++RegOption, [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<", , , , ">>},
-      [],
-      MsgId1),
-    #coap_message{type = ack,
-                  method = Method1,
-                  options = Opts} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method1),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?" ++ RegOption, [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<", , , , ">>
+        },
+        [],
+        MsgId1
+    ),
+    #coap_message{
+        type = ack,
+        method = Method1,
+        options = Opts
+    } = test_recv_coap_response(UdpSock),
+    ?assertEqual({ok, created}, Method1),
     ?LOGT("Options got: ~p", [Opts]),
     Location = maps:get(location_path, Opts),
     test_recv_mqtt_response(RespTopic),
@@ -1891,8 +2258,10 @@ server_cache_mode(Config, RegOption) ->
     send_read_command_1(2, UdpSock),
     send_read_command_1(3, UdpSock),
 
-    ?assertEqual(timeout_test_recv_coap_request,
-                 test_recv_coap_request(UdpSock)),
+    ?assertEqual(
+        timeout_test_recv_coap_request,
+        test_recv_coap_request(UdpSock)
+    ),
 
     device_update_1(UdpSock, Location),
 
@@ -1905,20 +2274,27 @@ case100_clients_api(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic),
 
     %% list
     {200, #{data := [Client1]}} = request(get, "/gateway/lwm2m/clients"),
     %% searching
     {200, #{data := [Client2]}} =
-        request(get, "/gateway/lwm2m/clients",
-                [{<<"endpoint_name">>, list_to_binary(Epn)}]),
+        request(
+            get,
+            "/gateway/lwm2m/clients",
+            [{<<"endpoint_name">>, list_to_binary(Epn)}]
+        ),
     {200, #{data := [Client3]}} =
-        request(get, "/gateway/lwm2m/clients",
-                [{<<"like_endpoint_name">>, list_to_binary(Epn)},
-                 {<<"gte_lifetime">>, <<"1">>}
-                ]),
+        request(
+            get,
+            "/gateway/lwm2m/clients",
+            [
+                {<<"like_endpoint_name">>, list_to_binary(Epn)},
+                {<<"gte_lifetime">>, <<"1">>}
+            ]
+        ),
     %% lookup
     ClientId = maps:get(clientid, Client1),
     {200, Client4} =
@@ -1936,28 +2312,31 @@ case100_subscription_api(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic),
 
     {200, #{data := [Client1]}} = request(get, "/gateway/lwm2m/clients"),
     ClientId = maps:get(clientid, Client1),
-    Path = "/gateway/lwm2m/clients/" ++
+    Path =
+        "/gateway/lwm2m/clients/" ++
             binary_to_list(ClientId) ++
             "/subscriptions",
 
     %% list
     {200, [InitSub]} = request(get, Path),
     ?assertEqual(
-       <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/#">>,
-       maps:get(topic, InitSub)),
+        <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/#">>,
+        maps:get(topic, InitSub)
+    ),
 
     %% create
-    SubReq = #{ topic => <<"tx">>
-              , qos => 1
-              , nl => 0
-              , rap => 0
-              , rh => 0
-              },
+    SubReq = #{
+        topic => <<"tx">>,
+        qos => 1,
+        nl => 0,
+        rap => 0,
+        rh => 0
+    },
     {201, _} = request(post, Path, SubReq),
     {200, _} = request(get, Path),
     {204, _} = request(delete, Path ++ "/tx"),
@@ -1970,17 +2349,19 @@ case100_subscription_api(Config) ->
 send_read_command_1(CmdId, _UdpSock) ->
     Epn = "urn:oma:lwm2m:oma:3",
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"read">>,
-                <<"data">> => #{<<"path">> => <<"/3/0/0">>}
-               },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{<<"path">> => <<"/3/0/0">>}
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
     timer:sleep(50).
 
 verify_read_response_1(CmdId, UdpSock) ->
     Epn = "urn:oma:lwm2m:oma:3",
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
 
     %% device receives a command
     Request = test_recv_coap_request(UdpSock),
@@ -1988,45 +2369,58 @@ verify_read_response_1(CmdId, UdpSock) ->
 
     %% device replies the command
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<"EMQ">>},
-      Request,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<"EMQ">>
+        },
+        Request,
+        true
+    ),
 
     ReadResult = emqx_json:encode(
-                   #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                     <<"msgType">> => <<"read">>,
-                     <<"data">> => #{
-                        <<"reqPath">> => <<"/3/0/0">>,
-                        <<"code">> => <<"2.05">>,
-                        <<"codeMsg">> => <<"content">>,
-                        <<"content">> =>
-                            [#{path => <<"/3/0/0">>,
-                               value => <<"EMQ">>
-                               }]
-                    }}),
+        #{
+            <<"requestID">> => CmdId,
+            <<"cacheID">> => CmdId,
+            <<"msgType">> => <<"read">>,
+            <<"data">> => #{
+                <<"reqPath">> => <<"/3/0/0">>,
+                <<"code">> => <<"2.05">>,
+                <<"codeMsg">> => <<"content">>,
+                <<"content">> =>
+                    [
+                        #{
+                            path => <<"/3/0/0">>,
+                            value => <<"EMQ">>
+                        }
+                    ]
+            }
+        }
+    ),
     ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)).
 
 device_update_1(UdpSock, Location) ->
     Epn = "urn:oma:lwm2m:oma:3",
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     ?LOGT("send UPDATE command", []),
     MsgId2 = 27,
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b~ts?lt=789", [?PORT, join_path(Location, <<>>)]),
-      #coap_content{payload = <<>>},
-      [],
-      MsgId2),
-    #coap_message{type = ack,
-                  id = MsgId2,
-                  method = Method2} = test_recv_coap_response(UdpSock),
-    {ok,changed} = Method2,
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b~ts?lt=789", [?PORT, join_path(Location, <<>>)]),
+        #coap_content{payload = <<>>},
+        [],
+        MsgId2
+    ),
+    #coap_message{
+        type = ack,
+        id = MsgId2,
+        method = Method2
+    } = test_recv_coap_response(UdpSock),
+    {ok, changed} = Method2,
     test_recv_mqtt_response(RespTopic).
 
 test_recv_mqtt_response(RespTopic) ->
@@ -2045,33 +2439,51 @@ test_send_coap_request(UdpSock, Method, Uri, Content, Options, MsgId) ->
     case resolve_uri(Uri) of
         {coap, {IpAddr, Port}, Path, Query} ->
             Request0 = request(
-                         con, Method, Content,
-                         [{uri_path, Path}, {uri_query, Query} | Options]
-                        ),
+                con,
+                Method,
+                Content,
+                [{uri_path, Path}, {uri_query, Query} | Options]
+            ),
             Request = Request0#coap_message{id = MsgId},
             ?LOGT("send_coap_request Request=~p", [Request]),
 
             RequestBinary = emqx_coap_frame:serialize_pkt(Request, undefined),
-            ?LOGT("test udp socket send to ~p:~p, data=~p",
-                  [IpAddr, Port, RequestBinary]),
+            ?LOGT(
+                "test udp socket send to ~p:~p, data=~p",
+                [IpAddr, Port, RequestBinary]
+            ),
             ok = gen_udp:send(UdpSock, IpAddr, Port, RequestBinary);
         {SchemeDiff, ChIdDiff, _, _} ->
             error(
-              lists:flatten(
-                io_lib:format(
-                  "scheme ~ts or ChId ~ts does not match with socket",
-                  [SchemeDiff, ChIdDiff])))
+                lists:flatten(
+                    io_lib:format(
+                        "scheme ~ts or ChId ~ts does not match with socket",
+                        [SchemeDiff, ChIdDiff]
+                    )
+                )
+            )
     end.
 
 test_recv_coap_response(UdpSock) ->
     {ok, {Address, Port, Packet}} = gen_udp:recv(UdpSock, 0, 2000),
     {ok, Response, _, _} = emqx_coap_frame:parse(Packet, undefined),
-    ?LOGT("test udp receive from ~p:~p, data1=~p, Response=~p",
-          [Address, Port, Packet, Response]),
-    #coap_message{type = ack, method = Method, id = Id,
-                  token = Token, options = Options, payload = Payload} = Response,
-    ?LOGT("receive coap response Method=~p, Id=~p, Token=~p, "
-          "Options=~p, Payload=~p", [Method, Id, Token, Options, Payload]),
+    ?LOGT(
+        "test udp receive from ~p:~p, data1=~p, Response=~p",
+        [Address, Port, Packet, Response]
+    ),
+    #coap_message{
+        type = ack,
+        method = Method,
+        id = Id,
+        token = Token,
+        options = Options,
+        payload = Payload
+    } = Response,
+    ?LOGT(
+        "receive coap response Method=~p, Id=~p, Token=~p, "
+        "Options=~p, Payload=~p",
+        [Method, Id, Token, Options, Payload]
+    ),
     Response.
 
 test_recv_coap_request(UdpSock) ->
@@ -2079,15 +2491,18 @@ test_recv_coap_request(UdpSock) ->
         {ok, {_Address, _Port, Packet}} ->
             {ok, Request, _, _} = emqx_coap_frame:parse(Packet, undefined),
             #coap_message{
-               type = con,
-               id = Id,
-               method = Method,
-               token = Token,
-               payload = Payload,
-               options = Options} = Request,
-            ?LOGT("receive coap request Method=~p, Id=~p, Token=~p, "
-                  "Options=~p, Payload=~p",
-                  [Method, Id, Token, Options, Payload]),
+                type = con,
+                id = Id,
+                method = Method,
+                token = Token,
+                payload = Payload,
+                options = Options
+            } = Request,
+            ?LOGT(
+                "receive coap request Method=~p, Id=~p, Token=~p, "
+                "Options=~p, Payload=~p",
+                [Method, Id, Token, Options, Payload]
+            ),
             Request;
         {error, Reason} ->
             ?LOGT("test_recv_coap_request failed, Reason=~p", [Reason]),
@@ -2101,21 +2516,30 @@ test_send_coap_response(UdpSock, Host, Port, Code, Content, Request, Ack) ->
 
     {ok, IpAddr} = inet:getaddr(Host, inet),
     Response = response(Code, Content, Request),
-    Response2 = case Ack of
-                    true -> Response#coap_message{type = ack};
-                    false -> Response
-                end,
+    Response2 =
+        case Ack of
+            true -> Response#coap_message{type = ack};
+            false -> Response
+        end,
     ?LOGT("test_send_coap_response Response=~p", [Response2]),
-    ok = gen_udp:send(UdpSock, IpAddr, Port,
-                      emqx_coap_frame:serialize_pkt(Response2, undefined)).
+    ok = gen_udp:send(
+        UdpSock,
+        IpAddr,
+        Port,
+        emqx_coap_frame:serialize_pkt(Response2, undefined)
+    ).
 
 test_send_empty_ack(UdpSock, Host, Port, Request) ->
     is_list(Host) orelse error("Host is not a string"),
     {ok, IpAddr} = inet:getaddr(Host, inet),
     EmptyACK = emqx_coap_message:ack(Request),
     ?LOGT("test_send_empty_ack EmptyACK=~p", [EmptyACK]),
-    ok = gen_udp:send(UdpSock, IpAddr, Port,
-                      emqx_coap_frame:serialize_pkt(EmptyACK, undefined)).
+    ok = gen_udp:send(
+        UdpSock,
+        IpAddr,
+        Port,
+        emqx_coap_frame:serialize_pkt(EmptyACK, undefined)
+    ).
 
 test_send_coap_observe_ack(UdpSock, Host, Port, Code, Content, Request) ->
     is_record(Content, coap_content) orelse
@@ -2146,21 +2570,25 @@ test_send_coap_notif(UdpSock, Host, Port, Content, ObSeq, Request) ->
 
 std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic) ->
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>, payload = ObjectList},
-      [],
-      MsgId1),
-    #coap_message{method = {ok,created}} = test_recv_coap_response(UdpSock),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=345&lwm2m=1", [?PORT, Epn]),
+        #coap_content{content_format = <<"text/plain">>, payload = ObjectList},
+        [],
+        MsgId1
+    ),
+    #coap_message{method = {ok, created}} = test_recv_coap_response(UdpSock),
     test_recv_mqtt_response(RespTopic),
     timer:sleep(100).
 
 resolve_uri(Uri) ->
-    {ok, #{scheme := Scheme,
-           host := Host,
-           port := PortNo,
-           path := Path} = URIMap} = emqx_http_lib:uri_parse(Uri),
+    {ok,
+        #{
+            scheme := Scheme,
+            host := Host,
+            port := PortNo,
+            path := Path
+        } = URIMap} = emqx_http_lib:uri_parse(Uri),
     Query = maps:get(query, URIMap, ""),
     {ok, PeerIP} = inet:getaddr(Host, inet),
     {Scheme, {PeerIP, PortNo}, split_path(Path), split_query(Query)}.
@@ -2177,21 +2605,25 @@ split_segments(Path, Char, Acc) ->
         0 ->
             [make_segment(Path) | Acc];
         N when N > 0 ->
-            split_segments(string:substr(Path, 1, N-1), Char,
-                [make_segment(string:substr(Path, N+1)) | Acc])
+            split_segments(
+                string:substr(Path, 1, N - 1),
+                Char,
+                [make_segment(string:substr(Path, N + 1)) | Acc]
+            )
     end.
 
 make_segment(Seg) ->
     list_to_binary(emqx_http_lib:uri_decode(Seg)).
 
-
 get_coap_path(Options) ->
     Seps = maps:get(uri_path, Options, []),
-    lists:foldl(fun(Sep, Acc) ->
-                        <>
-                end,
-                <<>>,
-                Seps).
+    lists:foldl(
+        fun(Sep, Acc) ->
+            <>
+        end,
+        <<>>,
+        Seps
+    ).
 
 get_coap_query(Options) ->
     maps:get(uri_query, Options, #{}).
@@ -2200,20 +2632,30 @@ get_coap_observe(Options) ->
     maps:get(observe, Options, undefined).
 
 join_path([], Acc) -> Acc;
-join_path([<<"/">>|T], Acc) ->
-    join_path(T, Acc);
-join_path([H|T], Acc) ->
-    join_path(T, <>).
+join_path([<<"/">> | T], Acc) -> join_path(T, Acc);
+join_path([H | T], Acc) -> join_path(T, <>).
 
 sprintf(Format, Args) ->
     lists:flatten(io_lib:format(Format, Args)).
 
 response(Code, #coap_content{content_format = Format, payload = Payload}, Req) ->
-    Msg = #coap_message{options = Opts}
-        = emqx_coap_message:response(Code, Payload, Req),
+    Msg =
+        #coap_message{options = Opts} =
+        emqx_coap_message:response(Code, Payload, Req),
     Msg#coap_message{options = Opts#{content_format => Format}}.
 
-request(Type, Method, #coap_content{content_format = Format,
-                                    payload = Payload}, Opts) ->
-    emqx_coap_message:request(Type, Method,
-                              Payload, [{content_format, Format} | Opts]).
+request(
+    Type,
+    Method,
+    #coap_content{
+        content_format = Format,
+        payload = Payload
+    },
+    Opts
+) ->
+    emqx_coap_message:request(
+        Type,
+        Method,
+        Payload,
+        [{content_format, Format} | Opts]
+    ).
diff --git a/apps/emqx_gateway/test/emqx_lwm2m_api_SUITE.erl b/apps/emqx_gateway/test/emqx_lwm2m_api_SUITE.erl
index 0194368bf..19200d2c6 100644
--- a/apps/emqx_gateway/test/emqx_lwm2m_api_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_lwm2m_api_SUITE.erl
@@ -28,38 +28,50 @@
 -include_lib("eunit/include/eunit.hrl").
 -include_lib("common_test/include/ct.hrl").
 
--define(CONF_DEFAULT, <<"
-gateway.lwm2m {
-  xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"
-  lifetime_min = 100s
-  lifetime_max = 86400s
-  qmode_time_window = 200
-  auto_observe = false
-  mountpoint = \"lwm2m/${username}\"
-  update_msg_publish_condition = contains_object_list
-  translators {
-    command = {topic = \"/dn/#\", qos = 0}
-    response = {topic = \"/up/resp\", qos = 0}
-    notify = {topic = \"/up/notify\", qos = 0}
-    register = {topic = \"/up/resp\", qos = 0}
-    update = {topic = \"/up/resp\", qos = 0}
-  }
-  listeners.udp.default {
-    bind = 5783
-  }
-}
-">>).
+-define(CONF_DEFAULT, <<
+    "\n"
+    "gateway.lwm2m {\n"
+    "  xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"\n"
+    "  lifetime_min = 100s\n"
+    "  lifetime_max = 86400s\n"
+    "  qmode_time_window = 200\n"
+    "  auto_observe = false\n"
+    "  mountpoint = \"lwm2m/${username}\"\n"
+    "  update_msg_publish_condition = contains_object_list\n"
+    "  translators {\n"
+    "    command = {topic = \"/dn/#\", qos = 0}\n"
+    "    response = {topic = \"/up/resp\", qos = 0}\n"
+    "    notify = {topic = \"/up/notify\", qos = 0}\n"
+    "    register = {topic = \"/up/resp\", qos = 0}\n"
+    "    update = {topic = \"/up/resp\", qos = 0}\n"
+    "  }\n"
+    "  listeners.udp.default {\n"
+    "    bind = 5783\n"
+    "  }\n"
+    "}\n"
+>>).
 
 -define(assertExists(Map, Key),
-        ?assertNotEqual(maps:get(Key, Map, undefined), undefined)).
+    ?assertNotEqual(maps:get(Key, Map, undefined), undefined)
+).
 
 -record(coap_content, {content_format, payload = <<>>}).
 
--import(emqx_lwm2m_SUITE, [ request/4, response/3, test_send_coap_response/7
-                          , test_recv_coap_request/1, test_recv_coap_response/1
-                          , test_send_coap_request/6, test_recv_mqtt_response/1
-                          , std_register/5, reslove_uri/1, split_path/1, split_query/1
-                          , join_path/2, sprintf/2]).
+-import(emqx_lwm2m_SUITE, [
+    request/4,
+    response/3,
+    test_send_coap_response/7,
+    test_recv_coap_request/1,
+    test_recv_coap_response/1,
+    test_send_coap_request/6,
+    test_recv_mqtt_response/1,
+    std_register/5,
+    reslove_uri/1,
+    split_path/1,
+    split_query/1,
+    join_path/2,
+    sprintf/2
+]).
 
 %%--------------------------------------------------------------------
 %% Setups
@@ -76,7 +88,7 @@ init_per_suite(Config) ->
 
 end_per_suite(Config) ->
     timer:sleep(300),
-    {ok, _} = emqx_conf:remove([<<"gateway">>,<<"lwm2m">>], #{}),
+    {ok, _} = emqx_conf:remove([<<"gateway">>, <<"lwm2m">>], #{}),
     emqx_mgmt_api_test_util:end_suite([emqx_conf]),
     Config.
 
@@ -85,7 +97,7 @@ init_per_testcase(_AllTestCase, Config) ->
     {ok, _} = application:ensure_all_started(emqx_gateway),
     {ok, ClientUdpSock} = gen_udp:open(0, [binary, {active, false}]),
 
-    {ok, C} = emqtt:start_link([{host, "localhost"},{port, 1883},{clientid, <<"c1">>}]),
+    {ok, C} = emqtt:start_link([{host, "localhost"}, {port, 1883}, {clientid, <<"c1">>}]),
     {ok, _} = emqtt:connect(C),
     timer:sleep(100),
 
@@ -104,22 +116,26 @@ t_lookup_cmd_read(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:1",
     MsgId1 = 15,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
     %% step 1, device register ...
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
-      #coap_content{
-         content_format = <<"text/plain">>,
-         payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                     ",,">>},
-      [],
-      MsgId1),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId1
+    ),
     #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method1),
+    ?assertEqual({ok, created}, Method1),
 
     timer:sleep(100),
     test_recv_mqtt_response(RespTopic),
@@ -127,13 +143,14 @@ t_lookup_cmd_read(Config) ->
     %% step2,  send a READ command to device
     CmdId = 206,
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
-    Command =   #{
-                  <<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                  <<"msgType">> => <<"read">>,
-                  <<"data">> => #{
-                                  <<"path">> => <<"/3/0/0">>
-                                 }
-                 },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"read">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/0">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     ?LOGT("CommandJson=~p", [CommandJson]),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
@@ -146,13 +163,14 @@ t_lookup_cmd_read(Config) ->
     timer:sleep(50),
 
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>},
+        Request2,
+        true
+    ),
 
     timer:sleep(200),
     normal_received_request(Epn, <<"/3/0/0">>, <<"read">>).
@@ -163,7 +181,7 @@ t_lookup_cmd_discover(Config) ->
     MsgId1 = 15,
     UdpSock = ?config(sock, Config),
     ObjectList = <<", , , , ">>,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
 
@@ -172,11 +190,14 @@ t_lookup_cmd_discover(Config) ->
     %% step2,  send a WRITE command to device
     CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
     CmdId = 307,
-    Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId,
-                <<"msgType">> => <<"discover">>,
-                <<"data">> => #{
-                                <<"path">> => <<"/3/0/7">>
-                               } },
+    Command = #{
+        <<"requestID">> => CmdId,
+        <<"cacheID">> => CmdId,
+        <<"msgType">> => <<"discover">>,
+        <<"data">> => #{
+            <<"path">> => <<"/3/0/7">>
+        }
+    },
     CommandJson = emqx_json:encode(Command),
     test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
 
@@ -189,14 +210,17 @@ t_lookup_cmd_discover(Config) ->
 
     PayloadDiscover = <<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2,">>,
     test_send_coap_response(
-      UdpSock,
-      "127.0.0.1",
-      ?PORT,
-      {ok, content},
-      #coap_content{content_format = <<"application/link-format">>,
-                    payload = PayloadDiscover},
-      Request2,
-      true),
+        UdpSock,
+        "127.0.0.1",
+        ?PORT,
+        {ok, content},
+        #coap_content{
+            content_format = <<"application/link-format">>,
+            payload = PayloadDiscover
+        },
+        Request2,
+        true
+    ),
     timer:sleep(200),
     discover_received_request(Epn, <<"/3/0/7">>, <<"discover">>).
 
@@ -204,21 +228,26 @@ t_read(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:3",
     MsgId1 = 15,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
     %% step 1, device register ...
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId1),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId1
+    ),
     #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method1),
+    ?assertEqual({ok, created}, Method1),
 
     timer:sleep(100),
     test_recv_mqtt_response(RespTopic),
@@ -231,26 +260,30 @@ t_read(Config) ->
     ?assertEqual(get, Method),
     ?assertEqual([<<"lwm2m">>, <<"3">>, <<"0">>, <<"0">>], maps:get(uri_path, Opts)).
 
-
 t_write(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:4",
     MsgId1 = 15,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
     %% step 1, device register ...
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId1),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId1
+    ),
     #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method1),
+    ?assertEqual({ok, created}, Method1),
 
     timer:sleep(100),
     test_recv_mqtt_response(RespTopic),
@@ -264,27 +297,30 @@ t_write(Config) ->
     ?assertEqual([<<"lwm2m">>, <<"3">>, <<"0">>, <<"13">>], maps:get(uri_path, Opts)),
     ?assertEqual(<<"application/vnd.oma.lwm2m+tlv">>, maps:get(content_format, Opts)).
 
-
-
 t_observe(Config) ->
     UdpSock = ?config(sock, Config),
     Epn = "urn:oma:lwm2m:oma:5",
     MsgId1 = 15,
-    RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"),
+    RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
     emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
     timer:sleep(200),
     %% step 1, device register ...
     test_send_coap_request(
-      UdpSock,
-      post,
-      sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
-      #coap_content{content_format = <<"text/plain">>,
-                    payload = <<";rt=\"oma.lwm2m\";ct=11543,"
-                                ",,">>},
-      [],
-      MsgId1),
+        UdpSock,
+        post,
+        sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=600&lwm2m=1", [?PORT, Epn]),
+        #coap_content{
+            content_format = <<"text/plain">>,
+            payload = <<
+                ";rt=\"oma.lwm2m\";ct=11543,"
+                ",,"
+            >>
+        },
+        [],
+        MsgId1
+    ),
     #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
-    ?assertEqual({ok,created}, Method1),
+    ?assertEqual({ok, created}, Method1),
 
     timer:sleep(100),
     test_recv_mqtt_response(RespTopic),
@@ -318,11 +354,13 @@ call_send_api(ClientId, Cmd, Query) ->
 
 no_received_request(ClientId, Path, Action) ->
     Response = call_lookup_api(ClientId, Path, Action),
-    NotReceived = #{<<"clientid">> => list_to_binary(ClientId),
-                    <<"action">> => Action,
-                    <<"code">> => <<"6.01">>,
-                    <<"codeMsg">> => <<"reply_not_received">>,
-                    <<"path">> => Path},
+    NotReceived = #{
+        <<"clientid">> => list_to_binary(ClientId),
+        <<"action">> => Action,
+        <<"code">> => <<"6.01">>,
+        <<"codeMsg">> => <<"reply_not_received">>,
+        <<"path">> => Path
+    },
     ?assertEqual(NotReceived, emqx_json:decode(Response, [return_maps])).
 normal_received_request(ClientId, Path, Action) ->
     Response = call_lookup_api(ClientId, Path, Action),
diff --git a/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl b/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl
index 9659b7b61..de6e3eed5 100644
--- a/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_sn_frame_SUITE.erl
@@ -96,15 +96,15 @@ t_puback(_) ->
     ?assertEqual(PubAck, parse(serialize_pkt(PubAck))).
 
 t_pubrec(_) ->
-    PubRec =  #mqtt_sn_message{type = ?SN_PUBREC, variable = 16#1234},
+    PubRec = #mqtt_sn_message{type = ?SN_PUBREC, variable = 16#1234},
     ?assertEqual(PubRec, parse(serialize_pkt(PubRec))).
 
 t_pubrel(_) ->
-    PubRel =  #mqtt_sn_message{type = ?SN_PUBREL, variable = 16#1234},
+    PubRel = #mqtt_sn_message{type = ?SN_PUBREL, variable = 16#1234},
     ?assertEqual(PubRel, parse(serialize_pkt(PubRel))).
 
 t_pubcomp(_) ->
-    PubComp =  #mqtt_sn_message{type = ?SN_PUBCOMP, variable = 16#1234},
+    PubComp = #mqtt_sn_message{type = ?SN_PUBCOMP, variable = 16#1234},
     ?assertEqual(PubComp, parse(serialize_pkt(PubComp))).
 
 t_subscribe(_) ->
@@ -165,10 +165,12 @@ random_test_body() ->
     Data = generate_random_binary(),
     case catch parse(Data) of
         Msg when is_record(Msg, mqtt_sn_message) -> ok;
-        {'EXIT', {Err, _Stack}}
-          when Err =:= unkown_message_type;
-               Err =:= malformed_message_len;
-               Err =:= malformed_message_flags -> ok
+        {'EXIT', {Err, _Stack}} when
+            Err =:= unkown_message_type;
+            Err =:= malformed_message_len;
+            Err =:= malformed_message_flags
+        ->
+            ok
     end.
 
 generate_random_binary() ->
@@ -180,4 +182,4 @@ gen_next(0, Acc) ->
     Acc;
 gen_next(N, Acc) ->
     Byte = rand:uniform(256) - 1,
-    gen_next(N-1, <>).
+    gen_next(N - 1, <>).
diff --git a/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl b/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl
index 449ad7306..cc1625c5e 100644
--- a/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_sn_protocol_SUITE.erl
@@ -19,10 +19,13 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--import(emqx_gateway_test_utils,
-        [ request/2
-        , request/3
-        ]).
+-import(
+    emqx_gateway_test_utils,
+    [
+        request/2,
+        request/3
+    ]
+).
 
 -include("src/mqttsn/include/emqx_sn.hrl").
 
@@ -32,13 +35,13 @@
 -include_lib("emqx/include/emqx.hrl").
 -include_lib("emqx/include/emqx_mqtt.hrl").
 
--define(HOST, {127,0,0,1}).
+-define(HOST, {127, 0, 0, 1}).
 -define(PORT, 1884).
 
--define(FLAG_DUP(X),X).
--define(FLAG_QOS(X),X).
--define(FLAG_RETAIN(X),X).
--define(FLAG_SESSION(X),X).
+-define(FLAG_DUP(X), X).
+-define(FLAG_QOS(X), X).
+-define(FLAG_RETAIN(X), X).
+-define(FLAG_SESSION(X), X).
 
 -define(LOG(Format, Args), ct:pal("TEST: " ++ Format, Args)).
 
@@ -52,33 +55,39 @@
 -define(FNU, 0).
 
 %% erlang:system_time should be unique and random enough
--define(CLIENTID, iolist_to_binary([atom_to_list(?FUNCTION_NAME), "-",
-                                    integer_to_list(erlang:system_time())])).
+-define(CLIENTID,
+    iolist_to_binary([
+        atom_to_list(?FUNCTION_NAME),
+        "-",
+        integer_to_list(erlang:system_time())
+    ])
+).
 
 -elvis([{elvis_style, dont_repeat_yourself, disable}]).
 
--define(CONF_DEFAULT, <<"
-gateway.mqttsn {
-  gateway_id = 1
-  broadcast = true
-  enable_qos3 = true
-  predefined = [
-    { id = 1,
-      topic = \"/predefined/topic/name/hello\"
-    },
-    { id = 2,
-      topic = \"/predefined/topic/name/nice\"
-    }
-  ]
-  clientinfo_override {
-    username = \"user1\"
-    password = \"pw123\"
-  }
-  listeners.udp.default {
-    bind = 1884
-  }
-}
-">>).
+-define(CONF_DEFAULT, <<
+    "\n"
+    "gateway.mqttsn {\n"
+    "  gateway_id = 1\n"
+    "  broadcast = true\n"
+    "  enable_qos3 = true\n"
+    "  predefined = [\n"
+    "    { id = 1,\n"
+    "      topic = \"/predefined/topic/name/hello\"\n"
+    "    },\n"
+    "    { id = 2,\n"
+    "      topic = \"/predefined/topic/name/nice\"\n"
+    "    }\n"
+    "  ]\n"
+    "  clientinfo_override {\n"
+    "    username = \"user1\"\n"
+    "    password = \"pw123\"\n"
+    "  }\n"
+    "  listeners.udp.default {\n"
+    "    bind = 1884\n"
+    "  }\n"
+    "}\n"
+>>).
 
 %%--------------------------------------------------------------------
 %% Setups
@@ -98,13 +107,15 @@ end_per_suite(_) ->
 
 restart_mqttsn_with_subs_resume_on() ->
     emqx_gateway_conf:update_gateway(
-      mqttsn,
-      #{<<"subs_resume">> => <<"true">>}).
+        mqttsn,
+        #{<<"subs_resume">> => <<"true">>}
+    ).
 
 restart_mqttsn_with_subs_resume_off() ->
     emqx_gateway_conf:update_gateway(
-      mqttsn,
-      #{<<"subs_resume">> => <<"false">>}).
+        mqttsn,
+        #{<<"subs_resume">> => <<"false">>}
+    ).
 
 %%--------------------------------------------------------------------
 %% Test cases
@@ -142,9 +153,11 @@ t_subscribe(_) ->
     send_register_msg(Socket, TopicName1, MsgId),
     ?assertEqual(<<7, ?SN_REGACK, TopicId:16, MsgId:16, 0:8>>, receive_response(Socket)),
     send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1,
-                   CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId:16,
-                   MsgId:16, ReturnCode>>, receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
     ?assert(lists:member(TopicName1, emqx_broker:topics())),
 
     send_unsubscribe_msg_normal_topic(Socket, TopicName1, MsgId),
@@ -174,9 +187,11 @@ t_subscribe_case01(_) ->
     send_register_msg(Socket, TopicName1, MsgId),
     ?assertEqual(<<7, ?SN_REGACK, TopicId:16, MsgId:16, 0:8>>, receive_response(Socket)),
     send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1,
-                   ?SN_NORMAL_TOPIC:2, TopicId:16, MsgId:16, ReturnCode>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_unsubscribe_msg_normal_topic(Socket, TopicName1, MsgId),
     ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@@ -204,9 +219,11 @@ t_subscribe_case02(_) ->
     send_register_msg(Socket, Topic1, MsgId),
     ?assertEqual(<<7, ?SN_REGACK, TopicId:16, MsgId:16, 0:8>>, receive_response(Socket)),
     send_subscribe_msg_predefined_topic(Socket, QoS, TopicId, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1,
-                   ?SN_NORMAL_TOPIC:2, TopicId:16, MsgId:16, ReturnCode>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_unsubscribe_msg_predefined_topic(Socket, TopicId, MsgId),
     ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@@ -232,9 +249,11 @@ t_subscribe_case03(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
 
     send_subscribe_msg_short_topic(Socket, QoS, <<"te">>, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1,
-                   ?SN_NORMAL_TOPIC:2, TopicId:16, MsgId:16, ReturnCode>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_unsubscribe_msg_short_topic(Socket, <<"te">>, MsgId),
     ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@@ -269,11 +288,10 @@ t_subscribe_case04(_) ->
     ?assertEqual(<<7, ?SN_REGACK, TopicId:16, MsgId:16, 0:8>>, receive_response(Socket)),
     send_subscribe_msg_normal_topic(Socket, QoS, Topic1, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId:16, MsgId:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_unsubscribe_msg_normal_topic(Socket, Topic1, MsgId),
     ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@@ -304,35 +322,31 @@ t_subscribe_case05(_) ->
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"abcD">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId1:16, MsgId:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"/sport/#">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"/a/+/water">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"/Tom/Home">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId2:16, MsgId:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId2:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
     send_unsubscribe_msg_normal_topic(Socket, <<"abcD">>, MsgId),
     ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
 
@@ -358,36 +372,33 @@ t_subscribe_case06(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
     send_register_msg(Socket, <<"abc">>, MsgId),
     ?assertEqual(
-       <<7, ?SN_REGACK, TopicId1:16, MsgId:16, 0:8>>,
-       receive_response(Socket)
-      ),
+        <<7, ?SN_REGACK, TopicId1:16, MsgId:16, 0:8>>,
+        receive_response(Socket)
+    ),
 
     send_register_msg(Socket, <<"/blue/#">>, MsgId),
     ?assertEqual(
-       <<7, ?SN_REGACK, TopicId0:16,
-         MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>,
-       receive_response(Socket)
-      ),
+        <<7, ?SN_REGACK, TopicId0:16, MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>,
+        receive_response(Socket)
+    ),
 
     send_register_msg(Socket, <<"/blue/+/white">>, MsgId),
     ?assertEqual(
-       <<7, ?SN_REGACK, TopicId0:16,
-         MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>,
-       receive_response(Socket)
-      ),
+        <<7, ?SN_REGACK, TopicId0:16, MsgId:16, ?SN_RC_NOT_SUPPORTED:8>>,
+        receive_response(Socket)
+    ),
     send_register_msg(Socket, <<"/$sys/rain">>, MsgId),
     ?assertEqual(
-       <<7, ?SN_REGACK, TopicId2:16, MsgId:16, 0:8>>,
-       receive_response(Socket)
-      ),
+        <<7, ?SN_REGACK, TopicId2:16, MsgId:16, 0:8>>,
+        receive_response(Socket)
+    ),
 
     send_subscribe_msg_short_topic(Socket, QoS, <<"Q2">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1,
-         CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     send_unsubscribe_msg_normal_topic(Socket, <<"Q2">>, MsgId),
     ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@@ -412,11 +423,10 @@ t_subscribe_case07(_) ->
 
     send_subscribe_msg_predefined_topic(Socket, QoS, TopicId1, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         ?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            ?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>,
+        receive_response(Socket)
+    ),
 
     send_unsubscribe_msg_predefined_topic(Socket, TopicId2, MsgId),
     ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)),
@@ -439,11 +449,10 @@ t_subscribe_case08(_) ->
 
     send_subscribe_msg_reserved_topic(Socket, QoS, TopicId2, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         ?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            ?SN_INVALID_TOPIC_ID:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>,
+        receive_response(Socket)
+    ),
 
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@@ -465,23 +474,21 @@ t_publish_negqos_case09(_) ->
 
     Topic = <<"abc">>,
 
-
     send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
     MsgId1 = 3,
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_normal_topic(Socket, NegQoS, MsgId1, TopicId1, Payload1),
     timer:sleep(100),
     case ?ENABLE_QOS3 of
-        true  ->
-            Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-                     Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-                     TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
+        true ->
+            Eexp =
+                <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1,
+                    ?SN_NORMAL_TOPIC:2, TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
             What = receive_response(Socket),
             ?assertEqual(Eexp, What)
     end,
@@ -505,18 +512,19 @@ t_publish_qos0_case01(_) ->
 
     Topic = <<"abc">>,
     send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1,
-                   CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId1:16,
-                   MsgId:16, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
     MsgId1 = 3,
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_normal_topic(Socket, QoS, MsgId1, TopicId1, Payload1),
     timer:sleep(100),
 
-    Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-             Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-             TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
+    Eexp =
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
     What = receive_response(Socket),
     ?assertEqual(Eexp, What),
 
@@ -539,20 +547,19 @@ t_publish_qos0_case02(_) ->
 
     send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     MsgId1 = 3,
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_predefined_topic(Socket, QoS, MsgId1, PredefTopicId, Payload1),
     timer:sleep(100),
 
-    Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-             Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC:2,
-             PredefTopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
+    Eexp =
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC:2,
+            PredefTopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
     What = receive_response(Socket),
     ?assertEqual(Eexp, What),
 
@@ -576,20 +583,19 @@ t_publish_qos0_case3(_) ->
     Topic = <<"/a/b/c">>,
     send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     MsgId1 = 3,
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_predefined_topic(Socket, QoS, MsgId1, TopicId, Payload1),
     timer:sleep(100),
 
-    Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-             Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-             TopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
+    Eexp =
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
     What = receive_response(Socket),
     ?assertEqual(Eexp, What),
 
@@ -612,11 +618,10 @@ t_publish_qos0_case04(_) ->
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"#">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     MsgId1 = 2,
     Payload1 = <<20, 21, 22, 23>>,
@@ -624,9 +629,9 @@ t_publish_qos0_case04(_) ->
     send_publish_msg_short_topic(Socket, QoS, MsgId1, Topic, Payload1),
     timer:sleep(100),
 
-    Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-             Will:1, CleanSession:1, ?SN_SHORT_TOPIC:2,
-             Topic/binary, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
+    Eexp =
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_SHORT_TOPIC:2,
+            Topic/binary, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
     What = receive_response(Socket),
     ?assertEqual(Eexp, What),
 
@@ -648,11 +653,10 @@ t_publish_qos0_case05(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
     send_subscribe_msg_short_topic(Socket, QoS, <<"/#">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
 
@@ -674,20 +678,19 @@ t_publish_qos0_case06(_) ->
     Topic = <<"abc">>,
     send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     MsgId1 = 3,
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_normal_topic(Socket, QoS, MsgId1, TopicId1, Payload1),
     timer:sleep(100),
 
-    Eexp = <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-             Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-             TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
+    Eexp =
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, (mid(0)):16, <<20, 21, 22, 23>>/binary>>,
     What = receive_response(Socket),
     ?assertEqual(Eexp, What),
 
@@ -710,23 +713,23 @@ t_publish_qos1_case01(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
     send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_normal_topic(Socket, QoS, MsgId, TopicId1, Payload1),
-    ?assertEqual(<<7, ?SN_PUBACK, TopicId1:16,
-                   MsgId:16, ?SN_RC_ACCEPTED>>, receive_response(Socket)),
+    ?assertEqual(
+        <<7, ?SN_PUBACK, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>, receive_response(Socket)
+    ),
     timer:sleep(100),
 
-    ?assertEqual(<<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-                   Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-                   TopicId1:16, MsgId:16, <<20, 21, 22, 23>>/binary>>,
-                 receive_response(Socket)
-                ),
+    ?assertEqual(
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, <<20, 21, 22, 23>>/binary>>,
+        receive_response(Socket)
+    ),
 
     send_disconnect_msg(Socket, undefined),
     gen_udp:close(Socket).
@@ -746,16 +749,17 @@ t_publish_qos1_case02(_) ->
 
     send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_predefined_topic(Socket, QoS, MsgId, PredefTopicId, Payload1),
-    ?assertEqual(<<7, ?SN_PUBACK, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<7, ?SN_PUBACK, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
     timer:sleep(100),
 
     send_disconnect_msg(Socket, undefined),
@@ -770,8 +774,10 @@ t_publish_qos1_case03(_) ->
     send_connect_msg(Socket, ClientId),
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
     send_publish_msg_predefined_topic(Socket, QoS, MsgId, tid(5), <<20, 21, 22, 23>>),
-    ?assertEqual(<<7, ?SN_PUBACK, TopicId5:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<7, ?SN_PUBACK, TopicId5:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>,
+        receive_response(Socket)
+    ),
 
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@@ -791,18 +797,19 @@ t_publish_qos1_case04(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
     send_subscribe_msg_short_topic(Socket, QoS, <<"ab">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     Topic = <<"ab">>,
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_short_topic(Socket, QoS, MsgId, Topic, Payload1),
     <> = Topic,
-    ?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
     timer:sleep(100),
 
     send_disconnect_msg(Socket, undefined),
@@ -822,15 +829,18 @@ t_publish_qos1_case05(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"ab">>, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1,
-                   ?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/#">>, <<20, 21, 22, 23>>),
     <> = <<"/#">>,
-    ?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16,
-                   MsgId:16, ?SN_RC_NOT_SUPPORTED>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>,
+        receive_response(Socket)
+    ),
 
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@@ -850,14 +860,17 @@ t_publish_qos1_case06(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"ab">>, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1,
-                   ?SN_NORMAL_TOPIC:2, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/+">>, <<20, 21, 22, 23>>),
     <> = <<"/+">>,
-    ?assertEqual(<<7, ?SN_PUBACK, TopicIdShort:16,
-                   MsgId:16, ?SN_RC_NOT_SUPPORTED>>, receive_response(Socket)),
+    ?assertEqual(
+        <<7, ?SN_PUBACK, TopicIdShort:16, MsgId:16, ?SN_RC_NOT_SUPPORTED>>, receive_response(Socket)
+    ),
 
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@@ -877,19 +890,20 @@ t_publish_qos2_case01(_) ->
     send_connect_msg(Socket, ClientId),
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
     send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, ?FNU:1, QoS:2, ?FNU:5, TopicId1:16, MsgId:16,
-                   ?SN_RC_ACCEPTED>>, receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, ?FNU:1, QoS:2, ?FNU:5, TopicId1:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
     Payload1 = <<20, 21, 22, 23>>,
 
     send_publish_msg_normal_topic(Socket, QoS, MsgId, TopicId1, Payload1),
     ?assertEqual(<<4, ?SN_PUBREC, MsgId:16>>, receive_response(Socket)),
     send_pubrel_msg(Socket, MsgId),
     ?assertEqual(
-       <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId1:16, 1:16, <<20, 21, 22, 23>>/binary>>,
-       receive_response(Socket)
-      ),
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, 1:16, <<20, 21, 22, 23>>/binary>>,
+        receive_response(Socket)
+    ),
     ?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)),
     timer:sleep(100),
 
@@ -912,10 +926,9 @@ t_publish_qos2_case02(_) ->
 
     send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, ?FNU:1, QoS:2,
-         ?FNU:5, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, ?FNU:1, QoS:2, ?FNU:5, PredefTopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_predefined_topic(Socket, QoS, MsgId, PredefTopicId, Payload1),
@@ -923,11 +936,10 @@ t_publish_qos2_case02(_) ->
     send_pubrel_msg(Socket, MsgId),
 
     ?assertEqual(
-       <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC:2,
-         PredefTopicId:16, 1:16, <<20, 21, 22, 23>>/binary>>,
-       receive_response(Socket)
-      ),
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_PREDEFINED_TOPIC:2,
+            PredefTopicId:16, 1:16, <<20, 21, 22, 23>>/binary>>,
+        receive_response(Socket)
+    ),
     ?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)),
 
     timer:sleep(100),
@@ -951,10 +963,9 @@ t_publish_qos2_case03(_) ->
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"/#">>, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, ?FNU:1, QoS:2,
-         ?FNU:5, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, ?FNU:1, QoS:2, ?FNU:5, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     Payload1 = <<20, 21, 22, 23>>,
     send_publish_msg_short_topic(Socket, QoS, MsgId, <<"/a">>, Payload1),
@@ -962,11 +973,10 @@ t_publish_qos2_case03(_) ->
     send_pubrel_msg(Socket, MsgId),
 
     ?assertEqual(
-       <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_SHORT_TOPIC:2,
-         <<"/a">>/binary, 1:16, <<20, 21, 22, 23>>/binary>>,
-       receive_response(Socket)
-      ),
+        <<11, ?SN_PUBLISH, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_SHORT_TOPIC:2,
+            <<"/a">>/binary, 1:16, <<20, 21, 22, 23>>/binary>>,
+        receive_response(Socket)
+    ),
     ?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket)),
     timer:sleep(100),
 
@@ -987,32 +997,57 @@ t_delivery_qos1_register_invalid_topic_id(_) ->
     ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)),
 
     send_subscribe_msg_normal_topic(Socket, QoS, <<"ab">>, MsgId),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1,
-                   ?SN_NORMAL_TOPIC:2, TopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId:16, MsgId:16, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
     Payload = <<"test-registration-inconsistent">>,
     _ = emqx:publish(emqx_message:make(test, ?QOS_1, <<"ab">>, Payload)),
 
     ?assertEqual(
-       <<(7 + byte_size(Payload)), ?SN_PUBLISH,
-         Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId:16, MsgId:16, Payload/binary>>, receive_response(Socket)),
+        <<
+            (7 + byte_size(Payload)),
+            ?SN_PUBLISH,
+            Dup:1,
+            QoS:2,
+            Retain:1,
+            Will:1,
+            CleanSession:1,
+            ?SN_NORMAL_TOPIC:2,
+            TopicId:16,
+            MsgId:16,
+            Payload/binary
+        >>,
+        receive_response(Socket)
+    ),
     %% acked with ?SN_RC_INVALID_TOPIC_ID to
     send_puback_msg(Socket, TopicId, MsgId, ?SN_RC_INVALID_TOPIC_ID),
 
     ?assertMatch(
-       {TopicId, _},
-       check_register_msg_on_udp(<<"ab">>, receive_response(Socket))),
+        {TopicId, _},
+        check_register_msg_on_udp(<<"ab">>, receive_response(Socket))
+    ),
     send_regack_msg(Socket, TopicId, MsgId + 1),
 
     %% receive the replay message
     ?assertEqual(
-       <<(7 + byte_size(Payload)), ?SN_PUBLISH,
-         Dup:1, QoS:2, Retain:1,
-         Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId:16, (MsgId):16, Payload/binary>>, receive_response(Socket)),
+        <<
+            (7 + byte_size(Payload)),
+            ?SN_PUBLISH,
+            Dup:1,
+            QoS:2,
+            Retain:1,
+            Will:1,
+            CleanSession:1,
+            ?SN_NORMAL_TOPIC:2,
+            TopicId:16,
+            (MsgId):16,
+            Payload/binary
+        >>,
+        receive_response(Socket)
+    ),
 
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@@ -1046,8 +1081,7 @@ t_will_case01(_) ->
     receive
         {deliver, WillTopic, #message{payload = WillMsg}} ->
             ok
-    after
-        1000 -> ct:fail(wait_willmsg_timeout)
+    after 1000 -> ct:fail(wait_willmsg_timeout)
     end,
     ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
 
@@ -1073,8 +1107,10 @@ t_will_test2(_) ->
 
     timer:sleep(4000),
 
-    receive_response(Socket), % ignore PUBACK
-    receive_response(Socket), % ignore PUBCOMP
+    % ignore PUBACK
+    receive_response(Socket),
+    % ignore PUBCOMP
+    receive_response(Socket),
 
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(udp_receive_timeout, receive_response(Socket)),
@@ -1123,7 +1159,8 @@ t_will_test4(_) ->
 
     timer:sleep(4000),
 
-    receive_response(Socket), % ignore PUBACK
+    % ignore PUBACK
+    receive_response(Socket),
 
     send_disconnect_msg(Socket, undefined),
     ?assertEqual(udp_receive_timeout, receive_response(Socket)),
@@ -1184,8 +1221,7 @@ t_will_case06(_) ->
     receive
         {deliver, WillTopic, #message{payload = WillMsg}} -> ok;
         Msg -> ct:print("received --- unex: ~p", [Msg])
-    after
-        1000 -> ct:fail(wait_willmsg_timeout)
+    after 1000 -> ct:fail(wait_willmsg_timeout)
     end,
     send_disconnect_msg(Socket, undefined),
 
@@ -1287,11 +1323,10 @@ t_asleep_test03_to_awake_qos1_dl_msg(_) ->
     ?assertEqual(<<7, ?SN_REGACK, TopicId1:16, MsgId1:16, 0:8>>, receive_response(Socket)),
     send_subscribe_msg_predefined_topic(Socket, QoS, TopicId1, MsgId),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId1:16, MsgId:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId1:16, MsgId:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     % goto asleep state
     send_disconnect_msg(Socket, 1),
@@ -1300,7 +1335,8 @@ t_asleep_test03_to_awake_qos1_dl_msg(_) ->
     timer:sleep(300),
 
     emqx_broker:publish(
-      emqx_message:make(<<"ct">>, QoS, TopicName1, Payload1)),
+        emqx_message:make(<<"ct">>, QoS, TopicName1, Payload1)
+    ),
 
     timer:sleep(50),
 
@@ -1310,8 +1346,8 @@ t_asleep_test03_to_awake_qos1_dl_msg(_) ->
     %% the broker should sent dl msgs to the awake client before sending the pingresp
     UdpData = receive_response(Socket),
     MsgId_udp = check_publish_msg_on_udp(
-                  {Dup, QoS, Retain, WillBit, CleanSession,
-                   ?SN_NORMAL_TOPIC, TopicId1, Payload1}, UdpData),
+        {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicId1, Payload1}, UdpData
+    ),
     send_puback_msg(Socket, TopicId1, MsgId_udp),
 
     %% check the pingresp is received at last
@@ -1348,11 +1384,10 @@ t_asleep_test04_to_awake_qos1_dl_msg(_) ->
     ReturnCode = 0,
     send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId1:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId1:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     % goto asleep state
     send_disconnect_msg(Socket, 1),
@@ -1387,14 +1422,14 @@ t_asleep_test04_to_awake_qos1_dl_msg(_) ->
 
     UdpData2 = receive_response(Socket),
     MsgId_udp2 = check_publish_msg_on_udp(
-                   {Dup, QoS, Retain, WillBit, CleanSession,
-                    ?SN_NORMAL_TOPIC, TopicIdNew, Payload1}, UdpData2),
+        {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload1}, UdpData2
+    ),
     send_puback_msg(Socket, TopicIdNew, MsgId_udp2),
 
     UdpData3 = receive_response(Socket),
     MsgId_udp3 = check_publish_msg_on_udp(
-                   {Dup, QoS, Retain, WillBit, CleanSession,
-                    ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData3),
+        {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData3
+    ),
     send_puback_msg(Socket, TopicIdNew, MsgId_udp3),
 
     ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)),
@@ -1435,11 +1470,10 @@ t_asleep_test05_to_awake_qos1_dl_msg(_) ->
     ReturnCode = 0,
     send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId1:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId1:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     % goto asleep state
     SleepDuration = 5,
@@ -1473,24 +1507,26 @@ t_asleep_test05_to_awake_qos1_dl_msg(_) ->
 
     UdpData2 = receive_response(Socket),
     MsgId2 = check_publish_msg_on_udp(
-               {Dup, QoS, Retain, WillBit, CleanSession,
-                ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData2),
+        {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData2
+    ),
     send_puback_msg(Socket, TopicIdNew, MsgId2),
     timer:sleep(50),
 
     UdpData3 = wrap_receive_response(Socket),
     MsgId3 = check_publish_msg_on_udp(
-               {Dup, QoS, Retain, WillBit, CleanSession,
-                ?SN_NORMAL_TOPIC, TopicIdNew, Payload3}, UdpData3),
+        {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload3}, UdpData3
+    ),
     send_puback_msg(Socket, TopicIdNew, MsgId3),
     timer:sleep(50),
 
     case receive_response(Socket) of
-        <<2,23>> -> ok;
+        <<2, 23>> ->
+            ok;
         UdpData4 ->
-            MsgId4 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit,
-                                               CleanSession, ?SN_NORMAL_TOPIC,
-                                               TopicIdNew, Payload4}, UdpData4),
+            MsgId4 = check_publish_msg_on_udp(
+                {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload4},
+                UdpData4
+            ),
             send_puback_msg(Socket, TopicIdNew, MsgId4)
     end,
     ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)),
@@ -1526,9 +1562,11 @@ t_asleep_test06_to_awake_qos2_dl_msg(_) ->
     timer:sleep(50),
     TopicId_tom = check_regack_msg_on_udp(MsgId1, receive_response(Socket)),
     send_subscribe_msg_predefined_topic(Socket, QoS, TopicId_tom, MsgId1),
-    ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1,
-                   ?SN_NORMAL_TOPIC:2, TopicId_tom:16, MsgId1:16, ReturnCode>>,
-                 receive_response(Socket)),
+    ?assertEqual(
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId_tom:16, MsgId1:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     % goto asleep state
     SleepDuration = 5,
@@ -1553,8 +1591,8 @@ t_asleep_test06_to_awake_qos2_dl_msg(_) ->
 
     UdpData = wrap_receive_response(Socket),
     MsgId_udp = check_publish_msg_on_udp(
-                  {Dup, QoS, Retain, WillBit, CleanSession,
-                   ?SN_NORMAL_TOPIC, TopicId_tom, Payload1}, UdpData),
+        {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicId_tom, Payload1}, UdpData
+    ),
     send_pubrec_msg(Socket, MsgId_udp),
     ?assertMatch(<<_:8, ?SN_PUBREL:8, _/binary>>, receive_response(Socket)),
     send_pubcomp_msg(Socket, MsgId_udp),
@@ -1594,11 +1632,10 @@ t_asleep_test07_to_connected(_) ->
     TopicId_tom = check_regack_msg_on_udp(MsgId1, receive_response(Socket)),
     send_subscribe_msg_predefined_topic(Socket, QoS, TopicId_tom, MsgId1),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId_tom:16, MsgId1:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId_tom:16, MsgId1:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
 
     % goto asleep state
     send_disconnect_msg(Socket, SleepDuration),
@@ -1675,11 +1712,10 @@ t_asleep_test09_to_awake_again_qos1_dl_msg(_) ->
     ReturnCode = 0,
     send_subscribe_msg_normal_topic(Socket, QoS, TopicName1, MsgId1),
     ?assertEqual(
-       <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1,
-         WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
-         TopicId0:16, MsgId1:16, ReturnCode>>,
-       receive_response(Socket)
-      ),
+        <<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, ?SN_NORMAL_TOPIC:2,
+            TopicId0:16, MsgId1:16, ReturnCode>>,
+        receive_response(Socket)
+    ),
     % goto asleep state
     SleepDuration = 5,
     send_disconnect_msg(Socket, SleepDuration),
@@ -1714,8 +1750,9 @@ t_asleep_test09_to_awake_again_qos1_dl_msg(_) ->
             ok;
         UdpData2 ->
             MsgId2 = check_publish_msg_on_udp(
-                       {Dup, QoS, Retain, WillBit, CleanSession,
-                        ?SN_NORMAL_TOPIC, TopicIdNew, Payload2}, UdpData2),
+                {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload2},
+                UdpData2
+            ),
             send_puback_msg(Socket, TopicIdNew, MsgId2)
     end,
     timer:sleep(100),
@@ -1725,8 +1762,9 @@ t_asleep_test09_to_awake_again_qos1_dl_msg(_) ->
             ok;
         UdpData3 ->
             MsgId3 = check_publish_msg_on_udp(
-                       {Dup, QoS, Retain, WillBit, CleanSession,
-                        ?SN_NORMAL_TOPIC, TopicIdNew, Payload3}, UdpData3),
+                {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload3},
+                UdpData3
+            ),
             send_puback_msg(Socket, TopicIdNew, MsgId3)
     end,
     timer:sleep(100),
@@ -1735,9 +1773,10 @@ t_asleep_test09_to_awake_again_qos1_dl_msg(_) ->
         udp_receive_timeout ->
             ok;
         UdpData4 ->
-            MsgId4 = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit,
-                                       CleanSession, ?SN_NORMAL_TOPIC,
-                                       TopicIdNew, Payload4}, UdpData4),
+            MsgId4 = check_publish_msg_on_udp(
+                {Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicIdNew, Payload4},
+                UdpData4
+            ),
             send_puback_msg(Socket, TopicIdNew, MsgId4)
     end,
     ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)),
@@ -1823,7 +1862,7 @@ t_awake_test02_to_disconnected(_) ->
     gen_udp:close(Socket).
 
 t_broadcast_test1(_) ->
-    {ok, Socket} = gen_udp:open( 0, [binary]),
+    {ok, Socket} = gen_udp:open(0, [binary]),
     send_searchgw_msg(Socket),
     ?assertEqual(<<3, ?SN_GWINFO, 1>>, receive_response(Socket)),
     timer:sleep(600),
@@ -1834,28 +1873,28 @@ t_register_subs_resume_on(_) ->
     MsgId = 1,
     {ok, Socket} = gen_udp:open(0, [binary]),
     send_connect_msg(Socket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
-    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-a">>, MsgId+1),
-    <<_, ?SN_SUBACK, 2#01000000,
-      TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
+    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-a">>, MsgId + 1),
+    <<_, ?SN_SUBACK, 2#01000000, TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
 
-    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-b">>, MsgId+2),
-    <<_, ?SN_SUBACK, 2#01000000,
-      TopicIdB:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
+    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-b">>, MsgId + 2),
+    <<_, ?SN_SUBACK, 2#01000000, TopicIdB:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
 
     _ = emqx:publish(
-          emqx_message:make(test, ?QOS_1, <<"topic-a">>, <<"test-a">>)),
+        emqx_message:make(test, ?QOS_1, <<"topic-a">>, <<"test-a">>)
+    ),
     _ = emqx:publish(
-          emqx_message:make(test, ?QOS_1, <<"topic-b">>, <<"test-b">>)),
+        emqx_message:make(test, ?QOS_1, <<"topic-b">>, <<"test-b">>)
+    ),
 
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgId1:16, "test-a">> = receive_response(Socket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgId1:16, "test-a">> = receive_response(Socket),
     send_puback_msg(Socket, TopicIdA, MsgId1, ?SN_RC_ACCEPTED),
 
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdB:16, MsgId2:16, "test-b">> = receive_response(Socket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdB:16, MsgId2:16, "test-b">> = receive_response(Socket),
     send_puback_msg(Socket, TopicIdB, MsgId2, ?SN_RC_ACCEPTED),
 
     send_disconnect_msg(Socket, undefined),
@@ -1874,43 +1913,36 @@ t_register_subs_resume_on(_) ->
 
     {ok, NSocket} = gen_udp:open(0, [binary]),
     send_connect_msg(NSocket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(NSocket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(NSocket)
+    ),
 
     %% receive subs register requests
-    <<_, ?SN_REGISTER,
-      TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
+    <<_, ?SN_REGISTER, TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
     send_regack_msg(NSocket, TopicIdA, RegMsgIdA),
 
-    <<_, ?SN_REGISTER,
-      TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
+    <<_, ?SN_REGISTER, TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
     send_regack_msg(NSocket, TopicIdB, RegMsgIdB),
 
     %% receive the queued messages
 
-    <<_, ?SN_PUBLISH, 2#00000000,
-      TopicIdA:16, 0:16, "m1">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00000000, TopicIdA:16, 0:16, "m1">> = receive_response(NSocket),
 
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgIdA1:16, "m2">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgIdA1:16, "m2">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdA, MsgIdA1, ?SN_RC_ACCEPTED),
 
-    <<_, ?SN_PUBLISH, 2#01000000,
-      TopicIdA:16, MsgIdA2:16, "m3">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#01000000, TopicIdA:16, MsgIdA2:16, "m3">> = receive_response(NSocket),
     send_pubrec_msg(NSocket, MsgIdA2),
     <<_, ?SN_PUBREL, MsgIdA2:16>> = receive_response(NSocket),
     send_pubcomp_msg(NSocket, MsgIdA2),
 
+    <<_, ?SN_PUBLISH, 2#00000000, TopicIdB:16, 0:16, "m1">> = receive_response(NSocket),
 
-    <<_, ?SN_PUBLISH, 2#00000000,
-      TopicIdB:16, 0:16, "m1">> = receive_response(NSocket),
-
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdB:16, MsgIdB1:16, "m2">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdB:16, MsgIdB1:16, "m2">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdB, MsgIdB1, ?SN_RC_ACCEPTED),
 
-    <<_, ?SN_PUBLISH, 2#01000000,
-      TopicIdB:16, MsgIdB2:16, "m3">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#01000000, TopicIdB:16, MsgIdB2:16, "m3">> = receive_response(NSocket),
     send_pubrec_msg(NSocket, MsgIdB2),
     <<_, ?SN_PUBREL, MsgIdB2:16>> = receive_response(NSocket),
     send_pubcomp_msg(NSocket, MsgIdB2),
@@ -1921,8 +1953,10 @@ t_register_subs_resume_on(_) ->
     gen_udp:close(NSocket),
     {ok, NSocket1} = gen_udp:open(0, [binary]),
     send_connect_msg(NSocket1, <<"test">>),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(NSocket1)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(NSocket1)
+    ),
     send_disconnect_msg(NSocket1, undefined),
     ?assertMatch(<<2, ?SN_DISCONNECT>>, receive_response(NSocket1)),
     gen_udp:close(NSocket1),
@@ -1932,28 +1966,28 @@ t_register_subs_resume_off(_) ->
     MsgId = 1,
     {ok, Socket} = gen_udp:open(0, [binary]),
     send_connect_msg(Socket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
-    send_subscribe_msg_normal_topic(Socket, ?QOS_1, <<"topic-a">>, MsgId+1),
-    <<_, ?SN_SUBACK, 2#00100000,
-      TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
+    send_subscribe_msg_normal_topic(Socket, ?QOS_1, <<"topic-a">>, MsgId + 1),
+    <<_, ?SN_SUBACK, 2#00100000, TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
 
-    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-b">>, MsgId+2),
-    <<_, ?SN_SUBACK, 2#01000000,
-      TopicIdB:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
+    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-b">>, MsgId + 2),
+    <<_, ?SN_SUBACK, 2#01000000, TopicIdB:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
 
     _ = emqx:publish(
-          emqx_message:make(test, ?QOS_1, <<"topic-a">>, <<"test-a">>)),
+        emqx_message:make(test, ?QOS_1, <<"topic-a">>, <<"test-a">>)
+    ),
     _ = emqx:publish(
-          emqx_message:make(test, ?QOS_2, <<"topic-b">>, <<"test-b">>)),
+        emqx_message:make(test, ?QOS_2, <<"topic-b">>, <<"test-b">>)
+    ),
 
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgId1:16, "test-a">> = receive_response(Socket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgId1:16, "test-a">> = receive_response(Socket),
     send_puback_msg(Socket, TopicIdA, MsgId1, ?SN_RC_ACCEPTED),
 
-    <<_, ?SN_PUBLISH, 2#01000000,
-      TopicIdB:16, MsgId2:16, "test-b">> = receive_response(Socket),
+    <<_, ?SN_PUBLISH, 2#01000000, TopicIdB:16, MsgId2:16, "test-b">> = receive_response(Socket),
     send_puback_msg(Socket, TopicIdB, MsgId2, ?SN_RC_ACCEPTED),
 
     send_disconnect_msg(Socket, undefined),
@@ -1970,57 +2004,49 @@ t_register_subs_resume_off(_) ->
 
     {ok, NSocket} = gen_udp:open(0, [binary]),
     send_connect_msg(NSocket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(NSocket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(NSocket)
+    ),
 
     %% qos1
 
     %% received the resume messages
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgIdA0:16, "m1">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgIdA0:16, "m1">> = receive_response(NSocket),
     %% only one qos1/qos2 inflight
     ?assertEqual(udp_receive_timeout, receive_response(NSocket)),
     send_puback_msg(NSocket, TopicIdA, MsgIdA0, ?SN_RC_INVALID_TOPIC_ID),
     %% recv register
-    <<_, ?SN_REGISTER,
-      TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
+    <<_, ?SN_REGISTER, TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
     send_regack_msg(NSocket, TopicIdA, RegMsgIdA),
     %% received the replay messages
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgIdA1:16, "m1">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgIdA1:16, "m1">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdA, MsgIdA1, ?SN_RC_ACCEPTED),
 
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgIdA2:16, "m2">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgIdA2:16, "m2">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdA, MsgIdA2, ?SN_RC_ACCEPTED),
 
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgIdA3:16, "m3">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgIdA3:16, "m3">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdA, MsgIdA3, ?SN_RC_ACCEPTED),
 
     %% qos2
-    <<_, ?SN_PUBLISH, 2#01000000,
-      TopicIdB:16, MsgIdB0:16, "m1">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#01000000, TopicIdB:16, MsgIdB0:16, "m1">> = receive_response(NSocket),
     %% only one qos1/qos2 inflight
     ?assertEqual(udp_receive_timeout, receive_response(NSocket)),
     send_puback_msg(NSocket, TopicIdB, MsgIdB0, ?SN_RC_INVALID_TOPIC_ID),
     %% recv register
-    <<_, ?SN_REGISTER,
-      TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
+    <<_, ?SN_REGISTER, TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
     send_regack_msg(NSocket, TopicIdB, RegMsgIdB),
     %% received the replay messages
-    <<_, ?SN_PUBLISH, 2#01000000,
-      TopicIdB:16, MsgIdB1:16, "m1">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#01000000, TopicIdB:16, MsgIdB1:16, "m1">> = receive_response(NSocket),
     send_pubrec_msg(NSocket, MsgIdB1),
     <<_, ?SN_PUBREL, MsgIdB1:16>> = receive_response(NSocket),
     send_pubcomp_msg(NSocket, MsgIdB1),
 
-    <<_, ?SN_PUBLISH, 2#01000000,
-      TopicIdB:16, MsgIdB2:16, "m2">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#01000000, TopicIdB:16, MsgIdB2:16, "m2">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdB, MsgIdB2, ?SN_RC_ACCEPTED),
 
-    <<_, ?SN_PUBLISH, 2#01000000,
-      TopicIdB:16, MsgIdB3:16, "m3">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#01000000, TopicIdB:16, MsgIdB3:16, "m3">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdB, MsgIdB3, ?SN_RC_ACCEPTED),
 
     %% no more messages
@@ -2029,8 +2055,10 @@ t_register_subs_resume_off(_) ->
     gen_udp:close(NSocket),
     {ok, NSocket1} = gen_udp:open(0, [binary]),
     send_connect_msg(NSocket1, <<"test">>),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(NSocket1)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(NSocket1)
+    ),
     send_disconnect_msg(NSocket1, undefined),
     ?assertMatch(<<2, ?SN_DISCONNECT>>, receive_response(NSocket1)),
     gen_udp:close(NSocket1).
@@ -2040,16 +2068,16 @@ t_register_skip_failure_topic_name_and_reach_max_retry_times(_) ->
     MsgId = 1,
     {ok, Socket} = gen_udp:open(0, [binary]),
     send_connect_msg(Socket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
-    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-a">>, MsgId+1),
-    <<_, ?SN_SUBACK, 2#01000000,
-      TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
+    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-a">>, MsgId + 1),
+    <<_, ?SN_SUBACK, 2#01000000, TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
 
-    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-b">>, MsgId+2),
-    <<_, ?SN_SUBACK, 2#01000000,
-      TopicIdB:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
+    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-b">>, MsgId + 2),
+    <<_, ?SN_SUBACK, 2#01000000, TopicIdB:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
 
     send_disconnect_msg(Socket, undefined),
     ?assertMatch(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@@ -2059,38 +2087,43 @@ t_register_skip_failure_topic_name_and_reach_max_retry_times(_) ->
 
     {ok, NSocket} = gen_udp:open(0, [binary]),
     send_connect_msg(NSocket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(NSocket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(NSocket)
+    ),
 
     %% receive subs register requests
 
     %% registered failured topic-name will be skipped
-    <<_, ?SN_REGISTER,
-      TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
+    <<_, ?SN_REGISTER, TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
     send_regack_msg(NSocket, TopicIdA, RegMsgIdA, ?SN_RC_INVALID_TOPIC_ID),
 
     %% the gateway try to shutdown this client if it reached max-retry-times
     %%
     %% times-0
-    <<_, ?SN_REGISTER,
-      TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
+    <<_, ?SN_REGISTER, TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
     %% times-1
-    timer:sleep(5000), %% RETYRY_TIMEOUT
-    <<_, ?SN_REGISTER,
-      TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
+
+    %% RETYRY_TIMEOUT
+    timer:sleep(5000),
+    <<_, ?SN_REGISTER, TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
     %% times-2
-    timer:sleep(5000), %% RETYRY_TIMEOUT
-    <<_, ?SN_REGISTER,
-      TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
+
+    %% RETYRY_TIMEOUT
+    timer:sleep(5000),
+    <<_, ?SN_REGISTER, TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
     %% just a ping
     send_pingreq_msg(NSocket, <<"test">>),
     ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(NSocket)),
     %% times-3
-    timer:sleep(5000), %% RETYRY_TIMEOUT
-    <<_, ?SN_REGISTER,
-      TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
+
+    %% RETYRY_TIMEOUT
+    timer:sleep(5000),
+    <<_, ?SN_REGISTER, TopicIdB:16, RegMsgIdB:16, "topic-b">> = receive_response(NSocket),
     %% shutdown due to reached max retry times
-    timer:sleep(5000), %% RETYRY_TIMEOUT
+
+    %% RETYRY_TIMEOUT
+    timer:sleep(5000),
     ?assertMatch(<<2, ?SN_DISCONNECT>>, receive_response(NSocket)),
     gen_udp:close(NSocket),
     restart_mqttsn_with_subs_resume_off().
@@ -2100,12 +2133,13 @@ t_register_enqueue_delivering_messages(_) ->
     MsgId = 1,
     {ok, Socket} = gen_udp:open(0, [binary]),
     send_connect_msg(Socket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(Socket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(Socket)
+    ),
 
-    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-a">>, MsgId+1),
-    <<_, ?SN_SUBACK, 2#01000000,
-      TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
+    send_subscribe_msg_normal_topic(Socket, ?QOS_2, <<"topic-a">>, MsgId + 1),
+    <<_, ?SN_SUBACK, 2#01000000, TopicIdA:16, _:16, ?SN_RC_ACCEPTED>> = receive_response(Socket),
 
     send_disconnect_msg(Socket, undefined),
     ?assertMatch(<<2, ?SN_DISCONNECT>>, receive_response(Socket)),
@@ -2113,14 +2147,15 @@ t_register_enqueue_delivering_messages(_) ->
 
     {ok, NSocket} = gen_udp:open(0, [binary]),
     send_connect_msg(NSocket, <<"test">>, 0),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(NSocket)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(NSocket)
+    ),
 
     %% receive subs register requests
 
     %% registered failured topic-name will be skipped
-    <<_, ?SN_REGISTER,
-      TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
+    <<_, ?SN_REGISTER, TopicIdA:16, RegMsgIdA:16, "topic-a">> = receive_response(NSocket),
 
     _ = emqx:publish(emqx_message:make(test, ?QOS_0, <<"topic-a">>, <<"m1">>)),
     _ = emqx:publish(emqx_message:make(test, ?QOS_1, <<"topic-a">>, <<"m2">>)),
@@ -2129,11 +2164,9 @@ t_register_enqueue_delivering_messages(_) ->
 
     %% receive the queued messages
 
-    <<_, ?SN_PUBLISH, 2#00000000,
-      TopicIdA:16, 0:16, "m1">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00000000, TopicIdA:16, 0:16, "m1">> = receive_response(NSocket),
 
-    <<_, ?SN_PUBLISH, 2#00100000,
-      TopicIdA:16, MsgIdA1:16, "m2">> = receive_response(NSocket),
+    <<_, ?SN_PUBLISH, 2#00100000, TopicIdA:16, MsgIdA1:16, "m2">> = receive_response(NSocket),
     send_puback_msg(NSocket, TopicIdA, MsgIdA1, ?SN_RC_ACCEPTED),
 
     %% no more messages
@@ -2142,8 +2175,10 @@ t_register_enqueue_delivering_messages(_) ->
     gen_udp:close(NSocket),
     {ok, NSocket1} = gen_udp:open(0, [binary]),
     send_connect_msg(NSocket1, <<"test">>),
-    ?assertMatch(<<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
-                 receive_response(NSocket1)),
+    ?assertMatch(
+        <<_, ?SN_CONNACK, ?SN_RC_ACCEPTED>>,
+        receive_response(NSocket1)
+    ),
     send_disconnect_msg(NSocket1, undefined),
     ?assertMatch(<<2, ?SN_DISCONNECT>>, receive_response(NSocket1)),
     gen_udp:close(NSocket1),
@@ -2155,7 +2190,8 @@ t_socket_passvice(_) ->
 
 t_clients_api(_) ->
     TsNow = emqx_gateway_utils:unix_ts_to_rfc3339(
-              erlang:system_time(millisecond)),
+        erlang:system_time(millisecond)
+    ),
     ClientId = <<"client_id_test1">>,
     {ok, Socket} = gen_udp:open(0, [binary]),
     send_connect_msg(Socket, ClientId),
@@ -2167,14 +2203,18 @@ t_clients_api(_) ->
     {200, #{data := [Client2]}} =
         request(get, "/gateway/mqttsn/clients", [{<<"clientid">>, ClientId}]),
     {200, #{data := [Client3]}} =
-        request(get, "/gateway/mqttsn/clients",
-                [{<<"like_clientid">>, <<"test1">>},
-                 {<<"proto_ver">>, <<"1.2">>},
-                 {<<"ip_address">>, <<"127.0.0.1">>},
-                 {<<"conn_state">>, <<"connected">>},
-                 {<<"clean_start">>, <<"true">>},
-                 {<<"gte_connected_at">>, TsNow}
-                ]),
+        request(
+            get,
+            "/gateway/mqttsn/clients",
+            [
+                {<<"like_clientid">>, <<"test1">>},
+                {<<"proto_ver">>, <<"1.2">>},
+                {<<"ip_address">>, <<"127.0.0.1">>},
+                {<<"conn_state">>, <<"connected">>},
+                {<<"clean_start">>, <<"true">>},
+                {<<"gte_connected_at">>, TsNow}
+            ]
+        ),
     %% lookup
     {200, Client4} =
         request(get, "/gateway/mqttsn/clients/client_id_test1"),
@@ -2198,12 +2238,13 @@ t_clients_subscription_api(_) ->
     %% list
     {200, []} = request(get, Path),
     %% create
-    SubReq = #{ topic => <<"tx">>
-              , qos => 1
-              , nl => 0
-              , rap => 0
-              , rh => 0
-              },
+    SubReq = #{
+        topic => <<"tx">>,
+        qos => 1,
+        nl => 0,
+        rap => 0,
+        rh => 0
+    },
     {201, SubsResp} = request(post, Path, SubReq),
 
     {200, [SubsResp]} = request(get, Path),
@@ -2229,8 +2270,10 @@ send_searchgw_msg(Socket) ->
 send_connect_msg(Socket, ClientId) ->
     send_connect_msg(Socket, ClientId, 1).
 
-send_connect_msg(Socket, ClientId, CleanSession) when CleanSession == 0;
-                                                      CleanSession == 1 ->
+send_connect_msg(Socket, ClientId, CleanSession) when
+    CleanSession == 0;
+    CleanSession == 1
+->
     Length = 6 + byte_size(ClientId),
     MsgType = ?SN_CONNECT,
     Dup = 0,
@@ -2240,8 +2283,9 @@ send_connect_msg(Socket, ClientId, CleanSession) when CleanSession == 0;
     TopicIdType = 0,
     ProtocolId = 1,
     Duration = 10,
-    Packet = <>,
+    Packet =
+        <>,
     ok = gen_udp:send(Socket, ?HOST, ?PORT, Packet).
 
 send_connect_msg_with_will(Socket, Duration, ClientId) ->
@@ -2249,8 +2293,9 @@ send_connect_msg_with_will(Socket, Duration, ClientId) ->
     Will = 1,
     CleanSession = 1,
     ProtocolId = 1,
-    ConnectPacket = <>,
+    ConnectPacket =
+        <>,
     ok = gen_udp:send(Socket, ?HOST, ?PORT, ConnectPacket).
 
 send_connect_msg_with_will1(Socket, Duration, ClientId) ->
@@ -2258,12 +2303,13 @@ send_connect_msg_with_will1(Socket, Duration, ClientId) ->
     Will = 1,
     CleanSession = 0,
     ProtocolId = 1,
-    ConnectPacket = <>,
+    ConnectPacket =
+        <>,
     ok = gen_udp:send(Socket, ?HOST, ?PORT, ConnectPacket).
 
 send_willtopic_msg(Socket, Topic, QoS) ->
-    Length = 3+byte_size(Topic),
+    Length = 3 + byte_size(Topic),
     MsgType = ?SN_WILLTOPIC,
     Retain = 0,
     WillTopicPacket = <>,
@@ -2276,12 +2322,12 @@ send_willtopic_empty_msg(Socket) ->
     ok = gen_udp:send(Socket, ?HOST, ?PORT, WillTopicPacket).
 
 send_willmsg_msg(Socket, Msg) ->
-    Length = 2+byte_size(Msg),
+    Length = 2 + byte_size(Msg),
     WillMsgPacket = <>,
     ok = gen_udp:send(Socket, ?HOST, ?PORT, WillMsgPacket).
 
 send_willtopicupd_msg(Socket, Topic, QoS) ->
-    Length = 3+byte_size(Topic),
+    Length = 3 + byte_size(Topic),
     MsgType = ?SN_WILLTOPICUPD,
     Retain = 0,
     WillTopicPacket = <>,
@@ -2294,7 +2340,7 @@ send_willtopicupd_empty_msg(Socket) ->
     ok = gen_udp:send(Socket, ?HOST, ?PORT, WillTopicPacket).
 
 send_willmsgupd_msg(Socket, Msg) ->
-    Length = 2+byte_size(Msg),
+    Length = 2 + byte_size(Msg),
     MsgType = ?SN_WILLMSGUPD,
     WillTopicPacket = <>,
     ok = gen_udp:send(Socket, ?HOST, ?PORT, WillTopicPacket).
@@ -2323,8 +2369,9 @@ send_publish_msg_normal_topic(Socket, QoS, MsgId, TopicId, Data) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = ?SN_NORMAL_TOPIC,
-    PublishPacket = <>,
+    PublishPacket =
+        <>,
     ?LOG("send_publish_msg_normal_topic TopicId=~p, Data=~p", [TopicId, Data]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, PublishPacket).
 
@@ -2336,8 +2383,9 @@ send_publish_msg_predefined_topic(Socket, QoS, MsgId, TopicId, Data) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = ?SN_PREDEFINED_TOPIC,
-    PublishPacket = <>,
+    PublishPacket =
+        <>,
     ?LOG("send_publish_msg_predefined_topic TopicId=~p, Data=~p", [TopicId, Data]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, PublishPacket).
 
@@ -2349,8 +2397,9 @@ send_publish_msg_short_topic(Socket, QoS, MsgId, TopicName, Data) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = 2,
-    PublishPacket = <>,
+    PublishPacket =
+        <>,
     ?LOG("send_publish_msg_short_topic TopicName=~p, Data=~p", [TopicName, Data]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, PublishPacket).
 
@@ -2393,8 +2442,9 @@ send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId) ->
     CleanSession = 0,
     TopicIdType = ?SN_NORMAL_TOPIC,
     Length = byte_size(Topic) + 5,
-    SubscribePacket = <>,
+    SubscribePacket =
+        <>,
     ?LOG("send_subscribe_msg_normal_topic Topic=~p, MsgId=~p", [Topic, MsgId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, SubscribePacket).
 
@@ -2406,8 +2456,9 @@ send_subscribe_msg_predefined_topic(Socket, QoS, TopicId, MsgId) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = ?SN_PREDEFINED_TOPIC,
-    SubscribePacket = <>,
+    SubscribePacket =
+        <>,
     ?LOG("send_subscribe_msg_predefined_topic TopicId=~p, MsgId=~p", [TopicId, MsgId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, SubscribePacket).
 
@@ -2419,8 +2470,9 @@ send_subscribe_msg_short_topic(Socket, QoS, Topic, MsgId) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = ?SN_SHORT_TOPIC,
-    SubscribePacket = <>,
+    SubscribePacket =
+        <>,
     ?LOG("send_subscribe_msg_short_topic Topic=~p, MsgId=~p", [Topic, MsgId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, SubscribePacket).
 
@@ -2432,8 +2484,9 @@ send_subscribe_msg_reserved_topic(Socket, QoS, TopicId, MsgId) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = ?SN_RESERVED_TOPIC,
-    SubscribePacket = <>,
+    SubscribePacket =
+        <>,
     ?LOG("send_subscribe_msg_reserved_topic TopicId=~p, MsgId=~p", [TopicId, MsgId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, SubscribePacket).
 
@@ -2445,8 +2498,9 @@ send_unsubscribe_msg_predefined_topic(Socket, TopicId, MsgId) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = ?SN_PREDEFINED_TOPIC,
-    UnSubscribePacket = <>,
+    UnSubscribePacket =
+        <>,
     ?LOG("send_unsubscribe_msg_predefined_topic TopicId=~p, MsgId=~p", [TopicId, MsgId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, UnSubscribePacket).
 
@@ -2459,8 +2513,9 @@ send_unsubscribe_msg_normal_topic(Socket, TopicName, MsgId) ->
     CleanSession = 0,
     TopicIdType = ?SN_NORMAL_TOPIC,
     Length = 5 + byte_size(TopicName),
-    UnSubscribePacket = <>,
+    UnSubscribePacket =
+        <>,
     ?LOG("send_unsubscribe_msg_normal_topic TopicName=~p, MsgId=~p", [TopicName, MsgId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, UnSubscribePacket).
 
@@ -2472,31 +2527,35 @@ send_unsubscribe_msg_short_topic(Socket, TopicId, MsgId) ->
     Will = 0,
     CleanSession = 0,
     TopicIdType = ?SN_SHORT_TOPIC,
-    UnSubscribePacket = <>,
+    UnSubscribePacket =
+        <>,
     ?LOG("send_unsubscribe_msg_short_topic TopicId=~p, MsgId=~p", [TopicId, MsgId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, UnSubscribePacket).
 
-send_pingreq_msg(Socket, ClientId)->
+send_pingreq_msg(Socket, ClientId) ->
     Length = 2,
     MsgType = ?SN_PINGREQ,
-    PingReqPacket = case ClientId of
-                        undefined ->
-                            <>;
-                        Other ->
-                            Size = byte_size(Other)+2,
-                            <>
-                    end,
+    PingReqPacket =
+        case ClientId of
+            undefined ->
+                <>;
+            Other ->
+                Size = byte_size(Other) + 2,
+                <>
+        end,
     ?LOG("send_pingreq_msg ClientId=~p", [ClientId]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, PingReqPacket).
 
 send_disconnect_msg(Socket, Duration) ->
-    Length = 2, Length2 = 4,
+    Length = 2,
+    Length2 = 4,
     MsgType = ?SN_DISCONNECT,
-    DisConnectPacket = case Duration of
-                           undefined -> <>;
-                           Other     -> <>
-                       end,
+    DisConnectPacket =
+        case Duration of
+            undefined -> <>;
+            Other -> <>
+        end,
     ?LOG("send_disconnect_msg Duration=~p", [Duration]),
     ok = gen_udp:send(Socket, ?HOST, ?PORT, DisConnectPacket).
 
@@ -2506,7 +2565,7 @@ tid(Id) -> Id.
 %% filter <<2, 23>> pingresp packet
 wrap_receive_response(Socket) ->
     case receive_response(Socket) of
-        <<2,23>> ->
+        <<2, 23>> ->
             ct:log("PingResp"),
             wrap_receive_response(Socket);
         Other ->
@@ -2534,15 +2593,22 @@ check_dispatched_message(Dup, QoS, Retain, TopicIdType, TopicId, Payload, Socket
     PubMsg = receive_response(Socket),
     Length = 7 + byte_size(Payload),
     ?LOG("check_dispatched_message ~p~n", [PubMsg]),
-    ?LOG("expected ~p xx ~p~n",
-         [<>, Payload]),
-    <> = PubMsg,
+    ?LOG(
+        "expected ~p xx ~p~n",
+        [
+            <>,
+            Payload
+        ]
+    ),
+    <> = PubMsg,
     case QoS of
-        0 -> ok;
-        1 -> send_puback_msg(Socket, TopicId, MsgId);
-        2 -> send_pubrel_msg(Socket, MsgId),
+        0 ->
+            ok;
+        1 ->
+            send_puback_msg(Socket, TopicId, MsgId);
+        2 ->
+            send_pubrel_msg(Socket, MsgId),
             ?assertEqual(<<4, ?SN_PUBCOMP, MsgId:16>>, receive_response(Socket))
     end,
     ok.
@@ -2550,15 +2616,19 @@ check_dispatched_message(Dup, QoS, Retain, TopicIdType, TopicId, Payload, Socket
 get_udp_broadcast_address() ->
     "255.255.255.255".
 
-check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession,
-                          TopicType, TopicId, Payload}, UdpData) ->
+check_publish_msg_on_udp(
+    {Dup, QoS, Retain, WillBit, CleanSession, TopicType, TopicId, Payload}, UdpData
+) ->
     <> = UdpData,
     ct:pal("UdpData: ~p, Payload: ~p, PayloadIn: ~p", [UdpData, Payload, PayloadIn]),
     Size9 = byte_size(Payload) + 7,
-    Eexp = <>,
-    ?assertEqual(Eexp, HeaderUdp),     % mqtt-sn header should be same
-    ?assertEqual(Payload, PayloadIn),  % payload should be same
+    Eexp =
+        <>,
+    % mqtt-sn header should be same
+    ?assertEqual(Eexp, HeaderUdp),
+    % payload should be same
+    ?assertEqual(Payload, PayloadIn),
     MsgId.
 
 check_register_msg_on_udp(TopicName, UdpData) ->
@@ -2577,7 +2647,6 @@ flush() ->
     flush([]).
 flush(Msgs) ->
     receive
-        M -> flush([M|Msgs])
-    after
-        0 -> lists:reverse(Msgs)
+        M -> flush([M | Msgs])
+    after 0 -> lists:reverse(Msgs)
     end.
diff --git a/apps/emqx_gateway/test/emqx_sn_registry_SUITE.erl b/apps/emqx_gateway/test/emqx_sn_registry_SUITE.erl
index 624acbef3..a09705a68 100644
--- a/apps/emqx_gateway/test/emqx_sn_registry_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_sn_registry_SUITE.erl
@@ -23,8 +23,10 @@
 
 -define(REGISTRY, emqx_sn_registry).
 -define(MAX_PREDEF_ID, 2).
--define(PREDEF_TOPICS, [#{id => 1, topic => <<"/predefined/topic/name/hello">>},
-                        #{id => 2, topic => <<"/predefined/topic/name/nice">>}]).
+-define(PREDEF_TOPICS, [
+    #{id => 1, topic => <<"/predefined/topic/name/hello">>},
+    #{id => 2, topic => <<"/predefined/topic/name/nice">>}
+]).
 
 %%--------------------------------------------------------------------
 %% Setups
@@ -58,60 +60,64 @@ end_per_testcase(_TestCase, Config) ->
 
 t_register(Config) ->
     Reg = proplists:get_value(reg, Config),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)),
-    ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic2">>)),
-    ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+1)),
-    ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
-    ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic2">>)),
+    ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 1)),
+    ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 2)),
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)),
     emqx_sn_registry:unregister_topic(Reg, <<"ClientId">>),
-    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+1)),
-    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)),
+    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 1)),
+    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 2)),
     ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
     ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)).
 
 t_register_case2(Config) ->
     Reg = proplists:get_value(reg, Config),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)),
-    ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic2">>)),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)),
-    ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+1)),
-    ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
-    ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic2">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)),
+    ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 1)),
+    ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 2)),
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)),
     ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic3">>)),
     ?REGISTRY:unregister_topic(Reg, <<"ClientId">>),
-    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+1)),
-    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)),
+    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 1)),
+    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 2)),
     ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
     ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)).
 
 t_reach_maximum(Config) ->
     Reg = proplists:get_value(reg, Config),
-    register_a_lot(?MAX_PREDEF_ID+1, 16#ffff, Reg),
+    register_a_lot(?MAX_PREDEF_ID + 1, 16#ffff, Reg),
     ?assertEqual({error, too_large}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicABC">>)),
-    Topic1 = iolist_to_binary(io_lib:format("Topic~p", [?MAX_PREDEF_ID+1])),
-    Topic2 = iolist_to_binary(io_lib:format("Topic~p", [?MAX_PREDEF_ID+2])),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic1)),
-    ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic2)),
+    Topic1 = iolist_to_binary(io_lib:format("Topic~p", [?MAX_PREDEF_ID + 1])),
+    Topic2 = iolist_to_binary(io_lib:format("Topic~p", [?MAX_PREDEF_ID + 2])),
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic1)),
+    ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic2)),
     ?REGISTRY:unregister_topic(Reg, <<"ClientId">>),
-    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+1)),
-    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)),
+    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 1)),
+    ?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 2)),
     ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic1)),
     ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic2)).
 
 t_register_case4(Config) ->
     Reg = proplists:get_value(reg, Config),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicA">>)),
-    ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicB">>)),
-    ?assertEqual(?MAX_PREDEF_ID+3, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicC">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicA">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicB">>)),
+    ?assertEqual(?MAX_PREDEF_ID + 3, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicC">>)),
     ?REGISTRY:unregister_topic(Reg, <<"ClientId">>),
-    ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicD">>)).
+    ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicD">>)).
 
 t_deny_wildcard_topic(Config) ->
     Reg = proplists:get_value(reg, Config),
-    ?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/TopicA/#">>)),
-    ?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/+/TopicB">>)).
+    ?assertEqual(
+        {error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/TopicA/#">>)
+    ),
+    ?assertEqual(
+        {error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/+/TopicB">>)
+    ).
 
 %%--------------------------------------------------------------------
 %% Helper funcs
@@ -122,4 +128,4 @@ register_a_lot(Max, Max, _Reg) ->
 register_a_lot(N, Max, Reg) when N < Max ->
     Topic = iolist_to_binary(["Topic", integer_to_list(N)]),
     ?assertEqual(N, ?REGISTRY:register_topic(Reg, <<"ClientId">>, Topic)),
-    register_a_lot(N+1, Max, Reg).
+    register_a_lot(N + 1, Max, Reg).
diff --git a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl
index cd06e58f5..ca61a63a4 100644
--- a/apps/emqx_gateway/test/emqx_stomp_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_stomp_SUITE.erl
@@ -22,25 +22,29 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
--import(emqx_gateway_test_utils,
-        [ assert_feilds_apperence/2
-        , request/2
-        , request/3
-        ]).
+-import(
+    emqx_gateway_test_utils,
+    [
+        assert_feilds_apperence/2,
+        request/2,
+        request/3
+    ]
+).
 
 -define(HEARTBEAT, <<$\n>>).
 
--define(CONF_DEFAULT, <<"
-gateway.stomp {
-  clientinfo_override {
-    username = \"${Packet.headers.login}\"
-    password = \"${Packet.headers.passcode}\"
-  }
-  listeners.tcp.default {
-    bind = 61613
-  }
-}
-">>).
+-define(CONF_DEFAULT, <<
+    "\n"
+    "gateway.stomp {\n"
+    "  clientinfo_override {\n"
+    "    username = \"${Packet.headers.login}\"\n"
+    "    password = \"${Packet.headers.passcode}\"\n"
+    "  }\n"
+    "  listeners.tcp.default {\n"
+    "    bind = 61613\n"
+    "  }\n"
+    "}\n"
+>>).
 
 all() -> emqx_common_test_helpers:all(?MODULE).
 
@@ -64,25 +68,45 @@ end_per_suite(_Cfg) ->
 t_connect(_) ->
     %% Connect should be succeed
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                     [{<<"accept-version">>, ?STOMP_VER},
-                                      {<<"host">>, <<"127.0.0.1:61613">>},
-                                      {<<"login">>, <<"guest">>},
-                                      {<<"passcode">>, <<"guest">>},
-                                      {<<"heart-beat">>, <<"1000,2000">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"1000,2000">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, Frame = #stomp_frame{command = <<"CONNECTED">>,
-                                  headers = _,
-                                  body    = _}, _, _} = parse(Data),
+        {ok,
+            Frame = #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
         <<"2000,1000">> = proplists:get_value(<<"heart-beat">>, Frame#stomp_frame.headers),
 
-        gen_tcp:send(Sock, serialize(<<"DISCONNECT">>,
-                                     [{<<"receipt">>, <<"12345">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"DISCONNECT">>,
+                [{<<"receipt">>, <<"12345">>}]
+            )
+        ),
 
         {ok, Data1} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"RECEIPT">>,
-                          headers = [{<<"receipt-id">>, <<"12345">>}],
-                          body    = _}, _, _} = parse(Data1)
+        {ok,
+            #stomp_frame{
+                command = <<"RECEIPT">>,
+                headers = [{<<"receipt-id">>, <<"12345">>}],
+                body = _
+            },
+            _, _} = parse(Data1)
     end),
 
     %% Connect will be failed, because of bad login or passcode
@@ -104,34 +128,52 @@ t_connect(_) ->
 
     %% Connect will be failed, because of bad version
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock,
-                     serialize(<<"CONNECT">>,
-                               [{<<"accept-version">>, <<"2.0,2.1">>},
-                                {<<"host">>, <<"127.0.0.1:61613">>},
-                                {<<"login">>, <<"guest">>},
-                                {<<"passcode">>, <<"guest">>},
-                                {<<"heart-beat">>, <<"1000,2000">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, <<"2.0,2.1">>},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"1000,2000">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
         {ok, Frame, _, _} = parse(Data),
         #stomp_frame{
-           command = <<"ERROR">>,
-           headers = _,
-           body    = <<"Login Failed: Supported protocol versions < 1.2">>} = Frame
+            command = <<"ERROR">>,
+            headers = _,
+            body = <<"Login Failed: Supported protocol versions < 1.2">>
+        } = Frame
     end).
 
 t_heartbeat(_) ->
     %% Test heart beat
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                    [{<<"accept-version">>, ?STOMP_VER},
-                                     {<<"host">>, <<"127.0.0.1:61613">>},
-                                     {<<"login">>, <<"guest">>},
-                                     {<<"passcode">>, <<"guest">>},
-                                     {<<"heart-beat">>, <<"1000,800">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"1000,800">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                          headers = _,
-                          body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
         {ok, ?HEARTBEAT} = gen_tcp:recv(Sock, 0),
         %% Server will close the connection because never receive the heart beat from client
@@ -140,243 +182,465 @@ t_heartbeat(_) ->
 
 t_subscribe(_) ->
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                    [{<<"accept-version">>, ?STOMP_VER},
-                                     {<<"host">>, <<"127.0.0.1:61613">>},
-                                     {<<"login">>, <<"guest">>},
-                                     {<<"passcode">>, <<"guest">>},
-                                     {<<"heart-beat">>, <<"0,0">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"0,0">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                          headers = _,
-                          body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
         %% Subscribe
-        gen_tcp:send(Sock, serialize(<<"SUBSCRIBE">>,
-                                    [{<<"id">>, 0},
-                                     {<<"destination">>, <<"/queue/foo">>},
-                                     {<<"ack">>, <<"auto">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SUBSCRIBE">>,
+                [
+                    {<<"id">>, 0},
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"ack">>, <<"auto">>}
+                ]
+            )
+        ),
 
         %% 'user-defined' header will be retain
-        gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                    [{<<"destination">>, <<"/queue/foo">>},
-                                     {<<"user-defined">>, <<"emq">>}],
-                                    <<"hello">>)),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SEND">>,
+                [
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"user-defined">>, <<"emq">>}
+                ],
+                <<"hello">>
+            )
+        ),
 
         {ok, Data1} = gen_tcp:recv(Sock, 0, 1000),
-        {ok, Frame = #stomp_frame{command = <<"MESSAGE">>,
-                                  headers = _,
-                                  body    = <<"hello">>}, _, _} = parse(Data1),
-        lists:foreach(fun({Key, Val}) ->
-                          Val = proplists:get_value(Key, Frame#stomp_frame.headers)
-                      end, [{<<"destination">>,  <<"/queue/foo">>},
-                            {<<"subscription">>, <<"0">>},
-                            {<<"user-defined">>, <<"emq">>}]),
+        {ok,
+            Frame = #stomp_frame{
+                command = <<"MESSAGE">>,
+                headers = _,
+                body = <<"hello">>
+            },
+            _, _} = parse(Data1),
+        lists:foreach(
+            fun({Key, Val}) ->
+                Val = proplists:get_value(Key, Frame#stomp_frame.headers)
+            end,
+            [
+                {<<"destination">>, <<"/queue/foo">>},
+                {<<"subscription">>, <<"0">>},
+                {<<"user-defined">>, <<"emq">>}
+            ]
+        ),
 
         %% Unsubscribe
-        gen_tcp:send(Sock, serialize(<<"UNSUBSCRIBE">>,
-                                    [{<<"id">>, 0},
-                                     {<<"receipt">>, <<"12345">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"UNSUBSCRIBE">>,
+                [
+                    {<<"id">>, 0},
+                    {<<"receipt">>, <<"12345">>}
+                ]
+            )
+        ),
 
         {ok, Data2} = gen_tcp:recv(Sock, 0, 1000),
 
-        {ok, #stomp_frame{command = <<"RECEIPT">>,
-                          headers = [{<<"receipt-id">>, <<"12345">>}],
-                          body    = _}, _, _} = parse(Data2),
+        {ok,
+            #stomp_frame{
+                command = <<"RECEIPT">>,
+                headers = [{<<"receipt-id">>, <<"12345">>}],
+                body = _
+            },
+            _, _} = parse(Data2),
 
-        gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                    [{<<"destination">>, <<"/queue/foo">>}],
-                                    <<"You will not receive this msg">>)),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SEND">>,
+                [{<<"destination">>, <<"/queue/foo">>}],
+                <<"You will not receive this msg">>
+            )
+        ),
 
         {error, timeout} = gen_tcp:recv(Sock, 0, 500)
     end).
 
 t_transaction(_) ->
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                     [{<<"accept-version">>, ?STOMP_VER},
-                                      {<<"host">>, <<"127.0.0.1:61613">>},
-                                      {<<"login">>, <<"guest">>},
-                                      {<<"passcode">>, <<"guest">>},
-                                      {<<"heart-beat">>, <<"0,0">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"0,0">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                                  headers = _,
-                                  body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
         %% Subscribe
-        gen_tcp:send(Sock, serialize(<<"SUBSCRIBE">>,
-                                    [{<<"id">>, 0},
-                                     {<<"destination">>, <<"/queue/foo">>},
-                                     {<<"ack">>, <<"auto">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SUBSCRIBE">>,
+                [
+                    {<<"id">>, 0},
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"ack">>, <<"auto">>}
+                ]
+            )
+        ),
 
         %% Transaction: tx1
-        gen_tcp:send(Sock, serialize(<<"BEGIN">>,
-                                    [{<<"transaction">>, <<"tx1">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"BEGIN">>,
+                [{<<"transaction">>, <<"tx1">>}]
+            )
+        ),
 
-        gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                    [{<<"destination">>, <<"/queue/foo">>},
-                                     {<<"transaction">>, <<"tx1">>}],
-                                    <<"hello">>)),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SEND">>,
+                [
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"transaction">>, <<"tx1">>}
+                ],
+                <<"hello">>
+            )
+        ),
 
         %% You will not receive any messages
         {error, timeout} = gen_tcp:recv(Sock, 0, 1000),
 
-        gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                    [{<<"destination">>, <<"/queue/foo">>},
-                                     {<<"transaction">>, <<"tx1">>}],
-                                    <<"hello again">>)),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SEND">>,
+                [
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"transaction">>, <<"tx1">>}
+                ],
+                <<"hello again">>
+            )
+        ),
 
-        gen_tcp:send(Sock, serialize(<<"COMMIT">>,
-                                    [{<<"transaction">>, <<"tx1">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"COMMIT">>,
+                [{<<"transaction">>, <<"tx1">>}]
+            )
+        ),
 
         ct:sleep(1000),
         {ok, Data1} = gen_tcp:recv(Sock, 0, 500),
 
-        {ok, #stomp_frame{command = <<"MESSAGE">>,
-                          headers = _,
-                          body    = <<"hello">>}, Rest1, _} = parse(Data1),
+        {ok,
+            #stomp_frame{
+                command = <<"MESSAGE">>,
+                headers = _,
+                body = <<"hello">>
+            },
+            Rest1, _} = parse(Data1),
 
         %{ok, Data2} = gen_tcp:recv(Sock, 0, 500),
-        {ok, #stomp_frame{command = <<"MESSAGE">>,
-                          headers = _,
-                          body    = <<"hello again">>}, _Rest2, _} = parse(Rest1),
+        {ok,
+            #stomp_frame{
+                command = <<"MESSAGE">>,
+                headers = _,
+                body = <<"hello again">>
+            },
+            _Rest2, _} = parse(Rest1),
 
         %% Transaction: tx2
-        gen_tcp:send(Sock, serialize(<<"BEGIN">>,
-                                    [{<<"transaction">>, <<"tx2">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"BEGIN">>,
+                [{<<"transaction">>, <<"tx2">>}]
+            )
+        ),
 
-        gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                    [{<<"destination">>, <<"/queue/foo">>},
-                                     {<<"transaction">>, <<"tx2">>}],
-                                    <<"hello">>)),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SEND">>,
+                [
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"transaction">>, <<"tx2">>}
+                ],
+                <<"hello">>
+            )
+        ),
 
-        gen_tcp:send(Sock, serialize(<<"ABORT">>,
-                                    [{<<"transaction">>, <<"tx2">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"ABORT">>,
+                [{<<"transaction">>, <<"tx2">>}]
+            )
+        ),
 
         %% You will not receive any messages
         {error, timeout} = gen_tcp:recv(Sock, 0, 1000),
 
-        gen_tcp:send(Sock, serialize(<<"DISCONNECT">>,
-                                     [{<<"receipt">>, <<"12345">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"DISCONNECT">>,
+                [{<<"receipt">>, <<"12345">>}]
+            )
+        ),
 
         {ok, Data3} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"RECEIPT">>,
-                          headers = [{<<"receipt-id">>, <<"12345">>}],
-                          body    = _}, _, _} = parse(Data3)
+        {ok,
+            #stomp_frame{
+                command = <<"RECEIPT">>,
+                headers = [{<<"receipt-id">>, <<"12345">>}],
+                body = _
+            },
+            _, _} = parse(Data3)
     end).
 
 t_receipt_in_error(_) ->
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                     [{<<"accept-version">>, ?STOMP_VER},
-                                      {<<"host">>, <<"127.0.0.1:61613">>},
-                                      {<<"login">>, <<"guest">>},
-                                      {<<"passcode">>, <<"guest">>},
-                                      {<<"heart-beat">>, <<"0,0">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"0,0">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                          headers = _,
-                          body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
-        gen_tcp:send(Sock, serialize(<<"ABORT">>,
-                                    [{<<"transaction">>, <<"tx1">>},
-                                     {<<"receipt">>, <<"12345">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"ABORT">>,
+                [
+                    {<<"transaction">>, <<"tx1">>},
+                    {<<"receipt">>, <<"12345">>}
+                ]
+            )
+        ),
 
         {ok, Data1} = gen_tcp:recv(Sock, 0),
-        {ok, Frame = #stomp_frame{command = <<"ERROR">>,
-                          headers = _,
-                          body    = <<"Transaction tx1 not found">>}, _, _} = parse(Data1),
+        {ok,
+            Frame = #stomp_frame{
+                command = <<"ERROR">>,
+                headers = _,
+                body = <<"Transaction tx1 not found">>
+            },
+            _, _} = parse(Data1),
 
-         <<"12345">> = proplists:get_value(<<"receipt-id">>, Frame#stomp_frame.headers)
+        <<"12345">> = proplists:get_value(<<"receipt-id">>, Frame#stomp_frame.headers)
     end).
 
 t_ack(_) ->
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                     [{<<"accept-version">>, ?STOMP_VER},
-                                      {<<"host">>, <<"127.0.0.1:61613">>},
-                                      {<<"login">>, <<"guest">>},
-                                      {<<"passcode">>, <<"guest">>},
-                                      {<<"heart-beat">>, <<"0,0">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"0,0">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                          headers = _,
-                          body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
         %% Subscribe
-        gen_tcp:send(Sock, serialize(<<"SUBSCRIBE">>,
-                                    [{<<"id">>, 0},
-                                     {<<"destination">>, <<"/queue/foo">>},
-                                     {<<"ack">>, <<"client">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SUBSCRIBE">>,
+                [
+                    {<<"id">>, 0},
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"ack">>, <<"client">>}
+                ]
+            )
+        ),
 
-        gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                    [{<<"destination">>, <<"/queue/foo">>}],
-                                    <<"ack test">>)),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SEND">>,
+                [{<<"destination">>, <<"/queue/foo">>}],
+                <<"ack test">>
+            )
+        ),
 
         {ok, Data1} = gen_tcp:recv(Sock, 0),
-        {ok, Frame = #stomp_frame{command = <<"MESSAGE">>,
-                                  headers = _,
-                                  body    = <<"ack test">>}, _, _} = parse(Data1),
+        {ok,
+            Frame = #stomp_frame{
+                command = <<"MESSAGE">>,
+                headers = _,
+                body = <<"ack test">>
+            },
+            _, _} = parse(Data1),
 
         AckId = proplists:get_value(<<"ack">>, Frame#stomp_frame.headers),
 
-        gen_tcp:send(Sock, serialize(<<"ACK">>,
-                                    [{<<"id">>, AckId},
-                                     {<<"receipt">>, <<"12345">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"ACK">>,
+                [
+                    {<<"id">>, AckId},
+                    {<<"receipt">>, <<"12345">>}
+                ]
+            )
+        ),
 
         {ok, Data2} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"RECEIPT">>,
-                                  headers = [{<<"receipt-id">>, <<"12345">>}],
-                                  body    = _}, _, _} = parse(Data2),
+        {ok,
+            #stomp_frame{
+                command = <<"RECEIPT">>,
+                headers = [{<<"receipt-id">>, <<"12345">>}],
+                body = _
+            },
+            _, _} = parse(Data2),
 
-        gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                    [{<<"destination">>, <<"/queue/foo">>}],
-                                    <<"nack test">>)),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SEND">>,
+                [{<<"destination">>, <<"/queue/foo">>}],
+                <<"nack test">>
+            )
+        ),
 
         {ok, Data3} = gen_tcp:recv(Sock, 0),
-        {ok, Frame1 = #stomp_frame{command = <<"MESSAGE">>,
-                                  headers = _,
-                                  body    = <<"nack test">>}, _, _} = parse(Data3),
+        {ok,
+            Frame1 = #stomp_frame{
+                command = <<"MESSAGE">>,
+                headers = _,
+                body = <<"nack test">>
+            },
+            _, _} = parse(Data3),
 
         AckId1 = proplists:get_value(<<"ack">>, Frame1#stomp_frame.headers),
 
-        gen_tcp:send(Sock, serialize(<<"NACK">>,
-                                    [{<<"id">>, AckId1},
-                                     {<<"receipt">>, <<"12345">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"NACK">>,
+                [
+                    {<<"id">>, AckId1},
+                    {<<"receipt">>, <<"12345">>}
+                ]
+            )
+        ),
 
         {ok, Data4} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"RECEIPT">>,
-                                  headers = [{<<"receipt-id">>, <<"12345">>}],
-                                  body    = _}, _, _} = parse(Data4)
+        {ok,
+            #stomp_frame{
+                command = <<"RECEIPT">>,
+                headers = [{<<"receipt-id">>, <<"12345">>}],
+                body = _
+            },
+            _, _} = parse(Data4)
     end).
 
 t_1000_msg_send(_) ->
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                     [{<<"accept-version">>, ?STOMP_VER},
-                                      {<<"host">>, <<"127.0.0.1:61613">>},
-                                      {<<"login">>, <<"guest">>},
-                                      {<<"passcode">>, <<"guest">>},
-                                      {<<"heart-beat">>, <<"0,0">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"0,0">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                          headers = _,
-                          body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
         Topic = <<"/queue/foo">>,
         SendFun = fun() ->
-            gen_tcp:send(Sock, serialize(<<"SEND">>,
-                                        [{<<"destination">>, Topic}],
-                                        <<"msgtest">>))
+            gen_tcp:send(
+                Sock,
+                serialize(
+                    <<"SEND">>,
+                    [{<<"destination">>, Topic}],
+                    <<"msgtest">>
+                )
+            )
         end,
 
         RecvFun = fun() ->
             receive
-                {deliver, Topic, _Msg}->
+                {deliver, Topic, _Msg} ->
                     ok
             after 100 ->
-                      ?assert(false, "waiting message timeout")
+                ?assert(false, "waiting message timeout")
             end
         end,
 
@@ -387,79 +651,147 @@ t_1000_msg_send(_) ->
 
 t_sticky_packets_truncate_after_headers(_) ->
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                     [{<<"accept-version">>, ?STOMP_VER},
-                                      {<<"host">>, <<"127.0.0.1:61613">>},
-                                      {<<"login">>, <<"guest">>},
-                                      {<<"passcode">>, <<"guest">>},
-                                      {<<"heart-beat">>, <<"0,0">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"0,0">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                          headers = _,
-                          body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
         Topic = <<"/queue/foo">>,
 
         emqx:subscribe(Topic),
-        gen_tcp:send(Sock, ["SEND\n",
-                            "content-length:3\n",
-                            "destination:/queue/foo\n"]),
+        gen_tcp:send(Sock, [
+            "SEND\n",
+            "content-length:3\n",
+            "destination:/queue/foo\n"
+        ]),
         timer:sleep(300),
-        gen_tcp:send(Sock, ["\nfoo",0]),
+        gen_tcp:send(Sock, ["\nfoo", 0]),
         receive
-            {deliver, Topic, _Msg}->
+            {deliver, Topic, _Msg} ->
                 ok
         after 100 ->
-                  ?assert(false, "waiting message timeout")
+            ?assert(false, "waiting message timeout")
         end
     end).
 t_rest_clienit_info(_) ->
     with_connection(fun(Sock) ->
-        gen_tcp:send(Sock, serialize(<<"CONNECT">>,
-                                     [{<<"accept-version">>, ?STOMP_VER},
-                                      {<<"host">>, <<"127.0.0.1:61613">>},
-                                      {<<"login">>, <<"guest">>},
-                                      {<<"passcode">>, <<"guest">>},
-                                      {<<"heart-beat">>, <<"0,0">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"CONNECT">>,
+                [
+                    {<<"accept-version">>, ?STOMP_VER},
+                    {<<"host">>, <<"127.0.0.1:61613">>},
+                    {<<"login">>, <<"guest">>},
+                    {<<"passcode">>, <<"guest">>},
+                    {<<"heart-beat">>, <<"0,0">>}
+                ]
+            )
+        ),
         {ok, Data} = gen_tcp:recv(Sock, 0),
-        {ok, #stomp_frame{command = <<"CONNECTED">>,
-                          headers = _,
-                          body    = _}, _, _} = parse(Data),
+        {ok,
+            #stomp_frame{
+                command = <<"CONNECTED">>,
+                headers = _,
+                body = _
+            },
+            _, _} = parse(Data),
 
         %% client lists
         {200, Clients} = request(get, "/gateway/stomp/clients"),
         ?assertEqual(1, length(maps:get(data, Clients))),
         StompClient = lists:nth(1, maps:get(data, Clients)),
         ClientId = maps:get(clientid, StompClient),
-        ClientPath = "/gateway/stomp/clients/"
-                     ++ binary_to_list(ClientId),
+        ClientPath =
+            "/gateway/stomp/clients/" ++
+                binary_to_list(ClientId),
         {200, StompClient1} = request(get, ClientPath),
         ?assertEqual(StompClient, StompClient1),
         assert_feilds_apperence(
-          [proto_name, awaiting_rel_max, inflight_cnt, disconnected_at,
-           send_msg, heap_size, connected, recv_cnt, send_pkt, mailbox_len,
-           username, recv_pkt, expiry_interval, clientid, mqueue_max,
-           send_oct, ip_address, is_bridge, awaiting_rel_cnt, mqueue_dropped,
-           mqueue_len, node, inflight_max, reductions, subscriptions_max,
-           connected_at, keepalive, created_at, clean_start,
-           subscriptions_cnt, recv_msg, send_cnt, proto_ver, recv_oct
-          ], StompClient),
+            [
+                proto_name,
+                awaiting_rel_max,
+                inflight_cnt,
+                disconnected_at,
+                send_msg,
+                heap_size,
+                connected,
+                recv_cnt,
+                send_pkt,
+                mailbox_len,
+                username,
+                recv_pkt,
+                expiry_interval,
+                clientid,
+                mqueue_max,
+                send_oct,
+                ip_address,
+                is_bridge,
+                awaiting_rel_cnt,
+                mqueue_dropped,
+                mqueue_len,
+                node,
+                inflight_max,
+                reductions,
+                subscriptions_max,
+                connected_at,
+                keepalive,
+                created_at,
+                clean_start,
+                subscriptions_cnt,
+                recv_msg,
+                send_cnt,
+                proto_ver,
+                recv_oct
+            ],
+            StompClient
+        ),
 
         %% sub & unsub
         {200, []} = request(get, ClientPath ++ "/subscriptions"),
-        gen_tcp:send(Sock, serialize(<<"SUBSCRIBE">>,
-                                    [{<<"id">>, 0},
-                                     {<<"destination">>, <<"/queue/foo">>},
-                                     {<<"ack">>, <<"client">>}])),
+        gen_tcp:send(
+            Sock,
+            serialize(
+                <<"SUBSCRIBE">>,
+                [
+                    {<<"id">>, 0},
+                    {<<"destination">>, <<"/queue/foo">>},
+                    {<<"ack">>, <<"client">>}
+                ]
+            )
+        ),
         timer:sleep(100),
 
         {200, Subs} = request(get, ClientPath ++ "/subscriptions"),
         ?assertEqual(1, length(Subs)),
         assert_feilds_apperence([topic, qos], lists:nth(1, Subs)),
 
-        {201, _} = request(post, ClientPath ++ "/subscriptions",
-                           #{topic => <<"t/a">>, qos => 1,
-                             sub_props => #{subid => <<"1001">>}}),
+        {201, _} = request(
+            post,
+            ClientPath ++ "/subscriptions",
+            #{
+                topic => <<"t/a">>,
+                qos => 1,
+                sub_props => #{subid => <<"1001">>}
+            }
+        ),
 
         {200, Subs1} = request(get, ClientPath ++ "/subscriptions"),
         ?assertEqual(2, length(Subs1)),
@@ -470,7 +802,8 @@ t_rest_clienit_info(_) ->
 
         %% kickout
         {204, _} = request(delete, ClientPath),
-        ignored = gen_server:call(emqx_cm, ignore, infinity), % sync
+        % sync
+        ignored = gen_server:call(emqx_cm, ignore, infinity),
         ok = emqx_pool:flush_async_tasks(),
         {200, Clients2} = request(get, "/gateway/stomp/clients"),
         ?assertEqual(0, length(maps:get(data, Clients2)))
@@ -484,10 +817,12 @@ t_rest_clienit_info(_) ->
 %% TODO: RateLimit, OOM,
 
 with_connection(DoFun) ->
-    {ok, Sock} = gen_tcp:connect({127, 0, 0, 1},
-                                 61613,
-                                 [binary, {packet, raw}, {active, false}],
-                                 3000),
+    {ok, Sock} = gen_tcp:connect(
+        {127, 0, 0, 1},
+        61613,
+        [binary, {packet, raw}, {active, false}],
+        3000
+    ),
     try
         DoFun(Sock)
     after
@@ -501,9 +836,10 @@ serialize(Command, Headers, Body) ->
     emqx_stomp_frame:serialize_pkt(emqx_stomp_frame:make(Command, Headers, Body), #{}).
 
 parse(Data) ->
-    ProtoEnv = #{max_headers => 10,
-                 max_header_length => 1024,
-                 max_body_length => 8192
-                },
+    ProtoEnv = #{
+        max_headers => 10,
+        max_header_length => 1024,
+        max_body_length => 8192
+    },
     Parser = emqx_stomp_frame:initial_parse_state(ProtoEnv),
     emqx_stomp_frame:parse(Data, Parser).
diff --git a/apps/emqx_gateway/test/emqx_stomp_heartbeat_SUITE.erl b/apps/emqx_gateway/test/emqx_stomp_heartbeat_SUITE.erl
index 5d1344cd3..162bc8eee 100644
--- a/apps/emqx_gateway/test/emqx_stomp_heartbeat_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_stomp_heartbeat_SUITE.erl
@@ -41,19 +41,24 @@ t_check_1(_) ->
 
 t_check_2(_) ->
     HrtBt = emqx_stomp_heartbeat:init({1, 0}),
-    #{incoming := _} = lists:foldl(fun(I, Acc) ->
-                            {ok, NAcc} = emqx_stomp_heartbeat:check(incoming, I, Acc),
-                            NAcc
-                       end, HrtBt, lists:seq(1,1000)),
+    #{incoming := _} = lists:foldl(
+        fun(I, Acc) ->
+            {ok, NAcc} = emqx_stomp_heartbeat:check(incoming, I, Acc),
+            NAcc
+        end,
+        HrtBt,
+        lists:seq(1, 1000)
+    ),
     ok.
 
 t_info(_) ->
     HrtBt = emqx_stomp_heartbeat:init({100, 100}),
-    #{incoming := _,
-      outgoing := _} = emqx_stomp_heartbeat:info(HrtBt).
+    #{
+        incoming := _,
+        outgoing := _
+    } = emqx_stomp_heartbeat:info(HrtBt).
 
 t_interval(_) ->
     HrtBt = emqx_stomp_heartbeat:init({1, 0}),
     1 = emqx_stomp_heartbeat:interval(incoming, HrtBt),
     undefined = emqx_stomp_heartbeat:interval(outgoing, HrtBt).
-
diff --git a/apps/emqx_gateway/test/emqx_tlv_SUITE.erl b/apps/emqx_gateway/test/emqx_tlv_SUITE.erl
index 37cc8cd13..c77599d41 100644
--- a/apps/emqx_gateway/test/emqx_tlv_SUITE.erl
+++ b/apps/emqx_gateway/test/emqx_tlv_SUITE.erl
@@ -30,8 +30,18 @@
 %%--------------------------------------------------------------------
 
 all() ->
-    [case01, case02, case03, case03_0,
-     case04, case05, case06, case07, case08, case09].
+    [
+        case01,
+        case02,
+        case03,
+        case03_0,
+        case04,
+        case05,
+        case06,
+        case07,
+        case08,
+        case09
+    ].
 
 init_per_suite(Config) ->
     Config.
@@ -44,9 +54,9 @@ end_per_suite(Config) ->
 %%--------------------------------------------------------------------
 
 case01(_Config) ->
-    Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20,
-             16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41,
-             16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65>>,
+    Data =
+        <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C,
+            16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65>>,
     R = emqx_lwm2m_tlv:parse(Data),
     Exp = [
         #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>}
@@ -58,28 +68,32 @@ case01(_Config) ->
 case02(_Config) ->
     Data = <<16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [ #{tlv_multiple_resource => 16#06,
-              value => [ #{tlv_resource_instance => 16#00, value => <<1>>},
-                         #{tlv_resource_instance => 16#01, value => <<5>>}]}
-          ],
+    Exp = [
+        #{
+            tlv_multiple_resource => 16#06,
+            value => [
+                #{tlv_resource_instance => 16#00, value => <<1>>},
+                #{tlv_resource_instance => 16#01, value => <<5>>}
+            ]
+        }
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
 
 case03(_Config) ->
-    Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20,
-             16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41,
-             16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8,
-             16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77,
-             16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32,
-             16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74,
-             16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30,
-             16#30, 16#31, 16#32, 16#33>>,
+    Data =
+        <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C,
+            16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01,
+            16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74,
+            16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8,
+            16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [ #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
-            #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
-            #{tlv_resource_with_value => 16#02, value => <<"345000123">>}
-          ],
+    Exp = [
+        #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
+        #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
+        #{tlv_resource_with_value => 16#02, value => <<"345000123">>}
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
@@ -87,50 +101,69 @@ case03(_Config) ->
 case03_0(_Config) ->
     Data = <<16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 16#01, 16#36, 16#01>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [ #{tlv_multiple_resource => 16#02,
-              value => [ #{tlv_resource_instance => 16#7F, value => <<16#07>>},
-                         #{tlv_resource_instance => 16#0136, value => <<16#01>>}
-                       ]}
-          ],
+    Exp = [
+        #{
+            tlv_multiple_resource => 16#02,
+            value => [
+                #{tlv_resource_instance => 16#7F, value => <<16#07>>},
+                #{tlv_resource_instance => 16#0136, value => <<16#01>>}
+            ]
+        }
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
 
 case04(_Config) ->
     % 6.4.3.1 Single Object Instance Request Example
-    Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20,
-             16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41,
-             16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8,
-             16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77,
-             16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32,
-             16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74,
-             16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30,
-             16#30, 16#31, 16#32, 16#33, 16#C3, 16#03, 16#31, 16#2E,
-             16#30, 16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01,
-             16#05, 16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8,
-             16#42, 16#01, 16#13, 16#88, 16#87, 16#08, 16#41, 16#00,
-             16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1, 16#09, 16#64,
-             16#C1, 16#0A, 16#0F, 16#83, 16#0B, 16#41, 16#00, 16#00,
-             16#C4, 16#0D, 16#51, 16#82, 16#42, 16#8F, 16#C6, 16#0E,
-             16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30, 16#C1, 16#10, 16#55>>,
+    Data =
+        <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C,
+            16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01,
+            16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74,
+            16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8,
+            16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33, 16#C3,
+            16#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05,
+            16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8, 16#42, 16#01, 16#13, 16#88, 16#87,
+            16#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1, 16#09, 16#64, 16#C1,
+            16#0A, 16#0F, 16#83, 16#0B, 16#41, 16#00, 16#00, 16#C4, 16#0D, 16#51, 16#82, 16#42,
+            16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30, 16#C1, 16#10, 16#55>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [ #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
-            #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
-            #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
-            #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
-            #{tlv_multiple_resource => 16#06, value => [#{tlv_resource_instance => 16#00, value => <<1>>},
-                                                        #{tlv_resource_instance => 16#01, value => <<5>>}]},
-            #{tlv_multiple_resource => 16#07, value => [#{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
-                                                        #{tlv_resource_instance => 16#01, value => <<16#1388:16>>}]},
-            #{tlv_multiple_resource => 16#08, value => [#{tlv_resource_instance => 16#00, value => <<16#7d>>},
-                                                        #{tlv_resource_instance => 16#01, value => <<16#0384:16>>}]},
-            #{tlv_resource_with_value => 16#09, value => <<16#64>>},
-            #{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
-            #{tlv_multiple_resource => 16#0B, value => [#{tlv_resource_instance => 16#00, value => <<16#00>>}]},
-            #{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
-            #{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
-            #{tlv_resource_with_value => 16#10, value => <<"U">>}
-          ],
+    Exp = [
+        #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
+        #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
+        #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
+        #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
+        #{
+            tlv_multiple_resource => 16#06,
+            value => [
+                #{tlv_resource_instance => 16#00, value => <<1>>},
+                #{tlv_resource_instance => 16#01, value => <<5>>}
+            ]
+        },
+        #{
+            tlv_multiple_resource => 16#07,
+            value => [
+                #{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
+                #{tlv_resource_instance => 16#01, value => <<16#1388:16>>}
+            ]
+        },
+        #{
+            tlv_multiple_resource => 16#08,
+            value => [
+                #{tlv_resource_instance => 16#00, value => <<16#7d>>},
+                #{tlv_resource_instance => 16#01, value => <<16#0384:16>>}
+            ]
+        },
+        #{tlv_resource_with_value => 16#09, value => <<16#64>>},
+        #{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
+        #{
+            tlv_multiple_resource => 16#0B,
+            value => [#{tlv_resource_instance => 16#00, value => <<16#00>>}]
+        },
+        #{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
+        #{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
+        #{tlv_resource_with_value => 16#10, value => <<"U">>}
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
@@ -138,43 +171,59 @@ case04(_Config) ->
 case05(_Config) ->
     % 6.4.3.2 Multiple Object Instance Request Examples
     %   A) Request on Single-Instance Object
-    Data = <<16#08, 16#00, 16#79, 16#C8, 16#00, 16#14, 16#4F, 16#70,
-             16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C,
-             16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E,
-             16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67,
-             16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74,
-             16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69,
-             16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34,
-             16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33, 16#C3,
-             16#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00,
-             16#01, 16#41, 16#01, 16#05, 16#88, 16#07, 16#08, 16#42,
-             16#00, 16#0E, 16#D8, 16#42, 16#01, 16#13, 16#88, 16#87,
-             16#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84,
-             16#C1, 16#09, 16#64, 16#C1, 16#0A, 16#0F, 16#83, 16#0B,
-             16#41, 16#00, 16#00, 16#C4, 16#0D, 16#51, 16#82, 16#42,
-             16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30,
-             16#30, 16#C1, 16#10, 16#55>>,
+    Data =
+        <<16#08, 16#00, 16#79, 16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F,
+            16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63,
+            16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69,
+            16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65,
+            16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31,
+            16#32, 16#33, 16#C3, 16#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00, 16#01,
+            16#41, 16#01, 16#05, 16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8, 16#42, 16#01,
+            16#13, 16#88, 16#87, 16#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1,
+            16#09, 16#64, 16#C1, 16#0A, 16#0F, 16#83, 16#0B, 16#41, 16#00, 16#00, 16#C4, 16#0D,
+            16#51, 16#82, 16#42, 16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30,
+            16#C1, 16#10, 16#55>>,
     R = emqx_lwm2m_tlv:parse(Data),
     Exp = [
-        #{tlv_object_instance => 16#00, value => [
-            #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
-            #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
-            #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
-            #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
-            #{tlv_multiple_resource => 16#06, value => [#{tlv_resource_instance => 16#00, value => <<1>>},
-                                                        #{tlv_resource_instance => 16#01, value => <<5>>}]},
-            #{tlv_multiple_resource => 16#07, value => [#{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
-                                                        #{tlv_resource_instance => 16#01, value => <<16#1388:16>>}
-            ]},
-            #{tlv_multiple_resource => 16#08, value => [#{tlv_resource_instance => 16#00, value => <<16#7d>>},
-                                                        #{tlv_resource_instance => 16#01, value => <<16#0384:16>>}]},
-            #{tlv_resource_with_value => 16#09, value => <<16#64>>},
-            #{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
-            #{tlv_multiple_resource => 16#0B, value => [#{tlv_resource_instance => 16#00, value => <<16#00>>}]},
-            #{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
-            #{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
-            #{tlv_resource_with_value => 16#10, value => <<"U">>}
-        ]}
+        #{
+            tlv_object_instance => 16#00,
+            value => [
+                #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
+                #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
+                #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
+                #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
+                #{
+                    tlv_multiple_resource => 16#06,
+                    value => [
+                        #{tlv_resource_instance => 16#00, value => <<1>>},
+                        #{tlv_resource_instance => 16#01, value => <<5>>}
+                    ]
+                },
+                #{
+                    tlv_multiple_resource => 16#07,
+                    value => [
+                        #{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
+                        #{tlv_resource_instance => 16#01, value => <<16#1388:16>>}
+                    ]
+                },
+                #{
+                    tlv_multiple_resource => 16#08,
+                    value => [
+                        #{tlv_resource_instance => 16#00, value => <<16#7d>>},
+                        #{tlv_resource_instance => 16#01, value => <<16#0384:16>>}
+                    ]
+                },
+                #{tlv_resource_with_value => 16#09, value => <<16#64>>},
+                #{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
+                #{
+                    tlv_multiple_resource => 16#0B,
+                    value => [#{tlv_resource_instance => 16#00, value => <<16#00>>}]
+                },
+                #{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
+                #{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
+                #{tlv_resource_with_value => 16#10, value => <<"U">>}
+            ]
+        }
     ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
@@ -183,25 +232,41 @@ case05(_Config) ->
 case06(_Config) ->
     % 6.4.3.2 Multiple Object Instance Request Examples
     %   B) Request on Multiple-Instances Object having 2 instances
-    Data = <<16#08, 16#00, 16#0E, 16#C1, 16#00, 16#01, 16#C1, 16#01,
-             16#00, 16#83, 16#02, 16#41, 16#7F, 16#07, 16#C1, 16#03,
-             16#7F, 16#08, 16#02, 16#12, 16#C1, 16#00, 16#03, 16#C1,
-             16#01, 16#00, 16#87, 16#02, 16#41, 16#7F, 16#07, 16#61,
-             16#01, 16#36, 16#01, 16#C1, 16#03, 16#7F>>,
+    Data =
+        <<16#08, 16#00, 16#0E, 16#C1, 16#00, 16#01, 16#C1, 16#01, 16#00, 16#83, 16#02, 16#41, 16#7F,
+            16#07, 16#C1, 16#03, 16#7F, 16#08, 16#02, 16#12, 16#C1, 16#00, 16#03, 16#C1, 16#01,
+            16#00, 16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 16#01, 16#36, 16#01, 16#C1, 16#03,
+            16#7F>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [#{tlv_object_instance => 16#00, value => [#{tlv_resource_with_value => 16#00, value => <<16#01>>},
-                                                     #{tlv_resource_with_value => 16#01, value => <<16#00>>},
-                                                     #{tlv_multiple_resource => 16#02,
-                                                       value => [#{tlv_resource_instance => 16#7F, value => <<16#07>>}]},
-                                                     #{tlv_resource_with_value => 16#03, value => <<16#7F>>}
-                                                    ]},
-           #{tlv_object_instance => 16#02, value => [#{tlv_resource_with_value => 16#00, value => <<16#03>>},
-                                                     #{tlv_resource_with_value => 16#01, value => <<16#00>>},
-                                                     #{tlv_multiple_resource => 16#02,
-                                                       value => [#{tlv_resource_instance => 16#7F, value => <<16#07>>},
-                                                                 #{tlv_resource_instance => 16#0136, value => <<16#01>>}]},
-                                                     #{tlv_resource_with_value => 16#03, value => <<16#7F>>}]}
-          ],
+    Exp = [
+        #{
+            tlv_object_instance => 16#00,
+            value => [
+                #{tlv_resource_with_value => 16#00, value => <<16#01>>},
+                #{tlv_resource_with_value => 16#01, value => <<16#00>>},
+                #{
+                    tlv_multiple_resource => 16#02,
+                    value => [#{tlv_resource_instance => 16#7F, value => <<16#07>>}]
+                },
+                #{tlv_resource_with_value => 16#03, value => <<16#7F>>}
+            ]
+        },
+        #{
+            tlv_object_instance => 16#02,
+            value => [
+                #{tlv_resource_with_value => 16#00, value => <<16#03>>},
+                #{tlv_resource_with_value => 16#01, value => <<16#00>>},
+                #{
+                    tlv_multiple_resource => 16#02,
+                    value => [
+                        #{tlv_resource_instance => 16#7F, value => <<16#07>>},
+                        #{tlv_resource_instance => 16#0136, value => <<16#01>>}
+                    ]
+                },
+                #{tlv_resource_with_value => 16#03, value => <<16#7F>>}
+            ]
+        }
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
@@ -209,16 +274,21 @@ case06(_Config) ->
 case07(_Config) ->
     % 6.4.3.2 Multiple Object Instance Request Examples
     %   C) Request on Multiple-Instances Object having 1 instance only
-    Data = <<16#08, 16#00, 16#0F, 16#C1, 16#00, 16#01, 16#C4, 16#01,
-             16#00, 16#01, 16#51, 16#80, 16#C1, 16#06, 16#01, 16#C1,
-             16#07, 16#55>>,
+    Data =
+        <<16#08, 16#00, 16#0F, 16#C1, 16#00, 16#01, 16#C4, 16#01, 16#00, 16#01, 16#51, 16#80, 16#C1,
+            16#06, 16#01, 16#C1, 16#07, 16#55>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [#{tlv_object_instance => 16#00,
-             value => [#{tlv_resource_with_value => 16#00, value => <<16#01>>},
-                       #{tlv_resource_with_value => 16#01, value => <<86400:32>>},
-                       #{tlv_resource_with_value => 16#06, value => <<16#01>>},
-                       #{tlv_resource_with_value => 16#07, value => <<$U>>}]}
-          ],
+    Exp = [
+        #{
+            tlv_object_instance => 16#00,
+            value => [
+                #{tlv_resource_with_value => 16#00, value => <<16#01>>},
+                #{tlv_resource_with_value => 16#01, value => <<86400:32>>},
+                #{tlv_resource_with_value => 16#06, value => <<16#01>>},
+                #{tlv_resource_with_value => 16#07, value => <<$U>>}
+            ]
+        }
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
@@ -226,17 +296,22 @@ case07(_Config) ->
 case08(_Config) ->
     % 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource
     %   Example 1) request to Object 65 Instance 0: Read /65/0
-    Data = <<16#88, 16#00, 16#0C, 16#44, 16#00, 16#00, 16#42, 16#00,
-             16#00, 16#44, 16#01, 16#00, 16#42, 16#00, 16#01, 16#C8,
-             16#01, 16#0D, 16#38, 16#36, 16#31, 16#33, 16#38, 16#30,
-             16#30, 16#37, 16#35, 16#35, 16#35, 16#30, 16#30, 16#C4,
-             16#02, 16#12, 16#34, 16#56, 16#78>>,
+    Data =
+        <<16#88, 16#00, 16#0C, 16#44, 16#00, 16#00, 16#42, 16#00, 16#00, 16#44, 16#01, 16#00, 16#42,
+            16#00, 16#01, 16#C8, 16#01, 16#0D, 16#38, 16#36, 16#31, 16#33, 16#38, 16#30, 16#30,
+            16#37, 16#35, 16#35, 16#35, 16#30, 16#30, 16#C4, 16#02, 16#12, 16#34, 16#56, 16#78>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [#{tlv_multiple_resource => 16#00, value => [#{tlv_resource_instance => 16#00, value => <<16#00, 16#42, 16#00, 16#00>>},
-                                                       #{tlv_resource_instance => 16#01, value => <<16#00, 16#42, 16#00, 16#01>>}]},
-           #{tlv_resource_with_value => 16#01, value => <<"8613800755500">>},
-           #{tlv_resource_with_value => 16#02, value => <<16#12345678:32>>}
-          ],
+    Exp = [
+        #{
+            tlv_multiple_resource => 16#00,
+            value => [
+                #{tlv_resource_instance => 16#00, value => <<16#00, 16#42, 16#00, 16#00>>},
+                #{tlv_resource_instance => 16#01, value => <<16#00, 16#42, 16#00, 16#01>>}
+            ]
+        },
+        #{tlv_resource_with_value => 16#01, value => <<"8613800755500">>},
+        #{tlv_resource_with_value => 16#02, value => <<16#12345678:32>>}
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
@@ -244,26 +319,33 @@ case08(_Config) ->
 case09(_Config) ->
     % 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource
     %   Example 2) request to Object 66: Read /66: TLV payload will contain 2 Object Instances
-    Data = <<16#08, 16#00, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 16#79,
-             16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20,
-             16#31, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#65,
-             16#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E,
-             16#32, 16#33, 16#34, 16#C4, 16#02, 16#00, 16#43, 16#00,
-             16#00, 16#08, 16#01, 16#26, 16#C8, 16#00, 16#0B, 16#6D,
-             16#79, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65,
-             16#20, 16#32, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74,
-             16#65, 16#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35,
-             16#2E, 16#32, 16#33, 16#35, 16#C4, 16#02, 16#FF, 16#FF,
-             16#FF, 16#FF>>,
+    Data =
+        <<16#08, 16#00, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 16#79, 16#53, 16#65, 16#72, 16#76, 16#69,
+            16#63, 16#65, 16#20, 16#31, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#65, 16#72,
+            16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E, 16#32, 16#33, 16#34, 16#C4, 16#02,
+            16#00, 16#43, 16#00, 16#00, 16#08, 16#01, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 16#79,
+            16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, 16#32, 16#C8, 16#01, 16#0F,
+            16#49, 16#6E, 16#74, 16#65, 16#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E,
+            16#32, 16#33, 16#35, 16#C4, 16#02, 16#FF, 16#FF, 16#FF, 16#FF>>,
     R = emqx_lwm2m_tlv:parse(Data),
-    Exp = [#{tlv_object_instance => 16#00, value => [#{tlv_resource_with_value => 16#00, value => <<"myService 1">>},
-                                                     #{tlv_resource_with_value => 16#01, value => <<"Internet.15.234">>},
-                                                     #{tlv_resource_with_value => 16#02, value => <<16#00, 16#43, 16#00, 16#00>>}]},
-           #{tlv_object_instance => 16#01, value => [#{tlv_resource_with_value => 16#00, value => <<"myService 2">>},
-                                                     #{tlv_resource_with_value => 16#01, value => <<"Internet.15.235">>},
-                                                     #{tlv_resource_with_value => 16#02, value => <<16#FF, 16#FF, 16#FF, 16#FF>>}
-                                                    ]}
-          ],
+    Exp = [
+        #{
+            tlv_object_instance => 16#00,
+            value => [
+                #{tlv_resource_with_value => 16#00, value => <<"myService 1">>},
+                #{tlv_resource_with_value => 16#01, value => <<"Internet.15.234">>},
+                #{tlv_resource_with_value => 16#02, value => <<16#00, 16#43, 16#00, 16#00>>}
+            ]
+        },
+        #{
+            tlv_object_instance => 16#01,
+            value => [
+                #{tlv_resource_with_value => 16#00, value => <<"myService 2">>},
+                #{tlv_resource_with_value => 16#01, value => <<"Internet.15.235">>},
+                #{tlv_resource_with_value => 16#02, value => <<16#FF, 16#FF, 16#FF, 16#FF>>}
+            ]
+        }
+    ],
     ?assertEqual(Exp, R),
     EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
     ?assertEqual(EncodedBinary, Data).
diff --git a/apps/emqx_gateway/test/props/emqx_sn_proper_types.erl b/apps/emqx_gateway/test/props/emqx_sn_proper_types.erl
index 85592557d..b30ab8a7c 100644
--- a/apps/emqx_gateway/test/props/emqx_sn_proper_types.erl
+++ b/apps/emqx_gateway/test/props/emqx_sn_proper_types.erl
@@ -31,20 +31,28 @@
 %%--------------------------------------------------------------------
 
 'ADVERTISE'() ->
-    ?LET({GwId, Duration}, {gateway_id(), duration()},
-         ?SN_ADVERTISE_MSG(GwId, Duration)).
+    ?LET(
+        {GwId, Duration},
+        {gateway_id(), duration()},
+        ?SN_ADVERTISE_MSG(GwId, Duration)
+    ).
 
 'SEARCHGW'() ->
     ?LET(Radius, radius(), ?SN_SEARCHGW_MSG(Radius)).
 
 'GWINFO'() ->
-    ?LET({GwId, GwAddr}, {gateway_id(), gateway_addr()},
-        ?SN_GWINFO_MSG(GwId, GwAddr)).
+    ?LET(
+        {GwId, GwAddr},
+        {gateway_id(), gateway_addr()},
+        ?SN_GWINFO_MSG(GwId, GwAddr)
+    ).
 
 'CONNECT'() ->
-    ?LET({Flags, ProtocolId, Duration, ClientId},
-         {conn_flags(), protocol_id(), duration(), clientid()},
-         ?SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId)).
+    ?LET(
+        {Flags, ProtocolId, Duration, ClientId},
+        {conn_flags(), protocol_id(), duration(), clientid()},
+        ?SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId)
+    ).
 
 'CONNACK'() ->
     ?LET(Rc, return_code(), ?SN_CONNACK_MSG(Rc)).
@@ -53,8 +61,11 @@
     ?SN_WILLTOPICREQ_MSG().
 
 'WILLTOPIC'() ->
-    ?LET({Flags, Topic}, {will_topic_flags(), topic()},
-        ?SN_WILLTOPIC_MSG(Flags, Topic)).
+    ?LET(
+        {Flags, Topic},
+        {will_topic_flags(), topic()},
+        ?SN_WILLTOPIC_MSG(Flags, Topic)
+    ).
 
 'WILLTOPCI_EMPTY'() ->
     ?SN_WILLTOPIC_EMPTY_MSG.
@@ -66,48 +77,67 @@
     ?LET(Payload, binary(), ?SN_WILLMSG_MSG(Payload)).
 
 'REGISTER'() ->
-    ?LET({MsgId, TopicName},
-         {message_id(), topic()},
-         ?SN_REGISTER_MSG(16#0000, MsgId, TopicName)).
+    ?LET(
+        {MsgId, TopicName},
+        {message_id(), topic()},
+        ?SN_REGISTER_MSG(16#0000, MsgId, TopicName)
+    ).
 
 'REGACK'() ->
-    ?LET({TopicId, MsgId, Rc},
-         {topic_id(), message_id(), return_code()},
-         ?SN_REGACK_MSG(TopicId, MsgId, Rc)).
+    ?LET(
+        {TopicId, MsgId, Rc},
+        {topic_id(), message_id(), return_code()},
+        ?SN_REGACK_MSG(TopicId, MsgId, Rc)
+    ).
 
 'PUBLISH'() ->
-    ?LET({Flags, MsgId, Data},
-         {publish_flags(), message_id(), binary()},
-         ?SN_PUBLISH_MSG(Flags, pub_topic_by_type(Flags), MsgId, Data)).
+    ?LET(
+        {Flags, MsgId, Data},
+        {publish_flags(), message_id(), binary()},
+        ?SN_PUBLISH_MSG(Flags, pub_topic_by_type(Flags), MsgId, Data)
+    ).
 
 'PUBACK'() ->
-    ?LET({TopicId, MsgId, Rc},
-         {topic_id(), message_id(), return_code()},
-         ?SN_PUBACK_MSG(TopicId, MsgId, Rc)).
+    ?LET(
+        {TopicId, MsgId, Rc},
+        {topic_id(), message_id(), return_code()},
+        ?SN_PUBACK_MSG(TopicId, MsgId, Rc)
+    ).
 
 'PUBCOMP_REC_REL'() ->
-    ?LET({Type, MsgId},
-         {oneof([?SN_PUBREC, ?SN_PUBREL, ?SN_PUBCOMP]), message_id()},
-        ?SN_PUBREC_MSG(Type, MsgId)).
+    ?LET(
+        {Type, MsgId},
+        {oneof([?SN_PUBREC, ?SN_PUBREL, ?SN_PUBCOMP]), message_id()},
+        ?SN_PUBREC_MSG(Type, MsgId)
+    ).
 
 'SUBSCRIBE'() ->
-    ?LET({Flags, MsgId},
-         {subscribe_flags(), message_id()},
-         ?SN_SUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))).
+    ?LET(
+        {Flags, MsgId},
+        {subscribe_flags(), message_id()},
+        ?SN_SUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))
+    ).
 
 'SUBACK'() ->
-    ?LET({Flags, TopicId, MsgId, Rc},
-         {suback_flags(), topic_id(), message_id(), return_code()},
-         ?SN_SUBACK_MSG(Flags, TopicId, MsgId, Rc)).
+    ?LET(
+        {Flags, TopicId, MsgId, Rc},
+        {suback_flags(), topic_id(), message_id(), return_code()},
+        ?SN_SUBACK_MSG(Flags, TopicId, MsgId, Rc)
+    ).
 
 'UNSUBSCRIBE'() ->
-    ?LET({Flags, MsgId},
-         {unsubscribe_flags(), message_id()},
-         ?SN_UNSUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))).
+    ?LET(
+        {Flags, MsgId},
+        {unsubscribe_flags(), message_id()},
+        ?SN_UNSUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))
+    ).
 
 'UNSUBACK'() ->
-    ?LET(MsgId, message_id(),
-        ?SN_UNSUBACK_MSG(MsgId)).
+    ?LET(
+        MsgId,
+        message_id(),
+        ?SN_UNSUBACK_MSG(MsgId)
+    ).
 
 'PINGREQ'() ->
     ?LET(ClientId, clientid(), ?SN_PINGREQ_MSG(ClientId)).
@@ -119,9 +149,11 @@
     ?LET(Duration, oneof([duration(), undefined]), ?SN_DISCONNECT_MSG(Duration)).
 
 'WILLTOPICUPD'() ->
-    ?LET({Flags, Topic},
-         {willtopic_upd_flags(), topic()},
-         ?SN_WILLTOPICUPD_MSG(Flags, Topic)).
+    ?LET(
+        {Flags, Topic},
+        {willtopic_upd_flags(), topic()},
+        ?SN_WILLTOPICUPD_MSG(Flags, Topic)
+    ).
 
 'WILLTOPICRESP'() ->
     ?LET(Rc, return_code(), ?SN_WILLTOPICRESP_MSG(Rc)).
@@ -148,45 +180,69 @@ radius() ->
     range(0, 16#ff).
 
 gateway_addr() ->
-    ?LET(L, oneof([0, range(2, 16#ff)]),
-         begin
+    ?LET(
+        L,
+        oneof([0, range(2, 16#ff)]),
+        begin
             Addr = list_to_binary([rand:uniform(256) - 1 || _ <- lists:seq(1, L)]),
             <<(byte_size(Addr)):8, Addr/binary>>
-         end).
+        end
+    ).
 
 mqtt_sn_flags() ->
-    ?LET({Dup, Qos, Retain, Will, CleanStart, IdType},
-         {boolean(), mqtt_sn_qos(), boolean(), boolean(), boolean(), topic_id_type()},
-         #mqtt_sn_flags{dup = Dup, qos = Qos, retain = Retain, will = Will,
-                        clean_start = CleanStart, topic_id_type = IdType}).
+    ?LET(
+        {Dup, Qos, Retain, Will, CleanStart, IdType},
+        {boolean(), mqtt_sn_qos(), boolean(), boolean(), boolean(), topic_id_type()},
+        #mqtt_sn_flags{
+            dup = Dup,
+            qos = Qos,
+            retain = Retain,
+            will = Will,
+            clean_start = CleanStart,
+            topic_id_type = IdType
+        }
+    ).
 
 conn_flags() ->
-    ?LET({Will, CleanStart}, {boolean(), boolean()},
-         #mqtt_sn_flags{will = Will,clean_start = CleanStart}).
+    ?LET(
+        {Will, CleanStart},
+        {boolean(), boolean()},
+        #mqtt_sn_flags{will = Will, clean_start = CleanStart}
+    ).
 
 will_topic_flags() ->
-    ?LET({Qos, Retain}, {mqtt_sn_qos(), boolean()},
-         #mqtt_sn_flags{qos = Qos, retain = Retain}).
+    ?LET(
+        {Qos, Retain},
+        {mqtt_sn_qos(), boolean()},
+        #mqtt_sn_flags{qos = Qos, retain = Retain}
+    ).
 
 publish_flags() ->
-     ?LET({Dup, Qos, Retain, IdType},
-         {boolean(), publish_qos(), boolean(), pub_topic_id_type()},
-         #mqtt_sn_flags{dup = Dup, qos = Qos, retain = Retain, topic_id_type = IdType}).
+    ?LET(
+        {Dup, Qos, Retain, IdType},
+        {boolean(), publish_qos(), boolean(), pub_topic_id_type()},
+        #mqtt_sn_flags{dup = Dup, qos = Qos, retain = Retain, topic_id_type = IdType}
+    ).
 
 subscribe_flags() ->
-     ?LET({Dup, Qos, IdType},
-         {boolean(), publish_qos(), topic_id_type()},
-         #mqtt_sn_flags{dup = Dup, qos = Qos, topic_id_type = IdType}).
+    ?LET(
+        {Dup, Qos, IdType},
+        {boolean(), publish_qos(), topic_id_type()},
+        #mqtt_sn_flags{dup = Dup, qos = Qos, topic_id_type = IdType}
+    ).
 
 suback_flags() ->
     ?LET(Qos, mqtt_sn_qos(), #mqtt_sn_flags{qos = Qos}).
 
-unsubscribe_flags()->
+unsubscribe_flags() ->
     ?LET(IdType, topic_id_type(), #mqtt_sn_flags{topic_id_type = IdType}).
 
 willtopic_upd_flags() ->
-    ?LET({Qos, Retain}, {mqtt_sn_qos(), boolean()},
-         #mqtt_sn_flags{qos = Qos, retain = Retain}).
+    ?LET(
+        {Qos, Retain},
+        {mqtt_sn_qos(), boolean()},
+        #mqtt_sn_flags{qos = Qos, retain = Retain}
+    ).
 
 mqtt_sn_qos() ->
     oneof([0, 1, 2]).
@@ -209,10 +265,13 @@ protocol_id() ->
 %% The ClientId field has a variable length and contains
 %% a 1-23 character long string
 clientid() ->
-    ?LET(L, range(1, 23),
+    ?LET(
+        L,
+        range(1, 23),
         begin
             list_to_binary([rand_09_az_AZ() || _ <- lists:seq(1, L)])
-        end).
+        end
+    ).
 
 return_code() ->
     range(0, 16#ff).
@@ -231,14 +290,14 @@ message_id() ->
     range(0, 16#ffff).
 
 topic_by_type(Flags) ->
-   case Flags#mqtt_sn_flags.topic_id_type of
-       0 -> topic();
-       1 -> range(0, 16#ffff);
-       2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()])
-   end.
+    case Flags#mqtt_sn_flags.topic_id_type of
+        0 -> topic();
+        1 -> range(0, 16#ffff);
+        2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()])
+    end.
 
 pub_topic_by_type(Flags) ->
-   case Flags#mqtt_sn_flags.topic_id_type of
-       2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()]);
-       _ -> range(0, 16#ffff)
-   end.
+    case Flags#mqtt_sn_flags.topic_id_type of
+        2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()]);
+        _ -> range(0, 16#ffff)
+    end.
diff --git a/apps/emqx_gateway/test/props/prop_emqx_sn_frame.erl b/apps/emqx_gateway/test/props/prop_emqx_sn_frame.erl
index bce41ec1e..c4d15a465 100644
--- a/apps/emqx_gateway/test/props/prop_emqx_sn_frame.erl
+++ b/apps/emqx_gateway/test/props/prop_emqx_sn_frame.erl
@@ -22,10 +22,14 @@
 -compile({no_auto_import, [register/1]}).
 
 -define(ALL(Vars, Types, Exprs),
-        ?SETUP(fun() ->
+    ?SETUP(
+        fun() ->
             State = do_setup(),
             fun() -> do_teardown(State) end
-         end, ?FORALL(Vars, Types, Exprs))).
+        end,
+        ?FORALL(Vars, Types, Exprs)
+    )
+).
 
 parse(D) ->
     {ok, P, _Rest, _State} = emqx_sn_frame:parse(D, #{}),
@@ -39,11 +43,14 @@ serialize(P) ->
 %%--------------------------------------------------------------------
 
 prop_parse_and_serialize() ->
-    ?ALL(Msg, mqtt_sn_message(),
-         begin
-             Msg = parse(serialize(Msg)),
-             true
-         end).
+    ?ALL(
+        Msg,
+        mqtt_sn_message(),
+        begin
+            Msg = parse(serialize(Msg)),
+            true
+        end
+    ).
 
 %%--------------------------------------------------------------------
 %% Helper
@@ -61,18 +68,32 @@ do_teardown(_) ->
 
 mqtt_sn_message() ->
     M = emqx_sn_proper_types,
-    oneof([ M:'ADVERTISE'(),       M:'SEARCHGW'()
-          , M:'GWINFO'(),          M:'CONNECT'()
-          , M:'CONNACK'(),         M:'WILLTOTPICREQ'()
-          , M:'WILLTOPIC'(),       M:'WILLTOPCI_EMPTY'()
-          , M:'WILLMESSAGEREQ'(),  M:'WILLMESSAGE'()
-          , M:'REGISTER'(),        M:'REGACK'()
-          , M:'PUBLISH'(),         M:'PUBACK'()
-          , M:'PUBCOMP_REC_REL'(), M:'SUBSCRIBE'()
-          , M:'SUBACK'(),          M:'UNSUBSCRIBE'()
-          , M:'UNSUBACK'(),        M:'PINGREQ'()
-          , M:'PINGRESP'(),        M:'DISCONNECT'()
-          , M:'DISCONNECT'(),      M:'WILLTOPICUPD'()
-          , M:'WILLTOPICRESP'(),   M:'WILLMSGUPD'()
-          , M:'WILLMSGRESP'()
-          ]).
+    oneof([
+        M:'ADVERTISE'(),
+        M:'SEARCHGW'(),
+        M:'GWINFO'(),
+        M:'CONNECT'(),
+        M:'CONNACK'(),
+        M:'WILLTOTPICREQ'(),
+        M:'WILLTOPIC'(),
+        M:'WILLTOPCI_EMPTY'(),
+        M:'WILLMESSAGEREQ'(),
+        M:'WILLMESSAGE'(),
+        M:'REGISTER'(),
+        M:'REGACK'(),
+        M:'PUBLISH'(),
+        M:'PUBACK'(),
+        M:'PUBCOMP_REC_REL'(),
+        M:'SUBSCRIBE'(),
+        M:'SUBACK'(),
+        M:'UNSUBSCRIBE'(),
+        M:'UNSUBACK'(),
+        M:'PINGREQ'(),
+        M:'PINGRESP'(),
+        M:'DISCONNECT'(),
+        M:'DISCONNECT'(),
+        M:'WILLTOPICUPD'(),
+        M:'WILLTOPICRESP'(),
+        M:'WILLMSGUPD'(),
+        M:'WILLMSGRESP'()
+    ]).
diff --git a/apps/emqx_gateway/test/test_mqtt_broker.erl b/apps/emqx_gateway/test/test_mqtt_broker.erl
index f3e579efa..de209011e 100644
--- a/apps/emqx_gateway/test/test_mqtt_broker.erl
+++ b/apps/emqx_gateway/test/test_mqtt_broker.erl
@@ -33,8 +33,8 @@ start(_, <<"attacker">>, _, _, _) ->
     {stop, auth_failure};
 start(ClientId, Username, Password, _Channel, KeepaliveInterval) ->
     true = is_binary(ClientId),
-    (true = ( is_binary(Username)) orelse (Username == undefined) ),
-    (true = ( is_binary(Password)) orelse (Password == undefined) ),
+    (true = (is_binary(Username)) orelse (Username == undefined)),
+    (true = (is_binary(Password)) orelse (Password == undefined)),
     self() ! {keepalive, start, KeepaliveInterval},
     {ok, []}.
 
@@ -61,40 +61,42 @@ stop() ->
 init(_Param) ->
     {ok, #state{subscriber = []}}.
 
-handle_call({subscribe, Topic, Proc}, _From, State=#state{subscriber = SubList}) ->
+handle_call({subscribe, Topic, Proc}, _From, State = #state{subscriber = SubList}) ->
     ?LOGT("test broker subscribe Topic=~p, Pid=~p~n", [Topic, Proc]),
     is_binary(Topic) orelse error("Topic should be a binary"),
-    {reply, {ok, []}, State#state{subscriber = [{Topic, Proc}|SubList]}};
-
-handle_call(get_subscribed_topics, _From, State=#state{subscriber = SubList}) ->
+    {reply, {ok, []}, State#state{subscriber = [{Topic, Proc} | SubList]}};
+handle_call(get_subscribed_topics, _From, State = #state{subscriber = SubList}) ->
     Response = subscribed_topics(SubList, []),
     ?LOGT("test broker get subscribed topics=~p~n", [Response]),
     {reply, Response, State};
-
-handle_call({unsubscribe, Topic}, _From, State=#state{subscriber = SubList}) ->
+handle_call({unsubscribe, Topic}, _From, State = #state{subscriber = SubList}) ->
     ?LOGT("test broker unsubscribe Topic=~p~n", [Topic]),
     is_binary(Topic) orelse error("Topic should be a binary"),
     NewSubList = proplists:delete(Topic, SubList),
     {reply, {ok, []}, State#state{subscriber = NewSubList}};
-
-
-handle_call({publish, {Topic, Msg, MatchedTopicFilter}}, _From, State=#state{subscriber = SubList}) ->
+handle_call(
+    {publish, {Topic, Msg, MatchedTopicFilter}}, _From, State = #state{subscriber = SubList}
+) ->
     (is_binary(Topic) and is_binary(Msg)) orelse error("Topic and Msg should be binary"),
     Pid = proplists:get_value(MatchedTopicFilter, SubList),
-    ?LOGT("test broker publish topic=~p, Msg=~p, Pid=~p, MatchedTopicFilter=~p, SubList=~p~n", [Topic, Msg, Pid, MatchedTopicFilter, SubList]),
-    (Pid == undefined) andalso ?LOGT("!!!!! this topic ~p has never been subscribed, please specify a valid topic filter", [MatchedTopicFilter]),
+    ?LOGT("test broker publish topic=~p, Msg=~p, Pid=~p, MatchedTopicFilter=~p, SubList=~p~n", [
+        Topic, Msg, Pid, MatchedTopicFilter, SubList
+    ]),
+    (Pid == undefined) andalso
+        ?LOGT(
+            "!!!!! this topic ~p has never been subscribed, please specify a valid topic filter", [
+                MatchedTopicFilter
+            ]
+        ),
     ?assertNotEqual(undefined, Pid),
     Pid ! {deliver, #message{topic = Topic, payload = Msg}},
     {reply, ok, State};
-
 handle_call(stop, _From, State) ->
     {stop, normal, stopped, State};
-
 handle_call(Req, _From, State) ->
     ?LOGT("test_broker_server: ignore call Req=~p~n", [Req]),
     {reply, {error, badreq}, State}.
 
-
 handle_cast(Msg, State) ->
     ?LOGT("test_broker_server: ignore cast msg=~p~n", [Msg]),
     {noreply, State}.
@@ -110,38 +112,37 @@ terminate(Reason, _State) ->
 code_change(_OldVsn, State, _Extra) ->
     {ok, State}.
 
-
-
-
 subscribed_topics([], Acc) ->
     Acc;
-subscribed_topics([{Topic,_Pid}|T], Acc) ->
-    subscribed_topics(T, [Topic|Acc]).
-
-
-
+subscribed_topics([{Topic, _Pid} | T], Acc) ->
+    subscribed_topics(T, [Topic | Acc]).
 
 -record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}).
 
--type(keepalive() :: #keepalive{}).
+-type keepalive() :: #keepalive{}.
 
 %% @doc Start a keepalive
--spec(start(fun(), integer(), any()) -> undefined | keepalive()).
+-spec start(fun(), integer(), any()) -> undefined | keepalive().
 start(_, 0, _) ->
     undefined;
 start(StatFun, TimeoutSec, TimeoutMsg) ->
     {ok, StatVal} = StatFun(),
-    #keepalive{statfun = StatFun, statval = StatVal,
-        tsec = TimeoutSec, tmsg = TimeoutMsg,
-        tref = timer(TimeoutSec, TimeoutMsg)}.
+    #keepalive{
+        statfun = StatFun,
+        statval = StatVal,
+        tsec = TimeoutSec,
+        tmsg = TimeoutMsg,
+        tref = timer(TimeoutSec, TimeoutMsg)
+    }.
 
 %% @doc Check keepalive, called when timeout.
--spec(check(keepalive()) -> {ok, keepalive()} | {error, any()}).
+-spec check(keepalive()) -> {ok, keepalive()} | {error, any()}.
 check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) ->
     case StatFun() of
         {ok, NewVal} ->
-            if NewVal =/= LastVal ->
-                {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})};
+            if
+                NewVal =/= LastVal ->
+                    {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})};
                 Repeat < 1 ->
                     {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = Repeat + 1})};
                 true ->
@@ -155,17 +156,16 @@ resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) ->
     KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}.
 
 %% @doc Cancel Keepalive
--spec(cancel(keepalive()) -> ok).
+-spec cancel(keepalive()) -> ok.
 cancel(#keepalive{tref = TRef}) ->
     cancel(TRef);
 cancel(undefined) ->
     ok;
 cancel(TRef) ->
-        catch erlang:cancel_timer(TRef).
+    catch erlang:cancel_timer(TRef).
 
 timer(Sec, Msg) ->
     erlang:send_after(timer:seconds(Sec), self(), Msg).
 
-
 log(Format, Args) ->
     logger:debug(Format, Args).
diff --git a/scripts/check-format.sh b/scripts/check-format.sh
index 582b24b3a..a8d6a11bd 100755
--- a/scripts/check-format.sh
+++ b/scripts/check-format.sh
@@ -8,7 +8,7 @@ set -euo pipefail
 cd -P -- "$(dirname -- "$0")/.."
 
 APPS=()
-APPS+=( 'apps/emqx' 'apps/emqx_modules' )
+APPS+=( 'apps/emqx' 'apps/emqx_modules' 'apps/emqx_gateway')
 APPS+=( 'lib-ee/emqx_enterprise_conf' 'lib-ee/emqx_license' )
 
 for app in "${APPS[@]}"; do