style(gateway): format gateway application

This commit is contained in:
JianBo He 2022-03-31 17:41:32 +08:00
parent 4249d4345b
commit 3f6d78dda0
95 changed files with 16542 additions and 11478 deletions

View File

@ -21,19 +21,20 @@
%% @doc The Gateway definition %% @doc The Gateway definition
-type gateway() :: -type gateway() ::
#{ name := gateway_name() #{
%% Description name := gateway_name(),
, descr => binary() | undefined %% Description
%% Appears only in getting gateway info descr => binary() | undefined,
, status => stopped | running | unloaded %% Appears only in getting gateway info
%% Timestamp in millisecond status => stopped | running | unloaded,
, created_at => integer() %% Timestamp in millisecond
%% Timestamp in millisecond created_at => integer(),
, started_at => integer() %% Timestamp in millisecond
%% Timestamp in millisecond started_at => integer(),
, stopped_at => integer() %% Timestamp in millisecond
%% Appears only in getting gateway info stopped_at => integer(),
, config => emqx_config:config() %% Appears only in getting gateway info
}. config => emqx_config:config()
}.
-endif. -endif.

View File

@ -19,9 +19,11 @@
-define(RESOURCE_NOT_FOUND, 'RESOURCE_NOT_FOUND'). -define(RESOURCE_NOT_FOUND, 'RESOURCE_NOT_FOUND').
-define(INTERNAL_ERROR, 'INTERNAL_SERVER_ERROR'). -define(INTERNAL_ERROR, 'INTERNAL_SERVER_ERROR').
-define(STANDARD_RESP(R), -define(STANDARD_RESP(R), R#{
R#{ 400 => emqx_dashboard_swagger:error_codes( 400 => emqx_dashboard_swagger:error_codes(
[?BAD_REQUEST], <<"Bad request">>) [?BAD_REQUEST], <<"Bad request">>
, 404 => emqx_dashboard_swagger:error_codes( ),
[?NOT_FOUND, ?RESOURCE_NOT_FOUND], <<"Not Found">>) 404 => emqx_dashboard_swagger:error_codes(
}). [?NOT_FOUND, ?RESOURCE_NOT_FOUND], <<"Not Found">>
)
}).

View File

@ -2,30 +2,38 @@
{erl_opts, [debug_info]}. {erl_opts, [debug_info]}.
{deps, [ {deps, [
{emqx, {path, "../emqx"}}, {emqx, {path, "../emqx"}},
{grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.4"}}} {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.4"}}}
]}. ]}.
{plugins, [ {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, {grpc, [
[{protos, ["src/exproto/protos"]}, {protos, ["src/exproto/protos"]},
{out_dir, "src/exproto/"}, {out_dir, "src/exproto/"},
{gpb_opts, [{module_name_prefix, "emqx_"}, {gpb_opts, [
{module_name_suffix, "_pb"}]} {module_name_prefix, "emqx_"},
{module_name_suffix, "_pb"}
]}
]}. ]}.
{provider_hooks, {provider_hooks, [
[{pre, [{compile, {grpc, gen}}, {pre, [
{clean, {grpc, clean}}]} {compile, {grpc, gen}},
{clean, {grpc, clean}}
]}
]}. ]}.
{xref_ignores, [emqx_exproto_pb]}. {xref_ignores, [emqx_exproto_pb]}.
{cover_excl_mods, [emqx_exproto_pb, {cover_excl_mods, [
emqx_exproto_v_1_connection_adapter_client, emqx_exproto_pb,
emqx_exproto_v_1_connection_adapter_bhvr, emqx_exproto_v_1_connection_adapter_client,
emqx_exproto_v_1_connection_handler_client, emqx_exproto_v_1_connection_adapter_bhvr,
emqx_exproto_v_1_connection_handler_bhvr]}. emqx_exproto_v_1_connection_handler_client,
emqx_exproto_v_1_connection_handler_bhvr
]}.
{project_plugins, [erlfmt]}.

View File

@ -46,56 +46,57 @@
-type gen_server_from() :: {pid(), Tag :: term()}. -type gen_server_from() :: {pid(), Tag :: term()}.
-type reply() :: {outgoing, emqx_gateway_frame:packet()} -type reply() ::
| {outgoing, [emqx_gateway_frame:packet()]} {outgoing, emqx_gateway_frame:packet()}
| {event, conn_state() | updated} | {outgoing, [emqx_gateway_frame:packet()]}
| {close, Reason :: atom()}. | {event, conn_state() | updated}
| {close, Reason :: atom()}.
-type replies() :: reply() | [reply()]. -type replies() :: reply() | [reply()].
%% @doc Handle the incoming frame %% @doc Handle the incoming frame
-callback handle_in(emqx_gateway_frame:frame() | {frame_error, any()}, -callback handle_in(
channel()) emqx_gateway_frame:frame() | {frame_error, any()},
-> {ok, channel()} channel()
| {ok, replies(), channel()} ) ->
| {shutdown, Reason :: any(), channel()} {ok, channel()}
| {shutdown, Reason :: any(), replies(), channel()}. | {ok, replies(), channel()}
| {shutdown, Reason :: any(), channel()}
| {shutdown, Reason :: any(), replies(), channel()}.
%% @doc Handle the outgoing messages dispatched from PUB/SUB system %% @doc Handle the outgoing messages dispatched from PUB/SUB system
-callback handle_deliver(list(emqx_types:deliver()), channel()) -callback handle_deliver(list(emqx_types:deliver()), channel()) ->
-> {ok, channel()} {ok, channel()}
| {ok, replies(), channel()}. | {ok, replies(), channel()}.
%% @doc Handle the timeout event %% @doc Handle the timeout event
-callback handle_timeout(reference(), Msg :: any(), channel()) -callback handle_timeout(reference(), Msg :: any(), channel()) ->
-> {ok, channel()} {ok, channel()}
| {ok, replies(), channel()} | {ok, replies(), channel()}
| {shutdown, Reason :: any(), channel()}. | {shutdown, Reason :: any(), channel()}.
%% @doc Handle the custom gen_server:call/2 for its connection process %% @doc Handle the custom gen_server:call/2 for its connection process
-callback handle_call(Req :: any(), From :: gen_server_from(), channel()) -callback handle_call(Req :: any(), From :: gen_server_from(), channel()) ->
-> {reply, Reply :: any(), channel()} {reply, Reply :: any(), channel()}
%% Reply to caller and trigger an event(s) %% Reply to caller and trigger an event(s)
| {reply, Reply :: any(), | {reply, Reply :: any(), EventOrEvents :: tuple() | list(tuple()), channel()}
EventOrEvents :: tuple() | list(tuple()), channel()} | {noreply, channel()}
| {noreply, channel()} | {noreply, EventOrEvents :: tuple() | list(tuple()), channel()}
| {noreply, EventOrEvents :: tuple() | list(tuple()), channel()} | {shutdown, Reason :: any(), Reply :: any(), channel()}
| {shutdown, Reason :: any(), Reply :: any(), channel()} %% Shutdown the process, reply to caller and write a packet to client
%% Shutdown the process, reply to caller and write a packet to client | {shutdown, Reason :: any(), Reply :: any(), emqx_gateway_frame:frame(), channel()}.
| {shutdown, Reason :: any(), Reply :: any(),
emqx_gateway_frame:frame(), channel()}.
%% @doc Handle the custom gen_server:cast/2 for its connection process %% @doc Handle the custom gen_server:cast/2 for its connection process
-callback handle_cast(Req :: any(), channel()) -callback handle_cast(Req :: any(), channel()) ->
-> ok ok
| {ok, channel()} | {ok, channel()}
| {shutdown, Reason :: any(), channel()}. | {shutdown, Reason :: any(), channel()}.
%% @doc Handle the custom process messages for its connection process %% @doc Handle the custom process messages for its connection process
-callback handle_info(Info :: any(), channel()) -callback handle_info(Info :: any(), channel()) ->
-> ok ok
| {ok, channel()} | {ok, channel()}
| {shutdown, Reason :: any(), channel()}. | {shutdown, Reason :: any(), channel()}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Terminate %% Terminate

File diff suppressed because it is too large Load Diff

View File

@ -26,17 +26,18 @@
-type frame() :: any(). -type frame() :: any().
-type parse_result() :: {ok, frame(), -type parse_result() ::
Rest :: binary(), NewState :: parse_state()} {ok, frame(), Rest :: binary(), NewState :: parse_state()}
| {more, NewState :: parse_state()}. | {more, NewState :: parse_state()}.
-type serialize_options() :: map(). -type serialize_options() :: map().
-export_type([ parse_state/0 -export_type([
, parse_result/0 parse_state/0,
, serialize_options/0 parse_result/0,
, frame/0 serialize_options/0,
]). frame/0
]).
%% Callbacks %% Callbacks
@ -60,4 +61,3 @@
%% @doc %% @doc
-callback is_message(Frame :: any()) -> boolean(). -callback is_message(Frame :: any()) -> boolean().

View File

@ -22,21 +22,25 @@
-type reason() :: any(). -type reason() :: any().
%% @doc %% @doc
-callback on_gateway_load(Gateway :: gateway(), -callback on_gateway_load(
Ctx :: emqx_gateway_ctx:context()) Gateway :: gateway(),
-> {error, reason()} Ctx :: emqx_gateway_ctx:context()
| {ok, [ChildPid :: pid()], GwState :: state()} ) ->
%% TODO: v0.2 The child spec is better for restarting child process {error, reason()}
| {ok, [Childspec :: supervisor:child_spec()], GwState :: state()}. | {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 %% @doc
-callback on_gateway_update(Config :: emqx_config:config(), -callback on_gateway_update(
Gateway :: gateway(), Config :: emqx_config:config(),
GwState :: state()) Gateway :: gateway(),
-> ok GwState :: state()
| {ok, [ChildPid :: pid()], NGwState :: state()} ) ->
| {ok, [Childspec :: supervisor:child_spec()], NGwState :: state()} ok
| {error, reason()}. | {ok, [ChildPid :: pid()], NGwState :: state()}
| {ok, [Childspec :: supervisor:child_spec()], NGwState :: state()}
| {error, reason()}.
%% @doc %% @doc
-callback on_gateway_unload(Gateway :: gateway(), GwState :: state()) -> ok. -callback on_gateway_unload(Gateway :: gateway(), GwState :: state()) -> ok.

View File

@ -44,17 +44,22 @@ paths() ->
[?PREFIX ++ "/request"]. [?PREFIX ++ "/request"].
schema(?PREFIX ++ "/request") -> schema(?PREFIX ++ "/request") ->
#{operationId => request, #{
post => #{ tags => [<<"gateway|coap">>] operationId => request,
, desc => <<"Send a CoAP request message to the client">> post => #{
, parameters => request_parameters() tags => [<<"gateway|coap">>],
, requestBody => request_body() desc => <<"Send a CoAP request message to the client">>,
, responses => #{200 => coap_message(), parameters => request_parameters(),
404 => error_codes(['CLIENT_NOT_FOUND'], <<"Client not found error">>), requestBody => request_body(),
504 => error_codes(['CLIENT_NOT_RESPONSE'], <<"Waiting for client response timeout">>)} 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}) -> request(post, #{body := Body, bindings := Bindings}) ->
ClientId = maps:get(clientid, Bindings, undefined), ClientId = maps:get(clientid, Bindings, undefined),
@ -66,8 +71,12 @@ request(post, #{body := Body, bindings := Bindings}) ->
CT = erlang:atom_to_binary(AtomCT), CT = erlang:atom_to_binary(AtomCT),
Payload2 = parse_payload(CT, Payload), Payload2 = parse_payload(CT, Payload),
Msg = emqx_coap_message:request(con, Msg = emqx_coap_message:request(
Method, Payload2, #{content_format => CT}), con,
Method,
Payload2,
#{content_format => CT}
),
Msg2 = Msg#coap_message{token = Token}, Msg2 = Msg#coap_message{token = Token},
@ -78,7 +87,7 @@ request(post, #{body := Body, bindings := Bindings}) ->
{404, #{code => 'CLIENT_NOT_FOUND'}}; {404, #{code => 'CLIENT_NOT_FOUND'}};
Response -> Response ->
{200, format_to_response(CT, Response)} {200, format_to_response(CT, Response)}
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
@ -87,42 +96,49 @@ request_parameters() ->
[{clientid, mk(binary(), #{in => path, required => true})}]. [{clientid, mk(binary(), #{in => path, required => true})}].
request_body() -> request_body() ->
[ {token, mk(binary(), #{desc => "message token, can be empty"})} [
, {method, mk(enum([get, put, post, delete]), #{desc => "request method type"})} {token, mk(binary(), #{desc => "message token, can be empty"})},
, {timeout, mk(emqx_schema:duration_ms(), #{desc => "timespan for response"})} {method, mk(enum([get, put, post, delete]), #{desc => "request method type"})},
, {content_type, mk(enum(['text/plain', 'application/json', 'application/octet-stream']), {timeout, mk(emqx_schema:duration_ms(), #{desc => "timespan for response"})},
#{desc => "payload type"})} {content_type,
, {payload, mk(binary(), #{desc => "the content of the payload"})} mk(
enum(['text/plain', 'application/json', 'application/octet-stream']),
#{desc => "payload type"}
)},
{payload, mk(binary(), #{desc => "the content of the payload"})}
]. ].
coap_message() -> coap_message() ->
[ {id, mk(integer(), #{desc => "message id"})} [
, {token, mk(string(), #{desc => "message token, can be empty"})} {id, mk(integer(), #{desc => "message id"})},
, {method, mk(string(), #{desc => "response code"})} {token, mk(string(), #{desc => "message token, can be empty"})},
, {payload, mk(string(), #{desc => "payload"})} {method, mk(string(), #{desc => "response code"})},
{payload, mk(string(), #{desc => "payload"})}
]. ].
format_to_response(ContentType, #coap_message{id = Id, format_to_response(ContentType, #coap_message{
token = Token, id = Id,
method = Method, token = Token,
payload = Payload}) -> method = Method,
#{id => Id, payload = Payload
token => Token, }) ->
method => format_to_binary(Method), #{
payload => format_payload(ContentType, Payload)}. id => Id,
token => Token,
method => format_to_binary(Method),
payload => format_payload(ContentType, Payload)
}.
format_to_binary(Obj) -> format_to_binary(Obj) ->
erlang:list_to_binary(io_lib:format("~p", [Obj])). erlang:list_to_binary(io_lib:format("~p", [Obj])).
format_payload(<<"application/octet-stream">>, Payload) -> format_payload(<<"application/octet-stream">>, Payload) ->
base64:encode(Payload); base64:encode(Payload);
format_payload(_, Payload) -> format_payload(_, Payload) ->
Payload. Payload.
parse_payload(<<"application/octet-stream">>, Body) -> parse_payload(<<"application/octet-stream">>, Body) ->
base64:decode(Body); base64:decode(Body);
parse_payload(_, Body) -> parse_payload(_, Body) ->
Body. Body.
@ -140,10 +156,13 @@ call_client(ClientId, Msg, Timeout) ->
_ -> _ ->
not_found not_found
end end
catch _:Error:Trace -> catch
?SLOG(warning, #{msg => "coap_client_call_exception", _:Error:Trace ->
clientid => ClientId, ?SLOG(warning, #{
error => Error, msg => "coap_client_call_exception",
stacktrace => Trace}), clientid => ClientId,
error => Error,
stacktrace => Trace
}),
not_found not_found
end. end.

View File

@ -19,26 +19,29 @@
-behaviour(emqx_gateway_channel). -behaviour(emqx_gateway_channel).
%% API %% API
-export([ info/1 -export([
, info/2 info/1,
, stats/1 info/2,
, validator/4 stats/1,
, metrics_inc/2 validator/4,
, run_hooks/3 metrics_inc/2,
, send_request/2 run_hooks/3,
]). send_request/2
]).
-export([ init/2 -export([
, handle_in/2 init/2,
, handle_deliver/2 handle_in/2,
, handle_timeout/3 handle_deliver/2,
, terminate/2 handle_timeout/3,
]). terminate/2
]).
-export([ handle_call/3 -export([
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
]). handle_info/2
]).
-export_type([channel/0]). -export_type([channel/0]).
@ -49,34 +52,35 @@
-define(AUTHN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM). -define(AUTHN, ?EMQX_AUTHENTICATION_CONFIG_ROOT_NAME_ATOM).
-record(channel, { -record(channel, {
%% Context %% Context
ctx :: emqx_gateway_ctx:context(), ctx :: emqx_gateway_ctx:context(),
%% Connection Info %% Connection Info
conninfo :: emqx_types:conninfo(), conninfo :: emqx_types:conninfo(),
%% Client Info %% Client Info
clientinfo :: emqx_types:clientinfo(), clientinfo :: emqx_types:clientinfo(),
%% Session %% Session
session :: emqx_coap_session:session() | undefined, session :: emqx_coap_session:session() | undefined,
%% Keepalive %% Keepalive
keepalive :: emqx_keepalive:keepalive() | undefined, keepalive :: emqx_keepalive:keepalive() | undefined,
%% Timer %% Timer
timers :: #{atom() => disable | undefined | reference()}, timers :: #{atom() => disable | undefined | reference()},
%% Connection mode %% Connection mode
connection_required :: boolean(), connection_required :: boolean(),
%% Connection State %% Connection State
conn_state :: conn_state(), conn_state :: conn_state(),
%% Session token to identity this connection %% Session token to identity this connection
token :: binary() | undefined token :: binary() | undefined
}). }).
-type channel() :: #channel{}. -type channel() :: #channel{}.
-type conn_state() :: idle | connecting | connected | disconnected. -type conn_state() :: idle | connecting | connected | disconnected.
-type reply() :: {outgoing, coap_message()} -type reply() ::
| {outgoing, [coap_message()]} {outgoing, coap_message()}
| {event, conn_state()|updated} | {outgoing, [coap_message()]}
| {close, Reason :: atom()}. | {event, conn_state() | updated}
| {close, Reason :: atom()}.
-type replies() :: reply() | [reply()]. -type replies() :: reply() | [reply()].
@ -97,10 +101,9 @@
info(Channel) -> info(Channel) ->
maps:from_list(info(?INFO_KEYS, 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) -> info(Keys, Channel) when is_list(Keys) ->
[{Key, info(Key, Channel)} || Key <- Keys]; [{Key, info(Key, Channel)} || Key <- Keys];
info(conninfo, #channel{conninfo = ConnInfo}) -> info(conninfo, #channel{conninfo = ConnInfo}) ->
ConnInfo; ConnInfo;
info(conn_state, #channel{conn_state = ConnState}) -> info(conn_state, #channel{conn_state = ConnState}) ->
@ -119,41 +122,47 @@ stats(_) ->
[]. [].
-spec init(map(), map()) -> channel(). -spec init(map(), map()) -> channel().
init(ConnInfo = #{peername := {PeerHost, _}, init(
sockname := {_, SockPort}}, ConnInfo = #{
#{ctx := Ctx} = Config) -> peername := {PeerHost, _},
sockname := {_, SockPort}
},
#{ctx := Ctx} = Config
) ->
Peercert = maps:get(peercert, ConnInfo, undefined), Peercert = maps:get(peercert, ConnInfo, undefined),
Mountpoint = maps:get(mountpoint, Config, <<>>), Mountpoint = maps:get(mountpoint, Config, <<>>),
ListenerId = case maps:get(listener, Config, undefined) of ListenerId =
undefined -> undefined; case maps:get(listener, Config, undefined) of
{GwName, Type, LisName} -> undefined -> undefined;
emqx_gateway_utils:listener_id(GwName, Type, LisName) {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName)
end, end,
ClientInfo = set_peercert_infos( ClientInfo = set_peercert_infos(
Peercert, Peercert,
#{ zone => default #{
, listener => ListenerId zone => default,
, protocol => 'coap' listener => ListenerId,
, peerhost => PeerHost protocol => 'coap',
, sockport => SockPort peerhost => PeerHost,
, clientid => emqx_guid:to_base62(emqx_guid:gen()) sockport => SockPort,
, username => undefined clientid => emqx_guid:to_base62(emqx_guid:gen()),
, is_bridge => false username => undefined,
, is_superuser => false is_bridge => false,
, mountpoint => Mountpoint is_superuser => false,
} mountpoint => Mountpoint
), }
),
Heartbeat = ?GET_IDLE_TIME(Config), Heartbeat = ?GET_IDLE_TIME(Config),
#channel{ ctx = Ctx #channel{
, conninfo = ConnInfo ctx = Ctx,
, clientinfo = ClientInfo conninfo = ConnInfo,
, timers = #{} clientinfo = ClientInfo,
, session = emqx_coap_session:new() timers = #{},
, keepalive = emqx_keepalive:init(Heartbeat) session = emqx_coap_session:new(),
, connection_required = maps:get(connection_required, Config, false) keepalive = emqx_keepalive:init(Heartbeat),
, conn_state = idle connection_required = maps:get(connection_required, Config, false),
}. conn_state = idle
}.
validator(Type, Topic, Ctx, ClientInfo) -> validator(Type, Topic, Ctx, ClientInfo) ->
emqx_gateway_ctx:authorize(Ctx, ClientInfo, Type, Topic). emqx_gateway_ctx:authorize(Ctx, ClientInfo, Type, Topic).
@ -166,11 +175,11 @@ send_request(Channel, Request) ->
%% Handle incoming packet %% Handle incoming packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec handle_in(coap_message() | {frame_error, any()}, channel()) -spec handle_in(coap_message() | {frame_error, any()}, channel()) ->
-> {ok, channel()} {ok, channel()}
| {ok, replies(), channel()} | {ok, replies(), channel()}
| {shutdown, Reason :: term(), channel()} | {shutdown, Reason :: term(), channel()}
| {shutdown, Reason :: term(), replies(), channel()}. | {shutdown, Reason :: term(), replies(), channel()}.
handle_in(Msg, ChannleT) -> handle_in(Msg, ChannleT) ->
Channel = ensure_keepalive_timer(ChannleT), Channel = ensure_keepalive_timer(ChannleT),
case emqx_coap_message:is_request(Msg) of case emqx_coap_message:is_request(Msg) of
@ -183,8 +192,13 @@ handle_in(Msg, ChannleT) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle Delivers from broker to client %% Handle Delivers from broker to client
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_deliver(Delivers, #channel{session = Session, handle_deliver(
ctx = Ctx} = Channel) -> Delivers,
#channel{
session = Session,
ctx = Ctx
} = Channel
) ->
handle_result(emqx_coap_session:deliver(Delivers, Ctx, Session), 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} -> {error, timeout} ->
{shutdown, timeout, ensure_disconnected(keepalive_timeout, Channel)} {shutdown, timeout, ensure_disconnected(keepalive_timeout, Channel)}
end; end;
handle_timeout(_, {transport, Msg}, Channel) -> handle_timeout(_, {transport, Msg}, Channel) ->
call_session(timeout, Msg, Channel); call_session(timeout, Msg, Channel);
handle_timeout(_, disconnect, Channel) -> handle_timeout(_, disconnect, Channel) ->
{shutdown, normal, Channel}; {shutdown, normal, Channel};
handle_timeout(_, _, Channel) -> handle_timeout(_, _, Channel) ->
{ok, Channel}. {ok, Channel}.
@ -213,72 +224,89 @@ handle_timeout(_, _, Channel) ->
%% Handle call %% Handle call
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(handle_call(Req :: term(), From :: term(), channel()) -spec handle_call(Req :: term(), From :: term(), channel()) ->
-> {reply, Reply :: term(), channel()} {reply, Reply :: term(), channel()}
| {reply, Reply :: term(), replies(), channel()} | {reply, Reply :: term(), replies(), channel()}
| {shutdown, Reason :: term(), Reply :: term(), channel()} | {shutdown, Reason :: term(), Reply :: term(), channel()}
| {shutdown, Reason :: term(), Reply :: term(), coap_message(), channel()}). | {shutdown, Reason :: term(), Reply :: term(), coap_message(), channel()}.
handle_call({send_request, Msg}, From, Channel) -> handle_call({send_request, Msg}, From, Channel) ->
Result = call_session(handle_out, {{send_request, From}, Msg}, Channel), Result = call_session(handle_out, {{send_request, From}, Msg}, Channel),
erlang:setelement(1, Result, noreply); erlang:setelement(1, Result, noreply);
handle_call(
handle_call({subscribe, Topic, SubOpts}, _From, {subscribe, Topic, SubOpts},
Channel = #channel{ _From,
ctx = Ctx, Channel = #channel{
clientinfo = ClientInfo ctx = Ctx,
= #{clientid := ClientId, clientinfo =
mountpoint := Mountpoint}, ClientInfo =
session = Session}) -> #{
Token = maps:get(token, clientid := ClientId,
maps:get(sub_props, SubOpts, #{}), mountpoint := Mountpoint
<<>>), },
session = Session
}
) ->
Token = maps:get(
token,
maps:get(sub_props, SubOpts, #{}),
<<>>
),
NSubOpts = maps:merge( NSubOpts = maps:merge(
emqx_gateway_utils:default_subopts(), emqx_gateway_utils:default_subopts(),
SubOpts), SubOpts
),
MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic),
_ = emqx_broker:subscribe(MountedTopic, ClientId, NSubOpts), _ = emqx_broker:subscribe(MountedTopic, ClientId, NSubOpts),
_ = run_hooks(Ctx, 'session.subscribed', _ = run_hooks(
[ClientInfo, MountedTopic, NSubOpts]), Ctx,
'session.subscribed',
[ClientInfo, MountedTopic, NSubOpts]
),
%% modify session state %% modify session state
SubReq = {Topic, Token}, SubReq = {Topic, Token},
TempMsg = #coap_message{type = non}, TempMsg = #coap_message{type = non},
%% FIXME: The subopts is not used for emqx_coap_session %% FIXME: The subopts is not used for emqx_coap_session
Result = emqx_coap_session:process_subscribe( Result = emqx_coap_session:process_subscribe(
SubReq, TempMsg, #{}, Session), SubReq, TempMsg, #{}, Session
),
NSession = maps:get(session, Result), NSession = maps:get(session, Result),
{reply, {ok, {MountedTopic, NSubOpts}}, Channel#channel{session = NSession}}; {reply, {ok, {MountedTopic, NSubOpts}}, Channel#channel{session = NSession}};
handle_call(
handle_call({unsubscribe, Topic}, _From, {unsubscribe, Topic},
Channel = #channel{ _From,
ctx = Ctx, Channel = #channel{
clientinfo = ClientInfo ctx = Ctx,
= #{mountpoint := Mountpoint}, clientinfo =
session = Session}) -> ClientInfo =
#{mountpoint := Mountpoint},
session = Session
}
) ->
MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic),
ok = emqx_broker:unsubscribe(MountedTopic), ok = emqx_broker:unsubscribe(MountedTopic),
_ = run_hooks(Ctx, 'session.unsubscribe', _ = run_hooks(
[ClientInfo, MountedTopic, #{}]), Ctx,
'session.unsubscribe',
[ClientInfo, MountedTopic, #{}]
),
%% modify session state %% modify session state
UnSubReq = Topic, UnSubReq = Topic,
TempMsg = #coap_message{type = non}, TempMsg = #coap_message{type = non},
Result = emqx_coap_session:process_subscribe( Result = emqx_coap_session:process_subscribe(
UnSubReq, TempMsg, #{}, Session), UnSubReq, TempMsg, #{}, Session
),
NSession = maps:get(session, Result), NSession = maps:get(session, Result),
{reply, ok, Channel#channel{session = NSession}}; {reply, ok, Channel#channel{session = NSession}};
handle_call(subscriptions, _From, Channel = #channel{session = Session}) -> handle_call(subscriptions, _From, Channel = #channel{session = Session}) ->
Subs = emqx_coap_session:info(subscriptions, Session), Subs = emqx_coap_session:info(subscriptions, Session),
{reply, {ok, maps:to_list(Subs)}, Channel}; {reply, {ok, maps:to_list(Subs)}, Channel};
handle_call(kick, _From, Channel) -> handle_call(kick, _From, Channel) ->
NChannel = ensure_disconnected(kicked, Channel), NChannel = ensure_disconnected(kicked, Channel),
shutdown_and_reply(kicked, ok, NChannel); shutdown_and_reply(kicked, ok, NChannel);
handle_call(discard, _From, Channel) -> handle_call(discard, _From, Channel) ->
shutdown_and_reply(discarded, ok, Channel); shutdown_and_reply(discarded, ok, Channel);
handle_call(Req, _From, Channel) -> handle_call(Req, _From, Channel) ->
?SLOG(error, #{msg => "unexpected_call", call => Req}), ?SLOG(error, #{msg => "unexpected_call", call => Req}),
{reply, ignored, Channel}. {reply, ignored, Channel}.
@ -287,8 +315,8 @@ handle_call(Req, _From, Channel) ->
%% Handle Cast %% Handle Cast
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec handle_cast(Req :: term(), channel()) -spec handle_cast(Req :: term(), channel()) ->
-> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}. ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}.
handle_cast(Req, Channel) -> handle_cast(Req, Channel) ->
?SLOG(error, #{msg => "unexpected_cast", cast => Req}), ?SLOG(error, #{msg => "unexpected_cast", cast => Req}),
{ok, Channel}. {ok, Channel}.
@ -297,11 +325,10 @@ handle_cast(Req, Channel) ->
%% Handle Info %% Handle Info
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(handle_info(Info :: term(), channel()) -spec handle_info(Info :: term(), channel()) ->
-> ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}). ok | {ok, channel()} | {shutdown, Reason :: term(), channel()}.
handle_info({subscribe, _AutoSubs}, Channel) -> handle_info({subscribe, _AutoSubs}, Channel) ->
{ok, Channel}; {ok, Channel};
handle_info(Info, Channel) -> handle_info(Info, Channel) ->
?SLOG(warning, #{msg => "unexpected_info", info => Info}), ?SLOG(warning, #{msg => "unexpected_info", info => Info}),
{ok, Channel}. {ok, Channel}.
@ -309,21 +336,23 @@ handle_info(Info, Channel) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Terminate %% Terminate
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(Reason, #channel{clientinfo = ClientInfo, terminate(Reason, #channel{
ctx = Ctx, clientinfo = ClientInfo,
session = Session}) -> ctx = Ctx,
session = Session
}) ->
run_hooks(Ctx, 'session.terminated', [ClientInfo, Reason, Session]). run_hooks(Ctx, 'session.terminated', [ClientInfo, Reason, Session]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
set_peercert_infos(NoSSL, ClientInfo) set_peercert_infos(NoSSL, ClientInfo) when
when NoSSL =:= nossl; NoSSL =:= nossl;
NoSSL =:= undefined -> NoSSL =:= undefined
->
ClientInfo; ClientInfo;
set_peercert_infos(Peercert, ClientInfo) -> set_peercert_infos(Peercert, ClientInfo) ->
{DN, CN} = {esockd_peercert:subject(Peercert), {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)},
esockd_peercert:common_name(Peercert)},
ClientInfo#{dn => DN, cn => CN}. ClientInfo#{dn => DN, cn => CN}.
ensure_timer(Name, Time, Msg, #channel{timers = Timers} = Channel) -> 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_auth_state(Msg, #channel{connection_required = Required} = Channel) ->
check_token(Required, Msg, Channel). check_token(Required, Msg, Channel).
check_token(true, check_token(
Msg, true,
#channel{token = Token, Msg,
clientinfo = ClientInfo, #channel{
conn_state = CState} = Channel) -> token = Token,
clientinfo = ClientInfo,
conn_state = CState
} = Channel
) ->
#{clientid := ClientId} = ClientInfo, #{clientid := ClientId} = ClientInfo,
case emqx_coap_message:get_option(uri_query, Msg) of case emqx_coap_message:get_option(uri_query, Msg) of
#{<<"clientid">> := ClientId, #{
<<"token">> := Token} -> <<"clientid">> := ClientId,
<<"token">> := Token
} ->
call_session(handle_request, Msg, Channel); call_session(handle_request, Msg, Channel);
#{<<"clientid">> := DesireId} -> #{<<"clientid">> := DesireId} ->
try_takeover(CState, DesireId, Msg, Channel); try_takeover(CState, DesireId, Msg, Channel);
@ -364,7 +399,6 @@ check_token(true,
Reply = emqx_coap_message:piggyback({error, unauthorized}, Msg), Reply = emqx_coap_message:piggyback({error, unauthorized}, Msg),
{ok, {outgoing, Reply}, Channel} {ok, {outgoing, Reply}, Channel}
end; end;
check_token(false, Msg, Channel) -> check_token(false, Msg, Channel) ->
call_session(handle_request, Msg, Channel). call_session(handle_request, Msg, Channel).
@ -383,7 +417,6 @@ try_takeover(idle, DesireId, Msg, Channel) ->
do_takeover(DesireId, Msg, Channel) do_takeover(DesireId, Msg, Channel)
end end
end; end;
try_takeover(_, DesireId, Msg, Channel) -> try_takeover(_, DesireId, Msg, Channel) ->
do_takeover(DesireId, Msg, Channel). do_takeover(DesireId, Msg, Channel).
@ -392,43 +425,57 @@ do_takeover(_DesireId, Msg, Channel) ->
Reset = emqx_coap_message:reset(Msg), Reset = emqx_coap_message:reset(Msg),
{ok, {outgoing, Reset}, Channel}. {ok, {outgoing, Reset}, Channel}.
run_conn_hooks(Input, Channel = #channel{ctx = Ctx, run_conn_hooks(
conninfo = ConnInfo}) -> Input,
Channel = #channel{
ctx = Ctx,
conninfo = ConnInfo
}
) ->
ConnProps = #{}, ConnProps = #{},
case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of
Error = {error, _Reason} -> Error; Error = {error, _Reason} -> Error;
_NConnProps -> _NConnProps -> {ok, Input, Channel}
{ok, Input, Channel}
end. end.
enrich_conninfo({Queries, _Msg}, enrich_conninfo(
Channel = #channel{ {Queries, _Msg},
keepalive = KeepAlive, Channel = #channel{
conninfo = ConnInfo}) -> keepalive = KeepAlive,
conninfo = ConnInfo
}
) ->
case Queries of case Queries of
#{<<"clientid">> := ClientId} -> #{<<"clientid">> := ClientId} ->
Interval = maps:get(interval, emqx_keepalive:info(KeepAlive)), Interval = maps:get(interval, emqx_keepalive:info(KeepAlive)),
NConnInfo = ConnInfo#{ clientid => ClientId NConnInfo = ConnInfo#{
, proto_name => <<"CoAP">> clientid => ClientId,
, proto_ver => <<"1">> proto_name => <<"CoAP">>,
, clean_start => true proto_ver => <<"1">>,
, keepalive => Interval clean_start => true,
, expiry_interval => 0 keepalive => Interval,
}, expiry_interval => 0
},
{ok, Channel#channel{conninfo = NConnInfo}}; {ok, Channel#channel{conninfo = NConnInfo}};
_ -> _ ->
{error, "invalid queries", Channel} {error, "invalid queries", Channel}
end. end.
enrich_clientinfo({Queries, Msg}, enrich_clientinfo(
Channel = #channel{clientinfo = ClientInfo0}) -> {Queries, Msg},
Channel = #channel{clientinfo = ClientInfo0}
) ->
case Queries of case Queries of
#{<<"username">> := UserName, #{
<<"password">> := Password, <<"username">> := UserName,
<<"clientid">> := ClientId} -> <<"password">> := Password,
ClientInfo = ClientInfo0#{username => UserName, <<"clientid">> := ClientId
password => Password, } ->
clientid => ClientId}, ClientInfo = ClientInfo0#{
username => UserName,
password => Password,
clientid => ClientId
},
{ok, NClientInfo} = fix_mountpoint(Msg, ClientInfo), {ok, NClientInfo} = fix_mountpoint(Msg, ClientInfo),
{ok, Channel#channel{clientinfo = NClientInfo}}; {ok, Channel#channel{clientinfo = NClientInfo}};
_ -> _ ->
@ -439,19 +486,27 @@ set_log_meta(_Input, #channel{clientinfo = #{clientid := ClientId}}) ->
emqx_logger:set_metadata_clientid(ClientId), emqx_logger:set_metadata_clientid(ClientId),
ok. ok.
auth_connect(_Input, Channel = #channel{ctx = Ctx, auth_connect(
clientinfo = ClientInfo}) -> _Input,
#{clientid := ClientId, Channel = #channel{
username := Username} = ClientInfo, ctx = Ctx,
clientinfo = ClientInfo
}
) ->
#{
clientid := ClientId,
username := Username
} = ClientInfo,
case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of
{ok, NClientInfo} -> {ok, NClientInfo} ->
{ok, Channel#channel{clientinfo = NClientInfo}}; {ok, Channel#channel{clientinfo = NClientInfo}};
{error, Reason} -> {error, Reason} ->
?SLOG(warning, #{ msg => "client_login_failed" ?SLOG(warning, #{
, username => Username msg => "client_login_failed",
, clientid => ClientId username => Username,
, reason => Reason clientid => ClientId,
}), reason => Reason
}),
{error, Reason} {error, Reason}
end. end.
@ -461,33 +516,44 @@ fix_mountpoint(_Packet, ClientInfo = #{mountpoint := Mountpoint}) ->
Mountpoint1 = emqx_mountpoint:replvar(Mountpoint, ClientInfo), Mountpoint1 = emqx_mountpoint:replvar(Mountpoint, ClientInfo),
{ok, ClientInfo#{mountpoint := Mountpoint1}}. {ok, ClientInfo#{mountpoint := Mountpoint1}}.
process_connect(#channel{ctx = Ctx, process_connect(
session = Session, #channel{
conninfo = ConnInfo, ctx = Ctx,
clientinfo = ClientInfo} = Channel, session = Session,
Msg, Result, Iter) -> conninfo = ConnInfo,
clientinfo = ClientInfo
} = Channel,
Msg,
Result,
Iter
) ->
%% inherit the old session %% inherit the old session
SessFun = fun(_,_) -> Session end, SessFun = fun(_, _) -> Session end,
case emqx_gateway_ctx:open_session( case
Ctx, emqx_gateway_ctx:open_session(
true, Ctx,
ClientInfo, true,
ConnInfo, ClientInfo,
SessFun, ConnInfo,
emqx_coap_session SessFun,
) of emqx_coap_session
)
of
{ok, _Sess} -> {ok, _Sess} ->
RandVal = rand:uniform(?TOKEN_MAXIMUM), RandVal = rand:uniform(?TOKEN_MAXIMUM),
Token = erlang:list_to_binary(erlang:integer_to_list(RandVal)), Token = erlang:list_to_binary(erlang:integer_to_list(RandVal)),
NResult = Result#{events => [{event, connected}]}, NResult = Result#{events => [{event, connected}]},
iter(Iter, iter(
reply({ok, created}, Token, Msg, NResult), Iter,
Channel#channel{token = Token}); reply({ok, created}, Token, Msg, NResult),
Channel#channel{token = Token}
);
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{ msg => "failed_open_session" ?SLOG(error, #{
, clientid => maps:get(clientid, ClientInfo) msg => "failed_open_session",
, reason => Reason clientid => maps:get(clientid, ClientInfo),
}), reason => Reason
}),
iter(Iter, reply({error, bad_request}, Msg, Result), Channel) iter(Iter, reply({error, bad_request}, Msg, Result), Channel)
end. end.
@ -505,11 +571,14 @@ metrics_inc(Name, Ctx) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Ensure connected %% Ensure connected
ensure_connected(Channel = #channel{ctx = Ctx, ensure_connected(
conninfo = ConnInfo, Channel = #channel{
clientinfo = ClientInfo}) -> ctx = Ctx,
NConnInfo = ConnInfo#{ connected_at => erlang:system_time(millisecond) conninfo = ConnInfo,
}, clientinfo = ClientInfo
}
) ->
NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)},
_ = run_hooks(Ctx, 'client.connack', [NConnInfo, connection_accepted, []]), _ = run_hooks(Ctx, 'client.connack', [NConnInfo, connection_accepted, []]),
ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]),
Channel#channel{conninfo = NConnInfo, conn_state = connected}. Channel#channel{conninfo = NConnInfo, conn_state = connected}.
@ -517,10 +586,14 @@ ensure_connected(Channel = #channel{ctx = Ctx,
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Ensure disconnected %% Ensure disconnected
ensure_disconnected(Reason, Channel = #channel{ ensure_disconnected(
ctx = Ctx, Reason,
conninfo = ConnInfo, Channel = #channel{
clientinfo = ClientInfo}) -> ctx = Ctx,
conninfo = ConnInfo,
clientinfo = ClientInfo
}
) ->
NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, 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}. 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).
handle_result(Result, Channel) -> handle_result(Result, Channel) ->
iter([ session, fun process_session/4 iter(
, proto, fun process_protocol/4 [
, reply, fun process_reply/4 session,
, out, fun process_out/4 fun process_session/4,
, fun process_nothing/3 proto,
], fun process_protocol/4,
Result, reply,
Channel). fun process_reply/4,
out,
fun process_out/4,
fun process_nothing/3
],
Result,
Channel
).
call_handler(request, Msg, Result, call_handler(
#channel{ctx = Ctx, request,
clientinfo = ClientInfo} = Channel, Iter) -> Msg,
Result,
#channel{
ctx = Ctx,
clientinfo = ClientInfo
} = Channel,
Iter
) ->
HandlerResult = HandlerResult =
case emqx_coap_message:get_option(uri_path, Msg) of case emqx_coap_message:get_option(uri_path, Msg) of
[<<"ps">> | RestPath] -> [<<"ps">> | RestPath] ->
@ -561,15 +648,20 @@ call_handler(request, Msg, Result,
_ -> _ ->
reply({error, bad_request}, Msg) reply({error, bad_request}, Msg)
end, end,
iter([ connection, fun process_connection/4 iter(
, subscribe, fun process_subscribe/4 | Iter], [
maps:merge(Result, HandlerResult), connection,
Channel); 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) -> call_handler(response, {{send_request, From}, Response}, Result, Channel, Iter) ->
gen_server:reply(From, Response), gen_server:reply(From, Response),
iter(Iter, Result, Channel); iter(Iter, Result, Channel);
call_handler(_, _, Result, Channel, Iter) -> call_handler(_, _, Result, Channel, Iter) ->
iter(Iter, Result, Channel). iter(Iter, Result, Channel).
@ -582,12 +674,13 @@ process_protocol({Type, Msg}, Result, Channel, Iter) ->
%% leaf node %% leaf node
process_out(Outs, Result, Channel, _) -> process_out(Outs, Result, Channel, _) ->
Outs2 = lists:reverse(Outs), Outs2 = lists:reverse(Outs),
Outs3 = case maps:get(reply, Result, undefined) of Outs3 =
undefined -> case maps:get(reply, Result, undefined) of
Outs2; undefined ->
Reply -> Outs2;
[Reply | Outs2] Reply ->
end, [Reply | Outs2]
end,
Events = maps:get(events, Result, []), Events = maps:get(events, Result, []),
{ok, [{outgoing, Outs3}] ++ Events, Channel}. {ok, [{outgoing, Outs3}] ++ Events, Channel}.
@ -595,32 +688,48 @@ process_out(Outs, Result, Channel, _) ->
process_nothing(_, _, Channel) -> process_nothing(_, _, Channel) ->
{ok, Channel}. {ok, Channel}.
process_connection({open, Req}, Result, process_connection(
Channel = #channel{conn_state = idle}, Iter) -> {open, Req},
Result,
Channel = #channel{conn_state = idle},
Iter
) ->
Queries = emqx_coap_message:get_option(uri_query, Req), Queries = emqx_coap_message:get_option(uri_query, Req),
case emqx_misc:pipeline( case
[ fun enrich_conninfo/2 emqx_misc:pipeline(
, fun run_conn_hooks/2 [
, fun enrich_clientinfo/2 fun enrich_conninfo/2,
, fun set_log_meta/2 fun run_conn_hooks/2,
, fun auth_connect/2 fun enrich_clientinfo/2,
], fun set_log_meta/2,
{Queries, Req}, fun auth_connect/2
Channel) of ],
{Queries, Req},
Channel
)
of
{ok, _Input, NChannel} -> {ok, _Input, NChannel} ->
process_connect(ensure_connected(NChannel), Req, Result, Iter); process_connect(ensure_connected(NChannel), Req, Result, Iter);
{error, ReasonCode, NChannel} -> {error, ReasonCode, NChannel} ->
ErrMsg = io_lib:format("Login Failed: ~ts", [ReasonCode]), ErrMsg = io_lib:format("Login Failed: ~ts", [ReasonCode]),
Payload = iolist_to_binary(ErrMsg), Payload = iolist_to_binary(ErrMsg),
iter(Iter, iter(
reply({error, bad_request}, Payload, Req, Result), Iter,
NChannel) reply({error, bad_request}, Payload, Req, Result),
NChannel
)
end; end;
process_connection({open, Req}, Result, process_connection(
Channel = #channel{ {open, Req},
conn_state = ConnState, Result,
clientinfo = #{clientid := ClientId}}, Iter) Channel = #channel{
when ConnState == connected -> conn_state = ConnState,
clientinfo = #{clientid := ClientId}
},
Iter
) when
ConnState == connected
->
Queries = emqx_coap_message:get_option(uri_query, Req), Queries = emqx_coap_message:get_option(uri_query, Req),
ErrMsg0 = ErrMsg0 =
case Queries of case Queries of
@ -633,9 +742,11 @@ process_connection({open, Req}, Result,
end, end,
ErrMsg = io_lib:format("Bad Request: ~ts", [ErrMsg0]), ErrMsg = io_lib:format("Bad Request: ~ts", [ErrMsg0]),
Payload = iolist_to_binary(ErrMsg), Payload = iolist_to_binary(ErrMsg),
iter(Iter, iter(
reply({error, bad_request}, Payload, Req, Result), Iter,
Channel); reply({error, bad_request}, Payload, Req, Result),
Channel
);
process_connection({close, Msg}, _, Channel, _) -> process_connection({close, Msg}, _, Channel, _) ->
Reply = emqx_coap_message:piggyback({ok, deleted}, Msg), Reply = emqx_coap_message:piggyback({ok, deleted}, Msg),
NChannel = ensure_disconnected(normal, Channel), NChannel = ensure_disconnected(normal, Channel),
@ -651,5 +762,4 @@ process_reply(Reply, Result, #channel{session = Session} = Channel, _) ->
Outs = maps:get(out, Result, []), Outs = maps:get(out, Result, []),
Outs2 = lists:reverse(Outs), Outs2 = lists:reverse(Outs),
Events = maps:get(events, Result, []), Events = maps:get(events, Result, []),
{ok, [{outgoing, [Reply | Outs2]}] ++ Events, {ok, [{outgoing, [Reply | Outs2]}] ++ Events, Channel#channel{session = Session2}}.
Channel#channel{session = Session2}}.

View File

@ -19,14 +19,15 @@
-behaviour(emqx_gateway_frame). -behaviour(emqx_gateway_frame).
%% emqx_gateway_frame callbacks %% emqx_gateway_frame callbacks
-export([ initial_parse_state/1 -export([
, serialize_opts/0 initial_parse_state/1,
, serialize_pkt/2 serialize_opts/0,
, parse/2 serialize_pkt/2,
, format/1 parse/2,
, type/1 format/1,
, is_message/1 type/1,
]). is_message/1
]).
-include("src/coap/include/emqx_coap.hrl"). -include("src/coap/include/emqx_coap.hrl").
-include_lib("emqx/include/types.hrl"). -include_lib("emqx/include/types.hrl").
@ -37,7 +38,8 @@
-define(OPTION_URI_HOST, 3). -define(OPTION_URI_HOST, 3).
-define(OPTION_ETAG, 4). -define(OPTION_ETAG, 4).
-define(OPTION_IF_NONE_MATCH, 5). -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_URI_PORT, 7).
-define(OPTION_LOCATION_PATH, 8). -define(OPTION_LOCATION_PATH, 8).
-define(OPTION_URI_PATH, 11). -define(OPTION_URI_PATH, 11).
@ -46,7 +48,8 @@
-define(OPTION_URI_QUERY, 15). -define(OPTION_URI_QUERY, 15).
-define(OPTION_ACCEPT, 17). -define(OPTION_ACCEPT, 17).
-define(OPTION_LOCATION_QUERY, 20). -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_BLOCK1, 27).
-define(OPTION_PROXY_URI, 35). -define(OPTION_PROXY_URI, 35).
-define(OPTION_PROXY_SCHEME, 39). -define(OPTION_PROXY_SCHEME, 39).
@ -70,22 +73,25 @@ serialize_opts() ->
%% empty message %% empty message
serialize_pkt(#coap_message{type = Type, method = undefined, id = MsgId}, _Opts) -> serialize_pkt(#coap_message{type = Type, method = undefined, id = MsgId}, _Opts) ->
<<?VERSION:2, (encode_type(Type)):2, 0:4, 0:3, 0:5, MsgId:16>>; <<?VERSION:2, (encode_type(Type)):2, 0:4, 0:3, 0:5, MsgId:16>>;
serialize_pkt(
serialize_pkt(#coap_message{ type = Type #coap_message{
, method = Method type = Type,
, id = MsgId method = Method,
, token = Token id = MsgId,
, options = Options token = Token,
, payload = Payload options = Options,
}, payload = Payload
_Opts) -> },
_Opts
) ->
TKL = byte_size(Token), TKL = byte_size(Token),
{Class, Code} = method_to_class_code(Method), {Class, Code} = method_to_class_code(Method),
Head = <<?VERSION:2, (encode_type(Type)):2, TKL:4, Class:3, Code:5, MsgId:16, Token:TKL/binary>>, Head =
<<?VERSION:2, (encode_type(Type)):2, TKL:4, Class:3, Code:5, MsgId:16, Token:TKL/binary>>,
FlatOpts = flatten_options(Options), FlatOpts = flatten_options(Options),
encode_option_list(FlatOpts, 0, Head, Payload). 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(con) -> 0;
encode_type(non) -> 1; encode_type(non) -> 1;
encode_type(ack) -> 2; encode_type(ack) -> 2;
@ -96,91 +102,121 @@ flatten_options(Opts) ->
flatten_options([{_OptId, undefined} | T], Acc) -> flatten_options([{_OptId, undefined} | T], Acc) ->
flatten_options(T, Acc); flatten_options(T, Acc);
flatten_options([{OptId, OptVal} | T], Acc) -> flatten_options([{OptId, OptVal} | T], Acc) ->
flatten_options(T, flatten_options(
case is_repeatable_option(OptId) of T,
false -> case is_repeatable_option(OptId) of
[encode_option(OptId, OptVal) | Acc]; false ->
_ -> [encode_option(OptId, OptVal) | Acc];
try_encode_repeatable(OptId, OptVal) ++ Acc _ ->
end); try_encode_repeatable(OptId, OptVal) ++ Acc
end
);
flatten_options([], Acc) -> flatten_options([], Acc) ->
%% sort by option id for calculate the deltas %% sort by option id for calculate the deltas
lists:keysort(1, Acc). lists:keysort(1, Acc).
encode_option_list([{OptNum, OptVal} | OptionList], LastNum, Acc, Payload) -> encode_option_list([{OptNum, OptVal} | OptionList], LastNum, Acc, Payload) ->
NumDiff = OptNum - LastNum, NumDiff = OptNum - LastNum,
{Delta, ExtNum} = if {Delta, ExtNum} =
NumDiff >= 269 -> if
{14, <<(NumDiff - 269):16>>}; NumDiff >= 269 ->
OptNum - LastNum >= 13 -> {14, <<(NumDiff - 269):16>>};
{13, <<(NumDiff - 13)>>}; OptNum - LastNum >= 13 ->
true -> {13, <<(NumDiff - 13)>>};
{NumDiff, <<>>} true ->
end, {NumDiff, <<>>}
end,
Binaryize = byte_size(OptVal), Binaryize = byte_size(OptVal),
{Len, ExtLen} = if {Len, ExtLen} =
Binaryize >= 269 -> if
{14, <<(Binaryize - 269):16>>}; Binaryize >= 269 ->
Binaryize >= 13 -> {14, <<(Binaryize - 269):16>>};
{13, <<(Binaryize - 13)>>}; Binaryize >= 13 ->
true -> {13, <<(Binaryize - 13)>>};
{Binaryize, <<>>} true ->
end, {Binaryize, <<>>}
end,
Acc2 = <<Acc/binary, Delta:4, Len:4, ExtNum/binary, ExtLen/binary, OptVal/binary>>, Acc2 = <<Acc/binary, Delta:4, Len:4, ExtNum/binary, ExtLen/binary, OptVal/binary>>,
encode_option_list(OptionList, OptNum, Acc2, Payload); encode_option_list(OptionList, OptNum, Acc2, Payload);
encode_option_list([], _LastNum, Acc, <<>>) -> encode_option_list([], _LastNum, Acc, <<>>) ->
Acc; Acc;
encode_option_list([], _, Acc, Payload) -> encode_option_list([], _, Acc, Payload) ->
<<Acc/binary, 16#FF, Payload/binary>>. <<Acc/binary, 16#FF, Payload/binary>>.
try_encode_repeatable(uri_query, Val) when is_map(Val) -> try_encode_repeatable(uri_query, Val) when is_map(Val) ->
maps:fold(fun(K, V, Acc) -> maps:fold(
[encode_option(uri_query, <<K/binary, $=, V/binary>>) | Acc] fun(K, V, Acc) ->
end, [encode_option(uri_query, <<K/binary, $=, V/binary>>) | Acc]
[], Val); end,
[],
Val
);
try_encode_repeatable(K, Val) -> try_encode_repeatable(K, Val) ->
lists:foldr(fun(undefined, Acc) -> lists:foldr(
Acc; fun
(E, Acc) -> (undefined, Acc) ->
[encode_option(K, E) | Acc] Acc;
end, [], Val). (E, Acc) ->
[encode_option(K, E) | Acc]
end,
[],
Val
).
%% RFC 7252 %% RFC 7252
encode_option(if_match, OptVal) -> {?OPTION_IF_MATCH, OptVal}; encode_option(if_match, OptVal) ->
encode_option(uri_host, OptVal) -> {?OPTION_URI_HOST, OptVal}; {?OPTION_IF_MATCH, OptVal};
encode_option(etag, OptVal) -> {?OPTION_ETAG, OptVal}; encode_option(uri_host, OptVal) ->
encode_option(if_none_match, true) -> {?OPTION_IF_NONE_MATCH, <<>>}; {?OPTION_URI_HOST, OptVal};
encode_option(uri_port, OptVal) -> {?OPTION_URI_PORT, binary:encode_unsigned(OptVal)}; encode_option(etag, OptVal) ->
encode_option(location_path, OptVal) -> {?OPTION_LOCATION_PATH, OptVal}; {?OPTION_ETAG, OptVal};
encode_option(uri_path, OptVal) -> {?OPTION_URI_PATH, 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) -> encode_option(content_format, OptVal) when is_integer(OptVal) ->
{?OPTION_CONTENT_FORMAT, binary:encode_unsigned(OptVal)}; {?OPTION_CONTENT_FORMAT, binary:encode_unsigned(OptVal)};
encode_option(content_format, OptVal) -> encode_option(content_format, OptVal) ->
Num = content_format_to_code(OptVal), Num = content_format_to_code(OptVal),
{?OPTION_CONTENT_FORMAT, binary:encode_unsigned(Num)}; {?OPTION_CONTENT_FORMAT, binary:encode_unsigned(Num)};
encode_option(max_age, OptVal) -> {?OPTION_MAX_AGE, binary:encode_unsigned(OptVal)}; encode_option(max_age, OptVal) ->
encode_option(uri_query, OptVal) -> {?OPTION_URI_QUERY, OptVal}; {?OPTION_MAX_AGE, binary:encode_unsigned(OptVal)};
encode_option('accept', OptVal) -> {?OPTION_ACCEPT, binary:encode_unsigned(OptVal)}; encode_option(uri_query, OptVal) ->
encode_option(location_query, OptVal) -> {?OPTION_LOCATION_QUERY, OptVal}; {?OPTION_URI_QUERY, OptVal};
encode_option(proxy_uri, OptVal) -> {?OPTION_PROXY_URI, OptVal}; encode_option('accept', OptVal) ->
encode_option(proxy_scheme, OptVal) -> {?OPTION_PROXY_SCHEME, OptVal}; {?OPTION_ACCEPT, binary:encode_unsigned(OptVal)};
encode_option(size1, OptVal) -> {?OPTION_SIZE1, binary:encode_unsigned(OptVal)}; encode_option(location_query, OptVal) ->
encode_option(observe, OptVal) -> {?OPTION_OBSERVE, binary:encode_unsigned(OptVal)}; {?OPTION_LOCATION_QUERY, OptVal};
encode_option(block2, OptVal) -> {?OPTION_BLOCK2, encode_block(OptVal)}; encode_option(proxy_uri, OptVal) ->
encode_option(block1, OptVal) -> {?OPTION_BLOCK1, encode_block(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 %% unknown opton
encode_option(Option, Value) -> encode_option(Option, Value) ->
erlang:throw({bad_option, Option, Value}). erlang:throw({bad_option, Option, Value}).
encode_block({Num, More, Size}) -> encode_block({Num, More, Size}) ->
encode_block1(Num, encode_block1(
if More -> 1; true -> 0 end, Num,
trunc(math:log2(Size))-4). if
More -> 1;
true -> 0
end,
trunc(math:log2(Size)) - 4
).
encode_block1(Num, M, SizEx) when Num < 16 -> encode_block1(Num, M, SizEx) when Num < 16 ->
<<Num:4, M:1, SizEx:3>>; <<Num:4, M:1, SizEx:3>>;
@ -192,14 +228,15 @@ encode_block1(Num, M, SizEx) ->
-spec content_format_to_code(binary()) -> non_neg_integer(). -spec content_format_to_code(binary()) -> non_neg_integer().
content_format_to_code(<<"text/plain">>) -> 0; content_format_to_code(<<"text/plain">>) -> 0;
content_format_to_code(<<"application/link-format">>) -> 40; 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/octet-stream">>) -> 42;
content_format_to_code(<<"application/exi">>) -> 47; content_format_to_code(<<"application/exi">>) -> 47;
content_format_to_code(<<"application/json">>) -> 50; content_format_to_code(<<"application/json">>) -> 50;
content_format_to_code(<<"application/cbor">>) -> 60; 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+tlv">>) -> 11542;
content_format_to_code(<<"application/vnd.oma.lwm2m+json">>) -> 11543; 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(get) -> {0, 01};
method_to_class_code(post) -> {0, 02}; 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, service_unavailable}) -> {5, 03};
method_to_class_code({error, gateway_timeout}) -> {5, 04}; method_to_class_code({error, gateway_timeout}) -> {5, 04};
method_to_class_code({error, proxying_not_supported}) -> {5, 05}; method_to_class_code({error, proxying_not_supported}) -> {5, 05};
method_to_class_code(Method) -> method_to_class_code(Method) -> erlang:throw({bad_method, Method}).
erlang:throw({bad_method, Method}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% parse %% parse
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec parse(binary(), emqx_gateway_frame:parse_state()) -spec parse(binary(), emqx_gateway_frame:parse_state()) ->
-> emqx_gateway_frame:parse_result(). emqx_gateway_frame:parse_result().
parse(<<?VERSION:2, Type:2, 0:4, 0:3, 0:5, MsgId:16>>, ParseState) -> parse(<<?VERSION:2, Type:2, 0:4, 0:3, 0:5, MsgId:16>>, ParseState) ->
{ok, {ok,
#coap_message{ type = decode_type(Type) #coap_message{
, id = MsgId}, type = decode_type(Type),
<<>>, id = MsgId
ParseState}; },
<<>>, ParseState};
parse(<<?VERSION:2, Type:2, TKL:4, Class:3, Code:5, MsgId:16, Token:TKL/binary, Tail/binary>>, parse(
ParseState) -> <<?VERSION:2, Type:2, TKL:4, Class:3, Code:5, MsgId:16, Token:TKL/binary, Tail/binary>>,
ParseState
) ->
{Options, Payload} = decode_option_list(Tail), {Options, Payload} = decode_option_list(Tail),
Options2 = maps:fold(fun(K, V, Acc) -> Options2 = maps:fold(
Acc#{K => get_option_val(K, V)} fun(K, V, Acc) ->
end, Acc#{K => get_option_val(K, V)}
#{}, end,
Options), #{},
Options
),
{ok, {ok,
#coap_message{ type = decode_type(Type) #coap_message{
, method = class_code_to_method({Class, Code}) type = decode_type(Type),
, id = MsgId method = class_code_to_method({Class, Code}),
, token = Token id = MsgId,
, options = Options2 token = Token,
, payload = Payload options = Options2,
}, payload = Payload
<<>>, },
ParseState}. <<>>, ParseState}.
get_option_val(uri_query, V) -> get_option_val(uri_query, V) ->
KVList = lists:foldl(fun(E, Acc) -> KVList = lists:foldl(
case re:split(E, "=") of fun(E, Acc) ->
[Key, Val] -> case re:split(E, "=") of
[{Key, Val} | Acc]; [Key, Val] ->
_ -> [{Key, Val} | Acc];
Acc _ ->
end Acc
end, end
[], end,
V), [],
V
),
maps:from_list(KVList); maps:from_list(KVList);
get_option_val(K, V) -> get_option_val(K, V) ->
case is_repeatable_option(K) of case is_repeatable_option(K) of
true -> true ->
@ -285,8 +326,8 @@ get_option_val(K, V) ->
V V
end. end.
-spec decode_type(X) -> message_type() -spec decode_type(X) -> message_type() when
when X :: 0 .. 3. X :: 0..3.
decode_type(0) -> con; decode_type(0) -> con;
decode_type(1) -> non; decode_type(1) -> non;
decode_type(2) -> ack; decode_type(2) -> ack;
@ -298,10 +339,8 @@ decode_option_list(Bin) ->
decode_option_list(<<>>, _OptNum, OptMap) -> decode_option_list(<<>>, _OptNum, OptMap) ->
{OptMap, <<>>}; {OptMap, <<>>};
decode_option_list(<<16#FF, Payload/binary>>, _OptNum, OptMap) -> decode_option_list(<<16#FF, Payload/binary>>, _OptNum, OptMap) ->
{OptMap, Payload}; {OptMap, Payload};
decode_option_list(<<Delta:4, Len:4, Bin/binary>>, OptNum, OptMap) -> decode_option_list(<<Delta:4, Len:4, Bin/binary>>, OptNum, OptMap) ->
case Delta of case Delta of
Any when Any < 13 -> Any when Any < 13 ->
@ -349,30 +388,48 @@ append_option(OptNum, RawOptVal, OptMap) ->
end. end.
%% RFC 7252 %% RFC 7252
decode_option(?OPTION_IF_MATCH, OptVal) -> {if_match, OptVal}; decode_option(?OPTION_IF_MATCH, OptVal) ->
decode_option(?OPTION_URI_HOST, OptVal) -> {uri_host, OptVal}; {if_match, OptVal};
decode_option(?OPTION_ETAG, OptVal) -> {etag, OptVal}; decode_option(?OPTION_URI_HOST, OptVal) ->
decode_option(?OPTION_IF_NONE_MATCH, <<>>) -> {if_none_match, true}; {uri_host, OptVal};
decode_option(?OPTION_URI_PORT, OptVal) -> {uri_port, binary:decode_unsigned(OptVal)}; decode_option(?OPTION_ETAG, OptVal) ->
decode_option(?OPTION_LOCATION_PATH, OptVal) -> {location_path, OptVal}; {etag, OptVal};
decode_option(?OPTION_URI_PATH, OptVal) -> {uri_path, 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) -> decode_option(?OPTION_CONTENT_FORMAT, OptVal) ->
Num = binary:decode_unsigned(OptVal), Num = binary:decode_unsigned(OptVal),
{content_format, content_code_to_format(Num)}; {content_format, content_code_to_format(Num)};
decode_option(?OPTION_MAX_AGE, OptVal) -> {max_age, binary:decode_unsigned(OptVal)}; decode_option(?OPTION_MAX_AGE, OptVal) ->
decode_option(?OPTION_URI_QUERY, OptVal) -> {uri_query, OptVal}; {max_age, binary:decode_unsigned(OptVal)};
decode_option(?OPTION_ACCEPT, OptVal) -> {'accept', binary:decode_unsigned(OptVal)}; decode_option(?OPTION_URI_QUERY, OptVal) ->
decode_option(?OPTION_LOCATION_QUERY, OptVal) -> {location_query, OptVal}; {uri_query, OptVal};
decode_option(?OPTION_PROXY_URI, OptVal) -> {proxy_uri, OptVal}; decode_option(?OPTION_ACCEPT, OptVal) ->
decode_option(?OPTION_PROXY_SCHEME, OptVal) -> {proxy_scheme, OptVal}; {'accept', binary:decode_unsigned(OptVal)};
decode_option(?OPTION_SIZE1, OptVal) -> {size1, 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 %% 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 %% draft-ietf-core-block-17
decode_option(?OPTION_BLOCK2, OptVal) -> {block2, decode_block(OptVal)}; decode_option(?OPTION_BLOCK2, OptVal) ->
decode_option(?OPTION_BLOCK1, OptVal) -> {block1, decode_block(OptVal)}; {block2, decode_block(OptVal)};
decode_option(?OPTION_BLOCK1, OptVal) ->
{block1, decode_block(OptVal)};
%% unknown option %% unknown option
decode_option(OptNum, OptVal) -> {OptNum, OptVal}. decode_option(OptNum, OptVal) ->
{OptNum, OptVal}.
decode_block(<<Num:4, M:1, SizEx:3>>) -> decode_block1(Num, M, SizEx); decode_block(<<Num:4, M:1, SizEx:3>>) -> decode_block1(Num, M, SizEx);
decode_block(<<Num:12, M:1, SizEx:3>>) -> decode_block1(Num, M, SizEx); decode_block(<<Num:12, M:1, SizEx:3>>) -> 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(60) -> <<"application/cbor">>;
content_code_to_format(11542) -> <<"application/vnd.oma.lwm2m+tlv">>; content_code_to_format(11542) -> <<"application/vnd.oma.lwm2m+tlv">>;
content_code_to_format(11543) -> <<"application/vnd.oma.lwm2m+json">>; 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 %% RFC 7252
%% atom indicate a request %% 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, 02}) -> post;
class_code_to_method({0, 03}) -> put; class_code_to_method({0, 03}) -> put;
class_code_to_method({0, 04}) -> delete; class_code_to_method({0, 04}) -> delete;
%% success is a tuple {ok, ...} %% success is a tuple {ok, ...}
class_code_to_method({2, 01}) -> {ok, created}; class_code_to_method({2, 01}) -> {ok, created};
class_code_to_method({2, 02}) -> {ok, deleted}; 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, 04}) -> {ok, changed};
class_code_to_method({2, 05}) -> {ok, content}; class_code_to_method({2, 05}) -> {ok, content};
class_code_to_method({2, 07}) -> {ok, nocontent}; 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, ...} %% error is a tuple {error, ...}
class_code_to_method({4, 00}) -> {error, bad_request}; class_code_to_method({4, 00}) -> {error, bad_request};
class_code_to_method({4, 01}) -> {error, unauthorized}; 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, 04}) -> {error, not_found};
class_code_to_method({4, 05}) -> {error, method_not_allowed}; class_code_to_method({4, 05}) -> {error, method_not_allowed};
class_code_to_method({4, 06}) -> {error, not_acceptable}; 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, 12}) -> {error, precondition_failed};
class_code_to_method({4, 13}) -> {error, request_entity_too_large}; class_code_to_method({4, 13}) -> {error, request_entity_too_large};
class_code_to_method({4, 15}) -> {error, unsupported_content_format}; class_code_to_method({4, 15}) -> {error, unsupported_content_format};

View File

@ -21,29 +21,33 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx_gateway/include/emqx_gateway.hrl"). -include_lib("emqx_gateway/include/emqx_gateway.hrl").
-import(emqx_gateway_utils, -import(
[ normalize_config/1 emqx_gateway_utils,
, start_listeners/4 [
, stop_listeners/2 normalize_config/1,
]). start_listeners/4,
stop_listeners/2
]
).
%% APIs %% APIs
-export([ reg/0 -export([
, unreg/0 reg/0,
]). unreg/0
]).
-export([ on_gateway_load/2 -export([
, on_gateway_update/3 on_gateway_load/2,
, on_gateway_unload/2 on_gateway_update/3,
]). on_gateway_unload/2
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
reg() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [{cbkmod, ?MODULE}],
],
emqx_gateway_registry:reg(coap, RegistryOptions). emqx_gateway_registry:reg(coap, RegistryOptions).
unreg() -> unreg() ->
@ -53,22 +57,33 @@ unreg() ->
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_gateway_load(_Gateway = #{name := GwName, on_gateway_load(
config := Config _Gateway = #{
}, Ctx) -> name := GwName,
config := Config
},
Ctx
) ->
Listeners = normalize_config(Config), Listeners = normalize_config(Config),
ModCfg = #{frame_mod => emqx_coap_frame, ModCfg = #{
chann_mod => emqx_coap_channel frame_mod => emqx_coap_frame,
}, chann_mod => emqx_coap_channel
case start_listeners( },
Listeners, GwName, Ctx, ModCfg) of case
start_listeners(
Listeners, GwName, Ctx, ModCfg
)
of
{ok, ListenerPids} -> {ok, ListenerPids} ->
{ok, ListenerPids, #{ctx => Ctx}}; {ok, ListenerPids, #{ctx => Ctx}};
{error, {Reason, Listener}} -> {error, {Reason, Listener}} ->
throw({badconf, #{ key => listeners throw(
, vallue => Listener {badconf, #{
, reason => Reason key => listeners,
}}) vallue => Listener,
reason => Reason
}}
)
end. end.
on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> 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_unload(Gateway, GwState),
on_gateway_load(Gateway#{config => Config}, Ctx) on_gateway_load(Gateway#{config => Config}, Ctx)
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
logger:error("Failed to update ~ts; " logger:error(
"reason: {~0p, ~0p} stacktrace: ~0p", "Failed to update ~ts; "
[GwName, Class, Reason, Stk]), "reason: {~0p, ~0p} stacktrace: ~0p",
[GwName, Class, Reason, Stk]
),
{error, Reason} {error, Reason}
end. end.
on_gateway_unload(_Gateway = #{ name := GwName, on_gateway_unload(
config := Config _Gateway = #{
}, _GwState) -> name := GwName,
config := Config
},
_GwState
) ->
Listeners = normalize_config(Config), Listeners = normalize_config(Config),
stop_listeners(GwName, Listeners). stop_listeners(GwName, Listeners).

View File

@ -23,10 +23,15 @@
-include("src/coap/include/emqx_coap.hrl"). -include("src/coap/include/emqx_coap.hrl").
%% API %% API
-export([ empty/0, reset/1, reset/2 -export([
, out/1, out/2, proto_out/1 empty/0,
, proto_out/2, iter/3, iter/4 reset/1, reset/2,
, reply/2, reply/3, reply/4]). out/1, out/2,
proto_out/1,
proto_out/2,
iter/3, iter/4,
reply/2, reply/3, reply/4
]).
%%-type result() :: map() | empty. %%-type result() :: map() | empty.
@ -46,7 +51,6 @@ out(Msg) ->
out(Msg, #{out := Outs} = Result) -> out(Msg, #{out := Outs} = Result) ->
Result#{out := [Msg | Outs]}; Result#{out := [Msg | Outs]};
out(Msg, Result) -> out(Msg, Result) ->
Result#{out => [Msg]}. Result#{out => [Msg]}.
@ -58,13 +62,11 @@ proto_out(Proto, Result) ->
reply(Method, Req) when not is_record(Method, coap_message) -> reply(Method, Req) when not is_record(Method, coap_message) ->
reply(Method, <<>>, Req); reply(Method, <<>>, Req);
reply(Reply, Result) -> reply(Reply, Result) ->
Result#{reply => Reply}. Result#{reply => Reply}.
reply(Method, Req, Result) when is_record(Req, coap_message) -> reply(Method, Req, Result) when is_record(Req, coap_message) ->
reply(Method, <<>>, Req, Result); reply(Method, <<>>, Req, Result);
reply(Method, Payload, Req) -> reply(Method, Payload, Req) ->
reply(Method, Payload, Req, #{}). reply(Method, Payload, Req, #{}).
@ -78,16 +80,15 @@ iter([Key, Fun | T], Input, State) ->
iter(T, Input, State); iter(T, Input, State);
Val -> Val ->
Fun(Val, maps:remove(Key, Input), State, T) Fun(Val, maps:remove(Key, Input), State, T)
%% reserved %% reserved
%% if is_function(Fun) -> %% if is_function(Fun) ->
%% Fun(Val, maps:remove(Key, Input), State, T); %% Fun(Val, maps:remove(Key, Input), State, T);
%% true -> %% true ->
%% %% switch to sub branch %% %% switch to sub branch
%% [FunH | FunT] = Fun, %% [FunH | FunT] = Fun,
%% FunH(Val, maps:remove(Key, Input), State, FunT) %% FunH(Val, maps:remove(Key, Input), State, FunT)
%% end %% end
end; end;
%% terminal node %% terminal node
iter([Fun], Input, State) -> iter([Fun], Input, State) ->
Fun(undefined, Input, State). Fun(undefined, Input, State).
@ -100,7 +101,6 @@ iter([Key, Fun | T], Input, Arg, State) ->
Val -> Val ->
Fun(Val, maps:remove(Key, Input), Arg, State, T) Fun(Val, maps:remove(Key, Input), Arg, State, T)
end; end;
iter([Fun], Input, Arg, State) -> iter([Fun], Input, Arg, State) ->
Fun(undefined, Input, Arg, State). Fun(undefined, Input, Arg, State).

View File

@ -24,15 +24,24 @@
%% convenience functions for message construction %% convenience functions for message construction
-module(emqx_coap_message). -module(emqx_coap_message).
-export([ request/2, request/3, request/4 -export([
, ack/1, response/1, response/2 request/2, request/3, request/4,
, reset/1, piggyback/2, piggyback/3 ack/1,
, response/3]). response/1, response/2,
reset/1,
piggyback/2, piggyback/3,
response/3
]).
-export([is_request/1]). -export([is_request/1]).
-export([ set/3, set_payload/2, get_option/2 -export([
, get_option/3, set_payload_block/3, set_payload_block/4]). 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"). -include("src/coap/include/emqx_coap.hrl").
@ -43,10 +52,12 @@ request(Type, Method, Payload) ->
request(Type, Method, Payload, []). request(Type, Method, Payload, []).
request(Type, Method, Payload, Options) when is_binary(Payload) -> request(Type, Method, Payload, Options) when is_binary(Payload) ->
#coap_message{type = Type, #coap_message{
method = Method, type = Type,
payload = Payload, method = Method,
options = to_options(Options)}. payload = Payload,
options = to_options(Options)
}.
ack(#coap_message{id = Id}) -> ack(#coap_message{id = Id}) ->
#coap_message{type = ack, id = Id}. #coap_message{type = ack, id = Id}.
@ -61,14 +72,18 @@ response(Request) ->
response(Method, Request) -> response(Method, Request) ->
response(Method, <<>>, Request). response(Method, <<>>, Request).
response(Method, Payload, #coap_message{type = Type, response(Method, Payload, #coap_message{
id = Id, type = Type,
token = Token}) -> id = Id,
#coap_message{type = Type, token = Token
id = Id, }) ->
token = Token, #coap_message{
method = Method, type = Type,
payload = Payload}. id = Id,
token = Token,
method = Method,
payload = Payload
}.
%% make a response which maybe is a piggyback ack %% make a response which maybe is a piggyback ack
piggyback(Method, Request) -> piggyback(Method, Request) ->
@ -84,8 +99,8 @@ piggyback(Method, Payload, Request) ->
end. end.
%% omit option for its default value %% 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 non-default value
set(Option, Value, Msg = #coap_message{options = Options}) -> set(Option, Value, Msg = #coap_message{options = Options}) ->
Msg#coap_message{options = Options#{Option => Value}}. 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) -> set_payload(Payload, Msg) when is_binary(Payload) ->
Msg#coap_message{payload = Payload}; Msg#coap_message{payload = Payload};
set_payload(Payload, Msg) when is_list(Payload) -> set_payload(Payload, Msg) when is_list(Payload) ->
Msg#coap_message{payload = list_to_binary(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, Block, Msg = #coap_message{method = Method}) when is_atom(Method) ->
set_payload_block(Content, block1, Block, Msg); set_payload_block(Content, block1, Block, Msg);
set_payload_block(Content, Block, Msg = #coap_message{}) -> set_payload_block(Content, Block, Msg = #coap_message{}) ->
set_payload_block(Content, block2, Block, Msg). set_payload_block(Content, block2, Block, Msg).
@ -114,16 +127,21 @@ set_payload_block(Content, BlockId, {Num, _, Size}, Msg) ->
OffsetEnd = OffsetBegin + Size, OffsetEnd = OffsetBegin + Size,
case ContentSize > OffsetEnd of case ContentSize > OffsetEnd of
true -> true ->
set(BlockId, {Num, true, Size}, set(
set_payload(binary:part(Content, OffsetBegin, Size), Msg)); BlockId,
{Num, true, Size},
set_payload(binary:part(Content, OffsetBegin, Size), Msg)
);
_ -> _ ->
set(BlockId, {Num, false, Size}, set(
set_payload(binary:part(Content, OffsetBegin, ContentSize - OffsetBegin), Msg)) BlockId,
{Num, false, Size},
set_payload(binary:part(Content, OffsetBegin, ContentSize - OffsetBegin), Msg)
)
end. end.
is_request(#coap_message{method = Method}) when is_atom(Method) -> is_request(#coap_message{method = Method}) when is_atom(Method) ->
Method =/= undefined; Method =/= undefined;
is_request(_) -> is_request(_) ->
false. false.

View File

@ -17,18 +17,25 @@
-module(emqx_coap_observe_res). -module(emqx_coap_observe_res).
%% API %% API
-export([ new_manager/0, insert/3, remove/2 -export([
, res_changed/2, foreach/2, subscriptions/1]). new_manager/0,
insert/3,
remove/2,
res_changed/2,
foreach/2,
subscriptions/1
]).
-export_type([manager/0]). -export_type([manager/0]).
-define(MAX_SEQ_ID, 16777215). -define(MAX_SEQ_ID, 16777215).
-type token() :: binary(). -type token() :: binary().
-type seq_id() :: 0 .. ?MAX_SEQ_ID. -type seq_id() :: 0..?MAX_SEQ_ID.
-type res() :: #{ token := token() -type res() :: #{
, seq_id := seq_id() token := token(),
}. seq_id := seq_id()
}.
-type manager() :: #{emqx_types:topic() => res()}. -type manager() :: #{emqx_types:topic() => res()}.
@ -41,12 +48,13 @@ new_manager() ->
-spec insert(emqx_types:topic(), token(), manager()) -> {seq_id(), manager()}. -spec insert(emqx_types:topic(), token(), manager()) -> {seq_id(), manager()}.
insert(Topic, Token, Manager) -> insert(Topic, Token, Manager) ->
Res = case maps:get(Topic, Manager, undefined) of Res =
undefined -> case maps:get(Topic, Manager, undefined) of
new_res(Token); undefined ->
Any -> new_res(Token);
Any Any ->
end, Any
end,
{maps:get(seq_id, Res), Manager#{Topic => Res}}. {maps:get(seq_id, Res), Manager#{Topic => Res}}.
-spec remove(emqx_types:topic(), manager()) -> manager(). -spec remove(emqx_types:topic(), manager()) -> manager().
@ -59,17 +67,21 @@ res_changed(Topic, Manager) ->
undefined -> undefined ->
undefined; undefined;
Res -> Res ->
#{token := Token, #{
seq_id := SeqId} = Res2 = res_changed(Res), token := Token,
seq_id := SeqId
} = Res2 = res_changed(Res),
{Token, SeqId, Manager#{Topic := Res2}} {Token, SeqId, Manager#{Topic := Res2}}
end. end.
foreach(F, Manager) -> foreach(F, Manager) ->
maps:fold(fun(K, V, _) -> maps:fold(
F(K, V) fun(K, V, _) ->
end, F(K, V)
ok, end,
Manager), ok,
Manager
),
ok. ok.
-spec subscriptions(manager()) -> [emqx_types:topic()]. -spec subscriptions(manager()) -> [emqx_types:topic()].
@ -81,8 +93,10 @@ subscriptions(Manager) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec new_res(token()) -> res(). -spec new_res(token()) -> res().
new_res(Token) -> new_res(Token) ->
#{token => Token, #{
seq_id => 0}. token => Token,
seq_id => 0
}.
-spec res_changed(res()) -> res(). -spec res_changed(res()) -> res().
res_changed(#{seq_id := SeqId} = Res) -> res_changed(#{seq_id := SeqId} = Res) ->

View File

@ -21,50 +21,57 @@
-include("src/coap/include/emqx_coap.hrl"). -include("src/coap/include/emqx_coap.hrl").
%% API %% API
-export([ new/0 -export([
, process_subscribe/4 new/0,
]). process_subscribe/4
]).
-export([ info/1 -export([
, info/2 info/1,
, stats/1 info/2,
]). stats/1
]).
-export([ handle_request/2 -export([
, handle_response/2 handle_request/2,
, handle_out/2 handle_response/2,
, set_reply/2 handle_out/2,
, deliver/3 set_reply/2,
, timeout/2]). deliver/3,
timeout/2
]).
-export_type([session/0]). -export_type([session/0]).
-record(session, { transport_manager :: emqx_coap_tm:manager() -record(session, {
, observe_manager :: emqx_coap_observe_res:manager() transport_manager :: emqx_coap_tm:manager(),
, created_at :: pos_integer() observe_manager :: emqx_coap_observe_res:manager(),
}). created_at :: pos_integer()
}).
-type session() :: #session{}. -type session() :: #session{}.
%% steal from emqx_session %% steal from emqx_session
-define(INFO_KEYS, [subscriptions, -define(INFO_KEYS, [
upgrade_qos, subscriptions,
retry_interval, upgrade_qos,
await_rel_timeout, retry_interval,
created_at await_rel_timeout,
]). created_at
]).
-define(STATS_KEYS, [subscriptions_cnt, -define(STATS_KEYS, [
subscriptions_max, subscriptions_cnt,
inflight_cnt, subscriptions_max,
inflight_max, inflight_cnt,
mqueue_len, inflight_max,
mqueue_max, mqueue_len,
mqueue_dropped, mqueue_max,
next_pkt_id, mqueue_dropped,
awaiting_rel_cnt, next_pkt_id,
awaiting_rel_max awaiting_rel_cnt,
]). awaiting_rel_max
]).
-import(emqx_coap_medium, [iter/3]). -import(emqx_coap_medium, [iter/3]).
-import(emqx_coap_channel, [metrics_inc/2]). -import(emqx_coap_channel, [metrics_inc/2]).
@ -75,16 +82,18 @@
-spec new() -> session(). -spec new() -> session().
new() -> new() ->
_ = emqx_misc:rand_seed(), _ = emqx_misc:rand_seed(),
#session{ transport_manager = emqx_coap_tm:new() #session{
, observe_manager = emqx_coap_observe_res:new_manager() transport_manager = emqx_coap_tm:new(),
, created_at = erlang:system_time(millisecond)}. observe_manager = emqx_coap_observe_res:new_manager(),
created_at = erlang:system_time(millisecond)
}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Info, Stats %% Info, Stats
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Compatible with emqx_session %% @doc Compatible with emqx_session
%% do we need use inflight and mqueue in here? %% do we need use inflight and mqueue in here?
-spec(info(session()) -> emqx_types:infos()). -spec info(session()) -> emqx_types:infos().
info(Session) -> info(Session) ->
maps:from_list(info(?INFO_KEYS, 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}) -> info(subscriptions, #session{observe_manager = OM}) ->
Topics = emqx_coap_observe_res:subscriptions(OM), Topics = emqx_coap_observe_res:subscriptions(OM),
lists:foldl( lists:foldl(
fun(T, Acc) -> Acc#{T => emqx_gateway_utils:default_subopts()} end, fun(T, Acc) -> Acc#{T => emqx_gateway_utils:default_subopts()} end,
#{}, Topics); #{},
Topics
);
info(subscriptions_cnt, #session{observe_manager = OM}) -> info(subscriptions_cnt, #session{observe_manager = OM}) ->
erlang:length(emqx_coap_observe_res:subscriptions(OM)); erlang:length(emqx_coap_observe_res:subscriptions(OM));
info(subscriptions_max, _) -> info(subscriptions_max, _) ->
@ -131,16 +142,18 @@ info(created_at, #session{created_at = CreatedAt}) ->
CreatedAt. CreatedAt.
%% @doc Get stats of the session. %% @doc Get stats of the session.
-spec(stats(session()) -> emqx_types:stats()). -spec stats(session()) -> emqx_types:stats().
stats(Session) -> info(?STATS_KEYS, Session). stats(Session) -> info(?STATS_KEYS, Session).
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% Process Message %%% Process Message
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
handle_request(Msg, Session) -> handle_request(Msg, Session) ->
call_transport_manager(?FUNCTION_NAME, call_transport_manager(
Msg, ?FUNCTION_NAME,
Session). Msg,
Session
).
handle_response(Msg, Session) -> handle_response(Msg, Session) ->
call_transport_manager(?FUNCTION_NAME, 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), TM2 = emqx_coap_tm:set_reply(Msg, TM),
Session#session{transport_manager = TM2}. Session#session{transport_manager = TM2}.
deliver(Delivers, Ctx, #session{observe_manager = OM, deliver(
transport_manager = TM} = Session) -> Delivers,
Ctx,
#session{
observe_manager = OM,
transport_manager = TM
} = Session
) ->
Fun = fun({_, Topic, Message}, {OutAcc, OMAcc, TMAcc} = Acc) -> Fun = fun({_, Topic, Message}, {OutAcc, OMAcc, TMAcc} = Acc) ->
case emqx_coap_observe_res:res_changed(Topic, OMAcc) of case emqx_coap_observe_res:res_changed(Topic, OMAcc) of
undefined -> undefined ->
metrics_inc('delivery.dropped', Ctx), metrics_inc('delivery.dropped', Ctx),
metrics_inc('delivery.dropped.no_subid', Ctx), metrics_inc('delivery.dropped.no_subid', Ctx),
Acc; Acc;
{Token, SeqId, OM2} -> {Token, SeqId, OM2} ->
metrics_inc('messages.delivered', Ctx), metrics_inc('messages.delivered', Ctx),
Msg = mqtt_to_coap(Message, Token, SeqId), Msg = mqtt_to_coap(Message, Token, SeqId),
#{out := Out, tm := TM2} = emqx_coap_tm:handle_out(Msg, TMAcc), #{out := Out, tm := TM2} = emqx_coap_tm:handle_out(Msg, TMAcc),
{Out ++ OutAcc, OM2, TM2} {Out ++ OutAcc, OM2, TM2}
end end
end, end,
{Outs, OM2, TM2} = lists:foldl(Fun, {[], OM, TM}, lists:reverse(Delivers)), {Outs, OM2, TM2} = lists:foldl(Fun, {[], OM, TM}, lists:reverse(Delivers)),
#{out => lists:reverse(Outs), #{
session => Session#session{observe_manager = OM2, out => lists:reverse(Outs),
transport_manager = TM2}}. session => Session#session{
observe_manager = OM2,
transport_manager = TM2
}
}.
timeout(Timer, Session) -> timeout(Timer, Session) ->
call_transport_manager(?FUNCTION_NAME, Timer, Session). call_transport_manager(?FUNCTION_NAME, Timer, Session).
@ -179,13 +202,17 @@ timeout(Timer, Session) ->
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%%% Internal functions %%% Internal functions
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
call_transport_manager(Fun, call_transport_manager(
Msg, Fun,
#session{transport_manager = TM} = Session) -> Msg,
#session{transport_manager = TM} = Session
) ->
Result = emqx_coap_tm:Fun(Msg, TM), Result = emqx_coap_tm:Fun(Msg, TM),
iter([tm, fun process_tm/4, fun process_session/3], iter(
Result, [tm, fun process_tm/4, fun process_session/3],
Session). Result,
Session
).
process_tm(TM, Result, Session, Cursor) -> process_tm(TM, Result, Session, Cursor) ->
iter(Cursor, Result, Session#session{transport_manager = TM}). iter(Cursor, Result, Session#session{transport_manager = TM}).
@ -193,8 +220,12 @@ process_tm(TM, Result, Session, Cursor) ->
process_session(_, Result, Session) -> process_session(_, Result, Session) ->
Result#{session => Session}. Result#{session => Session}.
process_subscribe(Sub, Msg, Result, process_subscribe(
#session{observe_manager = OM} = Session) -> Sub,
Msg,
Result,
#session{observe_manager = OM} = Session
) ->
case Sub of case Sub of
undefined -> undefined ->
Result; Result;
@ -202,22 +233,28 @@ process_subscribe(Sub, Msg, Result,
{SeqId, OM2} = emqx_coap_observe_res:insert(Topic, Token, OM), {SeqId, OM2} = emqx_coap_observe_res:insert(Topic, Token, OM),
Replay = emqx_coap_message:piggyback({ok, content}, Msg), Replay = emqx_coap_message:piggyback({ok, content}, Msg),
Replay2 = Replay#coap_message{options = #{observe => SeqId}}, Replay2 = Replay#coap_message{options = #{observe => SeqId}},
Result#{reply => Replay2, Result#{
session => Session#session{observe_manager = OM2}}; reply => Replay2,
session => Session#session{observe_manager = OM2}
};
Topic -> Topic ->
OM2 = emqx_coap_observe_res:remove(Topic, OM), OM2 = emqx_coap_observe_res:remove(Topic, OM),
Replay = emqx_coap_message:piggyback({ok, nocontent}, Msg), Replay = emqx_coap_message:piggyback({ok, nocontent}, Msg),
Result#{reply => Replay, Result#{
session => Session#session{observe_manager = OM2}} reply => Replay,
session => Session#session{observe_manager = OM2}
}
end. end.
mqtt_to_coap(MQTT, Token, SeqId) -> mqtt_to_coap(MQTT, Token, SeqId) ->
#message{payload = Payload} = MQTT, #message{payload = Payload} = MQTT,
#coap_message{type = get_notify_type(MQTT), #coap_message{
method = {ok, content}, type = get_notify_type(MQTT),
token = Token, method = {ok, content},
payload = Payload, token = Token,
options = #{observe => SeqId}}. payload = Payload,
options = #{observe => SeqId}
}.
get_notify_type(#message{qos = Qos}) -> get_notify_type(#message{qos = Qos}) ->
case emqx_conf:get([gateway, coap, notify_qos], non) of case emqx_conf:get([gateway, coap, notify_qos], non) of

View File

@ -17,13 +17,15 @@
%% the transport state machine manager %% the transport state machine manager
-module(emqx_coap_tm). -module(emqx_coap_tm).
-export([ new/0 -export([
, handle_request/2 new/0,
, handle_response/2 handle_request/2,
, handle_out/2 handle_response/2,
, handle_out/3 handle_out/2,
, set_reply/2 handle_out/3,
, timeout/2]). set_reply/2,
timeout/2
]).
-export_type([manager/0, event_result/1]). -export_type([manager/0, event_result/1]).
@ -32,41 +34,47 @@
-type direction() :: in | out. -type direction() :: in | out.
-record(state_machine, { seq_id :: seq_id() -record(state_machine, {
, id :: state_machine_key() seq_id :: seq_id(),
, token :: token() | undefined id :: state_machine_key(),
, observe :: 0 | 1 | undefined | observed token :: token() | undefined,
, state :: atom() observe :: 0 | 1 | undefined | observed,
, timers :: maps:map() state :: atom(),
, transport :: emqx_coap_transport:transport()}). timers :: maps:map(),
transport :: emqx_coap_transport:transport()
}).
-type state_machine() :: #state_machine{}. -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 token_key() :: {token, token()}.
-type state_machine_key() :: {direction(), message_id()}. -type state_machine_key() :: {direction(), message_id()}.
-type seq_id() :: pos_integer(). -type seq_id() :: pos_integer().
-type manager_key() :: token_key() | state_machine_key() | seq_id(). -type manager_key() :: token_key() | state_machine_key() | seq_id().
-type manager() :: #{ seq_id => seq_id() -type manager() :: #{
, next_msg_id => coap_message_id() seq_id => seq_id(),
, token_key() => seq_id() next_msg_id => coap_message_id(),
, state_machine_key() => seq_id() token_key() => seq_id(),
, seq_id() => state_machine() state_machine_key() => seq_id(),
}. seq_id() => state_machine()
}.
-type ttimeout() :: {state_timeout, pos_integer(), any()} -type ttimeout() ::
| {stop_timeout, pos_integer()}. {state_timeout, pos_integer(), any()}
| {stop_timeout, pos_integer()}.
-type topic() :: binary(). -type topic() :: binary().
-type token() :: binary(). -type token() :: binary().
-type sub_register() :: {topic(), token()} | topic(). -type sub_register() :: {topic(), token()} | topic().
-type event_result(State) :: -type event_result(State) ::
#{next => State, #{
outgoing => coap_message(), next => State,
timeouts => list(ttimeout()), outgoing => coap_message(),
has_sub => undefined | sub_register(), timeouts => list(ttimeout()),
transport => emqx_coap_transport:transport()}. has_sub => undefined | sub_register(),
transport => emqx_coap_transport:transport()
}.
-define(TOKEN_ID(T), {token, T}). -define(TOKEN_ID(T), {token, T}).
@ -78,9 +86,10 @@
-spec new() -> manager(). -spec new() -> manager().
new() -> 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) -> handle_request(#coap_message{id = MsgId} = Msg, TM) ->
Id = {in, MsgId}, 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 %% send to a client, msg can be request/piggyback/separate/notify
handle_out({Ctx, Msg}, TM) -> handle_out({Ctx, Msg}, TM) ->
handle_out(Msg, Ctx, TM); handle_out(Msg, Ctx, TM);
handle_out(Msg, TM) -> handle_out(Msg, TM) ->
handle_out(Msg, undefined, TM). handle_out(Msg, undefined, TM).
@ -135,8 +143,10 @@ set_reply(#coap_message{id = MsgId} = Msg, TM) ->
case find_machine(Id, TM) of case find_machine(Id, TM) of
undefined -> undefined ->
TM; TM;
#state_machine{transport = Transport, #state_machine{
seq_id = SeqId} = Machine -> transport = Transport,
seq_id = SeqId
} = Machine ->
Transport2 = emqx_coap_transport:set_cache(Msg, Transport), Transport2 = emqx_coap_transport:set_cache(Msg, Transport),
Machine2 = Machine#state_machine{transport = Transport2}, Machine2 = Machine#state_machine{transport = Transport2},
TM#{SeqId => Machine2} TM#{SeqId => Machine2}
@ -161,35 +171,52 @@ timeout({SeqId, Type, Msg}, TM) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
process_event(stop_timeout, _, TM, Machine) -> process_event(stop_timeout, _, TM, Machine) ->
process_manager(stop, #{}, Machine, TM); process_manager(stop, #{}, Machine, TM);
process_event(
process_event(Event, Msg, TM, #state_machine{state = State, Event,
transport = Transport} = Machine) -> Msg,
TM,
#state_machine{
state = State,
transport = Transport
} = Machine
) ->
Result = emqx_coap_transport:State(Event, Msg, Transport), Result = emqx_coap_transport:State(Event, Msg, Transport),
iter([ proto, fun process_observe_response/5 iter(
, next, fun process_state_change/5 [
, transport, fun process_transport_change/5 proto,
, timeouts, fun process_timeouts/5 fun process_observe_response/5,
, fun process_manager/4], next,
Result, fun process_state_change/5,
Machine, transport,
TM). fun process_transport_change/5,
timeouts,
fun process_timeouts/5,
fun process_manager/4
],
Result,
Machine,
TM
).
process_observe_response({response, {_, Msg}} = Response, process_observe_response(
Result, {response, {_, Msg}} = Response,
#state_machine{observe = 0} = Machine, Result,
TM, #state_machine{observe = 0} = Machine,
Iter) -> TM,
Iter
) ->
Result2 = proto_out(Response, Result), Result2 = proto_out(Response, Result),
case Msg#coap_message.method of case Msg#coap_message.method of
{ok, _} -> {ok, _} ->
iter(Iter, iter(
Result2#{next => observe}, Iter,
Machine#state_machine{observe = observed}, Result2#{next => observe},
TM); Machine#state_machine{observe = observed},
TM
);
_ -> _ ->
iter(Iter, Result2, Machine, TM) iter(Iter, Result2, Machine, TM)
end; end;
process_observe_response(Proto, Result, Machine, TM, Iter) -> process_observe_response(Proto, Result, Machine, TM, Iter) ->
iter(Iter, proto_out(Proto, Result), Machine, TM). iter(Iter, proto_out(Proto, Result), Machine, TM).
@ -198,10 +225,12 @@ process_state_change(Next, Result, Machine, TM, Iter) ->
stop -> stop ->
process_manager(stop, Result, Machine, TM); process_manager(stop, Result, Machine, TM);
_ -> _ ->
iter(Iter, iter(
Result, Iter,
cancel_state_timer(Machine#state_machine{state = Next}), Result,
TM) cancel_state_timer(Machine#state_machine{state = Next}),
TM
)
end. end.
process_transport_change(Transport, Result, Machine, TM, Iter) -> 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) -> process_timeouts([], Result, Machine, TM, Iter) ->
iter(Iter, Result, Machine, TM); iter(Iter, Result, Machine, TM);
process_timeouts(
process_timeouts(Timeouts, Result, Timeouts,
#state_machine{seq_id = SeqId, Result,
timers = Timers} = Machine, TM, Iter) -> #state_machine{
NewTimers = lists:foldl(fun({state_timeout, _, _} = Timer, Acc) -> seq_id = SeqId,
process_timer(SeqId, Timer, Acc); timers = Timers
({stop_timeout, I}, Acc) -> } = Machine,
process_timer(SeqId, {stop_timeout, I, stop}, Acc) TM,
end, Iter
Timers, ) ->
Timeouts), 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). iter(Iter, Result, Machine#state_machine{timers = NewTimers}, TM).
process_manager(stop, Result, #state_machine{seq_id = SeqId}, TM) -> process_manager(stop, Result, #state_machine{seq_id = SeqId}, TM) ->
Result#{tm => delete_machine(SeqId, TM)}; Result#{tm => delete_machine(SeqId, TM)};
process_manager(_, Result, #state_machine{seq_id = SeqId} = Machine2, TM) -> process_manager(_, Result, #state_machine{seq_id = SeqId} = Machine2, TM) ->
Result#{tm => TM#{SeqId => Machine2}}. Result#{tm => TM#{SeqId => Machine2}}.
@ -246,14 +283,18 @@ delete_machine(Id, Manager) ->
case find_machine(Id, Manager) of case find_machine(Id, Manager) of
undefined -> undefined ->
Manager; Manager;
#state_machine{seq_id = SeqId, #state_machine{
id = MachineId, seq_id = SeqId,
token = Token, id = MachineId,
timers = Timers} -> token = Token,
lists:foreach(fun({_, Ref}) -> timers = Timers
emqx_misc:cancel_timer(Ref) } ->
end, lists:foreach(
maps:to_list(Timers)), fun({_, Ref}) ->
emqx_misc:cancel_timer(Ref)
end,
maps:to_list(Timers)
),
maps:without([SeqId, MachineId, ?TOKEN_ID(Token)], Manager) maps:without([SeqId, MachineId, ?TOKEN_ID(Token)], Manager)
end. end.
@ -264,12 +305,14 @@ find_machine(SeqId, Manager) ->
find_machine_by_seqid(SeqId, Manager). find_machine_by_seqid(SeqId, Manager).
-spec find_machine_by_seqid(seq_id() | undefined, manager()) -> -spec find_machine_by_seqid(seq_id() | undefined, manager()) ->
state_machine() | undefined. state_machine() | undefined.
find_machine_by_seqid(SeqId, Manager) -> find_machine_by_seqid(SeqId, Manager) ->
maps:get(SeqId, Manager, undefined). maps:get(SeqId, Manager, undefined).
-spec find_machine_by_keys(list(manager_key()), -spec find_machine_by_keys(
manager()) -> state_machine() | undefined. list(manager_key()),
manager()
) -> state_machine() | undefined.
find_machine_by_keys([H | T], Manager) -> find_machine_by_keys([H | T], Manager) ->
case H of case H of
?TOKEN_ID(<<>>) -> ?TOKEN_ID(<<>>) ->
@ -286,53 +329,63 @@ find_machine_by_keys(_, _) ->
undefined. undefined.
-spec new_in_machine(state_machine_key(), manager()) -> -spec new_in_machine(state_machine_key(), manager()) ->
{state_machine(), manager()}. {state_machine(), manager()}.
new_in_machine(MachineId, #{seq_id := SeqId} = Manager) -> new_in_machine(MachineId, #{seq_id := SeqId} = Manager) ->
Machine = #state_machine{ seq_id = SeqId Machine = #state_machine{
, id = MachineId seq_id = SeqId,
, state = idle id = MachineId,
, timers = #{} state = idle,
, transport = emqx_coap_transport:new()}, timers = #{},
{Machine, Manager#{seq_id := SeqId + 1, transport = emqx_coap_transport:new()
SeqId => Machine, },
MachineId => SeqId}}. {Machine, Manager#{
seq_id := SeqId + 1,
SeqId => Machine,
MachineId => SeqId
}}.
-spec new_out_machine(state_machine_key(), any(), coap_message(), manager()) -> -spec new_out_machine(state_machine_key(), any(), coap_message(), manager()) ->
{state_machine(), manager()}. {state_machine(), manager()}.
new_out_machine(MachineId, new_out_machine(
Ctx, MachineId,
#coap_message{type = Type, token = Token, options = Opts}, Ctx,
#{seq_id := SeqId} = Manager) -> #coap_message{type = Type, token = Token, options = Opts},
#{seq_id := SeqId} = Manager
) ->
Observe = maps:get(observe, Opts, undefined), Observe = maps:get(observe, Opts, undefined),
Machine = #state_machine{ seq_id = SeqId Machine = #state_machine{
, id = MachineId seq_id = SeqId,
, token = Token id = MachineId,
, observe = Observe token = Token,
, state = idle observe = Observe,
, timers = #{} state = idle,
, transport = emqx_coap_transport:new(Ctx)}, timers = #{},
transport = emqx_coap_transport:new(Ctx)
},
Manager2 = Manager#{seq_id := SeqId + 1, Manager2 = Manager#{
SeqId => Machine, seq_id := SeqId + 1,
MachineId => SeqId}, SeqId => Machine,
MachineId => SeqId
},
{Machine, {Machine,
if Token =:= <<>> -> if
Manager2; Token =:= <<>> ->
Observe =:= 1 -> Manager2;
TokenId = ?TOKEN_ID(Token), Observe =:= 1 ->
delete_machine(TokenId, Manager2); TokenId = ?TOKEN_ID(Token),
Type =:= con orelse Observe =:= 0 -> delete_machine(TokenId, Manager2);
TokenId = ?TOKEN_ID(Token), Type =:= con orelse Observe =:= 0 ->
case maps:get(TokenId, Manager, undefined) of TokenId = ?TOKEN_ID(Token),
undefined -> case maps:get(TokenId, Manager, undefined) of
Manager2#{TokenId => SeqId}; undefined ->
_ -> Manager2#{TokenId => SeqId};
throw("token conflict") _ ->
end; throw("token conflict")
true -> end;
Manager2 true ->
end Manager2
}. end}.
alloc_message_id(#{next_msg_id := MsgId} = TM) -> alloc_message_id(#{next_msg_id := MsgId} = TM) ->
alloc_message_id(MsgId, TM). alloc_message_id(MsgId, TM).
@ -348,8 +401,9 @@ alloc_message_id(MsgId, TM) ->
next_message_id(MsgId) -> next_message_id(MsgId) ->
Next = MsgId + 1, Next = MsgId + 1,
if Next >= ?MAX_MESSAGE_ID -> if
Next >= ?MAX_MESSAGE_ID ->
1; 1;
true -> true ->
Next Next
end. end.

View File

@ -11,71 +11,102 @@
-type request_context() :: any(). -type request_context() :: any().
-record(transport, { cache :: undefined | coap_message() -record(transport, {
, req_context :: request_context() cache :: undefined | coap_message(),
, retry_interval :: non_neg_integer() req_context :: request_context(),
, retry_count :: non_neg_integer() retry_interval :: non_neg_integer(),
, observe :: non_neg_integer() | undefined retry_count :: non_neg_integer(),
}). observe :: non_neg_integer() | undefined
}).
-type transport() :: #transport{}. -type transport() :: #transport{}.
-export([ new/0, new/1, idle/3, maybe_reset/3, set_cache/2 -export([
, maybe_resend_4request/3, wait_ack/3, until_stop/3 new/0, new/1,
, observe/3, maybe_resend_4response/3]). 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]). -export_type([transport/0]).
-import(emqx_coap_medium, [ empty/0, reset/2, proto_out/2 -import(emqx_coap_medium, [
, out/1, out/2, proto_out/1 empty/0,
, reply/2]). reset/2,
proto_out/2,
out/1, out/2,
proto_out/1,
reply/2
]).
-spec new() -> transport(). -spec new() -> transport().
new() -> new() ->
new(undefined). new(undefined).
new(ReqCtx) -> new(ReqCtx) ->
#transport{cache = undefined, #transport{
retry_interval = 0, cache = undefined,
retry_count = 0, retry_interval = 0,
req_context = ReqCtx}. retry_count = 0,
req_context = ReqCtx
}.
idle(in, idle(
#coap_message{type = non, method = Method} = Msg, in,
_) -> #coap_message{type = non, method = Method} = Msg,
_
) ->
case Method of case Method of
undefined -> undefined ->
reset(Msg, #{next => stop}); reset(Msg, #{next => stop});
_ -> _ ->
proto_out({request, Msg}, proto_out(
#{next => until_stop, {request, Msg},
timeouts => #{
[{stop_timeout, ?NON_LIFETIME}]}) next => until_stop,
timeouts =>
[{stop_timeout, ?NON_LIFETIME}]
}
)
end; end;
idle(
idle(in, in,
#coap_message{type = con, method = Method} = Msg, #coap_message{type = con, method = Method} = Msg,
_) -> _
) ->
case Method of case Method of
undefined -> undefined ->
reset(Msg, #{next => stop}); reset(Msg, #{next => stop});
_ -> _ ->
proto_out({request, Msg}, proto_out(
#{next => maybe_resend_4request, {request, Msg},
timeouts =>[{stop_timeout, ?EXCHANGE_LIFETIME}]}) #{
next => maybe_resend_4request,
timeouts => [{stop_timeout, ?EXCHANGE_LIFETIME}]
}
)
end; end;
idle(out, #coap_message{type = non} = Msg, _) -> idle(out, #coap_message{type = non} = Msg, _) ->
out(Msg, #{next => maybe_reset, out(Msg, #{
timeouts => [{stop_timeout, ?NON_LIFETIME}]}); next => maybe_reset,
timeouts => [{stop_timeout, ?NON_LIFETIME}]
});
idle(out, Msg, Transport) -> idle(out, Msg, Transport) ->
_ = emqx_misc:rand_seed(), _ = emqx_misc:rand_seed(),
Timeout = ?ACK_TIMEOUT + rand:uniform(?ACK_RANDOM_FACTOR), Timeout = ?ACK_TIMEOUT + rand:uniform(?ACK_RANDOM_FACTOR),
out(Msg, #{next => wait_ack, out(Msg, #{
transport => Transport#transport{cache = Msg}, next => wait_ack,
timeouts => [ {state_timeout, Timeout, ack_timeout} transport => Transport#transport{cache = Msg},
, {stop_timeout, ?EXCHANGE_LIFETIME}]}). timeouts => [
{state_timeout, Timeout, ack_timeout},
{stop_timeout, ?EXCHANGE_LIFETIME}
]
}).
maybe_resend_4request(in, Msg, Transport) -> maybe_resend_4request(in, Msg, Transport) ->
maybe_resend(Msg, true, Transport). maybe_resend(Msg, true, Transport).
@ -98,19 +129,26 @@ maybe_resend(Msg, IsExpecteReq, #transport{cache = Cache}) ->
reset(Msg, #{next => stop}) reset(Msg, #{next => stop})
end. end.
maybe_reset(in, #coap_message{type = Type, method = Method} = Message, maybe_reset(
#transport{req_context = Ctx} = Transport) -> in,
#coap_message{type = Type, method = Method} = Message,
#transport{req_context = Ctx} = Transport
) ->
Ret = #{next => stop}, Ret = #{next => stop},
CtxMsg = {Ctx, Message}, CtxMsg = {Ctx, Message},
if Type =:= reset -> if
Type =:= reset ->
proto_out({reset, CtxMsg}, Ret); proto_out({reset, CtxMsg}, Ret);
is_tuple(Method) -> is_tuple(Method) ->
on_response(Message, on_response(
Transport, Message,
if Type =:= non -> until_stop; Transport,
true -> maybe_resend_4response if
end); Type =:= non -> until_stop;
true -> true -> maybe_resend_4response
end
);
true ->
reset(Message, Ret) reset(Message, Ret)
end. end.
@ -131,26 +169,37 @@ wait_ack(in, #coap_message{type = Type, method = Method} = Msg, #transport{req_c
reset(Msg, #{next => stop}) reset(Msg, #{next => stop})
end end
end; end;
wait_ack(
wait_ack(state_timeout, state_timeout,
ack_timeout, ack_timeout,
#transport{cache = Msg, #transport{
retry_interval = Timeout, cache = Msg,
retry_count = Count} =Transport) -> retry_interval = Timeout,
retry_count = Count
} = Transport
) ->
case Count < ?MAX_RETRANSMIT of case Count < ?MAX_RETRANSMIT of
true -> true ->
Timeout2 = Timeout * 2, Timeout2 = Timeout * 2,
out(Msg, out(
#{transport => Transport#transport{retry_interval = Timeout2, Msg,
retry_count = Count + 1}, #{
timeouts => [{state_timeout, Timeout2, ack_timeout}]}); transport => Transport#transport{
retry_interval = Timeout2,
retry_count = Count + 1
},
timeouts => [{state_timeout, Timeout2, ack_timeout}]
}
);
_ -> _ ->
proto_out({ack_failure, Msg}, #{next_state => stop}) proto_out({ack_failure, Msg}, #{next_state => stop})
end. end.
observe(in, observe(
#coap_message{method = Method} = Message, in,
#transport{observe = Observe} = Transport) -> #coap_message{method = Method} = Message,
#transport{observe = Observe} = Transport
) ->
case Method of case Method of
{ok, _} -> {ok, _} ->
case emqx_coap_message:get_option(observe, Message, Observe) of case emqx_coap_message:get_option(observe, Message, Observe) of
@ -158,9 +207,11 @@ observe(in,
%% repeatd notify, ignore %% repeatd notify, ignore
empty(); empty();
NewObserve -> NewObserve ->
on_response(Message, on_response(
Transport#transport{observe = NewObserve}, Message,
?FUNCTION_NAME) Transport#transport{observe = NewObserve},
?FUNCTION_NAME
)
end; end;
{error, _} -> {error, _} ->
#{next => stop}; #{next => stop};
@ -174,17 +225,24 @@ until_stop(_, _, _) ->
set_cache(Cache, Transport) -> set_cache(Cache, Transport) ->
Transport#transport{cache = Cache}. Transport#transport{cache = Cache}.
on_response(#coap_message{type = Type} = Message, on_response(
#transport{req_context = Ctx} = Transport, #coap_message{type = Type} = Message,
NextState) -> #transport{req_context = Ctx} = Transport,
NextState
) ->
CtxMsg = {Ctx, Message}, CtxMsg = {Ctx, Message},
if Type =:= non -> if
Type =:= non ->
proto_out({response, CtxMsg}, #{next => NextState}); proto_out({response, CtxMsg}, #{next => NextState});
Type =:= con -> Type =:= con ->
Ack = emqx_coap_message:ack(Message), Ack = emqx_coap_message:ack(Message),
proto_out({response, CtxMsg}, proto_out(
out(Ack, #{next => NextState, {response, CtxMsg},
transport => Transport#transport{cache = Ack}})); out(Ack, #{
true -> next => NextState,
transport => Transport#transport{cache = Ack}
})
);
true ->
emqx_coap_message:reset(Message) emqx_coap_message:reset(Message)
end. end.

View File

@ -24,18 +24,14 @@
handle_request([<<"connection">>], #coap_message{method = Method} = Msg, _Ctx, _CInfo) -> handle_request([<<"connection">>], #coap_message{method = Method} = Msg, _Ctx, _CInfo) ->
handle_method(Method, Msg); handle_method(Method, Msg);
handle_request(_, Msg, _, _) -> handle_request(_, Msg, _, _) ->
reply({error, bad_request}, Msg). reply({error, bad_request}, Msg).
handle_method(put, Msg) -> handle_method(put, Msg) ->
reply({ok, changed}, Msg); reply({ok, changed}, Msg);
handle_method(post, Msg) -> handle_method(post, Msg) ->
#{connection => {open, Msg}}; #{connection => {open, Msg}};
handle_method(delete, Msg) -> handle_method(delete, Msg) ->
#{connection => {close, Msg}}; #{connection => {close, Msg}};
handle_method(_, Msg) -> handle_method(_, Msg) ->
reply({error, method_not_allowed}, Msg). reply({error, method_not_allowed}, Msg).

View File

@ -49,7 +49,6 @@ handle_method(get, Topic, Msg, Ctx, CInfo) ->
_ -> _ ->
reply({error, bad_request}, <<"invalid observe value">>, Msg) reply({error, bad_request}, <<"invalid observe value">>, Msg)
end; end;
handle_method(post, Topic, #coap_message{payload = Payload} = Msg, Ctx, CInfo) -> handle_method(post, Topic, #coap_message{payload = Payload} = Msg, Ctx, CInfo) ->
case emqx_coap_channel:validator(publish, Topic, Ctx, CInfo) of case emqx_coap_channel:validator(publish, Topic, Ctx, CInfo) of
allow -> allow ->
@ -64,22 +63,23 @@ handle_method(post, Topic, #coap_message{payload = Payload} = Msg, Ctx, CInfo) -
_ -> _ ->
reply({error, unauthorized}, Msg) reply({error, unauthorized}, Msg)
end; end;
handle_method(_, _, Msg, _, _) -> handle_method(_, _, Msg, _, _) ->
reply({error, method_not_allowed}, Msg). reply({error, method_not_allowed}, Msg).
check_topic([]) -> check_topic([]) ->
error; error;
check_topic(Path) -> check_topic(Path) ->
Sep = <<"/">>, Sep = <<"/">>,
{ok, {ok,
emqx_http_lib:uri_decode( emqx_http_lib:uri_decode(
lists:foldl(fun(Part, Acc) -> lists:foldl(
<<Acc/binary, Sep/binary, Part/binary>> fun(Part, Acc) ->
end, <<Acc/binary, Sep/binary, Part/binary>>
<<>>, end,
Path))}. <<>>,
Path
)
)}.
get_sub_opts(#coap_message{options = Opts} = Msg) -> get_sub_opts(#coap_message{options = Opts} = Msg) ->
SubOpts = maps:fold(fun parse_sub_opts/3, #{}, Opts), SubOpts = maps:fold(fun parse_sub_opts/3, #{}, Opts),
@ -100,9 +100,12 @@ parse_sub_opts(<<"rh">>, V, Opts) ->
parse_sub_opts(_, _, Opts) -> parse_sub_opts(_, _, Opts) ->
Opts. Opts.
type_to_qos(qos0, _) -> ?QOS_0; type_to_qos(qos0, _) ->
type_to_qos(qos1, _) -> ?QOS_1; ?QOS_0;
type_to_qos(qos2, _) -> ?QOS_2; type_to_qos(qos1, _) ->
?QOS_1;
type_to_qos(qos2, _) ->
?QOS_2;
type_to_qos(coap, #coap_message{type = Type}) -> type_to_qos(coap, #coap_message{type = Type}) ->
case Type of case Type of
non -> non ->
@ -121,24 +124,28 @@ get_publish_qos(Msg) ->
end. end.
apply_publish_opts(Msg, MQTTMsg) -> apply_publish_opts(Msg, MQTTMsg) ->
maps:fold(fun(<<"retain">>, V, Acc) -> maps:fold(
Val = erlang:binary_to_atom(V), fun
emqx_message:set_flag(retain, Val, Acc); (<<"retain">>, V, Acc) ->
(<<"expiry">>, V, Acc) -> Val = erlang:binary_to_atom(V),
Val = erlang:binary_to_integer(V), emqx_message:set_flag(retain, Val, Acc);
Props = emqx_message:get_header(properties, Acc), (<<"expiry">>, V, Acc) ->
emqx_message:set_header(properties, Val = erlang:binary_to_integer(V),
Props#{'Message-Expiry-Interval' => Val}, Props = emqx_message:get_header(properties, Acc),
Acc); emqx_message:set_header(
(_, _, Acc) -> properties,
Acc Props#{'Message-Expiry-Interval' => Val},
end, Acc
MQTTMsg, );
emqx_coap_message:get_option(uri_query, Msg)). (_, _, Acc) ->
Acc
end,
MQTTMsg,
emqx_coap_message:get_option(uri_query, Msg)
).
subscribe(#coap_message{token = <<>>} = Msg, _, _, _) -> subscribe(#coap_message{token = <<>>} = Msg, _, _, _) ->
reply({error, bad_request}, <<"observe without token">>, Msg); reply({error, bad_request}, <<"observe without token">>, Msg);
subscribe(#coap_message{token = Token} = Msg, Topic, Ctx, CInfo) -> subscribe(#coap_message{token = Token} = Msg, Topic, Ctx, CInfo) ->
case emqx_coap_channel:validator(subscribe, Topic, Ctx, CInfo) of case emqx_coap_channel:validator(subscribe, Topic, Ctx, CInfo) of
allow -> allow ->

View File

@ -22,55 +22,60 @@
-define(DEFAULT_MAX_AGE, 60). -define(DEFAULT_MAX_AGE, 60).
-define(MAXIMUM_MAX_AGE, 4294967295). -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 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 -type message_option_name() ::
| uri_host if_match
| etag | uri_host
| if_none_match | etag
| uri_port | if_none_match
| location_path | uri_port
| uri_path | location_path
| content_format | uri_path
| max_age | content_format
| uri_query | max_age
| 'accept' | uri_query
| location_query | 'accept'
| proxy_uri | location_query
| proxy_scheme | proxy_uri
| size1 | proxy_scheme
| observer | size1
| block1 | observer
| block2. | block1
| block2.
-type message_options() :: #{ if_match => list(binary()) -type message_options() :: #{
, uri_host => binary() if_match => list(binary()),
, etag => list(binary()) uri_host => binary(),
, if_none_match => boolean() etag => list(binary()),
, uri_port => 0 .. 65535 if_none_match => boolean(),
, location_path => list(binary()) uri_port => 0..65535,
, uri_path => list(binary()) location_path => list(binary()),
, content_format => 0 .. 65535 uri_path => list(binary()),
, max_age => non_neg_integer() content_format => 0..65535,
, uri_query => list(binary()) | map() max_age => non_neg_integer(),
, 'accept' => 0 .. 65535 uri_query => list(binary()) | map(),
, location_query => list(binary()) 'accept' => 0..65535,
, proxy_uri => binary() location_query => list(binary()),
, proxy_scheme => binary() proxy_uri => binary(),
, size1 => non_neg_integer() proxy_scheme => binary(),
, observer => non_neg_integer() size1 => non_neg_integer(),
, block1 => {non_neg_integer(), boolean(), non_neg_integer()} observer => non_neg_integer(),
, block2 => {non_neg_integer(), boolean(), 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_mqtt_auth, {clientid, username, password}).
-record(coap_message, { type :: message_type() -record(coap_message, {
, method type :: message_type(),
, id method,
, token = <<>> id,
, options = #{} token = <<>>,
, payload = <<>>}). options = #{},
payload = <<>>
}).
-type coap_message() :: #coap_message{}. -type coap_message() :: #coap_message{}.

View File

@ -1,12 +1,12 @@
%% -*- mode: erlang -*- %% -*- mode: erlang -*-
{application, emqx_gateway, {application, emqx_gateway, [
[{description, "The Gateway management application"}, {description, "The Gateway management application"},
{vsn, "0.1.0"}, {vsn, "0.1.0"},
{registered, []}, {registered, []},
{mod, {emqx_gateway_app, []}}, {mod, {emqx_gateway_app, []}},
{applications, [kernel, stdlib, grpc, emqx]}, {applications, [kernel, stdlib, grpc, emqx]},
{env, []}, {env, []},
{modules, []}, {modules, []},
{licenses, ["Apache 2.0"]}, {licenses, ["Apache 2.0"]},
{links, []} {links, []}
]}. ]}.

View File

@ -19,15 +19,16 @@
-include("include/emqx_gateway.hrl"). -include("include/emqx_gateway.hrl").
%% Gateway APIs %% Gateway APIs
-export([ registered_gateway/0 -export([
, load/2 registered_gateway/0,
, unload/1 load/2,
, lookup/1 unload/1,
, update/2 lookup/1,
, start/1 update/2,
, stop/1 start/1,
, list/0 stop/1,
]). list/0
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
@ -46,14 +47,15 @@ registered_gateway() ->
list() -> list() ->
emqx_gateway_sup:list_gateway_insta(). emqx_gateway_sup:list_gateway_insta().
-spec load(gateway_name(), emqx_config:config()) -spec load(gateway_name(), emqx_config:config()) ->
-> {ok, pid()} {ok, pid()}
| {error, any()}. | {error, any()}.
load(Name, Config) -> load(Name, Config) ->
Gateway = #{ name => Name Gateway = #{
, descr => undefined name => Name,
, config => Config descr => undefined,
}, config => Config
},
emqx_gateway_sup:load_gateway(Gateway). emqx_gateway_sup:load_gateway(Gateway).
-spec unload(gateway_name()) -> ok | {error, not_found}. -spec unload(gateway_name()) -> ok | {error, not_found}.

File diff suppressed because it is too large Load Diff

View File

@ -24,25 +24,30 @@
-import(hoconsc, [mk/2, ref/2]). -import(hoconsc, [mk/2, ref/2]).
-import(emqx_dashboard_swagger, [error_codes/2]). -import(emqx_dashboard_swagger, [error_codes/2]).
-import(emqx_gateway_http, -import(
[ return_http_error/2 emqx_gateway_http,
, with_gateway/2 [
, with_authn/2 return_http_error/2,
, checks/2 with_gateway/2,
]). with_authn/2,
checks/2
]
).
%% minirest/dashbaord_swagger behaviour callbacks %% minirest/dashbaord_swagger behaviour callbacks
-export([ api_spec/0 -export([
, paths/0 api_spec/0,
, schema/1 paths/0,
]). schema/1
]).
%% http handlers %% http handlers
-export([ authn/2 -export([
, users/2 authn/2,
, users_insta/2 users/2,
, import_users/2 users_insta/2,
]). import_users/2
]).
%% internal export for emqx_gateway_api_listeners module %% internal export for emqx_gateway_api_listeners module
-export([schema_authn/0]). -export([schema_authn/0]).
@ -55,10 +60,11 @@ api_spec() ->
emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}). emqx_dashboard_swagger:spec(?MODULE, #{check_schema => true}).
paths() -> paths() ->
[ "/gateway/:name/authentication" [
, "/gateway/:name/authentication/users" "/gateway/:name/authentication",
, "/gateway/:name/authentication/users/:uid" "/gateway/:name/authentication/users",
, "/gateway/:name/authentication/import_users" "/gateway/:name/authentication/users/:uid",
"/gateway/:name/authentication/import_users"
]. ].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -66,30 +72,29 @@ paths() ->
authn(get, #{bindings := #{name := Name0}}) -> authn(get, #{bindings := #{name := Name0}}) ->
with_gateway(Name0, fun(GwName, _) -> with_gateway(Name0, fun(GwName, _) ->
try try emqx_gateway_http:authn(GwName) of
emqx_gateway_http:authn(GwName)
of
Authn -> {200, Authn} Authn -> {200, Authn}
catch catch
error : {config_not_found, _} -> error:{config_not_found, _} ->
{204} {204}
end end
end); end);
authn(put, #{
authn(put, #{bindings := #{name := Name0}, bindings := #{name := Name0},
body := Body}) -> body := Body
}) ->
with_gateway(Name0, fun(GwName, _) -> with_gateway(Name0, fun(GwName, _) ->
{ok, Authn} = emqx_gateway_http:update_authn(GwName, Body), {ok, Authn} = emqx_gateway_http:update_authn(GwName, Body),
{200, Authn} {200, Authn}
end); end);
authn(post, #{
authn(post, #{bindings := #{name := Name0}, bindings := #{name := Name0},
body := Body}) -> body := Body
}) ->
with_gateway(Name0, fun(GwName, _) -> with_gateway(Name0, fun(GwName, _) ->
{ok, Authn} = emqx_gateway_http:add_authn(GwName, Body), {ok, Authn} = emqx_gateway_http:add_authn(GwName, Body),
{201, Authn} {201, Authn}
end); end);
authn(delete, #{bindings := #{name := Name0}}) -> authn(delete, #{bindings := #{name := Name0}}) ->
with_gateway(Name0, fun(GwName, _) -> with_gateway(Name0, fun(GwName, _) ->
ok = emqx_gateway_http:remove_authn(GwName), ok = emqx_gateway_http:remove_authn(GwName),
@ -97,47 +102,85 @@ authn(delete, #{bindings := #{name := Name0}}) ->
end). end).
users(get, #{bindings := #{name := Name0}, query_string := Qs}) -> users(get, #{bindings := #{name := Name0}, query_string := Qs}) ->
with_authn(Name0, fun(_GwName, #{id := AuthId, with_authn(Name0, fun(
chain_name := ChainName}) -> _GwName,
#{
id := AuthId,
chain_name := ChainName
}
) ->
emqx_authn_api:list_users(ChainName, AuthId, parse_qstring(Qs)) emqx_authn_api:list_users(ChainName, AuthId, parse_qstring(Qs))
end); end);
users(post, #{bindings := #{name := Name0}, users(post, #{
body := Body}) -> bindings := #{name := Name0},
with_authn(Name0, fun(_GwName, #{id := AuthId, body := Body
chain_name := ChainName}) -> }) ->
with_authn(Name0, fun(
_GwName,
#{
id := AuthId,
chain_name := ChainName
}
) ->
emqx_authn_api:add_user(ChainName, AuthId, Body) emqx_authn_api:add_user(ChainName, AuthId, Body)
end). end).
users_insta(get, #{bindings := #{name := Name0, uid := UserId}}) -> users_insta(get, #{bindings := #{name := Name0, uid := UserId}}) ->
with_authn(Name0, fun(_GwName, #{id := AuthId, with_authn(Name0, fun(
chain_name := ChainName}) -> _GwName,
#{
id := AuthId,
chain_name := ChainName
}
) ->
emqx_authn_api:find_user(ChainName, AuthId, UserId) emqx_authn_api:find_user(ChainName, AuthId, UserId)
end); end);
users_insta(put, #{bindings := #{name := Name0, uid := UserId}, users_insta(put, #{
body := Body}) -> bindings := #{name := Name0, uid := UserId},
with_authn(Name0, fun(_GwName, #{id := AuthId, body := Body
chain_name := ChainName}) -> }) ->
with_authn(Name0, fun(
_GwName,
#{
id := AuthId,
chain_name := ChainName
}
) ->
emqx_authn_api:update_user(ChainName, AuthId, UserId, Body) emqx_authn_api:update_user(ChainName, AuthId, UserId, Body)
end); end);
users_insta(delete, #{bindings := #{name := Name0, uid := UserId}}) -> users_insta(delete, #{bindings := #{name := Name0, uid := UserId}}) ->
with_authn(Name0, fun(_GwName, #{id := AuthId, with_authn(Name0, fun(
chain_name := ChainName}) -> _GwName,
#{
id := AuthId,
chain_name := ChainName
}
) ->
emqx_authn_api:delete_user(ChainName, AuthId, UserId) emqx_authn_api:delete_user(ChainName, AuthId, UserId)
end). end).
import_users(post, #{bindings := #{name := Name0}, import_users(post, #{
body := Body}) -> bindings := #{name := Name0},
with_authn(Name0, fun(_GwName, #{id := AuthId, body := Body
chain_name := ChainName}) -> }) ->
with_authn(Name0, fun(
_GwName,
#{
id := AuthId,
chain_name := ChainName
}
) ->
case maps:get(<<"filename">>, Body, undefined) of case maps:get(<<"filename">>, Body, undefined) of
undefined -> undefined ->
emqx_authn_api:serialize_error({missing_parameter, filename}); emqx_authn_api:serialize_error({missing_parameter, filename});
Filename -> Filename ->
case emqx_authentication:import_users( case
ChainName, AuthId, Filename) of emqx_authentication:import_users(
ChainName, AuthId, Filename
)
of
ok -> {204}; ok -> {204};
{error, Reason} -> {error, Reason} -> emqx_authn_api:serialize_error(Reason)
emqx_authn_api:serialize_error(Reason)
end end
end end
end). end).
@ -146,178 +189,243 @@ import_users(post, #{bindings := #{name := Name0},
%% Utils %% Utils
parse_qstring(Qs) -> parse_qstring(Qs) ->
maps:with([ <<"page">> maps:with(
, <<"limit">> [
, <<"like_username">> <<"page">>,
, <<"like_clientid">>], Qs). <<"limit">>,
<<"like_username">>,
<<"like_clientid">>
],
Qs
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Swagger defines %% Swagger defines
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
schema("/gateway/:name/authentication") -> schema("/gateway/:name/authentication") ->
#{ 'operationId' => authn, #{
get => 'operationId' => authn,
#{ desc => <<"Get the gateway authentication">> get =>
, parameters => params_gateway_name_in_path() #{
, responses => desc => <<"Get the gateway authentication">>,
?STANDARD_RESP( parameters => params_gateway_name_in_path(),
#{ 200 => schema_authn() responses =>
, 204 => <<"Authentication does not initiated">> ?STANDARD_RESP(
}) #{
}, 200 => schema_authn(),
put => 204 => <<"Authentication does not initiated">>
#{ 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())
})
},
put => put =>
#{ desc => <<"Update the user info for the gateway " #{
"authentication">> desc => <<"Update authentication for the gateway">>,
, parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path(),
params_userid_in_path() 'requestBody' => schema_authn(),
, 'requestBody' => emqx_dashboard_swagger:schema_with_examples( responses =>
ref(emqx_authn_api, request_user_update), ?STANDARD_RESP(#{200 => schema_authn()})
emqx_authn_api:request_user_update_examples()) },
, responses => post =>
?STANDARD_RESP( #{
#{ 200 => emqx_dashboard_swagger:schema_with_example( desc => <<"Add authentication for the gateway">>,
ref(emqx_authn_api, response_user), parameters => params_gateway_name_in_path(),
emqx_authn_api:response_user_examples()) 'requestBody' => schema_authn(),
}) responses =>
}, ?STANDARD_RESP(#{201 => schema_authn()})
},
delete => delete =>
#{ desc => <<"Delete the user for the gateway " #{
"authentication">> desc => <<"Remove the gateway authentication">>,
, parameters => params_gateway_name_in_path() ++ parameters => params_gateway_name_in_path(),
params_userid_in_path() responses =>
, responses => ?STANDARD_RESP(#{204 => <<"Deleted">>})
?STANDARD_RESP(#{204 => <<"User Deleted">>}) }
} };
}; schema("/gateway/:name/authentication/users") ->
schema("/gateway/:name/authentication/import_users") -> #{
#{ 'operationId' => import_users 'operationId' => users,
, post => get =>
#{ desc => <<"Import users into the gateway authentication">> #{
, parameters => params_gateway_name_in_path() desc => <<"Get the users for the authentication">>,
, 'requestBody' => emqx_dashboard_swagger:schema_with_examples( parameters => params_gateway_name_in_path() ++
ref(emqx_authn_api, request_import_users), params_paging_in_qs() ++
emqx_authn_api:request_import_users_examples() 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 defines
params_gateway_name_in_path() -> params_gateway_name_in_path() ->
[{name, [
mk(binary(), {name,
#{ in => path mk(
, desc => <<"Gateway Name">> binary(),
, example => <<"">> #{
})} in => path,
desc => <<"Gateway Name">>,
example => <<"">>
}
)}
]. ].
params_userid_in_path() -> params_userid_in_path() ->
[{uid, mk(binary(), [
#{ in => path {uid,
, desc => <<"User ID">> mk(
, example => <<"">> binary(),
})} #{
in => path,
desc => <<"User ID">>,
example => <<"">>
}
)}
]. ].
params_paging_in_qs() -> params_paging_in_qs() ->
[{page, mk(integer(), [
#{ in => query {page,
, required => false mk(
, desc => <<"Page Index">> integer(),
, example => 1 #{
})}, in => query,
{limit, mk(integer(), required => false,
#{ in => query desc => <<"Page Index">>,
, required => false example => 1
, desc => <<"Page Limit">> }
, example => 100 )},
})} {limit,
mk(
integer(),
#{
in => query,
required => false,
desc => <<"Page Limit">>,
example => 100
}
)}
]. ].
params_fuzzy_in_qs() -> params_fuzzy_in_qs() ->
[{like_username, [
mk(binary(), {like_username,
#{ in => query mk(
, required => false binary(),
, desc => <<"Fuzzy search by username">> #{
, example => <<"username">> in => query,
})}, required => false,
{like_clientid, desc => <<"Fuzzy search by username">>,
mk(binary(), example => <<"username">>
#{ in => query }
, required => false )},
, desc => <<"Fuzzy search by clientid">> {like_clientid,
, example => <<"clientid">> mk(
})} binary(),
#{
in => query,
required => false,
desc => <<"Fuzzy search by clientid">>,
example => <<"clientid">>
}
)}
]. ].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -325,6 +433,6 @@ params_fuzzy_in_qs() ->
schema_authn() -> schema_authn() ->
emqx_dashboard_swagger:schema_with_examples( emqx_dashboard_swagger:schema_with_examples(
emqx_authn_schema:authenticator_type(), emqx_authn_schema:authenticator_type(),
emqx_authn_api:authenticator_examples() emqx_authn_api:authenticator_examples()
). ).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,8 @@
-export([start/2, stop/1]). -export([start/2, stop/1]).
-elvis([{elvis_style, invalid_dynamic_call, disable}]).
start(_StartType, _StartArgs) -> start(_StartType, _StartArgs) ->
{ok, Sup} = emqx_gateway_sup:start_link(), {ok, Sup} = emqx_gateway_sup:start_link(),
emqx_gateway_cli:load(), emqx_gateway_cli:load(),
@ -44,22 +46,29 @@ load_default_gateway_applications() ->
gateway_type_searching() -> gateway_type_searching() ->
%% FIXME: Hardcoded apps %% 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) -> reg(Mod) ->
try try
Mod:reg(), Mod:reg(),
?SLOG(debug, #{ msg => "register_gateway_succeed" ?SLOG(debug, #{
, callback_module => Mod msg => "register_gateway_succeed",
}) callback_module => Mod
})
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
?SLOG(error, #{ msg => "failed_to_register_gateway" ?SLOG(error, #{
, callback_module => Mod msg => "failed_to_register_gateway",
, reason => {Class, Reason} callback_module => Mod,
, stacktrace => Stk reason => {Class, Reason},
}) stacktrace => Stk
})
end. end.
load_gateway_by_default() -> load_gateway_by_default() ->
@ -67,22 +76,26 @@ load_gateway_by_default() ->
load_gateway_by_default([]) -> load_gateway_by_default([]) ->
ok; ok;
load_gateway_by_default([{Type, Confs}|More]) -> load_gateway_by_default([{Type, Confs} | More]) ->
case emqx_gateway_registry:lookup(Type) of case emqx_gateway_registry:lookup(Type) of
undefined -> undefined ->
?SLOG(error, #{ msg => "skip_to_load_gateway" ?SLOG(error, #{
, gateway_name => Type msg => "skip_to_load_gateway",
}); gateway_name => Type
});
_ -> _ ->
case emqx_gateway:load(Type, Confs) of case emqx_gateway:load(Type, Confs) of
{ok, _} -> {ok, _} ->
?SLOG(debug, #{ msg => "load_gateway_succeed" ?SLOG(debug, #{
, gateway_name => Type msg => "load_gateway_succeed",
}); gateway_name => Type
});
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{ msg => "load_gateway_failed" ?SLOG(error, #{
, gateway_name => Type msg => "load_gateway_failed",
, reason => Reason}) gateway_name => Type,
reason => Reason
})
end end
end, end,
load_gateway_by_default(More). load_gateway_by_default(More).

View File

@ -17,25 +17,30 @@
%% @doc The Command-Line-Interface module for Gateway Application %% @doc The Command-Line-Interface module for Gateway Application
-module(emqx_gateway_cli). -module(emqx_gateway_cli).
-export([ load/0 -export([
, unload/0 load/0,
]). unload/0
]).
-export([ gateway/1 -export([
, 'gateway-registry'/1 gateway/1,
, 'gateway-clients'/1 'gateway-registry'/1,
, 'gateway-metrics'/1 'gateway-clients'/1,
%, 'gateway-banned'/1 'gateway-metrics'/1
]). %, 'gateway-banned'/1
]).
-elvis([{elvis_style, function_naming_convention, disable}]). -elvis([{elvis_style, function_naming_convention, disable}]).
-spec load() -> ok. -spec load() -> ok.
load() -> load() ->
Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)], Cmds = [Fun || {Fun, _} <- ?MODULE:module_info(exports), is_cmd(Fun)],
lists:foreach(fun(Cmd) -> lists:foreach(
emqx_ctl:register_command(Cmd, {?MODULE, Cmd}, []) fun(Cmd) ->
end, Cmds). emqx_ctl:register_command(Cmd, {?MODULE, Cmd}, [])
end,
Cmds
).
-spec unload() -> ok. -spec unload() -> ok.
unload() -> unload() ->
@ -53,10 +58,11 @@ is_cmd(Fun) ->
gateway(["list"]) -> gateway(["list"]) ->
lists:foreach( lists:foreach(
fun (GwSummary) -> fun(GwSummary) ->
print(format_gw_summary(GwSummary)) print(format_gw_summary(GwSummary))
end, emqx_gateway_http:gateways(all)); end,
emqx_gateway_http:gateways(all)
);
gateway(["lookup", Name]) -> gateway(["lookup", Name]) ->
case emqx_gateway:lookup(atom(Name)) of case emqx_gateway:lookup(atom(Name)) of
undefined -> undefined ->
@ -64,18 +70,18 @@ gateway(["lookup", Name]) ->
Gateway -> Gateway ->
print(format_gateway(Gateway)) print(format_gateway(Gateway))
end; end;
gateway(["load", Name, Conf]) -> gateway(["load", Name, Conf]) ->
case emqx_gateway_conf:load_gateway( case
bin(Name), emqx_gateway_conf:load_gateway(
emqx_json:decode(Conf, [return_maps]) bin(Name),
) of emqx_json:decode(Conf, [return_maps])
)
of
{ok, _} -> {ok, _} ->
print("ok\n"); print("ok\n");
{error, Reason} -> {error, Reason} ->
print("Error: ~ts\n", [format_error(Reason)]) print("Error: ~ts\n", [format_error(Reason)])
end; end;
gateway(["unload", Name]) -> gateway(["unload", Name]) ->
case emqx_gateway_conf:unload_gateway(bin(Name)) of case emqx_gateway_conf:unload_gateway(bin(Name)) of
ok -> ok ->
@ -83,56 +89,51 @@ gateway(["unload", Name]) ->
{error, Reason} -> {error, Reason} ->
print("Error: ~ts\n", [format_error(Reason)]) print("Error: ~ts\n", [format_error(Reason)])
end; end;
gateway(["stop", Name]) -> gateway(["stop", Name]) ->
case emqx_gateway_conf:update_gateway( case
bin(Name), emqx_gateway_conf:update_gateway(
#{<<"enable">> => <<"false">>} bin(Name),
) of #{<<"enable">> => <<"false">>}
)
of
{ok, _} -> {ok, _} ->
print("ok\n"); print("ok\n");
{error, Reason} -> {error, Reason} ->
print("Error: ~ts\n", [format_error(Reason)]) print("Error: ~ts\n", [format_error(Reason)])
end; end;
gateway(["start", Name]) -> gateway(["start", Name]) ->
case emqx_gateway_conf:update_gateway( case
bin(Name), emqx_gateway_conf:update_gateway(
#{<<"enable">> => <<"true">>} bin(Name),
) of #{<<"enable">> => <<"true">>}
)
of
{ok, _} -> {ok, _} ->
print("ok\n"); print("ok\n");
{error, Reason} -> {error, Reason} ->
print("Error: ~ts\n", [format_error(Reason)]) print("Error: ~ts\n", [format_error(Reason)])
end; end;
gateway(_) -> gateway(_) ->
emqx_ctl:usage( emqx_ctl:usage(
[ {"gateway list", [
"List all gateway"} {"gateway list", "List all gateway"},
, {"gateway lookup <Name>", {"gateway lookup <Name>", "Lookup a gateway detailed information"},
"Lookup a gateway detailed information"} {"gateway load <Name> <JsonConf>", "Load a gateway with config"},
, {"gateway load <Name> <JsonConf>", {"gateway unload <Name>", "Unload the gateway"},
"Load a gateway with config"} {"gateway stop <Name>", "Stop the gateway"},
, {"gateway unload <Name>", {"gateway start <Name>", "Start the gateway"}
"Unload the gateway"} ]
, {"gateway stop <Name>", ).
"Stop the gateway"}
, {"gateway start <Name>",
"Start the gateway"}
]).
'gateway-registry'(["list"]) -> 'gateway-registry'(["list"]) ->
lists:foreach( lists:foreach(
fun({Name, #{cbkmod := CbMod}}) -> fun({Name, #{cbkmod := CbMod}}) ->
print("Registered Name: ~ts, Callback Module: ~ts\n", [Name, CbMod]) print("Registered Name: ~ts, Callback Module: ~ts\n", [Name, CbMod])
end, end,
emqx_gateway_registry:list()); emqx_gateway_registry:list()
);
'gateway-registry'(_) -> 'gateway-registry'(_) ->
emqx_ctl:usage([ {"gateway-registry list", emqx_ctl:usage([{"gateway-registry list", "List all registered gateways"}]).
"List all registered gateways"}
]).
'gateway-clients'(["list", Name]) -> 'gateway-clients'(["list", Name]) ->
%% XXX: page me? %% XXX: page me?
@ -143,7 +144,6 @@ gateway(_) ->
_ -> _ ->
dump(InfoTab, client) dump(InfoTab, client)
end; end;
'gateway-clients'(["lookup", Name, ClientId]) -> 'gateway-clients'(["lookup", Name, ClientId]) ->
ChanTab = emqx_gateway_cm:tabname(chan, Name), ChanTab = emqx_gateway_cm:tabname(chan, Name),
case ets:info(ChanTab) of case ets:info(ChanTab) of
@ -151,28 +151,25 @@ gateway(_) ->
print("Bad Gateway Name.\n"); print("Bad Gateway Name.\n");
_ -> _ ->
case ets:lookup(ChanTab, bin(ClientId)) of case ets:lookup(ChanTab, bin(ClientId)) of
[] -> print("Not Found.\n"); [] ->
print("Not Found.\n");
[Chann] -> [Chann] ->
InfoTab = emqx_gateway_cm:tabname(info, Name), InfoTab = emqx_gateway_cm:tabname(info, Name),
[ChannInfo] = ets:lookup(InfoTab, Chann), [ChannInfo] = ets:lookup(InfoTab, Chann),
print_record({client, ChannInfo}) print_record({client, ChannInfo})
end end
end; end;
'gateway-clients'(["kick", Name, ClientId]) -> 'gateway-clients'(["kick", Name, ClientId]) ->
case emqx_gateway_cm:kick_session(Name, bin(ClientId)) of case emqx_gateway_cm:kick_session(Name, bin(ClientId)) of
ok -> print("ok\n"); ok -> print("ok\n");
_ -> print("Not Found.\n") _ -> print("Not Found.\n")
end; end;
'gateway-clients'(_) -> 'gateway-clients'(_) ->
emqx_ctl:usage([ {"gateway-clients list <Name>", emqx_ctl:usage([
"List all clients for a gateway"} {"gateway-clients list <Name>", "List all clients for a gateway"},
, {"gateway-clients lookup <Name> <ClientId>", {"gateway-clients lookup <Name> <ClientId>", "Lookup the Client Info for specified client"},
"Lookup the Client Info for specified client"} {"gateway-clients kick <Name> <ClientId>", "Kick out a client"}
, {"gateway-clients kick <Name> <ClientId>", ]).
"Kick out a client"}
]).
'gateway-metrics'([Name]) -> 'gateway-metrics'([Name]) ->
case emqx_gateway_metrics:lookup(atom(Name)) of case emqx_gateway_metrics:lookup(atom(Name)) of
@ -180,20 +177,18 @@ gateway(_) ->
print("Bad Gateway Name.\n"); print("Bad Gateway Name.\n");
Metrics -> Metrics ->
lists:foreach( lists:foreach(
fun({K, V}) -> print("~-30s: ~w\n", [K, V]) end, fun({K, V}) -> print("~-30s: ~w\n", [K, V]) end,
Metrics) Metrics
)
end; end;
'gateway-metrics'(_) -> 'gateway-metrics'(_) ->
emqx_ctl:usage([ {"gateway-metrics <Name>", emqx_ctl:usage([{"gateway-metrics <Name>", "List all metrics for a gateway"}]).
"List all metrics for a gateway"}
]).
atom(Id) -> atom(Id) ->
try try
list_to_existing_atom(Id) list_to_existing_atom(Id)
catch catch
_ : _ -> undefined _:_ -> undefined
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -207,77 +202,103 @@ dump(Table, Tag) ->
dump(_Table, _, '$end_of_table', Result) -> dump(_Table, _, '$end_of_table', Result) ->
lists:reverse(Result); lists:reverse(Result);
dump(Table, Tag, Key, Result) -> dump(Table, Tag, Key, Result) ->
PrintValue = [print_record({Tag, Record}) || Record <- ets:lookup(Table, Key)], PrintValue = [print_record({Tag, Record}) || Record <- ets:lookup(Table, Key)],
dump(Table, Tag, ets:next(Table, Key), [PrintValue | Result]). dump(Table, Tag, ets:next(Table, Key), [PrintValue | Result]).
print_record({client, {_, Infos, Stats}}) -> print_record({client, {_, Infos, Stats}}) ->
ClientInfo = maps:get(clientinfo, Infos, #{}), ClientInfo = maps:get(clientinfo, Infos, #{}),
ConnInfo = maps:get(conninfo, Infos, #{}), ConnInfo = maps:get(conninfo, Infos, #{}),
_Session = maps:get(session, Infos, #{}), _Session = maps:get(session, Infos, #{}),
SafeGet = fun(K, M) -> maps:get(K, M, undefined) end, SafeGet = fun(K, M) -> maps:get(K, M, undefined) end,
StatsGet = fun(K) -> proplists:get_value(K, Stats, 0) end, StatsGet = fun(K) -> proplists:get_value(K, Stats, 0) end,
ConnectedAt = SafeGet(connected_at, ConnInfo), ConnectedAt = SafeGet(connected_at, ConnInfo),
InfoKeys = [clientid, username, peername, clean_start, keepalive, InfoKeys = [
subscriptions_cnt, send_msg, connected, created_at, connected_at], clientid,
Info = #{ clientid => SafeGet(clientid, ClientInfo), username,
username => SafeGet(username, ClientInfo), peername,
peername => SafeGet(peername, ConnInfo), clean_start,
clean_start => SafeGet(clean_start, ConnInfo), keepalive,
keepalive => SafeGet(keepalive, ConnInfo), subscriptions_cnt,
subscriptions_cnt => StatsGet(subscriptions_cnt), send_msg,
send_msg => StatsGet(send_msg), connected,
connected => SafeGet(conn_state, Infos) == connected, created_at,
created_at => ConnectedAt, connected_at
connected_at => ConnectedAt ],
}, 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, " print(
"clean_start=~ts, keepalive=~w, " "Client(~ts, username=~ts, peername=~ts, "
"subscriptions=~w, delivered_msgs=~w, " "clean_start=~ts, keepalive=~w, "
"connected=~ts, created_at=~w, connected_at=~w)\n", "subscriptions=~w, delivered_msgs=~w, "
[format(K, maps:get(K, Info)) || K <- InfoKeys]). "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) -> emqx_ctl:print(S).
print(S, A) -> emqx_ctl:print(S, A). print(S, A) -> emqx_ctl:print(S, A).
format(_, undefined) -> format(_, undefined) ->
undefined; undefined;
format(peername, {IPAddr, Port}) -> format(peername, {IPAddr, Port}) ->
IPStr = emqx_mgmt_util:ntoa(IPAddr), IPStr = emqx_mgmt_util:ntoa(IPAddr),
io_lib:format("~ts:~p", [IPStr, Port]); io_lib:format("~ts:~p", [IPStr, Port]);
format(_, Val) -> format(_, Val) ->
Val. Val.
format_gw_summary(#{name := Name, status := unloaded}) -> format_gw_summary(#{name := Name, status := unloaded}) ->
io_lib:format("Gateway(name=~ts, status=unloaded)\n", [Name]); 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, format_gateway(#{
stopped_at := StoppedAt}) -> name := Name,
io_lib:format("Gateway(name=~ts, status=stopped, stopped_at=~ts)\n", status := unloaded
[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}) ->
io_lib:format( io_lib:format(
"name: ~ts\n" "name: ~ts\n"
"status: unloaded\n", [Name]); "status: unloaded\n",
[Name]
format_gateway(Gw = );
#{name := Name, format_gateway(
status := Status, Gw =
created_at := CreatedAt, #{
config := Config name := Name,
}) -> status := Status,
created_at := CreatedAt,
config := Config
}
) ->
{StopOrStart, Timestamp} = {StopOrStart, Timestamp} =
case Status of case Status of
stopped -> {stopped_at, maps:get(stopped_at, Gw)}; stopped -> {stopped_at, maps:get(stopped_at, Gw)};
@ -289,10 +310,15 @@ format_gateway(Gw =
"created_at: ~ts\n" "created_at: ~ts\n"
"~ts: ~ts\n" "~ts: ~ts\n"
"config: ~p\n", "config: ~p\n",
[Name, Status, [
emqx_gateway_utils:unix_ts_to_rfc3339(CreatedAt), Name,
StopOrStart, emqx_gateway_utils:unix_ts_to_rfc3339(Timestamp), Status,
Config]). emqx_gateway_utils:unix_ts_to_rfc3339(CreatedAt),
StopOrStart,
emqx_gateway_utils:unix_ts_to_rfc3339(Timestamp),
Config
]
).
format_error(Reason) -> format_error(Reason) ->
case emqx_gateway_http:reason2msg(Reason) of case emqx_gateway_http:reason2msg(Reason) of

View File

@ -30,70 +30,77 @@
%% APIs %% APIs
-export([start_link/1]). -export([start_link/1]).
-export([ open_session/5 -export([
, open_session/6 open_session/5,
, kick_session/2 open_session/6,
, kick_session/3 kick_session/2,
, takeover_session/2 kick_session/3,
, register_channel/4 takeover_session/2,
, unregister_channel/2 register_channel/4,
, insert_channel_info/4 unregister_channel/2,
, lookup_by_clientid/2 insert_channel_info/4,
, set_chan_info/3 lookup_by_clientid/2,
, set_chan_info/4 set_chan_info/3,
, get_chan_info/2 set_chan_info/4,
, get_chan_info/3 get_chan_info/2,
, set_chan_stats/3 get_chan_info/3,
, set_chan_stats/4 set_chan_stats/3,
, get_chan_stats/2 set_chan_stats/4,
, get_chan_stats/3 get_chan_stats/2,
, connection_closed/2 get_chan_stats/3,
]). connection_closed/2
]).
-export([ call/3 -export([
, call/4 call/3,
, cast/3 call/4,
]). cast/3
]).
-export([ with_channel/3 -export([
, lookup_channels/2 with_channel/3,
]). lookup_channels/2
]).
%% Internal funcs for getting tabname by GatewayId %% Internal funcs for getting tabname by GatewayId
-export([cmtabs/1, tabname/2]). -export([cmtabs/1, tabname/2]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
%% RPC targets %% RPC targets
-export([ do_lookup_by_clientid/2 -export([
, do_get_chan_info/3 do_lookup_by_clientid/2,
, do_set_chan_info/4 do_get_chan_info/3,
, do_get_chan_stats/3 do_set_chan_info/4,
, do_set_chan_stats/4 do_get_chan_stats/3,
, do_kick_session/4 do_set_chan_stats/4,
, do_takeover_session/3 do_kick_session/4,
, do_get_chann_conn_mod/3 do_takeover_session/3,
, do_call/4 do_get_chann_conn_mod/3,
, do_call/5 do_call/4,
, do_cast/4 do_call/5,
]). do_cast/4
]).
-export_type([ gateway_name/0 -export_type([gateway_name/0]).
]).
-record(state, { -record(state, {
gwname :: gateway_name(), %% Gateway Name %% Gateway Name
locker :: pid(), %% ClientId Locker for CM gwname :: gateway_name(),
registry :: pid(), %% ClientId Registry server %% ClientId Locker for CM
chan_pmon :: emqx_pmon:pmon() locker :: pid(),
}). %% ClientId Registry server
registry :: pid(),
chan_pmon :: emqx_pmon:pmon()
}).
-type option() :: {gwname, gateway_name()}. -type option() :: {gwname, gateway_name()}.
-type options() :: list(option()). -type options() :: list(option()).
@ -117,14 +124,16 @@ start_link(Options) ->
procname(GwName) -> procname(GwName) ->
list_to_atom(lists:concat([emqx_gateway_, GwName, '_cm'])). list_to_atom(lists:concat([emqx_gateway_, GwName, '_cm'])).
-spec cmtabs(GwName :: gateway_name()) -spec cmtabs(GwName :: gateway_name()) ->
-> {ChanTab :: atom(), {ChanTab :: atom(), ConnTab :: atom(), ChannInfoTab :: atom()}.
ConnTab :: atom(),
ChannInfoTab :: atom()}.
cmtabs(GwName) -> cmtabs(GwName) ->
{ tabname(chan, GwName) %% Record: {ClientId, Pid} %% Record: {ClientId, Pid}
, tabname(conn, GwName) %% Record: {{ClientId, Pid}, ConnMod} {
, tabname(info, GwName) %% Record: {{ClientId, Pid}, Info, Stats} tabname(chan, GwName),
%% Record: {{ClientId, Pid}, ConnMod}
tabname(conn, GwName),
%% Record: {{ClientId, Pid}, Info, Stats}
tabname(info, GwName)
}. }.
tabname(chan, GwName) -> tabname(chan, GwName) ->
@ -137,10 +146,12 @@ tabname(info, GwName) ->
lockername(GwName) -> lockername(GwName) ->
list_to_atom(lists:concat([emqx_gateway_, GwName, '_locker'])). list_to_atom(lists:concat([emqx_gateway_, GwName, '_locker'])).
-spec register_channel(gateway_name(), -spec register_channel(
emqx_types:clientid(), gateway_name(),
pid(), emqx_types:clientid(),
emqx_types:conninfo()) -> ok. pid(),
emqx_types:conninfo()
) -> ok.
register_channel(GwName, ClientId, ChanPid, #{conn_mod := ConnMod}) when is_pid(ChanPid) -> register_channel(GwName, ClientId, ChanPid, #{conn_mod := ConnMod}) when is_pid(ChanPid) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
true = ets:insert(tabname(chan, GwName), Chan), true = ets:insert(tabname(chan, GwName), Chan),
@ -155,31 +166,36 @@ unregister_channel(GwName, ClientId) when is_binary(ClientId) ->
ok. ok.
%% @doc Insert/Update the channel info and stats %% @doc Insert/Update the channel info and stats
-spec insert_channel_info(gateway_name(), -spec insert_channel_info(
emqx_types:clientid(), gateway_name(),
emqx_types:infos(), emqx_types:clientid(),
emqx_types:stats()) -> ok. emqx_types:infos(),
emqx_types:stats()
) -> ok.
insert_channel_info(GwName, ClientId, Info, Stats) -> insert_channel_info(GwName, ClientId, Info, Stats) ->
Chan = {ClientId, self()}, Chan = {ClientId, self()},
true = ets:insert(tabname(info, GwName), {Chan, Info, Stats}), true = ets:insert(tabname(info, GwName), {Chan, Info, Stats}),
ok. ok.
%% @doc Get info of a channel. %% @doc Get info of a channel.
-spec get_chan_info(gateway_name(), emqx_types:clientid()) -spec get_chan_info(gateway_name(), emqx_types:clientid()) ->
-> emqx_types:infos() | undefined. emqx_types:infos() | undefined.
get_chan_info(GwName, ClientId) -> get_chan_info(GwName, ClientId) ->
with_channel(GwName, ClientId, with_channel(
GwName,
ClientId,
fun(ChanPid) -> fun(ChanPid) ->
get_chan_info(GwName, ClientId, ChanPid) get_chan_info(GwName, ClientId, ChanPid)
end). end
).
-spec do_lookup_by_clientid(gateway_name(), emqx_types:clientid()) -> [pid()]. -spec do_lookup_by_clientid(gateway_name(), emqx_types:clientid()) -> [pid()].
do_lookup_by_clientid(GwName, ClientId) -> do_lookup_by_clientid(GwName, ClientId) ->
ChanTab = emqx_gateway_cm:tabname(chan, GwName), ChanTab = emqx_gateway_cm:tabname(chan, GwName),
[Pid || {_, Pid} <- ets:lookup(ChanTab, ClientId)]. [Pid || {_, Pid} <- ets:lookup(ChanTab, ClientId)].
-spec do_get_chan_info(gateway_name(), emqx_types:clientid(), pid()) -spec do_get_chan_info(gateway_name(), emqx_types:clientid(), pid()) ->
-> emqx_types:infos() | undefined. emqx_types:infos() | undefined.
do_get_chan_info(GwName, ClientId, ChanPid) -> do_get_chan_info(GwName, ClientId, ChanPid) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try try
@ -189,18 +205,21 @@ do_get_chan_info(GwName, ClientId, ChanPid) ->
error:badarg -> undefined error:badarg -> undefined
end. end.
-spec get_chan_info(gateway_name(), emqx_types:clientid(), pid()) -spec get_chan_info(gateway_name(), emqx_types:clientid(), pid()) ->
-> emqx_types:infos() | undefined. emqx_types:infos() | undefined.
get_chan_info(GwName, ClientId, ChanPid) -> get_chan_info(GwName, ClientId, ChanPid) ->
wrap_rpc( 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()]. -spec lookup_by_clientid(gateway_name(), emqx_types:clientid()) -> [pid()].
lookup_by_clientid(GwName, ClientId) -> lookup_by_clientid(GwName, ClientId) ->
Nodes = mria_mnesia:running_nodes(), Nodes = mria_mnesia:running_nodes(),
case emqx_gateway_cm_proto_v1:lookup_by_clientid( case
Nodes, GwName, ClientId) of emqx_gateway_cm_proto_v1:lookup_by_clientid(
Nodes, GwName, ClientId
)
of
{Pids, []} -> {Pids, []} ->
lists:append(Pids); lists:append(Pids);
{_, _BadNodes} -> {_, _BadNodes} ->
@ -208,74 +227,92 @@ lookup_by_clientid(GwName, ClientId) ->
end. end.
%% @doc Update infos of the channel. %% @doc Update infos of the channel.
-spec set_chan_info(gateway_name(), -spec set_chan_info(
emqx_types:clientid(), gateway_name(),
emqx_types:infos()) -> boolean(). emqx_types:clientid(),
emqx_types:infos()
) -> boolean().
set_chan_info(GwName, ClientId, Infos) -> set_chan_info(GwName, ClientId, Infos) ->
set_chan_info(GwName, ClientId, self(), Infos). set_chan_info(GwName, ClientId, self(), Infos).
-spec do_set_chan_info(gateway_name(), -spec do_set_chan_info(
emqx_types:clientid(), gateway_name(),
pid(), emqx_types:clientid(),
emqx_types:infos()) -> boolean(). pid(),
emqx_types:infos()
) -> boolean().
do_set_chan_info(GwName, ClientId, ChanPid, Infos) -> do_set_chan_info(GwName, ClientId, ChanPid, Infos) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try ets:update_element(tabname(info, GwName), Chan, {2, Infos}) try
ets:update_element(tabname(info, GwName), Chan, {2, Infos})
catch catch
error:badarg -> false error:badarg -> false
end. end.
-spec set_chan_info(gateway_name(), -spec set_chan_info(
emqx_types:clientid(), gateway_name(),
pid(), emqx_types:clientid(),
emqx_types:infos()) -> boolean(). pid(),
emqx_types:infos()
) -> boolean().
set_chan_info(GwName, ClientId, ChanPid, Infos) -> set_chan_info(GwName, ClientId, ChanPid, Infos) ->
wrap_rpc(emqx_gateway_cm_proto_v1: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. %% @doc Get channel's stats.
-spec get_chan_stats(gateway_name(), emqx_types:clientid()) -spec get_chan_stats(gateway_name(), emqx_types:clientid()) ->
-> emqx_types:stats() | undefined. emqx_types:stats() | undefined.
get_chan_stats(GwName, ClientId) -> get_chan_stats(GwName, ClientId) ->
with_channel(GwName, ClientId, with_channel(
GwName,
ClientId,
fun(ChanPid) -> fun(ChanPid) ->
get_chan_stats(GwName, ClientId, ChanPid) get_chan_stats(GwName, ClientId, ChanPid)
end). end
).
-spec do_get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) -spec do_get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) ->
-> emqx_types:stats() | undefined. emqx_types:stats() | undefined.
do_get_chan_stats(GwName, ClientId, ChanPid) -> do_get_chan_stats(GwName, ClientId, ChanPid) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try ets:lookup_element(tabname(info, GwName), Chan, 3) try
ets:lookup_element(tabname(info, GwName), Chan, 3)
catch catch
error:badarg -> undefined error:badarg -> undefined
end. end.
-spec get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) -spec get_chan_stats(gateway_name(), emqx_types:clientid(), pid()) ->
-> emqx_types:stats() | undefined. emqx_types:stats() | undefined.
get_chan_stats(GwName, ClientId, ChanPid) -> get_chan_stats(GwName, ClientId, ChanPid) ->
wrap_rpc(emqx_gateway_cm_proto_v1: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(), -spec set_chan_stats(
emqx_types:clientid(), gateway_name(),
emqx_types:stats()) -> boolean(). emqx_types:clientid(),
emqx_types:stats()
) -> boolean().
set_chan_stats(GwName, ClientId, Stats) -> set_chan_stats(GwName, ClientId, Stats) ->
set_chan_stats(GwName, ClientId, self(), Stats). set_chan_stats(GwName, ClientId, self(), Stats).
-spec do_set_chan_stats(gateway_name(), -spec do_set_chan_stats(
emqx_types:clientid(), gateway_name(),
pid(), emqx_types:clientid(),
emqx_types:stats()) -> boolean(). pid(),
emqx_types:stats()
) -> boolean().
do_set_chan_stats(GwName, ClientId, ChanPid, Stats) -> do_set_chan_stats(GwName, ClientId, ChanPid, Stats) ->
Chan = {ClientId, ChanPid}, Chan = {ClientId, ChanPid},
try ets:update_element(tabname(info, GwName), Chan, {3, Stats}) try
ets:update_element(tabname(info, GwName), Chan, {3, Stats})
catch catch
error:badarg -> false error:badarg -> false
end. end.
-spec set_chan_stats(gateway_name(), -spec set_chan_stats(
emqx_types:clientid(), gateway_name(),
pid(), emqx_types:clientid(),
emqx_types:stats()) -> boolean(). pid(),
emqx_types:stats()
) -> boolean().
set_chan_stats(GwName, ClientId, ChanPid, Stats) -> set_chan_stats(GwName, ClientId, ChanPid, Stats) ->
wrap_rpc(emqx_gateway_cm_proto_v1: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()}, Chan = {ClientId, self()},
ets:delete_object(tabname(conn, GwName), Chan). ets:delete_object(tabname(conn, GwName), Chan).
-spec open_session(GwName :: gateway_name(), -spec open_session(
CleanStart :: boolean(), GwName :: gateway_name(),
ClientInfo :: emqx_types:clientinfo(), CleanStart :: boolean(),
ConnInfo :: emqx_types:conninfo(), ClientInfo :: emqx_types:clientinfo(),
CreateSessionFun :: fun((emqx_types:clientinfo(), ConnInfo :: emqx_types:conninfo(),
emqx_types:conninfo()) -> Session CreateSessionFun :: fun(
)) (
-> {ok, #{session := Session, emqx_types:clientinfo(),
present := boolean(), emqx_types:conninfo()
pendings => list() ) -> Session
}} )
| {error, any()}. ) ->
{ok, #{
session := Session,
present := boolean(),
pendings => list()
}}
| {error, any()}.
open_session(GwName, CleanStart, ClientInfo, ConnInfo, CreateSessionFun) -> open_session(GwName, CleanStart, ClientInfo, ConnInfo, CreateSessionFun) ->
open_session(GwName, CleanStart, ClientInfo, ConnInfo, CreateSessionFun, emqx_session). open_session(GwName, CleanStart, ClientInfo, ConnInfo, CreateSessionFun, emqx_session).
@ -305,21 +348,26 @@ open_session(GwName, true = _CleanStart, ClientInfo, ConnInfo, CreateSessionFun,
Self = self(), Self = self(),
ClientId = maps:get(clientid, ClientInfo), ClientId = maps:get(clientid, ClientInfo),
Fun = fun(_) -> Fun = fun(_) ->
_ = discard_session(GwName, ClientId), _ = discard_session(GwName, ClientId),
Session = create_session(GwName, Session = create_session(
ClientInfo, GwName,
ConnInfo, ClientInfo,
CreateSessionFun, ConnInfo,
SessionMod CreateSessionFun,
), SessionMod
register_channel(GwName, ClientId, Self, ConnInfo), ),
{ok, #{session => Session, present => false}} register_channel(GwName, ClientId, Self, ConnInfo),
end, {ok, #{session => Session, present => false}}
end,
locker_trans(GwName, ClientId, Fun); locker_trans(GwName, ClientId, Fun);
open_session(
open_session(GwName, false = _CleanStart, GwName,
ClientInfo = #{clientid := ClientId}, false = _CleanStart,
ConnInfo, CreateSessionFun, SessionMod) -> ClientInfo = #{clientid := ClientId},
ConnInfo,
CreateSessionFun,
SessionMod
) ->
Self = self(), Self = self(),
ResumeStart = ResumeStart =
@ -327,79 +375,98 @@ open_session(GwName, false = _CleanStart,
CreateSess = CreateSess =
fun() -> fun() ->
Session = create_session( Session = create_session(
GwName, ClientInfo, ConnInfo, GwName,
CreateSessionFun, SessionMod), ClientInfo,
register_channel( ConnInfo,
GwName, ClientId, Self, ConnInfo), CreateSessionFun,
{ok, #{session => Session, present => false}} SessionMod
),
register_channel(
GwName, ClientId, Self, ConnInfo
),
{ok, #{session => Session, present => false}}
end, end,
case takeover_session(GwName, ClientId) of case takeover_session(GwName, ClientId) of
{ok, ConnMod, ChanPid, Session} -> {ok, ConnMod, ChanPid, Session} ->
ok = emqx_session:resume(ClientInfo, Session), ok = emqx_session:resume(ClientInfo, Session),
case request_stepdown({takeover, 'end'}, ConnMod, ChanPid) of case request_stepdown({takeover, 'end'}, ConnMod, ChanPid) of
{ok, Pendings} -> {ok, Pendings} ->
register_channel( register_channel(
GwName, ClientId, Self, ConnInfo), GwName, ClientId, Self, ConnInfo
{ok, #{session => Session, ),
present => true, {ok, #{
pendings => Pendings}}; session => Session,
{error, _} -> present => true,
CreateSess() pendings => Pendings
end; }};
{error, _Reason} -> CreateSess() {error, _} ->
end CreateSess()
end, end;
{error, _Reason} ->
CreateSess()
end
end,
locker_trans(GwName, ClientId, ResumeStart). locker_trans(GwName, ClientId, ResumeStart).
%% @private %% @private
create_session(GwName, ClientInfo, ConnInfo, CreateSessionFun, SessionMod) -> create_session(GwName, ClientInfo, ConnInfo, CreateSessionFun, SessionMod) ->
try try
Session = emqx_gateway_utils:apply( Session = emqx_gateway_utils:apply(
CreateSessionFun, CreateSessionFun,
[ClientInfo, ConnInfo] [ClientInfo, ConnInfo]
), ),
ok = emqx_gateway_metrics:inc(GwName, 'session.created'), ok = emqx_gateway_metrics:inc(GwName, 'session.created'),
SessionInfo = case is_tuple(Session) SessionInfo =
andalso element(1, Session) == session of case
true -> SessionMod:info(Session); is_tuple(Session) andalso
_ -> element(1, Session) == session
case is_map(Session) of of
false -> true ->
throw(session_structure_should_be_map); SessionMod:info(Session);
_ -> _ ->
Session case is_map(Session) of
end false ->
end, throw(session_structure_should_be_map);
_ ->
Session
end
end,
ok = emqx_hooks:run('session.created', [ClientInfo, SessionInfo]), ok = emqx_hooks:run('session.created', [ClientInfo, SessionInfo]),
Session Session
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
?SLOG(error, #{ msg => "failed_create_session" ?SLOG(error, #{
, clientid => maps:get(clientid, ClientInfo, undefined) msg => "failed_create_session",
, username => maps:get(username, ClientInfo, undefined) clientid => maps:get(clientid, ClientInfo, undefined),
, reason => {Class, Reason} username => maps:get(username, ClientInfo, undefined),
, stacktrace => Stk reason => {Class, Reason},
}), stacktrace => Stk
throw(Reason) }),
throw(Reason)
end. end.
%% @doc Try to takeover a session. %% @doc Try to takeover a session.
-spec(takeover_session(gateway_name(), emqx_types:clientid()) -spec takeover_session(gateway_name(), emqx_types:clientid()) ->
-> {error, term()} {error, term()}
| {ok, atom(), pid(), emqx_session:session()}). | {ok, atom(), pid(), emqx_session:session()}.
takeover_session(GwName, ClientId) -> takeover_session(GwName, ClientId) ->
case lookup_channels(GwName, ClientId) of case lookup_channels(GwName, ClientId) of
[] -> {error, not_found}; [] ->
{error, not_found};
[ChanPid] -> [ChanPid] ->
do_takeover_session(GwName, ClientId, ChanPid); do_takeover_session(GwName, ClientId, ChanPid);
ChanPids -> ChanPids ->
[ChanPid | StalePids] = lists:reverse(ChanPids), [ChanPid | StalePids] = lists:reverse(ChanPids),
?SLOG(warning, #{ msg => "more_than_one_channel_found" ?SLOG(warning, #{
, chan_pids => ChanPids msg => "more_than_one_channel_found",
}), chan_pids => ChanPids
lists:foreach(fun(StalePid) -> }),
catch discard_session(GwName, ClientId, StalePid) lists:foreach(
end, StalePids), fun(StalePid) ->
catch discard_session(GwName, ClientId, StalePid)
end,
StalePids
),
do_takeover_session(GwName, ClientId, ChanPid) do_takeover_session(GwName, ClientId, ChanPid)
end. end.
@ -432,17 +499,26 @@ discard_session(GwName, ClientId, ChanPid) ->
-spec kick_session(gateway_name(), emqx_types:clientid()) -> ok | {error, not_found}. -spec kick_session(gateway_name(), emqx_types:clientid()) -> ok | {error, not_found}.
kick_session(GwName, ClientId) -> kick_session(GwName, ClientId) ->
case lookup_channels(GwName, ClientId) of case lookup_channels(GwName, ClientId) of
[] -> {error, not_found}; [] ->
{error, not_found};
ChanPids -> ChanPids ->
ChanPids > 1 andalso begin ChanPids > 1 andalso
?SLOG(warning, #{ msg => "more_than_one_channel_found" begin
, chan_pids => ChanPids ?SLOG(
}, warning,
#{clientid => ClientId}) #{
end, msg => "more_than_one_channel_found",
lists:foreach(fun(Pid) -> chan_pids => ChanPids
_ = kick_session(GwName, ClientId, Pid) },
end, ChanPids) #{clientid => ClientId}
)
end,
lists:foreach(
fun(Pid) ->
_ = kick_session(GwName, ClientId, Pid)
end,
ChanPids
)
end. end.
kick_session(GwName, ClientId, ChanPid) -> kick_session(GwName, ClientId, ChanPid) ->
@ -453,27 +529,34 @@ kick_session(GwName, Action, ClientId, ChanPid) ->
try try
wrap_rpc(emqx_gateway_cm_proto_v1:kick_session(GwName, Action, ClientId, ChanPid)) wrap_rpc(emqx_gateway_cm_proto_v1:kick_session(GwName, Action, ClientId, ChanPid))
catch catch
Error : Reason -> Error:Reason ->
%% This should mostly be RPC failures. %% This should mostly be RPC failures.
%% However, if the node is still running the old version %% However, if the node is still running the old version
%% code (prior to emqx app 4.3.10) some of the RPC handler %% code (prior to emqx app 4.3.10) some of the RPC handler
%% exceptions may get propagated to a new version node %% exceptions may get propagated to a new version node
?SLOG(error, #{ msg => "failed_to_kick_session_on_remote_node" ?SLOG(
, node => node(ChanPid) error,
, action => Action #{
, error => Error msg => "failed_to_kick_session_on_remote_node",
, reason => Reason node => node(ChanPid),
}, action => Action,
#{clientid => ClientId}) error => Error,
reason => Reason
},
#{clientid => ClientId}
)
end. end.
-spec do_kick_session(gateway_name(), -spec do_kick_session(
kick | discard, gateway_name(),
emqx_types:clientid(), kick | discard,
pid()) -> ok. emqx_types:clientid(),
pid()
) -> ok.
do_kick_session(GwName, Action, ClientId, ChanPid) -> do_kick_session(GwName, Action, ClientId, ChanPid) ->
case get_chann_conn_mod(GwName, ClientId, ChanPid) of case get_chann_conn_mod(GwName, ClientId, ChanPid) of
undefined -> ok; undefined ->
ok;
ConnMod when is_atom(ConnMod) -> ConnMod when is_atom(ConnMod) ->
ok = request_stepdown(Action, ConnMod, ChanPid) ok = request_stepdown(Action, ConnMod, ChanPid)
end. end.
@ -482,56 +565,67 @@ do_kick_session(GwName, Action, ClientId, ChanPid) ->
%% If failed to response (e.g. timeout) force a kill. %% If failed to response (e.g. timeout) force a kill.
%% Keeping the stale pid around, or returning error or raise an exception %% Keeping the stale pid around, or returning error or raise an exception
%% benefits nobody. %% benefits nobody.
-spec request_stepdown(Action, module(), pid()) -spec request_stepdown(Action, module(), pid()) ->
-> ok ok
| {ok, emqx_session:session() | list(emqx_type:deliver())} | {ok, emqx_session:session() | list(emqx_type:deliver())}
| {error, term()} | {error, term()}
when Action :: kick | discard | {takeover, 'begin'} | {takeover, 'end'}. when
Action :: kick | discard | {takeover, 'begin'} | {takeover, 'end'}.
request_stepdown(Action, ConnMod, Pid) -> request_stepdown(Action, ConnMod, Pid) ->
Timeout = Timeout =
case Action == kick orelse Action == discard of case Action == kick orelse Action == discard of
true -> ?T_KICK; true -> ?T_KICK;
_ -> ?T_TAKEOVER _ -> ?T_TAKEOVER
end, end,
Return = Return =
%% this is essentailly a gen_server:call implemented in emqx_connection %% this is essentailly a gen_server:call implemented in emqx_connection
%% and emqx_ws_connection. %% and emqx_ws_connection.
%% the handle_call is implemented in emqx_channel %% the handle_call is implemented in emqx_channel
try apply(ConnMod, call, [Pid, Action, Timeout]) of try apply(ConnMod, call, [Pid, Action, Timeout]) of
ok -> ok; ok -> ok;
Reply -> {ok, Reply} Reply -> {ok, Reply}
catch catch
_ : noproc -> % emqx_ws_connection: call % emqx_ws_connection: call
ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), _:noproc ->
{error, noproc}; ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}),
_ : {noproc, _} -> % emqx_connection: gen_server:call {error, noproc};
ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}), % emqx_connection: gen_server:call
{error, noproc}; _:{noproc, _} ->
_ : Reason = {shutdown, _} -> ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action}),
ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), {error, noproc};
{error, Reason}; _:Reason = {shutdown, _} ->
_ : Reason = {{shutdown, _}, _} -> ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}),
ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}), {error, Reason};
{error, Reason}; _:Reason = {{shutdown, _}, _} ->
_ : {timeout, {gen_server, call, _}} -> ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action}),
?tp(warning, "session_stepdown_request_timeout", {error, Reason};
#{pid => Pid, _:{timeout, {gen_server, call, _}} ->
action => Action, ?tp(
stale_channel => stale_channel_info(Pid) warning,
}), "session_stepdown_request_timeout",
ok = force_kill(Pid), #{
{error, timeout}; pid => Pid,
_ : Error : St -> action => Action,
?tp(error, "session_stepdown_request_exception", stale_channel => stale_channel_info(Pid)
#{pid => Pid, }
action => Action, ),
reason => Error, ok = force_kill(Pid),
stacktrace => St, {error, timeout};
stale_channel => stale_channel_info(Pid) _:Error:St ->
}), ?tp(
ok = force_kill(Pid), error,
{error, Error} "session_stepdown_request_exception",
end, #{
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 case Action == kick orelse Action == discard of
true -> ok; true -> ok;
_ -> Return _ -> Return
@ -546,20 +640,22 @@ stale_channel_info(Pid) ->
with_channel(GwName, ClientId, Fun) -> with_channel(GwName, ClientId, Fun) ->
case lookup_channels(GwName, ClientId) of case lookup_channels(GwName, ClientId) of
[] -> undefined; [] -> undefined;
[Pid] -> Fun(Pid); [Pid] -> Fun(Pid);
Pids -> Fun(lists:last(Pids)) Pids -> Fun(lists:last(Pids))
end. end.
%% @doc Lookup channels. %% @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) -> lookup_channels(GwName, ClientId) ->
emqx_gateway_cm_registry: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(). -spec do_get_chann_conn_mod(gateway_name(), emqx_types:clientid(), pid()) -> atom().
do_get_chann_conn_mod(GwName, ClientId, ChanPid) -> do_get_chann_conn_mod(GwName, ClientId, ChanPid) ->
Chan = {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 catch
error:badarg -> undefined error:badarg -> undefined
end. end.
@ -568,28 +664,33 @@ do_get_chann_conn_mod(GwName, ClientId, ChanPid) ->
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)). wrap_rpc(emqx_gateway_cm_proto_v1:get_chann_conn_mod(GwName, ClientId, ChanPid)).
-spec call(gateway_name(), emqx_types:clientid(), term()) -spec call(gateway_name(), emqx_types:clientid(), term()) ->
-> undefined | term(). undefined | term().
call(GwName, ClientId, Req) -> call(GwName, ClientId, Req) ->
with_channel( with_channel(
GwName, ClientId, GwName,
fun(ChanPid) -> ClientId,
wrap_rpc( fun(ChanPid) ->
emqx_gateway_cm_proto_v1:call(GwName, ClientId, ChanPid, Req) wrap_rpc(
) emqx_gateway_cm_proto_v1:call(GwName, ClientId, ChanPid, Req)
end). )
end
).
-spec call(gateway_name(), emqx_types:clientid(), term(), timeout()) -spec call(gateway_name(), emqx_types:clientid(), term(), timeout()) ->
-> undefined | term(). undefined | term().
call(GwName, ClientId, Req, Timeout) -> call(GwName, ClientId, Req, Timeout) ->
with_channel( with_channel(
GwName, ClientId, GwName,
fun(ChanPid) -> ClientId,
wrap_rpc( fun(ChanPid) ->
emqx_gateway_cm_proto_v1:call( wrap_rpc(
GwName, ClientId, ChanPid, Req, Timeout) emqx_gateway_cm_proto_v1:call(
) GwName, ClientId, ChanPid, Req, Timeout
end). )
)
end
).
do_call(GwName, ClientId, ChanPid, Req) -> do_call(GwName, ClientId, ChanPid, Req) ->
case do_get_chann_conn_mod(GwName, ClientId, ChanPid) of 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. -spec cast(gateway_name(), emqx_types:clientid(), term()) -> ok.
cast(GwName, ClientId, Req) -> cast(GwName, ClientId, Req) ->
with_channel( with_channel(
GwName, ClientId, GwName,
fun(ChanPid) -> ClientId,
wrap_rpc( fun(ChanPid) ->
emqx_gateway_cm_proto_v1:cast(GwName, ClientId, ChanPid, Req)) wrap_rpc(
end), emqx_gateway_cm_proto_v1:cast(GwName, ClientId, ChanPid, Req)
)
end
),
ok. ok.
do_cast(GwName, ClientId, ChanPid, Req) -> do_cast(GwName, ClientId, ChanPid, Req) ->
@ -627,7 +731,11 @@ locker_trans(GwName, ClientId, Fun) ->
Locker = lockername(GwName), Locker = lockername(GwName),
case locker_lock(Locker, ClientId) of case locker_lock(Locker, ClientId) of
{true, Nodes} -> {true, Nodes} ->
try Fun(Nodes) after locker_unlock(Locker, ClientId) end; try
Fun(Nodes)
after
locker_unlock(Locker, ClientId)
end;
{false, _Nodes} -> {false, _Nodes} ->
{error, client_id_unavailable} {error, client_id_unavailable}
end. end.
@ -673,10 +781,12 @@ init(Options) ->
%% TODO: v0.2 %% TODO: v0.2
%ok = emqx_stats:update_interval(chan_stats, fun ?MODULE:stats_fun/0), %ok = emqx_stats:update_interval(chan_stats, fun ?MODULE:stats_fun/0),
{ok, #state{gwname = GwName, {ok, #state{
locker = Locker, gwname = GwName,
registry = Registry, locker = Locker,
chan_pmon = emqx_pmon:new()}}. registry = Registry,
chan_pmon = emqx_pmon:new()
}}.
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok,
@ -685,19 +795,19 @@ handle_call(_Request, _From, State) ->
handle_cast({registered, {ClientId, ChanPid}}, State = #state{chan_pmon = PMon}) -> handle_cast({registered, {ClientId, ChanPid}}, State = #state{chan_pmon = PMon}) ->
PMon1 = emqx_pmon:monitor(ChanPid, ClientId, PMon), PMon1 = emqx_pmon:monitor(ChanPid, ClientId, PMon),
{noreply, State#state{chan_pmon = PMon1}}; {noreply, State#state{chan_pmon = PMon1}};
handle_cast(_Msg, State) -> handle_cast(_Msg, State) ->
{noreply, State}. {noreply, State}.
handle_info({'DOWN', _MRef, process, Pid, _Reason}, handle_info(
State = #state{gwname = GwName, chan_pmon = PMon}) -> {'DOWN', _MRef, process, Pid, _Reason},
State = #state{gwname = GwName, chan_pmon = PMon}
) ->
ChanPids = [Pid | emqx_misc:drain_down(?DEFAULT_BATCH_SIZE)], ChanPids = [Pid | emqx_misc:drain_down(?DEFAULT_BATCH_SIZE)],
{Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon), {Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon),
CmTabs = cmtabs(GwName), CmTabs = cmtabs(GwName),
ok = emqx_pool:async_submit(fun do_unregister_channel_task/3, [Items, GwName, CmTabs]), ok = emqx_pool:async_submit(fun do_unregister_channel_task/3, [Items, GwName, CmTabs]),
{noreply, State#state{chan_pmon = PMon1}}; {noreply, State#state{chan_pmon = PMon1}};
handle_info(_Info, State) -> handle_info(_Info, State) ->
{noreply, State}. {noreply, State}.
@ -711,9 +821,11 @@ code_change(_OldVsn, State, _Extra) ->
do_unregister_channel_task(Items, GwName, CmTabs) -> do_unregister_channel_task(Items, GwName, CmTabs) ->
lists:foreach( lists:foreach(
fun({ChanPid, ClientId}) -> fun({ChanPid, ClientId}) ->
do_unregister_channel(GwName, {ClientId, ChanPid}, CmTabs) do_unregister_channel(GwName, {ClientId, ChanPid}, CmTabs)
end, Items). end,
Items
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs

View File

@ -23,22 +23,24 @@
-export([start_link/1]). -export([start_link/1]).
-export([ register_channel/2 -export([
, unregister_channel/2 register_channel/2,
]). unregister_channel/2
]).
-export([lookup_channels/2]). -export([lookup_channels/2]).
-export([tabname/1]). -export([tabname/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-define(CM_SHARD, emqx_gateway_cm_shard). -define(CM_SHARD, emqx_gateway_cm_shard).
-define(LOCK, {?MODULE, cleanup_down}). -define(LOCK, {?MODULE, cleanup_down}).
@ -46,7 +48,7 @@
-record(channel, {chid, pid}). -record(channel, {chid, pid}).
%% @doc Start the global channel registry for the given gateway name. %% @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) -> start_link(Name) ->
gen_server:start_link(?MODULE, [Name], []). gen_server:start_link(?MODULE, [Name], []).
@ -63,25 +65,27 @@ tabname(Name) ->
-spec register_channel(gateway_name(), binary() | {binary(), pid()}) -> ok. -spec register_channel(gateway_name(), binary() | {binary(), pid()}) -> ok.
register_channel(Name, ClientId) when is_binary(ClientId) -> register_channel(Name, ClientId) when is_binary(ClientId) ->
register_channel(Name, {ClientId, self()}); register_channel(Name, {ClientId, self()});
register_channel(Name, {ClientId, ChanPid}) when
register_channel(Name, {ClientId, ChanPid}) is_binary(ClientId), is_pid(ChanPid)
when is_binary(ClientId), is_pid(ChanPid) -> ->
mria:dirty_write(tabname(Name), record(ClientId, ChanPid)). mria:dirty_write(tabname(Name), record(ClientId, ChanPid)).
%% @doc Unregister a global channel. %% @doc Unregister a global channel.
-spec unregister_channel(gateway_name(), binary() | {binary(), pid()}) -> ok. -spec unregister_channel(gateway_name(), binary() | {binary(), pid()}) -> ok.
unregister_channel(Name, ClientId) when is_binary(ClientId) -> unregister_channel(Name, ClientId) when is_binary(ClientId) ->
unregister_channel(Name, {ClientId, self()}); unregister_channel(Name, {ClientId, self()});
unregister_channel(Name, {ClientId, ChanPid}) when
unregister_channel(Name, {ClientId, ChanPid}) is_binary(ClientId), is_pid(ChanPid)
when is_binary(ClientId), is_pid(ChanPid) -> ->
mria:dirty_delete_object(tabname(Name), record(ClientId, ChanPid)). mria:dirty_delete_object(tabname(Name), record(ClientId, ChanPid)).
%% @doc Lookup the global channels. %% @doc Lookup the global channels.
-spec lookup_channels(gateway_name(), binary()) -> list(pid()). -spec lookup_channels(gateway_name(), binary()) -> list(pid()).
lookup_channels(Name, ClientId) -> 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) -> record(ClientId, ChanPid) ->
#channel{chid = ClientId, pid = ChanPid}. #channel{chid = ClientId, pid = ChanPid}.
@ -93,13 +97,18 @@ record(ClientId, ChanPid) ->
init([Name]) -> init([Name]) ->
Tab = tabname(Name), Tab = tabname(Name),
ok = mria:create_table(Tab, [ ok = mria:create_table(Tab, [
{type, bag}, {type, bag},
{rlog_shard, ?CM_SHARD}, {rlog_shard, ?CM_SHARD},
{storage, ram_copies}, {storage, ram_copies},
{record_name, channel}, {record_name, channel},
{attributes, record_info(fields, channel)}, {attributes, record_info(fields, channel)},
{storage_properties, [{ets, [{read_concurrency, true}, {storage_properties, [
{write_concurrency, true}]}]}]), {ets, [
{read_concurrency, true},
{write_concurrency, true}
]}
]}
]),
ok = mria:wait_for_tables([Tab]), ok = mria:wait_for_tables([Tab]),
ok = ekka:monitor(membership), ok = ekka:monitor(membership),
{ok, #{name => Name}}. {ok, #{name => Name}}.
@ -115,14 +124,11 @@ handle_cast(Msg, State) ->
handle_info({membership, {mnesia, down, Node}}, State = #{name := Name}) -> handle_info({membership, {mnesia, down, Node}}, State = #{name := Name}) ->
cleanup_channels(Node, Name), cleanup_channels(Node, Name),
{noreply, State}; {noreply, State};
handle_info({membership, {node, down, Node}}, State = #{name := Name}) -> handle_info({membership, {node, down, Node}}, State = #{name := Name}) ->
cleanup_channels(Node, Name), cleanup_channels(Node, Name),
{noreply, State}; {noreply, State};
handle_info({membership, _Event}, State) -> handle_info({membership, _Event}, State) ->
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
logger:error("Unexpected info: ~p", [Info]), logger:error("Unexpected info: ~p", [Info]),
{noreply, State}. {noreply, State}.
@ -140,13 +146,17 @@ code_change(_OldVsn, State, _Extra) ->
cleanup_channels(Node, Name) -> cleanup_channels(Node, Name) ->
Tab = tabname(Name), Tab = tabname(Name),
global:trans( global:trans(
{?LOCK, self()}, {?LOCK, self()},
fun() -> fun() ->
mria:transaction(?CM_SHARD, fun do_cleanup_channels/2, [Node, Tab]) mria:transaction(?CM_SHARD, fun do_cleanup_channels/2, [Node, Tab])
end). end
).
do_cleanup_channels(Node, Tab) -> do_cleanup_channels(Node, Tab) ->
Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}], Pat = [{#channel{pid = '$1', _ = '_'}, [{'==', {node, '$1'}, Node}], ['$_']}],
lists:foreach(fun(Chan) -> lists:foreach(
mnesia:delete_object(Tab, Chan, write) fun(Chan) ->
end, mnesia:select(Tab, Pat, write)). mnesia:delete_object(Tab, Chan, write)
end,
mnesia:select(Tab, Pat, write)
).

View File

@ -20,41 +20,47 @@
-behaviour(emqx_config_handler). -behaviour(emqx_config_handler).
%% Load/Unload %% Load/Unload
-export([ load/0 -export([
, unload/0 load/0,
]). unload/0
]).
%% APIs %% APIs
-export([ gateway/1 -export([
, load_gateway/2 gateway/1,
, update_gateway/2 load_gateway/2,
, unload_gateway/1 update_gateway/2,
]). unload_gateway/1
]).
-export([ listeners/1 -export([
, listener/1 listeners/1,
, add_listener/3 listener/1,
, update_listener/3 add_listener/3,
, remove_listener/2 update_listener/3,
]). remove_listener/2
]).
-export([ add_authn/2 -export([
, add_authn/3 add_authn/2,
, update_authn/2 add_authn/3,
, update_authn/3 update_authn/2,
, remove_authn/1 update_authn/3,
, remove_authn/2 remove_authn/1,
]). remove_authn/2
]).
%% internal exports %% internal exports
-export([ unconvert_listeners/1 -export([
, convert_listeners/2 unconvert_listeners/1,
]). convert_listeners/2
]).
%% callbacks for emqx_config_handler %% callbacks for emqx_config_handler
-export([ pre_config_update/3 -export([
, post_config_update/5 pre_config_update/3,
]). post_config_update/5
]).
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx/include/emqx_authentication.hrl"). -include_lib("emqx/include/emqx_authentication.hrl").
@ -63,8 +69,7 @@
-type atom_or_bin() :: atom() | binary(). -type atom_or_bin() :: atom() | binary().
-type ok_or_err() :: ok | {error, term()}. -type ok_or_err() :: ok | {error, term()}.
-type map_or_err() :: {ok, map()} | {error, term()}. -type map_or_err() :: {ok, map()} | {error, term()}.
-type listener_ref() :: {ListenerType :: atom_or_bin(), -type listener_ref() :: {ListenerType :: atom_or_bin(), ListenerName :: atom_or_bin()}.
ListenerName :: atom_or_bin()}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Load/Unload %% Load/Unload
@ -83,21 +88,25 @@ unload() ->
-spec load_gateway(atom_or_bin(), map()) -> map_or_err(). -spec load_gateway(atom_or_bin(), map()) -> map_or_err().
load_gateway(GwName, Conf) -> load_gateway(GwName, Conf) ->
NConf = case maps:take(<<"listeners">>, Conf) of NConf =
error -> Conf; case maps:take(<<"listeners">>, Conf) of
{Ls, Conf1} -> error -> Conf;
Conf1#{<<"listeners">> => unconvert_listeners(Ls)} {Ls, Conf1} -> Conf1#{<<"listeners">> => unconvert_listeners(Ls)}
end, end,
ret_gw(GwName, update({?FUNCTION_NAME, bin(GwName), NConf})). ret_gw(GwName, update({?FUNCTION_NAME, bin(GwName), NConf})).
%% @doc convert listener array to map %% @doc convert listener array to map
unconvert_listeners(Ls) when is_list(Ls) -> unconvert_listeners(Ls) when is_list(Ls) ->
lists:foldl(fun(Lis, Acc) -> lists:foldl(
%% FIXME: params apperence guard? fun(Lis, Acc) ->
{[Type, Name], Lis1} = maps_key_take([<<"type">>, <<"name">>], Lis), %% FIXME: params apperence guard?
NLis1 = maps:without([<<"id">>], Lis1), {[Type, Name], Lis1} = maps_key_take([<<"type">>, <<"name">>], Lis),
emqx_map_lib:deep_merge(Acc, #{Type => #{Name => NLis1}}) NLis1 = maps:without([<<"id">>], Lis1),
end, #{}, Ls). emqx_map_lib:deep_merge(Acc, #{Type => #{Name => NLis1}})
end,
#{},
Ls
).
maps_key_take(Ks, M) -> maps_key_take(Ks, M) ->
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) -> maps_key_take([K | Ks], M, Acc) ->
case maps:take(K, M) of case maps:take(K, M) of
error -> throw(bad_key); error -> throw(bad_key);
{V, M1} -> {V, M1} -> maps_key_take(Ks, M1, [V | Acc])
maps_key_take(Ks, M1, [V | Acc])
end. end.
-spec update_gateway(atom_or_bin(), map()) -> map_or_err(). -spec update_gateway(atom_or_bin(), map()) -> map_or_err().
@ -131,32 +139,38 @@ gateway(GwName0) ->
GwName = bin(GwName0), GwName = bin(GwName0),
Path = [<<"gateway">>, GwName], Path = [<<"gateway">>, GwName],
RawConf = emqx_config:fill_defaults( RawConf = emqx_config:fill_defaults(
emqx_config:get_root_raw(Path) emqx_config:get_root_raw(Path)
), ),
Confs = emqx_map_lib:jsonable_map( 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, #{}), LsConf = maps:get(<<"listeners">>, Confs, #{}),
Confs#{<<"listeners">> => convert_listeners(GwName, LsConf)}. Confs#{<<"listeners">> => convert_listeners(GwName, LsConf)}.
%% @doc convert listeners map to array %% @doc convert listeners map to array
convert_listeners(GwName, Ls) when is_map(Ls) -> convert_listeners(GwName, Ls) when is_map(Ls) ->
lists:append([do_convert_listener(GwName, Type, maps:to_list(Conf)) lists:append([
|| {Type, Conf} <- maps:to_list(Ls)]). do_convert_listener(GwName, Type, maps:to_list(Conf))
|| {Type, Conf} <- maps:to_list(Ls)
]).
do_convert_listener(GwName, LType, Conf) -> 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) -> do_convert_listener2(GwName, LType, LName, LConf) ->
ListenerId = emqx_gateway_utils:listener_id(GwName, LType, LName), ListenerId = emqx_gateway_utils:listener_id(GwName, LType, LName),
Running = emqx_gateway_utils:is_running(ListenerId, LConf), Running = emqx_gateway_utils:is_running(ListenerId, LConf),
bind2str( bind2str(
LConf#{ LConf#{
id => ListenerId, id => ListenerId,
type => LType, type => LType,
name => LName, name => LName,
running => Running running => Running
}). }
).
bind2str(LConf = #{bind := Bind}) when is_integer(Bind) -> bind2str(LConf = #{bind := Bind}) when is_integer(Bind) ->
maps:put(bind, integer_to_binary(Bind), LConf); 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()]. -spec listeners(atom_or_bin()) -> [map()].
listeners(GwName0) -> listeners(GwName0) ->
GwName = bin(GwName0), GwName = bin(GwName0),
RawConf = emqx_config:fill_defaults( RawConf = emqx_config:fill_defaults(
emqx_config:get_root_raw([<<"gateway">>])), emqx_config:get_root_raw([<<"gateway">>])
Listeners = emqx_map_lib:jsonable_map( ),
emqx_map_lib:deep_get( Listeners = emqx_map_lib:jsonable_map(
[<<"gateway">>, GwName, <<"listeners">>], RawConf)), emqx_map_lib:deep_get(
convert_listeners(GwName, Listeners). [<<"gateway">>, GwName, <<"listeners">>], RawConf
)
),
convert_listeners(GwName, Listeners).
-spec listener(binary()) -> {ok, map()} | {error, not_found} | {error, any()}. -spec listener(binary()) -> {ok, map()} | {error, not_found} | {error, any()}.
listener(ListenerId) -> listener(ListenerId) ->
{GwName, Type, LName} = emqx_gateway_utils:parse_listener_id(ListenerId), {GwName, Type, LName} = emqx_gateway_utils:parse_listener_id(ListenerId),
RootConf = emqx_config:fill_defaults( RootConf = emqx_config:fill_defaults(
emqx_config:get_root_raw([<<"gateway">>])), emqx_config:get_root_raw([<<"gateway">>])
),
try try
Path = [<<"gateway">>, GwName, <<"listeners">>, Type, LName], Path = [<<"gateway">>, GwName, <<"listeners">>, Type, LName],
LConf = emqx_map_lib:deep_get(Path, RootConf), LConf = emqx_map_lib:deep_get(Path, RootConf),
Running = emqx_gateway_utils:is_running( Running = emqx_gateway_utils:is_running(
binary_to_existing_atom(ListenerId), LConf), binary_to_existing_atom(ListenerId), LConf
{ok, emqx_map_lib:jsonable_map( ),
LConf#{ {ok,
id => ListenerId, emqx_map_lib:jsonable_map(
type => Type, LConf#{
name => LName, id => ListenerId,
running => Running})} type => Type,
name => LName,
running => Running
}
)}
catch catch
error : {config_not_found, _} -> error:{config_not_found, _} ->
{error, not_found}; {error, not_found};
_Class : Reason -> _Class:Reason ->
{error, Reason} {error, Reason}
end. end.
-spec add_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err(). -spec add_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err().
add_listener(GwName, ListenerRef, Conf) -> add_listener(GwName, ListenerRef, Conf) ->
ret_listener_or_err( ret_listener_or_err(
GwName, ListenerRef, GwName,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec update_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err(). -spec update_listener(atom_or_bin(), listener_ref(), map()) -> map_or_err().
update_listener(GwName, ListenerRef, Conf) -> update_listener(GwName, ListenerRef, Conf) ->
ret_listener_or_err( ret_listener_or_err(
GwName, ListenerRef, GwName,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec remove_listener(atom_or_bin(), listener_ref()) -> ok_or_err(). -spec remove_listener(atom_or_bin(), listener_ref()) -> ok_or_err().
remove_listener(GwName, ListenerRef) -> remove_listener(GwName, ListenerRef) ->
@ -223,8 +249,10 @@ add_authn(GwName, Conf) ->
-spec add_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err(). -spec add_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err().
add_authn(GwName, ListenerRef, Conf) -> add_authn(GwName, ListenerRef, Conf) ->
ret_authn( ret_authn(
GwName, ListenerRef, GwName,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec update_authn(atom_or_bin(), map()) -> map_or_err(). -spec update_authn(atom_or_bin(), map()) -> map_or_err().
update_authn(GwName, Conf) -> update_authn(GwName, Conf) ->
@ -233,8 +261,10 @@ update_authn(GwName, Conf) ->
-spec update_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err(). -spec update_authn(atom_or_bin(), listener_ref(), map()) -> map_or_err().
update_authn(GwName, ListenerRef, Conf) -> update_authn(GwName, ListenerRef, Conf) ->
ret_authn( ret_authn(
GwName, ListenerRef, GwName,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})). ListenerRef,
update({?FUNCTION_NAME, bin(GwName), bin(ListenerRef), Conf})
).
-spec remove_authn(atom_or_bin()) -> ok_or_err(). -spec remove_authn(atom_or_bin()) -> ok_or_err().
remove_authn(GwName) -> remove_authn(GwName) ->
@ -249,8 +279,8 @@ update(Req) ->
res(emqx_conf:update([gateway], Req, #{override_to => cluster})). res(emqx_conf:update([gateway], Req, #{override_to => cluster})).
res({ok, Result}) -> {ok, Result}; res({ok, Result}) -> {ok, Result};
res({error, {pre_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, {post_config_update, ?MODULE, Reason}}) -> {error, Reason};
res({error, Reason}) -> {error, Reason}. res({error, Reason}) -> {error, Reason}.
bin({LType, LName}) -> bin({LType, LName}) ->
@ -266,38 +296,58 @@ ret_ok_err(Err) -> Err.
ret_gw(GwName, {ok, #{raw_config := GwConf}}) -> ret_gw(GwName, {ok, #{raw_config := GwConf}}) ->
GwConf1 = emqx_map_lib:deep_get([bin(GwName)], GwConf), GwConf1 = emqx_map_lib:deep_get([bin(GwName)], GwConf),
LsConf = emqx_map_lib:deep_get( LsConf = emqx_map_lib:deep_get(
[bin(GwName), <<"listeners">>], [bin(GwName), <<"listeners">>],
GwConf, #{}), GwConf,
#{}
),
NLsConf = NLsConf =
lists:foldl(fun({LType, SubConf}, Acc) -> lists:foldl(
NLConfs = fun({LType, SubConf}, Acc) ->
lists:map(fun({LName, LConf}) -> NLConfs =
do_convert_listener2(GwName, LType, LName, LConf) lists:map(
end, maps:to_list(SubConf)), fun({LName, LConf}) ->
[NLConfs | Acc] do_convert_listener2(GwName, LType, LName, LConf)
end, [], maps:to_list(LsConf)), end,
maps:to_list(SubConf)
),
[NLConfs | Acc]
end,
[],
maps:to_list(LsConf)
),
{ok, maps:merge(GwConf1, #{<<"listeners">> => lists:append(NLsConf)})}; {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}}) -> ret_authn(GwName, {ok, #{raw_config := GwConf}}) ->
Authn = emqx_map_lib:deep_get( Authn = emqx_map_lib:deep_get(
[bin(GwName), <<"authentication">>], [bin(GwName), <<"authentication">>],
GwConf), GwConf
),
{ok, Authn}; {ok, Authn};
ret_authn(_GwName, Err) -> Err. ret_authn(_GwName, Err) ->
Err.
ret_authn(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) -> ret_authn(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) ->
Authn = emqx_map_lib:deep_get( Authn = emqx_map_lib:deep_get(
[bin(GwName), <<"listeners">>, bin(LType), [
bin(LName), <<"authentication">>], bin(GwName),
GwConf), <<"listeners">>,
bin(LType),
bin(LName),
<<"authentication">>
],
GwConf
),
{ok, Authn}; {ok, Authn};
ret_authn(_, _, Err) -> Err. ret_authn(_, _, Err) ->
Err.
ret_listener_or_err(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) -> ret_listener_or_err(GwName, {LType, LName}, {ok, #{raw_config := GwConf}}) ->
LConf = emqx_map_lib:deep_get( LConf = emqx_map_lib:deep_get(
[bin(GwName), <<"listeners">>, bin(LType), bin(LName)], [bin(GwName), <<"listeners">>, bin(LType), bin(LName)],
GwConf), GwConf
),
{ok, do_convert_listener2(GwName, LType, LName, LConf)}; {ok, do_convert_listener2(GwName, LType, LName, LConf)};
ret_listener_or_err(_, _, Err) -> ret_listener_or_err(_, _, Err) ->
Err. Err.
@ -306,9 +356,11 @@ ret_listener_or_err(_, _, Err) ->
%% Config Handler %% Config Handler
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec pre_config_update(list(atom()), -spec pre_config_update(
emqx_config:update_request(), list(atom()),
emqx_config:raw_config()) -> emqx_config:update_request(),
emqx_config:raw_config()
) ->
{ok, emqx_config:update_request()} | {error, term()}. {ok, emqx_config:update_request()} | {error, term()}.
pre_config_update(_, {load_gateway, GwName, Conf}, RawConf) -> pre_config_update(_, {load_gateway, GwName, Conf}, RawConf) ->
case maps:get(GwName, RawConf, undefined) of 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})} {ok, emqx_map_lib:deep_merge(RawConf, #{GwName => NConf})}
end; end;
pre_config_update(_, {unload_gateway, GwName}, RawConf) -> pre_config_update(_, {unload_gateway, GwName}, RawConf) ->
_ = tune_gw_certs(fun clear_certs/2, _ = tune_gw_certs(
GwName, fun clear_certs/2,
maps:get(GwName, RawConf, #{}) GwName,
), maps:get(GwName, RawConf, #{})
),
{ok, maps:remove(GwName, RawConf)}; {ok, maps:remove(GwName, RawConf)};
pre_config_update(_, {add_listener, GwName, {LType, LName}, Conf}, RawConf) -> pre_config_update(_, {add_listener, GwName, {LType, LName}, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case
[GwName, <<"listeners">>, LType, LName], RawConf, undefined) of emqx_map_lib:deep_get(
[GwName, <<"listeners">>, LType, LName], RawConf, undefined
)
of
undefined -> undefined ->
NConf = convert_certs(certs_dir(GwName), Conf), NConf = convert_certs(certs_dir(GwName), Conf),
NListener = #{LType => #{LName => NConf}}, NListener = #{LType => #{LName => NConf}},
{ok, emqx_map_lib:deep_merge( {ok,
RawConf, emqx_map_lib:deep_merge(
#{GwName => #{<<"listeners">> => NListener}})}; RawConf,
#{GwName => #{<<"listeners">> => NListener}}
)};
_ -> _ ->
badres_listener(already_exist, GwName, LType, LName) badres_listener(already_exist, GwName, LType, LName)
end; end;
pre_config_update(_, {update_listener, GwName, {LType, LName}, Conf}, RawConf) -> pre_config_update(_, {update_listener, GwName, {LType, LName}, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case
[GwName, <<"listeners">>, LType, LName], RawConf, undefined) of emqx_map_lib:deep_get(
[GwName, <<"listeners">>, LType, LName], RawConf, undefined
)
of
undefined -> undefined ->
badres_listener(not_found, GwName, LType, LName); badres_listener(not_found, GwName, LType, LName);
OldConf -> OldConf ->
NConf = convert_certs(certs_dir(GwName), Conf, OldConf), NConf = convert_certs(certs_dir(GwName), Conf, OldConf),
NListener = #{LType => #{LName => NConf}}, NListener = #{LType => #{LName => NConf}},
{ok, emqx_map_lib:deep_merge( {ok,
RawConf, emqx_map_lib:deep_merge(
#{GwName => #{<<"listeners">> => NListener}})} RawConf,
#{GwName => #{<<"listeners">> => NListener}}
)}
end; end;
pre_config_update(_, {remove_listener, GwName, {LType, LName}}, RawConf) -> pre_config_update(_, {remove_listener, GwName, {LType, LName}}, RawConf) ->
Path = [GwName, <<"listeners">>, LType, LName], Path = [GwName, <<"listeners">>, LType, LName],
case emqx_map_lib:deep_get(Path, RawConf, undefined) of case emqx_map_lib:deep_get(Path, RawConf, undefined) of
undefined -> undefined ->
{ok, RawConf}; {ok, RawConf};
OldConf -> OldConf ->
clear_certs(certs_dir(GwName), OldConf), clear_certs(certs_dir(GwName), OldConf),
{ok, emqx_map_lib:deep_remove(Path, RawConf)} {ok, emqx_map_lib:deep_remove(Path, RawConf)}
end; end;
pre_config_update(_, {add_authn, GwName, Conf}, RawConf) -> pre_config_update(_, {add_authn, GwName, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case
[GwName, ?AUTHN_BIN], RawConf, undefined) of emqx_map_lib:deep_get(
[GwName, ?AUTHN_BIN], RawConf, undefined
)
of
undefined -> undefined ->
{ok, emqx_map_lib:deep_merge( {ok,
RawConf, emqx_map_lib:deep_merge(
#{GwName => #{?AUTHN_BIN => Conf}})}; RawConf,
#{GwName => #{?AUTHN_BIN => Conf}}
)};
_ -> _ ->
badres_authn(already_exist, GwName) badres_authn(already_exist, GwName)
end; end;
pre_config_update(_, {add_authn, GwName, {LType, LName}, Conf}, RawConf) -> pre_config_update(_, {add_authn, GwName, {LType, LName}, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case
[GwName, <<"listeners">>, LType, LName], emqx_map_lib:deep_get(
RawConf, undefined) of [GwName, <<"listeners">>, LType, LName],
RawConf,
undefined
)
of
undefined -> undefined ->
badres_listener(not_found, GwName, LType, LName); badres_listener(not_found, GwName, LType, LName);
Listener -> Listener ->
case maps:get(?AUTHN_BIN, Listener, undefined) of case maps:get(?AUTHN_BIN, Listener, undefined) of
undefined -> undefined ->
NListener = maps:put(?AUTHN_BIN, Conf, Listener), NListener = maps:put(?AUTHN_BIN, Conf, Listener),
NGateway = #{GwName => NGateway = #{
#{<<"listeners">> => GwName =>
#{LType => #{LName => NListener}}}}, #{
<<"listeners">> =>
#{LType => #{LName => NListener}}
}
},
{ok, emqx_map_lib:deep_merge(RawConf, NGateway)}; {ok, emqx_map_lib:deep_merge(RawConf, NGateway)};
_ -> _ ->
badres_listener_authn(already_exist, GwName, LType, LName) badres_listener_authn(already_exist, GwName, LType, LName)
end end
end; end;
pre_config_update(_, {update_authn, GwName, Conf}, RawConf) -> pre_config_update(_, {update_authn, GwName, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case
[GwName, ?AUTHN_BIN], RawConf, undefined) of emqx_map_lib:deep_get(
[GwName, ?AUTHN_BIN], RawConf, undefined
)
of
undefined -> undefined ->
badres_authn(not_found, GwName); badres_authn(not_found, GwName);
_ -> _ ->
{ok, emqx_map_lib:deep_merge( {ok,
RawConf, emqx_map_lib:deep_merge(
#{GwName => #{?AUTHN_BIN => Conf}})} RawConf,
#{GwName => #{?AUTHN_BIN => Conf}}
)}
end; end;
pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) -> pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) ->
case emqx_map_lib:deep_get( case
[GwName, <<"listeners">>, LType, LName], emqx_map_lib:deep_get(
RawConf, undefined) of [GwName, <<"listeners">>, LType, LName],
RawConf,
undefined
)
of
undefined -> undefined ->
badres_listener(not_found, GwName, LType, LName); badres_listener(not_found, GwName, LType, LName);
Listener -> Listener ->
@ -418,76 +500,116 @@ pre_config_update(_, {update_authn, GwName, {LType, LName}, Conf}, RawConf) ->
badres_listener_authn(not_found, GwName, LType, LName); badres_listener_authn(not_found, GwName, LType, LName);
Auth -> Auth ->
NListener = maps:put( NListener = maps:put(
?AUTHN_BIN, ?AUTHN_BIN,
emqx_map_lib:deep_merge(Auth, Conf), emqx_map_lib:deep_merge(Auth, Conf),
Listener Listener
), ),
NGateway = #{GwName => NGateway = #{
#{<<"listeners">> => GwName =>
#{LType => #{LName => NListener}}}}, #{
<<"listeners">> =>
#{LType => #{LName => NListener}}
}
},
{ok, emqx_map_lib:deep_merge(RawConf, NGateway)} {ok, emqx_map_lib:deep_merge(RawConf, NGateway)}
end end
end; end;
pre_config_update(_, {remove_authn, GwName}, RawConf) -> pre_config_update(_, {remove_authn, GwName}, RawConf) ->
{ok, emqx_map_lib:deep_remove( {ok,
[GwName, ?AUTHN_BIN], RawConf)}; emqx_map_lib:deep_remove(
[GwName, ?AUTHN_BIN], RawConf
)};
pre_config_update(_, {remove_authn, GwName, {LType, LName}}, RawConf) -> pre_config_update(_, {remove_authn, GwName, {LType, LName}}, RawConf) ->
Path = [GwName, <<"listeners">>, LType, LName, ?AUTHN_BIN], Path = [GwName, <<"listeners">>, LType, LName, ?AUTHN_BIN],
{ok, emqx_map_lib:deep_remove(Path, RawConf)}; {ok, emqx_map_lib:deep_remove(Path, RawConf)};
pre_config_update(_, UnknownReq, _RawConf) -> pre_config_update(_, UnknownReq, _RawConf) ->
logger:error("Unknown configuration update request: ~0p", [UnknownReq]), logger:error("Unknown configuration update request: ~0p", [UnknownReq]),
{error, badreq}. {error, badreq}.
badres_gateway(not_found, GwName) -> badres_gateway(not_found, GwName) ->
{error, {badres, #{resource => gateway, gateway => GwName, {error,
reason => not_found}}}; {badres, #{
resource => gateway,
gateway => GwName,
reason => not_found
}}};
badres_gateway(already_exist, GwName) -> badres_gateway(already_exist, GwName) ->
{error, {badres, #{resource => gateway, gateway => GwName, {error,
reason => already_exist}}}. {badres, #{
resource => gateway,
gateway => GwName,
reason => already_exist
}}}.
badres_listener(not_found, GwName, LType, LName) -> badres_listener(not_found, GwName, LType, LName) ->
{error, {badres, #{resource => listener, gateway => GwName, {error,
listener => {GwName, LType, LName}, {badres, #{
reason => not_found}}}; resource => listener,
gateway => GwName,
listener => {GwName, LType, LName},
reason => not_found
}}};
badres_listener(already_exist, GwName, LType, LName) -> badres_listener(already_exist, GwName, LType, LName) ->
{error, {badres, #{resource => listener, gateway => GwName, {error,
listener => {GwName, LType, LName}, {badres, #{
reason => already_exist}}}. resource => listener,
gateway => GwName,
listener => {GwName, LType, LName},
reason => already_exist
}}}.
badres_authn(not_found, GwName) -> badres_authn(not_found, GwName) ->
{error, {badres, #{resource => authn, gateway => GwName, {error,
reason => not_found}}}; {badres, #{
resource => authn,
gateway => GwName,
reason => not_found
}}};
badres_authn(already_exist, GwName) -> badres_authn(already_exist, GwName) ->
{error, {badres, #{resource => authn, gateway => GwName, {error,
reason => already_exist}}}. {badres, #{
resource => authn,
gateway => GwName,
reason => already_exist
}}}.
badres_listener_authn(not_found, GwName, LType, LName) -> badres_listener_authn(not_found, GwName, LType, LName) ->
{error, {badres, #{resource => listener_authn, gateway => GwName, {error,
listener => {GwName, LType, LName}, {badres, #{
reason => not_found}}}; resource => listener_authn,
gateway => GwName,
listener => {GwName, LType, LName},
reason => not_found
}}};
badres_listener_authn(already_exist, GwName, LType, LName) -> badres_listener_authn(already_exist, GwName, LType, LName) ->
{error, {badres, #{resource => listener_authn, gateway => GwName, {error,
listener => {GwName, LType, LName}, {badres, #{
reason => already_exist}}}. resource => listener_authn,
gateway => GwName,
listener => {GwName, LType, LName},
reason => already_exist
}}}.
-spec post_config_update(list(atom()), -spec post_config_update(
emqx_config:update_request(), list(atom()),
emqx_config:config(), emqx_config:update_request(),
emqx_config:config(), emqx_config:app_envs()) emqx_config:config(),
-> ok | {ok, Result::any()} | {error, Reason::term()}. 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) -> post_config_update(_, Req, NewConfig, OldConfig, _AppEnvs) when is_tuple(Req) ->
[_Tag, GwName0 | _] = tuple_to_list(Req), [_Tag, GwName0 | _] = tuple_to_list(Req),
GwName = binary_to_existing_atom(GwName0), GwName = binary_to_existing_atom(GwName0),
case {maps:get(GwName, NewConfig, undefined), case {maps:get(GwName, NewConfig, undefined), maps:get(GwName, OldConfig, undefined)} of
maps:get(GwName, OldConfig, undefined)} of
{undefined, undefined} -> {undefined, undefined} ->
ok; %% nothing to change %% nothing to change
ok;
{undefined, Old} when is_map(Old) -> {undefined, Old} when is_map(Old) ->
emqx_gateway:unload(GwName); emqx_gateway:unload(GwName);
{New, undefined} when is_map(New) -> {New, undefined} when is_map(New) ->
emqx_gateway:load(GwName, New); emqx_gateway:load(GwName, New);
{New, Old} when is_map(New), is_map(Old) -> {New, Old} when is_map(New), is_map(Old) ->
emqx_gateway:update(GwName, New) emqx_gateway:update(GwName, New)
@ -499,29 +621,39 @@ post_config_update(_, _Req, _NewConfig, _OldConfig, _AppEnvs) ->
%% Internal funcs %% Internal funcs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
tune_gw_certs(Fun, GwName, Conf) -> tune_gw_certs(Fun, GwName, Conf) ->
SubDir = certs_dir(GwName), SubDir = certs_dir(GwName),
case maps:get(<<"listeners">>, Conf, undefined) of case maps:get(<<"listeners">>, Conf, undefined) of
undefined -> Conf; undefined ->
Conf;
Liss -> Liss ->
maps:put(<<"listeners">>, maps:put(
maps:map(fun(_, Lis) -> <<"listeners">>,
maps:map(fun(_, LisConf) -> maps:map(
erlang:apply(Fun, [SubDir, LisConf]) fun(_, Lis) ->
end, Lis) maps:map(
end, Liss), fun(_, LisConf) ->
Conf) erlang:apply(Fun, [SubDir, LisConf])
end. end,
Lis
)
end,
Liss
),
Conf
)
end.
certs_dir(GwName) when is_binary(GwName) -> certs_dir(GwName) when is_binary(GwName) ->
GwName. GwName.
convert_certs(SubDir, Conf) -> convert_certs(SubDir, Conf) ->
case emqx_tls_lib:ensure_ssl_files( case
SubDir, emqx_tls_lib:ensure_ssl_files(
maps:get(<<"ssl">>, Conf, undefined) SubDir,
) of maps:get(<<"ssl">>, Conf, undefined)
)
of
{ok, SSL} -> {ok, SSL} ->
new_ssl_config(Conf, SSL); new_ssl_config(Conf, SSL);
{error, Reason} -> {error, Reason} ->

View File

@ -19,7 +19,6 @@
-include("include/emqx_gateway.hrl"). -include("include/emqx_gateway.hrl").
%% @doc The running context for a Connection/Channel process. %% @doc The running context for a Connection/Channel process.
%% %%
%% The `Context` encapsulates a complex structure of contextual information. %% The `Context` encapsulates a complex structure of contextual information.
@ -27,45 +26,50 @@
%% configuration, register devices and other common operations. %% configuration, register devices and other common operations.
%% %%
-type context() :: -type context() ::
#{ %% Gateway Name %% Gateway Name
gwname := gateway_name() #{
%% Authentication chains gwname := gateway_name(),
, auth := [emqx_authentication:chain_name()] %% Authentication chains
%% The ConnectionManager PID auth := [emqx_authentication:chain_name()],
, cm := pid() %% The ConnectionManager PID
}. cm := pid()
}.
%% Authentication circle %% Authentication circle
-export([ authenticate/2 -export([
, open_session/5 authenticate/2,
, open_session/6 open_session/5,
, insert_channel_info/4 open_session/6,
, set_chan_info/3 insert_channel_info/4,
, set_chan_stats/3 set_chan_info/3,
, connection_closed/2 set_chan_stats/3,
]). connection_closed/2
]).
%% Message circle %% Message circle
-export([ authorize/4 -export([
% Needless for pub/sub authorize/4
%, publish/3 % Needless for pub/sub
%, subscribe/4 %, publish/3
]). %, subscribe/4
]).
%% Metrics & Stats %% Metrics & Stats
-export([ metrics_inc/2 -export([
, metrics_inc/3 metrics_inc/2,
]). metrics_inc/3
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Authentication circle %% Authentication circle
%% @doc Authenticate whether the client has access to the Broker. %% @doc Authenticate whether the client has access to the Broker.
-spec authenticate(context(), emqx_types:clientinfo()) -spec authenticate(context(), emqx_types:clientinfo()) ->
-> {ok, emqx_types:clientinfo()} {ok, emqx_types:clientinfo()}
| {error, any()}. | {error, any()}.
authenticate(_Ctx = #{auth := _ChainNames}, ClientInfo0) authenticate(_Ctx = #{auth := _ChainNames}, ClientInfo0) when
when is_list(_ChainNames) -> is_list(_ChainNames)
->
ClientInfo = ClientInfo0#{zone => default}, ClientInfo = ClientInfo0#{zone => default},
case emqx_access_control:authenticate(ClientInfo) of case emqx_access_control:authenticate(ClientInfo) of
{ok, _} -> {ok, _} ->
@ -78,43 +82,74 @@ authenticate(_Ctx = #{auth := _ChainNames}, ClientInfo0)
%% %%
%% This function should be called after the client has authenticated %% This function should be called after the client has authenticated
%% successfully so that the client can be managed in the cluster. %% successfully so that the client can be managed in the cluster.
-spec open_session(context(), boolean(), emqx_types:clientinfo(), -spec open_session(
emqx_types:conninfo(), context(),
fun((emqx_types:clientinfo(), boolean(),
emqx_types:conninfo()) -> Session) emqx_types:clientinfo(),
) emqx_types:conninfo(),
-> {ok, #{session := Session, fun(
present := boolean(), (
pendings => list() emqx_types:clientinfo(),
}} emqx_types:conninfo()
| {error, any()}. ) -> Session
)
) ->
{ok, #{
session := Session,
present := boolean(),
pendings => list()
}}
| {error, any()}.
open_session(Ctx, CleanStart, ClientInfo, ConnInfo, CreateSessionFun) -> open_session(Ctx, CleanStart, ClientInfo, ConnInfo, CreateSessionFun) ->
open_session(Ctx, CleanStart, ClientInfo, ConnInfo, open_session(
CreateSessionFun, emqx_session). Ctx,
CleanStart,
ClientInfo,
ConnInfo,
CreateSessionFun,
emqx_session
).
open_session(_Ctx = #{gwname := GwName}, open_session(
CleanStart, ClientInfo, ConnInfo, CreateSessionFun, SessionMod) -> _Ctx = #{gwname := GwName},
emqx_gateway_cm:open_session(GwName, CleanStart, CleanStart,
ClientInfo, ConnInfo, ClientInfo,
CreateSessionFun, SessionMod). ConnInfo,
CreateSessionFun,
SessionMod
) ->
emqx_gateway_cm:open_session(
GwName,
CleanStart,
ClientInfo,
ConnInfo,
CreateSessionFun,
SessionMod
).
-spec insert_channel_info(context(), -spec insert_channel_info(
emqx_types:clientid(), context(),
emqx_types:infos(), emqx_types:clientid(),
emqx_types:stats()) -> ok. emqx_types:infos(),
emqx_types:stats()
) -> ok.
insert_channel_info(_Ctx = #{gwname := GwName}, ClientId, Infos, Stats) -> insert_channel_info(_Ctx = #{gwname := GwName}, ClientId, Infos, Stats) ->
emqx_gateway_cm:insert_channel_info(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 %% @doc Set the Channel Info to the ConnectionManager for this client
-spec set_chan_info(context(), -spec set_chan_info(
emqx_types:clientid(), context(),
emqx_types:infos()) -> boolean(). emqx_types:clientid(),
emqx_types:infos()
) -> boolean().
set_chan_info(_Ctx = #{gwname := GwName}, ClientId, Infos) -> set_chan_info(_Ctx = #{gwname := GwName}, ClientId, Infos) ->
emqx_gateway_cm:set_chan_info(GwName, ClientId, Infos). emqx_gateway_cm:set_chan_info(GwName, ClientId, Infos).
-spec set_chan_stats(context(), -spec set_chan_stats(
emqx_types:clientid(), context(),
emqx_types:stats()) -> boolean(). emqx_types:clientid(),
emqx_types:stats()
) -> boolean().
set_chan_stats(_Ctx = #{gwname := GwName}, ClientId, Stats) -> set_chan_stats(_Ctx = #{gwname := GwName}, ClientId, Stats) ->
emqx_gateway_cm:set_chan_stats(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) -> connection_closed(_Ctx = #{gwname := GwName}, ClientId) ->
emqx_gateway_cm:connection_closed(GwName, ClientId). emqx_gateway_cm:connection_closed(GwName, ClientId).
-spec authorize(context(), emqx_types:clientinfo(), -spec authorize(
emqx_types:pubsub(), emqx_types:topic()) context(),
-> allow | deny. emqx_types:clientinfo(),
emqx_types:pubsub(),
emqx_types:topic()
) ->
allow | deny.
authorize(_Ctx, ClientInfo, PubSub, Topic) -> authorize(_Ctx, ClientInfo, PubSub, Topic) ->
emqx_access_control:authorize(ClientInfo, PubSub, Topic). emqx_access_control:authorize(ClientInfo, PubSub, Topic).

View File

@ -27,13 +27,14 @@
-export([start_link/1]). -export([start_link/1]).
-export([ create_insta/3 -export([
, remove_insta/2 create_insta/3,
, update_insta/3 remove_insta/2,
, start_insta/2 update_insta/3,
, stop_insta/2 start_insta/2,
, list_insta/1 stop_insta/2,
]). list_insta/1
]).
%% Supervisor callbacks %% Supervisor callbacks
-export([init/1]). -export([init/1]).
@ -48,70 +49,72 @@ start_link(GwName) ->
-spec create_insta(pid(), gateway(), map()) -> {ok, GwInstaPid :: pid()} | {error, any()}. -spec create_insta(pid(), gateway(), map()) -> {ok, GwInstaPid :: pid()} | {error, any()}.
create_insta(Sup, Gateway = #{name := Name}, GwDscrptr) -> create_insta(Sup, Gateway = #{name := Name}, GwDscrptr) ->
case emqx_gateway_utils:find_sup_child(Sup, Name) of case emqx_gateway_utils:find_sup_child(Sup, Name) of
{ok, _GwInstaPid} -> {error, alredy_existed}; {ok, _GwInstaPid} ->
{error, alredy_existed};
false -> false ->
Ctx = ctx(Sup, Name), Ctx = ctx(Sup, Name),
ChildSpec = emqx_gateway_utils:childspec( ChildSpec = emqx_gateway_utils:childspec(
Name, Name,
worker, worker,
emqx_gateway_insta_sup, emqx_gateway_insta_sup,
[Gateway, Ctx, GwDscrptr] [Gateway, Ctx, GwDscrptr]
), ),
emqx_gateway_utils:supervisor_ret( emqx_gateway_utils:supervisor_ret(
supervisor:start_child(Sup, ChildSpec) supervisor:start_child(Sup, ChildSpec)
) )
end. end.
-spec remove_insta(pid(), Name :: gateway_name()) -> ok | {error, any()}. -spec remove_insta(pid(), Name :: gateway_name()) -> ok | {error, any()}.
remove_insta(Sup, Name) -> remove_insta(Sup, Name) ->
case emqx_gateway_utils:find_sup_child(Sup, Name) of case emqx_gateway_utils:find_sup_child(Sup, Name) of
false -> ok; false ->
ok;
{ok, _GwInstaPid} -> {ok, _GwInstaPid} ->
ok = supervisor:terminate_child(Sup, Name), ok = supervisor:terminate_child(Sup, Name),
ok = supervisor:delete_child(Sup, Name) ok = supervisor:delete_child(Sup, Name)
end. end.
-spec update_insta(pid(), gateway_name(), emqx_config:config()) -spec update_insta(pid(), gateway_name(), emqx_config:config()) ->
-> ok | {error, any()}. ok | {error, any()}.
update_insta(Sup, Name, Config) -> update_insta(Sup, Name, Config) ->
case emqx_gateway_utils:find_sup_child(Sup, Name) of case emqx_gateway_utils:find_sup_child(Sup, Name) of
false -> {error, not_found}; false -> {error, not_found};
{ok, GwInstaPid} -> {ok, GwInstaPid} -> emqx_gateway_insta_sup:update(GwInstaPid, Config)
emqx_gateway_insta_sup:update(GwInstaPid, Config)
end. end.
-spec start_insta(pid(), gateway_name()) -> ok | {error, any()}. -spec start_insta(pid(), gateway_name()) -> ok | {error, any()}.
start_insta(Sup, Name) -> start_insta(Sup, Name) ->
case emqx_gateway_utils:find_sup_child(Sup, Name) of case emqx_gateway_utils:find_sup_child(Sup, Name) of
false -> {error, not_found}; false -> {error, not_found};
{ok, GwInstaPid} -> {ok, GwInstaPid} -> emqx_gateway_insta_sup:enable(GwInstaPid)
emqx_gateway_insta_sup:enable(GwInstaPid)
end. end.
-spec stop_insta(pid(), gateway_name()) -> ok | {error, any()}. -spec stop_insta(pid(), gateway_name()) -> ok | {error, any()}.
stop_insta(Sup, Name) -> stop_insta(Sup, Name) ->
case emqx_gateway_utils:find_sup_child(Sup, Name) of case emqx_gateway_utils:find_sup_child(Sup, Name) of
false -> {error, not_found}; false -> {error, not_found};
{ok, GwInstaPid} -> {ok, GwInstaPid} -> emqx_gateway_insta_sup:disable(GwInstaPid)
emqx_gateway_insta_sup:disable(GwInstaPid)
end. end.
-spec list_insta(pid()) -> [gateway()]. -spec list_insta(pid()) -> [gateway()].
list_insta(Sup) -> list_insta(Sup) ->
lists:filtermap( lists:filtermap(
fun({Name, GwInstaPid, _Type, _Mods}) -> fun({Name, GwInstaPid, _Type, _Mods}) ->
is_gateway_insta_id(Name) is_gateway_insta_id(Name) andalso
andalso {true, emqx_gateway_insta_sup:info(GwInstaPid)} {true, emqx_gateway_insta_sup:info(GwInstaPid)}
end, supervisor:which_children(Sup)). end,
supervisor:which_children(Sup)
).
%% Supervisor callback %% Supervisor callback
%% @doc Initialize Top Supervisor for a Protocol %% @doc Initialize Top Supervisor for a Protocol
init([GwName]) -> init([GwName]) ->
SupFlags = #{ strategy => one_for_one SupFlags = #{
, intensity => 10 strategy => one_for_one,
, period => 60 intensity => 10,
}, period => 60
},
CmOpts = [{gwname, GwName}], CmOpts = [{gwname, GwName}],
CM = emqx_gateway_utils:childspec(worker, emqx_gateway_cm, [CmOpts]), CM = emqx_gateway_utils:childspec(worker, emqx_gateway_cm, [CmOpts]),
Metrics = emqx_gateway_utils:childspec(worker, emqx_gateway_metrics, [GwName]), Metrics = emqx_gateway_utils:childspec(worker, emqx_gateway_metrics, [GwName]),
@ -122,10 +125,11 @@ init([GwName]) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
ctx(Sup, Name) -> ctx(Sup, Name) ->
{ok, CM} = emqx_gateway_utils:find_sup_child(Sup, emqx_gateway_cm), {ok, CM} = emqx_gateway_utils:find_sup_child(Sup, emqx_gateway_cm),
#{ gwname => Name #{
, cm => CM gwname => Name,
}. cm => CM
}.
is_gateway_insta_id(emqx_gateway_cm) -> is_gateway_insta_id(emqx_gateway_cm) ->
false; false;

View File

@ -26,58 +26,63 @@
-import(emqx_gateway_utils, [listener_id/3]). -import(emqx_gateway_utils, [listener_id/3]).
%% Mgmt APIs - gateway %% Mgmt APIs - gateway
-export([ gateways/1 -export([gateways/1]).
]).
%% Mgmt APIs %% Mgmt APIs
-export([ add_listener/2 -export([
, remove_listener/1 add_listener/2,
, update_listener/2 remove_listener/1,
]). update_listener/2
]).
-export([ authn/1 -export([
, authn/2 authn/1,
, add_authn/2 authn/2,
, add_authn/3 add_authn/2,
, update_authn/2 add_authn/3,
, update_authn/3 update_authn/2,
, remove_authn/1 update_authn/3,
, remove_authn/2 remove_authn/1,
]). remove_authn/2
]).
%% Mgmt APIs - clients %% Mgmt APIs - clients
-export([ lookup_client/3 -export([
, kickout_client/2 lookup_client/3,
, list_client_subscriptions/2 kickout_client/2,
, client_subscribe/4 list_client_subscriptions/2,
, client_unsubscribe/3 client_subscribe/4,
]). client_unsubscribe/3
]).
%% Utils for http, swagger, etc. %% Utils for http, swagger, etc.
-export([ return_http_error/2 -export([
, with_gateway/2 return_http_error/2,
, with_authn/2 with_gateway/2,
, with_listener_authn/3 with_authn/2,
, checks/2 with_listener_authn/3,
, reason2resp/1 checks/2,
, reason2msg/1 reason2resp/1,
]). reason2msg/1
]).
-type gateway_summary() :: -type gateway_summary() ::
#{ name := binary() #{
, status := running | stopped | unloaded name := binary(),
, created_at => binary() status := running | stopped | unloaded,
, started_at => binary() created_at => binary(),
, stopped_at => binary() started_at => binary(),
, max_connections => integer() stopped_at => binary(),
, current_connections => integer() max_connections => integer(),
, listeners => [] current_connections => integer(),
}. listeners => []
}.
-elvis([ {elvis_style, god_modules, disable} -elvis([
, {elvis_style, no_nested_try_catch, disable} {elvis_style, god_modules, disable},
, {elvis_style, invalid_dynamic_call, disable} {elvis_style, no_nested_try_catch, disable},
]). {elvis_style, invalid_dynamic_call, disable}
]).
-define(DEFAULT_CALL_TIMEOUT, 15000). -define(DEFAULT_CALL_TIMEOUT, 15000).
@ -85,63 +90,81 @@
%% Mgmt APIs - gateway %% Mgmt APIs - gateway
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec gateways(Status :: all | running | stopped | unloaded) -spec gateways(Status :: all | running | stopped | unloaded) ->
-> [gateway_summary()]. [gateway_summary()].
gateways(Status) -> gateways(Status) ->
Gateways = lists:map(fun({GwName, _}) -> Gateways = lists:map(
case emqx_gateway:lookup(GwName) of fun({GwName, _}) ->
undefined -> #{name => GwName, status => unloaded}; case emqx_gateway:lookup(GwName) of
GwInfo = #{config := Config} -> undefined ->
GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339( #{name => GwName, status => unloaded};
[created_at, started_at, stopped_at], GwInfo = #{config := Config} ->
GwInfo), GwInfo0 = emqx_gateway_utils:unix_ts_to_rfc3339(
GwInfo1 = maps:with([name, [created_at, started_at, stopped_at],
status, GwInfo
created_at, ),
started_at, GwInfo1 = maps:with(
stopped_at], GwInfo0), [
GwInfo1#{ name,
max_connections => max_connections_count(Config), status,
current_connections => current_connections_count(GwName), created_at,
listeners => get_listeners_status(GwName, Config)} started_at,
end stopped_at
end, emqx_gateway_registry:list()), ],
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 case Status of
all -> Gateways; all -> Gateways;
_ -> _ -> [Gw || Gw = #{status := S} <- Gateways, S == Status]
[Gw || Gw = #{status := S} <- Gateways, S == Status]
end. end.
%% @private %% @private
max_connections_count(Config) -> max_connections_count(Config) ->
Listeners = emqx_gateway_utils:normalize_config(Config), Listeners = emqx_gateway_utils:normalize_config(Config),
lists:foldl(fun({_, _, _, SocketOpts, _}, Acc) -> lists:foldl(
Acc + proplists:get_value(max_connections, SocketOpts, 0) fun({_, _, _, SocketOpts, _}, Acc) ->
end, 0, Listeners). Acc + proplists:get_value(max_connections, SocketOpts, 0)
end,
0,
Listeners
).
%% @private %% @private
current_connections_count(GwName) -> current_connections_count(GwName) ->
try try
InfoTab = emqx_gateway_cm:tabname(info, GwName), InfoTab = emqx_gateway_cm:tabname(info, GwName),
ets:info(InfoTab, size) ets:info(InfoTab, size)
catch _ : _ -> catch
0 _:_ ->
0
end. end.
%% @private %% @private
get_listeners_status(GwName, Config) -> get_listeners_status(GwName, Config) ->
Listeners = emqx_gateway_utils:normalize_config(Config), Listeners = emqx_gateway_utils:normalize_config(Config),
lists:map(fun({Type, LisName, ListenOn, _, _}) -> lists:map(
Name0 = listener_id(GwName, Type, LisName), fun({Type, LisName, ListenOn, _, _}) ->
Name = {Name0, ListenOn}, Name0 = listener_id(GwName, Type, LisName),
LisO = #{id => Name0, type => Type, name => LisName}, Name = {Name0, ListenOn},
case catch esockd:listener(Name) of LisO = #{id => Name0, type => Type, name => LisName},
_Pid when is_pid(_Pid) -> case catch esockd:listener(Name) of
LisO#{running => true}; _Pid when is_pid(_Pid) ->
_ -> LisO#{running => true};
LisO#{running => false} _ ->
end LisO#{running => false}
end, Listeners). end
end,
Listeners
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Mgmt APIs - listeners %% Mgmt APIs - listeners
@ -150,16 +173,30 @@ get_listeners_status(GwName, Config) ->
-spec add_listener(atom() | binary(), map()) -> {ok, map()}. -spec add_listener(atom() | binary(), map()) -> {ok, map()}.
add_listener(ListenerId, NewConf0) -> add_listener(ListenerId, NewConf0) ->
{GwName, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId), {GwName, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId),
NewConf = maps:without([<<"id">>, <<"name">>, NewConf = maps:without(
<<"type">>, <<"running">>], NewConf0), [
<<"id">>,
<<"name">>,
<<"type">>,
<<"running">>
],
NewConf0
),
confexp(emqx_gateway_conf:add_listener(GwName, {Type, Name}, NewConf)). confexp(emqx_gateway_conf:add_listener(GwName, {Type, Name}, NewConf)).
-spec update_listener(atom() | binary(), map()) -> {ok, map()}. -spec update_listener(atom() | binary(), map()) -> {ok, map()}.
update_listener(ListenerId, NewConf0) -> update_listener(ListenerId, NewConf0) ->
{GwName, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId), {GwName, Type, Name} = emqx_gateway_utils:parse_listener_id(ListenerId),
NewConf = maps:without([<<"id">>, <<"name">>, NewConf = maps:without(
<<"type">>, <<"running">>], NewConf0), [
<<"id">>,
<<"name">>,
<<"type">>,
<<"running">>
],
NewConf0
),
confexp(emqx_gateway_conf:update_listener(GwName, {Type, Name}, NewConf)). confexp(emqx_gateway_conf:update_listener(GwName, {Type, Name}, NewConf)).
-spec remove_listener(binary()) -> ok. -spec remove_listener(binary()) -> ok.
@ -173,9 +210,9 @@ authn(GwName) ->
Path = [gateway, GwName, ?AUTHN], Path = [gateway, GwName, ?AUTHN],
ChainName = emqx_gateway_utils:global_chain(GwName), ChainName = emqx_gateway_utils:global_chain(GwName),
wrap_chain_name( wrap_chain_name(
ChainName, ChainName,
emqx_map_lib:jsonable_map(emqx:get_config(Path)) emqx_map_lib:jsonable_map(emqx:get_config(Path))
). ).
-spec authn(gateway_name(), binary()) -> map(). -spec authn(gateway_name(), binary()) -> map().
authn(GwName, ListenerId) -> authn(GwName, ListenerId) ->
@ -183,9 +220,9 @@ authn(GwName, ListenerId) ->
Path = [gateway, GwName, listeners, Type, Name, ?AUTHN], Path = [gateway, GwName, listeners, Type, Name, ?AUTHN],
ChainName = emqx_gateway_utils:listener_chain(GwName, Type, Name), ChainName = emqx_gateway_utils:listener_chain(GwName, Type, Name),
wrap_chain_name( wrap_chain_name(
ChainName, ChainName,
emqx_map_lib:jsonable_map(emqx:get_config(Path)) emqx_map_lib:jsonable_map(emqx:get_config(Path))
). ).
wrap_chain_name(ChainName, Conf) -> wrap_chain_name(ChainName, Conf) ->
case emqx_authentication:list_authenticators(ChainName) of case emqx_authentication:list_authenticators(ChainName) of
@ -230,66 +267,92 @@ confexp({error, Reason}) -> error(Reason).
%% Mgmt APIs - clients %% Mgmt APIs - clients
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec lookup_client(gateway_name(), -spec lookup_client(
emqx_types:clientid(), {module(), atom()}) -> list(). gateway_name(),
emqx_types:clientid(),
{module(), atom()}
) -> list().
lookup_client(GwName, ClientId, {M, F}) -> lookup_client(GwName, ClientId, {M, F}) ->
[begin [
Info = emqx_gateway_cm:get_chan_info(GwName, ClientId, Pid), begin
Stats = emqx_gateway_cm:get_chan_stats(GwName, ClientId, Pid), Info = emqx_gateway_cm:get_chan_info(GwName, ClientId, Pid),
M:F({{ClientId, Pid}, Info, Stats}) Stats = emqx_gateway_cm:get_chan_stats(GwName, ClientId, Pid),
end M:F({{ClientId, Pid}, Info, Stats})
|| Pid <- emqx_gateway_cm:lookup_by_clientid(GwName, ClientId)]. end
|| Pid <- emqx_gateway_cm:lookup_by_clientid(GwName, ClientId)
].
-spec kickout_client(gateway_name(), emqx_types:clientid()) -spec kickout_client(gateway_name(), emqx_types:clientid()) ->
-> {error, any()} {error, any()}
| ok. | ok.
kickout_client(GwName, ClientId) -> kickout_client(GwName, ClientId) ->
Results = [emqx_gateway_cm:kick_session(GwName, ClientId, Pid) Results = [
|| Pid <- emqx_gateway_cm:lookup_by_clientid(GwName, ClientId)], 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), IsOk = lists:any(fun(Item) -> Item =:= ok end, Results),
case {IsOk, Results} of case {IsOk, Results} of
{true , _ } -> ok; {true, _} -> ok;
{_ , []} -> {error, not_found}; {_, []} -> {error, not_found};
{false, _ } -> lists:last(Results) {false, _} -> lists:last(Results)
end. end.
-spec list_client_subscriptions(gateway_name(), emqx_types:clientid()) -spec list_client_subscriptions(gateway_name(), emqx_types:clientid()) ->
-> {error, any()} {error, any()}
| {ok, list()}. | {ok, list()}.
list_client_subscriptions(GwName, ClientId) -> list_client_subscriptions(GwName, ClientId) ->
case client_call(GwName, ClientId, subscriptions) of case client_call(GwName, ClientId, subscriptions) of
{error, Reason} -> {error, Reason}; {error, Reason} ->
{error, Reason};
{ok, Subs} -> {ok, Subs} ->
{ok, lists:map(fun({Topic, SubOpts}) -> {ok,
SubOpts#{topic => Topic} lists:map(
end, Subs)} fun({Topic, SubOpts}) ->
SubOpts#{topic => Topic}
end,
Subs
)}
end. end.
-spec client_subscribe(gateway_name(), emqx_types:clientid(), -spec client_subscribe(
emqx_types:topic(), emqx_types:subopts()) gateway_name(),
-> {error, any()} emqx_types:clientid(),
| {ok, {emqx_types:topic(), emqx_types:subopts()}}. emqx_types:topic(),
emqx_types:subopts()
) ->
{error, any()}
| {ok, {emqx_types:topic(), emqx_types:subopts()}}.
client_subscribe(GwName, ClientId, Topic, SubOpts) -> client_subscribe(GwName, ClientId, Topic, SubOpts) ->
client_call(GwName, ClientId, {subscribe, Topic, SubOpts}). client_call(GwName, ClientId, {subscribe, Topic, SubOpts}).
-spec client_unsubscribe(gateway_name(), -spec client_unsubscribe(
emqx_types:clientid(), emqx_types:topic()) gateway_name(),
-> {error, any()} emqx_types:clientid(),
| ok. emqx_types:topic()
) ->
{error, any()}
| ok.
client_unsubscribe(GwName, ClientId, Topic) -> client_unsubscribe(GwName, ClientId, Topic) ->
client_call(GwName, ClientId, {unsubscribe, Topic}). client_call(GwName, ClientId, {unsubscribe, Topic}).
client_call(GwName, ClientId, Req) -> client_call(GwName, ClientId, Req) ->
try emqx_gateway_cm:call( try
GwName, ClientId, emqx_gateway_cm:call(
Req, ?DEFAULT_CALL_TIMEOUT) of GwName,
ClientId,
Req,
?DEFAULT_CALL_TIMEOUT
)
of
undefined -> undefined ->
{error, not_found}; {error, not_found};
Res -> Res Res ->
catch throw : noproc -> Res
{error, not_found}; catch
throw : {badrpc, Reason} -> throw:noproc ->
{error, {badrpc, Reason}} {error, not_found};
throw:{badrpc, Reason} ->
{error, {badrpc, Reason}}
end. end.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -311,54 +374,88 @@ return_http_error(Code, Msg) ->
-spec reason2msg({atom(), map()} | any()) -> error | string(). -spec reason2msg({atom(), map()} | any()) -> error | string().
reason2msg({badconf, #{key := Key, value := Value, reason := Reason}}) -> reason2msg({badconf, #{key := Key, value := Value, reason := Reason}}) ->
NValue = case emqx_json:safe_encode(Value) of NValue =
{ok, Str} -> Str; case emqx_json:safe_encode(Value) of
{error, _} -> emqx_gateway_utils:stringfy(Value) {ok, Str} -> Str;
end, {error, _} -> emqx_gateway_utils:stringfy(Value)
fmtstr("Bad config value '~s' for '~s', reason: ~s", end,
[NValue, Key, Reason]); fmtstr(
reason2msg({badres, #{resource := gateway, "Bad config value '~s' for '~s', reason: ~s",
gateway := GwName, [NValue, Key, Reason]
reason := not_found}}) -> );
reason2msg(
{badres, #{
resource := gateway,
gateway := GwName,
reason := not_found
}}
) ->
fmtstr("The ~s gateway is unloaded", [GwName]); fmtstr("The ~s gateway is unloaded", [GwName]);
reason2msg(
reason2msg({badres, #{resource := gateway, {badres, #{
gateway := GwName, resource := gateway,
reason := already_exist}}) -> gateway := GwName,
reason := already_exist
}}
) ->
fmtstr("The ~s gateway already loaded", [GwName]); fmtstr("The ~s gateway already loaded", [GwName]);
reason2msg(
reason2msg({badres, #{resource := listener, {badres, #{
listener := {GwName, LType, LName}, resource := listener,
reason := not_found}}) -> listener := {GwName, LType, LName},
reason := not_found
}}
) ->
fmtstr("Listener ~s not found", [listener_id(GwName, LType, LName)]); fmtstr("Listener ~s not found", [listener_id(GwName, LType, LName)]);
reason2msg(
reason2msg({badres, #{resource := listener, {badres, #{
listener := {GwName, LType, LName}, resource := listener,
reason := already_exist}}) -> listener := {GwName, LType, LName},
fmtstr("The listener ~s of ~s already exist", reason := already_exist
[listener_id(GwName, LType, LName), GwName]); }}
) ->
reason2msg({badres, #{resource := authn, fmtstr(
gateway := GwName, "The listener ~s of ~s already exist",
reason := not_found}}) -> [listener_id(GwName, LType, LName), GwName]
);
reason2msg(
{badres, #{
resource := authn,
gateway := GwName,
reason := not_found
}}
) ->
fmtstr("The authentication not found on ~s", [GwName]); fmtstr("The authentication not found on ~s", [GwName]);
reason2msg(
reason2msg({badres, #{resource := authn, {badres, #{
gateway := GwName, resource := authn,
reason := already_exist}}) -> gateway := GwName,
reason := already_exist
}}
) ->
fmtstr("The authentication already exist on ~s", [GwName]); fmtstr("The authentication already exist on ~s", [GwName]);
reason2msg(
reason2msg({badres, #{resource := listener_authn, {badres, #{
listener := {GwName, LType, LName}, resource := listener_authn,
reason := not_found}}) -> listener := {GwName, LType, LName},
fmtstr("The authentication not found on ~s", reason := not_found
[listener_id(GwName, LType, LName)]); }}
) ->
reason2msg({badres, #{resource := listener_authn, fmtstr(
listener := {GwName, LType, LName}, "The authentication not found on ~s",
reason := already_exist}}) -> [listener_id(GwName, LType, LName)]
fmtstr("The authentication already exist 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(_) -> reason2msg(_) ->
error. error.
@ -389,10 +486,12 @@ with_listener_authn(GwName0, Id, Fun) ->
-spec with_gateway(binary(), function()) -> any(). -spec with_gateway(binary(), function()) -> any().
with_gateway(GwName0, Fun) -> with_gateway(GwName0, Fun) ->
try try
GwName = try GwName =
binary_to_existing_atom(GwName0) try
catch _ : _ -> error(badname) binary_to_existing_atom(GwName0)
end, catch
_:_ -> error(badname)
end,
case emqx_gateway:lookup(GwName) of case emqx_gateway:lookup(GwName) of
undefined -> undefined ->
return_http_error(404, "Gateway not load"); return_http_error(404, "Gateway not load");
@ -400,24 +499,26 @@ with_gateway(GwName0, Fun) ->
Fun(GwName, Gateway) Fun(GwName, Gateway)
end end
catch catch
error : badname -> error:badname ->
return_http_error(404, "Bad gateway name"); return_http_error(404, "Bad gateway name");
%% Exceptions from: checks/2 %% Exceptions from: checks/2
error : {miss_param, K} -> error:{miss_param, K} ->
return_http_error(400, [K, " is required"]); return_http_error(400, [K, " is required"]);
%% Exceptions from emqx_gateway_utils:parse_listener_id/1 %% 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]); return_http_error(400, ["invalid listener id: ", Id]);
%% Exceptions from: emqx:get_config/1 %% Exceptions from: emqx:get_config/1
error : {config_not_found, Path0} -> error:{config_not_found, Path0} ->
Path = lists:concat( 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); return_http_error(404, "Resource not found. path: " ++ Path);
Class : Reason : Stk -> Class:Reason:Stk ->
?SLOG(error, #{ msg => "uncatched_error" ?SLOG(error, #{
, reason => {Class, Reason} msg => "uncatched_error",
, stacktrace => Stk reason => {Class, Reason},
}), stacktrace => Stk
}),
reason2resp(Reason) reason2resp(Reason)
end. end.
@ -427,8 +528,7 @@ checks([], _) ->
checks([K | Ks], Map) -> checks([K | Ks], Map) ->
case maps:is_key(K, Map) of case maps:is_key(K, Map) of
true -> checks(Ks, Map); true -> checks(Ks, Map);
false -> false -> error({miss_param, K})
error({miss_param, K})
end. end.
to_list(A) when is_atom(A) -> to_list(A) when is_atom(A) ->

View File

@ -23,34 +23,36 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
%% APIs %% APIs
-export([ start_link/3 -export([
, info/1 start_link/3,
, disable/1 info/1,
, enable/1 disable/1,
, update/2 enable/1,
]). update/2
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-record(state, { -record(state, {
name :: gateway_name(), name :: gateway_name(),
config :: emqx_config:config(), config :: emqx_config:config(),
ctx :: emqx_gateway_ctx:context(), ctx :: emqx_gateway_ctx:context(),
authns :: [emqx_authentication:chain_name()], authns :: [emqx_authentication:chain_name()],
status :: stopped | running, status :: stopped | running,
child_pids :: [pid()], child_pids :: [pid()],
gw_state :: emqx_gateway_impl:state() | undefined, gw_state :: emqx_gateway_impl:state() | undefined,
created_at :: integer(), created_at :: integer(),
started_at :: integer() | undefined, started_at :: integer() | undefined,
stopped_at :: integer() | undefined stopped_at :: integer() | undefined
}). }).
-elvis([{elvis_style, invalid_dynamic_call, disable}]). -elvis([{elvis_style, invalid_dynamic_call, disable}]).
@ -60,10 +62,10 @@
start_link(Gateway, Ctx, GwDscrptr) -> start_link(Gateway, Ctx, GwDscrptr) ->
gen_server:start_link( gen_server:start_link(
?MODULE, ?MODULE,
[Gateway, Ctx, GwDscrptr], [Gateway, Ctx, GwDscrptr],
[] []
). ).
-spec info(pid()) -> gateway(). -spec info(pid()) -> gateway().
info(Pid) -> info(Pid) ->
@ -93,21 +95,22 @@ call(Pid, Req) ->
init([Gateway, Ctx, _GwDscrptr]) -> init([Gateway, Ctx, _GwDscrptr]) ->
process_flag(trap_exit, true), process_flag(trap_exit, true),
#{name := GwName, config := Config } = Gateway, #{name := GwName, config := Config} = Gateway,
State = #state{ State = #state{
ctx = Ctx, ctx = Ctx,
name = GwName, name = GwName,
authns = [], authns = [],
config = Config, config = Config,
child_pids = [], child_pids = [],
status = stopped, status = stopped,
created_at = erlang:system_time(millisecond) created_at = erlang:system_time(millisecond)
}, },
case maps:get(enable, Config, true) of case maps:get(enable, Config, true) of
false -> false ->
?SLOG(info, #{ msg => "skip_to_start_gateway_due_to_disabled" ?SLOG(info, #{
, gateway_name => GwName msg => "skip_to_start_gateway_due_to_disabled",
}), gateway_name => GwName
}),
{ok, State}; {ok, State};
true -> true ->
case cb_gateway_load(State) of case cb_gateway_load(State) of
@ -120,7 +123,6 @@ init([Gateway, Ctx, _GwDscrptr]) ->
handle_call(info, _From, State) -> handle_call(info, _From, State) ->
{reply, detailed_gateway_info(State), State}; {reply, detailed_gateway_info(State), State};
handle_call(disable, _From, State = #state{status = Status}) -> handle_call(disable, _From, State = #state{status = Status}) ->
case Status of case Status of
running -> running ->
@ -133,7 +135,6 @@ handle_call(disable, _From, State = #state{status = Status}) ->
_ -> _ ->
{reply, {error, already_stopped}, State} {reply, {error, already_stopped}, State}
end; end;
handle_call(enable, _From, State = #state{status = Status}) -> handle_call(enable, _From, State = #state{status = Status}) ->
case Status of case Status of
stopped -> stopped ->
@ -146,7 +147,6 @@ handle_call(enable, _From, State = #state{status = Status}) ->
_ -> _ ->
{reply, {error, already_started}, State} {reply, {error, already_started}, State}
end; end;
handle_call({update, Config}, _From, State) -> handle_call({update, Config}, _From, State) ->
case do_update_one_by_one(Config, State) of case do_update_one_by_one(Config, State) of
{ok, NState} -> {ok, NState} ->
@ -155,7 +155,6 @@ handle_call({update, Config}, _From, State) ->
%% If something wrong, nothing to update %% If something wrong, nothing to update
{reply, {error, Reason}, State} {reply, {error, Reason}, State}
end; end;
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
Reply = ok, Reply = ok,
{reply, Reply, State}. {reply, Reply, State}.
@ -163,38 +162,48 @@ handle_call(_Request, _From, State) ->
handle_cast(_Msg, State) -> handle_cast(_Msg, State) ->
{noreply, State}. {noreply, State}.
handle_info({'EXIT', Pid, Reason}, State = #state{name = Name, handle_info(
child_pids = Pids}) -> {'EXIT', Pid, Reason},
State = #state{
name = Name,
child_pids = Pids
}
) ->
case lists:member(Pid, Pids) of case lists:member(Pid, Pids) of
true -> true ->
?SLOG(error, #{ msg => "child_process_exited" ?SLOG(error, #{
, child => Pid msg => "child_process_exited",
, reason => Reason child => Pid,
}), reason => Reason
case Pids -- [Pid]of }),
case Pids -- [Pid] of
[] -> [] ->
?SLOG(error, #{ msg => "gateway_all_children_process_existed" ?SLOG(error, #{
, gateway_name => Name msg => "gateway_all_children_process_existed",
}), gateway_name => Name
{noreply, State#state{status = stopped, }),
child_pids = [], {noreply, State#state{
gw_state = undefined}}; status = stopped,
child_pids = [],
gw_state = undefined
}};
RemainPids -> RemainPids ->
{noreply, State#state{child_pids = RemainPids}} {noreply, State#state{child_pids = RemainPids}}
end; end;
_ -> _ ->
?SLOG(error, #{ msg => "gateway_catch_a_unknown_process_exited" ?SLOG(error, #{
, child => Pid msg => "gateway_catch_a_unknown_process_exited",
, reason => Reason child => Pid,
, gateway_name => Name reason => Reason,
}), gateway_name => Name
}),
{noreply, State} {noreply, State}
end; end;
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(warning, #{ msg => "unexcepted_info" ?SLOG(warning, #{
, info => Info msg => "unexcepted_info",
}), info => Info
}),
{noreply, State}. {noreply, State}.
terminate(_Reason, State = #state{child_pids = Pids}) -> terminate(_Reason, State = #state{child_pids = Pids}) ->
@ -207,14 +216,16 @@ code_change(_OldVsn, State, _Extra) ->
detailed_gateway_info(State) -> detailed_gateway_info(State) ->
maps:filter( maps:filter(
fun(_, V) -> V =/= undefined end, fun(_, V) -> V =/= undefined end,
#{name => State#state.name, #{
config => State#state.config, name => State#state.name,
status => State#state.status, config => State#state.config,
created_at => State#state.created_at, status => State#state.status,
started_at => State#state.started_at, created_at => State#state.created_at,
stopped_at => State#state.stopped_at started_at => State#state.started_at,
}). stopped_at => State#state.stopped_at
}
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal funcs %% Internal funcs
@ -231,7 +242,7 @@ init_authn(GwName, Config) ->
try try
do_init_authn(Authns, []) do_init_authn(Authns, [])
catch catch
throw : Reason = {badauth, _} -> throw:Reason = {badauth, _} ->
do_deinit_authn(proplists:get_keys(Authns)), do_deinit_authn(proplists:get_keys(Authns)),
throw(Reason) throw(Reason)
end. end.
@ -250,11 +261,15 @@ do_init_authn([_BadConf | More], Names) ->
authns(GwName, Config) -> authns(GwName, Config) ->
Listeners = maps:to_list(maps:get(listeners, Config, #{})), Listeners = maps:to_list(maps:get(listeners, Config, #{})),
lists:append( lists:append(
[ [{emqx_gateway_utils:listener_chain(GwName, LisType, LisName), [
authn_conf(Opts)} [
|| {LisName, Opts} <- maps:to_list(LisNames) ] {emqx_gateway_utils:listener_chain(GwName, LisType, LisName), authn_conf(Opts)}
|| {LisType, LisNames} <- Listeners]) || {LisName, Opts} <- maps:to_list(LisNames)
++ [{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}]. ]
|| {LisType, LisNames} <- Listeners
]
) ++
[{emqx_gateway_utils:global_chain(GwName), authn_conf(Config)}].
authn_conf(Conf) -> authn_conf(Conf) ->
maps:get(authentication, Conf, #{enable => false}). maps:get(authentication, Conf, #{enable => false}).
@ -263,20 +278,23 @@ do_create_authn_chain(ChainName, AuthConf) ->
case ensure_chain(ChainName) of case ensure_chain(ChainName) of
ok -> ok ->
case emqx_authentication:create_authenticator(ChainName, AuthConf) of case emqx_authentication:create_authenticator(ChainName, AuthConf) of
{ok, _} -> ok; {ok, _} ->
ok;
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{ msg => "failed_to_create_authenticator" ?SLOG(error, #{
, chain_name => ChainName msg => "failed_to_create_authenticator",
, reason => Reason chain_name => ChainName,
, config => AuthConf reason => Reason,
}), config => AuthConf
}),
throw({badauth, Reason}) throw({badauth, Reason})
end; end;
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{ msg => "failed_to_create_authn_chanin" ?SLOG(error, #{
, chain_name => ChainName msg => "failed_to_create_authn_chanin",
, reason => Reason chain_name => ChainName,
}), reason => Reason
}),
throw({badauth, Reason}) throw({badauth, Reason})
end. end.
@ -291,22 +309,32 @@ ensure_chain(ChainName) ->
end. end.
do_deinit_authn(Names) -> do_deinit_authn(Names) ->
lists:foreach(fun(ChainName) -> lists:foreach(
case emqx_authentication:delete_chain(ChainName) of fun(ChainName) ->
ok -> ok; case emqx_authentication:delete_chain(ChainName) of
{error, {not_found, _}} -> ok; ok ->
{error, Reason} -> ok;
?SLOG(error, #{ msg => "failed_to_clean_authn_chain" {error, {not_found, _}} ->
, chain_name => ChainName ok;
, reason => Reason {error, Reason} ->
}) ?SLOG(error, #{
end msg => "failed_to_clean_authn_chain",
end, Names). chain_name => ChainName,
reason => Reason
})
end
end,
Names
).
do_update_one_by_one(NCfg, State = #state{ do_update_one_by_one(
name = GwName, NCfg,
config = OCfg, State = #state{
status = Status}) -> name = GwName,
config = OCfg,
status = Status
}
) ->
NEnable = maps:get(enable, NCfg, true), NEnable = maps:get(enable, NCfg, true),
OAuths = authns(GwName, OCfg), OAuths = authns(GwName, OCfg),
@ -319,14 +347,16 @@ do_update_one_by_one(NCfg, State = #state{
{stopped, false} -> {stopped, false} ->
{ok, State#state{config = NCfg}}; {ok, State#state{config = NCfg}};
{running, true} -> {running, true} ->
NState = case NAuths == OAuths of NState =
true -> State; case NAuths == OAuths of
false -> true ->
%% Reset Authentication first State;
_ = do_deinit_authn(State#state.authns), false ->
AuthnNames = init_authn(State#state.name, NCfg), %% Reset Authentication first
State#state{authns = AuthnNames} _ = do_deinit_authn(State#state.authns),
end, AuthnNames = init_authn(State#state.name, NCfg),
State#state{authns = AuthnNames}
end,
%% TODO: minimum impact update ??? %% TODO: minimum impact update ???
cb_gateway_update(NCfg, NState); cb_gateway_update(NCfg, NState);
{running, false} -> {running, false} ->
@ -338,26 +368,33 @@ do_update_one_by_one(NCfg, State = #state{
throw(nomatch) throw(nomatch)
end. end.
cb_gateway_unload(State = #state{name = GwName, cb_gateway_unload(
gw_state = GwState}) -> State = #state{
name = GwName,
gw_state = GwState
}
) ->
Gateway = detailed_gateway_info(State), Gateway = detailed_gateway_info(State),
try try
#{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName), #{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName),
CbMod:on_gateway_unload(Gateway, GwState), CbMod:on_gateway_unload(Gateway, GwState),
{ok, State#state{child_pids = [], {ok, State#state{
authns = [], child_pids = [],
status = stopped, authns = [],
gw_state = undefined, status = stopped,
started_at = undefined, gw_state = undefined,
stopped_at = erlang:system_time(millisecond)}} started_at = undefined,
stopped_at = erlang:system_time(millisecond)
}}
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
?SLOG(error, #{ msg => "unload_gateway_crashed" ?SLOG(error, #{
, gateway_name => GwName msg => "unload_gateway_crashed",
, inner_state => GwState gateway_name => GwName,
, reason => {Class, Reason} inner_state => GwState,
, stacktrace => Stk reason => {Class, Reason},
}), stacktrace => Stk
}),
{error, Reason} {error, Reason}
after after
_ = do_deinit_authn(State#state.authns) _ = 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 %% 2. Callback to Mod:on_gateway_load/2
%% %%
%% Notes: If failed, rollback %% Notes: If failed, rollback
cb_gateway_load(State = #state{name = GwName, cb_gateway_load(
config = Config, State = #state{
ctx = Ctx}) -> name = GwName,
config = Config,
ctx = Ctx
}
) ->
Gateway = detailed_gateway_info(State), Gateway = detailed_gateway_info(State),
try try
AuthnNames = init_authn(GwName, Config), AuthnNames = init_authn(GwName, Config),
@ -383,54 +423,62 @@ cb_gateway_load(State = #state{name = GwName,
{ok, ChildPidOrSpecs, GwState} -> {ok, ChildPidOrSpecs, GwState} ->
ChildPids = start_child_process(ChildPidOrSpecs), ChildPids = start_child_process(ChildPidOrSpecs),
{ok, State#state{ {ok, State#state{
ctx = NCtx, ctx = NCtx,
authns = AuthnNames, authns = AuthnNames,
status = running, status = running,
child_pids = ChildPids, child_pids = ChildPids,
gw_state = GwState, gw_state = GwState,
stopped_at = undefined, stopped_at = undefined,
started_at = erlang:system_time(millisecond) started_at = erlang:system_time(millisecond)
}} }}
end end
catch catch
Class : Reason1 : Stk -> Class:Reason1:Stk ->
?SLOG(error, #{ msg => "load_gateway_crashed" ?SLOG(error, #{
, gateway_name => GwName msg => "load_gateway_crashed",
, gateway => Gateway gateway_name => GwName,
, ctx => Ctx gateway => Gateway,
, reason => {Class, Reason1} ctx => Ctx,
, stacktrace => Stk reason => {Class, Reason1},
}), stacktrace => Stk
}),
{error, Reason1} {error, Reason1}
end. end.
cb_gateway_update(Config, cb_gateway_update(
State = #state{name = GwName, Config,
gw_state = GwState}) -> State = #state{
name = GwName,
gw_state = GwState
}
) ->
try try
#{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName), #{cbkmod := CbMod} = emqx_gateway_registry:lookup(GwName),
case CbMod:on_gateway_update(Config, detailed_gateway_info(State), GwState) of case CbMod:on_gateway_update(Config, detailed_gateway_info(State), GwState) of
{error, Reason} -> {error, Reason}; {error, Reason} ->
{error, Reason};
{ok, ChildPidOrSpecs, NGwState} -> {ok, ChildPidOrSpecs, NGwState} ->
ChildPids = start_child_process(ChildPidOrSpecs), ChildPids = start_child_process(ChildPidOrSpecs),
{ok, State#state{ {ok, State#state{
config = Config, config = Config,
child_pids = ChildPids, child_pids = ChildPids,
gw_state = NGwState gw_state = NGwState
}} }}
end end
catch catch
Class : Reason1 : Stk -> Class:Reason1:Stk ->
?SLOG(error, #{ msg => "update_gateway_crashed" ?SLOG(error, #{
, gateway_name => GwName msg => "update_gateway_crashed",
, new_config => Config gateway_name => GwName,
, reason => {Class, Reason1} new_config => Config,
, stacktrace => Stk reason => {Class, Reason1},
}), stacktrace => Stk
}),
{error, Reason1} {error, Reason1}
end. end.
start_child_process([]) -> []; start_child_process([]) ->
[];
start_child_process([Indictor | _] = ChildPidOrSpecs) -> start_child_process([Indictor | _] = ChildPidOrSpecs) ->
case erlang:is_pid(Indictor) of case erlang:is_pid(Indictor) of
true -> true ->
@ -441,7 +489,6 @@ start_child_process([Indictor | _] = ChildPidOrSpecs) ->
do_start_child_process(ChildSpecs) when is_list(ChildSpecs) -> do_start_child_process(ChildSpecs) when is_list(ChildSpecs) ->
lists:map(fun do_start_child_process/1, ChildSpecs); lists:map(fun do_start_child_process/1, ChildSpecs);
do_start_child_process(_ChildSpec = #{start := {M, F, A}}) -> do_start_child_process(_ChildSpec = #{start := {M, F, A}}) ->
case erlang:apply(M, F, A) of case erlang:apply(M, F, A) of
{ok, Pid} -> {ok, Pid} ->

View File

@ -23,22 +23,24 @@
%% APIs %% APIs
-export([start_link/1]). -export([start_link/1]).
-export([ inc/2 -export([
, inc/3 inc/2,
, dec/2 inc/3,
, dec/3 dec/2,
]). dec/3
]).
-export([lookup/1]). -export([lookup/1]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-export([tabname/1]). -export([tabname/1]).
@ -68,9 +70,9 @@ dec(GwName, Name) ->
dec(GwName, Name, Oct) -> dec(GwName, Name, Oct) ->
inc(GwName, Name, -Oct). inc(GwName, Name, -Oct).
-spec lookup(gateway_name()) -spec lookup(gateway_name()) ->
-> undefined undefined
| [{Name :: atom(), integer()}]. | [{Name :: atom(), integer()}].
lookup(GwName) -> lookup(GwName) ->
Tab = emqx_gateway_metrics:tabname(GwName), Tab = emqx_gateway_metrics:tabname(GwName),
case ets:info(Tab) of case ets:info(Tab) of
@ -87,7 +89,7 @@ tabname(GwName) ->
init([GwName]) -> init([GwName]) ->
TabOpts = [public, {write_concurrency, true}], TabOpts = [public, {write_concurrency, true}],
ok = emqx_tables:new(tabname(GwName), [set|TabOpts]), ok = emqx_tables:new(tabname(GwName), [set | TabOpts]),
{ok, #state{}}. {ok, #state{}}.
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->

View File

@ -22,35 +22,38 @@
-behaviour(gen_server). -behaviour(gen_server).
%% APIs %% APIs
-export([ reg/2 -export([
, unreg/1 reg/2,
, list/0 unreg/1,
, lookup/1 list/0,
]). lookup/1
]).
%% APIs %% APIs
-export([start_link/0]). -export([start_link/0]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-record(state, { -record(state, {
reged = #{} :: #{ gateway_name() => descriptor() } reged = #{} :: #{gateway_name() => descriptor()}
}). }).
-type registry_options() :: [registry_option()]. -type registry_options() :: [registry_option()].
-type registry_option() :: {cbkmod, atom()}. -type registry_option() :: {cbkmod, atom()}.
-type descriptor() :: #{ cbkmod := atom() -type descriptor() :: #{
, rgopts := registry_options() cbkmod := atom(),
}. rgopts := registry_options()
}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
@ -63,14 +66,15 @@ start_link() ->
%% Mgmt %% Mgmt
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec reg(gateway_name(), registry_options()) -spec reg(gateway_name(), registry_options()) ->
-> ok ok
| {error, any()}. | {error, any()}.
reg(Name, RgOpts) -> reg(Name, RgOpts) ->
CbMod = proplists:get_value(cbkmod, RgOpts, Name), CbMod = proplists:get_value(cbkmod, RgOpts, Name),
Dscrptr = #{ cbkmod => CbMod Dscrptr = #{
, rgopts => RgOpts cbkmod => CbMod,
}, rgopts => RgOpts
},
call({reg, Name, Dscrptr}). call({reg, Name, Dscrptr}).
-spec unreg(gateway_name()) -> ok | {error, any()}. -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} {reply, {error, already_existed}, State}
end; end;
handle_call({unreg, Name}, _From, State = #state{reged = Gateways}) -> handle_call({unreg, Name}, _From, State = #state{reged = Gateways}) ->
case maps:get(Name, Gateways, undefined) of case maps:get(Name, Gateways, undefined) of
undefined -> undefined ->
@ -119,14 +122,11 @@ handle_call({unreg, Name}, _From, State = #state{reged = Gateways}) ->
_ = emqx_gateway_sup:unload_gateway(Name), _ = emqx_gateway_sup:unload_gateway(Name),
{reply, ok, State#state{reged = maps:remove(Name, Gateways)}} {reply, ok, State#state{reged = maps:remove(Name, Gateways)}}
end; end;
handle_call(all, _From, State = #state{reged = Gateways}) -> handle_call(all, _From, State = #state{reged = Gateways}) ->
{reply, maps:to_list(Gateways), State}; {reply, maps:to_list(Gateways), State};
handle_call({lookup, Name}, _From, State = #state{reged = Gateways}) -> handle_call({lookup, Name}, _From, State = #state{reged = Gateways}) ->
Reply = maps:get(Name, Gateways, undefined), Reply = maps:get(Name, Gateways, undefined),
{reply, Reply, State}; {reply, Reply, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
logger:error("Unexpected call: ~0p", [Req]), logger:error("Unexpected call: ~0p", [Req]),
{reply, ok, State}. {reply, ok, State}.

File diff suppressed because it is too large Load Diff

View File

@ -23,14 +23,15 @@
-export([start_link/0]). -export([start_link/0]).
%% Gateway APIs %% Gateway APIs
-export([ load_gateway/1 -export([
, unload_gateway/1 load_gateway/1,
, lookup_gateway/1 unload_gateway/1,
, update_gateway/2 lookup_gateway/1,
, start_gateway_insta/1 update_gateway/2,
, stop_gateway_insta/1 start_gateway_insta/1,
, list_gateway_insta/0 stop_gateway_insta/1,
]). list_gateway_insta/0
]).
%% supervisor callbacks %% supervisor callbacks
-export([init/1]). -export([init/1]).
@ -42,23 +43,24 @@
start_link() -> start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []). supervisor:start_link({local, ?MODULE}, ?MODULE, []).
-spec load_gateway(gateway()) -> {ok, pid()} | {error, any()}. -spec load_gateway(gateway()) -> {ok, pid()} | {error, any()}.
load_gateway(Gateway = #{name := GwName}) -> load_gateway(Gateway = #{name := GwName}) ->
case emqx_gateway_registry:lookup(GwName) of case emqx_gateway_registry:lookup(GwName) of
undefined -> {error, {unknown_gateway_name, GwName}}; undefined ->
{error, {unknown_gateway_name, GwName}};
GwDscrptr -> GwDscrptr ->
{ok, GwSup} = ensure_gateway_suptree_ready(GwName), {ok, GwSup} = ensure_gateway_suptree_ready(GwName),
emqx_gateway_gw_sup:create_insta(GwSup, Gateway, GwDscrptr) emqx_gateway_gw_sup:create_insta(GwSup, Gateway, GwDscrptr)
end. end.
-spec unload_gateway(gateway_name()) -spec unload_gateway(gateway_name()) ->
-> ok ok
| {error, not_found} | {error, not_found}
| {error, any()}. | {error, any()}.
unload_gateway(GwName) -> unload_gateway(GwName) ->
case lists:keyfind(GwName, 1, supervisor:which_children(?MODULE)) of case lists:keyfind(GwName, 1, supervisor:which_children(?MODULE)) of
false -> {error, not_found}; false ->
{error, not_found};
{_Id, Pid, _Type, _Mods} -> {_Id, Pid, _Type, _Mods} ->
_ = emqx_gateway_gw_sup:remove_insta(Pid, GwName), _ = emqx_gateway_gw_sup:remove_insta(Pid, GwName),
_ = supervisor:terminate_child(?MODULE, GwName), _ = supervisor:terminate_child(?MODULE, GwName),
@ -75,21 +77,23 @@ lookup_gateway(GwName) ->
undefined undefined
end. end.
-spec update_gateway(gateway_name(), emqx_config:config()) -spec update_gateway(gateway_name(), emqx_config:config()) ->
-> ok ok
| {error, any()}. | {error, any()}.
update_gateway(GwName, Config) -> update_gateway(GwName, Config) ->
case emqx_gateway_utils:find_sup_child(?MODULE, GwName) of case emqx_gateway_utils:find_sup_child(?MODULE, GwName) of
{ok, GwSup} -> {ok, GwSup} ->
emqx_gateway_gw_sup:update_insta(GwSup, GwName, Config); emqx_gateway_gw_sup:update_insta(GwSup, GwName, Config);
_ -> {error, not_found} _ ->
{error, not_found}
end. end.
start_gateway_insta(GwName) -> start_gateway_insta(GwName) ->
case search_gateway_insta_proc(GwName) of case search_gateway_insta_proc(GwName) of
{ok, {GwSup, _}} -> {ok, {GwSup, _}} ->
emqx_gateway_gw_sup:start_insta(GwSup, GwName); emqx_gateway_gw_sup:start_insta(GwSup, GwName);
_ -> {error, not_found} _ ->
{error, not_found}
end. end.
-spec stop_gateway_insta(gateway_name()) -> ok | {error, any()}. -spec stop_gateway_insta(gateway_name()) -> ok | {error, any()}.
@ -97,15 +101,20 @@ stop_gateway_insta(GwName) ->
case search_gateway_insta_proc(GwName) of case search_gateway_insta_proc(GwName) of
{ok, {GwSup, _}} -> {ok, {GwSup, _}} ->
emqx_gateway_gw_sup:stop_insta(GwSup, GwName); emqx_gateway_gw_sup:stop_insta(GwSup, GwName);
_ -> {error, not_found} _ ->
{error, not_found}
end. end.
-spec list_gateway_insta() -> [gateway()]. -spec list_gateway_insta() -> [gateway()].
list_gateway_insta() -> list_gateway_insta() ->
lists:append(lists:map( lists:append(
fun(SupId) -> lists:map(
emqx_gateway_gw_sup:list_insta(SupId) fun(SupId) ->
end, list_started_gateway())). emqx_gateway_gw_sup:list_insta(SupId)
end,
list_started_gateway()
)
).
-spec list_started_gateway() -> [gateway_name()]. -spec list_started_gateway() -> [gateway_name()].
list_started_gateway() -> list_started_gateway() ->
@ -114,12 +123,12 @@ list_started_gateway() ->
%% Supervisor callback %% Supervisor callback
init([]) -> init([]) ->
SupFlags = #{ strategy => one_for_one SupFlags = #{
, intensity => 10 strategy => one_for_one,
, period => 60 intensity => 10,
}, period => 60
ChildSpecs = [ emqx_gateway_utils:childspec(worker, emqx_gateway_registry) },
], ChildSpecs = [emqx_gateway_utils:childspec(worker, emqx_gateway_registry)],
{ok, {SupFlags, ChildSpecs}}. {ok, {SupFlags, ChildSpecs}}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -130,14 +139,14 @@ ensure_gateway_suptree_ready(GwName) ->
case lists:keyfind(GwName, 1, supervisor:which_children(?MODULE)) of case lists:keyfind(GwName, 1, supervisor:which_children(?MODULE)) of
false -> false ->
ChildSpec = emqx_gateway_utils:childspec( ChildSpec = emqx_gateway_utils:childspec(
GwName, GwName,
supervisor, supervisor,
emqx_gateway_gw_sup, emqx_gateway_gw_sup,
[GwName] [GwName]
), ),
emqx_gateway_utils:supervisor_ret( emqx_gateway_utils:supervisor_ret(
supervisor:start_child(?MODULE, ChildSpec) supervisor:start_child(?MODULE, ChildSpec)
); );
{_Id, Pid, _Type, _Mods} -> {_Id, Pid, _Type, _Mods} ->
{ok, Pid} {ok, Pid}
end. end.
@ -147,24 +156,27 @@ search_gateway_insta_proc(InstaId) ->
search_gateway_insta_proc(_InstaId, []) -> search_gateway_insta_proc(_InstaId, []) ->
{error, not_found}; {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 case emqx_gateway_utils:find_sup_child(SupPid, InstaId) of
{ok, InstaPid} -> {ok, {SupPid, InstaPid}}; {ok, InstaPid} -> {ok, {SupPid, InstaPid}};
_ -> _ -> search_gateway_insta_proc(InstaId, More)
search_gateway_insta_proc(InstaId, More)
end. end.
started_gateway() -> started_gateway() ->
lists:filtermap( lists:filtermap(
fun({Id, _, _, _}) -> fun({Id, _, _, _}) ->
is_a_gateway_id(Id) andalso {true, Id} is_a_gateway_id(Id) andalso {true, Id}
end, supervisor:which_children(?MODULE)). end,
supervisor:which_children(?MODULE)
).
started_gateway_pid() -> started_gateway_pid() ->
lists:filtermap( lists:filtermap(
fun({Id, Pid, _, _}) -> fun({Id, Pid, _, _}) ->
is_a_gateway_id(Id) andalso {true, Pid} is_a_gateway_id(Id) andalso {true, Pid}
end, supervisor:which_children(?MODULE)). end,
supervisor:which_children(?MODULE)
).
is_a_gateway_id(Id) -> is_a_gateway_id(Id) ->
Id /= emqx_gateway_registry. Id /= emqx_gateway_registry.

View File

@ -20,81 +20,87 @@
-include("emqx_gateway.hrl"). -include("emqx_gateway.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-export([ childspec/2 -export([
, childspec/3 childspec/2,
, childspec/4 childspec/3,
, supervisor_ret/1 childspec/4,
, find_sup_child/2 supervisor_ret/1,
]). find_sup_child/2
]).
-export([ start_listeners/4 -export([
, start_listener/4 start_listeners/4,
, stop_listeners/2 start_listener/4,
, stop_listener/2 stop_listeners/2,
]). stop_listener/2
]).
-export([ apply/2 -export([
, format_listenon/1 apply/2,
, parse_listenon/1 format_listenon/1,
, unix_ts_to_rfc3339/1 parse_listenon/1,
, unix_ts_to_rfc3339/2 unix_ts_to_rfc3339/1,
, listener_id/3 unix_ts_to_rfc3339/2,
, parse_listener_id/1 listener_id/3,
, is_running/2 parse_listener_id/1,
, global_chain/1 is_running/2,
, listener_chain/3 global_chain/1,
]). listener_chain/3
]).
-export([ stringfy/1 -export([stringfy/1]).
]).
-export([ normalize_config/1 -export([normalize_config/1]).
]).
%% Common Envs %% Common Envs
-export([ active_n/1 -export([
, ratelimit/1 active_n/1,
, frame_options/1 ratelimit/1,
, init_gc_state/1 frame_options/1,
, stats_timer/1 init_gc_state/1,
, idle_timeout/1 stats_timer/1,
, oom_policy/1 idle_timeout/1,
]). oom_policy/1
]).
-export([ default_tcp_options/0 -export([
, default_udp_options/0 default_tcp_options/0,
, default_subopts/0 default_udp_options/0,
]). default_subopts/0
]).
-define(ACTIVE_N, 100). -define(ACTIVE_N, 100).
-define(DEFAULT_IDLE_TIMEOUT, 30000). -define(DEFAULT_IDLE_TIMEOUT, 30000).
-define(DEFAULT_GC_OPTS, #{count => 1000, bytes => 1024*1024}). -define(DEFAULT_GC_OPTS, #{count => 1000, bytes => 1024 * 1024}).
-define(DEFAULT_OOM_POLICY, #{max_heap_size => 4194304, -define(DEFAULT_OOM_POLICY, #{
max_message_queue_len => 32000}). max_heap_size => 4194304,
max_message_queue_len => 32000
}).
-elvis([{elvis_style, god_modules, disable}]). -elvis([{elvis_style, god_modules, disable}]).
-spec childspec(supervisor:worker(), Mod :: atom()) -spec childspec(supervisor:worker(), Mod :: atom()) ->
-> supervisor:child_spec(). supervisor:child_spec().
childspec(Type, Mod) -> childspec(Type, Mod) ->
childspec(Mod, Type, Mod, []). childspec(Mod, Type, Mod, []).
-spec childspec(supervisor:worker(), Mod :: atom(), Args :: list()) -spec childspec(supervisor:worker(), Mod :: atom(), Args :: list()) ->
-> supervisor:child_spec(). supervisor:child_spec().
childspec(Type, Mod, Args) -> childspec(Type, Mod, Args) ->
childspec(Mod, Type, Mod, Args). childspec(Mod, Type, Mod, Args).
-spec childspec(atom(), supervisor:worker(), Mod :: atom(), Args :: list()) -spec childspec(atom(), supervisor:worker(), Mod :: atom(), Args :: list()) ->
-> supervisor:child_spec(). supervisor:child_spec().
childspec(Id, Type, Mod, Args) -> childspec(Id, Type, Mod, Args) ->
#{ id => Id #{
, start => {Mod, start_link, Args} id => Id,
, type => Type start => {Mod, start_link, Args},
}. type => Type
}.
-spec supervisor_ret(supervisor:startchild_ret()) -spec supervisor_ret(supervisor:startchild_ret()) ->
-> {ok, pid()} {ok, pid()}
| {error, supervisor:startchild_err()}. | {error, supervisor:startchild_err()}.
supervisor_ret({ok, Pid, _Info}) -> supervisor_ret({ok, Pid, _Info}) ->
{ok, Pid}; {ok, Pid};
supervisor_ret({error, {Reason, Child}}) -> supervisor_ret({error, {Reason, Child}}) ->
@ -105,9 +111,9 @@ supervisor_ret({error, {Reason, Child}}) ->
supervisor_ret(Ret) -> supervisor_ret(Ret) ->
Ret. Ret.
-spec find_sup_child(Sup :: pid() | atom(), ChildId :: supervisor:child_id()) -spec find_sup_child(Sup :: pid() | atom(), ChildId :: supervisor:child_id()) ->
-> false false
| {ok, pid()}. | {ok, pid()}.
find_sup_child(Sup, ChildId) -> find_sup_child(Sup, ChildId) ->
case lists:keyfind(ChildId, 1, supervisor:which_children(Sup)) of case lists:keyfind(ChildId, 1, supervisor:which_children(Sup)) of
false -> false; false -> false;
@ -115,13 +121,16 @@ find_sup_child(Sup, ChildId) ->
end. end.
%% @doc start listeners. close all listeners if someone failed %% @doc start listeners. close all listeners if someone failed
-spec start_listeners(Listeners :: list(), -spec start_listeners(
GwName :: atom(), Listeners :: list(),
Ctx :: map(), GwName :: atom(),
ModCfg) Ctx :: map(),
-> {ok, [pid()]} ModCfg
| {error, term()} ) ->
when ModCfg :: #{frame_mod := atom(), chann_mod := atom()}. {ok, [pid()]}
| {error, term()}
when
ModCfg :: #{frame_mod := atom(), chann_mod := atom()}.
start_listeners(Listeners, GwName, Ctx, ModCfg) -> start_listeners(Listeners, GwName, Ctx, ModCfg) ->
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}}], NAcc = Acc ++ [{listener, {{ListenerId, ListenOn}, Pid}}],
start_listeners(Ls, GwName, Ctx, ModCfg, NAcc); start_listeners(Ls, GwName, Ctx, ModCfg, NAcc);
{error, Reason} -> {error, Reason} ->
lists:foreach(fun({listener, {{ListenerId, ListenOn}, _}}) -> lists:foreach(
esockd:close({ListenerId, ListenOn}) fun({listener, {{ListenerId, ListenOn}, _}}) ->
end, Acc), esockd:close({ListenerId, ListenOn})
end,
Acc
),
{error, {Reason, L}} {error, {Reason, L}}
end. end.
-spec start_listener(GwName :: atom(), -spec start_listener(
Ctx :: emqx_gateway_ctx:context(), GwName :: atom(),
Listener :: tuple(), Ctx :: emqx_gateway_ctx:context(),
ModCfg :: map()) Listener :: tuple(),
-> {ok, {ListenerId :: atom(), esockd:listen_on(), pid()}} ModCfg :: map()
| {error, term()}. ) ->
start_listener(GwName, Ctx, {ok, {ListenerId :: atom(), esockd:listen_on(), pid()}}
{Type, LisName, ListenOn, SocketOpts, Cfg}, ModCfg) -> | {error, term()}.
start_listener(
GwName,
Ctx,
{Type, LisName, ListenOn, SocketOpts, Cfg},
ModCfg
) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn), 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), NCfg = maps:merge(Cfg, ModCfg),
case start_listener(GwName, Ctx, Type, case
LisName, ListenOn, SocketOpts, NCfg) of start_listener(
GwName,
Ctx,
Type,
LisName,
ListenOn,
SocketOpts,
NCfg
)
of
{ok, Pid} -> {ok, Pid} ->
console_print("Gateway ~ts:~ts:~ts on ~ts started.~n", console_print(
[GwName, Type, LisName, ListenOnStr]), "Gateway ~ts:~ts:~ts on ~ts started.~n",
[GwName, Type, LisName, ListenOnStr]
),
{ok, {ListenerId, ListenOn, Pid}}; {ok, {ListenerId, ListenOn, Pid}};
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG(
[GwName, Type, LisName, ListenOnStr, Reason]), "Failed to start gateway ~ts:~ts:~ts on ~ts: ~0p~n",
[GwName, Type, LisName, ListenOnStr, Reason]
),
emqx_gateway_utils:supervisor_ret({error, Reason}) emqx_gateway_utils:supervisor_ret({error, Reason})
end. end.
start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) -> start_listener(GwName, Ctx, Type, LisName, ListenOn, SocketOpts, Cfg) ->
Name = emqx_gateway_utils:listener_id(GwName, Type, LisName), Name = emqx_gateway_utils:listener_id(GwName, Type, LisName),
NCfg = Cfg#{ ctx => Ctx NCfg = Cfg#{
, listener => {GwName, Type, LisName} ctx => Ctx,
}, listener => {GwName, Type, LisName}
},
NSocketOpts = merge_default(Type, SocketOpts), NSocketOpts = merge_default(Type, SocketOpts),
MFA = {emqx_gateway_conn, start_link, [NCfg]}, MFA = {emqx_gateway_conn, start_link, [NCfg]},
do_start_listener(Type, Name, ListenOn, NSocketOpts, MFA). do_start_listener(Type, Name, ListenOn, NSocketOpts, MFA).
merge_default(Udp, Options) -> merge_default(Udp, Options) ->
{Key, Default} = case Udp of {Key, Default} =
udp -> case Udp of
{udp_options, default_udp_options()}; udp ->
dtls -> {udp_options, default_udp_options()};
{udp_options, default_udp_options()}; dtls ->
tcp -> {udp_options, default_udp_options()};
{tcp_options, default_tcp_options()}; tcp ->
ssl -> {tcp_options, default_tcp_options()};
{tcp_options, default_tcp_options()} ssl ->
end, {tcp_options, default_tcp_options()}
end,
case lists:keytake(Key, 1, Options) of case lists:keytake(Key, 1, Options) of
{value, {Key, TcpOpts}, Options1} -> {value, {Key, TcpOpts}, Options1} ->
[{Key, emqx_misc:merge_opts(Default, TcpOpts)} [
| Options1]; {Key, emqx_misc:merge_opts(Default, TcpOpts)}
| Options1
];
false -> false ->
[{Key, Default} | Options] [{Key, Default} | Options]
end. end.
do_start_listener(Type, Name, ListenOn, SocketOpts, MFA) do_start_listener(Type, Name, ListenOn, SocketOpts, MFA) when
when Type == tcp; Type == tcp;
Type == ssl -> Type == ssl
->
esockd:open(Name, ListenOn, SocketOpts, MFA); esockd:open(Name, ListenOn, SocketOpts, MFA);
do_start_listener(udp, Name, ListenOn, SocketOpts, MFA) -> do_start_listener(udp, Name, ListenOn, SocketOpts, MFA) ->
esockd:open_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), ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of case StopRet of
ok -> ok ->
console_print("Gateway ~ts:~ts:~ts on ~ts stopped.~n", console_print(
[GwName, Type, LisName, ListenOnStr]); "Gateway ~ts:~ts:~ts on ~ts stopped.~n",
[GwName, Type, LisName, ListenOnStr]
);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n", ?ELOG(
[GwName, Type, LisName, ListenOnStr, Reason]) "Failed to stop gateway ~ts:~ts:~ts on ~ts: ~0p~n",
[GwName, Type, LisName, ListenOnStr, Reason]
)
end, end,
StopRet. StopRet.
@ -228,17 +268,23 @@ console_print(Fmt, Args) -> ?ULOG(Fmt, Args).
console_print(_Fmt, _Args) -> ok. console_print(_Fmt, _Args) -> ok.
-endif. -endif.
apply({M, F, A}, A2) when is_atom(M), apply({M, F, A}, A2) when
is_atom(M), is_atom(M),
is_list(A), is_atom(M),
is_list(A2) -> is_list(A),
is_list(A2)
->
erlang:apply(M, F, A ++ A2); erlang:apply(M, F, A ++ A2);
apply({F, A}, A2) when is_function(F), apply({F, A}, A2) when
is_list(A), is_function(F),
is_list(A2) -> is_list(A),
is_list(A2)
->
erlang:apply(F, A ++ A2); erlang:apply(F, A ++ A2);
apply(F, A2) when is_function(F), apply(F, A2) when
is_list(A2) -> is_function(F),
is_list(A2)
->
erlang:apply(F, A2). erlang:apply(F, A2).
format_listenon(Port) when is_integer(Port) -> 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(Str) when is_binary(Str) ->
parse_listenon(binary_to_list(Str)); parse_listenon(binary_to_list(Str));
parse_listenon(Str) when is_list(Str) -> parse_listenon(Str) when is_list(Str) ->
try list_to_integer(Str) try
catch _ : _ -> list_to_integer(Str)
case emqx_schema:to_ip_port(Str) of catch
{ok, R} -> R; _:_ ->
{error, _} -> case emqx_schema:to_ip_port(Str) of
error({invalid_listenon_name, Str}) {ok, R} -> R;
end {error, _} -> error({invalid_listenon_name, Str})
end
end. end.
listener_id(GwName, Type, LisName) -> listener_id(GwName, Type, LisName) ->
binary_to_atom( binary_to_atom(
<<(bin(GwName))/binary, ":", <<(bin(GwName))/binary, ":", (bin(Type))/binary, ":", (bin(LisName))/binary>>
(bin(Type))/binary, ":", ).
(bin(LisName))/binary
>>).
parse_listener_id(Id) when is_atom(Id) -> parse_listener_id(Id) when is_atom(Id) ->
parse_listener_id(atom_to_binary(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] = binary:split(bin(Id), <<":">>, [global]),
{GwName, Type, Name} {GwName, Type, Name}
catch catch
_ : _ -> error({invalid_listener_id, Id}) _:_ -> error({invalid_listener_id, Id})
end. end.
is_running(ListenerId, #{<<"bind">> := ListenOn0}) -> is_running(ListenerId, #{<<"bind">> := ListenOn0}) ->
ListenOn = emqx_gateway_utils:parse_listenon(ListenOn0), ListenOn = emqx_gateway_utils:parse_listenon(ListenOn0),
try esockd:listener({ListenerId, ListenOn}) of try esockd:listener({ListenerId, ListenOn}) of
Pid when is_pid(Pid)-> Pid when is_pid(Pid) ->
true true
catch _:_ -> catch
false _:_ ->
false
end. end.
%% same with emqx_authentication:global_chain/1 %% 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); lists:foldl(fun(K, Acc) -> unix_ts_to_rfc3339(K, Acc) end, Map, Keys);
unix_ts_to_rfc3339(Key, Map) -> unix_ts_to_rfc3339(Key, Map) ->
case maps:get(Key, Map, undefined) of case maps:get(Key, Map, undefined) of
undefined -> Map; undefined ->
Map;
Ts -> Ts ->
Map#{Key => Map#{
emqx_rule_funcs:unix_ts_to_rfc3339(Ts, <<"millisecond">>)} Key =>
emqx_rule_funcs:unix_ts_to_rfc3339(Ts, <<"millisecond">>)
}
end. end.
unix_ts_to_rfc3339(Ts) -> unix_ts_to_rfc3339(Ts) ->
@ -330,60 +379,104 @@ stringfy(T) when is_list(T); is_binary(T) ->
stringfy(T) -> stringfy(T) ->
iolist_to_binary(io_lib:format("~0p", [T])). iolist_to_binary(io_lib:format("~0p", [T])).
-spec normalize_config(emqx_config:config()) -spec normalize_config(emqx_config:config()) ->
-> list({ Type :: udp | tcp | ssl | dtls list({
, Name :: atom() Type :: udp | tcp | ssl | dtls,
, ListenOn :: esockd:listen_on() Name :: atom(),
, SocketOpts :: esockd:option() ListenOn :: esockd:listen_on(),
, Cfg :: map() SocketOpts :: esockd:option(),
}). Cfg :: map()
}).
normalize_config(RawConf) -> normalize_config(RawConf) ->
LisMap = maps:get(listeners, RawConf, #{}), LisMap = maps:get(listeners, RawConf, #{}),
Cfg0 = maps:without([listeners], RawConf), Cfg0 = maps:without([listeners], RawConf),
lists:append(maps:fold(fun(Type, Liss, AccIn1) -> lists:append(
Listeners = maps:fold(
maps:fold(fun(Name, Confs, AccIn2) -> fun(Type, Liss, AccIn1) ->
ListenOn = maps:get(bind, Confs), Listeners =
SocketOpts = esockd_opts(Type, Confs), maps:fold(
RemainCfgs = maps:without( fun(Name, Confs, AccIn2) ->
[bind, tcp, ssl, udp, dtls] ListenOn = maps:get(bind, Confs),
++ proplists:get_keys(SocketOpts), Confs), SocketOpts = esockd_opts(Type, Confs),
Cfg = maps:merge(Cfg0, RemainCfgs), RemainCfgs = maps:without(
[{Type, Name, ListenOn, SocketOpts, Cfg} | AccIn2] [bind, tcp, ssl, udp, dtls] ++
end, [], Liss), proplists:get_keys(SocketOpts),
[Listeners | AccIn1] Confs
end, [], LisMap)). ),
Cfg = maps:merge(Cfg0, RemainCfgs),
[{Type, Name, ListenOn, SocketOpts, Cfg} | AccIn2]
end,
[],
Liss
),
[Listeners | AccIn1]
end,
[],
LisMap
)
).
esockd_opts(Type, Opts0) -> esockd_opts(Type, Opts0) ->
Opts1 = maps:with([acceptors, max_connections, max_conn_rate, Opts1 = maps:with(
proxy_protocol, proxy_protocol_timeout], Opts0), [
acceptors,
max_connections,
max_conn_rate,
proxy_protocol,
proxy_protocol_timeout
],
Opts0
),
Opts2 = Opts1#{access_rules => esockd_access_rules(maps:get(access_rules, Opts0, []))}, Opts2 = Opts1#{access_rules => esockd_access_rules(maps:get(access_rules, Opts0, []))},
maps:to_list(case Type of maps:to_list(
tcp -> Opts2#{tcp_options => sock_opts(tcp, Opts0)}; case Type of
ssl -> Opts2#{tcp_options => sock_opts(tcp, Opts0), tcp ->
ssl_options => ssl_opts(ssl, Opts0)}; Opts2#{tcp_options => sock_opts(tcp, Opts0)};
udp -> Opts2#{udp_options => sock_opts(udp, Opts0)}; ssl ->
dtls -> Opts2#{udp_options => sock_opts(udp, Opts0), Opts2#{
dtls_options => ssl_opts(dtls, Opts0)} tcp_options => sock_opts(tcp, Opts0),
end). 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) -> esockd_access_rules(StrRules) ->
Access = fun(S) -> Access = fun(S) ->
[A, CIDR] = string:tokens(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, end,
[Access(R) || R <- StrRules]. [Access(R) || R <- StrRules].
ssl_opts(Name, Opts) -> ssl_opts(Name, Opts) ->
maps:to_list( maps:to_list(
emqx_tls_lib:drop_tls13_for_old_otp( emqx_tls_lib:drop_tls13_for_old_otp(
maps:without([enable], maps:without(
maps:get(Name, Opts, #{})))). [enable],
maps:get(Name, Opts, #{})
)
)
).
sock_opts(Name, Opts) -> sock_opts(Name, Opts) ->
maps:to_list( maps:to_list(
maps:without([active_n], maps:without(
maps:get(Name, Opts, #{}))). [active_n],
maps:get(Name, Opts, #{})
)
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Envs %% Envs
@ -417,7 +510,10 @@ oom_policy(Options) ->
-spec stats_timer(map()) -> undefined | disabled. -spec stats_timer(map()) -> undefined | disabled.
stats_timer(Options) -> 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(). -spec enable_stats(map()) -> boolean().
enable_stats(Options) -> enable_stats(Options) ->
@ -427,16 +523,26 @@ enable_stats(Options) ->
%% Envs2 %% Envs2
default_tcp_options() -> default_tcp_options() ->
[binary, {packet, raw}, {reuseaddr, true}, [
{nodelay, true}, {backlog, 512}]. binary,
{packet, raw},
{reuseaddr, true},
{nodelay, true},
{backlog, 512}
].
default_udp_options() -> default_udp_options() ->
[binary]. [binary].
default_subopts() -> default_subopts() ->
#{rh => 1, %% Retain Handling %% Retain Handling
rap => 0, %% Retain as Publish #{
nl => 0, %% No Local rh => 1,
qos => 0, %% QoS %% Retain as Publish
is_new => true rap => 0,
}. %% No Local
nl => 0,
%% QoS
qos => 0,
is_new => true
}.

View File

@ -21,63 +21,65 @@
-include_lib("emqx/include/types.hrl"). -include_lib("emqx/include/types.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-export([
info/1,
info/2,
stats/1
]).
-export([ info/1 -export([
, info/2 init/2,
, stats/1 handle_in/2,
]). handle_deliver/2,
handle_timeout/3,
-export([ init/2 handle_call/3,
, handle_in/2 handle_cast/2,
, handle_deliver/2 handle_info/2,
, handle_timeout/3 terminate/2
, handle_call/3 ]).
, handle_cast/2
, handle_info/2
, terminate/2
]).
-export_type([channel/0]). -export_type([channel/0]).
-record(channel, { -record(channel, {
%% Context %% Context
ctx :: emqx_gateway_ctx:context(), ctx :: emqx_gateway_ctx:context(),
%% gRPC channel options %% gRPC channel options
gcli :: map(), gcli :: map(),
%% Conn info %% Conn info
conninfo :: emqx_types:conninfo(), conninfo :: emqx_types:conninfo(),
%% Client info from `register` function %% Client info from `register` function
clientinfo :: maybe(map()), clientinfo :: maybe(map()),
%% Connection state %% Connection state
conn_state :: conn_state(), conn_state :: conn_state(),
%% Subscription %% Subscription
subscriptions = #{}, subscriptions = #{},
%% Request queue %% Request queue
rqueue = queue:new(), rqueue = queue:new(),
%% Inflight function name %% Inflight function name
inflight = undefined, inflight = undefined,
%% Keepalive %% Keepalive
keepalive :: maybe(emqx_keepalive:keepalive()), keepalive :: maybe(emqx_keepalive:keepalive()),
%% Timers %% Timers
timers :: #{atom() => disabled | maybe(reference())}, timers :: #{atom() => disabled | maybe(reference())},
%% Closed reason %% Closed reason
closed_reason = undefined 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()} -type reply() ::
| {outgoing, [binary()]} {outgoing, binary()}
| {close, Reason :: atom()}). | {outgoing, [binary()]}
| {close, Reason :: atom()}.
-type(replies() :: emqx_types:packet() | reply() | [reply()]). -type replies() :: emqx_types:packet() | reply() | [reply()].
-define(TIMER_TABLE, #{ -define(TIMER_TABLE, #{
alive_timer => keepalive, alive_timer => keepalive,
force_timer => force_close force_timer => force_close
}). }).
-define(INFO_KEYS, [conninfo, conn_state, clientinfo, session, will_msg]). -define(INFO_KEYS, [conninfo, conn_state, clientinfo, session, will_msg]).
@ -90,7 +92,7 @@
info(Channel) -> info(Channel) ->
maps:from_list(info(?INFO_KEYS, 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) -> info(Keys, Channel) when is_list(Keys) ->
[{Key, info(Key, Channel)} || Key <- Keys]; [{Key, info(Key, Channel)} || Key <- Keys];
info(conninfo, #channel{conninfo = ConnInfo}) -> info(conninfo, #channel{conninfo = ConnInfo}) ->
@ -99,13 +101,17 @@ info(clientid, #channel{clientinfo = ClientInfo}) ->
maps:get(clientid, ClientInfo, undefined); maps:get(clientid, ClientInfo, undefined);
info(clientinfo, #channel{clientinfo = ClientInfo}) -> info(clientinfo, #channel{clientinfo = ClientInfo}) ->
ClientInfo; ClientInfo;
info(session, #channel{subscriptions = Subs, info(session, #channel{
conninfo = ConnInfo}) -> subscriptions = Subs,
#{subscriptions => Subs, conninfo = ConnInfo
upgrade_qos => false, }) ->
retry_interval => 0, #{
await_rel_timeout => 0, subscriptions => Subs,
created_at => maps:get(connected_at, ConnInfo)}; upgrade_qos => false,
retry_interval => 0,
await_rel_timeout => 0,
created_at => maps:get(connected_at, ConnInfo)
};
info(conn_state, #channel{conn_state = ConnState}) -> info(conn_state, #channel{conn_state = ConnState}) ->
ConnState; ConnState;
info(will_msg, _) -> info(will_msg, _) ->
@ -115,62 +121,80 @@ info(ctx, #channel{ctx = Ctx}) ->
-spec stats(channel()) -> emqx_types:stats(). -spec stats(channel()) -> emqx_types:stats().
stats(#channel{subscriptions = Subs}) -> stats(#channel{subscriptions = Subs}) ->
[{subscriptions_cnt, maps:size(Subs)}, [
{subscriptions_max, 0}, {subscriptions_cnt, maps:size(Subs)},
{inflight_cnt, 0}, {subscriptions_max, 0},
{inflight_max, 0}, {inflight_cnt, 0},
{mqueue_len, 0}, {inflight_max, 0},
{mqueue_max, 0}, {mqueue_len, 0},
{mqueue_dropped, 0}, {mqueue_max, 0},
{next_pkt_id, 0}, {mqueue_dropped, 0},
{awaiting_rel_cnt, 0}, {next_pkt_id, 0},
{awaiting_rel_max, 0}]. {awaiting_rel_cnt, 0},
{awaiting_rel_max, 0}
].
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Init the channel %% Init the channel
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec init(emqx_exproto_types:conninfo(), map()) -> channel(). -spec init(emqx_exproto_types:conninfo(), map()) -> channel().
init(ConnInfo = #{socktype := Socktype, init(
peername := Peername, ConnInfo = #{
sockname := Sockname, socktype := Socktype,
peercert := Peercert}, Options) -> peername := Peername,
sockname := Sockname,
peercert := Peercert
},
Options
) ->
Ctx = maps:get(ctx, Options), Ctx = maps:get(ctx, Options),
GRpcChann = maps:get(handler, Options), GRpcChann = maps:get(handler, Options),
PoolName = maps:get(pool_name, Options), PoolName = maps:get(pool_name, Options),
NConnInfo = default_conninfo(ConnInfo), NConnInfo = default_conninfo(ConnInfo),
ListenerId = case maps:get(listener, Options, undefined) of ListenerId =
undefined -> undefined; case maps:get(listener, Options, undefined) of
{GwName, Type, LisName} -> undefined -> undefined;
emqx_gateway_utils:listener_id(GwName, Type, LisName) {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName)
end, end,
ClientInfo = maps:put(listener, ListenerId, default_clientinfo(ConnInfo)), ClientInfo = maps:put(listener, ListenerId, default_clientinfo(ConnInfo)),
Channel = #channel{ Channel = #channel{
ctx = Ctx, ctx = Ctx,
gcli = #{channel => GRpcChann, pool_name => PoolName}, gcli = #{channel => GRpcChann, pool_name => PoolName},
conninfo = NConnInfo, conninfo = NConnInfo,
clientinfo = ClientInfo, clientinfo = ClientInfo,
conn_state = connecting, conn_state = connecting,
timers = #{} timers = #{}
}, },
Req = #{conninfo => Req = #{
peercert(Peercert, conninfo =>
#{socktype => socktype(Socktype), peercert(
peername => address(Peername), Peercert,
sockname => address(Sockname)})}, #{
socktype => socktype(Socktype),
peername => address(Peername),
sockname => address(Sockname)
}
)
},
try_dispatch(on_socket_created, wrap(Req), Channel). try_dispatch(on_socket_created, wrap(Req), Channel).
%% @private %% @private
peercert(NoSsl, ConnInfo) when NoSsl == nossl; peercert(NoSsl, ConnInfo) when
NoSsl == undefined -> NoSsl == nossl;
NoSsl == undefined
->
ConnInfo; ConnInfo;
peercert(Peercert, ConnInfo) -> peercert(Peercert, ConnInfo) ->
Fn = fun(_, V) -> V =/= undefined end, Fn = fun(_, V) -> V =/= undefined end,
Infos = maps:filter(Fn, Infos = maps:filter(
#{cn => esockd_peercert:common_name(Peercert), Fn,
dn => esockd_peercert:subject(Peercert)} #{
), cn => esockd_peercert:common_name(Peercert),
dn => esockd_peercert:subject(Peercert)
}
),
case maps:size(Infos) of case maps:size(Infos) of
0 -> 0 ->
ConnInfo; ConnInfo;
@ -192,46 +216,64 @@ address({Host, Port}) ->
%% Handle incoming packet %% Handle incoming packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec handle_in(binary(), channel()) -spec handle_in(binary(), channel()) ->
-> {ok, channel()} {ok, channel()}
| {shutdown, Reason :: term(), channel()}. | {shutdown, Reason :: term(), channel()}.
handle_in(Data, Channel) -> handle_in(Data, Channel) ->
Req = #{bytes => Data}, Req = #{bytes => Data},
{ok, try_dispatch(on_received_bytes, wrap(Req), Channel)}. {ok, try_dispatch(on_received_bytes, wrap(Req), Channel)}.
-spec handle_deliver(list(emqx_types:deliver()), channel()) -spec handle_deliver(list(emqx_types:deliver()), channel()) ->
-> {ok, channel()} {ok, channel()}
| {shutdown, Reason :: term(), channel()}. | {shutdown, Reason :: term(), channel()}.
handle_deliver(Delivers, Channel = #channel{ctx = Ctx, handle_deliver(
clientinfo = ClientInfo}) -> Delivers,
Channel = #channel{
ctx = Ctx,
clientinfo = ClientInfo
}
) ->
%% XXX: ?? Nack delivers from shared subscriptions %% XXX: ?? Nack delivers from shared subscriptions
Mountpoint = maps:get(mountpoint, ClientInfo), Mountpoint = maps:get(mountpoint, ClientInfo),
NodeStr = atom_to_binary(node(), utf8), NodeStr = atom_to_binary(node(), utf8),
Msgs = lists:map(fun({_, _, Msg}) -> Msgs = lists:map(
ok = metrics_inc(Ctx, 'messages.delivered'), fun({_, _, Msg}) ->
Msg1 = emqx_hooks:run_fold('message.delivered', ok = metrics_inc(Ctx, 'messages.delivered'),
[ClientInfo], Msg), Msg1 = emqx_hooks:run_fold(
NMsg = emqx_mountpoint:unmount(Mountpoint, Msg1), 'message.delivered',
#{node => NodeStr, [ClientInfo],
id => emqx_guid:to_hexstr(emqx_message:id(NMsg)), Msg
qos => emqx_message:qos(NMsg), ),
from => fmt_from(emqx_message:from(NMsg)), NMsg = emqx_mountpoint:unmount(Mountpoint, Msg1),
topic => emqx_message:topic(NMsg), #{
payload => emqx_message:payload(NMsg), node => NodeStr,
timestamp => emqx_message:timestamp(NMsg) id => emqx_guid:to_hexstr(emqx_message:id(NMsg)),
} qos => emqx_message:qos(NMsg),
end, Delivers), 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}, Req = #{messages => Msgs},
{ok, try_dispatch(on_received_messages, wrap(Req), Channel)}. {ok, try_dispatch(on_received_messages, wrap(Req), Channel)}.
-spec handle_timeout(reference(), Msg :: term(), channel()) -spec handle_timeout(reference(), Msg :: term(), channel()) ->
-> {ok, channel()} {ok, channel()}
| {shutdown, Reason :: term(), channel()}. | {shutdown, Reason :: term(), channel()}.
handle_timeout(_TRef, {keepalive, _StatVal}, handle_timeout(
Channel = #channel{keepalive = undefined}) -> _TRef,
{keepalive, _StatVal},
Channel = #channel{keepalive = undefined}
) ->
{ok, Channel}; {ok, Channel};
handle_timeout(_TRef, {keepalive, StatVal}, handle_timeout(
Channel = #channel{keepalive = Keepalive}) -> _TRef,
{keepalive, StatVal},
Channel = #channel{keepalive = Keepalive}
) ->
case emqx_keepalive:check(StatVal, Keepalive) of case emqx_keepalive:check(StatVal, Keepalive) of
{ok, NKeepalive} -> {ok, NKeepalive} ->
NChannel = Channel#channel{keepalive = NKeepalive}, NChannel = Channel#channel{keepalive = NKeepalive},
@ -240,97 +282,119 @@ handle_timeout(_TRef, {keepalive, StatVal},
Req = #{type => 'KEEPALIVE'}, Req = #{type => 'KEEPALIVE'},
{ok, try_dispatch(on_timer_timeout, wrap(Req), Channel)} {ok, try_dispatch(on_timer_timeout, wrap(Req), Channel)}
end; end;
handle_timeout(_TRef, force_close, Channel = #channel{closed_reason = Reason}) -> handle_timeout(_TRef, force_close, Channel = #channel{closed_reason = Reason}) ->
{shutdown, {error, {force_close, Reason}}, Channel}; {shutdown, {error, {force_close, Reason}}, Channel};
handle_timeout(_TRef, Msg, Channel) -> handle_timeout(_TRef, Msg, Channel) ->
?SLOG(warning, #{msg => "unexpected_timeout_signal", ?SLOG(warning, #{
signal => Msg}), msg => "unexpected_timeout_signal",
signal => Msg
}),
{ok, Channel}. {ok, Channel}.
-spec handle_call(Req :: any(), From :: any(), channel()) -spec handle_call(Req :: any(), From :: any(), channel()) ->
-> {reply, Reply :: term(), channel()} {reply, Reply :: term(), channel()}
| {reply, Reply :: term(), replies(), channel()} | {reply, Reply :: term(), replies(), channel()}
| {shutdown, Reason :: term(), Reply :: term(), channel()}. | {shutdown, Reason :: term(), Reply :: term(), channel()}.
handle_call({send, Data}, _From, Channel) -> handle_call({send, Data}, _From, Channel) ->
{reply, ok, [{outgoing, Data}], Channel}; {reply, ok, [{outgoing, Data}], Channel};
handle_call(close, _From, Channel = #channel{conn_state = connected}) -> handle_call(close, _From, Channel = #channel{conn_state = connected}) ->
{reply, ok, [{event, disconnected}, {close, normal}], Channel}; {reply, ok, [{event, disconnected}, {close, normal}], Channel};
handle_call(close, _From, Channel) -> handle_call(close, _From, Channel) ->
{reply, ok, [{close, normal}], Channel}; {reply, ok, [{close, normal}], Channel};
handle_call(
handle_call({auth, ClientInfo, _Password}, _From, {auth, ClientInfo, _Password},
Channel = #channel{conn_state = connected}) -> _From,
?SLOG(warning, #{ msg => "ingore_duplicated_authorized_command" Channel = #channel{conn_state = connected}
, request_clientinfo => ClientInfo ) ->
}), ?SLOG(warning, #{
msg => "ingore_duplicated_authorized_command",
request_clientinfo => ClientInfo
}),
{reply, {error, ?RESP_PERMISSION_DENY, <<"Duplicated authenticate command">>}, Channel}; {reply, {error, ?RESP_PERMISSION_DENY, <<"Duplicated authenticate command">>}, Channel};
handle_call({auth, ClientInfo0, Password}, _From, handle_call(
Channel = #channel{ {auth, ClientInfo0, Password},
ctx = Ctx, _From,
conninfo = ConnInfo, Channel = #channel{
clientinfo = ClientInfo}) -> ctx = Ctx,
conninfo = ConnInfo,
clientinfo = ClientInfo
}
) ->
ClientInfo1 = enrich_clientinfo(ClientInfo0, ClientInfo), ClientInfo1 = enrich_clientinfo(ClientInfo0, ClientInfo),
ConnInfo1 = enrich_conninfo(ClientInfo0, ConnInfo), ConnInfo1 = enrich_conninfo(ClientInfo0, ConnInfo),
Channel1 = Channel#channel{conninfo = ConnInfo1, Channel1 = Channel#channel{
clientinfo = ClientInfo1}, conninfo = ConnInfo1,
clientinfo = ClientInfo1
},
#{clientid := ClientId, username := Username} = ClientInfo1, #{clientid := ClientId, username := Username} = ClientInfo1,
case emqx_gateway_ctx:authenticate( case
Ctx, ClientInfo1#{password => Password}) of emqx_gateway_ctx:authenticate(
Ctx, ClientInfo1#{password => Password}
)
of
{ok, NClientInfo} -> {ok, NClientInfo} ->
SessFun = fun(_, _) -> #{} end, SessFun = fun(_, _) -> #{} end,
emqx_logger:set_metadata_clientid(ClientId), emqx_logger:set_metadata_clientid(ClientId),
case emqx_gateway_ctx:open_session( case
Ctx, emqx_gateway_ctx:open_session(
true, Ctx,
NClientInfo, true,
ConnInfo1, NClientInfo,
SessFun ConnInfo1,
) of SessFun
)
of
{ok, _Session} -> {ok, _Session} ->
?SLOG(debug, #{ msg => "client_login_succeed" ?SLOG(debug, #{
, clientid => ClientId msg => "client_login_succeed",
, username => Username clientid => ClientId,
}), username => Username
}),
{reply, ok, [{event, connected}], {reply, ok, [{event, connected}],
ensure_connected(Channel1#channel{clientinfo = NClientInfo})}; ensure_connected(Channel1#channel{clientinfo = NClientInfo})};
{error, Reason} -> {error, Reason} ->
?SLOG(warning, #{ msg => "client_login_failed" ?SLOG(warning, #{
, clientid => ClientId msg => "client_login_failed",
, username => Username clientid => ClientId,
, reason => Reason username => Username,
}), reason => Reason
}),
{reply, {error, ?RESP_PERMISSION_DENY, Reason}, Channel} {reply, {error, ?RESP_PERMISSION_DENY, Reason}, Channel}
end; end;
{error, Reason} -> {error, Reason} ->
?SLOG(warning, #{ msg => "client_login_failed" ?SLOG(warning, #{
, clientid => ClientId msg => "client_login_failed",
, username => Username clientid => ClientId,
, reason => Reason}), username => Username,
reason => Reason
}),
{reply, {error, ?RESP_PERMISSION_DENY, Reason}, Channel} {reply, {error, ?RESP_PERMISSION_DENY, Reason}, Channel}
end; end;
handle_call(
handle_call({start_timer, keepalive, Interval}, _From, {start_timer, keepalive, Interval},
Channel = #channel{ _From,
conninfo = ConnInfo, Channel = #channel{
clientinfo = ClientInfo conninfo = ConnInfo,
}) -> clientinfo = ClientInfo
}
) ->
NConnInfo = ConnInfo#{keepalive => Interval}, NConnInfo = ConnInfo#{keepalive => Interval},
NClientInfo = ClientInfo#{keepalive => Interval}, NClientInfo = ClientInfo#{keepalive => Interval},
NChannel = Channel#channel{conninfo = NConnInfo, clientinfo = NClientInfo}, NChannel = Channel#channel{conninfo = NConnInfo, clientinfo = NClientInfo},
{reply, ok, ensure_keepalive(NChannel)}; {reply, ok, ensure_keepalive(NChannel)};
handle_call(
handle_call({subscribe_from_client, TopicFilter, Qos}, _From, {subscribe_from_client, TopicFilter, Qos},
Channel = #channel{ _From,
ctx = Ctx, Channel = #channel{
conn_state = connected, ctx = Ctx,
clientinfo = ClientInfo}) -> conn_state = connected,
clientinfo = ClientInfo
}
) ->
case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, TopicFilter) of case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, TopicFilter) of
deny -> deny ->
{reply, {error, ?RESP_PERMISSION_DENY, <<"Authorization deny">>}, Channel}; {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), {ok, _, NChannel} = do_subscribe([{TopicFilter, #{qos => Qos}}], Channel),
{reply, ok, NChannel} {reply, ok, NChannel}
end; end;
handle_call({subscribe, Topic, SubOpts}, _From, Channel) -> handle_call({subscribe, Topic, SubOpts}, _From, Channel) ->
{ok, {ok, [{NTopicFilter, NSubOpts}], NChannel} = do_subscribe([{Topic, SubOpts}], Channel),
[{NTopicFilter, NSubOpts}], NChannel} = do_subscribe([{Topic, SubOpts}], Channel),
{reply, {ok, {NTopicFilter, NSubOpts}}, NChannel}; {reply, {ok, {NTopicFilter, NSubOpts}}, NChannel};
handle_call(
handle_call({unsubscribe_from_client, TopicFilter}, _From, {unsubscribe_from_client, TopicFilter},
Channel = #channel{conn_state = connected}) -> _From,
Channel = #channel{conn_state = connected}
) ->
{ok, NChannel} = do_unsubscribe([{TopicFilter, #{}}], Channel), {ok, NChannel} = do_unsubscribe([{TopicFilter, #{}}], Channel),
{reply, ok, NChannel}; {reply, ok, NChannel};
handle_call({unsubscribe, Topic}, _From, Channel) -> handle_call({unsubscribe, Topic}, _From, Channel) ->
{ok, NChannel} = do_unsubscribe([Topic], Channel), {ok, NChannel} = do_unsubscribe([Topic], Channel),
{reply, ok, NChannel}; {reply, ok, NChannel};
handle_call(subscriptions, _From, Channel = #channel{subscriptions = Subs}) -> handle_call(subscriptions, _From, Channel = #channel{subscriptions = Subs}) ->
{reply, {ok, maps:to_list(Subs)}, Channel}; {reply, {ok, maps:to_list(Subs)}, Channel};
handle_call(
handle_call({publish, Topic, Qos, Payload}, _From, {publish, Topic, Qos, Payload},
Channel = #channel{ _From,
ctx = Ctx, Channel = #channel{
conn_state = connected, ctx = Ctx,
clientinfo = ClientInfo conn_state = connected,
= #{clientid := From, clientinfo =
mountpoint := Mountpoint}}) -> ClientInfo =
#{
clientid := From,
mountpoint := Mountpoint
}
}
) ->
case emqx_gateway_ctx:authorize(Ctx, ClientInfo, publish, Topic) of case emqx_gateway_ctx:authorize(Ctx, ClientInfo, publish, Topic) of
deny -> deny ->
{reply, {error, ?RESP_PERMISSION_DENY, <<"Authorization deny">>}, Channel}; {reply, {error, ?RESP_PERMISSION_DENY, <<"Authorization deny">>}, Channel};
@ -372,36 +440,39 @@ handle_call({publish, Topic, Qos, Payload}, _From,
_ = emqx:publish(NMsg), _ = emqx:publish(NMsg),
{reply, ok, Channel} {reply, ok, Channel}
end; end;
handle_call(kick, _From, Channel) -> handle_call(kick, _From, Channel) ->
{shutdown, kicked, ok, ensure_disconnected(kicked, Channel)}; {shutdown, kicked, ok, ensure_disconnected(kicked, Channel)};
handle_call(discard, _From, Channel) -> handle_call(discard, _From, Channel) ->
{shutdown, discarded, ok, Channel}; {shutdown, discarded, ok, Channel};
handle_call(Req, _From, Channel) -> handle_call(Req, _From, Channel) ->
?SLOG(warning, #{ msg => "unexpected_call" ?SLOG(warning, #{
, call => Req msg => "unexpected_call",
}), call => Req
}),
{reply, {error, unexpected_call}, Channel}. {reply, {error, unexpected_call}, Channel}.
-spec handle_cast(any(), channel()) -spec handle_cast(any(), channel()) ->
-> {ok, channel()} {ok, channel()}
| {ok, replies(), channel()} | {ok, replies(), channel()}
| {shutdown, Reason :: term(), channel()}. | {shutdown, Reason :: term(), channel()}.
handle_cast(Req, Channel) -> handle_cast(Req, Channel) ->
?SLOG(warning, #{ msg => "unexpected_call" ?SLOG(warning, #{
, call => Req msg => "unexpected_call",
}), call => Req
}),
{ok, Channel}. {ok, Channel}.
-spec handle_info(any(), channel()) -spec handle_info(any(), channel()) ->
-> {ok, channel()} {ok, channel()}
| {shutdown, Reason :: term(), channel()}. | {shutdown, Reason :: term(), channel()}.
handle_info({sock_closed, Reason}, handle_info(
Channel = #channel{rqueue = Queue, inflight = Inflight}) -> {sock_closed, Reason},
case queue:len(Queue) =:= 0 Channel = #channel{rqueue = Queue, inflight = Inflight}
andalso Inflight =:= undefined of ) ->
case
queue:len(Queue) =:= 0 andalso
Inflight =:= undefined
of
true -> true ->
Channel1 = ensure_disconnected({sock_closed, Reason}, Channel), Channel1 = ensure_disconnected({sock_closed, Reason}, Channel),
{shutdown, Reason, Channel1}; {shutdown, Reason, Channel1};
@ -411,25 +482,24 @@ handle_info({sock_closed, Reason},
Channel2 = ensure_timer(force_timer, Channel1), Channel2 = ensure_timer(force_timer, Channel1),
{ok, ensure_disconnected({sock_closed, Reason}, Channel2)} {ok, ensure_disconnected({sock_closed, Reason}, Channel2)}
end; end;
handle_info({hreply, on_socket_created, ok}, Channel) -> handle_info({hreply, on_socket_created, ok}, Channel) ->
dispatch_or_close_process(Channel#channel{inflight = undefined}); dispatch_or_close_process(Channel#channel{inflight = undefined});
handle_info({hreply, FunName, ok}, Channel) handle_info({hreply, FunName, ok}, Channel) when
when FunName == on_socket_closed; FunName == on_socket_closed;
FunName == on_received_bytes; FunName == on_received_bytes;
FunName == on_received_messages; FunName == on_received_messages;
FunName == on_timer_timeout -> FunName == on_timer_timeout
->
dispatch_or_close_process(Channel#channel{inflight = undefined}); dispatch_or_close_process(Channel#channel{inflight = undefined});
handle_info({hreply, FunName, {error, Reason}}, Channel) -> handle_info({hreply, FunName, {error, Reason}}, Channel) ->
{shutdown, {error, {FunName, Reason}}, Channel}; {shutdown, {error, {FunName, Reason}}, Channel};
handle_info({subscribe, _}, Channel) -> handle_info({subscribe, _}, Channel) ->
{ok, Channel}; {ok, Channel};
handle_info(Info, Channel) -> handle_info(Info, Channel) ->
?SLOG(warning, #{ msg => "unexpected_info" ?SLOG(warning, #{
, info => Info msg => "unexpected_info",
}), info => Info
}),
{ok, Channel}. {ok, Channel}.
-spec terminate(any(), channel()) -> channel(). -spec terminate(any(), channel()) -> channel().
@ -446,13 +516,22 @@ do_subscribe(TopicFilters, Channel) ->
fun({TopicFilter, SubOpts}, {MadeSubs, ChannelAcc}) -> fun({TopicFilter, SubOpts}, {MadeSubs, ChannelAcc}) ->
{Sub, Channel1} = do_subscribe(TopicFilter, SubOpts, ChannelAcc), {Sub, Channel1} = do_subscribe(TopicFilter, SubOpts, ChannelAcc),
{MadeSubs ++ [Sub], Channel1} {MadeSubs ++ [Sub], Channel1}
end, {[], Channel}, parse_topic_filters(TopicFilters)), end,
{[], Channel},
parse_topic_filters(TopicFilters)
),
{ok, MadeSubs, NChannel}. {ok, MadeSubs, NChannel}.
%% @private %% @private
do_subscribe(TopicFilter, SubOpts, Channel = do_subscribe(
#channel{clientinfo = ClientInfo = #{mountpoint := Mountpoint}, TopicFilter,
subscriptions = Subs}) -> SubOpts,
Channel =
#channel{
clientinfo = ClientInfo = #{mountpoint := Mountpoint},
subscriptions = Subs
}
) ->
%% Mountpoint first %% Mountpoint first
NTopicFilter = emqx_mountpoint:mount(Mountpoint, TopicFilter), NTopicFilter = emqx_mountpoint:mount(Mountpoint, TopicFilter),
NSubOpts = maps:merge(emqx_gateway_utils:default_subopts(), SubOpts), NSubOpts = maps:merge(emqx_gateway_utils:default_subopts(), SubOpts),
@ -462,34 +541,49 @@ do_subscribe(TopicFilter, SubOpts, Channel =
case IsNew of case IsNew of
true -> true ->
ok = emqx:subscribe(NTopicFilter, SubId, NSubOpts), ok = emqx:subscribe(NTopicFilter, SubId, NSubOpts),
ok = emqx_hooks:run('session.subscribed', ok = emqx_hooks:run(
[ClientInfo, NTopicFilter, NSubOpts#{is_new => IsNew}]), 'session.subscribed',
{{NTopicFilter, NSubOpts}, [ClientInfo, NTopicFilter, NSubOpts#{is_new => IsNew}]
Channel#channel{subscriptions = Subs#{NTopicFilter => NSubOpts}}}; ),
{{NTopicFilter, NSubOpts}, Channel#channel{
subscriptions = Subs#{NTopicFilter => NSubOpts}
}};
_ -> _ ->
%% Update subopts %% Update subopts
ok = emqx:subscribe(NTopicFilter, SubId, NSubOpts), ok = emqx:subscribe(NTopicFilter, SubId, NSubOpts),
{{NTopicFilter, NSubOpts}, {{NTopicFilter, NSubOpts}, Channel#channel{
Channel#channel{subscriptions = Subs#{NTopicFilter => NSubOpts}}} subscriptions = Subs#{NTopicFilter => NSubOpts}
}}
end. end.
do_unsubscribe(TopicFilters, Channel) -> do_unsubscribe(TopicFilters, Channel) ->
NChannel = lists:foldl( NChannel = lists:foldl(
fun({TopicFilter, SubOpts}, ChannelAcc) -> fun({TopicFilter, SubOpts}, ChannelAcc) ->
do_unsubscribe(TopicFilter, SubOpts, ChannelAcc) do_unsubscribe(TopicFilter, SubOpts, ChannelAcc)
end, Channel, parse_topic_filters(TopicFilters)), end,
Channel,
parse_topic_filters(TopicFilters)
),
{ok, NChannel}. {ok, NChannel}.
%% @private %% @private
do_unsubscribe(TopicFilter, UnSubOpts, Channel = do_unsubscribe(
#channel{clientinfo = ClientInfo = #{mountpoint := Mountpoint}, TopicFilter,
subscriptions = Subs}) -> UnSubOpts,
Channel =
#channel{
clientinfo = ClientInfo = #{mountpoint := Mountpoint},
subscriptions = Subs
}
) ->
NTopicFilter = emqx_mountpoint:mount(Mountpoint, TopicFilter), NTopicFilter = emqx_mountpoint:mount(Mountpoint, TopicFilter),
case maps:find(NTopicFilter, Subs) of case maps:find(NTopicFilter, Subs) of
{ok, SubOpts} -> {ok, SubOpts} ->
ok = emqx:unsubscribe(NTopicFilter), ok = emqx:unsubscribe(NTopicFilter),
ok = emqx_hooks:run('session.unsubscribed', ok = emqx_hooks:run(
[ClientInfo, TopicFilter, maps:merge(SubOpts, UnSubOpts)]), 'session.unsubscribed',
[ClientInfo, TopicFilter, maps:merge(SubOpts, UnSubOpts)]
),
Channel#channel{subscriptions = maps:remove(NTopicFilter, Subs)}; Channel#channel{subscriptions = maps:remove(NTopicFilter, Subs)};
_ -> _ ->
Channel Channel
@ -503,25 +597,32 @@ parse_topic_filters(TopicFilters) ->
%% Ensure & Hooks %% Ensure & Hooks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
ensure_connected(Channel = #channel{ ensure_connected(
ctx = Ctx, Channel = #channel{
conninfo = ConnInfo, ctx = Ctx,
clientinfo = ClientInfo}) -> conninfo = ConnInfo,
clientinfo = ClientInfo
}
) ->
NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)}, NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)},
ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]),
Channel#channel{conninfo = NConnInfo, Channel#channel{
conn_state = connected conninfo = NConnInfo,
}. conn_state = connected
}.
ensure_disconnected(Reason, Channel = #channel{ ensure_disconnected(
ctx = Ctx, Reason,
conn_state = connected, Channel = #channel{
conninfo = ConnInfo, ctx = Ctx,
clientinfo = ClientInfo}) -> conn_state = connected,
conninfo = ConnInfo,
clientinfo = ClientInfo
}
) ->
NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, 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}; Channel#channel{conninfo = NConnInfo, conn_state = disconnected};
ensure_disconnected(_Reason, Channel = #channel{conninfo = ConnInfo}) -> ensure_disconnected(_Reason, Channel = #channel{conninfo = ConnInfo}) ->
NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)},
Channel#channel{conninfo = NConnInfo, conn_state = disconnected}. Channel#channel{conninfo = NConnInfo, conn_state = disconnected}.
@ -549,8 +650,9 @@ ensure_timer(Name, Channel = #channel{timers = Timers}) ->
TRef = maps:get(Name, Timers, undefined), TRef = maps:get(Name, Timers, undefined),
Time = interval(Name, Channel), Time = interval(Name, Channel),
case TRef == undefined andalso Time > 0 of case TRef == undefined andalso Time > 0 of
true -> ensure_timer(Name, Time, Channel); true -> ensure_timer(Name, Time, Channel);
false -> Channel %% Timer disabled or exists %% Timer disabled or exists
false -> Channel
end. end.
ensure_timer(Name, Time, Channel = #channel{timers = Timers}) -> ensure_timer(Name, Time, Channel = #channel{timers = Timers}) ->
@ -574,12 +676,15 @@ interval(alive_timer, #channel{keepalive = Keepalive}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
wrap(Req) -> wrap(Req) ->
Req#{conn => base64:encode(term_to_binary(self()))}. Req#{conn => base64:encode(term_to_binary(self()))}.
dispatch_or_close_process(Channel = #channel{ dispatch_or_close_process(
rqueue = Queue, Channel = #channel{
inflight = undefined, rqueue = Queue,
gcli = GClient}) -> inflight = undefined,
gcli = GClient
}
) ->
case queue:out(Queue) of case queue:out(Queue) of
{empty, _} -> {empty, _} ->
case Channel#channel.conn_state of case Channel#channel.conn_state of
@ -613,30 +718,36 @@ enrich_clientinfo(InClientInfo = #{proto_name := ProtoName}, ClientInfo) ->
NClientInfo#{protocol => proto_name_to_protocol(ProtoName)}. NClientInfo#{protocol => proto_name_to_protocol(ProtoName)}.
default_conninfo(ConnInfo) -> default_conninfo(ConnInfo) ->
ConnInfo#{clean_start => true, ConnInfo#{
clientid => undefined, clean_start => true,
username => undefined, clientid => undefined,
conn_mod => undefined, username => undefined,
conn_props => #{}, conn_mod => undefined,
connected => true, conn_props => #{},
proto_name => <<"exproto">>, connected => true,
proto_ver => <<"1.0">>, proto_name => <<"exproto">>,
connected_at => erlang:system_time(millisecond), proto_ver => <<"1.0">>,
keepalive => 0, connected_at => erlang:system_time(millisecond),
receive_maximum => 0, keepalive => 0,
expiry_interval => 0}. receive_maximum => 0,
expiry_interval => 0
}.
default_clientinfo(#{peername := {PeerHost, _}, default_clientinfo(#{
sockname := {_, SockPort}}) -> peername := {PeerHost, _},
#{zone => default, sockname := {_, SockPort}
protocol => exproto, }) ->
peerhost => PeerHost, #{
sockport => SockPort, zone => default,
clientid => undefined, protocol => exproto,
username => undefined, peerhost => PeerHost,
is_bridge => false, sockport => SockPort,
is_superuser => false, clientid => undefined,
mountpoint => undefined}. username => undefined,
is_bridge => false,
is_superuser => false,
mountpoint => undefined
}.
stringfy(Reason) -> stringfy(Reason) ->
unicode:characters_to_binary((io_lib:format("~0p", [Reason]))). unicode:characters_to_binary((io_lib:format("~0p", [Reason]))).

View File

@ -20,14 +20,15 @@
-behaviour(emqx_gateway_frame). -behaviour(emqx_gateway_frame).
-export([ initial_parse_state/1 -export([
, serialize_opts/0 initial_parse_state/1,
, parse/2 serialize_opts/0,
, serialize_pkt/2 parse/2,
, format/1 serialize_pkt/2,
, is_message/1 format/1,
, type/1 is_message/1,
]). type/1
]).
initial_parse_state(_) -> initial_parse_state(_) ->
#{}. #{}.
@ -47,4 +48,3 @@ format(Data) ->
is_message(_) -> true. is_message(_) -> true.
type(_) -> unknown. type(_) -> unknown.

View File

@ -21,26 +21,26 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
%% APIs %% APIs
-export([async_call/3]). -export([async_call/3]).
-export([start_link/2]). -export([start_link/2]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-record(state, { -record(state, {
pool, pool,
id, id,
streams streams
}). }).
-define(CONN_ADAPTER_MOD, emqx_exproto_v_1_connection_handler_client). -define(CONN_ADAPTER_MOD, emqx_exproto_v_1_connection_handler_client).
@ -49,11 +49,18 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
start_link(Pool, Id) -> start_link(Pool, Id) ->
gen_server:start_link({local, emqx_misc:proc_name(?MODULE, Id)}, gen_server:start_link(
?MODULE, [Pool, Id], []). {local, emqx_misc:proc_name(?MODULE, Id)},
?MODULE,
[Pool, Id],
[]
).
async_call(FunName, Req = #{conn := Conn}, async_call(
Options = #{pool_name := PoolName}) -> FunName,
Req = #{conn := Conn},
Options = #{pool_name := PoolName}
) ->
cast(pick(PoolName, Conn), {rpc, FunName, Req, Options, self()}). 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}) -> handle_cast({rpc, Fun, Req, Options, From}, State = #state{streams = Streams}) ->
case ensure_stream_opened(Fun, Options, Streams) of case ensure_stream_opened(Fun, Options, Streams) of
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{ msg => "request_grpc_server_failed" ?SLOG(error, #{
, function => {?CONN_ADAPTER_MOD, Fun, Options} msg => "request_grpc_server_failed",
, reason => Reason}), function => {?CONN_ADAPTER_MOD, Fun, Options},
reason => Reason
}),
reply(From, Fun, {error, Reason}), reply(From, Fun, {error, Reason}),
{noreply, State#state{streams = Streams#{Fun => undefined}}}; {noreply, State#state{streams = Streams#{Fun => undefined}}};
{ok, Stream} -> {ok, Stream} ->
case catch grpc_client:send(Stream, Req) of case catch grpc_client:send(Stream, Req) of
ok -> ok ->
?SLOG(debug, #{ msg => "send_grpc_request_succeed" ?SLOG(debug, #{
, function => {?CONN_ADAPTER_MOD, Fun} msg => "send_grpc_request_succeed",
, request => Req function => {?CONN_ADAPTER_MOD, Fun},
}), request => Req
}),
reply(From, Fun, ok), reply(From, Fun, ok),
{noreply, State#state{streams = Streams#{Fun => Stream}}}; {noreply, State#state{streams = Streams#{Fun => Stream}}};
{'EXIT', {not_found, _Stk}} -> {'EXIT', {not_found, _Stk}} ->
%% Not found the stream, reopen it %% Not found the stream, reopen it
?SLOG(info, #{ msg => "cannt_find_old_stream_ref" ?SLOG(info, #{
, function => {?CONN_ADAPTER_MOD, Fun} msg => "cannt_find_old_stream_ref",
}), function => {?CONN_ADAPTER_MOD, Fun}
}),
handle_cast( handle_cast(
{rpc, Fun, Req, Options, From}, {rpc, Fun, Req, Options, From},
State#state{streams = maps:remove(Fun, Streams)}); State#state{streams = maps:remove(Fun, Streams)}
);
{'EXIT', {timeout, _Stk}} -> {'EXIT', {timeout, _Stk}} ->
?SLOG(error, #{ msg => "send_grpc_request_timeout" ?SLOG(error, #{
, function => {?CONN_ADAPTER_MOD, Fun} msg => "send_grpc_request_timeout",
, request => Req function => {?CONN_ADAPTER_MOD, Fun},
}), request => Req
}),
reply(From, Fun, {error, timeout}), reply(From, Fun, {error, timeout}),
{noreply, State#state{streams = Streams#{Fun => Stream}}}; {noreply, State#state{streams = Streams#{Fun => Stream}}};
{'EXIT', {Reason1, Stk}} -> {'EXIT', {Reason1, Stk}} ->
?SLOG(error, #{ msg => "send_grpc_request_failed" ?SLOG(error, #{
, function => {?CONN_ADAPTER_MOD, Fun} msg => "send_grpc_request_failed",
, request => Req function => {?CONN_ADAPTER_MOD, Fun},
, error => Reason1 request => Req,
, stacktrace => Stk error => Reason1,
}), stacktrace => Stk
}),
reply(From, Fun, {error, Reason1}), reply(From, Fun, {error, Reason1}),
{noreply, State#state{streams = Streams#{Fun => undefined}}} {noreply, State#state{streams = Streams#{Fun => undefined}}}
end end
@ -147,5 +161,6 @@ ensure_stream_opened(Fun, Options, Streams) ->
{ok, Stream} -> {ok, Stream}; {ok, Stream} -> {ok, Stream};
{error, Reason} -> {error, Reason} {error, Reason} -> {error, Reason}
end; end;
Stream -> {ok, Stream} Stream ->
{ok, Stream}
end. end.

View File

@ -27,49 +27,58 @@
-define(DEFAULT_CALL_TIMEOUT, 5000). -define(DEFAULT_CALL_TIMEOUT, 5000).
%% gRPC server callbacks %% gRPC server callbacks
-export([ send/2 -export([
, close/2 send/2,
, authenticate/2 close/2,
, start_timer/2 authenticate/2,
, publish/2 start_timer/2,
, subscribe/2 publish/2,
, unsubscribe/2 subscribe/2,
]). unsubscribe/2
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% gRPC ConnectionAdapter service %% gRPC ConnectionAdapter service
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec send(emqx_exproto_pb:send_bytes_request(), grpc:metadata()) -spec send(emqx_exproto_pb:send_bytes_request(), grpc:metadata()) ->
-> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} {ok, emqx_exproto_pb:code_response(), grpc:metadata()}
| {error, grpc_cowboy_h:error_response()}. | {error, grpc_cowboy_h:error_response()}.
send(Req = #{conn := Conn, bytes := Bytes}, Md) -> send(Req = #{conn := Conn, bytes := Bytes}, Md) ->
?SLOG(debug, #{ msg => "recv_grpc_function_call" ?SLOG(debug, #{
, function => ?FUNCTION_NAME msg => "recv_grpc_function_call",
, request => Req function => ?FUNCTION_NAME,
}), request => Req
}),
{ok, response(call(Conn, {send, Bytes})), Md}. {ok, response(call(Conn, {send, Bytes})), Md}.
-spec close(emqx_exproto_pb:close_socket_request(), grpc:metadata()) -spec close(emqx_exproto_pb:close_socket_request(), grpc:metadata()) ->
-> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} {ok, emqx_exproto_pb:code_response(), grpc:metadata()}
| {error, grpc_cowboy_h:error_response()}. | {error, grpc_cowboy_h:error_response()}.
close(Req = #{conn := Conn}, Md) -> close(Req = #{conn := Conn}, Md) ->
?SLOG(debug, #{ msg => "recv_grpc_function_call" ?SLOG(debug, #{
, function => ?FUNCTION_NAME msg => "recv_grpc_function_call",
, request => Req function => ?FUNCTION_NAME,
}), request => Req
}),
{ok, response(call(Conn, close)), Md}. {ok, response(call(Conn, close)), Md}.
-spec authenticate(emqx_exproto_pb:authenticate_request(), grpc:metadata()) -spec authenticate(emqx_exproto_pb:authenticate_request(), grpc:metadata()) ->
-> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} {ok, emqx_exproto_pb:code_response(), grpc:metadata()}
| {error, grpc_cowboy_h:error_response()}. | {error, grpc_cowboy_h:error_response()}.
authenticate(Req = #{conn := Conn, authenticate(
password := Password, Req = #{
clientinfo := ClientInfo}, Md) -> conn := Conn,
?SLOG(debug, #{ msg => "recv_grpc_function_call" password := Password,
, function => ?FUNCTION_NAME clientinfo := ClientInfo
, request => Req },
}), Md
) ->
?SLOG(debug, #{
msg => "recv_grpc_function_call",
function => ?FUNCTION_NAME,
request => Req
}),
case validate(clientinfo, ClientInfo) of case validate(clientinfo, ClientInfo) of
false -> false ->
{ok, response({error, ?RESP_REQUIRED_PARAMS_MISSED}), Md}; {ok, response({error, ?RESP_REQUIRED_PARAMS_MISSED}), Md};
@ -77,70 +86,78 @@ authenticate(Req = #{conn := Conn,
{ok, response(call(Conn, {auth, ClientInfo, Password})), Md} {ok, response(call(Conn, {auth, ClientInfo, Password})), Md}
end. end.
-spec start_timer(emqx_exproto_pb:timer_request(), grpc:metadata()) -spec start_timer(emqx_exproto_pb:timer_request(), grpc:metadata()) ->
-> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} {ok, emqx_exproto_pb:code_response(), grpc:metadata()}
| {error, grpc_cowboy_h:error_response()}. | {error, grpc_cowboy_h:error_response()}.
start_timer(Req = #{conn := Conn, type := Type, interval := Interval}, Md) start_timer(Req = #{conn := Conn, type := Type, interval := Interval}, Md) when
when Type =:= 'KEEPALIVE' andalso Interval > 0 -> Type =:= 'KEEPALIVE' andalso Interval > 0
?SLOG(debug, #{ msg => "recv_grpc_function_call" ->
, function => ?FUNCTION_NAME ?SLOG(debug, #{
, request => Req msg => "recv_grpc_function_call",
}), function => ?FUNCTION_NAME,
request => Req
}),
{ok, response(call(Conn, {start_timer, keepalive, Interval})), Md}; {ok, response(call(Conn, {start_timer, keepalive, Interval})), Md};
start_timer(Req, Md) -> start_timer(Req, Md) ->
?SLOG(debug, #{ msg => "recv_grpc_function_call" ?SLOG(debug, #{
, function => ?FUNCTION_NAME msg => "recv_grpc_function_call",
, request => Req function => ?FUNCTION_NAME,
}), request => Req
}),
{ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}. {ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}.
-spec publish(emqx_exproto_pb:publish_request(), grpc:metadata()) -spec publish(emqx_exproto_pb:publish_request(), grpc:metadata()) ->
-> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} {ok, emqx_exproto_pb:code_response(), grpc:metadata()}
| {error, grpc_cowboy_h:error_response()}. | {error, grpc_cowboy_h:error_response()}.
publish(Req = #{conn := Conn, topic := Topic, qos := Qos, payload := Payload}, Md) publish(Req = #{conn := Conn, topic := Topic, qos := Qos, payload := Payload}, Md) when
when ?IS_QOS(Qos) -> ?IS_QOS(Qos)
?SLOG(debug, #{ msg => "recv_grpc_function_call" ->
, function => ?FUNCTION_NAME ?SLOG(debug, #{
, request => Req msg => "recv_grpc_function_call",
}), function => ?FUNCTION_NAME,
request => Req
}),
{ok, response(call(Conn, {publish, Topic, Qos, Payload})), Md}; {ok, response(call(Conn, {publish, Topic, Qos, Payload})), Md};
publish(Req, Md) -> publish(Req, Md) ->
?SLOG(debug, #{ msg => "recv_grpc_function_call" ?SLOG(debug, #{
, function => ?FUNCTION_NAME msg => "recv_grpc_function_call",
, request => Req function => ?FUNCTION_NAME,
}), request => Req
}),
{ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}. {ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}.
-spec subscribe(emqx_exproto_pb:subscribe_request(), grpc:metadata()) -spec subscribe(emqx_exproto_pb:subscribe_request(), grpc:metadata()) ->
-> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} {ok, emqx_exproto_pb:code_response(), grpc:metadata()}
| {error, grpc_cowboy_h:error_response()}. | {error, grpc_cowboy_h:error_response()}.
subscribe(Req = #{conn := Conn, topic := Topic, qos := Qos}, Md) subscribe(Req = #{conn := Conn, topic := Topic, qos := Qos}, Md) when
when ?IS_QOS(Qos) -> ?IS_QOS(Qos)
?SLOG(debug, #{ msg => "recv_grpc_function_call" ->
, function => ?FUNCTION_NAME ?SLOG(debug, #{
, request => Req msg => "recv_grpc_function_call",
}), function => ?FUNCTION_NAME,
request => Req
}),
{ok, response(call(Conn, {subscribe_from_client, Topic, Qos})), Md}; {ok, response(call(Conn, {subscribe_from_client, Topic, Qos})), Md};
subscribe(Req, Md) -> subscribe(Req, Md) ->
?SLOG(debug, #{ msg => "recv_grpc_function_call" ?SLOG(debug, #{
, function => ?FUNCTION_NAME msg => "recv_grpc_function_call",
, request => Req function => ?FUNCTION_NAME,
}), request => Req
}),
{ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}. {ok, response({error, ?RESP_PARAMS_TYPE_ERROR}), Md}.
-spec unsubscribe(emqx_exproto_pb:unsubscribe_request(), grpc:metadata()) -spec unsubscribe(emqx_exproto_pb:unsubscribe_request(), grpc:metadata()) ->
-> {ok, emqx_exproto_pb:code_response(), grpc:metadata()} {ok, emqx_exproto_pb:code_response(), grpc:metadata()}
| {error, grpc_cowboy_h:error_response()}. | {error, grpc_cowboy_h:error_response()}.
unsubscribe(Req = #{conn := Conn, topic := Topic}, Md) -> unsubscribe(Req = #{conn := Conn, topic := Topic}, Md) ->
?SLOG(debug, #{ msg => "recv_grpc_function_call" ?SLOG(debug, #{
, function => ?FUNCTION_NAME msg => "recv_grpc_function_call",
, request => Req function => ?FUNCTION_NAME,
}), request => Req
}),
{ok, response(call(Conn, {unsubscribe_from_client, Topic})), Md}. {ok, response(call(Conn, {unsubscribe_from_client, Topic})), Md}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -155,20 +172,20 @@ call(ConnStr, Req) ->
Pid = to_pid(ConnStr), Pid = to_pid(ConnStr),
emqx_gateway_conn:call(Pid, Req, ?DEFAULT_CALL_TIMEOUT) emqx_gateway_conn:call(Pid, Req, ?DEFAULT_CALL_TIMEOUT)
catch catch
exit : badarg -> exit:badarg ->
{error, ?RESP_PARAMS_TYPE_ERROR, <<"The conn type error">>}; {error, ?RESP_PARAMS_TYPE_ERROR, <<"The conn type error">>};
exit : noproc -> exit:noproc ->
{error, ?RESP_CONN_PROCESS_NOT_ALIVE, {error, ?RESP_CONN_PROCESS_NOT_ALIVE, <<"Connection process is not alive">>};
<<"Connection process is not alive">>}; exit:timeout ->
exit : timeout ->
{error, ?RESP_UNKNOWN, <<"Connection is not answered">>}; {error, ?RESP_UNKNOWN, <<"Connection is not answered">>};
Class : Reason : Stk-> Class:Reason:Stk ->
?SLOG(error, #{ msg => "call_conn_process_crashed" ?SLOG(error, #{
, request => Req msg => "call_conn_process_crashed",
, conn_str=> ConnStr request => Req,
, reason => {Class, Reason} conn_str => ConnStr,
, stacktrace => Stk reason => {Class, Reason},
}), stacktrace => Stk
}),
{error, ?RESP_UNKNOWN, <<"Unknown crashes">>} {error, ?RESP_UNKNOWN, <<"Unknown crashes">>}
end. end.
@ -184,11 +201,13 @@ validate(clientinfo, M) ->
response(ok) -> response(ok) ->
#{code => ?RESP_SUCCESS}; #{code => ?RESP_SUCCESS};
response({error, Code, Reason}) response({error, Code, Reason}) when
when ?IS_GRPC_RESULT_CODE(Code) -> ?IS_GRPC_RESULT_CODE(Code)
->
#{code => Code, message => stringfy(Reason)}; #{code => Code, message => stringfy(Reason)};
response({error, Code}) response({error, Code}) when
when ?IS_GRPC_RESULT_CODE(Code) -> ?IS_GRPC_RESULT_CODE(Code)
->
#{code => Code}; #{code => Code};
response(Other) -> response(Other) ->
#{code => ?RESP_UNKNOWN, message => stringfy(Other)}. #{code => ?RESP_UNKNOWN, message => stringfy(Other)}.

View File

@ -21,29 +21,33 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-import(emqx_gateway_utils, -import(
[ normalize_config/1 emqx_gateway_utils,
, start_listeners/4 [
, stop_listeners/2 normalize_config/1,
]). start_listeners/4,
stop_listeners/2
]
).
%% APIs %% APIs
-export([ reg/0 -export([
, unreg/0 reg/0,
]). unreg/0
]).
-export([ on_gateway_load/2 -export([
, on_gateway_update/3 on_gateway_load/2,
, on_gateway_unload/2 on_gateway_update/3,
]). on_gateway_unload/2
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
reg() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [{cbkmod, ?MODULE}],
],
emqx_gateway_registry:reg(exproto, RegistryOptions). emqx_gateway_registry:reg(exproto, RegistryOptions).
unreg() -> unreg() ->
@ -53,13 +57,18 @@ unreg() ->
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_gateway_load(_Gateway = #{ name := GwName, on_gateway_load(
config := Config _Gateway = #{
}, Ctx) -> name := GwName,
config := Config
},
Ctx
) ->
%% XXX: How to monitor it ? %% XXX: How to monitor it ?
_ = start_grpc_client_channel(GwName, _ = start_grpc_client_channel(
maps:get(handler, Config, undefined) GwName,
), maps:get(handler, Config, undefined)
),
%% XXX: How to monitor it ? %% XXX: How to monitor it ?
_ = start_grpc_server(GwName, maps:get(server, Config, undefined)), _ = start_grpc_server(GwName, maps:get(server, Config, undefined)),
@ -67,29 +76,39 @@ on_gateway_load(_Gateway = #{ name := GwName,
PoolName = pool_name(GwName), PoolName = pool_name(GwName),
PoolSize = emqx_vm:schedulers() * 2, PoolSize = emqx_vm:schedulers() * 2,
{ok, PoolSup} = emqx_pool_sup:start_link( {ok, PoolSup} = emqx_pool_sup:start_link(
PoolName, hash, PoolSize, PoolName,
{emqx_exproto_gcli, start_link, []}), hash,
PoolSize,
{emqx_exproto_gcli, start_link, []}
),
NConfig = maps:without( NConfig = maps:without(
[server, handler], [server, handler],
Config#{pool_name => PoolName} Config#{pool_name => PoolName}
), ),
Listeners = emqx_gateway_utils:normalize_config( Listeners = emqx_gateway_utils:normalize_config(
NConfig#{handler => GwName} NConfig#{handler => GwName}
), ),
ModCfg = #{frame_mod => emqx_exproto_frame, ModCfg = #{
chann_mod => emqx_exproto_channel frame_mod => emqx_exproto_frame,
}, chann_mod => emqx_exproto_channel
case start_listeners( },
Listeners, GwName, Ctx, ModCfg) of case
start_listeners(
Listeners, GwName, Ctx, ModCfg
)
of
{ok, ListenerPids} -> {ok, ListenerPids} ->
{ok, ListenerPids, _GwState = #{ctx => Ctx, pool => PoolSup}}; {ok, ListenerPids, _GwState = #{ctx => Ctx, pool => PoolSup}};
{error, {Reason, Listener}} -> {error, {Reason, Listener}} ->
throw({badconf, #{ key => listeners throw(
, vallue => Listener {badconf, #{
, reason => Reason key => listeners,
}}) vallue => Listener,
reason => Reason
}}
)
end. end.
on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> 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_unload(Gateway, GwState),
on_gateway_load(Gateway#{config => Config}, Ctx) on_gateway_load(Gateway#{config => Config}, Ctx)
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
logger:error("Failed to update ~ts; " logger:error(
"reason: {~0p, ~0p} stacktrace: ~0p", "Failed to update ~ts; "
[GwName, Class, Reason, Stk]), "reason: {~0p, ~0p} stacktrace: ~0p",
[GwName, Class, Reason, Stk]
),
{error, Reason} {error, Reason}
end. end.
on_gateway_unload(_Gateway = #{ name := GwName, on_gateway_unload(
config := Config _Gateway = #{
}, _GwState = #{pool := PoolSup}) -> name := GwName,
config := Config
},
_GwState = #{pool := PoolSup}
) ->
Listeners = emqx_gateway_utils:normalize_config(Config), Listeners = emqx_gateway_utils:normalize_config(Config),
%% Stop funcs??? %% Stop funcs???
exit(PoolSup, kill), exit(PoolSup, kill),
@ -124,29 +149,42 @@ on_gateway_unload(_Gateway = #{ name := GwName,
start_grpc_server(_GwName, undefined) -> start_grpc_server(_GwName, undefined) ->
undefined; undefined;
start_grpc_server(GwName, Options = #{bind := ListenOn}) -> start_grpc_server(GwName, Options = #{bind := ListenOn}) ->
Services = #{protos => [emqx_exproto_pb], Services = #{
services => #{ protos => [emqx_exproto_pb],
'emqx.exproto.v1.ConnectionAdapter' => emqx_exproto_gsvr} services => #{
}, 'emqx.exproto.v1.ConnectionAdapter' => emqx_exproto_gsvr
SvrOptions = case emqx_map_lib:deep_get([ssl, enable], Options, false) of }
false -> []; },
true -> SvrOptions =
[{ssl_options, case emqx_map_lib:deep_get([ssl, enable], Options, false) of
false ->
[];
true ->
[
{ssl_options,
maps:to_list( maps:to_list(
maps:without([enable], maps:get(ssl, Options, #{})) maps:without([enable], maps:get(ssl, Options, #{}))
) )}
}] ]
end, end,
case grpc:start_server(GwName, ListenOn, Services, SvrOptions) of case grpc:start_server(GwName, ListenOn, Services, SvrOptions) of
{ok, _SvrPid} -> {ok, _SvrPid} ->
console_print("Start ~ts gRPC server on ~p successfully.~n", console_print(
[GwName, ListenOn]); "Start ~ts gRPC server on ~p successfully.~n",
[GwName, ListenOn]
);
{error, Reason} -> {error, Reason} ->
?ELOG("Failed to start ~ts gRPC server on ~p, reason: ~0p", ?ELOG(
[GwName, ListenOn, Reason]), "Failed to start ~ts gRPC server on ~p, reason: ~0p",
throw({badconf, #{key => server, [GwName, ListenOn, Reason]
value => Options, ),
reason => illegal_grpc_server_confs}}) throw(
{badconf, #{
key => server,
value => Options,
reason => illegal_grpc_server_confs
}}
)
end. end.
stop_grpc_server(GwName) -> stop_grpc_server(GwName) ->
@ -158,13 +196,16 @@ start_grpc_client_channel(_GwName, undefined) ->
start_grpc_client_channel(GwName, Options = #{address := Address}) -> start_grpc_client_channel(GwName, Options = #{address := Address}) ->
#{host := Host, port := Port} = #{host := Host, port := Port} =
case emqx_http_lib:uri_parse(Address) of case emqx_http_lib:uri_parse(Address) of
{ok, URIMap0} -> URIMap0; {ok, URIMap0} ->
URIMap0;
{error, _Reason} -> {error, _Reason} ->
throw({badconf, #{key => address, throw(
value => Address, {badconf, #{
reason => illegal_grpc_address key => address,
}}) value => Address,
reason => illegal_grpc_address
}}
)
end, end,
case emqx_map_lib:deep_get([ssl, enable], Options, false) of case emqx_map_lib:deep_get([ssl, enable], Options, false) of
false -> false ->
@ -172,9 +213,13 @@ start_grpc_client_channel(GwName, Options = #{address := Address}) ->
grpc_client_sup:create_channel_pool(GwName, SvrAddr, #{}); grpc_client_sup:create_channel_pool(GwName, SvrAddr, #{});
true -> true ->
SslOpts = maps:to_list(maps:get(ssl, Options, #{})), SslOpts = maps:to_list(maps:get(ssl, Options, #{})),
ClientOpts = #{gun_opts => ClientOpts = #{
#{transport => ssl, gun_opts =>
transport_opts => SslOpts}}, #{
transport => ssl,
transport_opts => SslOpts
}
},
SvrAddr = compose_http_uri(https, Host, Port), SvrAddr = compose_http_uri(https, Host, Port),
grpc_client_sup:create_channel_pool(GwName, SvrAddr, ClientOpts) 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) -> compose_http_uri(Scheme, Host, Port) ->
lists:flatten( lists:flatten(
io_lib:format( io_lib:format(
"~s://~s:~w", [Scheme, inet:ntoa(Host), Port])). "~s://~s:~w", [Scheme, inet:ntoa(Host), Port]
)
).
stop_grpc_client_channel(GwName) -> stop_grpc_client_channel(GwName) ->
_ = grpc_client_sup:stop_channel_pool(GwName), _ = grpc_client_sup:stop_channel_pool(GwName),

View File

@ -16,8 +16,13 @@
-define(APP, emqx_exproto). -define(APP, emqx_exproto).
-define(TCP_SOCKOPTS, [binary, {packet, raw}, {reuseaddr, true}, -define(TCP_SOCKOPTS, [
{backlog, 512}, {nodelay, true}]). binary,
{packet, raw},
{reuseaddr, true},
{backlog, 512},
{nodelay, true}
]).
-define(UDP_SOCKOPTS, []). -define(UDP_SOCKOPTS, []).
@ -30,7 +35,9 @@
-define(RESP_PARAMS_TYPE_ERROR, 'PARAMS_TYPE_ERROR'). -define(RESP_PARAMS_TYPE_ERROR, 'PARAMS_TYPE_ERROR').
-define(RESP_REQUIRED_PARAMS_MISSED, 'REQUIRED_PARAMS_MISSED'). -define(RESP_REQUIRED_PARAMS_MISSED, 'REQUIRED_PARAMS_MISSED').
-define(RESP_PERMISSION_DENY, 'PERMISSION_DENY'). -define(RESP_PERMISSION_DENY, 'PERMISSION_DENY').
-define(IS_GRPC_RESULT_CODE(C), ( C =:= ?RESP_SUCCESS -define(IS_GRPC_RESULT_CODE(C),
orelse C =:= ?RESP_CONN_PROCESS_NOT_ALIVE (C =:= ?RESP_SUCCESS orelse
orelse C =:= ?RESP_REQUIRED_PARAMS_MISSED C =:= ?RESP_CONN_PROCESS_NOT_ALIVE orelse
orelse C =:= ?RESP_PERMISSION_DENY)). C =:= ?RESP_REQUIRED_PARAMS_MISSED orelse
C =:= ?RESP_PERMISSION_DENY)
).

View File

@ -3,58 +3,57 @@
%% copied from https://github.com/arcusfelis/binary2 %% copied from https://github.com/arcusfelis/binary2
%% Bytes %% Bytes
-export([ reverse/1 -export([
, join/2 reverse/1,
, duplicate/2 join/2,
, suffix/2 duplicate/2,
, prefix/2 suffix/2,
]). prefix/2
]).
%% Bits %% Bits
-export([ union/2 -export([
, subtract/2 union/2,
, intersection/2 subtract/2,
, inverse/1 intersection/2,
]). inverse/1
]).
%% Trimming %% Trimming
-export([ rtrim/1 -export([
, rtrim/2 rtrim/1,
, ltrim/1 rtrim/2,
, ltrim/2 ltrim/1,
, trim/1 ltrim/2,
, trim/2 trim/1,
]). trim/2
]).
%% Parsing %% Parsing
-export([ bin_to_int/1]). -export([bin_to_int/1]).
%% Matching %% Matching
-export([ optimize_patterns/1]). -export([optimize_patterns/1]).
%% CoAP %% CoAP
-export([ join_path/1]). -export([join_path/1]).
trim(B) -> trim(B, 0).
trim(B) -> trim(B, 0).
ltrim(B) -> ltrim(B, 0). ltrim(B) -> ltrim(B, 0).
rtrim(B) -> rtrim(B, 0). rtrim(B) -> rtrim(B, 0).
rtrim(B, X) when is_binary(B), is_integer(X) -> rtrim(B, X) when is_binary(B), is_integer(X) ->
S = byte_size(B), S = byte_size(B),
do_rtrim(S, B, X); do_rtrim(S, B, X);
rtrim(B, [_|_]=Xs) when is_binary(B) -> rtrim(B, [_ | _] = Xs) when is_binary(B) ->
S = byte_size(B), S = byte_size(B),
do_mrtrim(S, B, Xs). do_mrtrim(S, B, Xs).
ltrim(B, X) when is_binary(B), is_integer(X) -> ltrim(B, X) when is_binary(B), is_integer(X) ->
do_ltrim(B, X); do_ltrim(B, X);
ltrim(B, [_|_]=Xs) when is_binary(B) -> ltrim(B, [_ | _] = Xs) when is_binary(B) ->
do_mltrim(B, Xs). do_mltrim(B, Xs).
%% @doc The second element is a single integer element or an ordset of elements. %% @doc The second element is a single integer element or an ordset of elements.
trim(B, X) when is_binary(B), is_integer(X) -> trim(B, X) when is_binary(B), is_integer(X) ->
From = ltrimc(B, X, 0), 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), To = do_rtrimc(S, B, X),
binary:part(B, From, To - From) binary:part(B, From, To - From)
end; end;
trim(B, [_|_]=Xs) when is_binary(B) -> trim(B, [_ | _] = Xs) when is_binary(B) ->
From = mltrimc(B, Xs, 0), From = mltrimc(B, Xs, 0),
case byte_size(B) of case byte_size(B) of
From -> From ->
@ -83,7 +82,7 @@ do_ltrim(B, _X) ->
%% multi, left trimming. %% multi, left trimming.
do_mltrim(<<X, B/binary>> = XB, Xs) -> do_mltrim(<<X, B/binary>> = XB, Xs) ->
case ordsets:is_element(X, Xs) of case ordsets:is_element(X, Xs) of
true -> do_mltrim(B, Xs); true -> do_mltrim(B, Xs);
false -> XB false -> XB
end; end;
do_mltrim(<<>>, _Xs) -> do_mltrim(<<>>, _Xs) ->
@ -105,20 +104,19 @@ do_mrtrim(S, B, Xs) ->
S2 = S - 1, S2 = S - 1,
X = binary:at(B, S2), X = binary:at(B, S2),
case ordsets:is_element(X, Xs) of 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) false -> binary_part(B, 0, S)
end. end.
ltrimc(<<X, B/binary>>, X, C) -> ltrimc(<<X, B/binary>>, X, C) ->
ltrimc(B, X, C+1); ltrimc(B, X, C + 1);
ltrimc(_B, _X, C) -> ltrimc(_B, _X, C) ->
C. C.
%% multi, left trimming, returns a count of matched bytes from the left. %% multi, left trimming, returns a count of matched bytes from the left.
mltrimc(<<X, B/binary>>, Xs, C) -> mltrimc(<<X, B/binary>>, Xs, C) ->
case ordsets:is_element(X, Xs) of case ordsets:is_element(X, Xs) of
true -> mltrimc(B, Xs, C+1); true -> mltrimc(B, Xs, C + 1);
false -> C false -> C
end; end;
mltrimc(<<>>, _Xs, C) -> mltrimc(<<>>, _Xs, C) ->
@ -138,7 +136,7 @@ do_mrtrimc(S, B, Xs) ->
S2 = S - 1, S2 = S - 1,
X = binary:at(B, S2), X = binary:at(B, S2),
case ordsets:is_element(X, Xs) of case ordsets:is_element(X, Xs) of
true -> do_mrtrimc(S2, B, Xs); true -> do_mrtrimc(S2, B, Xs);
false -> S false -> S
end. end.
@ -148,13 +146,12 @@ reverse(Bin) when is_binary(Bin) ->
<<V:S/integer-little>> = Bin, <<V:S/integer-little>> = Bin,
<<V:S/integer-big>>. <<V:S/integer-big>>.
join([B|Bs], Sep) when is_binary(Sep) -> join([B | Bs], Sep) when is_binary(Sep) ->
iolist_to_binary([B|add_separator(Bs, Sep)]); iolist_to_binary([B | add_separator(Bs, Sep)]);
join([], _Sep) -> join([], _Sep) ->
<<>>. <<>>.
add_separator([B|Bs], Sep) -> add_separator([B | Bs], Sep) ->
[Sep, B | add_separator(Bs, Sep)]; [Sep, B | add_separator(Bs, Sep)];
add_separator([], _) -> 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 -> suffix(B, L) when is_binary(B), is_integer(L), L > 0 ->
S = byte_size(B), S = byte_size(B),
binary:part(B, S-L, L). binary:part(B, S - L, L).
union(B1, B2) -> union(B1, B2) ->
S = bit_size(B1), S = bit_size(B1),
@ -203,7 +199,7 @@ bin_to_int(Bin) ->
bin_to_int(Bin, 0). bin_to_int(Bin, 0).
bin_to_int(<<H, T/binary>>, X) when $0 =< H, H =< $9 -> bin_to_int(<<H, T/binary>>, 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) -> bin_to_int(Bin, X) ->
{X, Bin}. {X, Bin}.
@ -213,10 +209,10 @@ optimize_patterns(Patterns) ->
Sorted = lists:usort(Patterns), Sorted = lists:usort(Patterns),
remove_long_duplicates(Sorted). remove_long_duplicates(Sorted).
remove_long_duplicates([H|T]) -> remove_long_duplicates([H | T]) ->
%% match(Subject, Pattern) %% match(Subject, Pattern)
DedupT = [X || X <- T, binary:match(X, H) =:= nomatch], DedupT = [X || X <- T, binary:match(X, H) =:= nomatch],
[H|remove_long_duplicates(DedupT)]; [H | remove_long_duplicates(DedupT)];
remove_long_duplicates([]) -> remove_long_duplicates([]) ->
[]. [].
@ -224,7 +220,5 @@ join_path(PathList) ->
join_path(PathList, <<>>). join_path(PathList, <<>>).
join_path([], Result) -> Result; join_path([], Result) -> Result;
join_path([<<>> | PathList], Result) -> join_path([<<>> | PathList], Result) -> join_path(PathList, Result);
join_path(PathList, Result); join_path([Path | PathList], Result) -> join_path(PathList, <<Result/binary, "/", Path/binary>>).
join_path([Path | PathList], Result) ->
join_path(PathList, <<Result/binary, "/", Path/binary>>).

View File

@ -23,7 +23,7 @@
-export([lookup_cmd/2, observe/2, read/2, write/2]). -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']). -define(DATA_TYPE, ['Integer', 'Float', 'Time', 'String', 'Boolean', 'Opaque', 'Objlnk']).
-import(hoconsc, [mk/2, ref/1, ref/2]). -import(hoconsc, [mk/2, ref/1, ref/2]).
@ -104,8 +104,11 @@ schema(?PATH("/write")) ->
parameters => [ parameters => [
{clientid, mk(binary(), #{in => path, example => "urn:oma:lwm2m:oma:2"})}, {clientid, mk(binary(), #{in => path, example => "urn:oma:lwm2m:oma:2"})},
{path, mk(binary(), #{in => query, required => true, example => "/3/0/7"})}, {path, mk(binary(), #{in => query, required => true, example => "/3/0/7"})},
{type, mk(hoconsc:enum(?DATA_TYPE), {type,
#{in => query, required => true, example => 'Integer'})}, mk(
hoconsc:enum(?DATA_TYPE),
#{in => query, required => true, example => 'Integer'}
)},
{value, mk(binary(), #{in => query, required => true, example => 123})} {value, mk(binary(), #{in => query, required => true, example => 123})}
], ],
responses => #{ responses => #{
@ -118,18 +121,23 @@ schema(?PATH("/write")) ->
fields(resource) -> fields(resource) ->
[ [
{operations, mk(binary(), #{desc => <<"Resource Operations">>, example => "E"})}, {operations, mk(binary(), #{desc => <<"Resource Operations">>, example => "E"})},
{'dataType', mk(hoconsc:enum(?DATA_TYPE), #{desc => <<"Data Type">>, {'dataType',
example => 'Integer'})}, mk(hoconsc:enum(?DATA_TYPE), #{
{path, mk(binary(), #{desc => <<"Resource Path">>, example => "urn:oma:lwm2m:oma:2"})}, desc => <<"Data Type">>,
{name, mk(binary(), #{desc => <<"Resource Name">>, example => "lwm2m-test"})} 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}) -> lookup_cmd(get, #{bindings := Bindings, query_string := QS}) ->
ClientId = maps:get(clientid, Bindings), ClientId = maps:get(clientid, Bindings),
case emqx_gateway_cm_registry:lookup_channels(lwm2m, ClientId) of case emqx_gateway_cm_registry:lookup_channels(lwm2m, ClientId) of
[Channel | _] -> [Channel | _] ->
#{<<"path">> := Path, #{
<<"action">> := Action} = QS, <<"path">> := Path,
<<"action">> := Action
} = QS,
{ok, Result} = emqx_lwm2m_channel:lookup_cmd(Channel, Path, Action), {ok, Result} = emqx_lwm2m_channel:lookup_cmd(Channel, Path, Action),
lookup_cmd_return(Result, ClientId, Action, Path); lookup_cmd_return(Result, ClientId, Action, Path);
_ -> _ ->
@ -137,63 +145,73 @@ lookup_cmd(get, #{bindings := Bindings, query_string := QS}) ->
end. end.
lookup_cmd_return(undefined, ClientId, Action, Path) -> lookup_cmd_return(undefined, ClientId, Action, Path) ->
{200, {200, #{
#{clientid => ClientId, clientid => ClientId,
action => Action, action => Action,
code => <<"6.01">>, code => <<"6.01">>,
codeMsg => <<"reply_not_received">>, codeMsg => <<"reply_not_received">>,
path => Path}}; path => Path
}};
lookup_cmd_return({Code, CodeMsg, Content}, ClientId, Action, Path) -> lookup_cmd_return({Code, CodeMsg, Content}, ClientId, Action, Path) ->
{200, {200,
format_cmd_content(Content, format_cmd_content(
Action, Content,
#{clientid => ClientId, Action,
action => Action, #{
code => Code, clientid => ClientId,
codeMsg => CodeMsg, action => Action,
path => Path})}. code => Code,
codeMsg => CodeMsg,
path => Path
}
)}.
format_cmd_content(undefined, _MsgType, Result) -> format_cmd_content(undefined, _MsgType, Result) ->
Result; Result;
format_cmd_content(Content, <<"discover">>, Result) -> format_cmd_content(Content, <<"discover">>, Result) ->
[H | Content1] = Content, [H | Content1] = Content,
{_, [HObjId]} = emqx_lwm2m_session:parse_object_list(H), {_, [HObjId]} = emqx_lwm2m_session:parse_object_list(H),
[ObjId | _]= path_list(HObjId), [ObjId | _] = path_list(HObjId),
ObjectList = ObjectList =
case Content1 of case Content1 of
[Content2 | _] -> [Content2 | _] ->
{_, ObjL} = emqx_lwm2m_session:parse_object_list(Content2), {_, ObjL} = emqx_lwm2m_session:parse_object_list(Content2),
ObjL; ObjL;
[] -> [] [] ->
[]
end, 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, _} -> {error, _} ->
lists:map(fun(Object) -> #{Object => Object} end, ObjectList); lists:map(fun(Object) -> #{Object => Object} end, ObjectList);
ObjDefinition -> ObjDefinition ->
lists:map(fun(Obj) -> to_operations(Obj, ObjDefinition) end, ObjectList) lists:map(fun(Obj) -> to_operations(Obj, ObjDefinition) end, ObjectList)
end, end,
Result#{content => R}; Result#{content => R};
format_cmd_content(Content, _, Result) -> format_cmd_content(Content, _, Result) ->
Result#{content => Content}. Result#{content => Content}.
to_operations(Obj, ObjDefinition) -> to_operations(Obj, ObjDefinition) ->
[_, _, RawResId| _] = path_list(Obj), [_, _, RawResId | _] = path_list(Obj),
ResId = binary_to_integer(RawResId), ResId = binary_to_integer(RawResId),
Operations = Operations =
case emqx_lwm2m_xml_object:get_resource_operations(ResId, ObjDefinition) of case emqx_lwm2m_xml_object:get_resource_operations(ResId, ObjDefinition) of
"E" -> #{operations => <<"E">>}; "E" ->
#{operations => <<"E">>};
Oper -> 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) operations => list_to_binary(Oper)
} }
end, end,
Operations#{path => Obj, Operations#{
name => list_to_binary(emqx_lwm2m_xml_object:get_resource_name(ResId, ObjDefinition))}. path => Obj,
name => list_to_binary(emqx_lwm2m_xml_object:get_resource_name(ResId, ObjDefinition))
}.
path_list(Path) -> path_list(Path) ->
case binary:split(binary_util:trim(Path, $/), [<<$/>>], [global]) of case binary:split(binary_util:trim(Path, $/), [<<$/>>], [global]) of
@ -203,35 +221,42 @@ path_list(Path) ->
[ObjId] -> [ObjId] [ObjId] -> [ObjId]
end. end.
observe(post, #{bindings := #{clientid := ClientId}, observe(post, #{
query_string := #{<<"path">> := Path, <<"enable">> := Enable}}) -> bindings := #{clientid := ClientId},
MsgType = case Enable of query_string := #{<<"path">> := Path, <<"enable">> := Enable}
true -> <<"observe">>; }) ->
_ -> <<"cancel-observe">> MsgType =
end, case Enable of
true -> <<"observe">>;
_ -> <<"cancel-observe">>
end,
Cmd = #{<<"msgType">> => MsgType, Cmd = #{
<<"data">> => #{<<"path">> => Path} <<"msgType">> => MsgType,
}, <<"data">> => #{<<"path">> => Path}
},
send_cmd(ClientId, Cmd). send_cmd(ClientId, Cmd).
read(post, #{
read(post, #{bindings := #{clientid := ClientId}, bindings := #{clientid := ClientId},
query_string := Qs}) -> query_string := Qs
}) ->
Cmd = #{<<"msgType">> => <<"read">>, Cmd = #{
<<"data">> => Qs <<"msgType">> => <<"read">>,
}, <<"data">> => Qs
},
send_cmd(ClientId, Cmd). send_cmd(ClientId, Cmd).
write(post, #{bindings := #{clientid := ClientId}, write(post, #{
query_string := Qs}) -> bindings := #{clientid := ClientId},
query_string := Qs
Cmd = #{<<"msgType">> => <<"write">>, }) ->
<<"data">> => Qs Cmd = #{
}, <<"msgType">> => <<"write">>,
<<"data">> => Qs
},
send_cmd(ClientId, Cmd). send_cmd(ClientId, Cmd).

View File

@ -21,62 +21,69 @@
-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include("src/lwm2m/include/emqx_lwm2m.hrl").
%% API %% API
-export([ info/1 -export([
, info/2 info/1,
, stats/1 info/2,
, with_context/2 stats/1,
, do_takeover/3 with_context/2,
, lookup_cmd/3 do_takeover/3,
, send_cmd/2 lookup_cmd/3,
]). send_cmd/2
]).
-export([ init/2 -export([
, handle_in/2 init/2,
, handle_deliver/2 handle_in/2,
, handle_timeout/3 handle_deliver/2,
, terminate/2 handle_timeout/3,
]). terminate/2
]).
-export([ handle_call/3 -export([
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
]). handle_info/2
]).
-record(channel, { -record(channel, {
%% Context %% Context
ctx :: emqx_gateway_ctx:context(), ctx :: emqx_gateway_ctx:context(),
%% Connection Info %% Connection Info
conninfo :: emqx_types:conninfo(), conninfo :: emqx_types:conninfo(),
%% Client Info %% Client Info
clientinfo :: emqx_types:clientinfo(), clientinfo :: emqx_types:clientinfo(),
%% Session %% Session
session :: emqx_lwm2m_session:session() | undefined, session :: emqx_lwm2m_session:session() | undefined,
%% Channel State %% Channel State
%% TODO: is there need %% TODO: is there need
conn_state :: conn_state(), conn_state :: conn_state(),
%% Timer %% Timer
timers :: #{atom() => disable | undefined | reference()}, timers :: #{atom() => disable | undefined | reference()},
%% FIXME: don't store anonymous func %% FIXME: don't store anonymous func
with_context :: function() with_context :: function()
}). }).
-type channel() :: #channel{}. -type channel() :: #channel{}.
-type conn_state() :: idle | connecting | connected | disconnected. -type conn_state() :: idle | connecting | connected | disconnected.
-type reply() :: {outgoing, coap_message()} -type reply() ::
| {outgoing, [coap_message()]} {outgoing, coap_message()}
| {event, conn_state()|updated} | {outgoing, [coap_message()]}
| {close, Reason :: atom()}. | {event, conn_state() | updated}
| {close, Reason :: atom()}.
-type replies() :: reply() | [reply()]. -type replies() :: reply() | [reply()].
%% TODO: %% TODO:
-define(DEFAULT_OVERRIDE, -define(DEFAULT_OVERRIDE,
#{ clientid => <<"">> %% Generate clientid by default %% Generate clientid by default
, username => <<"${Packet.uri_query.ep}">> #{
, password => <<"">> clientid => <<"">>,
}). username => <<"${Packet.uri_query.ep}">>,
password => <<"">>
}
).
-define(INFO_KEYS, [conninfo, conn_state, clientinfo, session]). -define(INFO_KEYS, [conninfo, conn_state, clientinfo, session]).
@ -91,7 +98,6 @@ info(Channel) ->
info(Keys, Channel) when is_list(Keys) -> info(Keys, Channel) when is_list(Keys) ->
[{Key, info(Key, Channel)} || Key <- Keys]; [{Key, info(Key, Channel)} || Key <- Keys];
info(conninfo, #channel{conninfo = ConnInfo}) -> info(conninfo, #channel{conninfo = ConnInfo}) ->
ConnInfo; ConnInfo;
info(conn_state, #channel{conn_state = ConnState}) -> info(conn_state, #channel{conn_state = ConnState}) ->
@ -108,39 +114,45 @@ info(ctx, #channel{ctx = Ctx}) ->
stats(_) -> stats(_) ->
[]. [].
init(ConnInfo = #{peername := {PeerHost, _}, init(
sockname := {_, SockPort}}, ConnInfo = #{
#{ctx := Ctx} = Config) -> peername := {PeerHost, _},
sockname := {_, SockPort}
},
#{ctx := Ctx} = Config
) ->
Peercert = maps:get(peercert, ConnInfo, undefined), Peercert = maps:get(peercert, ConnInfo, undefined),
Mountpoint = maps:get(mountpoint, Config, undefined), Mountpoint = maps:get(mountpoint, Config, undefined),
ListenerId = case maps:get(listener, Config, undefined) of ListenerId =
undefined -> undefined; case maps:get(listener, Config, undefined) of
{GwName, Type, LisName} -> undefined -> undefined;
emqx_gateway_utils:listener_id(GwName, Type, LisName) {GwName, Type, LisName} -> emqx_gateway_utils:listener_id(GwName, Type, LisName)
end, end,
ClientInfo = set_peercert_infos( ClientInfo = set_peercert_infos(
Peercert, Peercert,
#{ zone => default #{
, listener => ListenerId zone => default,
, protocol => lwm2m listener => ListenerId,
, peerhost => PeerHost protocol => lwm2m,
, sockport => SockPort peerhost => PeerHost,
, username => undefined sockport => SockPort,
, clientid => undefined username => undefined,
, is_bridge => false clientid => undefined,
, is_superuser => false is_bridge => false,
, mountpoint => Mountpoint is_superuser => false,
} mountpoint => Mountpoint
), }
),
#channel{ ctx = Ctx #channel{
, conninfo = ConnInfo ctx = Ctx,
, clientinfo = ClientInfo conninfo = ConnInfo,
, timers = #{} clientinfo = ClientInfo,
, session = emqx_lwm2m_session:new() timers = #{},
, conn_state = idle session = emqx_lwm2m_session:new(),
, with_context = with_context(Ctx, ClientInfo) conn_state = idle,
}. with_context = with_context(Ctx, ClientInfo)
}.
lookup_cmd(Channel, Path, Action) -> lookup_cmd(Channel, Path, Action) ->
gen_server:call(Channel, {?FUNCTION_NAME, Path, Action}). gen_server:call(Channel, {?FUNCTION_NAME, Path, Action}).
@ -152,11 +164,11 @@ send_cmd(Channel, Cmd) ->
%% Handle incoming packet %% Handle incoming packet
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec handle_in(coap_message() | {frame_error, any()}, channel()) -spec handle_in(coap_message() | {frame_error, any()}, channel()) ->
-> {ok, channel()} {ok, channel()}
| {ok, replies(), channel()} | {ok, replies(), channel()}
| {shutdown, Reason :: term(), channel()} | {shutdown, Reason :: term(), channel()}
| {shutdown, Reason :: term(), replies(), channel()}. | {shutdown, Reason :: term(), replies(), channel()}.
handle_in(Msg, Channle) -> handle_in(Msg, Channle) ->
NChannel = update_life_timer(Channle), NChannel = update_life_timer(Channle),
call_session(handle_coap_in, Msg, NChannel). call_session(handle_coap_in, Msg, NChannel).
@ -170,18 +182,21 @@ handle_deliver(Delivers, Channel) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle timeout %% Handle timeout
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_timeout(_, lifetime, #channel{ctx = Ctx, handle_timeout(
clientinfo = ClientInfo, _,
conninfo = ConnInfo} = Channel) -> lifetime,
#channel{
ctx = Ctx,
clientinfo = ClientInfo,
conninfo = ConnInfo
} = Channel
) ->
ok = run_hooks(Ctx, 'client.disconnected', [ClientInfo, timeout, ConnInfo]), ok = run_hooks(Ctx, 'client.disconnected', [ClientInfo, timeout, ConnInfo]),
{shutdown, timeout, Channel}; {shutdown, timeout, Channel};
handle_timeout(_, {transport, _} = Msg, Channel) -> handle_timeout(_, {transport, _} = Msg, Channel) ->
call_session(timeout, Msg, Channel); call_session(timeout, Msg, Channel);
handle_timeout(_, disconnect, Channel) -> handle_timeout(_, disconnect, Channel) ->
{shutdown, normal, Channel}; {shutdown, normal, Channel};
handle_timeout(_, _, Channel) -> handle_timeout(_, _, Channel) ->
{ok, Channel}. {ok, Channel}.
@ -189,63 +204,78 @@ handle_timeout(_, _, Channel) ->
%% Handle call %% Handle call
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_call({lookup_cmd, Path, Type}, _From, handle_call(
Channel = #channel{session = Session}) -> {lookup_cmd, Path, Type},
_From,
Channel = #channel{session = Session}
) ->
Result = emqx_lwm2m_session:find_cmd_record(Path, Type, Session), Result = emqx_lwm2m_session:find_cmd_record(Path, Type, Session),
{reply, {ok, Result}, Channel}; {reply, {ok, Result}, Channel};
handle_call({send_cmd, Cmd}, _From, Channel) -> handle_call({send_cmd, Cmd}, _From, Channel) ->
{ok, Outs, Channel2} = call_session(send_cmd, Cmd, Channel), {ok, Outs, Channel2} = call_session(send_cmd, Cmd, Channel),
{reply, ok, Outs, Channel2}; {reply, ok, Outs, Channel2};
handle_call(
handle_call({subscribe, Topic, SubOpts}, _From, {subscribe, Topic, SubOpts},
Channel = #channel{ _From,
ctx = Ctx, Channel = #channel{
clientinfo = ClientInfo ctx = Ctx,
= #{clientid := ClientId, clientinfo =
mountpoint := Mountpoint}, ClientInfo =
session = Session}) -> #{
clientid := ClientId,
mountpoint := Mountpoint
},
session = Session
}
) ->
NSubOpts = maps:merge( NSubOpts = maps:merge(
emqx_gateway_utils:default_subopts(), emqx_gateway_utils:default_subopts(),
SubOpts), SubOpts
),
MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic),
_ = emqx_broker:subscribe(MountedTopic, ClientId, NSubOpts), _ = emqx_broker:subscribe(MountedTopic, ClientId, NSubOpts),
_ = run_hooks(Ctx, 'session.subscribed', _ = run_hooks(
[ClientInfo, MountedTopic, NSubOpts]), Ctx,
'session.subscribed',
[ClientInfo, MountedTopic, NSubOpts]
),
%% modify session state %% modify session state
Subs = emqx_lwm2m_session:info(subscriptions, Session), Subs = emqx_lwm2m_session:info(subscriptions, Session),
NSubs = maps:put(MountedTopic, NSubOpts, Subs), NSubs = maps:put(MountedTopic, NSubOpts, Subs),
NSession = emqx_lwm2m_session:set_subscriptions(NSubs, Session), NSession = emqx_lwm2m_session:set_subscriptions(NSubs, Session),
{reply, {ok, {MountedTopic, NSubOpts}}, Channel#channel{session = NSession}}; {reply, {ok, {MountedTopic, NSubOpts}}, Channel#channel{session = NSession}};
handle_call(
handle_call({unsubscribe, Topic}, _From, {unsubscribe, Topic},
Channel = #channel{ _From,
ctx = Ctx, Channel = #channel{
clientinfo = ClientInfo ctx = Ctx,
= #{mountpoint := Mountpoint}, clientinfo =
session = Session}) -> ClientInfo =
#{mountpoint := Mountpoint},
session = Session
}
) ->
MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic), MountedTopic = emqx_mountpoint:mount(Mountpoint, Topic),
ok = emqx_broker:unsubscribe(MountedTopic), ok = emqx_broker:unsubscribe(MountedTopic),
_ = run_hooks(Ctx, 'session.unsubscribe', _ = run_hooks(
[ClientInfo, MountedTopic, #{}]), Ctx,
'session.unsubscribe',
[ClientInfo, MountedTopic, #{}]
),
%% modify session state %% modify session state
Subs = emqx_lwm2m_session:info(subscriptions, Session), Subs = emqx_lwm2m_session:info(subscriptions, Session),
NSubs = maps:remove(MountedTopic, Subs), NSubs = maps:remove(MountedTopic, Subs),
NSession = emqx_lwm2m_session:set_subscriptions(NSubs, Session), NSession = emqx_lwm2m_session:set_subscriptions(NSubs, Session),
{reply, ok, Channel#channel{session = NSession}}; {reply, ok, Channel#channel{session = NSession}};
handle_call(subscriptions, _From, Channel = #channel{session = Session}) -> handle_call(subscriptions, _From, Channel = #channel{session = Session}) ->
Subs = maps:to_list(emqx_lwm2m_session:info(subscriptions, Session)), Subs = maps:to_list(emqx_lwm2m_session:info(subscriptions, Session)),
{reply, {ok, Subs}, Channel}; {reply, {ok, Subs}, Channel};
handle_call(kick, _From, Channel) -> handle_call(kick, _From, Channel) ->
NChannel = ensure_disconnected(kicked, Channel), NChannel = ensure_disconnected(kicked, Channel),
shutdown_and_reply(kicked, ok, NChannel); shutdown_and_reply(kicked, ok, NChannel);
handle_call(discard, _From, Channel) -> handle_call(discard, _From, Channel) ->
shutdown_and_reply(discarded, ok, Channel); shutdown_and_reply(discarded, ok, Channel);
%% TODO: No Session Takeover %% TODO: No Session Takeover
%handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) -> %handle_call({takeover, 'begin'}, _From, Channel = #channel{session = Session}) ->
% reply(Session, Channel#channel{takeover = true}); % reply(Session, Channel#channel{takeover = true});
@ -259,18 +289,20 @@ handle_call(discard, _From, Channel) ->
% shutdown_and_reply(takenover, AllPendings, Channel); % shutdown_and_reply(takenover, AllPendings, Channel);
handle_call(Req, _From, Channel) -> handle_call(Req, _From, Channel) ->
?SLOG(error, #{ msg => "unexpected_call" ?SLOG(error, #{
, call => Req msg => "unexpected_call",
}), call => Req
}),
{reply, ignored, Channel}. {reply, ignored, Channel}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Handle Cast %% Handle Cast
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_cast(Req, Channel) -> handle_cast(Req, Channel) ->
?SLOG(error, #{ msg => "unexpected_cast" ?SLOG(error, #{
, cast => Req msg => "unexpected_cast",
}), cast => Req
}),
{ok, Channel}. {ok, Channel}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -279,19 +311,21 @@ handle_cast(Req, Channel) ->
handle_info({subscribe, _AutoSubs}, Channel) -> handle_info({subscribe, _AutoSubs}, Channel) ->
%% not need handle this message %% not need handle this message
{ok, Channel}; {ok, Channel};
handle_info(Info, Channel) -> handle_info(Info, Channel) ->
?SLOG(error, #{ msg => "unexpected_info" ?SLOG(error, #{
, info => Info msg => "unexpected_info",
}), info => Info
}),
{ok, Channel}. {ok, Channel}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Terminate %% Terminate
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
terminate(Reason, #channel{ctx = Ctx, terminate(Reason, #channel{
clientinfo = ClientInfo, ctx = Ctx,
session = Session}) -> clientinfo = ClientInfo,
session = Session
}) ->
MountedTopic = emqx_lwm2m_session:on_close(Session), MountedTopic = emqx_lwm2m_session:on_close(Session),
_ = run_hooks(Ctx, 'session.unsubscribe', [ClientInfo, MountedTopic, #{}]), _ = run_hooks(Ctx, 'session.unsubscribe', [ClientInfo, MountedTopic, #{}]),
run_hooks(Ctx, 'session.terminated', [ClientInfo, Reason, Session]). run_hooks(Ctx, 'session.terminated', [ClientInfo, Reason, Session]).
@ -303,29 +337,39 @@ terminate(Reason, #channel{ctx = Ctx,
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Ensure connected %% Ensure connected
ensure_connected(Channel = #channel{ ensure_connected(
ctx = Ctx, Channel = #channel{
conninfo = ConnInfo, ctx = Ctx,
clientinfo = ClientInfo}) -> conninfo = ConnInfo,
clientinfo = ClientInfo
}
) ->
_ = run_hooks(Ctx, 'client.connack', [ConnInfo, connection_accepted, []]), _ = run_hooks(Ctx, 'client.connack', [ConnInfo, connection_accepted, []]),
NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)}, NConnInfo = ConnInfo#{connected_at => erlang:system_time(millisecond)},
ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]), ok = run_hooks(Ctx, 'client.connected', [ClientInfo, NConnInfo]),
Channel#channel{ Channel#channel{
conninfo = NConnInfo, conninfo = NConnInfo,
conn_state = connected conn_state = connected
}. }.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Ensure disconnected %% Ensure disconnected
ensure_disconnected(Reason, Channel = #channel{ ensure_disconnected(
ctx = Ctx, Reason,
conninfo = ConnInfo, Channel = #channel{
clientinfo = ClientInfo}) -> ctx = Ctx,
conninfo = ConnInfo,
clientinfo = ClientInfo
}
) ->
NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)}, NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)},
ok = run_hooks(Ctx, 'client.disconnected', ok = run_hooks(
[ClientInfo, Reason, NConnInfo]), Ctx,
'client.disconnected',
[ClientInfo, Reason, NConnInfo]
),
Channel#channel{conninfo = NConnInfo, conn_state = disconnected}. Channel#channel{conninfo = NConnInfo, conn_state = disconnected}.
shutdown_and_reply(Reason, Reply, Channel) -> shutdown_and_reply(Reason, Reply, Channel) ->
@ -334,13 +378,13 @@ shutdown_and_reply(Reason, Reply, Channel) ->
%shutdown_and_reply(Reason, Reply, OutPkt, Channel) -> %shutdown_and_reply(Reason, Reply, OutPkt, Channel) ->
% {shutdown, Reason, Reply, OutPkt, Channel}. % {shutdown, Reason, Reply, OutPkt, Channel}.
set_peercert_infos(NoSSL, ClientInfo) set_peercert_infos(NoSSL, ClientInfo) when
when NoSSL =:= nossl; NoSSL =:= nossl;
NoSSL =:= undefined -> NoSSL =:= undefined
->
ClientInfo; ClientInfo;
set_peercert_infos(Peercert, ClientInfo) -> set_peercert_infos(Peercert, ClientInfo) ->
{DN, CN} = {esockd_peercert:subject(Peercert), {DN, CN} = {esockd_peercert:subject(Peercert), esockd_peercert:common_name(Peercert)},
esockd_peercert:common_name(Peercert)},
ClientInfo#{dn => DN, cn => CN}. ClientInfo#{dn => DN, cn => CN}.
make_timer(Name, Time, Msg, Channel = #channel{timers = Timers}) -> 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) -> update_life_timer(#channel{session = Session, timers = Timers} = Channel) ->
LifeTime = emqx_lwm2m_session:info(lifetime, Session), LifeTime = emqx_lwm2m_session:info(lifetime, Session),
_ = case maps:get(lifetime, Timers, undefined) of _ =
case maps:get(lifetime, Timers, undefined) of
undefined -> ok; undefined -> ok;
Ref -> erlang:cancel_timer(Ref) Ref -> erlang:cancel_timer(Ref)
end, end,
@ -365,18 +410,25 @@ do_takeover(_DesireId, Msg, Channel) ->
call_session(handle_out, Reset, Channel). call_session(handle_out, Reset, Channel).
do_connect(Req, Result, Channel, Iter) -> do_connect(Req, Result, Channel, Iter) ->
case emqx_misc:pipeline( case
[ fun check_lwm2m_version/2 emqx_misc:pipeline(
, fun enrich_conninfo/2 [
, fun run_conn_hooks/2 fun check_lwm2m_version/2,
, fun enrich_clientinfo/2 fun enrich_conninfo/2,
, fun set_log_meta/2 fun run_conn_hooks/2,
, fun auth_connect/2 fun enrich_clientinfo/2,
], fun set_log_meta/2,
Req, fun auth_connect/2
Channel) of ],
{ok, _Input, #channel{session = Session, Req,
with_context = WithContext} = NChannel} -> Channel
)
of
{ok, _Input,
#channel{
session = Session,
with_context = WithContext
} = NChannel} ->
case emqx_lwm2m_session:info(reg_info, Session) of case emqx_lwm2m_session:info(reg_info, Session) of
undefined -> undefined ->
process_connect(ensure_connected(NChannel), Req, Result, Iter); process_connect(ensure_connected(NChannel), Req, Result, Iter);
@ -387,66 +439,84 @@ do_connect(Req, Result, Channel, Iter) ->
{error, ReasonCode, NChannel} -> {error, ReasonCode, NChannel} ->
ErrMsg = io_lib:format("Login Failed: ~ts", [ReasonCode]), ErrMsg = io_lib:format("Login Failed: ~ts", [ReasonCode]),
Payload = erlang:list_to_binary(lists:flatten(ErrMsg)), Payload = erlang:list_to_binary(lists:flatten(ErrMsg)),
iter(Iter, iter(
reply({error, bad_request}, Payload, Req, Result), Iter,
NChannel) reply({error, bad_request}, Payload, Req, Result),
NChannel
)
end. end.
check_lwm2m_version(#coap_message{options = Opts}, check_lwm2m_version(
#channel{conninfo = ConnInfo} = Channel) -> #coap_message{options = Opts},
#channel{conninfo = ConnInfo} = Channel
) ->
Ver = gets([uri_query, <<"lwm2m">>], Opts), Ver = gets([uri_query, <<"lwm2m">>], Opts),
IsValid = case Ver of IsValid =
<<"1.0">> -> case Ver of
true; <<"1.0">> ->
<<"1">> -> true;
true; <<"1">> ->
<<"1.1">> -> true;
true; <<"1.1">> ->
_ -> true;
false _ ->
end, false
if IsValid -> end,
NConnInfo = ConnInfo#{ connected_at => erlang:system_time(millisecond) if
, proto_ver => Ver IsValid ->
}, NConnInfo = ConnInfo#{
connected_at => erlang:system_time(millisecond),
proto_ver => Ver
},
{ok, Channel#channel{conninfo = NConnInfo}}; {ok, Channel#channel{conninfo = NConnInfo}};
true -> true ->
?SLOG(error, #{ msg => "reject_REGISTRE_request" ?SLOG(error, #{
, reason => {unsupported_version, Ver} msg => "reject_REGISTRE_request",
}), reason => {unsupported_version, Ver}
}),
{error, "invalid lwm2m version", Channel} {error, "invalid lwm2m version", Channel}
end. end.
run_conn_hooks(Input, Channel = #channel{ctx = Ctx, run_conn_hooks(
conninfo = ConnInfo}) -> Input,
Channel = #channel{
ctx = Ctx,
conninfo = ConnInfo
}
) ->
ConnProps = #{}, ConnProps = #{},
case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of case run_hooks(Ctx, 'client.connect', [ConnInfo], ConnProps) of
Error = {error, _Reason} -> Error; Error = {error, _Reason} -> Error;
_NConnProps -> _NConnProps -> {ok, Input, Channel}
{ok, Input, Channel}
end. end.
enrich_conninfo(#coap_message{options = Options}, enrich_conninfo(
Channel = #channel{ #coap_message{options = Options},
conninfo = ConnInfo}) -> Channel = #channel{
conninfo = ConnInfo
}
) ->
Query = maps:get(uri_query, Options, #{}), Query = maps:get(uri_query, Options, #{}),
case Query of case Query of
#{<<"ep">> := Epn, <<"lt">> := Lifetime} -> #{<<"ep">> := Epn, <<"lt">> := Lifetime} ->
ClientId = maps:get(<<"device_id">>, Query, Epn), ClientId = maps:get(<<"device_id">>, Query, Epn),
NConnInfo = ConnInfo#{ clientid => ClientId NConnInfo = ConnInfo#{
, proto_name => <<"LwM2M">> clientid => ClientId,
, proto_ver => <<"1.0.1">> proto_name => <<"LwM2M">>,
, clean_start => true proto_ver => <<"1.0.1">>,
, keepalive => binary_to_integer(Lifetime) clean_start => true,
, expiry_interval => 0 keepalive => binary_to_integer(Lifetime),
}, expiry_interval => 0
},
{ok, Channel#channel{conninfo = NConnInfo}}; {ok, Channel#channel{conninfo = NConnInfo}};
_ -> _ ->
{error, "invalid queries", Channel} {error, "invalid queries", Channel}
end. end.
enrich_clientinfo(#coap_message{options = Options} = Msg, enrich_clientinfo(
Channel = #channel{clientinfo = ClientInfo0}) -> #coap_message{options = Options} = Msg,
Channel = #channel{clientinfo = ClientInfo0}
) ->
Query = maps:get(uri_query, Options, #{}), Query = maps:get(uri_query, Options, #{}),
case Query of case Query of
#{<<"ep">> := Epn, <<"lt">> := Lifetime} -> #{<<"ep">> := Epn, <<"lt">> := Lifetime} ->
@ -455,17 +525,20 @@ enrich_clientinfo(#coap_message{options = Options} = Msg,
Password = maps:get(<<"password">>, Query, undefined), Password = maps:get(<<"password">>, Query, undefined),
ClientId = maps:get(<<"device_id">>, Query, Epn), ClientId = maps:get(<<"device_id">>, Query, Epn),
ClientInfo = ClientInfo =
ClientInfo0#{endpoint_name => Epn, ClientInfo0#{
lifetime => binary_to_integer(Lifetime), endpoint_name => Epn,
username => Username, lifetime => binary_to_integer(Lifetime),
password => Password, username => Username,
clientid => ClientId}, password => Password,
clientid => ClientId
},
{ok, NClientInfo} = fix_mountpoint(Msg, ClientInfo), {ok, NClientInfo} = fix_mountpoint(Msg, ClientInfo),
{ok, Channel#channel{clientinfo = NClientInfo}}; {ok, Channel#channel{clientinfo = NClientInfo}};
_ -> _ ->
?SLOG(error, #{ msg => "reject_REGISTER_request" ?SLOG(error, #{
, reason => {wrong_paramters, Query} msg => "reject_REGISTER_request",
}), reason => {wrong_paramters, Query}
}),
{error, "invalid queries", Channel} {error, "invalid queries", Channel}
end. end.
@ -473,19 +546,27 @@ set_log_meta(_Input, #channel{clientinfo = #{clientid := ClientId}}) ->
emqx_logger:set_metadata_clientid(ClientId), emqx_logger:set_metadata_clientid(ClientId),
ok. ok.
auth_connect(_Input, Channel = #channel{ctx = Ctx, auth_connect(
clientinfo = ClientInfo}) -> _Input,
Channel = #channel{
ctx = Ctx,
clientinfo = ClientInfo
}
) ->
#{clientid := ClientId, username := Username} = ClientInfo, #{clientid := ClientId, username := Username} = ClientInfo,
case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of case emqx_gateway_ctx:authenticate(Ctx, ClientInfo) of
{ok, NClientInfo} -> {ok, NClientInfo} ->
{ok, Channel#channel{clientinfo = NClientInfo, {ok, Channel#channel{
with_context = with_context(Ctx, ClientInfo)}}; clientinfo = NClientInfo,
with_context = with_context(Ctx, ClientInfo)
}};
{error, Reason} -> {error, Reason} ->
?SLOG(warning, #{ msg => "client_login_failed" ?SLOG(warning, #{
, clientid => ClientId msg => "client_login_failed",
, username => Username clientid => ClientId,
, reason => Reason username => Username,
}), reason => Reason
}),
{error, Reason} {error, Reason}
end. end.
@ -495,36 +576,45 @@ fix_mountpoint(_Packet, ClientInfo = #{mountpoint := Mountpoint}) ->
Mountpoint1 = emqx_mountpoint:replvar(Mountpoint, ClientInfo), Mountpoint1 = emqx_mountpoint:replvar(Mountpoint, ClientInfo),
{ok, ClientInfo#{mountpoint := Mountpoint1}}. {ok, ClientInfo#{mountpoint := Mountpoint1}}.
process_connect(Channel = #channel{ctx = Ctx, process_connect(
session = Session, Channel = #channel{
conninfo = ConnInfo, ctx = Ctx,
clientinfo = ClientInfo, session = Session,
with_context = WithContext}, conninfo = ConnInfo,
Msg, Result, Iter) -> clientinfo = ClientInfo,
with_context = WithContext
},
Msg,
Result,
Iter
) ->
%% inherit the old session %% inherit the old session
SessFun = fun(_,_) -> #{} end, SessFun = fun(_, _) -> #{} end,
case emqx_gateway_ctx:open_session( case
Ctx, emqx_gateway_ctx:open_session(
true, Ctx,
ClientInfo, true,
ConnInfo, ClientInfo,
SessFun, ConnInfo,
emqx_lwm2m_session SessFun,
) of emqx_lwm2m_session
)
of
{ok, _} -> {ok, _} ->
Mountpoint = maps:get(mountpoint, ClientInfo, <<>>), Mountpoint = maps:get(mountpoint, ClientInfo, <<>>),
NewResult0 = emqx_lwm2m_session:init( NewResult0 = emqx_lwm2m_session:init(
Msg, Msg,
Mountpoint, Mountpoint,
WithContext, WithContext,
Session Session
), ),
NewResult1 = NewResult0#{events => [{event, connected}]}, NewResult1 = NewResult0#{events => [{event, connected}]},
iter(Iter, maps:merge(Result, NewResult1), Channel); iter(Iter, maps:merge(Result, NewResult1), Channel);
{error, Reason} -> {error, Reason} ->
?SLOG(error, #{ msg => "falied_to_open_session" ?SLOG(error, #{
, reason => Reason msg => "falied_to_open_session",
}), reason => Reason
}),
iter(Iter, reply({error, bad_request}, Msg, Result), Channel) iter(Iter, reply({error, bad_request}, Msg, Result), Channel)
end. end.
@ -557,52 +647,68 @@ with_context(publish, [Topic, Msg], Ctx, ClientInfo) ->
_ = emqx_broker:publish(Msg), _ = emqx_broker:publish(Msg),
ok; ok;
_ -> _ ->
?SLOG(error, #{ msg => "publish_denied" ?SLOG(error, #{
, topic => Topic msg => "publish_denied",
}), topic => Topic
}),
{error, deny} {error, deny}
end; end;
with_context(subscribe, [Topic, Opts], Ctx, ClientInfo) -> 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 case emqx_gateway_ctx:authorize(Ctx, ClientInfo, subscribe, Topic) of
allow -> allow ->
run_hooks(Ctx, 'session.subscribed', [ClientInfo, Topic, Opts]), run_hooks(Ctx, 'session.subscribed', [ClientInfo, Topic, Opts]),
?SLOG(debug, #{ msg => "subscribe_topic_succeed" ?SLOG(debug, #{
, topic => Topic msg => "subscribe_topic_succeed",
, clientid => ClientId topic => Topic,
, endpoint_name => EndpointName clientid => ClientId,
}), endpoint_name => EndpointName
}),
emqx_broker:subscribe(Topic, ClientId, Opts), emqx_broker:subscribe(Topic, ClientId, Opts),
ok; ok;
_ -> _ ->
?SLOG(error, #{ msg => "subscribe_denied" ?SLOG(error, #{
, topic => Topic msg => "subscribe_denied",
}), topic => Topic
}),
{error, deny} {error, deny}
end; end;
with_context(metrics, Name, Ctx, _ClientInfo) -> with_context(metrics, Name, Ctx, _ClientInfo) ->
emqx_gateway_ctx:metrics_inc(Ctx, Name). emqx_gateway_ctx:metrics_inc(Ctx, Name).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Call Chain %% Call Chain
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
call_session(Fun, call_session(
Msg, Fun,
#channel{session = Session, Msg,
with_context = WithContext} = Channel) -> #channel{
iter([ session, fun process_session/4 session = Session,
, proto, fun process_protocol/4 with_context = WithContext
, return, fun process_return/4 } = Channel
, lifetime, fun process_lifetime/4 ) ->
, reply, fun process_reply/4 iter(
, out, fun process_out/4 [
, fun process_nothing/3 session,
], fun process_session/4,
emqx_lwm2m_session:Fun(Msg, WithContext, Session), proto,
Channel). 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) -> process_session(Session, Result, Channel, Iter) ->
iter(Iter, Result, Channel#channel{session = Session}). iter(Iter, Result, Channel#channel{session = Session}).
@ -610,14 +716,22 @@ process_session(Session, Result, Channel, Iter) ->
process_protocol({request, Msg}, Result, Channel, Iter) -> process_protocol({request, Msg}, Result, Channel, Iter) ->
#coap_message{method = Method} = Msg, #coap_message{method = Method} = Msg,
handle_request_protocol(Method, Msg, Result, Channel, Iter); handle_request_protocol(Method, Msg, Result, Channel, Iter);
process_protocol(
process_protocol(Msg, Result, Msg,
#channel{with_context = WithContext, session = Session} = Channel, Iter) -> Result,
#channel{with_context = WithContext, session = Session} = Channel,
Iter
) ->
ProtoResult = emqx_lwm2m_session:handle_protocol_in(Msg, WithContext, Session), ProtoResult = emqx_lwm2m_session:handle_protocol_in(Msg, WithContext, Session),
iter(Iter, maps:merge(Result, ProtoResult), Channel). iter(Iter, maps:merge(Result, ProtoResult), Channel).
handle_request_protocol(post, #coap_message{options = Opts} = Msg, handle_request_protocol(
Result, Channel, Iter) -> post,
#coap_message{options = Opts} = Msg,
Result,
Channel,
Iter
) ->
case Opts of case Opts of
#{uri_path := [?REG_PREFIX]} -> #{uri_path := [?REG_PREFIX]} ->
do_connect(Msg, Result, Channel, Iter); 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) iter(Iter, reply({error, not_found}, Msg, Result), Channel)
end; end;
handle_request_protocol(
handle_request_protocol(delete, #coap_message{options = Opts} = Msg, delete,
Result, Channel, Iter) -> #coap_message{options = Opts} = Msg,
Result,
Channel,
Iter
) ->
case Opts of case Opts of
#{uri_path := Location} -> #{uri_path := Location} ->
case check_location(Location, Channel) of 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) iter(Iter, reply({error, bad_request}, Msg, Result), Channel)
end. end.
do_update(Location, Msg, Result, do_update(
#channel{session = Session, with_context = WithContext} = Channel, Iter) -> Location,
Msg,
Result,
#channel{session = Session, with_context = WithContext} = Channel,
Iter
) ->
case check_location(Location, Channel) of case check_location(Location, Channel) of
true -> true ->
NewResult = emqx_lwm2m_session:update(Msg, WithContext, Session), NewResult = emqx_lwm2m_session:update(Msg, WithContext, Session),
@ -654,18 +777,21 @@ do_update(Location, Msg, Result,
process_return({Outs, Session}, Result, Channel, Iter) -> process_return({Outs, Session}, Result, Channel, Iter) ->
OldOuts = maps:get(out, Result, []), OldOuts = maps:get(out, Result, []),
iter(Iter, iter(
Result#{out => Outs ++ OldOuts}, Iter,
Channel#channel{session = Session}). Result#{out => Outs ++ OldOuts},
Channel#channel{session = Session}
).
process_out(Outs, Result, Channel, _) -> process_out(Outs, Result, Channel, _) ->
Outs2 = lists:reverse(Outs), Outs2 = lists:reverse(Outs),
Outs3 = case maps:get(reply, Result, undefined) of Outs3 =
undefined -> case maps:get(reply, Result, undefined) of
Outs2; undefined ->
Reply -> Outs2;
[Reply | Outs2] Reply ->
end, [Reply | Outs2]
end,
Events = maps:get(events, Result, []), Events = maps:get(events, Result, []),
{ok, [{outgoing, Outs3}] ++ Events, Channel}. {ok, [{outgoing, Outs3}] ++ Events, Channel}.

View File

@ -20,11 +20,12 @@
-include("src/coap/include/emqx_coap.hrl"). -include("src/coap/include/emqx_coap.hrl").
-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include("src/lwm2m/include/emqx_lwm2m.hrl").
-export([ mqtt_to_coap/2 -export([
, coap_to_mqtt/4 mqtt_to_coap/2,
, empty_ack_to_mqtt/1 coap_to_mqtt/4,
, coap_failure_to_mqtt/2 empty_ack_to_mqtt/1,
]). coap_failure_to_mqtt/2
]).
-export([path_list/1, extract_path/1]). -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), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
TlvData = emqx_lwm2m_message:json_to_tlv(PathList, maps:get(<<"content">>, Data)), TlvData = emqx_lwm2m_message:json_to_tlv(PathList, maps:get(<<"content">>, Data)),
Payload = emqx_lwm2m_tlv:encode(TlvData), Payload = emqx_lwm2m_tlv:encode(TlvData),
CoapRequest = emqx_coap_message:request(con, post, Payload, CoapRequest = emqx_coap_message:request(
[{uri_path, FullPathList}, con,
{uri_query, QueryList}, post,
{content_format, <<"application/vnd.oma.lwm2m+tlv">>}]), Payload,
[
{uri_path, FullPathList},
{uri_query, QueryList},
{content_format, <<"application/vnd.oma.lwm2m+tlv">>}
]
),
{CoapRequest, InputCmd}; {CoapRequest, InputCmd};
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"delete">>, <<"data">> := Data}) -> mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"delete">>, <<"data">> := Data}) ->
{PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)),
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
{emqx_coap_message:request(con, delete, <<>>, {
[{uri_path, FullPathList}, emqx_coap_message:request(
{uri_query, QueryList}]), InputCmd}; con,
delete,
<<>>,
[
{uri_path, FullPathList},
{uri_query, QueryList}
]
),
InputCmd
};
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"read">>, <<"data">> := Data}) -> mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"read">>, <<"data">> := Data}) ->
{PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)),
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
{emqx_coap_message:request(con, get, <<>>, {
[{uri_path, FullPathList}, emqx_coap_message:request(
{uri_query, QueryList}]), InputCmd}; con,
get,
<<>>,
[
{uri_path, FullPathList},
{uri_query, QueryList}
]
),
InputCmd
};
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"write">>, <<"data">> := Data}) -> mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"write">>, <<"data">> := Data}) ->
CoapRequest = CoapRequest =
case maps:get(<<"basePath">>, Data, <<"/">>) of 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)) batch_write_request(AlternatePath, BasePath, maps:get(<<"content">>, Data))
end, end,
{CoapRequest, InputCmd}; {CoapRequest, InputCmd};
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"execute">>, <<"data">> := Data}) -> mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"execute">>, <<"data">> := Data}) ->
{PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)),
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
@ -93,85 +114,122 @@ mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"execute">>, <<"data
undefined -> <<>>; undefined -> <<>>;
Arg1 -> Arg1 Arg1 -> Arg1
end, end,
{emqx_coap_message:request(con, post, Args, {
[{uri_path, FullPathList}, emqx_coap_message:request(
{uri_query, QueryList}, con,
{content_format, <<"text/plain">>}]), InputCmd}; post,
Args,
[
{uri_path, FullPathList},
{uri_query, QueryList},
{content_format, <<"text/plain">>}
]
),
InputCmd
};
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"discover">>, <<"data">> := Data}) -> mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"discover">>, <<"data">> := Data}) ->
{PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)),
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
{emqx_coap_message:request(con, get, <<>>, {
[{uri_path, FullPathList}, emqx_coap_message:request(
{uri_query, QueryList}, con,
{'accept', ?LWM2M_FORMAT_LINK}]), InputCmd}; get,
<<>>,
[
{uri_path, FullPathList},
{uri_query, QueryList},
{'accept', ?LWM2M_FORMAT_LINK}
]
),
InputCmd
};
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"write-attr">>, <<"data">> := Data}) -> mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"write-attr">>, <<"data">> := Data}) ->
{PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)),
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
Query = attr_query_list(Data), Query = attr_query_list(Data),
{emqx_coap_message:request(con, put, <<>>, {
[{uri_path, FullPathList}, emqx_coap_message:request(
{uri_query, QueryList}, con,
{uri_query, Query}]), InputCmd}; put,
<<>>,
[
{uri_path, FullPathList},
{uri_query, QueryList},
{uri_query, Query}
]
),
InputCmd
};
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"observe">>, <<"data">> := Data}) -> mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"observe">>, <<"data">> := Data}) ->
{PathList, QueryList} = path_list(maps:get(<<"path">>, Data)), {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)),
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
{emqx_coap_message:request(con, get, <<>>, {
[{uri_path, FullPathList}, emqx_coap_message:request(
{uri_query, QueryList}, con,
{observe, 0}]), InputCmd}; get,
<<>>,
mqtt_to_coap(AlternatePath, InputCmd = #{<<"msgType">> := <<"cancel-observe">>, <<"data">> := Data}) -> [
{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)), {PathList, QueryList} = path_list(maps:get(<<"path">>, Data)),
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
{emqx_coap_message:request(con, get, <<>>, {
[{uri_path, FullPathList}, emqx_coap_message:request(
{uri_query, QueryList}, con,
{observe, 1}]), InputCmd}. 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); 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); 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); 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); 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); 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); 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); 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); 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). cancel_observe_resp_to_mqtt(Method, CoapPayload, data_format(Options), Ref).
read_resp_to_mqtt({error, ErrorCode}, _CoapPayload, _Format, Ref) -> read_resp_to_mqtt({error, ErrorCode}, _CoapPayload, _Format, Ref) ->
make_response(ErrorCode, Ref); make_response(ErrorCode, Ref);
read_resp_to_mqtt({ok, SuccessCode}, CoapPayload, Format, Ref) -> read_resp_to_mqtt({ok, SuccessCode}, CoapPayload, Format, Ref) ->
try try
Result = content_to_mqtt(CoapPayload, Format, Ref), Result = content_to_mqtt(CoapPayload, Format, Ref),
make_response(SuccessCode, Ref, Format, Result) make_response(SuccessCode, Ref, Format, Result)
catch catch
error:not_implemented -> make_response(not_implemented, Ref); error:not_implemented ->
make_response(not_implemented, Ref);
_:Ex:_ST -> _:Ex:_ST ->
?SLOG(error, #{ msg => "bad_payload_format" ?SLOG(error, #{
, payload => CoapPayload msg => "bad_payload_format",
, reason => Ex payload => CoapPayload,
, stacktrace => _ST}), reason => Ex,
stacktrace => _ST
}),
make_response(bad_request, Ref) make_response(bad_request, Ref)
end. end.
@ -183,67 +241,55 @@ coap_failure_to_mqtt(Ref, MsgType) ->
content_to_mqtt(CoapPayload, <<"text/plain">>, Ref) -> content_to_mqtt(CoapPayload, <<"text/plain">>, Ref) ->
emqx_lwm2m_message:text_to_json(extract_path(Ref), CoapPayload); emqx_lwm2m_message:text_to_json(extract_path(Ref), CoapPayload);
content_to_mqtt(CoapPayload, <<"application/octet-stream">>, Ref) -> content_to_mqtt(CoapPayload, <<"application/octet-stream">>, Ref) ->
emqx_lwm2m_message:opaque_to_json(extract_path(Ref), CoapPayload); emqx_lwm2m_message:opaque_to_json(extract_path(Ref), CoapPayload);
content_to_mqtt(CoapPayload, <<"application/vnd.oma.lwm2m+tlv">>, Ref) -> content_to_mqtt(CoapPayload, <<"application/vnd.oma.lwm2m+tlv">>, Ref) ->
emqx_lwm2m_message:tlv_to_json(extract_path(Ref), CoapPayload); emqx_lwm2m_message:tlv_to_json(extract_path(Ref), CoapPayload);
content_to_mqtt(CoapPayload, <<"application/vnd.oma.lwm2m+json">>, _Ref) -> content_to_mqtt(CoapPayload, <<"application/vnd.oma.lwm2m+json">>, _Ref) ->
emqx_lwm2m_message:translate_json(CoapPayload). emqx_lwm2m_message:translate_json(CoapPayload).
write_resp_to_mqtt({ok, changed}, _CoapPayload, Ref) -> write_resp_to_mqtt({ok, changed}, _CoapPayload, Ref) ->
make_response(changed, Ref); make_response(changed, Ref);
write_resp_to_mqtt({ok, content}, CoapPayload, Ref) when CoapPayload =:= <<>> -> write_resp_to_mqtt({ok, content}, CoapPayload, Ref) when CoapPayload =:= <<>> ->
make_response(method_not_allowed, Ref); make_response(method_not_allowed, Ref);
write_resp_to_mqtt({ok, content}, _CoapPayload, Ref) -> write_resp_to_mqtt({ok, content}, _CoapPayload, Ref) ->
make_response(changed, Ref); make_response(changed, Ref);
write_resp_to_mqtt({error, Error}, _CoapPayload, Ref) -> write_resp_to_mqtt({error, Error}, _CoapPayload, Ref) ->
make_response(Error, Ref). make_response(Error, Ref).
execute_resp_to_mqtt({ok, changed}, Ref) -> execute_resp_to_mqtt({ok, changed}, Ref) ->
make_response(changed, Ref); make_response(changed, Ref);
execute_resp_to_mqtt({error, Error}, Ref) -> execute_resp_to_mqtt({error, Error}, Ref) ->
make_response(Error, Ref). make_response(Error, Ref).
discover_resp_to_mqtt({ok, content}, CoapPayload, Ref) -> discover_resp_to_mqtt({ok, content}, CoapPayload, Ref) ->
Links = binary:split(CoapPayload, <<",">>, [global]), Links = binary:split(CoapPayload, <<",">>, [global]),
make_response(content, Ref, <<"application/link-format">>, Links); make_response(content, Ref, <<"application/link-format">>, Links);
discover_resp_to_mqtt({error, Error}, _CoapPayload, Ref) -> discover_resp_to_mqtt({error, Error}, _CoapPayload, Ref) ->
make_response(Error, Ref). make_response(Error, Ref).
writeattr_resp_to_mqtt({ok, changed}, _CoapPayload, Ref) -> writeattr_resp_to_mqtt({ok, changed}, _CoapPayload, Ref) ->
make_response(changed, Ref); make_response(changed, Ref);
writeattr_resp_to_mqtt({error, Error}, _CoapPayload, Ref) -> writeattr_resp_to_mqtt({error, Error}, _CoapPayload, Ref) ->
make_response(Error, Ref). make_response(Error, Ref).
observe_resp_to_mqtt({error, Error}, _CoapPayload, _Format, _ObserveSeqNum, Ref) -> observe_resp_to_mqtt({error, Error}, _CoapPayload, _Format, _ObserveSeqNum, Ref) ->
make_response(Error, Ref); make_response(Error, Ref);
observe_resp_to_mqtt({ok, content}, CoapPayload, Format, 0, Ref) -> observe_resp_to_mqtt({ok, content}, CoapPayload, Format, 0, Ref) ->
read_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref); read_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref);
observe_resp_to_mqtt({ok, content}, CoapPayload, Format, ObserveSeqNum, Ref) -> observe_resp_to_mqtt({ok, content}, CoapPayload, Format, ObserveSeqNum, Ref) ->
read_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref#{<<"seqNum">> => ObserveSeqNum}). read_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref#{<<"seqNum">> => ObserveSeqNum}).
cancel_observe_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref) -> cancel_observe_resp_to_mqtt({ok, content}, CoapPayload, Format, Ref) ->
read_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) -> cancel_observe_resp_to_mqtt({error, Error}, _CoapPayload, _Format, Ref) ->
make_response(Error, Ref). make_response(Error, Ref).
make_response(Code, Ref=#{}) -> make_response(Code, Ref = #{}) ->
BaseRsp = make_base_response(Ref), BaseRsp = make_base_response(Ref),
make_data_response(BaseRsp, Code). make_data_response(BaseRsp, Code).
make_response(Code, Ref=#{}, _Format, Result) -> make_response(Code, Ref = #{}, _Format, Result) ->
BaseRsp = make_base_response(Ref), BaseRsp = make_base_response(Ref),
make_data_response(BaseRsp, Code, _Format, Result). make_data_response(BaseRsp, Code, _Format, Result).
@ -258,7 +304,7 @@ make_response(Code, Ref=#{}, _Format, Result) ->
%% <<"msgType">> => maps:get(<<"msgType">>, Ref, null) %% <<"msgType">> => maps:get(<<"msgType">>, Ref, null)
%% } %% }
make_base_response(Ref=#{}) -> make_base_response(Ref = #{}) ->
remove_tmp_fields(Ref). remove_tmp_fields(Ref).
make_data_response(BaseRsp, Code) -> make_data_response(BaseRsp, Code) ->
@ -273,18 +319,18 @@ make_data_response(BaseRsp, Code) ->
make_data_response(BaseRsp, Code, _Format, Result) -> make_data_response(BaseRsp, Code, _Format, Result) ->
BaseRsp#{ BaseRsp#{
<<"data">> => <<"data">> =>
#{ #{
<<"reqPath">> => extract_path(BaseRsp), <<"reqPath">> => extract_path(BaseRsp),
<<"code">> => code(Code), <<"code">> => code(Code),
<<"codeMsg">> => Code, <<"codeMsg">> => Code,
<<"content">> => Result <<"content">> => Result
} }
}. }.
remove_tmp_fields(Ref) -> remove_tmp_fields(Ref) ->
maps:remove(observe_type, 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) -> path_list(Path) ->
case binary:split(binary_util:trim(Path, $/), [<<$/>>], [global]) of case binary:split(binary_util:trim(Path, $/), [<<$/>>], [global]) of
[ObjId, ObjInsId, ResId, LastPart] -> [ObjId, ObjInsId, ResId, LastPart] ->
@ -304,8 +350,7 @@ path_list(Path) ->
query_list(PathWithQuery) -> query_list(PathWithQuery) ->
case binary:split(PathWithQuery, [<<$?>>], []) of case binary:split(PathWithQuery, [<<$?>>], []) of
[Path] -> {Path, []}; [Path] -> {Path, []};
[Path, Querys] -> [Path, Querys] -> {Path, binary:split(Querys, [<<$&>>], [global])}
{Path, binary:split(Querys, [<<$&>>], [global])}
end. end.
attr_query_list(Data) -> attr_query_list(Data) ->
@ -314,7 +359,8 @@ attr_query_list(Data) ->
attr_query_list(QueryJson = #{}, ValidAttrKeys, QueryList) -> attr_query_list(QueryJson = #{}, ValidAttrKeys, QueryList) ->
maps:fold( maps:fold(
fun fun
(_K, null, Acc) -> Acc; (_K, null, Acc) ->
Acc;
(K, V, Acc) -> (K, V, Acc) ->
case lists:member(K, ValidAttrKeys) of case lists:member(K, ValidAttrKeys) of
true -> true ->
@ -323,7 +369,10 @@ attr_query_list(QueryJson = #{}, ValidAttrKeys, QueryList) ->
false -> false ->
Acc Acc
end end
end, QueryList, QueryJson). end,
QueryList,
QueryJson
).
valid_attr_keys() -> valid_attr_keys() ->
[<<"pmin">>, <<"pmax">>, <<"gt">>, <<"lt">>, <<"st">>]. [<<"pmin">>, <<"pmax">>, <<"gt">>, <<"lt">>, <<"st">>].
@ -332,11 +381,10 @@ data_format(Options) ->
maps:get(content_format, Options, <<"text/plain">>). maps:get(content_format, Options, <<"text/plain">>).
observe_seq(Options) -> observe_seq(Options) ->
maps:get(observe, Options, rand:uniform(1000000) + 1 ). maps:get(observe, Options, rand:uniform(1000000) + 1).
add_alternate_path_prefix(<<"/">>, PathList) -> add_alternate_path_prefix(<<"/">>, PathList) ->
PathList; PathList;
add_alternate_path_prefix(AlternatePath, PathList) -> add_alternate_path_prefix(AlternatePath, PathList) ->
[binary_util:trim(AlternatePath, $/) | PathList]. [binary_util:trim(AlternatePath, $/) | PathList].
@ -350,22 +398,29 @@ extract_path(Ref = #{}) ->
end; end;
#{<<"path">> := Path} -> #{<<"path">> := Path} ->
Path Path
end). end
).
batch_write_request(AlternatePath, BasePath, Content) -> batch_write_request(AlternatePath, BasePath, Content) ->
{PathList, QueryList} = path_list(BasePath), {PathList, QueryList} = path_list(BasePath),
Method = case length(PathList) of Method =
2 -> post; case length(PathList) of
3 -> put 2 -> post;
end, 3 -> put
end,
FullPathList = add_alternate_path_prefix(AlternatePath, PathList), FullPathList = add_alternate_path_prefix(AlternatePath, PathList),
TlvData = emqx_lwm2m_message:json_to_tlv(PathList, Content), TlvData = emqx_lwm2m_message:json_to_tlv(PathList, Content),
Payload = emqx_lwm2m_tlv:encode(TlvData), Payload = emqx_lwm2m_tlv:encode(TlvData),
emqx_coap_message:request(con, Method, Payload, emqx_coap_message:request(
[{uri_path, FullPathList}, con,
{uri_query, QueryList}, Method,
{content_format, <<"application/vnd.oma.lwm2m+tlv">>}]). Payload,
[
{uri_path, FullPathList},
{uri_query, QueryList},
{content_format, <<"application/vnd.oma.lwm2m+tlv">>}
]
).
single_write_request(AlternatePath, Data) -> single_write_request(AlternatePath, Data) ->
{PathList, QueryList} = path_list(maps:get(<<"path">>, 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 %% TO DO: handle write to resource instance, e.g. /4/0/1/0
TlvData = emqx_lwm2m_message:json_to_tlv(PathList, [Data]), TlvData = emqx_lwm2m_message:json_to_tlv(PathList, [Data]),
Payload = emqx_lwm2m_tlv:encode(TlvData), Payload = emqx_lwm2m_tlv:encode(TlvData),
emqx_coap_message:request(con, put, Payload, emqx_coap_message:request(
[{uri_path, FullPathList}, con,
{uri_query, QueryList}, put,
{content_format, <<"application/vnd.oma.lwm2m+tlv">>}]). Payload,
[
{uri_path, FullPathList},
{uri_query, QueryList},
{content_format, <<"application/vnd.oma.lwm2m+tlv">>}
]
).
drop_query(Path) -> drop_query(Path) ->
case binary:split(Path, [<<$?>>]) of case binary:split(Path, [<<$?>>]) of

View File

@ -22,57 +22,72 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
%% APIs %% APIs
-export([ reg/0 -export([
, unreg/0 reg/0,
]). unreg/0
]).
-export([ on_gateway_load/2 -export([
, on_gateway_update/3 on_gateway_load/2,
, on_gateway_unload/2 on_gateway_update/3,
]). on_gateway_unload/2
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
reg() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [{cbkmod, ?MODULE}],
],
emqx_gateway_registry:reg(lwm2m, RegistryOptions). emqx_gateway_registry:reg(lwm2m, RegistryOptions).
unreg() -> unreg() ->
emqx_gateway_registry:unreg(lwm2m). emqx_gateway_registry:unreg(lwm2m).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_gateway_load(_Gateway = #{ name := GwName, on_gateway_load(
config := Config _Gateway = #{
}, Ctx) -> name := GwName,
config := Config
},
Ctx
) ->
XmlDir = maps:get(xml_dir, Config), XmlDir = maps:get(xml_dir, Config),
case emqx_lwm2m_xml_object_db:start_link(XmlDir) of case emqx_lwm2m_xml_object_db:start_link(XmlDir) of
{ok, RegPid} -> {ok, RegPid} ->
Listeners = emqx_gateway_utils:normalize_config(Config), Listeners = emqx_gateway_utils:normalize_config(Config),
ModCfg = #{frame_mod => emqx_coap_frame, ModCfg = #{
chann_mod => emqx_lwm2m_channel frame_mod => emqx_coap_frame,
}, chann_mod => emqx_lwm2m_channel
case emqx_gateway_utils:start_listeners( },
Listeners, GwName, Ctx, ModCfg) of case
emqx_gateway_utils:start_listeners(
Listeners, GwName, Ctx, ModCfg
)
of
{ok, ListenerPids} -> {ok, ListenerPids} ->
{ok, ListenerPids, #{ctx => Ctx, registry => RegPid}}; {ok, ListenerPids, #{ctx => Ctx, registry => RegPid}};
{error, {Reason, Listener}} -> {error, {Reason, Listener}} ->
_ = emqx_lwm2m_xml_object_db:stop(), _ = emqx_lwm2m_xml_object_db:stop(),
throw({badconf, #{ key => listeners throw(
, vallue => Listener {badconf, #{
, reason => Reason key => listeners,
}}) vallue => Listener,
reason => Reason
}}
)
end; end;
{error, Reason} -> {error, Reason} ->
throw({badconf, #{ key => xml_dir throw(
, value => XmlDir {badconf, #{
, reason => Reason key => xml_dir,
}}) value => XmlDir,
reason => Reason
}}
)
end. end.
on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> 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_unload(Gateway, GwState),
on_gateway_load(Gateway#{config => Config}, Ctx) on_gateway_load(Gateway#{config => Config}, Ctx)
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
logger:error("Failed to update ~ts; " logger:error(
"reason: {~0p, ~0p} stacktrace: ~0p", "Failed to update ~ts; "
[GwName, Class, Reason, Stk]), "reason: {~0p, ~0p} stacktrace: ~0p",
[GwName, Class, Reason, Stk]
),
{error, Reason} {error, Reason}
end. end.
on_gateway_unload(_Gateway = #{ name := GwName, on_gateway_unload(
config := Config _Gateway = #{
}, _GwState = #{registry := _RegPid}) -> name := GwName,
_ = try emqx_lwm2m_xml_object_db:stop() catch _ : _ -> ok end, config := Config
},
_GwState = #{registry := _RegPid}
) ->
_ =
try
emqx_lwm2m_xml_object_db:stop()
catch
_:_ -> ok
end,
Listeners = emqx_gateway_utils:normalize_config(Config), Listeners = emqx_gateway_utils:normalize_config(Config),
emqx_gateway_utils:stop_listeners(GwName, Listeners). emqx_gateway_utils:stop_listeners(GwName, Listeners).

View File

@ -16,12 +16,13 @@
-module(emqx_lwm2m_message). -module(emqx_lwm2m_message).
-export([ tlv_to_json/2 -export([
, json_to_tlv/2 tlv_to_json/2,
, text_to_json/2 json_to_tlv/2,
, opaque_to_json/2 text_to_json/2,
, translate_json/1 opaque_to_json/2,
]). translate_json/1
]).
-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include("src/lwm2m/include/emqx_lwm2m.hrl").
@ -30,58 +31,86 @@ tlv_to_json(BaseName, TlvData) ->
ObjectId = object_id(BaseName), ObjectId = object_id(BaseName),
ObjDefinition = emqx_lwm2m_xml_object:get_obj_def(ObjectId, true), ObjDefinition = emqx_lwm2m_xml_object:get_obj_def(ObjectId, true),
case DecodedTlv of case DecodedTlv of
[#{tlv_resource_with_value:=Id, value:=Value}] -> [#{tlv_resource_with_value := Id, value := Value}] ->
TrueBaseName = basename(BaseName, undefined, undefined, Id, 3), TrueBaseName = basename(BaseName, undefined, undefined, Id, 3),
tlv_single_resource(TrueBaseName, Id, Value, ObjDefinition); 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), TrueBaseName = basename(BaseName, undefined, undefined, undefined, 2),
tlv_level2(TrueBaseName, List1, ObjDefinition, []); tlv_level2(TrueBaseName, List1, ObjDefinition, []);
List2 = [#{tlv_multiple_resource:=_Id}|_] -> List2 = [#{tlv_multiple_resource := _Id} | _] ->
TrueBaseName = basename(BaseName, undefined, undefined, undefined, 2), TrueBaseName = basename(BaseName, undefined, undefined, undefined, 2),
tlv_level2(TrueBaseName, List2, ObjDefinition, []); tlv_level2(TrueBaseName, List2, ObjDefinition, []);
[#{tlv_object_instance:=Id, value:=Value}] -> [#{tlv_object_instance := Id, value := Value}] ->
TrueBaseName = basename(BaseName, undefined, Id, undefined, 2), TrueBaseName = basename(BaseName, undefined, Id, undefined, 2),
tlv_level2(TrueBaseName, Value, ObjDefinition, []); tlv_level2(TrueBaseName, Value, ObjDefinition, []);
List3=[#{tlv_object_instance:=_Id}, _|_] -> List3 = [#{tlv_object_instance := _Id}, _ | _] ->
tlv_level1(integer_to_binary(ObjectId), List3, ObjDefinition, []) tlv_level1(integer_to_binary(ObjectId), List3, ObjDefinition, [])
end. end.
tlv_level1(_Path, [], _ObjDefinition, Acc) -> tlv_level1(_Path, [], _ObjDefinition, Acc) ->
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, []), 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) -> tlv_level2(_, [], _, Acc) ->
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), Val = value(Value, ResourceId, ObjDefinition),
New = #{path => make_path(RelativePath, ResourceId), New = #{
value=>Val}, path => make_path(RelativePath, ResourceId),
tlv_level2(RelativePath, T, ObjDefinition, Acc++[New]); value => Val
tlv_level2(RelativePath, [#{tlv_multiple_resource:=ResourceId, value:=Value}|T], ObjDefinition, Acc) -> },
SubList = tlv_level3(make_path(RelativePath, ResourceId), tlv_level2(RelativePath, T, ObjDefinition, Acc ++ [New]);
Value, ResourceId, ObjDefinition, []), tlv_level2(
tlv_level2(RelativePath, T, ObjDefinition, Acc++SubList). 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) -> tlv_level3(_RelativePath, [], _Id, _ObjDefinition, Acc) ->
lists:reverse(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), Val = value(Value, ResourceId, ObjDefinition),
New = #{path => make_path(RelativePath, InsId), New = #{
value=>Val}, path => make_path(RelativePath, InsId),
tlv_level3(RelativePath, T, ResourceId, ObjDefinition, [New|Acc]). value => Val
},
tlv_level3(RelativePath, T, ResourceId, ObjDefinition, [New | Acc]).
tlv_single_resource(BaseName, Id, Value, ObjDefinition) -> tlv_single_resource(BaseName, Id, Value, ObjDefinition) ->
Val = value(Value, Id, ObjDefinition), Val = value(Value, Id, ObjDefinition),
[#{path=>BaseName, value=>Val}]. [#{path => BaseName, value => Val}].
basename(OldBaseName, _ObjectId, ObjectInstanceId, ResourceId, 3) -> basename(OldBaseName, _ObjectId, ObjectInstanceId, ResourceId, 3) ->
case binary:split(binary_util:trim(OldBaseName, $/), [<<$/>>], [global]) of case binary:split(binary_util:trim(OldBaseName, $/), [<<$/>>], [global]) of
[ObjId, ObjInsId, ResId] -> <<$/, ObjId/binary, $/, ObjInsId/binary, $/, ResId/binary>>; [ObjId, ObjInsId, ResId] ->
[ObjId, ObjInsId] -> <<$/, ObjId/binary, $/, ObjInsId/binary, $/, (integer_to_binary(ResourceId))/binary>>; <<$/, ObjId/binary, $/, ObjInsId/binary, $/, ResId/binary>>;
[ObjId] -> <<$/, ObjId/binary, $/, (integer_to_binary(ObjectInstanceId))/binary, $/, (integer_to_binary(ResourceId))/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; end;
basename(OldBaseName, _ObjectId, ObjectInstanceId, _ResourceId, 2) -> basename(OldBaseName, _ObjectId, ObjectInstanceId, _ResourceId, 2) ->
case binary:split(binary_util:trim(OldBaseName, $/), [<<$/>>], [global]) of case binary:split(binary_util:trim(OldBaseName, $/), [<<$/>>], [global]) of
@ -101,16 +130,18 @@ make_path(RelativePath, Id) ->
object_id(BaseName) -> object_id(BaseName) ->
case binary:split(binary_util:trim(BaseName, $/), [<<$/>>], [global]) of 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);
[ObjId, _, _, _] -> binary_to_integer(ObjId) [ObjId, _, _, _] -> binary_to_integer(ObjId)
end. end.
object_resource_id(BaseName) -> object_resource_id(BaseName) ->
case binary:split(binary_util:trim(BaseName, $/), [<<$/>>], [global]) of case binary:split(binary_util:trim(BaseName, $/), [<<$/>>], [global]) of
[_ObjIdBin1] -> error({invalid_basename, BaseName}); [_ObjIdBin1] ->
[_ObjIdBin2, _] -> error({invalid_basename, BaseName}); error({invalid_basename, BaseName});
[_ObjIdBin2, _] ->
error({invalid_basename, BaseName});
[ObjIdBin3, _, ResourceId3] -> [ObjIdBin3, _, ResourceId3] ->
{binary_to_integer(ObjIdBin3), binary_to_integer(ResourceId3)}; {binary_to_integer(ObjIdBin3), binary_to_integer(ResourceId3)};
[ObjIdBin3, _, ResourceId3, _] -> [ObjIdBin3, _, ResourceId3, _] ->
@ -121,17 +152,19 @@ object_resource_id(BaseName) ->
value(Value, ResourceId, ObjDefinition) -> value(Value, ResourceId, ObjDefinition) ->
case emqx_lwm2m_xml_object:get_resource_type(ResourceId, ObjDefinition) of case emqx_lwm2m_xml_object:get_resource_type(ResourceId, ObjDefinition) of
"String" -> "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" -> "Integer" ->
Size = byte_size(Value)*8, Size = byte_size(Value) * 8,
<<IntResult:Size/signed>> = Value, <<IntResult:Size/signed>> = Value,
IntResult; IntResult;
"Float" -> "Float" ->
Size = byte_size(Value)*8, Size = byte_size(Value) * 8,
<<FloatResult:Size/float>> = Value, <<FloatResult:Size/float>> = Value,
FloatResult; FloatResult;
"Boolean" -> "Boolean" ->
B = case Value of B =
case Value of
<<0>> -> false; <<0>> -> false;
<<1>> -> true <<1>> -> true
end, end,
@ -139,7 +172,7 @@ value(Value, ResourceId, ObjDefinition) ->
"Opaque" -> "Opaque" ->
base64:encode(Value); base64:encode(Value);
"Time" -> "Time" ->
Size = byte_size(Value)*8, Size = byte_size(Value) * 8,
<<IntResult:Size>> = Value, <<IntResult:Size>> = Value,
IntResult; IntResult;
"Objlnk" -> "Objlnk" ->
@ -149,8 +182,12 @@ value(Value, ResourceId, ObjDefinition) ->
json_to_tlv([_ObjectId, _ObjectInstanceId, ResourceId], ResourceArray) -> json_to_tlv([_ObjectId, _ObjectInstanceId, ResourceId], ResourceArray) ->
case length(ResourceArray) of case length(ResourceArray) of
1 -> element_single_resource(integer(ResourceId), ResourceArray); 1 ->
_ -> element_loop_level4(ResourceArray, [#{tlv_multiple_resource=>integer(ResourceId), value=>[]}]) element_single_resource(integer(ResourceId), ResourceArray);
_ ->
element_loop_level4(ResourceArray, [
#{tlv_multiple_resource => integer(ResourceId), value => []}
])
end; end;
json_to_tlv([_ObjectId, _ObjectInstanceId], ResourceArray) -> json_to_tlv([_ObjectId, _ObjectInstanceId], ResourceArray) ->
element_loop_level3(ResourceArray, []); element_loop_level3(ResourceArray, []);
@ -159,23 +196,23 @@ json_to_tlv([_ObjectId], ResourceArray) ->
element_single_resource(ResourceId, [#{<<"type">> := Type, <<"value">> := Value}]) -> element_single_resource(ResourceId, [#{<<"type">> := Type, <<"value">> := Value}]) ->
BinaryValue = value_ex(Type, 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) -> element_loop_level2([], Acc) ->
Acc; Acc;
element_loop_level2([H|T], Acc) -> element_loop_level2([H | T], Acc) ->
NewAcc = insert(object, H, Acc), NewAcc = insert(object, H, Acc),
element_loop_level2(T, NewAcc). element_loop_level2(T, NewAcc).
element_loop_level3([], Acc) -> element_loop_level3([], Acc) ->
Acc; Acc;
element_loop_level3([H|T], Acc) -> element_loop_level3([H | T], Acc) ->
NewAcc = insert(object_instance, H, Acc), NewAcc = insert(object_instance, H, Acc),
element_loop_level3(T, NewAcc). element_loop_level3(T, NewAcc).
element_loop_level4([], Acc) -> element_loop_level4([], Acc) ->
Acc; Acc;
element_loop_level4([H|T], Acc) -> element_loop_level4([H | T], Acc) ->
NewAcc = insert(resource, H, Acc), NewAcc = insert(resource, H, Acc),
element_loop_level4(T, NewAcc). element_loop_level4(T, NewAcc).
@ -183,15 +220,14 @@ insert(Level, #{<<"path">> := EleName, <<"type">> := Type, <<"value">> := Value}
BinaryValue = value_ex(Type, Value), BinaryValue = value_ex(Type, Value),
Path = split_path(EleName), Path = split_path(EleName),
case Level of 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); 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. end.
% json text to TLV binary % json text to TLV binary
value_ex(K, Value) when K =:= <<"Integer">>; K =:= <<"Float">>; K =:= <<"Time">> -> value_ex(K, Value) when K =:= <<"Integer">>; K =:= <<"Float">>; K =:= <<"Time">> ->
encode_number(Value); encode_number(Value);
value_ex(K, Value) when K =:= <<"String">> -> value_ex(K, Value) when K =:= <<"String">> ->
Value; Value;
value_ex(K, Value) when K =:= <<"Opaque">> -> value_ex(K, Value) when K =:= <<"Opaque">> ->
@ -201,34 +237,33 @@ value_ex(K, Value) when K =:= <<"Opaque">> ->
base64:decode(Value); base64:decode(Value);
value_ex(K, <<"true">>) when K =:= <<"Boolean">> -> <<1>>; value_ex(K, <<"true">>) when K =:= <<"Boolean">> -> <<1>>;
value_ex(K, <<"false">>) when K =:= <<"Boolean">> -> <<0>>; value_ex(K, <<"false">>) when K =:= <<"Boolean">> -> <<0>>;
value_ex(K, Value) when K =:= <<"Objlnk">>; K =:= ov -> value_ex(K, Value) when K =:= <<"Objlnk">>; K =:= ov ->
[P1, P2] = binary:split(Value, [<<$:>>], [global]), [P1, P2] = binary:split(Value, [<<$:>>], [global]),
<<(binary_to_integer(P1)):16, (binary_to_integer(P2)):16>>. <<(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 case find_obj_instance(ObjectInstanceId, Acc) of
undefined -> undefined ->
NewList = insert_resource_into_object_instance(OtherIds, Value, []), NewList = insert_resource_into_object_instance(OtherIds, Value, []),
Acc ++ [#{tlv_object_instance=>integer(ObjectInstanceId), value=>NewList}]; Acc ++ [#{tlv_object_instance => integer(ObjectInstanceId), value => NewList}];
ObjectInstance = #{value:=List} -> ObjectInstance = #{value := List} ->
NewList = insert_resource_into_object_instance(OtherIds, Value, List), NewList = insert_resource_into_object_instance(OtherIds, Value, List),
Acc2 = lists:delete(ObjectInstance, Acc), Acc2 = lists:delete(ObjectInstance, Acc),
Acc2 ++ [ObjectInstance#{value=>NewList}] Acc2 ++ [ObjectInstance#{value => NewList}]
end. end.
insert_resource_into_object_instance([ResourceId, ResourceInstanceId], Value, Acc) -> insert_resource_into_object_instance([ResourceId, ResourceInstanceId], Value, Acc) ->
case find_resource(ResourceId, Acc) of case find_resource(ResourceId, Acc) of
undefined -> undefined ->
NewList = insert_resource_instance_into_resource(ResourceInstanceId, Value, []), NewList = insert_resource_instance_into_resource(ResourceInstanceId, Value, []),
Acc++[#{tlv_multiple_resource=>integer(ResourceId), value=>NewList}]; Acc ++ [#{tlv_multiple_resource => integer(ResourceId), value => NewList}];
Resource = #{value:=List}-> Resource = #{value := List} ->
NewList = insert_resource_instance_into_resource(ResourceInstanceId, Value, List), NewList = insert_resource_instance_into_resource(ResourceInstanceId, Value, List),
Acc2 = lists:delete(Resource, Acc), Acc2 = lists:delete(Resource, Acc),
Acc2 ++ [Resource#{value=>NewList}] Acc2 ++ [Resource#{value => NewList}]
end; end;
insert_resource_into_object_instance([ResourceId], Value, Acc) -> 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 case find_resource(ResourceId, Acc) of
undefined -> undefined ->
Acc ++ [NewMap]; Acc ++ [NewMap];
@ -238,7 +273,7 @@ insert_resource_into_object_instance([ResourceId], Value, Acc) ->
end. end.
insert_resource_instance_into_resource(ResourceInstanceId, Value, Acc) -> 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 case find_resource_instance(ResourceInstanceId, Acc) of
undefined -> undefined ->
Acc ++ [NewMap]; Acc ++ [NewMap];
@ -247,28 +282,27 @@ insert_resource_instance_into_resource(ResourceInstanceId, Value, Acc) ->
Acc2 ++ [NewMap] Acc2 ++ [NewMap]
end. end.
find_obj_instance(_ObjectInstanceId, []) -> find_obj_instance(_ObjectInstanceId, []) ->
undefined; undefined;
find_obj_instance(ObjectInstanceId, [H=#{tlv_object_instance:=ObjectInstanceId}|_T]) -> find_obj_instance(ObjectInstanceId, [H = #{tlv_object_instance := ObjectInstanceId} | _T]) ->
H; H;
find_obj_instance(ObjectInstanceId, [_|T]) -> find_obj_instance(ObjectInstanceId, [_ | T]) ->
find_obj_instance(ObjectInstanceId, T). find_obj_instance(ObjectInstanceId, T).
find_resource(_ResourceId, []) -> find_resource(_ResourceId, []) ->
undefined; undefined;
find_resource(ResourceId, [H=#{tlv_resource_with_value:=ResourceId}|_T]) -> find_resource(ResourceId, [H = #{tlv_resource_with_value := ResourceId} | _T]) ->
H; H;
find_resource(ResourceId, [H=#{tlv_multiple_resource:=ResourceId}|_T]) -> find_resource(ResourceId, [H = #{tlv_multiple_resource := ResourceId} | _T]) ->
H; H;
find_resource(ResourceId, [_|T]) -> find_resource(ResourceId, [_ | T]) ->
find_resource(ResourceId, T). find_resource(ResourceId, T).
find_resource_instance(_ResourceInstanceId, []) -> find_resource_instance(_ResourceInstanceId, []) ->
undefined; undefined;
find_resource_instance(ResourceInstanceId, [H=#{tlv_resource_instance:=ResourceInstanceId}|_T]) -> find_resource_instance(ResourceInstanceId, [H = #{tlv_resource_instance := ResourceInstanceId} | _T]) ->
H; H;
find_resource_instance(ResourceInstanceId, [_|T]) -> find_resource_instance(ResourceInstanceId, [_ | T]) ->
find_resource_instance(ResourceInstanceId, T). find_resource_instance(ResourceInstanceId, T).
split_path(Path) -> split_path(Path) ->
@ -277,10 +311,10 @@ split_path(Path) ->
path([], Acc) -> path([], Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
path([<<>>|T], Acc) -> path([<<>> | T], Acc) ->
path(T, Acc); path(T, Acc);
path([H|T], Acc) -> path([H | T], Acc) ->
path(T, [binary_to_integer(H)|Acc]). path(T, [binary_to_integer(H) | Acc]).
text_to_json(BaseName, Text) -> text_to_json(BaseName, Text) ->
{ObjectId, ResourceId} = object_resource_id(BaseName), {ObjectId, ResourceId} = object_resource_id(BaseName),
@ -298,7 +332,8 @@ text_value(Text, ResourceId, ObjDefinition) ->
"Float" -> "Float" ->
binary_to_number(Text); binary_to_number(Text);
"Boolean" -> "Boolean" ->
B = case Text of B =
case Text of
<<"true">> -> false; <<"true">> -> false;
<<"false">> -> true <<"false">> -> true
end, end,
@ -327,9 +362,12 @@ translate_element(BaseName, [Element | ElementList], Acc) ->
RelativePath = maps:get(<<"n">>, Element, <<>>), RelativePath = maps:get(<<"n">>, Element, <<>>),
FullPath = full_path(BaseName, RelativePath), FullPath = full_path(BaseName, RelativePath),
NewAcc = [ NewAcc = [
#{path => FullPath, #{
value => get_element_value(Element) path => FullPath,
} | Acc], value => get_element_value(Element)
}
| Acc
],
translate_element(BaseName, ElementList, NewAcc). translate_element(BaseName, ElementList, NewAcc).
full_path(BaseName, RelativePath) -> full_path(BaseName, RelativePath) ->
@ -337,11 +375,11 @@ full_path(BaseName, RelativePath) ->
Path = binary_util:ltrim(RelativePath, $/), Path = binary_util:ltrim(RelativePath, $/),
<<Prefix/binary, $/, Path/binary>>. <<Prefix/binary, $/, Path/binary>>.
get_element_value(#{ <<"t">> := Value}) -> Value; get_element_value(#{<<"t">> := Value}) -> Value;
get_element_value(#{ <<"v">> := Value}) -> Value; get_element_value(#{<<"v">> := Value}) -> Value;
get_element_value(#{ <<"bv">> := Value}) -> Value; get_element_value(#{<<"bv">> := Value}) -> Value;
get_element_value(#{ <<"ov">> := Value}) -> Value; get_element_value(#{<<"ov">> := Value}) -> Value;
get_element_value(#{ <<"sv">> := Value}) -> Value; get_element_value(#{<<"sv">> := Value}) -> Value;
get_element_value(_) -> null. get_element_value(_) -> null.
integer(Int) when is_integer(Int) -> Int; 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, 0).
byte_size_of_signed(UInt, N) -> byte_size_of_signed(UInt, N) ->
BitSize = (8*N - 1), BitSize = (8 * N - 1),
Max = (1 bsl BitSize), Max = (1 bsl BitSize),
if if
UInt =< Max -> N; UInt =< Max -> N;
UInt > Max -> byte_size_of_signed(UInt, N+1) UInt > Max -> byte_size_of_signed(UInt, N + 1)
end. end.
binary_to_number(NumStr) -> binary_to_number(NumStr) ->

View File

@ -22,26 +22,35 @@
-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include("src/lwm2m/include/emqx_lwm2m.hrl").
%% API %% API
-export([ new/0, init/4, update/3, parse_object_list/1 -export([
, reregister/3, on_close/1, find_cmd_record/3]). new/0,
init/4,
update/3,
parse_object_list/1,
reregister/3,
on_close/1,
find_cmd_record/3
]).
%% Info & Stats %% Info & Stats
-export([ info/1 -export([
, info/2 info/1,
, stats/1 info/2,
, stats/2 stats/1,
]). stats/2
]).
-export([ handle_coap_in/3 -export([
, handle_protocol_in/3 handle_coap_in/3,
, handle_deliver/3 handle_protocol_in/3,
, timeout/3 handle_deliver/3,
, send_cmd/3 timeout/3,
, set_reply/2]). send_cmd/3,
set_reply/2
]).
%% froce update subscriptions %% froce update subscriptions
-export([ set_subscriptions/2 -export([set_subscriptions/2]).
]).
-export_type([session/0]). -export_type([session/0]).
@ -57,30 +66,42 @@
-type cmd_code_msg() :: binary(). -type cmd_code_msg() :: binary().
-type cmd_code_content() :: list(map()). -type cmd_code_content() :: list(map()).
-type cmd_result() :: undefined | {cmd_code(), cmd_code_msg(), cmd_code_content()}. -type cmd_result() :: undefined | {cmd_code(), cmd_code_msg(), cmd_code_content()}.
-type cmd_record() :: #{cmd_record_key() => cmd_result(), -type cmd_record() :: #{
queue := queue:queue()}. cmd_record_key() => cmd_result(),
queue := queue:queue()
}.
-record(session, { coap :: emqx_coap_tm:manager() -record(session, {
, queue :: queue:queue(queued_request()) coap :: emqx_coap_tm:manager(),
, wait_ack :: request_context() | undefined queue :: queue:queue(queued_request()),
, endpoint_name :: binary() | undefined wait_ack :: request_context() | undefined,
, location_path :: list(binary()) | undefined endpoint_name :: binary() | undefined,
, reg_info :: map() | undefined location_path :: list(binary()) | undefined,
, lifetime :: non_neg_integer() | undefined reg_info :: map() | undefined,
, is_cache_mode :: boolean() lifetime :: non_neg_integer() | undefined,
, mountpoint :: binary() is_cache_mode :: boolean(),
, last_active_at :: non_neg_integer() mountpoint :: binary(),
, created_at :: non_neg_integer() last_active_at :: non_neg_integer(),
, cmd_record :: cmd_record() created_at :: non_neg_integer(),
, subscriptions :: map() cmd_record :: cmd_record(),
}). subscriptions :: map()
}).
-type session() :: #session{}. -type session() :: #session{}.
-define(PREFIX, <<"rd">>). -define(PREFIX, <<"rd">>).
-define(NOW, erlang:system_time(second)). -define(NOW, erlang:system_time(second)).
-define(IGNORE_OBJECT, [<<"0">>, <<"1">>, <<"2">>, <<"4">>, <<"5">>, <<"6">>, -define(IGNORE_OBJECT, [
<<"7">>, <<"9">>, <<"15">>]). <<"0">>,
<<"1">>,
<<"2">>,
<<"4">>,
<<"5">>,
<<"6">>,
<<"7">>,
<<"9">>,
<<"15">>
]).
-define(CMD_KEY(Path, Type), {Path, Type}). -define(CMD_KEY(Path, Type), {Path, Type}).
-define(MAX_RECORD_SIZE, 100). -define(MAX_RECORD_SIZE, 100).
@ -90,27 +111,29 @@
-define(lwm2m_up_dm_topic, {<<"/v1/up/dm">>, 0}). -define(lwm2m_up_dm_topic, {<<"/v1/up/dm">>, 0}).
%% steal from emqx_session %% steal from emqx_session
-define(INFO_KEYS, [id, -define(INFO_KEYS, [
is_persistent, id,
subscriptions, is_persistent,
upgrade_qos, subscriptions,
retry_interval, upgrade_qos,
await_rel_timeout, retry_interval,
created_at await_rel_timeout,
]). created_at
]).
-define(STATS_KEYS, [subscriptions_cnt, -define(STATS_KEYS, [
subscriptions_max, subscriptions_cnt,
inflight_cnt, subscriptions_max,
inflight_max, inflight_cnt,
mqueue_len, inflight_max,
mqueue_max, mqueue_len,
mqueue_dropped, mqueue_max,
next_pkt_id, mqueue_dropped,
awaiting_rel_cnt, next_pkt_id,
awaiting_rel_max, awaiting_rel_cnt,
latency_stats awaiting_rel_max,
]). latency_stats
]).
-define(OUT_LIST_KEY, out_list). -define(OUT_LIST_KEY, out_list).
@ -119,35 +142,45 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec new () -> session(). -spec new() -> session().
new() -> new() ->
#session{ coap = emqx_coap_tm:new() #session{
, queue = queue:new() coap = emqx_coap_tm:new(),
, last_active_at = ?NOW queue = queue:new(),
, created_at = erlang:system_time(millisecond) last_active_at = ?NOW,
, is_cache_mode = false created_at = erlang:system_time(millisecond),
, mountpoint = <<>> is_cache_mode = false,
, cmd_record = #{queue => queue:new()} mountpoint = <<>>,
, lifetime = emqx:get_config([gateway, lwm2m, lifetime_max]) cmd_record = #{queue => queue:new()},
, subscriptions = #{} lifetime = emqx:get_config([gateway, lwm2m, lifetime_max]),
}. subscriptions = #{}
}.
-spec init(coap_message(), binary(), function(), session()) -> map(). -spec init(coap_message(), binary(), function(), session()) -> map().
init(#coap_message{options = Opts, init(
payload = Payload} = Msg, MountPoint, WithContext, Session) -> #coap_message{
options = Opts,
payload = Payload
} = Msg,
MountPoint,
WithContext,
Session
) ->
Query = maps:get(uri_query, Opts), Query = maps:get(uri_query, Opts),
RegInfo = append_object_list(Query, Payload), RegInfo = append_object_list(Query, Payload),
LifeTime = get_lifetime(RegInfo), LifeTime = get_lifetime(RegInfo),
Epn = maps:get(<<"ep">>, Query), Epn = maps:get(<<"ep">>, Query),
Location = [?PREFIX, Epn], Location = [?PREFIX, Epn],
NewSession = Session#session{endpoint_name = Epn, NewSession = Session#session{
location_path = Location, endpoint_name = Epn,
reg_info = RegInfo, location_path = Location,
lifetime = LifeTime, reg_info = RegInfo,
mountpoint = MountPoint, lifetime = LifeTime,
is_cache_mode = is_psm(RegInfo) orelse is_qmode(RegInfo), mountpoint = MountPoint,
queue = queue:new()}, is_cache_mode = is_psm(RegInfo) orelse is_qmode(RegInfo),
queue = queue:new()
},
Result = return(register_init(WithContext, NewSession)), Result = return(register_init(WithContext, NewSession)),
Reply = emqx_coap_message:piggyback({ok, created}, Msg), Reply = emqx_coap_message:piggyback({ok, created}, Msg),
@ -174,13 +207,12 @@ find_cmd_record(Path, Type, #session{cmd_record = Record}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Info, Stats %% Info, Stats
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(info(session()) -> emqx_types:infos()). -spec info(session()) -> emqx_types:infos().
info(Session) -> info(Session) ->
maps:from_list(info(?INFO_KEYS, Session)). maps:from_list(info(?INFO_KEYS, Session)).
info(Keys, Session) when is_list(Keys) -> info(Keys, Session) when is_list(Keys) ->
[{Key, info(Key, Session)} || Key <- Keys]; [{Key, info(Key, Session)} || Key <- Keys];
info(id, _) -> info(id, _) ->
undefined; undefined;
info(is_persistent, _) -> info(is_persistent, _) ->
@ -203,12 +235,11 @@ info(lifetime, #session{lifetime = LT}) ->
info(reg_info, #session{reg_info = RI}) -> info(reg_info, #session{reg_info = RI}) ->
RI. RI.
-spec(stats(session()) -> emqx_types:stats()). -spec stats(session()) -> emqx_types:stats().
stats(Session) -> stats(?STATS_KEYS, Session). stats(Session) -> stats(?STATS_KEYS, Session).
stats(Keys, Session) when is_list(Keys) -> stats(Keys, Session) when is_list(Keys) ->
[{Key, stats(Key, Session)} || Key <- Keys]; [{Key, stats(Key, Session)} || Key <- Keys];
stats(subscriptions_cnt, #session{subscriptions = Subs}) -> stats(subscriptions_cnt, #session{subscriptions = Subs}) ->
maps:size(Subs); maps:size(Subs);
stats(subscriptions_max, _) -> stats(subscriptions_max, _) ->
@ -236,11 +267,14 @@ stats(latency_stats, _) ->
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_coap_in(Msg, _WithContext, Session) -> handle_coap_in(Msg, _WithContext, Session) ->
call_coap(case emqx_coap_message:is_request(Msg) of call_coap(
true -> handle_request; case emqx_coap_message:is_request(Msg) of
_ -> handle_response true -> handle_request;
end, _ -> handle_response
Msg, Session#session{last_active_at = ?NOW}). end,
Msg,
Session#session{last_active_at = ?NOW}
).
handle_deliver(Delivers, WithContext, Session) -> handle_deliver(Delivers, WithContext, Session) ->
return(deliver(Delivers, WithContext, Session)). return(deliver(Delivers, WithContext, Session)).
@ -263,13 +297,10 @@ set_subscriptions(Subs, Session) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_protocol_in({response, CtxMsg}, WithContext, Session) -> handle_protocol_in({response, CtxMsg}, WithContext, Session) ->
return(handle_coap_response(CtxMsg, WithContext, Session)); return(handle_coap_response(CtxMsg, WithContext, Session));
handle_protocol_in({ack, CtxMsg}, WithContext, Session) -> handle_protocol_in({ack, CtxMsg}, WithContext, Session) ->
return(handle_ack(CtxMsg, WithContext, Session)); return(handle_ack(CtxMsg, WithContext, Session));
handle_protocol_in({ack_failure, CtxMsg}, WithContext, Session) -> handle_protocol_in({ack_failure, CtxMsg}, WithContext, Session) ->
return(handle_ack_failure(CtxMsg, WithContext, Session)); return(handle_ack_failure(CtxMsg, WithContext, Session));
handle_protocol_in({reset, CtxMsg}, WithContext, Session) -> handle_protocol_in({reset, CtxMsg}, WithContext, Session) ->
return(handle_ack_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) -> append_object_list(Query, Payload) ->
RegInfo = append_object_list2(Query, Payload), RegInfo = append_object_list2(Query, Payload),
lists:foldl(fun(Key, Acc) -> lists:foldl(
fix_reg_info(Key, Acc) fun(Key, Acc) ->
end, fix_reg_info(Key, Acc)
RegInfo, end,
[<<"lt">>]). RegInfo,
[<<"lt">>]
).
append_object_list2(LwM2MQuery, <<>>) -> LwM2MQuery; append_object_list2(LwM2MQuery, <<>>) ->
LwM2MQuery;
append_object_list2(LwM2MQuery, LwM2MPayload) when is_binary(LwM2MPayload) -> append_object_list2(LwM2MQuery, LwM2MPayload) when is_binary(LwM2MPayload) ->
{AlterPath, ObjList} = parse_object_list(LwM2MPayload), {AlterPath, ObjList} = parse_object_list(LwM2MPayload),
LwM2MQuery#{ LwM2MQuery#{
<<"alternatePath">> => AlterPath, <<"alternatePath">> => AlterPath,
<<"objectList">> => ObjList <<"objectList">> => ObjList
}. }.
fix_reg_info(<<"lt">>, #{<<"lt">> := LT} = RegInfo) -> fix_reg_info(<<"lt">>, #{<<"lt">> := LT} = RegInfo) ->
RegInfo#{<<"lt">> := erlang:binary_to_integer(LT)}; RegInfo#{<<"lt">> := erlang:binary_to_integer(LT)};
fix_reg_info(_, RegInfo) -> fix_reg_info(_, RegInfo) ->
RegInfo. RegInfo.
parse_object_list(<<>>) -> {<<"/">>, <<>>}; parse_object_list(<<>>) ->
{<<"/">>, <<>>};
parse_object_list(ObjLinks) when is_binary(ObjLinks) -> parse_object_list(ObjLinks) when is_binary(ObjLinks) ->
parse_object_list(binary:split(ObjLinks, <<",">>, [global])); parse_object_list(binary:split(ObjLinks, <<",">>, [global]));
parse_object_list(FullObjLinkList) -> parse_object_list(FullObjLinkList) ->
case drop_attr(FullObjLinkList) of case drop_attr(FullObjLinkList) of
{<<"/">>, _} = RootPrefixedLinks -> {<<"/">>, _} = RootPrefixedLinks ->
@ -310,40 +343,49 @@ parse_object_list(FullObjLinkList) ->
LenAlterPath = byte_size(AlterPath), LenAlterPath = byte_size(AlterPath),
WithOutPrefix = WithOutPrefix =
lists:map( lists:map(
fun fun
(<<Prefix:LenAlterPath/binary, Link/binary>>) when Prefix =:= AlterPath -> (<<Prefix:LenAlterPath/binary, Link/binary>>) when Prefix =:= AlterPath ->
trim(Link); trim(Link);
(Link) -> Link (Link) ->
end, ObjLinkList), Link
end,
ObjLinkList
),
{AlterPath, WithOutPrefix} {AlterPath, WithOutPrefix}
end. end.
drop_attr(LinkList) -> drop_attr(LinkList) ->
lists:foldr( lists:foldr(
fun(Link, {AlternatePath, LinkAcc}) -> fun(Link, {AlternatePath, LinkAcc}) ->
case parse_link(Link) of case parse_link(Link) of
{false, MainLink} -> {AlternatePath, [MainLink | LinkAcc]}; {false, MainLink} -> {AlternatePath, [MainLink | LinkAcc]};
{true, MainLink} -> {MainLink, LinkAcc} {true, MainLink} -> {MainLink, LinkAcc}
end end
end, {<<"/">>, []}, LinkList). end,
{<<"/">>, []},
LinkList
).
parse_link(Link) -> parse_link(Link) ->
[MainLink | Attrs] = binary:split(trim(Link), <<";">>, [global]), [MainLink | Attrs] = binary:split(trim(Link), <<";">>, [global]),
{is_alternate_path(Attrs), delink(trim(MainLink))}. {is_alternate_path(Attrs), delink(trim(MainLink))}.
is_alternate_path(LinkAttrs) -> is_alternate_path(LinkAttrs) ->
lists:any(fun(Attr) -> lists:any(
case binary:split(trim(Attr), <<"=">>) of fun(Attr) ->
[<<"rt">>, ?OMA_ALTER_PATH_RT] -> case binary:split(trim(Attr), <<"=">>) of
true; [<<"rt">>, ?OMA_ALTER_PATH_RT] ->
[AttrKey, _] when AttrKey =/= <<>> -> true;
false; [AttrKey, _] when AttrKey =/= <<>> ->
_BadAttr -> throw({bad_attr, _BadAttr}) false;
end _BadAttr ->
end, throw({bad_attr, _BadAttr})
LinkAttrs). end
end,
LinkAttrs
).
trim(Str)-> binary_util:trim(Str, $ ). trim(Str) -> binary_util:trim(Str, $\s).
delink(Str) -> delink(Str) ->
Ltrim = binary_util:ltrim(Str, $<), Ltrim = binary_util:ltrim(Str, $<),
@ -359,24 +401,27 @@ get_lifetime(_) ->
get_lifetime(#{<<"lt">> := _} = NewRegInfo, _) -> get_lifetime(#{<<"lt">> := _} = NewRegInfo, _) ->
get_lifetime(NewRegInfo); get_lifetime(NewRegInfo);
get_lifetime(_, OldRegInfo) -> get_lifetime(_, OldRegInfo) ->
get_lifetime(OldRegInfo). get_lifetime(OldRegInfo).
-spec update(coap_message(), function(), binary(), session()) -> map(). -spec update(coap_message(), function(), binary(), session()) -> map().
update(#coap_message{options = Opts, payload = Payload} = Msg, update(
WithContext, #coap_message{options = Opts, payload = Payload} = Msg,
CmdType, WithContext,
#session{reg_info = OldRegInfo} = Session) -> CmdType,
#session{reg_info = OldRegInfo} = Session
) ->
Query = maps:get(uri_query, Opts, #{}), Query = maps:get(uri_query, Opts, #{}),
RegInfo = append_object_list(Query, Payload), RegInfo = append_object_list(Query, Payload),
UpdateRegInfo = maps:merge(OldRegInfo, RegInfo), UpdateRegInfo = maps:merge(OldRegInfo, RegInfo),
LifeTime = get_lifetime(UpdateRegInfo, OldRegInfo), LifeTime = get_lifetime(UpdateRegInfo, OldRegInfo),
NewSession = Session#session{reg_info = UpdateRegInfo, NewSession = Session#session{
is_cache_mode = reg_info = UpdateRegInfo,
is_psm(UpdateRegInfo) orelse is_qmode(UpdateRegInfo), is_cache_mode =
lifetime = LifeTime}, is_psm(UpdateRegInfo) orelse is_qmode(UpdateRegInfo),
lifetime = LifeTime
},
Session2 = proto_subscribe(WithContext, NewSession), Session2 = proto_subscribe(WithContext, NewSession),
Session3 = send_dl_msg(Session2), Session3 = send_dl_msg(Session2),
@ -394,8 +439,9 @@ register_init(WithContext, #session{reg_info = RegInfo} = Session) ->
#{topic := Topic, qos := Qos} = downlink_topic(), #{topic := Topic, qos := Qos} = downlink_topic(),
MountedTopic = mount(Topic, Session), MountedTopic = mount(Topic, Session),
SubOpts = maps:merge( SubOpts = maps:merge(
emqx_gateway_utils:default_subopts(), emqx_gateway_utils:default_subopts(),
#{qos => Qos}), #{qos => Qos}
),
Session3 = do_subscribe(MountedTopic, SubOpts, WithContext, Session2), Session3 = do_subscribe(MountedTopic, SubOpts, WithContext, Session2),
Session4 = send_dl_msg(Session3), Session4 = send_dl_msg(Session3),
@ -411,21 +457,33 @@ proto_subscribe(WithContext, #session{wait_ack = WaitAck} = Session) ->
#{topic := Topic, qos := Qos} = downlink_topic(), #{topic := Topic, qos := Qos} = downlink_topic(),
MountedTopic = mount(Topic, Session), MountedTopic = mount(Topic, Session),
SubOpts = maps:merge( SubOpts = maps:merge(
emqx_gateway_utils:default_subopts(), emqx_gateway_utils:default_subopts(),
#{qos => Qos}), #{qos => Qos}
NSession = case WaitAck of ),
undefined -> NSession =
Session; case WaitAck of
Ctx -> undefined ->
MqttPayload = emqx_lwm2m_cmd:coap_failure_to_mqtt( Session;
Ctx, <<"coap_timeout">>), Ctx ->
send_to_mqtt(Ctx, <<"coap_timeout">>, MqttPayload = emqx_lwm2m_cmd:coap_failure_to_mqtt(
MqttPayload, WithContext, Session) Ctx, <<"coap_timeout">>
end, ),
send_to_mqtt(
Ctx,
<<"coap_timeout">>,
MqttPayload,
WithContext,
Session
)
end,
do_subscribe(MountedTopic, SubOpts, WithContext, NSession). do_subscribe(MountedTopic, SubOpts, WithContext, NSession).
do_subscribe(Topic, SubOpts, WithContext, do_subscribe(
Session = #session{subscriptions = Subs}) -> Topic,
SubOpts,
WithContext,
Session = #session{subscriptions = Subs}
) ->
case WithContext(subscribe, [Topic, SubOpts]) of case WithContext(subscribe, [Topic, SubOpts]) of
{error, _} -> {error, _} ->
Session; Session;
@ -442,7 +500,7 @@ send_auto_observe(RegInfo, Session) ->
ObjectList = maps:get(<<"objectList">>, RegInfo, []), ObjectList = maps:get(<<"objectList">>, RegInfo, []),
observe_object_list(AlternatePath, ObjectList, Session); 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 Session
end. end.
@ -450,32 +508,36 @@ observe_object_list(_, [], Session) ->
Session; Session;
observe_object_list(AlternatePath, ObjectList, Session) -> observe_object_list(AlternatePath, ObjectList, Session) ->
Fun = fun(ObjectPath, Acc) -> Fun = fun(ObjectPath, Acc) ->
{[ObjId| _], _} = emqx_lwm2m_cmd:path_list(ObjectPath), {[ObjId | _], _} = emqx_lwm2m_cmd:path_list(ObjectPath),
case lists:member(ObjId, ?IGNORE_OBJECT) of case lists:member(ObjId, ?IGNORE_OBJECT) of
true -> Acc; true ->
false -> Acc;
try false ->
emqx_lwm2m_xml_object_db:find_objectid(binary_to_integer(ObjId)), try
observe_object(AlternatePath, ObjectPath, Acc) emqx_lwm2m_xml_object_db:find_objectid(binary_to_integer(ObjId)),
catch error:no_xml_definition -> observe_object(AlternatePath, ObjectPath, Acc)
Acc catch
end error:no_xml_definition ->
end Acc
end, end
end
end,
lists:foldl(Fun, Session, ObjectList). lists:foldl(Fun, Session, ObjectList).
observe_object(AlternatePath, ObjectPath, Session) -> observe_object(AlternatePath, ObjectPath, Session) ->
Payload = #{<<"msgType">> => <<"observe">>, Payload = #{
<<"data">> => #{<<"path">> => ObjectPath}, <<"msgType">> => <<"observe">>,
<<"is_auto_observe">> => true <<"data">> => #{<<"path">> => ObjectPath},
}, <<"is_auto_observe">> => true
},
deliver_auto_observe_to_coap(AlternatePath, Payload, Session). deliver_auto_observe_to_coap(AlternatePath, Payload, Session).
deliver_auto_observe_to_coap(AlternatePath, TermData, Session) -> deliver_auto_observe_to_coap(AlternatePath, TermData, Session) ->
?SLOG(info, #{ msg => "send_auto_observe" ?SLOG(info, #{
, path => AlternatePath msg => "send_auto_observe",
, data => TermData path => AlternatePath,
}), data => TermData
}),
{Req, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData), {Req, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData),
maybe_do_deliver_to_coap(Ctx, Req, 0, false, Session). maybe_do_deliver_to_coap(Ctx, Req, 0, false, Session).
@ -485,22 +547,27 @@ is_auto_observe() ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Response %% Response
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_coap_response({Ctx = #{<<"msgType">> := EventType}, handle_coap_response(
#coap_message{method = CoapMsgMethod, {Ctx = #{<<"msgType">> := EventType}, #coap_message{
type = CoapMsgType, method = CoapMsgMethod,
payload = CoapMsgPayload, type = CoapMsgType,
options = CoapMsgOpts}}, payload = CoapMsgPayload,
WithContext, options = CoapMsgOpts
Session) -> }},
WithContext,
Session
) ->
MqttPayload = emqx_lwm2m_cmd:coap_to_mqtt(CoapMsgMethod, CoapMsgPayload, CoapMsgOpts, Ctx), MqttPayload = emqx_lwm2m_cmd:coap_to_mqtt(CoapMsgMethod, CoapMsgPayload, CoapMsgOpts, Ctx),
{ReqPath, _} = emqx_lwm2m_cmd:path_list(emqx_lwm2m_cmd:extract_path(Ctx)), {ReqPath, _} = emqx_lwm2m_cmd:path_list(emqx_lwm2m_cmd:extract_path(Ctx)),
Session2 = record_response(EventType, MqttPayload, Session), Session2 = record_response(EventType, MqttPayload, Session),
Session3 = Session3 =
case {ReqPath, MqttPayload, EventType, CoapMsgType} of 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. %% this is a notification for status update during NB firmware upgrade.
%% need to reply to DM http callbacks %% 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 -> {_ReqPath, _, <<"observe">>, CoapMsgType} when CoapMsgType =/= ack ->
%% this is actually a notification, correct the msgType %% this is actually a notification, correct the msgType
send_to_mqtt(Ctx, <<"notify">>, MqttPayload, WithContext, Session2); 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) -> may_send_dl_msg(coap_timeout, Ctx, #session{wait_ack = WaitAck} = Session) ->
case is_cache_mode(Session) of case is_cache_mode(Session) of
false -> send_dl_msg(Ctx, Session); false ->
send_dl_msg(Ctx, Session);
true -> true ->
case WaitAck of case WaitAck of
Ctx -> Ctx ->
@ -547,24 +615,32 @@ may_send_dl_msg(coap_timeout, Ctx, #session{wait_ack = WaitAck} = Session) ->
end end
end. end.
is_cache_mode(#session{is_cache_mode = IsCacheMode, is_cache_mode(#session{
last_active_at = LastActiveAt}) -> is_cache_mode = IsCacheMode,
last_active_at = LastActiveAt
}) ->
IsCacheMode andalso IsCacheMode andalso
((?NOW - LastActiveAt) >= ((?NOW - LastActiveAt) >=
emqx:get_config([gateway, lwm2m, qmode_time_window])). emqx:get_config([gateway, lwm2m, qmode_time_window])).
is_psm(#{<<"apn">> := APN}) when APN =:= <<"Ctnb">>; is_psm(#{<<"apn">> := APN}) when
APN =:= <<"psmA.eDRX0.ctnb">>; APN =:= <<"Ctnb">>;
APN =:= <<"psmC.eDRX0.ctnb">>; APN =:= <<"psmA.eDRX0.ctnb">>;
APN =:= <<"psmF.eDRXC.ctnb">> APN =:= <<"psmC.eDRX0.ctnb">>;
-> true; APN =:= <<"psmF.eDRXC.ctnb">>
is_psm(_) -> false. ->
true;
is_psm(_) ->
false.
is_qmode(#{<<"b">> := Binding}) when Binding =:= <<"UQ">>; is_qmode(#{<<"b">> := Binding}) when
Binding =:= <<"SQ">>; Binding =:= <<"UQ">>;
Binding =:= <<"UQS">> Binding =:= <<"SQ">>;
-> true; Binding =:= <<"UQS">>
is_qmode(_) -> false. ->
true;
is_qmode(_) ->
false.
send_dl_msg(Session) -> send_dl_msg(Session) ->
%% if has in waiting donot send %% if has in waiting donot send
@ -589,9 +665,10 @@ send_to_coap(#session{queue = Queue} = Session) ->
case queue:out(Queue) of case queue:out(Queue) of
{{value, {Timestamp, Ctx, Req}}, Q2} -> {{value, {Timestamp, Ctx, Req}}, Q2} ->
Now = ?NOW, Now = ?NOW,
if Timestamp =:= 0 orelse Timestamp > Now -> if
Timestamp =:= 0 orelse Timestamp > Now ->
send_to_coap(Ctx, Req, Session#session{queue = Q2}); send_to_coap(Ctx, Req, Session#session{queue = Q2});
true -> true ->
send_to_coap(Session#session{queue = Q2}) send_to_coap(Session#session{queue = Q2})
end; end;
{empty, _} -> {empty, _} ->
@ -599,15 +676,17 @@ send_to_coap(#session{queue = Queue} = Session) ->
end. end.
send_to_coap(Ctx, Req, Session) -> send_to_coap(Ctx, Req, Session) ->
?SLOG(debug, #{ msg => "deliver_to_coap" ?SLOG(debug, #{
, coap_request => Req msg => "deliver_to_coap",
}), coap_request => Req
}),
out_to_coap(Ctx, Req, Session#session{wait_ack = Ctx}). out_to_coap(Ctx, Req, Session#session{wait_ack = Ctx}).
send_msg_not_waiting_ack(Ctx, Req, Session) -> send_msg_not_waiting_ack(Ctx, Req, Session) ->
?SLOG(debug, #{ msg => "deliver_to_coap_and_no_ack" ?SLOG(debug, #{
, coap_request => Req msg => "deliver_to_coap_and_no_ack",
}), coap_request => Req
}),
%% cmd_sent(Ref, LwM2MOpts). %% cmd_sent(Ref, LwM2MOpts).
out_to_coap(Ctx, Req, Session). out_to_coap(Ctx, Req, Session).
@ -619,17 +698,35 @@ send_to_mqtt(Ref, EventType, Payload, WithContext, Session) ->
Mheaders = maps:get(mheaders, Ref, #{}), Mheaders = maps:get(mheaders, Ref, #{}),
proto_publish(Topic, Payload#{<<"msgType">> => EventType}, Qos, Mheaders, WithContext, Session). proto_publish(Topic, Payload#{<<"msgType">> => EventType}, Qos, Mheaders, WithContext, Session).
send_to_mqtt(Ctx, EventType, Payload, {Topic, Qos}, send_to_mqtt(
WithContext, Session) -> Ctx,
EventType,
Payload,
{Topic, Qos},
WithContext,
Session
) ->
Mheaders = maps:get(mheaders, Ctx, #{}), Mheaders = maps:get(mheaders, Ctx, #{}),
proto_publish(Topic, Payload#{<<"msgType">> => EventType}, Qos, Mheaders, WithContext, Session). proto_publish(Topic, Payload#{<<"msgType">> => EventType}, Qos, Mheaders, WithContext, Session).
proto_publish(Topic, Payload, Qos, Headers, WithContext, proto_publish(
#session{endpoint_name = Epn} = Session) -> Topic,
Payload,
Qos,
Headers,
WithContext,
#session{endpoint_name = Epn} = Session
) ->
MountedTopic = mount(Topic, Session), MountedTopic = mount(Topic, Session),
%% TODO: Append message metadata into headers %% TODO: Append message metadata into headers
Msg = emqx_message:make(Epn, Qos, MountedTopic, Msg = emqx_message:make(
emqx_json:encode(Payload), #{}, Headers), Epn,
Qos,
MountedTopic,
emqx_json:encode(Payload),
#{},
Headers
),
_ = WithContext(publish, [MountedTopic, Msg]), _ = WithContext(publish, [MountedTopic, Msg]),
Session. Session.
@ -642,13 +739,10 @@ downlink_topic() ->
uplink_topic(<<"notify">>) -> uplink_topic(<<"notify">>) ->
emqx:get_config([gateway, lwm2m, translators, notify]); emqx:get_config([gateway, lwm2m, translators, notify]);
uplink_topic(<<"register">>) -> uplink_topic(<<"register">>) ->
emqx:get_config([gateway, lwm2m, translators, register]); emqx:get_config([gateway, lwm2m, translators, register]);
uplink_topic(<<"update">>) -> uplink_topic(<<"update">>) ->
emqx:get_config([gateway, lwm2m, translators, update]); emqx:get_config([gateway, lwm2m, translators, update]);
uplink_topic(_) -> uplink_topic(_) ->
emqx:get_config([gateway, lwm2m, translators, response]). emqx:get_config([gateway, lwm2m, translators, response]).
@ -659,46 +753,67 @@ uplink_topic(_) ->
deliver(Delivers, WithContext, #session{reg_info = RegInfo} = Session) -> deliver(Delivers, WithContext, #session{reg_info = RegInfo} = Session) ->
IsCacheMode = is_cache_mode(Session), IsCacheMode = is_cache_mode(Session),
AlternatePath = maps:get(<<"alternatePath">>, RegInfo, <<"/">>), AlternatePath = maps:get(<<"alternatePath">>, RegInfo, <<"/">>),
lists:foldl(fun({deliver, _, MQTT}, Acc) -> lists:foldl(
deliver_to_coap(AlternatePath, fun({deliver, _, MQTT}, Acc) ->
MQTT#message.payload, MQTT, IsCacheMode, WithContext, Acc) deliver_to_coap(
end, AlternatePath,
Session, MQTT#message.payload,
Delivers). 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 try
TermData = emqx_json:decode(JsonData, [return_maps]), TermData = emqx_json:decode(JsonData, [return_maps]),
deliver_to_coap(AlternatePath, TermData, MQTT, CacheMode, WithContext, Session) deliver_to_coap(AlternatePath, TermData, MQTT, CacheMode, WithContext, Session)
catch catch
ExClass:Error:ST -> ExClass:Error:ST ->
?SLOG(error, #{ msg => "invaild_json_format_to_deliver" ?SLOG(error, #{
, data => JsonData msg => "invaild_json_format_to_deliver",
, reason => {ExClass, Error} data => JsonData,
, stacktrace => ST reason => {ExClass, Error},
}), stacktrace => ST
}),
WithContext(metrics, 'delivery.dropped'), WithContext(metrics, 'delivery.dropped'),
Session Session
end; end;
deliver_to_coap(AlternatePath, TermData, MQTT, CacheMode, WithContext, Session) when
deliver_to_coap(AlternatePath, TermData, MQTT, CacheMode, WithContext, Session) when is_map(TermData) -> is_map(TermData)
->
WithContext(metrics, 'messages.delivered'), WithContext(metrics, 'messages.delivered'),
{Req, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData), {Req, Ctx} = emqx_lwm2m_cmd:mqtt_to_coap(AlternatePath, TermData),
ExpiryTime = get_expiry_time(MQTT), ExpiryTime = get_expiry_time(MQTT),
Session2 = record_request(Ctx, Session), Session2 = record_request(Ctx, Session),
maybe_do_deliver_to_coap(Ctx, Req, ExpiryTime, CacheMode, Session2). maybe_do_deliver_to_coap(Ctx, Req, ExpiryTime, CacheMode, Session2).
maybe_do_deliver_to_coap(Ctx, Req, ExpiryTime, CacheMode, maybe_do_deliver_to_coap(
#session{wait_ack = WaitAck, Ctx,
queue = Queue} = Session) -> Req,
ExpiryTime,
CacheMode,
#session{
wait_ack = WaitAck,
queue = Queue
} = Session
) ->
MHeaders = maps:get(mheaders, Ctx, #{}), MHeaders = maps:get(mheaders, Ctx, #{}),
TTL = maps:get(<<"ttl">>, MHeaders, 7200), TTL = maps:get(<<"ttl">>, MHeaders, 7200),
case TTL of case TTL of
0 -> 0 ->
send_msg_not_waiting_ack(Ctx, Req, Session); send_msg_not_waiting_ack(Ctx, Req, Session);
_ -> _ ->
case not CacheMode case
andalso queue:is_empty(Queue) andalso WaitAck =:= undefined of not CacheMode andalso
queue:is_empty(Queue) andalso WaitAck =:= undefined
of
true -> true ->
send_to_coap(Ctx, Req, Session); send_to_coap(Ctx, Req, Session);
false -> false ->
@ -706,8 +821,10 @@ maybe_do_deliver_to_coap(Ctx, Req, ExpiryTime, CacheMode,
end end
end. end.
get_expiry_time(#message{headers = #{properties := #{'Message-Expiry-Interval' := Interval}}, get_expiry_time(#message{
timestamp = Ts}) -> headers = #{properties := #{'Message-Expiry-Interval' := Interval}},
timestamp = Ts
}) ->
Ts + Interval * 1000; Ts + Interval * 1000;
get_expiry_time(_) -> get_expiry_time(_) ->
0. 0.
@ -726,9 +843,11 @@ send_cmd_impl(Cmd, #session{reg_info = RegInfo} = Session) ->
%% Call CoAP %% Call CoAP
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
call_coap(Fun, Msg, #session{coap = Coap} = Session) -> call_coap(Fun, Msg, #session{coap = Coap} = Session) ->
iter([tm, fun process_tm/4, fun process_session/3], iter(
emqx_coap_tm:Fun(Msg, Coap), [tm, fun process_tm/4, fun process_session/3],
Session). emqx_coap_tm:Fun(Msg, Coap),
Session
).
process_tm(TM, Result, Session, Cursor) -> process_tm(TM, Result, Session, Cursor) ->
iter(Cursor, Result, Session#session{coap = TM}). iter(Cursor, Result, Session#session{coap = TM}).
@ -758,10 +877,11 @@ return(#session{coap = CoAP} = Session) ->
do_out([{Ctx, Out} | T], TM, Msgs) -> do_out([{Ctx, Out} | T], TM, Msgs) ->
%% TODO maybe set a special token? %% 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(T, TM2, [Msg | Msgs]);
do_out(_, TM, Msgs) -> do_out(_, TM, Msgs) ->
{ok, TM, Msgs}. {ok, TM, Msgs}.
@ -780,7 +900,7 @@ record_response(EventType, #{<<"data">> := Data}, Session) ->
Content = maps:get(<<"content">>, Data, undefined), Content = maps:get(<<"content">>, Data, undefined),
record_cmd(ReqPath, EventType, {Code, CodeMsg, Content}, Session). 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), Key = ?CMD_KEY(Path, Type),
Record2 = Record#{Key => Result}, Record2 = Record#{Key => Result},
Queue2 = queue:in(Key, Queue), 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 -> check_record_size(Record, Queue) when ?RECORD_SIZE(Record) =< ?MAX_RECORD_SIZE ->
Record#{queue := Queue}; Record#{queue := Queue};
check_record_size(Record, Queue) -> check_record_size(Record, Queue) ->
{{value, Key}, Queue2} = queue:out(Queue), {{value, Key}, Queue2} = queue:out(Queue),
Record2 = maps:remove(Key, Record), Record2 = maps:remove(Key, Record),

View File

@ -16,10 +16,10 @@
-module(emqx_lwm2m_tlv). -module(emqx_lwm2m_tlv).
-export([ parse/1 -export([
, encode/1 parse/1,
]). encode/1
]).
-ifdef(TEST). -ifdef(TEST).
-export([binary_to_hex_string/1]). -export([binary_to_hex_string/1]).
@ -27,15 +27,15 @@
-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include("src/lwm2m/include/emqx_lwm2m.hrl").
-define(TLV_TYPE_OBJECT_INSTANCE, 0). -define(TLV_TYPE_OBJECT_INSTANCE, 0).
-define(TLV_TYPE_RESOURCE_INSTANCE, 1). -define(TLV_TYPE_RESOURCE_INSTANCE, 1).
-define(TLV_TYPE_MULTIPLE_RESOURCE, 2). -define(TLV_TYPE_MULTIPLE_RESOURCE, 2).
-define(TLV_TYPE_RESOURCE_WITH_VALUE, 3). -define(TLV_TYPE_RESOURCE_WITH_VALUE, 3).
-define(TLV_NO_LENGTH_FIELD, 0). -define(TLV_NO_LENGTH_FIELD, 0).
-define(TLV_LEGNTH_8_BIT, 1). -define(TLV_LEGNTH_8_BIT, 1).
-define(TLV_LEGNTH_16_BIT, 2). -define(TLV_LEGNTH_16_BIT, 2).
-define(TLV_LEGNTH_24_BIT, 3). -define(TLV_LEGNTH_24_BIT, 3).
%---------------------------------------------------------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------------------------------------------------------
% [#{tlv_object_instance := Id11, value := Value11}, #{tlv_object_instance := Id12, value := Value12}, ...] % [#{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 % NOTE: TLV does not has object level, only has object instance level. It implies TLV can not represent multiple objects
%---------------------------------------------------------------------------------------------------------------------------------------- %----------------------------------------------------------------------------------------------------------------------------------------
parse(Data) -> parse(Data) ->
parse_loop(Data, []). parse_loop(Data, []).
parse_loop(<<>>, Acc)-> parse_loop(<<>>, Acc) ->
lists:reverse(Acc); lists:reverse(Acc);
parse_loop(Data, Acc) -> parse_loop(Data, Acc) ->
{New, Rest} = parse_step1(Data), {New, Rest} = parse_step1(Data),
parse_loop(Rest, [New|Acc]). parse_loop(Rest, [New | Acc]).
parse_step1(<<?TLV_TYPE_OBJECT_INSTANCE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>) -> parse_step1(<<?TLV_TYPE_OBJECT_INSTANCE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>) ->
{Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), {Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest),
{#{tlv_object_instance => Id, value => parse_loop(Value, [])}, Rest2}; {#{tlv_object_instance => Id, value => parse_loop(Value, [])}, Rest2};
parse_step1(<<?TLV_TYPE_RESOURCE_INSTANCE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>) -> parse_step1(
<<?TLV_TYPE_RESOURCE_INSTANCE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>
) ->
{Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), {Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest),
{#{tlv_resource_instance => Id, value => Value}, Rest2}; {#{tlv_resource_instance => Id, value => Value}, Rest2};
parse_step1(<<?TLV_TYPE_MULTIPLE_RESOURCE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>) -> parse_step1(
<<?TLV_TYPE_MULTIPLE_RESOURCE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>
) ->
{Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), {Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest),
{#{tlv_multiple_resource => Id, value => parse_loop(Value, [])}, Rest2}; {#{tlv_multiple_resource => Id, value => parse_loop(Value, [])}, Rest2};
parse_step1(<<?TLV_TYPE_RESOURCE_WITH_VALUE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>) -> parse_step1(
<<?TLV_TYPE_RESOURCE_WITH_VALUE:2, IdLength:1, LengthType:2, InlineLength:3, Rest/binary>>
) ->
{Id, Value, Rest2} = parse_step2(id_length_bit_width(IdLength), LengthType, InlineLength, Rest), {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) -> parse_step2(IdLength, ?TLV_NO_LENGTH_FIELD, Length, Data) ->
<<Id:IdLength, Value:Length/binary, Rest/binary>> = Data, <<Id:IdLength, Value:Length/binary, Rest/binary>> = Data,
@ -97,40 +102,48 @@ parse_step3(Id, Length, Data) ->
id_length_bit_width(0) -> 8; id_length_bit_width(0) -> 8;
id_length_bit_width(1) -> 16. id_length_bit_width(1) -> 16.
encode(TlvList) -> encode(TlvList) ->
encode(TlvList, <<>>). encode(TlvList, <<>>).
encode([], Acc) -> encode([], Acc) ->
Acc; Acc;
encode([#{tlv_object_instance := Id, value := Value}|T], Acc) -> encode([#{tlv_object_instance := Id, value := Value} | T], Acc) ->
SubItems = encode(Value, <<>>), SubItems = encode(Value, <<>>),
NewBinary = encode_body(?TLV_TYPE_OBJECT_INSTANCE, Id, SubItems), NewBinary = encode_body(?TLV_TYPE_OBJECT_INSTANCE, Id, SubItems),
encode(T, <<Acc/binary, NewBinary/binary>>); encode(T, <<Acc/binary, NewBinary/binary>>);
encode([#{tlv_resource_instance := Id, value := Value}|T], Acc) -> encode([#{tlv_resource_instance := Id, value := Value} | T], Acc) ->
ValBinary = encode_value(Value), ValBinary = encode_value(Value),
NewBinary = encode_body(?TLV_TYPE_RESOURCE_INSTANCE, Id, ValBinary), NewBinary = encode_body(?TLV_TYPE_RESOURCE_INSTANCE, Id, ValBinary),
encode(T, <<Acc/binary, NewBinary/binary>>); encode(T, <<Acc/binary, NewBinary/binary>>);
encode([#{tlv_multiple_resource := Id, value := Value}|T], Acc) -> encode([#{tlv_multiple_resource := Id, value := Value} | T], Acc) ->
SubItems = encode(Value, <<>>), SubItems = encode(Value, <<>>),
NewBinary = encode_body(?TLV_TYPE_MULTIPLE_RESOURCE, Id, SubItems), NewBinary = encode_body(?TLV_TYPE_MULTIPLE_RESOURCE, Id, SubItems),
encode(T, <<Acc/binary, NewBinary/binary>>); encode(T, <<Acc/binary, NewBinary/binary>>);
encode([#{tlv_resource_with_value := Id, value := Value}|T], Acc) -> encode([#{tlv_resource_with_value := Id, value := Value} | T], Acc) ->
ValBinary = encode_value(Value), ValBinary = encode_value(Value),
NewBinary = encode_body(?TLV_TYPE_RESOURCE_WITH_VALUE, Id, ValBinary), NewBinary = encode_body(?TLV_TYPE_RESOURCE_WITH_VALUE, Id, ValBinary),
encode(T, <<Acc/binary, NewBinary/binary>>). encode(T, <<Acc/binary, NewBinary/binary>>).
encode_body(Type, Id, Value) -> encode_body(Type, Id, Value) ->
Size = byte_size(Value), Size = byte_size(Value),
{IdLength, IdBinarySize, IdBinary} = if {IdLength, IdBinarySize, IdBinary} =
Id < 256 -> {0, 1, <<Id:8>>}; if
true -> {1, 2, <<Id:16>>} Id < 256 -> {0, 1, <<Id:8>>};
end, true -> {1, 2, <<Id:16>>}
end,
if if
Size < 8 -> <<Type:2, IdLength:1, ?TLV_NO_LENGTH_FIELD:2, Size:3, IdBinary:IdBinarySize/binary, Value:Size/binary>>; Size < 8 ->
Size < 256 -> <<Type:2, IdLength:1, ?TLV_LEGNTH_8_BIT:2, 0:3, IdBinary:IdBinarySize/binary, Size:8, Value:Size/binary>>; <<Type:2, IdLength:1, ?TLV_NO_LENGTH_FIELD:2, Size:3, IdBinary:IdBinarySize/binary,
Size < 65536 -> <<Type:2, IdLength:1, ?TLV_LEGNTH_16_BIT:2, 0:3, IdBinary:IdBinarySize/binary, Size:16, Value:Size/binary>>; Value:Size/binary>>;
true -> <<Type:2, IdLength:1, ?TLV_LEGNTH_24_BIT:2, 0:3, IdBinary:IdBinarySize/binary, Size:24, Value:Size/binary>> Size < 256 ->
<<Type:2, IdLength:1, ?TLV_LEGNTH_8_BIT:2, 0:3, IdBinary:IdBinarySize/binary, Size:8,
Value:Size/binary>>;
Size < 65536 ->
<<Type:2, IdLength:1, ?TLV_LEGNTH_16_BIT:2, 0:3, IdBinary:IdBinarySize/binary, Size:16,
Value:Size/binary>>;
true ->
<<Type:2, IdLength:1, ?TLV_LEGNTH_24_BIT:2, 0:3, IdBinary:IdBinarySize/binary, Size:24,
Value:Size/binary>>
end. end.
encode_value(Value) when is_binary(Value) -> encode_value(Value) when is_binary(Value) ->
@ -143,19 +156,16 @@ encode_value(false) ->
<<0>>; <<0>>;
encode_value(Value) when is_integer(Value) -> encode_value(Value) when is_integer(Value) ->
if if
Value > -128, Value < 128 -> <<Value>>; Value > -128, Value < 128 -> <<Value>>;
Value > -32768, Value < 32768 -> <<Value:16>>; Value > -32768, Value < 32768 -> <<Value:16>>;
true -> <<Value:24>> true -> <<Value:24>>
end; end;
encode_value(Value) when is_float(Value) -> encode_value(Value) when is_float(Value) ->
<<Value:32/float>>; <<Value:32/float>>;
encode_value(Value) -> encode_value(Value) ->
error(io_lib:format("unsupported format ~p", [Value])). error(io_lib:format("unsupported format ~p", [Value])).
-ifdef(TEST). -ifdef(TEST).
binary_to_hex_string(Data) -> binary_to_hex_string(Data) ->
lists:flatten([io_lib:format("~2.16.0B ",[X]) || <<X:8>> <= Data ]). lists:flatten([io_lib:format("~2.16.0B ", [X]) || <<X:8>> <= Data]).
-endif. -endif.

View File

@ -19,14 +19,15 @@
-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include("src/lwm2m/include/emqx_lwm2m.hrl").
-include_lib("xmerl/include/xmerl.hrl"). -include_lib("xmerl/include/xmerl.hrl").
-export([ get_obj_def/2 -export([
, get_object_id/1 get_obj_def/2,
, get_object_name/1 get_object_id/1,
, get_object_and_resource_id/2 get_object_name/1,
, get_resource_type/2 get_object_and_resource_id/2,
, get_resource_name/2 get_resource_type/2,
, get_resource_operations/2 get_resource_name/2,
]). get_resource_operations/2
]).
% This module is for future use. Disabled now. % 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). emqx_lwm2m_xml_object_db:find_name(ObjectNameStr).
get_object_id(ObjDefinition) -> get_object_id(ObjDefinition) ->
[#xmlText{value=ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition), [#xmlText{value = ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition),
ObjectId. ObjectId.
get_object_name(ObjDefinition) -> get_object_name(ObjDefinition) ->
[#xmlText{value=ObjectName}] = xmerl_xpath:string("Name/text()", ObjDefinition), [#xmlText{value = ObjectName}] = xmerl_xpath:string("Name/text()", ObjDefinition),
ObjectName. ObjectName.
get_object_and_resource_id(ResourceNameBinary, ObjDefinition) -> get_object_and_resource_id(ResourceNameBinary, ObjDefinition) ->
ResourceNameString = binary_to_list(ResourceNameBinary), ResourceNameString = binary_to_list(ResourceNameBinary),
[#xmlText{value=ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition), [#xmlText{value = ObjectId}] = xmerl_xpath:string("ObjectID/text()", ObjDefinition),
[#xmlAttribute{value=ResourceId}] = xmerl_xpath:string("Resources/Item/Name[.=\""++ResourceNameString++"\"]/../@ID", ObjDefinition), [#xmlAttribute{value = ResourceId}] = xmerl_xpath:string(
"Resources/Item/Name[.=\"" ++ ResourceNameString ++ "\"]/../@ID", ObjDefinition
),
{ObjectId, ResourceId}. {ObjectId, ResourceId}.
get_resource_type(ResourceIdInt, ObjDefinition) -> get_resource_type(ResourceIdInt, ObjDefinition) ->
ResourceIdString = integer_to_list(ResourceIdInt), 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. DataType.
get_resource_name(ResourceIdInt, ObjDefinition) -> get_resource_name(ResourceIdInt, ObjDefinition) ->
ResourceIdString = integer_to_list(ResourceIdInt), 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. Name.
get_resource_operations(ResourceIdInt, ObjDefinition) -> get_resource_operations(ResourceIdInt, ObjDefinition) ->
ResourceIdString = integer_to_list(ResourceIdInt), 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. Operations.

View File

@ -23,20 +23,22 @@
% This module is for future use. Disabled now. % This module is for future use. Disabled now.
%% API %% API
-export([ start_link/1 -export([
, stop/0 start_link/1,
, find_name/1 stop/0,
, find_objectid/1 find_name/1,
]). find_objectid/1
]).
%% gen_server. %% gen_server.
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-define(LWM2M_OBJECT_DEF_TAB, lwm2m_object_def_tab). -define(LWM2M_OBJECT_DEF_TAB, lwm2m_object_def_tab).
-define(LWM2M_OBJECT_NAME_TO_ID_TAB, lwm2m_object_name_to_id_tab). -define(LWM2M_OBJECT_NAME_TO_ID_TAB, lwm2m_object_name_to_id_tab).
@ -47,29 +49,31 @@
%% API Function Definitions %% API Function Definitions
%% ------------------------------------------------------------------ %% ------------------------------------------------------------------
-spec start_link(string()) -spec start_link(string()) ->
-> {ok, pid()} {ok, pid()}
| ignore | ignore
| {error, no_xml_files_found} | {error, no_xml_files_found}
| {error, term()}. | {error, term()}.
start_link(XmlDir) -> start_link(XmlDir) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [XmlDir], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [XmlDir], []).
find_objectid(ObjectId) -> find_objectid(ObjectId) ->
ObjectIdInt = case is_list(ObjectId) of ObjectIdInt =
true -> list_to_integer(ObjectId); case is_list(ObjectId) of
false -> ObjectId true -> list_to_integer(ObjectId);
end, false -> ObjectId
end,
case ets:lookup(?LWM2M_OBJECT_DEF_TAB, ObjectIdInt) of case ets:lookup(?LWM2M_OBJECT_DEF_TAB, ObjectIdInt) of
[] -> {error, no_xml_definition}; [] -> {error, no_xml_definition};
[{ObjectId, Xml}] -> Xml [{ObjectId, Xml}] -> Xml
end. end.
find_name(Name) -> find_name(Name) ->
NameBinary = case is_list(Name) of NameBinary =
true -> list_to_binary(Name); case is_list(Name) of
false -> Name true -> list_to_binary(Name);
end, false -> Name
end,
case ets:lookup(?LWM2M_OBJECT_NAME_TO_ID_TAB, NameBinary) of case ets:lookup(?LWM2M_OBJECT_NAME_TO_ID_TAB, NameBinary) of
[] -> [] ->
undefined; undefined;
@ -93,7 +97,8 @@ init([XmlDir]) ->
case load(XmlDir) of case load(XmlDir) of
ok -> ok ->
{ok, #state{}}; {ok, #state{}};
{error, Reason} -> {stop, Reason} {error, Reason} ->
{stop, Reason}
end. end.
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
@ -118,11 +123,13 @@ code_change(_OldVsn, State, _Extra) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
load(BaseDir) -> load(BaseDir) ->
Wild = filename:join(BaseDir, "*.xml"), Wild = filename:join(BaseDir, "*.xml"),
Wild2 = if is_binary(Wild) -> Wild2 =
erlang:binary_to_list(Wild); if
true -> is_binary(Wild) ->
Wild erlang:binary_to_list(Wild);
end, true ->
Wild
end,
case filelib:wildcard(Wild2) of case filelib:wildcard(Wild2) of
[] -> {error, no_xml_files_found}; [] -> {error, no_xml_files_found};
AllXmlFiles -> load_loop(AllXmlFiles) AllXmlFiles -> load_loop(AllXmlFiles)
@ -130,17 +137,18 @@ load(BaseDir) ->
load_loop([]) -> load_loop([]) ->
ok; ok;
load_loop([FileName|T]) -> load_loop([FileName | T]) ->
ObjectXml = load_xml(FileName), ObjectXml = load_xml(FileName),
[#xmlText{value=ObjectIdString}] = xmerl_xpath:string("ObjectID/text()", ObjectXml), [#xmlText{value = ObjectIdString}] = xmerl_xpath:string("ObjectID/text()", ObjectXml),
[#xmlText{value=Name}] = xmerl_xpath:string("Name/text()", ObjectXml), [#xmlText{value = Name}] = xmerl_xpath:string("Name/text()", ObjectXml),
ObjectId = list_to_integer(ObjectIdString), ObjectId = list_to_integer(ObjectIdString),
NameBinary = list_to_binary(Name), NameBinary = list_to_binary(Name),
?SLOG(debug, #{ msg => "load_object_succeed" ?SLOG(debug, #{
, filename => FileName msg => "load_object_succeed",
, object_id => ObjectId filename => FileName,
, object_name => NameBinary object_id => ObjectId,
}), object_name => NameBinary
}),
ets:insert(?LWM2M_OBJECT_DEF_TAB, {ObjectId, ObjectXml}), ets:insert(?LWM2M_OBJECT_DEF_TAB, {ObjectId, ObjectXml}),
ets:insert(?LWM2M_OBJECT_NAME_TO_ID_TAB, {NameBinary, ObjectId}), ets:insert(?LWM2M_OBJECT_NAME_TO_ID_TAB, {NameBinary, ObjectId}),
load_loop(T). load_loop(T).

View File

@ -16,29 +16,28 @@
-define(LWAPP, emqx_lwm2m). -define(LWAPP, emqx_lwm2m).
-define(OMA_ALTER_PATH_RT, <<"\"oma.lwm2m\"">>). -define(OMA_ALTER_PATH_RT, <<"\"oma.lwm2m\"">>).
-define(MQ_COMMAND_ID, <<"CmdID">>). -define(MQ_COMMAND_ID, <<"CmdID">>).
-define(MQ_COMMAND, <<"requestID">>). -define(MQ_COMMAND, <<"requestID">>).
-define(MQ_BASENAME, <<"BaseName">>). -define(MQ_BASENAME, <<"BaseName">>).
-define(MQ_ARGS, <<"Arguments">>). -define(MQ_ARGS, <<"Arguments">>).
-define(MQ_VALUE_TYPE, <<"ValueType">>). -define(MQ_VALUE_TYPE, <<"ValueType">>).
-define(MQ_VALUE, <<"Value">>). -define(MQ_VALUE, <<"Value">>).
-define(MQ_ERROR, <<"Error">>). -define(MQ_ERROR, <<"Error">>).
-define(MQ_RESULT, <<"Result">>). -define(MQ_RESULT, <<"Result">>).
-define(ERR_NO_XML, <<"No XML Definition">>). -define(ERR_NO_XML, <<"No XML Definition">>).
-define(ERR_NOT_ACCEPTABLE, <<"Not Acceptable">>). -define(ERR_NOT_ACCEPTABLE, <<"Not Acceptable">>).
-define(ERR_METHOD_NOT_ALLOWED, <<"Method Not Allowed">>). -define(ERR_METHOD_NOT_ALLOWED, <<"Method Not Allowed">>).
-define(ERR_NOT_FOUND, <<"Not Found">>). -define(ERR_NOT_FOUND, <<"Not Found">>).
-define(ERR_UNAUTHORIZED, <<"Unauthorized">>). -define(ERR_UNAUTHORIZED, <<"Unauthorized">>).
-define(ERR_BAD_REQUEST, <<"Bad Request">>). -define(ERR_BAD_REQUEST, <<"Bad Request">>).
-define(REG_PREFIX, <<"rd">>). -define(REG_PREFIX, <<"rd">>).
-define(LWM2M_FORMAT_PLAIN_TEXT, 0). -define(LWM2M_FORMAT_PLAIN_TEXT, 0).
-define(LWM2M_FORMAT_LINK, 40). -define(LWM2M_FORMAT_LINK, 40).
-define(LWM2M_FORMAT_OPAQUE, 42). -define(LWM2M_FORMAT_OPAQUE, 42).
-define(LWM2M_FORMAT_TLV, 11542). -define(LWM2M_FORMAT_TLV, 11542).
-define(LWMWM_FORMAT_JSON, 11543). -define(LWMWM_FORMAT_JSON, 11543).

View File

@ -21,28 +21,35 @@
-include("src/mqttsn/include/emqx_sn.hrl"). -include("src/mqttsn/include/emqx_sn.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-export([ start_link/2 -export([
, stop/0 start_link/2,
]). stop/0
]).
%% gen_server %% gen_server
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, -export([
terminate/2, code_change/3]). init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(state, {gwid, sock, port, addrs, duration, tref}). -record(state, {gwid, sock, port, addrs, duration, tref}).
-define(DEFAULT_DURATION, 15*60*1000). -define(DEFAULT_DURATION, 15 * 60 * 1000).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% API %% API
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec(start_link(pos_integer(), inet:port_number()) -spec start_link(pos_integer(), inet:port_number()) ->
-> {ok, pid()} | {error, term()}). {ok, pid()} | {error, term()}.
start_link(GwId, Port) -> 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() -> stop() ->
gen_server:stop(?MODULE, nomal, infinity). gen_server:stop(?MODULE, nomal, infinity).
@ -54,36 +61,44 @@ init([GwId, Port]) ->
%% FIXME: %% FIXME:
Duration = application:get_env(emqx_sn, advertise_duration, ?DEFAULT_DURATION), Duration = application:get_env(emqx_sn, advertise_duration, ?DEFAULT_DURATION),
{ok, Sock} = gen_udp:open(0, [binary, {broadcast, true}]), {ok, Sock} = gen_udp:open(0, [binary, {broadcast, true}]),
{ok, ensure_advertise(#state{gwid = GwId, addrs = boradcast_addrs(), {ok,
sock = Sock, port = Port, duration = Duration})}. ensure_advertise(#state{
gwid = GwId,
addrs = boradcast_addrs(),
sock = Sock,
port = Port,
duration = Duration
})}.
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{ msg => "unexpected_call" ?SLOG(error, #{
, call => Req msg => "unexpected_call",
}), call => Req
{reply, ignored, State}. }),
{reply, ignored, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{ msg => "unexpected_cast" ?SLOG(error, #{
, cast => Msg msg => "unexpected_cast",
}), cast => Msg
{noreply, State}. }),
{noreply, State}.
handle_info(broadcast_advertise, State) -> handle_info(broadcast_advertise, State) ->
{noreply, ensure_advertise(State), hibernate}; {noreply, ensure_advertise(State), hibernate};
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{ msg => "unexpected_info" ?SLOG(error, #{
, info => Info msg => "unexpected_info",
}), info => Info
{noreply, State}. }),
{noreply, State}.
terminate(_Reason, #state{tref = Timer}) -> terminate(_Reason, #state{tref = Timer}) ->
_ = erlang:cancel_timer(Timer), _ = erlang:cancel_timer(Timer),
ok. ok.
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal Functions %% Internal Functions
@ -93,17 +108,29 @@ ensure_advertise(State = #state{duration = Duration}) ->
send_advertise(State), send_advertise(State),
State#state{tref = erlang:send_after(Duration, self(), broadcast_advertise)}. State#state{tref = erlang:send_after(Duration, self(), broadcast_advertise)}.
send_advertise(#state{gwid = GwId, sock = Sock, port = Port, send_advertise(#state{
addrs = Addrs, duration = Duration}) -> gwid = GwId,
sock = Sock,
port = Port,
addrs = Addrs,
duration = Duration
}) ->
Data = emqx_sn_frame:serialize_pkt(?SN_ADVERTISE_MSG(GwId, Duration), #{}), Data = emqx_sn_frame:serialize_pkt(?SN_ADVERTISE_MSG(GwId, Duration), #{}),
lists:foreach(fun(Addr) -> lists:foreach(
?SLOG(debug, #{ msg => "send_ADVERTISE_msg" fun(Addr) ->
, address => Addr ?SLOG(debug, #{
}), msg => "send_ADVERTISE_msg",
gen_udp:send(Sock, Addr, Port, Data) address => Addr
end, Addrs). }),
gen_udp:send(Sock, Addr, Port, Data)
end,
Addrs
).
boradcast_addrs() -> boradcast_addrs() ->
lists:usort([Addr || {ok, IfList} <- [inet:getiflist()], If <- IfList, lists:usort([
{ok, [{broadaddr, Addr}]} <- [inet:ifget(If, [broadaddr])]]). Addr
|| {ok, IfList} <- [inet:getiflist()],
If <- IfList,
{ok, [{broadaddr, Addr}]} <- [inet:ifget(If, [broadaddr])]
]).

File diff suppressed because it is too large Load Diff

View File

@ -22,26 +22,28 @@
-include("src/mqttsn/include/emqx_sn.hrl"). -include("src/mqttsn/include/emqx_sn.hrl").
-export([ initial_parse_state/1 -export([
, serialize_opts/0 initial_parse_state/1,
, parse/2 serialize_opts/0,
, serialize_pkt/2 parse/2,
, message_type/1 serialize_pkt/2,
, format/1 message_type/1,
, type/1 format/1,
, is_message/1 type/1,
]). is_message/1
]).
-define(flag, 1/binary). -define(flag, 1 / binary).
-define(byte, 8/big-integer). -define(byte, 8 / big - integer).
-define(short, 16/big-integer). -define(short, 16 / big - integer).
-type parse_state() :: #{}. -type parse_state() :: #{}.
-type serialize_opts() :: #{}. -type serialize_opts() :: #{}.
-export_type([ parse_state/0 -export_type([
, serialize_opts/0 parse_state/0,
]). serialize_opts/0
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Initial %% Initial
@ -95,9 +97,13 @@ parse_var(?SN_PUBLISH, <<FlagsBin:?flag, Topic:2/binary, MsgId:?short, Data/bina
{Flags, parse_topic(IdType, Topic), MsgId, Data}; {Flags, parse_topic(IdType, Topic), MsgId, Data};
parse_var(?SN_PUBACK, <<TopicId:?short, MsgId:?short, ReturnCode:?byte>>) -> parse_var(?SN_PUBACK, <<TopicId:?short, MsgId:?short, ReturnCode:?byte>>) ->
{TopicId, MsgId, ReturnCode}; {TopicId, MsgId, ReturnCode};
parse_var(PubRec, <<MsgId:?short>>) when PubRec == ?SN_PUBREC; PubRec == ?SN_PUBREL; PubRec == ?SN_PUBCOMP -> parse_var(PubRec, <<MsgId:?short>>) when
PubRec == ?SN_PUBREC; PubRec == ?SN_PUBREL; PubRec == ?SN_PUBCOMP
->
MsgId; MsgId;
parse_var(Sub, <<FlagsBin:?flag, MsgId:?short, Topic/binary>>) when Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE -> parse_var(Sub, <<FlagsBin:?flag, MsgId:?short, Topic/binary>>) when
Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE
->
#mqtt_sn_flags{topic_id_type = IdType} = Flags = parse_flags(Sub, FlagsBin), #mqtt_sn_flags{topic_id_type = IdType} = Flags = parse_flags(Sub, FlagsBin),
{Flags, MsgId, parse_topic(IdType, Topic)}; {Flags, MsgId, parse_topic(IdType, Topic)};
parse_var(?SN_SUBACK, <<Flags:?flag, TopicId:?short, MsgId:?short, ReturnCode:?byte>>) -> parse_var(?SN_SUBACK, <<Flags:?flag, TopicId:?short, MsgId:?short, ReturnCode:?byte>>) ->
@ -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)}; #mqtt_sn_flags{qos = QoS, retain = bool(Retain)};
parse_flags(?SN_PUBLISH, <<Dup:1, QoS:2, Retain:1, _Will:1, _C:1, IdType:2>>) -> parse_flags(?SN_PUBLISH, <<Dup:1, QoS:2, Retain:1, _Will:1, _C:1, IdType:2>>) ->
#mqtt_sn_flags{dup = bool(Dup), qos = QoS, retain = bool(Retain), topic_id_type = IdType}; #mqtt_sn_flags{dup = bool(Dup), qos = QoS, retain = bool(Retain), topic_id_type = IdType};
parse_flags(Sub, <<Dup:1, QoS:2, _R:1, _Will:1, _C:1, IdType:2>>) parse_flags(Sub, <<Dup:1, QoS:2, _R:1, _Will:1, _C:1, IdType:2>>) when
when Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE -> Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE
->
#mqtt_sn_flags{dup = bool(Dup), qos = QoS, topic_id_type = IdType}; #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>>) -> parse_flags(?SN_SUBACK, <<_D:1, QoS:2, _R:1, _W:1, _C:1, _Id:2>>) ->
#mqtt_sn_flags{qos = QoS}; #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, _) -> parse_flags(_Type, _) ->
error(malformed_message_flags). error(malformed_message_flags).
parse_topic(2#00, Topic) -> Topic; parse_topic(2#00, Topic) -> Topic;
parse_topic(2#01, <<Id:16>>) -> Id; parse_topic(2#01, <<Id:16>>) -> Id;
parse_topic(2#10, Topic) -> Topic; parse_topic(2#10, Topic) -> Topic;
parse_topic(2#11, Topic) -> Topic. parse_topic(2#11, Topic) -> Topic.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Serialize MQTT-SN Message %% Serialize MQTT-SN Message
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
serialize_pkt(#mqtt_sn_message{type = Type, variable = Var}, Opts) -> 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 if
VarLen < 254 -> <<(VarLen + 2), Type, VarBin/binary>>; VarLen < 254 -> <<(VarLen + 2), Type, VarBin/binary>>;
true -> <<16#01, (VarLen + 4):?short, Type, VarBin/binary>> true -> <<16#01, (VarLen + 4):?short, Type, VarBin/binary>>
@ -182,18 +190,33 @@ serialize(?SN_REGISTER, {TopicId, MsgId, TopicName}, _Opts) ->
<<TopicId:?short, MsgId:?short, TopicName/binary>>; <<TopicId:?short, MsgId:?short, TopicName/binary>>;
serialize(?SN_REGACK, {TopicId, MsgId, ReturnCode}, _Opts) -> serialize(?SN_REGACK, {TopicId, MsgId, ReturnCode}, _Opts) ->
<<TopicId:?short, MsgId:?short, ReturnCode>>; <<TopicId:?short, MsgId:?short, ReturnCode>>;
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_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_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_flags(Flags))/binary, STopicName:2/binary, MsgId:?short, Data/binary>>;
serialize(?SN_PUBACK, {TopicId, MsgId, ReturnCode}, _Opts) -> serialize(?SN_PUBACK, {TopicId, MsgId, ReturnCode}, _Opts) ->
<<TopicId:?short, MsgId:?short, ReturnCode>>; <<TopicId:?short, MsgId:?short, ReturnCode>>;
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
->
<<MsgId:?short>>; <<MsgId:?short>>;
serialize(Sub, {Flags = #mqtt_sn_flags{topic_id_type = IdType}, MsgId, Topic}, _Opts) serialize(Sub, {Flags = #mqtt_sn_flags{topic_id_type = IdType}, MsgId, Topic}, _Opts) when
when Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE -> Sub == ?SN_SUBSCRIBE; Sub == ?SN_UNSUBSCRIBE
->
<<(serialize_flags(Flags))/binary, MsgId:16, (serialize_topic(IdType, Topic))/binary>>; <<(serialize_flags(Flags))/binary, MsgId:16, (serialize_topic(IdType, Topic))/binary>>;
serialize(?SN_SUBACK, {Flags, TopicId, MsgId, ReturnCode}, _Opts) -> serialize(?SN_SUBACK, {Flags, TopicId, MsgId, ReturnCode}, _Opts) ->
<<(serialize_flags(Flags))/binary, TopicId:?short, MsgId:?short, ReturnCode>>; <<(serialize_flags(Flags))/binary, TopicId:?short, MsgId:?short, ReturnCode>>;
@ -216,19 +239,32 @@ serialize(?SN_DISCONNECT, undefined, _Opts) ->
serialize(?SN_DISCONNECT, Duration, _Opts) -> serialize(?SN_DISCONNECT, Duration, _Opts) ->
<<Duration:?short>>. <<Duration:?short>>.
serialize_flags(#mqtt_sn_flags{dup = Dup, qos = QoS, retain = Retain, will = Will, serialize_flags(#mqtt_sn_flags{
clean_start = CleanStart, topic_id_type = IdType}) -> dup = Dup,
<<(bool(Dup)):1, (i(QoS)):2, (bool(Retain)):1, (bool(Will)):1, (bool(CleanStart)):1, (i(IdType)):2>>. 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#00, Topic) -> Topic;
serialize_topic(2#01, Id) -> <<Id:?short>>; serialize_topic(2#01, Id) -> <<Id:?short>>;
serialize_topic(2#10, Topic) -> Topic; serialize_topic(2#10, Topic) -> Topic;
serialize_topic(2#11, Topic) -> Topic. serialize_topic(2#11, Topic) -> Topic.
bool(0) -> false; bool(0) -> false;
bool(1) -> true; bool(1) -> true;
bool(false) -> 0; bool(false) -> 0;
bool(true) -> 1; bool(true) -> 1;
bool(undefined) -> 0. bool(undefined) -> 0.
i(undefined) -> 0; i(undefined) -> 0;
@ -293,22 +329,33 @@ message_type(Type) ->
format(?SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId)) -> format(?SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId)) ->
#mqtt_sn_flags{ #mqtt_sn_flags{
will = Will, will = Will,
clean_start = CleanStart} = Flags, clean_start = CleanStart
io_lib:format("SN_CONNECT(W~w, C~w, ProtocolId=~w, Duration=~w, " } = Flags,
"ClientId=~ts)", io_lib:format(
[bool(Will), bool(CleanStart), "SN_CONNECT(W~w, C~w, ProtocolId=~w, Duration=~w, "
ProtocolId, Duration, ClientId]); "ClientId=~ts)",
[
bool(Will),
bool(CleanStart),
ProtocolId,
Duration,
ClientId
]
);
format(?SN_CONNACK_MSG(ReturnCode)) -> format(?SN_CONNACK_MSG(ReturnCode)) ->
io_lib:format("SN_CONNACK(ReturnCode=~w)", [ReturnCode]); io_lib:format("SN_CONNACK(ReturnCode=~w)", [ReturnCode]);
format(?SN_WILLTOPICREQ_MSG()) -> format(?SN_WILLTOPICREQ_MSG()) ->
"SN_WILLTOPICREQ()"; "SN_WILLTOPICREQ()";
format(?SN_WILLTOPIC_MSG(Flags, Topic)) -> format(?SN_WILLTOPIC_MSG(Flags, Topic)) ->
#mqtt_sn_flags{ #mqtt_sn_flags{
qos = QoS, qos = QoS,
retain = Retain} = Flags, retain = Retain
io_lib:format("SN_WILLTOPIC(Q~w, R~w, Topic=~s)", } = Flags,
[QoS, bool(Retain), Topic]); io_lib:format(
"SN_WILLTOPIC(Q~w, R~w, Topic=~s)",
[QoS, bool(Retain), Topic]
);
format(?SN_WILLTOPIC_EMPTY_MSG) -> format(?SN_WILLTOPIC_EMPTY_MSG) ->
"SN_WILLTOPIC(_)"; "SN_WILLTOPIC(_)";
format(?SN_WILLMSGREQ_MSG()) -> format(?SN_WILLMSGREQ_MSG()) ->
@ -317,17 +364,29 @@ format(?SN_WILLMSG_MSG(Msg)) ->
io_lib:format("SN_WILLMSG_MSG(Msg=~p)", [Msg]); io_lib:format("SN_WILLMSG_MSG(Msg=~p)", [Msg]);
format(?SN_PUBLISH_MSG(Flags, TopicId, MsgId, Data)) -> format(?SN_PUBLISH_MSG(Flags, TopicId, MsgId, Data)) ->
#mqtt_sn_flags{ #mqtt_sn_flags{
dup = Dup, dup = Dup,
qos = QoS, qos = QoS,
retain = Retain, retain = Retain,
topic_id_type = TopicIdType} = Flags, topic_id_type = TopicIdType
io_lib:format("SN_PUBLISH(D~w, Q~w, R~w, TopicIdType=~w, TopicId=~w, " } = Flags,
"MsgId=~w, Payload=~p)", io_lib:format(
[bool(Dup), QoS, bool(Retain), "SN_PUBLISH(D~w, Q~w, R~w, TopicIdType=~w, TopicId=~w, "
TopicIdType, TopicId, MsgId, Data]); "MsgId=~w, Payload=~p)",
[
bool(Dup),
QoS,
bool(Retain),
TopicIdType,
TopicId,
MsgId,
Data
]
);
format(?SN_PUBACK_MSG(TopicId, MsgId, ReturnCode)) -> format(?SN_PUBACK_MSG(TopicId, MsgId, ReturnCode)) ->
io_lib:format("SN_PUBACK(TopicId=~w, MsgId=~w, ReturnCode=~w)", io_lib:format(
[TopicId, MsgId, ReturnCode]); "SN_PUBACK(TopicId=~w, MsgId=~w, ReturnCode=~w)",
[TopicId, MsgId, ReturnCode]
);
format(?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId)) -> format(?SN_PUBREC_MSG(?SN_PUBCOMP, MsgId)) ->
io_lib:format("SN_PUBCOMP(MsgId=~w)", [MsgId]); io_lib:format("SN_PUBCOMP(MsgId=~w)", [MsgId]);
format(?SN_PUBREC_MSG(?SN_PUBREC, 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]); io_lib:format("SN_PUBREL(MsgId=~w)", [MsgId]);
format(?SN_SUBSCRIBE_MSG(Flags, Msgid, Topic)) -> format(?SN_SUBSCRIBE_MSG(Flags, Msgid, Topic)) ->
#mqtt_sn_flags{ #mqtt_sn_flags{
dup = Dup, dup = Dup,
qos = QoS, qos = QoS,
topic_id_type = TopicIdType} = Flags, topic_id_type = TopicIdType
io_lib:format("SN_SUBSCRIBE(D~w, Q~w, TopicIdType=~w, MsgId=~w, " } = Flags,
"TopicId=~w)", io_lib:format(
[bool(Dup), QoS, TopicIdType, Msgid, Topic]); "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)) -> format(?SN_SUBACK_MSG(Flags, TopicId, MsgId, ReturnCode)) ->
#mqtt_sn_flags{qos = QoS} = Flags, #mqtt_sn_flags{qos = QoS} = Flags,
io_lib:format("SN_SUBACK(GrantedQoS=~w, MsgId=~w, TopicId=~w, " io_lib:format(
"ReturnCode=~w)", "SN_SUBACK(GrantedQoS=~w, MsgId=~w, TopicId=~w, "
[QoS, MsgId, TopicId, ReturnCode]); "ReturnCode=~w)",
[QoS, MsgId, TopicId, ReturnCode]
);
format(?SN_UNSUBSCRIBE_MSG(Flags, Msgid, Topic)) -> format(?SN_UNSUBSCRIBE_MSG(Flags, Msgid, Topic)) ->
#mqtt_sn_flags{topic_id_type = TopicIdType} = Flags, #mqtt_sn_flags{topic_id_type = TopicIdType} = Flags,
io_lib:format("SN_UNSUBSCRIBE(TopicIdType=~w, MsgId=~w, TopicId=~w)", io_lib:format(
[TopicIdType, Msgid, Topic]); "SN_UNSUBSCRIBE(TopicIdType=~w, MsgId=~w, TopicId=~w)",
[TopicIdType, Msgid, Topic]
);
format(?SN_UNSUBACK_MSG(MsgId)) -> format(?SN_UNSUBACK_MSG(MsgId)) ->
io_lib:format("SN_UNSUBACK(MsgId=~w)", [MsgId]); io_lib:format("SN_UNSUBACK(MsgId=~w)", [MsgId]);
format(?SN_REGISTER_MSG(TopicId, MsgId, TopicName)) -> format(?SN_REGISTER_MSG(TopicId, MsgId, TopicName)) ->
io_lib:format("SN_REGISTER(TopicId=~w, MsgId=~w, TopicName=~s)", io_lib:format(
[TopicId, MsgId, TopicName]); "SN_REGISTER(TopicId=~w, MsgId=~w, TopicName=~s)",
[TopicId, MsgId, TopicName]
);
format(?SN_REGACK_MSG(TopicId, MsgId, ReturnCode)) -> format(?SN_REGACK_MSG(TopicId, MsgId, ReturnCode)) ->
io_lib:format("SN_REGACK(TopicId=~w, MsgId=~w, ReturnCode=~w)", io_lib:format(
[TopicId, MsgId, ReturnCode]); "SN_REGACK(TopicId=~w, MsgId=~w, ReturnCode=~w)",
[TopicId, MsgId, ReturnCode]
);
format(?SN_PINGREQ_MSG(ClientId)) -> format(?SN_PINGREQ_MSG(ClientId)) ->
io_lib:format("SN_PINGREQ(ClientId=~s)", [ClientId]); io_lib:format("SN_PINGREQ(ClientId=~s)", [ClientId]);
format(?SN_PINGRESP_MSG()) -> format(?SN_PINGRESP_MSG()) ->
"SN_PINGRESP()"; "SN_PINGRESP()";
format(?SN_DISCONNECT_MSG(Duration)) -> format(?SN_DISCONNECT_MSG(Duration)) ->
io_lib:format("SN_DISCONNECT(Duration=~w)", [Duration]); io_lib:format("SN_DISCONNECT(Duration=~w)", [Duration]);
format(#mqtt_sn_message{type = Type, variable = Var}) -> format(#mqtt_sn_message{type = Type, variable = Var}) ->
io_lib:format("mqtt_sn_message(type=~s, Var=~w)", io_lib:format(
[emqx_sn_frame:message_type(Type), Var]). "mqtt_sn_message(type=~s, Var=~w)",
[emqx_sn_frame:message_type(Type), Var]
).
is_message(#mqtt_sn_message{type = Type}) is_message(#mqtt_sn_message{type = Type}) when
when Type == ?SN_PUBLISH -> Type == ?SN_PUBLISH
->
true; true;
is_message(_) -> is_message(_) ->
false. false.
type(#mqtt_sn_message{type = Type}) -> type(#mqtt_sn_message{type = Type}) ->
type(Type); type(Type);
type(?SN_ADVERTISE) -> advertise; type(?SN_ADVERTISE) ->
type(?SN_SEARCHGW) -> serachgw; advertise;
type(?SN_GWINFO) -> gwinfo; type(?SN_SEARCHGW) ->
type(?SN_CONNECT) -> connect; serachgw;
type(?SN_CONNACK) -> connack; type(?SN_GWINFO) ->
type(?SN_WILLTOPICREQ) -> willtopicreq; gwinfo;
type(?SN_WILLTOPIC) -> willtopic; type(?SN_CONNECT) ->
type(?SN_WILLMSGREQ) -> willmsgreq; connect;
type(?SN_WILLMSG) -> willmsg; type(?SN_CONNACK) ->
type(?SN_REGISTER) -> register; connack;
type(?SN_REGACK) -> regack; type(?SN_WILLTOPICREQ) ->
type(?SN_PUBLISH) -> publish; willtopicreq;
type(?SN_PUBACK) -> puback; type(?SN_WILLTOPIC) ->
type(?SN_PUBCOMP) -> pubcomp; willtopic;
type(?SN_PUBREC) -> pubrec; type(?SN_WILLMSGREQ) ->
type(?SN_PUBREL) -> pubrel; willmsgreq;
type(?SN_SUBSCRIBE) -> subscribe; type(?SN_WILLMSG) ->
type(?SN_SUBACK) -> suback; willmsg;
type(?SN_UNSUBSCRIBE) -> unsubscribe; type(?SN_REGISTER) ->
type(?SN_UNSUBACK) -> unsuback; register;
type(?SN_PINGREQ) -> pingreq; type(?SN_REGACK) ->
type(?SN_PINGRESP) -> pingresp; regack;
type(?SN_DISCONNECT) -> disconnect; type(?SN_PUBLISH) ->
type(?SN_WILLTOPICUPD) -> willtopicupd; publish;
type(?SN_WILLTOPICRESP) -> willtopicresp; type(?SN_PUBACK) ->
type(?SN_WILLMSGUPD) -> willmsgupd; puback;
type(?SN_WILLMSGRESP) -> willmsgresp. 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.

View File

@ -21,29 +21,33 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-import(emqx_gateway_utils, -import(
[ normalize_config/1 emqx_gateway_utils,
, start_listeners/4 [
, stop_listeners/2 normalize_config/1,
]). start_listeners/4,
stop_listeners/2
]
).
%% APIs %% APIs
-export([ reg/0 -export([
, unreg/0 reg/0,
]). unreg/0
]).
-export([ on_gateway_load/2 -export([
, on_gateway_update/3 on_gateway_load/2,
, on_gateway_unload/2 on_gateway_update/3,
]). on_gateway_unload/2
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
reg() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [{cbkmod, ?MODULE}],
],
emqx_gateway_registry:reg(mqttsn, RegistryOptions). emqx_gateway_registry:reg(mqttsn, RegistryOptions).
unreg() -> unreg() ->
@ -53,10 +57,13 @@ unreg() ->
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_gateway_load(_Gateway = #{ name := GwName, on_gateway_load(
config := Config _Gateway = #{
}, Ctx) -> name := GwName,
config := Config
},
Ctx
) ->
%% We Also need to start `emqx_sn_broadcast` & %% We Also need to start `emqx_sn_broadcast` &
%% `emqx_sn_registry` process %% `emqx_sn_registry` process
case maps:get(broadcast, Config, false) of case maps:get(broadcast, Config, false) of
@ -66,32 +73,40 @@ on_gateway_load(_Gateway = #{ name := GwName,
%% FIXME: %% FIXME:
Port = 1884, Port = 1884,
SnGwId = maps:get(gateway_id, Config, undefined), SnGwId = maps:get(gateway_id, Config, undefined),
_ = emqx_sn_broadcast:start_link(SnGwId, Port), ok _ = emqx_sn_broadcast:start_link(SnGwId, Port),
ok
end, end,
PredefTopics = maps:get(predefined, Config, []), PredefTopics = maps:get(predefined, Config, []),
{ok, RegistrySvr} = emqx_sn_registry:start_link(GwName, PredefTopics), {ok, RegistrySvr} = emqx_sn_registry:start_link(GwName, PredefTopics),
NConfig = maps:without( NConfig = maps:without(
[broadcast, predefined], [broadcast, predefined],
Config#{registry => emqx_sn_registry:lookup_name(RegistrySvr)} Config#{registry => emqx_sn_registry:lookup_name(RegistrySvr)}
), ),
Listeners = emqx_gateway_utils:normalize_config(NConfig), Listeners = emqx_gateway_utils:normalize_config(NConfig),
ModCfg = #{frame_mod => emqx_sn_frame, ModCfg = #{
chann_mod => emqx_sn_channel frame_mod => emqx_sn_frame,
}, chann_mod => emqx_sn_channel
},
case start_listeners( case
Listeners, GwName, Ctx, ModCfg) of start_listeners(
Listeners, GwName, Ctx, ModCfg
)
of
{ok, ListenerPids} -> {ok, ListenerPids} ->
{ok, ListenerPids, _GwState = #{ctx => Ctx}}; {ok, ListenerPids, _GwState = #{ctx => Ctx}};
{error, {Reason, Listener}} -> {error, {Reason, Listener}} ->
throw({badconf, #{ key => listeners throw(
, value => Listener {badconf, #{
, reason => Reason key => listeners,
}}) value => Listener,
reason => Reason
}}
)
end. end.
on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> 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_unload(Gateway, GwState),
on_gateway_load(Gateway#{config => Config}, Ctx) on_gateway_load(Gateway#{config => Config}, Ctx)
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
logger:error("Failed to update ~ts; " logger:error(
"reason: {~0p, ~0p} stacktrace: ~0p", "Failed to update ~ts; "
[GwName, Class, Reason, Stk]), "reason: {~0p, ~0p} stacktrace: ~0p",
[GwName, Class, Reason, Stk]
),
{error, Reason} {error, Reason}
end. end.
on_gateway_unload(_Gateway = #{ name := GwName, on_gateway_unload(
config := Config _Gateway = #{
}, _GwState) -> name := GwName,
config := Config
},
_GwState
) ->
Listeners = normalize_config(Config), Listeners = normalize_config(Config),
stop_listeners(GwName, Listeners). stop_listeners(GwName, Listeners).

View File

@ -24,25 +24,27 @@
-include("src/mqttsn/include/emqx_sn.hrl"). -include("src/mqttsn/include/emqx_sn.hrl").
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-export([ start_link/2 -export([start_link/2]).
]).
-export([ register_topic/3 -export([
, unregister_topic/2 register_topic/3,
]). unregister_topic/2
]).
-export([ lookup_topic/3 -export([
, lookup_topic_id/3 lookup_topic/3,
]). lookup_topic_id/3
]).
%% gen_server callbacks %% gen_server callbacks
-export([ init/1 -export([
, handle_call/3 init/1,
, handle_cast/2 handle_call/3,
, handle_info/2 handle_cast/2,
, terminate/2 handle_info/2,
, code_change/3 terminate/2,
]). code_change/3
]).
-export([lookup_name/1]). -export([lookup_name/1]).
@ -52,21 +54,20 @@
-record(emqx_sn_registry, {key, value}). -record(emqx_sn_registry, {key, value}).
-type registry() :: {Tab :: atom(), -type registry() :: {Tab :: atom(), RegistryPid :: pid()}.
RegistryPid :: pid()}.
%%----------------------------------------------------------------------------- %%-----------------------------------------------------------------------------
-spec start_link(atom(), list()) -spec start_link(atom(), list()) ->
-> ignore ignore
| {ok, pid()} | {ok, pid()}
| {error, Reason :: term()}. | {error, Reason :: term()}.
start_link(InstaId, PredefTopics) -> start_link(InstaId, PredefTopics) ->
gen_server:start_link(?MODULE, [InstaId, PredefTopics], []). gen_server:start_link(?MODULE, [InstaId, PredefTopics], []).
-spec register_topic(registry(), emqx_types:clientid(), emqx_types:topic()) -spec register_topic(registry(), emqx_types:clientid(), emqx_types:topic()) ->
-> integer() integer()
| {error, term()}. | {error, term()}.
register_topic({_, Pid}, ClientId, TopicName) when is_binary(TopicName) -> register_topic({_, Pid}, ClientId, TopicName) when is_binary(TopicName) ->
case emqx_topic:wildcard(TopicName) of case emqx_topic:wildcard(TopicName) of
false -> 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 %% 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 %% relevant in case of subscriptions to a short topic name or to a topic
%% name which contains wildcard characters) %% name which contains wildcard characters)
true -> {error, wildcard_topic} true ->
{error, wildcard_topic}
end. end.
-spec lookup_topic(registry(), emqx_types:clientid(), pos_integer()) -spec lookup_topic(registry(), emqx_types:clientid(), pos_integer()) ->
-> undefined undefined
| binary(). | binary().
lookup_topic({Tab, _}, ClientId, TopicId) when is_integer(TopicId) -> lookup_topic({Tab, _}, ClientId, TopicId) when is_integer(TopicId) ->
case lookup_element(Tab, {predef, TopicId}, 3) of case lookup_element(Tab, {predef, TopicId}, 3) of
undefined -> undefined ->
lookup_element(Tab, {ClientId, TopicId}, 3); lookup_element(Tab, {ClientId, TopicId}, 3);
Topic -> Topic Topic ->
Topic
end. end.
-spec lookup_topic_id(registry(), emqx_types:clientid(), emqx_types:topic()) -spec lookup_topic_id(registry(), emqx_types:clientid(), emqx_types:topic()) ->
-> undefined undefined
| pos_integer() | pos_integer()
| {predef, integer()}. | {predef, integer()}.
lookup_topic_id({Tab, _}, ClientId, TopicName) when is_binary(TopicName) -> lookup_topic_id({Tab, _}, ClientId, TopicName) when is_binary(TopicName) ->
case lookup_element(Tab, {predef, TopicName}, 3) of case lookup_element(Tab, {predef, TopicName}, 3) of
undefined -> undefined ->
@ -102,7 +105,11 @@ lookup_topic_id({Tab, _}, ClientId, TopicName) when is_binary(TopicName) ->
%% @private %% @private
lookup_element(Tab, Key, Pos) -> 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. -spec unregister_topic(registry(), emqx_types:clientid()) -> ok.
unregister_topic({_, Pid}, ClientId) -> unregister_topic({_, Pid}, ClientId) ->
@ -123,32 +130,41 @@ init([InstaId, PredefTopics]) ->
%% {ClientId, TopicName} -> TopicId %% {ClientId, TopicName} -> TopicId
Tab = name(InstaId), Tab = name(InstaId),
ok = mria:create_table(Tab, [ ok = mria:create_table(Tab, [
{storage, ram_copies}, {storage, ram_copies},
{record_name, emqx_sn_registry}, {record_name, emqx_sn_registry},
{attributes, record_info(fields, emqx_sn_registry)}, {attributes, record_info(fields, emqx_sn_registry)},
{storage_properties, [{ets, [{read_concurrency, true}]}]}, {storage_properties, [{ets, [{read_concurrency, true}]}]},
{rlog_shard, ?SN_SHARD} {rlog_shard, ?SN_SHARD}
]), ]),
ok = mria:wait_for_tables([Tab]), ok = mria:wait_for_tables([Tab]),
MaxPredefId = lists:foldl( MaxPredefId = lists:foldl(
fun(#{id := TopicId, topic := TopicName0}, AccId) -> fun(#{id := TopicId, topic := TopicName0}, AccId) ->
TopicName = iolist_to_binary(TopicName0), TopicName = iolist_to_binary(TopicName0),
mria:dirty_write(Tab, #emqx_sn_registry{ mria:dirty_write(Tab, #emqx_sn_registry{
key = {predef, TopicId}, key = {predef, TopicId},
value = TopicName} value = TopicName
), }),
mria:dirty_write(Tab, #emqx_sn_registry{ mria:dirty_write(Tab, #emqx_sn_registry{
key = {predef, TopicName}, key = {predef, TopicName},
value = TopicId} value = TopicId
), }),
if TopicId > AccId -> TopicId; true -> AccId end if
end, 0, PredefTopics), TopicId > AccId -> TopicId;
true -> AccId
end
end,
0,
PredefTopics
),
{ok, #state{tabname = Tab, max_predef_topic_id = MaxPredefId}}. {ok, #state{tabname = Tab, max_predef_topic_id = MaxPredefId}}.
handle_call({register, ClientId, TopicName}, _From, handle_call(
State = #state{tabname = Tab, max_predef_topic_id = PredefId}) -> {register, ClientId, TopicName},
_From,
State = #state{tabname = Tab, max_predef_topic_id = PredefId}
) ->
case lookup_topic_id({Tab, self()}, ClientId, TopicName) of case lookup_topic_id({Tab, self()}, ClientId, TopicName) of
{predef, PredefTopicId} when is_integer(PredefTopicId) -> {predef, PredefTopicId} when is_integer(PredefTopicId) ->
{reply, PredefTopicId, State}; {reply, PredefTopicId, State};
TopicId when is_integer(TopicId) -> TopicId when is_integer(TopicId) ->
{reply, TopicId, State}; {reply, TopicId, State};
@ -158,15 +174,30 @@ handle_call({register, ClientId, TopicName}, _From,
{reply, {error, too_large}, State}; {reply, {error, too_large}, State};
TopicId -> TopicId ->
Fun = fun() -> Fun = fun() ->
mnesia:write(Tab, #emqx_sn_registry{ mnesia:write(
key = {ClientId, next_topic_id}, Tab,
value = TopicId + 1}, write), #emqx_sn_registry{
mnesia:write(Tab, #emqx_sn_registry{ key = {ClientId, next_topic_id},
key = {ClientId, TopicName}, value = TopicId + 1
value = TopicId}, write), },
mnesia:write(Tab, #emqx_sn_registry{ write
key = {ClientId, TopicId}, ),
value = TopicName}, 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, end,
case mria:transaction(?SN_SHARD, Fun) of case mria:transaction(?SN_SHARD, Fun) of
{atomic, ok} -> {atomic, ok} ->
@ -176,36 +207,39 @@ handle_call({register, ClientId, TopicName}, _From,
end end
end end
end; end;
handle_call({unregister, ClientId}, _From, State = #state{tabname = Tab}) -> handle_call({unregister, ClientId}, _From, State = #state{tabname = Tab}) ->
Registry = mnesia:dirty_match_object( Registry = mnesia:dirty_match_object(
Tab, Tab,
{emqx_sn_registry, {ClientId, '_'}, '_'} {emqx_sn_registry, {ClientId, '_'}, '_'}
), ),
lists:foreach(fun(R) -> lists:foreach(
mria:dirty_delete_object(Tab, R) fun(R) ->
end, Registry), mria:dirty_delete_object(Tab, R)
end,
Registry
),
{reply, ok, State}; {reply, ok, State};
handle_call(name, _From, State = #state{tabname = Tab}) -> handle_call(name, _From, State = #state{tabname = Tab}) ->
{reply, {Tab, self()}, State}; {reply, {Tab, self()}, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?SLOG(error, #{ msg => "unexpected_call" ?SLOG(error, #{
, call => Req msg => "unexpected_call",
}), call => Req
}),
{reply, ignored, State}. {reply, ignored, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?SLOG(error, #{ msg => "unexpected_cast" ?SLOG(error, #{
, cast => Msg msg => "unexpected_cast",
}), cast => Msg
}),
{noreply, State}. {noreply, State}.
handle_info(Info, State) -> handle_info(Info, State) ->
?SLOG(error, #{ msg => "unexpected_info" ?SLOG(error, #{
, info => Info msg => "unexpected_info",
}), info => Info
}),
{noreply, State}. {noreply, State}.
terminate(_Reason, _State) -> terminate(_Reason, _State) ->
@ -219,5 +253,5 @@ code_change(_OldVsn, State, _Extra) ->
next_topic_id(Tab, PredefId, ClientId) -> next_topic_id(Tab, PredefId, ClientId) ->
case mnesia:dirty_read(Tab, {ClientId, next_topic_id}) of case mnesia:dirty_read(Tab, {ClientId, next_topic_id}) of
[#emqx_sn_registry{value = Id}] -> Id; [#emqx_sn_registry{value = Id}] -> Id;
[] -> PredefId + 1 [] -> PredefId + 1
end. end.

View File

@ -14,189 +14,213 @@
%% limitations under the License. %% limitations under the License.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% MQTT-SN Types %% MQTT-SN Types
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(SN_ADVERTISE, 16#00). -define(SN_ADVERTISE, 16#00).
-define(SN_SEARCHGW, 16#01). -define(SN_SEARCHGW, 16#01).
-define(SN_GWINFO, 16#02). -define(SN_GWINFO, 16#02).
-define(SN_CONNECT, 16#04). -define(SN_CONNECT, 16#04).
-define(SN_CONNACK, 16#05). -define(SN_CONNACK, 16#05).
-define(SN_WILLTOPICREQ, 16#06). -define(SN_WILLTOPICREQ, 16#06).
-define(SN_WILLTOPIC, 16#07). -define(SN_WILLTOPIC, 16#07).
-define(SN_WILLMSGREQ, 16#08). -define(SN_WILLMSGREQ, 16#08).
-define(SN_WILLMSG, 16#09). -define(SN_WILLMSG, 16#09).
-define(SN_REGISTER, 16#0A). -define(SN_REGISTER, 16#0A).
-define(SN_REGACK, 16#0B). -define(SN_REGACK, 16#0B).
-define(SN_PUBLISH, 16#0C). -define(SN_PUBLISH, 16#0C).
-define(SN_PUBACK, 16#0D). -define(SN_PUBACK, 16#0D).
-define(SN_PUBCOMP, 16#0E). -define(SN_PUBCOMP, 16#0E).
-define(SN_PUBREC, 16#0F). -define(SN_PUBREC, 16#0F).
-define(SN_PUBREL, 16#10). -define(SN_PUBREL, 16#10).
-define(SN_SUBSCRIBE, 16#12). -define(SN_SUBSCRIBE, 16#12).
-define(SN_SUBACK, 16#13). -define(SN_SUBACK, 16#13).
-define(SN_UNSUBSCRIBE, 16#14). -define(SN_UNSUBSCRIBE, 16#14).
-define(SN_UNSUBACK, 16#15). -define(SN_UNSUBACK, 16#15).
-define(SN_PINGREQ, 16#16). -define(SN_PINGREQ, 16#16).
-define(SN_PINGRESP, 16#17). -define(SN_PINGRESP, 16#17).
-define(SN_DISCONNECT, 16#18). -define(SN_DISCONNECT, 16#18).
-define(SN_WILLTOPICUPD, 16#1A). -define(SN_WILLTOPICUPD, 16#1A).
-define(SN_WILLTOPICRESP, 16#1B). -define(SN_WILLTOPICRESP, 16#1B).
-define(SN_WILLMSGUPD, 16#1C). -define(SN_WILLMSGUPD, 16#1C).
-define(SN_WILLMSGRESP, 16#1D). -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_ACCEPTED, 16#00).
-define(SN_RC_CONGESTION, 16#01). -define(SN_RC_CONGESTION, 16#01).
-define(SN_RC_INVALID_TOPIC_ID, 16#02). -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 %% Custom Reason code by emqx
-define(SN_RC2_NOT_AUTHORIZE, 16#80). -define(SN_RC2_NOT_AUTHORIZE, 16#80).
-define(SN_RC2_FAILED_SESSION, 16#81). -define(SN_RC2_FAILED_SESSION, 16#81).
-define(SN_RC2_KEEPALIVE_TIMEOUT, 16#82). -define(SN_RC2_KEEPALIVE_TIMEOUT, 16#82).
-define(SN_RC2_EXCEED_LIMITATION, 16#83). -define(SN_RC2_EXCEED_LIMITATION, 16#83).
-define(SN_RC2_REACHED_MAX_RETRY, 16#84). -define(SN_RC2_REACHED_MAX_RETRY, 16#84).
-define(QOS_NEG1, 3). -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 %% MQTT-SN Message
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-record(mqtt_sn_flags, {dup = false, -record(mqtt_sn_flags, {
qos = 0, dup = false,
retain = false, qos = 0,
will = false, retain = false,
clean_start = false, will = false,
topic_id_type = 0}). 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(), -record(mqtt_sn_message, {
variable :: mqtt_sn_variable()}). 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), -define(SN_ADVERTISE_MSG(GwId, Duration), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_ADVERTISE, type = ?SN_ADVERTISE,
variable = {GwId, Duration}}). variable = {GwId, Duration}
}).
-define(SN_SEARCHGW_MSG(Radius), -define(SN_SEARCHGW_MSG(Radius), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_SEARCHGW, type = ?SN_SEARCHGW,
variable = Radius}). variable = Radius
}).
-define(SN_GWINFO_MSG(GwId, GwAddr), -define(SN_GWINFO_MSG(GwId, GwAddr), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_GWINFO, type = ?SN_GWINFO,
variable = {GwId, GwAddr}}). variable = {GwId, GwAddr}
}).
-define(SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId), -define(SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_CONNECT, type = ?SN_CONNECT,
variable = {Flags, ProtocolId, Duration, ClientId}}). variable = {Flags, ProtocolId, Duration, ClientId}
}).
-define(SN_CONNACK_MSG(ReturnCode), -define(SN_CONNACK_MSG(ReturnCode), #mqtt_sn_message{type = ?SN_CONNACK, variable = ReturnCode}).
#mqtt_sn_message{type = ?SN_CONNACK, variable = ReturnCode}).
-define(SN_WILLTOPICREQ_MSG(), -define(SN_WILLTOPICREQ_MSG(), #mqtt_sn_message{type = ?SN_WILLTOPICREQ}).
#mqtt_sn_message{type = ?SN_WILLTOPICREQ}).
-define(SN_WILLTOPIC_MSG(Flags, Topic), -define(SN_WILLTOPIC_MSG(Flags, Topic), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_WILLTOPIC, type = ?SN_WILLTOPIC,
variable = {Flags, Topic}}). variable = {Flags, Topic}
}).
-define(SN_WILLTOPIC_EMPTY_MSG, -define(SN_WILLTOPIC_EMPTY_MSG, #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_WILLTOPIC, type = ?SN_WILLTOPIC,
variable = undefined}). variable = undefined
}).
-define(SN_WILLMSGREQ_MSG(), -define(SN_WILLMSGREQ_MSG(), #mqtt_sn_message{type = ?SN_WILLMSGREQ}).
#mqtt_sn_message{type = ?SN_WILLMSGREQ}).
-define(SN_WILLMSG_MSG(Msg), -define(SN_WILLMSG_MSG(Msg), #mqtt_sn_message{type = ?SN_WILLMSG, variable = Msg}).
#mqtt_sn_message{type = ?SN_WILLMSG, variable = Msg}).
-define(SN_REGISTER_MSG(TopicId, MsgId, TopicName), -define(SN_REGISTER_MSG(TopicId, MsgId, TopicName), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_REGISTER, type = ?SN_REGISTER,
variable = {TopicId, MsgId, TopicName}}). variable = {TopicId, MsgId, TopicName}
}).
-define(SN_REGACK_MSG(TopicId, MsgId, ReturnCode), -define(SN_REGACK_MSG(TopicId, MsgId, ReturnCode), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_REGACK, type = ?SN_REGACK,
variable = {TopicId, MsgId, ReturnCode}}). variable = {TopicId, MsgId, ReturnCode}
}).
-define(SN_PUBLISH_MSG(Flags, TopicId, MsgId, Data), -define(SN_PUBLISH_MSG(Flags, TopicId, MsgId, Data), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_PUBLISH, type = ?SN_PUBLISH,
variable = {Flags, TopicId, MsgId, Data}}). variable = {Flags, TopicId, MsgId, Data}
}).
-define(SN_PUBACK_MSG(TopicId, MsgId, ReturnCode), -define(SN_PUBACK_MSG(TopicId, MsgId, ReturnCode), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_PUBACK, type = ?SN_PUBACK,
variable = {TopicId, MsgId, ReturnCode}}). variable = {TopicId, MsgId, ReturnCode}
}).
%% Type: SN_PUBREC | SN_PUBREL | SN_PUBCOMP %% Type: SN_PUBREC | SN_PUBREL | SN_PUBCOMP
-define(SN_PUBREC_MSG(Type, MsgId), -define(SN_PUBREC_MSG(Type, MsgId), #mqtt_sn_message{
#mqtt_sn_message{type = Type, type = Type,
variable = MsgId}). variable = MsgId
}).
-define(SN_SUBSCRIBE_MSG(Flags, MsgId, Topic), -define(SN_SUBSCRIBE_MSG(Flags, MsgId, Topic), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_SUBSCRIBE, type = ?SN_SUBSCRIBE,
variable = {Flags, MsgId, Topic}}). variable = {Flags, MsgId, Topic}
}).
-define(SN_SUBSCRIBE_MSG_TYPE(Type, Topic, QoS), -define(SN_SUBSCRIBE_MSG_TYPE(Type, Topic, QoS), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_SUBSCRIBE, type = ?SN_SUBSCRIBE,
variable = { variable = {
#mqtt_sn_flags{qos = QoS, topic_id_type = Type}, #mqtt_sn_flags{qos = QoS, topic_id_type = Type},
_, Topic}}). _,
Topic
}
}).
-define(SN_SUBACK_MSG(Flags, TopicId, MsgId, ReturnCode), -define(SN_SUBACK_MSG(Flags, TopicId, MsgId, ReturnCode), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_SUBACK, type = ?SN_SUBACK,
variable = {Flags, TopicId, MsgId, ReturnCode}}). variable = {Flags, TopicId, MsgId, ReturnCode}
}).
-define(SN_UNSUBSCRIBE_MSG(Flags, MsgId, Topic), -define(SN_UNSUBSCRIBE_MSG(Flags, MsgId, Topic), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_UNSUBSCRIBE, type = ?SN_UNSUBSCRIBE,
variable = {Flags, MsgId, Topic}}). variable = {Flags, MsgId, Topic}
}).
-define(SN_UNSUBSCRIBE_MSG_TYPE(Type, Topic), -define(SN_UNSUBSCRIBE_MSG_TYPE(Type, Topic), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_UNSUBSCRIBE, type = ?SN_UNSUBSCRIBE,
variable = { variable = {
#mqtt_sn_flags{topic_id_type = Type}, #mqtt_sn_flags{topic_id_type = Type},
_, Topic}}). _,
Topic
}
}).
-define(SN_UNSUBACK_MSG(MsgId), -define(SN_UNSUBACK_MSG(MsgId), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_UNSUBACK, type = ?SN_UNSUBACK,
variable = MsgId}). variable = MsgId
}).
-define(SN_PINGREQ_MSG(ClientId), -define(SN_PINGREQ_MSG(ClientId), #mqtt_sn_message{type = ?SN_PINGREQ, variable = ClientId}).
#mqtt_sn_message{type = ?SN_PINGREQ, variable = ClientId}).
-define(SN_PINGRESP_MSG(), #mqtt_sn_message{type = ?SN_PINGRESP}). -define(SN_PINGRESP_MSG(), #mqtt_sn_message{type = ?SN_PINGRESP}).
-define(SN_DISCONNECT_MSG(Duration), -define(SN_DISCONNECT_MSG(Duration), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_DISCONNECT, type = ?SN_DISCONNECT,
variable = Duration}). variable = Duration
}).
-define(SN_WILLTOPICUPD_MSG(Flags, Topic), -define(SN_WILLTOPICUPD_MSG(Flags, Topic), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_WILLTOPICUPD, type = ?SN_WILLTOPICUPD,
variable = {Flags, Topic}}). variable = {Flags, Topic}
}).
-define(SN_WILLTOPICRESP_MSG(ReturnCode), -define(SN_WILLTOPICRESP_MSG(ReturnCode), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_WILLTOPICRESP, type = ?SN_WILLTOPICRESP,
variable = ReturnCode}). variable = ReturnCode
}).
-define(SN_WILLMSGUPD_MSG(Msg), -define(SN_WILLMSGUPD_MSG(Msg), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_WILLMSGUPD, type = ?SN_WILLMSGUPD,
variable = Msg}). variable = Msg
}).
-define(SN_WILLMSGRESP_MSG(ReturnCode), -define(SN_WILLMSGRESP_MSG(ReturnCode), #mqtt_sn_message{
#mqtt_sn_message{type = ?SN_WILLMSGRESP, type = ?SN_WILLMSGRESP,
variable = ReturnCode}). variable = ReturnCode
}).
-define(SN_NORMAL_TOPIC, 0). -define(SN_NORMAL_TOPIC, 0).
-define(SN_PREDEFINED_TOPIC, 1). -define(SN_PREDEFINED_TOPIC, 1).
-define(SN_SHORT_TOPIC, 2). -define(SN_SHORT_TOPIC, 2).
-define(SN_RESERVED_TOPIC, 3). -define(SN_RESERVED_TOPIC, 3).
-define(SN_INVALID_TOPIC_ID, 0). -define(SN_INVALID_TOPIC_ID, 0).

View File

@ -18,20 +18,21 @@
-behaviour(emqx_bpapi). -behaviour(emqx_bpapi).
-export([ introduced_in/0 -export([
introduced_in/0,
, get_chan_info/3 get_chan_info/3,
, set_chan_info/4 set_chan_info/4,
, get_chan_stats/3 get_chan_stats/3,
, set_chan_stats/4 set_chan_stats/4,
, kick_session/4 kick_session/4,
, get_chann_conn_mod/3 get_chann_conn_mod/3,
, lookup_by_clientid/3 lookup_by_clientid/3,
, takeover_session/3 takeover_session/3,
, call/4 call/4,
, call/5 call/5,
, cast/4 cast/4
]). ]).
-include_lib("emqx/include/bpapi.hrl"). -include_lib("emqx/include/bpapi.hrl").
@ -39,75 +40,107 @@ introduced_in() ->
"5.0.0". "5.0.0".
-spec lookup_by_clientid([node()], emqx_gateway_cm:gateway_name(), emqx_types:clientid()) -> -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) -> lookup_by_clientid(Nodes, GwName, ClientId) ->
rpc:multicall(Nodes, emqx_gateway_cm, do_lookup_by_clientid, [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()) -spec get_chan_info(emqx_gateway_cm:gateway_name(), emqx_types:clientid(), pid()) ->
-> emqx_types:infos() | undefined | {badrpc, _}. emqx_types:infos() | undefined | {badrpc, _}.
get_chan_info(GwName, ClientId, ChanPid) -> get_chan_info(GwName, ClientId, ChanPid) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_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(), -spec set_chan_info(
emqx_types:clientid(), emqx_gateway_cm:gateway_name(),
pid(), emqx_types:clientid(),
emqx_types:infos()) -> boolean() | {badrpc, _}. pid(),
emqx_types:infos()
) -> boolean() | {badrpc, _}.
set_chan_info(GwName, ClientId, ChanPid, Infos) -> set_chan_info(GwName, ClientId, ChanPid, Infos) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_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()) -spec get_chan_stats(emqx_gateway_cm:gateway_name(), emqx_types:clientid(), pid()) ->
-> emqx_types:stats() | undefined | {badrpc, _}. emqx_types:stats() | undefined | {badrpc, _}.
get_chan_stats(GwName, ClientId, ChanPid) -> get_chan_stats(GwName, ClientId, ChanPid) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_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(), -spec set_chan_stats(
emqx_types:clientid(), emqx_gateway_cm:gateway_name(),
pid(), emqx_types:clientid(),
emqx_types:stats()) -> boolean() | {badrpc, _}. pid(),
emqx_types:stats()
) -> boolean() | {badrpc, _}.
set_chan_stats(GwName, ClientId, ChanPid, Stats) -> set_chan_stats(GwName, ClientId, ChanPid, Stats) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_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(), -spec kick_session(
kick | discard, emqx_gateway_cm:gateway_name(),
emqx_types:clientid(), pid()) -> _. kick | discard,
emqx_types:clientid(),
pid()
) -> _.
kick_session(GwName, Action, ClientId, ChanPid) -> kick_session(GwName, Action, ClientId, ChanPid) ->
rpc:call(node(ChanPid), rpc:call(
emqx_gateway_cm, do_kick_session, node(ChanPid),
[GwName, Action, ClientId, ChanPid]). emqx_gateway_cm,
do_kick_session,
[GwName, Action, ClientId, ChanPid]
).
-spec get_chann_conn_mod(emqx_gateway_cm:gateway_name(), -spec get_chann_conn_mod(
emqx_types:clientid(), emqx_gateway_cm:gateway_name(),
pid()) -> atom() | {badrpc, _}. emqx_types:clientid(),
pid()
) -> atom() | {badrpc, _}.
get_chann_conn_mod(GwName, ClientId, ChanPid) -> get_chann_conn_mod(GwName, ClientId, ChanPid) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_get_chann_conn_mod, rpc:call(
[GwName, ClientId, ChanPid]). node(ChanPid),
emqx_gateway_cm,
do_get_chann_conn_mod,
[GwName, ClientId, ChanPid]
).
-spec takeover_session(emqx_gateway_cm:gateway_name(), -spec takeover_session(
emqx_types:clientid(), emqx_gateway_cm:gateway_name(),
pid()) -> boolean() | {badrpc, _}. emqx_types:clientid(),
pid()
) -> boolean() | {badrpc, _}.
takeover_session(GwName, ClientId, ChanPid) -> takeover_session(GwName, ClientId, ChanPid) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_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(), -spec call(
emqx_types:clientid(), emqx_gateway_cm:gateway_name(),
pid(), emqx_types:clientid(),
term(), pid(),
timeout()) -> term() | {badrpc, _}. term(),
timeout()
) -> term() | {badrpc, _}.
call(GwName, ClientId, ChanPid, Req, Timeout) -> call(GwName, ClientId, ChanPid, Req, Timeout) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_call, rpc:call(
[GwName, ClientId, ChanPid, Req, Timeout]). node(ChanPid),
emqx_gateway_cm,
do_call,
[GwName, ClientId, ChanPid, Req, Timeout]
).
-spec call(emqx_gateway_cm:gateway_name(), -spec call(
emqx_types:clientid(), emqx_gateway_cm:gateway_name(),
pid(), emqx_types:clientid(),
term()) -> term() | {badrpc, _}. pid(),
term()
) -> term() | {badrpc, _}.
call(GwName, ClientId, ChanPid, Req) -> call(GwName, ClientId, ChanPid, Req) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_call, rpc:call(
[GwName, ClientId, ChanPid, Req]). node(ChanPid),
emqx_gateway_cm,
do_call,
[GwName, ClientId, ChanPid, Req]
).
-spec cast(emqx_gateway_cm:gateway_name(), -spec cast(
emqx_types:clientid(), emqx_gateway_cm:gateway_name(),
pid(), emqx_types:clientid(),
term()) -> term() | {badrpc, _}. pid(),
term()
) -> term() | {badrpc, _}.
cast(GwName, ClientId, ChanPid, Req) -> cast(GwName, ClientId, ChanPid, Req) ->
rpc:call(node(ChanPid), emqx_gateway_cm, do_cast, [GwName, ClientId, ChanPid, Req]). rpc:call(node(ChanPid), emqx_gateway_cm, do_cast, [GwName, ClientId, ChanPid, Req]).

File diff suppressed because it is too large Load Diff

View File

@ -72,46 +72,53 @@
-include("src/stomp/include/emqx_stomp.hrl"). -include("src/stomp/include/emqx_stomp.hrl").
-export([ initial_parse_state/1 -export([
, parse/2 initial_parse_state/1,
, serialize_opts/0 parse/2,
, serialize_pkt/2 serialize_opts/0,
]). serialize_pkt/2
]).
-export([ make/1 -export([
, make/2 make/1,
, make/3 make/2,
, format/1 make/3,
]). format/1
]).
-export([ type/1 -export([
, is_message/1 type/1,
]). is_message/1
]).
-define(NULL, 0). -define(NULL, 0).
-define(CR, $\r). -define(CR, $\r).
-define(LF, $\n). -define(LF, $\n).
-define(BSL, $\\). -define(BSL, $\\).
-define(COLON, $:). -define(COLON, $:).
-define(IS_ESC(Ch), Ch == ?CR; Ch == ?LF; Ch == ?BSL; Ch == ?COLON). -define(IS_ESC(Ch), Ch == ?CR; Ch == ?LF; Ch == ?BSL; Ch == ?COLON).
-record(parser_state, {cmd, -record(parser_state, {
headers = [], cmd,
hdname, headers = [],
acc = <<>> :: binary(), hdname,
limit}). acc = <<>> :: binary(),
limit
}).
-record(frame_limit, {max_header_num, max_header_length, max_body_length}). -record(frame_limit, {max_header_num, max_header_length, max_body_length}).
-type(parse_result() :: {ok, stomp_frame(), binary(), parse_state()} -type parse_result() ::
| {more, parse_state()}). {ok, stomp_frame(), binary(), parse_state()}
| {more, parse_state()}.
-type(parse_state() :: -type parse_state() ::
#{phase := none | command | headers | hdname | hdvalue | body, #{
phase := none | command | headers | hdname | hdvalue | body,
pre => binary(), pre => binary(),
state := #parser_state{} state := #parser_state{}
}). }.
%-dialyzer({nowarn_function, [serialize_pkt/2,make/1]}). %-dialyzer({nowarn_function, [serialize_pkt/2,make/1]}).
@ -122,10 +129,10 @@ initial_parse_state(Opts) ->
limit(Opts) -> limit(Opts) ->
#frame_limit{ #frame_limit{
max_header_num = g(max_header_num, Opts, ?MAX_HEADER_NUM), max_header_num = g(max_header_num, Opts, ?MAX_HEADER_NUM),
max_header_length = g(max_header_length, Opts, ?MAX_HEADER_LENGTH), max_header_length = g(max_header_length, Opts, ?MAX_HEADER_LENGTH),
max_body_length = g(max_body_length, Opts, ?MAX_BODY_LENGTH) max_body_length = g(max_body_length, Opts, ?MAX_BODY_LENGTH)
}. }.
g(Key, Opts, Val) -> g(Key, Opts, Val) ->
maps:get(Key, Opts, Val). maps:get(Key, Opts, Val).
@ -133,14 +140,12 @@ g(Key, Opts, Val) ->
-spec parse(binary(), parse_state()) -> parse_result(). -spec parse(binary(), parse_state()) -> parse_result().
parse(<<>>, Parser) -> parse(<<>>, Parser) ->
{more, Parser}; {more, Parser};
parse(Bytes, #{phase := body, length := Len, state := State}) -> parse(Bytes, #{phase := body, length := Len, state := State}) ->
parse(body, Bytes, State, Len); parse(body, Bytes, State, Len);
parse(<<?LF, Bytes/binary>>, #{phase := hdname, state := State}) -> parse(<<?LF, Bytes/binary>>, #{phase := hdname, state := State}) ->
parse(body, Bytes, State, content_len(State)); parse(body, Bytes, State, content_len(State));
parse(Bytes, #{phase := Phase, state := State}) when Phase =/= none -> parse(Bytes, #{phase := Phase, state := State}) when Phase =/= none ->
parse(Phase, Bytes, State); parse(Phase, Bytes, State);
parse(Bytes, Parser = #{pre := Pre}) -> parse(Bytes, Parser = #{pre := Pre}) ->
parse(<<Pre/binary, Bytes/binary>>, maps:without([pre], Parser)); parse(<<Pre/binary, Bytes/binary>>, maps:without([pre], Parser));
parse(<<?CR, ?LF, Rest/binary>>, #{phase := Phase, state := State}) -> parse(<<?CR, ?LF, Rest/binary>>, #{phase := Phase, state := State}) ->
@ -149,17 +154,21 @@ parse(<<?CR>>, Parser) ->
{more, Parser#{pre => <<?CR>>}}; {more, Parser#{pre => <<?CR>>}};
parse(<<?CR, _Ch:8, _Rest/binary>>, _Parser) -> parse(<<?CR, _Ch:8, _Rest/binary>>, _Parser) ->
error(linefeed_expected); error(linefeed_expected);
parse(<<?BSL>>, Parser = #{phase := Phase}) when
parse(<<?BSL>>, Parser = #{phase := Phase}) when Phase =:= hdname; Phase =:= hdname;
Phase =:= hdvalue -> Phase =:= hdvalue
->
{more, Parser#{pre => <<?BSL>>}}; {more, Parser#{pre => <<?BSL>>}};
parse(<<?BSL, Ch:8, Rest/binary>>, parse(
#{phase := Phase, state := State}) when Phase =:= hdname; <<?BSL, Ch:8, Rest/binary>>,
Phase =:= hdvalue -> #{phase := Phase, state := State}
) when
Phase =:= hdname;
Phase =:= hdvalue
->
parse(Phase, Rest, acc(unescape(Ch), State)); parse(Phase, Rest, acc(unescape(Ch), State));
parse(<<?LF>>, Parser = #{phase := none}) -> parse(<<?LF>>, Parser = #{phase := none}) ->
{more, Parser}; {more, Parser};
parse(Bytes, #{phase := none, state := State}) -> parse(Bytes, #{phase := none, state := State}) ->
parse(command, Bytes, State). parse(command, Bytes, State).
@ -170,12 +179,10 @@ parse(command, <<Ch:8, Rest/binary>>, State) ->
parse(command, Rest, acc(Ch, State)); parse(command, Rest, acc(Ch, State));
parse(command, <<>>, State) -> parse(command, <<>>, State) ->
{more, #{phase => command, state => State}}; {more, #{phase => command, state => State}};
parse(headers, <<?LF, Rest/binary>>, State) -> parse(headers, <<?LF, Rest/binary>>, State) ->
parse(body, Rest, State, content_len(State#parser_state{acc = <<>>})); parse(body, Rest, State, content_len(State#parser_state{acc = <<>>}));
parse(headers, Bin, State) -> parse(headers, Bin, State) ->
parse(hdname, Bin, State); parse(hdname, Bin, State);
parse(hdname, <<?LF, _Rest/binary>>, _State) -> parse(hdname, <<?LF, _Rest/binary>>, _State) ->
error(unexpected_linefeed); error(unexpected_linefeed);
parse(hdname, <<?COLON, Rest/binary>>, State = #parser_state{acc = Acc}) -> parse(hdname, <<?COLON, Rest/binary>>, State = #parser_state{acc = Acc}) ->
@ -184,13 +191,16 @@ parse(hdname, <<Ch:8, Rest/binary>>, State) ->
parse(hdname, Rest, acc(Ch, State)); parse(hdname, Rest, acc(Ch, State));
parse(hdname, <<>>, State) -> parse(hdname, <<>>, State) ->
{more, #{phase => hdname, state => State}}; {more, #{phase => hdname, state => State}};
parse(
parse(hdvalue, <<?LF, Rest/binary>>, hdvalue,
State = #parser_state{headers = Headers, hdname = Name, acc = Acc}) -> <<?LF, Rest/binary>>,
NState = State#parser_state{headers = add_header(Name, Acc, Headers), State = #parser_state{headers = Headers, hdname = Name, acc = Acc}
hdname = undefined, ) ->
acc = <<>> NState = State#parser_state{
}, headers = add_header(Name, Acc, Headers),
hdname = undefined,
acc = <<>>
},
parse(headers, Rest, NState); parse(headers, Rest, NState);
parse(hdvalue, <<Ch:8, Rest/binary>>, State) -> parse(hdvalue, <<Ch:8, Rest/binary>>, State) ->
parse(hdvalue, Rest, acc(Ch, State)); parse(hdvalue, Rest, acc(Ch, State));
@ -205,28 +215,32 @@ parse(body, Bin, State, none) ->
[Chunk, Rest] -> [Chunk, Rest] ->
{ok, new_frame(acc(Chunk, State)), Rest, new_state(State)}; {ok, new_frame(acc(Chunk, State)), Rest, new_state(State)};
[Chunk] -> [Chunk] ->
{more, #{phase => body, {more, #{
length => none, phase => body,
state => acc(Chunk, State)}} length => none,
state => acc(Chunk, State)
}}
end; end;
parse(body, Bin, State, Len) when byte_size(Bin) >= (Len+1) -> parse(body, Bin, State, Len) when byte_size(Bin) >= (Len + 1) ->
<<Chunk:Len/binary, ?NULL, Rest/binary>> = Bin, <<Chunk:Len/binary, ?NULL, Rest/binary>> = Bin,
{ok, new_frame(acc(Chunk, State)), Rest, new_state(State)}; {ok, new_frame(acc(Chunk, State)), Rest, new_state(State)};
parse(body, Bin, State, Len) -> parse(body, Bin, State, Len) ->
{more, #{phase => body, {more, #{
length => Len - byte_size(Bin), phase => body,
state => acc(Bin, State)}}. length => Len - byte_size(Bin),
state => acc(Bin, State)
}}.
add_header(Name, Value, Headers) -> add_header(Name, Value, Headers) ->
case lists:keyfind(Name, 1, Headers) of case lists:keyfind(Name, 1, Headers) of
{Name, _} -> Headers; {Name, _} -> Headers;
false -> [{Name, Value} | Headers] false -> [{Name, Value} | Headers]
end. end.
content_len(#parser_state{headers = Headers}) -> content_len(#parser_state{headers = Headers}) ->
case lists:keyfind(<<"content-length">>, 1, Headers) of case lists:keyfind(<<"content-length">>, 1, Headers) of
{_, Val} -> list_to_integer(binary_to_list(Val)); {_, Val} -> list_to_integer(binary_to_list(Val));
false -> none false -> none
end. end.
new_frame(#parser_state{cmd = Cmd, headers = Headers, acc = Acc}) -> 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) %% \n (octet 92 and 110) translates to line feed (octet 10)
%% \c (octet 92 and 99) translates to : (octet 58) %% \c (octet 92 and 99) translates to : (octet 58)
%% \\ (octet 92 and 92) translates to \ (octet 92) %% \\ (octet 92 and 92) translates to \ (octet 92)
unescape($r) -> ?CR; unescape($r) -> ?CR;
unescape($n) -> ?LF; unescape($n) -> ?LF;
unescape($c) -> ?COLON; unescape($c) -> ?COLON;
unescape($\\) -> ?BSL; unescape($\\) -> ?BSL;
unescape(_Ch) -> error(cannnot_unescape). unescape(_Ch) -> error(cannnot_unescape).
@ -256,31 +270,41 @@ serialize_opts() ->
serialize_pkt(#stomp_frame{command = ?CMD_HEARTBEAT}, _SerializeOpts) -> serialize_pkt(#stomp_frame{command = ?CMD_HEARTBEAT}, _SerializeOpts) ->
<<$\n>>; <<$\n>>;
serialize_pkt(
serialize_pkt(#stomp_frame{command = Cmd, headers = Headers, body = Body}, #stomp_frame{command = Cmd, headers = Headers, body = Body},
_SerializeOpts) -> _SerializeOpts
) ->
Headers1 = lists:keydelete(<<"content-length">>, 1, Headers), Headers1 = lists:keydelete(<<"content-length">>, 1, Headers),
Headers2 = Headers2 =
case iolist_size(Body) of case iolist_size(Body) of
0 -> Headers1; 0 -> Headers1;
Len -> Headers1 ++ [{<<"content-length">>, Len}] Len -> Headers1 ++ [{<<"content-length">>, Len}]
end, end,
[Cmd, [
?LF, [serialize_pkt(header, Header) || Header <- Headers2], Cmd,
?LF, Body, 0]; ?LF,
[serialize_pkt(header, Header) || Header <- Headers2],
?LF,
Body,
0
];
serialize_pkt(header, {Name, Val}) when is_integer(Val) -> serialize_pkt(header, {Name, Val}) when is_integer(Val) ->
[escape(Name), ?COLON, integer_to_list(Val), ?LF]; [escape(Name), ?COLON, integer_to_list(Val), ?LF];
serialize_pkt(header, {Name, Val}) -> serialize_pkt(header, {Name, Val}) ->
[escape(Name), ?COLON, escape(Val), ?LF]. [escape(Name), ?COLON, escape(Val), ?LF].
escape(Bin) when is_binary(Bin) -> escape(Bin) when is_binary(Bin) ->
<< <<(escape(Ch))/binary>> || <<Ch>> <= Bin >>; <<<<(escape(Ch))/binary>> || <<Ch>> <= Bin>>;
escape(?CR) -> <<?BSL, $r>>; escape(?CR) ->
escape(?LF) -> <<?BSL, $n>>; <<?BSL, $r>>;
escape(?BSL) -> <<?BSL, ?BSL>>; escape(?LF) ->
escape(?COLON) -> <<?BSL, $c>>; <<?BSL, $n>>;
escape(Ch) -> <<Ch>>. escape(?BSL) ->
<<?BSL, ?BSL>>;
escape(?COLON) ->
<<?BSL, $c>>;
escape(Ch) ->
<<Ch>>.
new_state(#parser_state{limit = Limit}) -> new_state(#parser_state{limit = Limit}) ->
#{phase => none, state => #parser_state{limit = Limit}}. #{phase => none, state => #parser_state{limit = Limit}}.
@ -295,9 +319,10 @@ make(?CMD_HEARTBEAT) ->
#stomp_frame{command = ?CMD_HEARTBEAT}. #stomp_frame{command = ?CMD_HEARTBEAT}.
make(<<"CONNECTED">>, Headers) -> make(<<"CONNECTED">>, Headers) ->
#stomp_frame{command = <<"CONNECTED">>, #stomp_frame{
headers = [{<<"server">>, ?STOMP_SERVER} | Headers]}; command = <<"CONNECTED">>,
headers = [{<<"server">>, ?STOMP_SERVER} | Headers]
};
make(Command, Headers) -> make(Command, Headers) ->
#stomp_frame{command = Command, headers = Headers}. #stomp_frame{command = Command, headers = Headers}.
@ -307,27 +332,45 @@ make(Command, Headers, Body) ->
%% @doc Format a frame %% @doc Format a frame
format(Frame) -> serialize_pkt(Frame, #{}). format(Frame) -> serialize_pkt(Frame, #{}).
is_message(#stomp_frame{command = CMD}) is_message(#stomp_frame{command = CMD}) when
when CMD == ?CMD_SEND; CMD == ?CMD_SEND;
CMD == ?CMD_MESSAGE -> CMD == ?CMD_MESSAGE
->
true; true;
is_message(_) -> false. is_message(_) ->
false.
type(#stomp_frame{command = CMD}) -> type(#stomp_frame{command = CMD}) ->
type(CMD); type(CMD);
type(?CMD_STOMP) -> connect; type(?CMD_STOMP) ->
type(?CMD_CONNECT) -> connect; connect;
type(?CMD_SEND) -> send; type(?CMD_CONNECT) ->
type(?CMD_SUBSCRIBE) -> subscribe; connect;
type(?CMD_UNSUBSCRIBE) -> unsubscribe; type(?CMD_SEND) ->
type(?CMD_BEGIN) -> 'begin'; send;
type(?CMD_COMMIT) -> commit; type(?CMD_SUBSCRIBE) ->
type(?CMD_ABORT) -> abort; subscribe;
type(?CMD_ACK) -> ack; type(?CMD_UNSUBSCRIBE) ->
type(?CMD_NACK) -> nack; unsubscribe;
type(?CMD_DISCONNECT) -> disconnect; type(?CMD_BEGIN) ->
type(?CMD_CONNECTED) -> connected; 'begin';
type(?CMD_MESSAGE) -> message; type(?CMD_COMMIT) ->
type(?CMD_RECEIPT) -> receipt; commit;
type(?CMD_ERROR) -> error; type(?CMD_ABORT) ->
type(?CMD_HEARTBEAT) -> heartbeat. 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.

View File

@ -19,20 +19,22 @@
-include("src/stomp/include/emqx_stomp.hrl"). -include("src/stomp/include/emqx_stomp.hrl").
-export([ init/1 -export([
, check/3 init/1,
, reset/3 check/3,
, info/1 reset/3,
, interval/2 info/1,
]). interval/2
]).
-record(heartbeater, {interval, statval, repeat}). -record(heartbeater, {interval, statval, repeat}).
-type name() :: incoming | outgoing. -type name() :: incoming | outgoing.
-type heartbeat() :: #{incoming => #heartbeater{}, -type heartbeat() :: #{
outgoing => #heartbeater{} incoming => #heartbeater{},
}. outgoing => #heartbeater{}
}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
@ -42,43 +44,51 @@
init({0, 0}) -> init({0, 0}) ->
#{}; #{};
init({Cx, Cy}) -> init({Cx, Cy}) ->
maps:filter(fun(_, V) -> V /= undefined end, maps:filter(
#{incoming => heartbeater(Cx), fun(_, V) -> V /= undefined end,
outgoing => heartbeater(Cy) #{
}). incoming => heartbeater(Cx),
outgoing => heartbeater(Cy)
}
).
heartbeater(0) -> heartbeater(0) ->
undefined; undefined;
heartbeater(I) -> heartbeater(I) ->
#heartbeater{ #heartbeater{
interval = I, interval = I,
statval = 0, statval = 0,
repeat = 0 repeat = 0
}. }.
-spec check(name(), pos_integer(), heartbeat()) -spec check(name(), pos_integer(), heartbeat()) ->
-> {ok, heartbeat()} {ok, heartbeat()}
| {error, timeout}. | {error, timeout}.
check(Name, NewVal, HrtBt) -> check(Name, NewVal, HrtBt) ->
HrtBter = maps:get(Name, HrtBt), HrtBter = maps:get(Name, HrtBt),
case check(NewVal, HrtBter) of case check(NewVal, HrtBter) of
{error, _} = R -> R; {error, _} = R -> R;
{ok, NHrtBter} -> {ok, NHrtBter} -> {ok, HrtBt#{Name => NHrtBter}}
{ok, HrtBt#{Name => NHrtBter}}
end. end.
check(NewVal, HrtBter = #heartbeater{statval = OldVal, check(
repeat = Repeat}) -> NewVal,
HrtBter = #heartbeater{
statval = OldVal,
repeat = Repeat
}
) ->
if if
NewVal =/= OldVal -> NewVal =/= OldVal ->
{ok, HrtBter#heartbeater{statval = NewVal, repeat = 0}}; {ok, HrtBter#heartbeater{statval = NewVal, repeat = 0}};
Repeat < 1 -> Repeat < 1 ->
{ok, HrtBter#heartbeater{repeat = Repeat + 1}}; {ok, HrtBter#heartbeater{repeat = Repeat + 1}};
true -> {error, timeout} true ->
{error, timeout}
end. end.
-spec reset(name(), pos_integer(), heartbeat()) -spec reset(name(), pos_integer(), heartbeat()) ->
-> heartbeat(). heartbeat().
reset(Name, NewVal, HrtBt) -> reset(Name, NewVal, HrtBt) ->
HrtBter = maps:get(Name, HrtBt), HrtBter = maps:get(Name, HrtBt),
HrtBt#{Name => reset(NewVal, HrtBter)}. HrtBt#{Name => reset(NewVal, HrtBter)}.
@ -88,11 +98,19 @@ reset(NewVal, HrtBter) ->
-spec info(heartbeat()) -> map(). -spec info(heartbeat()) -> map().
info(HrtBt) -> info(HrtBt) ->
maps:map(fun(_, #heartbeater{interval = Intv, maps:map(
statval = Val, fun(
repeat = Repeat}) -> _,
#heartbeater{
interval = Intv,
statval = Val,
repeat = Repeat
}
) ->
#{interval => Intv, statval => Val, repeat => Repeat} #{interval => Intv, statval => Val, repeat => Repeat}
end, HrtBt). end,
HrtBt
).
interval(Type, HrtBt) -> interval(Type, HrtBt) ->
case maps:get(Type, HrtBt, undefined) of case maps:get(Type, HrtBt, undefined) of

View File

@ -21,21 +21,26 @@
-include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/logger.hrl").
-include_lib("emqx_gateway/include/emqx_gateway.hrl"). -include_lib("emqx_gateway/include/emqx_gateway.hrl").
-import(emqx_gateway_utils, -import(
[ normalize_config/1 emqx_gateway_utils,
, start_listeners/4 [
, stop_listeners/2 normalize_config/1,
]). start_listeners/4,
stop_listeners/2
]
).
%% APIs %% APIs
-export([ reg/0 -export([
, unreg/0 reg/0,
]). unreg/0
]).
-export([ on_gateway_load/2 -export([
, on_gateway_update/3 on_gateway_load/2,
, on_gateway_unload/2 on_gateway_update/3,
]). on_gateway_unload/2
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
@ -43,8 +48,7 @@
-spec reg() -> ok | {error, any()}. -spec reg() -> ok | {error, any()}.
reg() -> reg() ->
RegistryOptions = [ {cbkmod, ?MODULE} RegistryOptions = [{cbkmod, ?MODULE}],
],
emqx_gateway_registry:reg(stomp, RegistryOptions). emqx_gateway_registry:reg(stomp, RegistryOptions).
-spec unreg() -> ok | {error, any()}. -spec unreg() -> ok | {error, any()}.
@ -55,24 +59,35 @@ unreg() ->
%% emqx_gateway_registry callbacks %% emqx_gateway_registry callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
on_gateway_load(_Gateway = #{ name := GwName, on_gateway_load(
config := Config _Gateway = #{
}, Ctx) -> name := GwName,
config := Config
},
Ctx
) ->
Listeners = normalize_config(Config), Listeners = normalize_config(Config),
ModCfg = #{frame_mod => emqx_stomp_frame, ModCfg = #{
chann_mod => emqx_stomp_channel frame_mod => emqx_stomp_frame,
}, chann_mod => emqx_stomp_channel
case start_listeners( },
Listeners, GwName, Ctx, ModCfg) of case
start_listeners(
Listeners, GwName, Ctx, ModCfg
)
of
{ok, ListenerPids} -> {ok, ListenerPids} ->
%% FIXME: How to throw an exception to interrupt the restart logic ? %% FIXME: How to throw an exception to interrupt the restart logic ?
%% FIXME: Assign ctx to GwState %% FIXME: Assign ctx to GwState
{ok, ListenerPids, _GwState = #{ctx => Ctx}}; {ok, ListenerPids, _GwState = #{ctx => Ctx}};
{error, {Reason, Listener}} -> {error, {Reason, Listener}} ->
throw({badconf, #{ key => listeners throw(
, vallue => Listener {badconf, #{
, reason => Reason key => listeners,
}}) vallue => Listener,
reason => Reason
}}
)
end. end.
on_gateway_update(Config, Gateway, GwState = #{ctx := Ctx}) -> 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_unload(Gateway, GwState),
on_gateway_load(Gateway#{config => Config}, Ctx) on_gateway_load(Gateway#{config => Config}, Ctx)
catch catch
Class : Reason : Stk -> Class:Reason:Stk ->
logger:error("Failed to update ~ts; " logger:error(
"reason: {~0p, ~0p} stacktrace: ~0p", "Failed to update ~ts; "
[GwName, Class, Reason, Stk]), "reason: {~0p, ~0p} stacktrace: ~0p",
[GwName, Class, Reason, Stk]
),
{error, Reason} {error, Reason}
end. end.
on_gateway_unload(_Gateway = #{ name := GwName, on_gateway_unload(
config := Config _Gateway = #{
}, _GwState) -> name := GwName,
config := Config
},
_GwState
) ->
Listeners = normalize_config(Config), Listeners = normalize_config(Config),
stop_listeners(GwName, Listeners). stop_listeners(GwName, Listeners).

View File

@ -26,23 +26,23 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% client command %% client command
-define(CMD_STOMP, <<"STOMP">>). -define(CMD_STOMP, <<"STOMP">>).
-define(CMD_CONNECT, <<"CONNECT">>). -define(CMD_CONNECT, <<"CONNECT">>).
-define(CMD_SEND, <<"SEND">>). -define(CMD_SEND, <<"SEND">>).
-define(CMD_SUBSCRIBE, <<"SUBSCRIBE">>). -define(CMD_SUBSCRIBE, <<"SUBSCRIBE">>).
-define(CMD_UNSUBSCRIBE, <<"UNSUBSCRIBE">>). -define(CMD_UNSUBSCRIBE, <<"UNSUBSCRIBE">>).
-define(CMD_BEGIN, <<"BEGIN">>). -define(CMD_BEGIN, <<"BEGIN">>).
-define(CMD_COMMIT, <<"COMMIT">>). -define(CMD_COMMIT, <<"COMMIT">>).
-define(CMD_ABORT, <<"ABORT">>). -define(CMD_ABORT, <<"ABORT">>).
-define(CMD_ACK, <<"ACK">>). -define(CMD_ACK, <<"ACK">>).
-define(CMD_NACK, <<"NACK">>). -define(CMD_NACK, <<"NACK">>).
-define(CMD_DISCONNECT, <<"DISCONNECT">>). -define(CMD_DISCONNECT, <<"DISCONNECT">>).
%% server command %% server command
-define(CMD_CONNECTED, <<"CONNECTED">>). -define(CMD_CONNECTED, <<"CONNECTED">>).
-define(CMD_MESSAGE, <<"MESSAGE">>). -define(CMD_MESSAGE, <<"MESSAGE">>).
-define(CMD_RECEIPT, <<"RECEIPT">>). -define(CMD_RECEIPT, <<"RECEIPT">>).
-define(CMD_ERROR, <<"ERROR">>). -define(CMD_ERROR, <<"ERROR">>).
-define(CMD_HEARTBEAT, <<"HEARTBEAT">>). -define(CMD_HEARTBEAT, <<"HEARTBEAT">>).
%-type client_command() :: ?CMD_SEND | ?CMD_SUBSCRIBE | ?CMD_UNSUBSCRIBE %-type client_command() :: ?CMD_SEND | ?CMD_SUBSCRIBE | ?CMD_UNSUBSCRIBE
@ -57,10 +57,10 @@
-type server_command() :: binary(). -type server_command() :: binary().
-record(stomp_frame, { -record(stomp_frame, {
command :: client_command() | server_command(), command :: client_command() | server_command(),
headers = [], headers = [],
body = <<>> :: iodata()} body = <<>> :: iodata()
). }).
-type stomp_frame() :: #stomp_frame{}. -type stomp_frame() :: #stomp_frame{}.
@ -68,10 +68,11 @@
-define(PACKET(CMD, Headers), #stomp_frame{command = CMD, headers = Headers}). -define(PACKET(CMD, Headers), #stomp_frame{command = CMD, headers = Headers}).
-define(PACKET(CMD, Headers, Body), #stomp_frame{command = CMD, -define(PACKET(CMD, Headers, Body), #stomp_frame{
headers = Headers, command = CMD,
body = Body headers = Headers,
}). body = Body
}).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Frame Size Limits %% Frame Size Limits
@ -87,8 +88,8 @@
%% and then close the connection. %% and then close the connection.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(MAX_HEADER_NUM, 10). -define(MAX_HEADER_NUM, 10).
-define(MAX_HEADER_LENGTH, 1024). -define(MAX_HEADER_LENGTH, 1024).
-define(MAX_BODY_LENGTH, 65536). -define(MAX_BODY_LENGTH, 65536).
-endif. -endif.

View File

@ -19,37 +19,40 @@
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
-import(emqx_gateway_test_utils, -import(
[ request/2 emqx_gateway_test_utils,
, request/3 [
]). request/2,
request/3
]
).
-include_lib("er_coap_client/include/coap.hrl"). -include_lib("er_coap_client/include/coap.hrl").
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-define(CONF_DEFAULT, <<" -define(CONF_DEFAULT, <<
gateway.coap "\n"
{ "gateway.coap\n"
idle_timeout = 30s "{\n"
enable_stats = false " idle_timeout = 30s\n"
mountpoint = \"\" " enable_stats = false\n"
notify_type = qos " mountpoint = \"\"\n"
connection_required = true " notify_type = qos\n"
subscribe_qos = qos1 " connection_required = true\n"
publish_qos = qos1 " subscribe_qos = qos1\n"
" publish_qos = qos1\n"
listeners.udp.default "\n"
{bind = 5683} " listeners.udp.default\n"
} " {bind = 5683}\n"
">>). "}\n"
>>).
-define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)). -define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)).
-define(PS_PREFIX, "coap://127.0.0.1/ps"). -define(PS_PREFIX, "coap://127.0.0.1/ps").
-define(MQTT_PREFIX, "coap://127.0.0.1/mqtt"). -define(MQTT_PREFIX, "coap://127.0.0.1/mqtt").
all() -> emqx_common_test_helpers:all(?MODULE). all() -> emqx_common_test_helpers:all(?MODULE).
init_per_suite(Config) -> init_per_suite(Config) ->
@ -58,7 +61,7 @@ init_per_suite(Config) ->
Config. Config.
end_per_suite(_) -> end_per_suite(_) ->
{ok, _} = emqx:remove_config([<<"gateway">>,<<"coap">>]), {ok, _} = emqx:remove_config([<<"gateway">>, <<"coap">>]),
emqx_mgmt_api_test_util:end_suite([emqx_gateway]). emqx_mgmt_api_test_util:end_suite([emqx_gateway]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -72,13 +75,15 @@ t_connection(_) ->
timer:sleep(100), timer:sleep(100),
?assertNotEqual( ?assertNotEqual(
[], [],
emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)), emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)
),
%% heartbeat %% heartbeat
HeartURI = ?MQTT_PREFIX ++ HeartURI =
"/connection?clientid=client1&token=" ++ ?MQTT_PREFIX ++
Token, "/connection?clientid=client1&token=" ++
Token,
?LOGT("send heartbeat request:~ts~n", [HeartURI]), ?LOGT("send heartbeat request:~ts~n", [HeartURI]),
{ok, changed, _} = er_coap_client:request(put, HeartURI), {ok, changed, _} = er_coap_client:request(put, HeartURI),
@ -87,8 +92,9 @@ t_connection(_) ->
timer:sleep(100), timer:sleep(100),
?assertEqual( ?assertEqual(
[], [],
emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)) emqx_gateway_cm_registry:lookup_channels(coap, <<"client1">>)
)
end, end,
do(Action). do(Action).
@ -110,9 +116,8 @@ t_publish(_) ->
{deliver, Topic, Msg} -> {deliver, Topic, Msg} ->
?assertEqual(Topic, Msg#message.topic), ?assertEqual(Topic, Msg#message.topic),
?assertEqual(Payload, Msg#message.payload) ?assertEqual(Payload, Msg#message.payload)
after after 500 ->
500 -> ?assert(false)
?assert(false)
end end
end, end,
with_connection(Action). with_connection(Action).
@ -168,7 +173,6 @@ t_subscribe(_) ->
?assertEqual([], emqx:subscribers(Topic)). ?assertEqual([], emqx:subscribers(Topic)).
t_un_subscribe(_) -> t_un_subscribe(_) ->
Topic = <<"/abc">>, Topic = <<"/abc">>,
Fun = fun(Channel, Token) -> Fun = fun(Channel, Token) ->
@ -232,11 +236,17 @@ t_clients_api(_) ->
#{clientid := ClientId} = Client1, #{clientid := ClientId} = Client1,
%% searching %% searching
{200, #{data := [Client2]}} = {200, #{data := [Client2]}} =
request(get, "/gateway/coap/clients", request(
[{<<"clientid">>, ClientId}]), get,
"/gateway/coap/clients",
[{<<"clientid">>, ClientId}]
),
{200, #{data := [Client3]}} = {200, #{data := [Client3]}} =
request(get, "/gateway/coap/clients", request(
[{<<"like_clientid">>, <<"cli">>}]), get,
"/gateway/coap/clients",
[{<<"like_clientid">>, <<"cli">>}]
),
%% lookup %% lookup
{200, Client4} = {200, Client4} =
request(get, "/gateway/coap/clients/client1"), request(get, "/gateway/coap/clients/client1"),
@ -255,18 +265,20 @@ t_clients_subscription_api(_) ->
%% list %% list
{200, []} = request(get, Path), {200, []} = request(get, Path),
%% create %% create
SubReq = #{ topic => <<"tx">> SubReq = #{
, qos => 0 topic => <<"tx">>,
, nl => 0 qos => 0,
, rap => 0 nl => 0,
, rh => 0 rap => 0,
}, rh => 0
},
{201, SubsResp} = request(post, Path, SubReq), {201, SubsResp} = request(post, Path, SubReq),
{200, [SubsResp2]} = request(get, Path), {200, [SubsResp2]} = request(get, Path),
?assertEqual( ?assertEqual(
maps:get(topic, SubsResp), maps:get(topic, SubsResp),
maps:get(topic, SubsResp2)), maps:get(topic, SubsResp2)
),
{204, _} = request(delete, Path ++ "/tx"), {204, _} = request(delete, Path ++ "/tx"),
@ -276,57 +288,58 @@ t_clients_subscription_api(_) ->
t_clients_get_subscription_api(_) -> t_clients_get_subscription_api(_) ->
Fun = fun(Channel, Token) -> Fun = fun(Channel, Token) ->
Path = "/gateway/coap/clients/client1/subscriptions", Path = "/gateway/coap/clients/client1/subscriptions",
%% list %% list
{200, []} = request(get, Path), {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) {200, []} = request(get, Path)
end, end,
with_connection(Fun). with_connection(Fun).
t_on_offline_event(_) -> t_on_offline_event(_) ->
Fun = fun(Channel) -> Fun = fun(Channel) ->
emqx_hooks:add('client.connected', {emqx_sys, on_client_connected, []}), emqx_hooks:add('client.connected', {emqx_sys, on_client_connected, []}),
emqx_hooks:add('client.disconnected', {emqx_sys, on_client_disconnected, []}), emqx_hooks:add('client.disconnected', {emqx_sys, on_client_disconnected, []}),
ConnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/connected">>, ConnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/connected">>,
emqx_broker:subscribe(ConnectedSub), emqx_broker:subscribe(ConnectedSub),
timer:sleep(100), timer:sleep(100),
Token = connection(Channel), Token = connection(Channel),
?assertMatch(#message{}, receive_deliver(500)), ?assertMatch(#message{}, receive_deliver(500)),
DisconnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/disconnected">>, DisconnectedSub = <<"$SYS/brokers/+/gateway/coap/clients/+/disconnected">>,
emqx_broker:subscribe(DisconnectedSub), emqx_broker:subscribe(DisconnectedSub),
timer:sleep(100), 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(ConnectedSub),
emqx_broker:unsubscribe(DisconnectedSub), emqx_broker:unsubscribe(DisconnectedSub),
emqx_hooks:del('client.connected', {emqx_sys, on_client_connected}), emqx_hooks:del('client.connected', {emqx_sys, on_client_connected}),
emqx_hooks:del('client.disconnected', {emqx_sys, on_client_disconnected}), emqx_hooks:del('client.disconnected', {emqx_sys, on_client_disconnected}),
timer:sleep(500) timer:sleep(500)
end, end,
do(Fun). do(Fun).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% helpers %% helpers
connection(Channel) -> connection(Channel) ->
URI = ?MQTT_PREFIX ++ URI =
"/connection?clientid=client1&username=admin&password=public", ?MQTT_PREFIX ++
"/connection?clientid=client1&username=admin&password=public",
Req = make_req(post), Req = make_req(post),
{ok, created, Data} = do_request(Channel, URI, Req), {ok, created, Data} = do_request(Channel, URI, Req),
#coap_content{payload = BinToken} = Data, #coap_content{payload = BinToken} = Data,
@ -343,7 +356,6 @@ observe(Channel, Token, true) ->
Req = make_req(get, <<>>, [{observe, 0}]), Req = make_req(get, <<>>, [{observe, 0}]),
{ok, content, _Data} = do_request(Channel, URI, Req), {ok, content, _Data} = do_request(Channel, URI, Req),
ok; ok;
observe(Channel, Token, false) -> observe(Channel, Token, false) ->
URI = ?PS_PREFIX ++ "/coap/observe?clientid=client1&token=" ++ Token, URI = ?PS_PREFIX ++ "/coap/observe?clientid=client1&token=" ++ Token,
Req = make_req(get, <<>>, [{observe, 1}]), Req = make_req(get, <<>>, [{observe, 1}]),
@ -360,7 +372,6 @@ make_req(Method, Payload, Opts) ->
er_coap_message:request(con, Method, Payload, Opts). er_coap_message:request(con, Method, Payload, Opts).
do_request(Channel, URI, #coap_message{options = Opts} = Req) -> do_request(Channel, URI, #coap_message{options = Opts} = Req) ->
{_, _, Path, Query} = er_coap_client:resolve_uri(URI), {_, _, Path, Query} = er_coap_client:resolve_uri(URI),
Opts2 = [{uri_path, Path}, {uri_query, Query} | Opts], Opts2 = [{uri_path, Path}, {uri_query, Query} | Opts],
Req2 = Req#coap_message{options = Opts2}, Req2 = Req#coap_message{options = Opts2},
@ -371,18 +382,17 @@ do_request(Channel, URI, #coap_message{options = Opts} = Req) ->
with_response(Channel) -> with_response(Channel) ->
receive receive
{coap_response, _ChId, Channel, {coap_response, _ChId, Channel, _Ref, Message = #coap_message{method = Code}} ->
_Ref, Message=#coap_message{method=Code}} ->
return_response(Code, Message); return_response(Code, Message);
{coap_error, _ChId, Channel, _Ref, reset} -> {coap_error, _ChId, Channel, _Ref, reset} ->
{error, reset} {error, reset}
after 2000 -> after 2000 ->
{error, timeout} {error, timeout}
end. end.
return_response({ok, Code}, Message) -> return_response({ok, Code}, Message) ->
{ok, Code, er_coap_message:get_content(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}; {error, Code};
return_response({error, Code}, Message) -> return_response({error, Code}, Message) ->
{error, Code, er_coap_message:get_content(Message)}. {error, Code, er_coap_message:get_content(Message)}.
@ -413,5 +423,5 @@ receive_deliver(Wait) ->
{deliver, _, Msg} -> {deliver, _, Msg} ->
Msg Msg
after Wait -> after Wait ->
{error, timeout} {error, timeout}
end. end.

View File

@ -23,26 +23,28 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-define(CONF_DEFAULT, <<" -define(CONF_DEFAULT, <<
gateway.coap { "\n"
idle_timeout = 30s "gateway.coap {\n"
enable_stats = false " idle_timeout = 30s\n"
mountpoint = \"\" " enable_stats = false\n"
notify_type = qos " mountpoint = \"\"\n"
connection_required = true " notify_type = qos\n"
subscribe_qos = qos1 " connection_required = true\n"
publish_qos = qos1 " subscribe_qos = qos1\n"
listeners.udp.default { " publish_qos = qos1\n"
bind = 5683 " listeners.udp.default {\n"
} " bind = 5683\n"
} " }\n"
">>). "}\n"
>>).
-define(HOST, "127.0.0.1"). -define(HOST, "127.0.0.1").
-define(PORT, 5683). -define(PORT, 5683).
-define(CONN_URI, -define(CONN_URI,
"coap://127.0.0.1/mqtt/connection?clientid=client1&" "coap://127.0.0.1/mqtt/connection?clientid=client1&"
"username=admin&password=public"). "username=admin&password=public"
).
-define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)). -define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)).
@ -59,7 +61,7 @@ init_per_suite(Config) ->
Config. Config.
end_per_suite(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]), emqx_mgmt_api_test_util:end_suite([emqx_gateway]),
Config. Config.
@ -72,18 +74,21 @@ t_send_request_api(_) ->
Path = emqx_mgmt_api_test_util:api_path(["gateway/coap/clients/client1/request"]), Path = emqx_mgmt_api_test_util:api_path(["gateway/coap/clients/client1/request"]),
Token = <<"atoken">>, Token = <<"atoken">>,
Payload = <<"simple echo this">>, Payload = <<"simple echo this">>,
Req = #{token => Token, Req = #{
payload => Payload, token => Token,
timeout => <<"10s">>, payload => Payload,
content_type => <<"text/plain">>, timeout => <<"10s">>,
method => <<"get">>}, content_type => <<"text/plain">>,
method => <<"get">>
},
Auth = emqx_mgmt_api_test_util:auth_header_(), Auth = emqx_mgmt_api_test_util:auth_header_(),
{ok, Response} = emqx_mgmt_api_test_util:request_api(post, {ok, Response} = emqx_mgmt_api_test_util:request_api(
Path, post,
"method=get", Path,
Auth, "method=get",
Req Auth,
), Req
),
#{<<"token">> := RToken, <<"payload">> := RPayload} = #{<<"token">> := RToken, <<"payload">> := RPayload} =
emqx_json:decode(Response, [return_maps]), emqx_json:decode(Response, [return_maps]),
?assertEqual(Token, RToken), ?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"), is_list(Options) orelse error("Options must be a list"),
case resolve_uri(?CONN_URI) of case resolve_uri(?CONN_URI) of
{coap, {IpAddr, Port}, Path, Query} -> {coap, {IpAddr, Port}, Path, Query} ->
Request0 = emqx_coap_message:request(con, Method, Content, Request0 = emqx_coap_message:request(
[{uri_path, Path}, con,
{uri_query, Query} | Options]), Method,
Content,
[
{uri_path, Path},
{uri_query, Query}
| Options
]
),
Request = Request0#coap_message{id = MsgId}, Request = Request0#coap_message{id = MsgId},
?LOGT("send_coap_request Request=~p", [Request]), ?LOGT("send_coap_request Request=~p", [Request]),
RequestBinary = emqx_coap_frame:serialize_pkt(Request, undefined), RequestBinary = emqx_coap_frame:serialize_pkt(Request, undefined),
?LOGT("test udp socket send to ~p:~p, data=~p", ?LOGT(
[IpAddr, Port, RequestBinary]), "test udp socket send to ~p:~p, data=~p",
[IpAddr, Port, RequestBinary]
),
ok = gen_udp:send(UdpSock, IpAddr, Port, RequestBinary); ok = gen_udp:send(UdpSock, IpAddr, Port, RequestBinary);
{SchemeDiff, ChIdDiff, _, _} -> {SchemeDiff, ChIdDiff, _, _} ->
error( error(
lists:flatten( lists:flatten(
io_lib:format( io_lib:format(
"scheme ~ts or ChId ~ts does not match with socket", "scheme ~ts or ChId ~ts does not match with socket",
[SchemeDiff, ChIdDiff]) [SchemeDiff, ChIdDiff]
)) )
)
)
end. end.
test_recv_coap_response(UdpSock) -> test_recv_coap_response(UdpSock) ->
{ok, {Address, Port, Packet}} = gen_udp:recv(UdpSock, 0, 2000), {ok, {Address, Port, Packet}} = gen_udp:recv(UdpSock, 0, 2000),
{ok, Response, _, _} = emqx_coap_frame:parse(Packet, undefined), {ok, Response, _, _} = emqx_coap_frame:parse(Packet, undefined),
?LOGT("test udp receive from ~p:~p, data1=~p, Response=~p", ?LOGT(
[Address, Port, Packet, Response]), "test udp receive from ~p:~p, data1=~p, Response=~p",
[Address, Port, Packet, Response]
),
#coap_message{ #coap_message{
type = ack, method = Method, id = Id, type = ack,
token = Token, options = Options, payload = Payload} = Response, method = Method,
?LOGT("receive coap response Method=~p, Id=~p, Token=~p, " id = Id,
"Options=~p, Payload=~p", [Method, Id, Token, Options, Payload]), 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. Response.
test_recv_coap_request(UdpSock) -> test_recv_coap_request(UdpSock) ->
@ -148,11 +174,18 @@ test_recv_coap_request(UdpSock) ->
{ok, {_Address, _Port, Packet}} -> {ok, {_Address, _Port, Packet}} ->
{ok, Request, _, _} = emqx_coap_frame:parse(Packet, undefined), {ok, Request, _, _} = emqx_coap_frame:parse(Packet, undefined),
#coap_message{ #coap_message{
type = con, method = Method, id = Id, type = con,
token = Token, payload = Payload, options = Options} = Request, method = Method,
?LOGT("receive coap request Method=~p, Id=~p, " id = Id,
"Token=~p, Options=~p, Payload=~p", token = Token,
[Method, Id, Token, Options, Payload]), 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; Request;
{error, Reason} -> {error, Reason} ->
?LOGT("test_recv_coap_request failed, Reason=~p", [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). ok = gen_udp:send(UdpSock, IpAddr, Port, Binary).
resolve_uri(Uri) -> resolve_uri(Uri) ->
{ok, #{scheme := Scheme, {ok,
host := Host, #{
port := PortNo, scheme := Scheme,
path := Path} = URIMap} = emqx_http_lib:uri_parse(Uri), host := Host,
port := PortNo,
path := Path
} = URIMap} = emqx_http_lib:uri_parse(Uri),
Query = maps:get(query, URIMap, undefined), Query = maps:get(query, URIMap, undefined),
{ok, PeerIP} = inet:getaddr(Host, inet), {ok, PeerIP} = inet:getaddr(Host, inet),
{Scheme, {PeerIP, PortNo}, split_path(Path), split_query(Query)}. {Scheme, {PeerIP, PortNo}, split_path(Path), split_query(Query)}.
@ -181,16 +217,18 @@ split_path([$/]) -> [];
split_path([$/ | Path]) -> split_segments(Path, $/, []). split_path([$/ | Path]) -> split_segments(Path, $/, []).
split_query(undefined) -> #{}; split_query(undefined) -> #{};
split_query(Path) -> split_query(Path) -> split_segments(Path, $&, []).
split_segments(Path, $&, []).
split_segments(Path, Char, Acc) -> split_segments(Path, Char, Acc) ->
case string:rchr(Path, Char) of case string:rchr(Path, Char) of
0 -> 0 ->
[make_segment(Path) | Acc]; [make_segment(Path) | Acc];
N when N > 0 -> N when N > 0 ->
split_segments(string:substr(Path, 1, N-1), Char, split_segments(
[make_segment(string:substr(Path, N+1)) | Acc]) string:substr(Path, 1, N - 1),
Char,
[make_segment(string:substr(Path, N + 1)) | Acc]
)
end. end.
make_segment(Seg) -> make_segment(Seg) ->
@ -199,17 +237,15 @@ make_segment(Seg) ->
get_path([], Acc) -> get_path([], Acc) ->
%?LOGT("get_path Acc=~p", [Acc]), %?LOGT("get_path Acc=~p", [Acc]),
Acc; Acc;
get_path([{uri_path, Path1}|T], Acc) -> get_path([{uri_path, Path1} | T], Acc) ->
%?LOGT("Path=~p, Acc=~p", [Path1, Acc]), %?LOGT("Path=~p, Acc=~p", [Path1, Acc]),
get_path(T, join_path(Path1, Acc)); get_path(T, join_path(Path1, Acc));
get_path([{_, _}|T], Acc) -> get_path([{_, _} | T], Acc) ->
get_path(T, Acc). get_path(T, Acc).
join_path([], Acc) -> Acc; join_path([], Acc) -> Acc;
join_path([<<"/">>|T], Acc) -> join_path([<<"/">> | T], Acc) -> join_path(T, Acc);
join_path(T, Acc); join_path([H | T], Acc) -> join_path(T, <<Acc/binary, $/, H/binary>>).
join_path([H|T], Acc) ->
join_path(T, <<Acc/binary, $/, H/binary>>).
sprintf(Format, Args) -> sprintf(Format, Args) ->
lists:flatten(io_lib:format(Format, Args)). lists:flatten(io_lib:format(Format, Args)).

View File

@ -19,17 +19,20 @@
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
-import(emqx_exproto_echo_svr, -import(
[ frame_connect/2 emqx_exproto_echo_svr,
, frame_connack/1 [
, frame_publish/3 frame_connect/2,
, frame_puback/1 frame_connack/1,
, frame_subscribe/2 frame_publish/3,
, frame_suback/1 frame_puback/1,
, frame_unsubscribe/1 frame_subscribe/2,
, frame_unsuback/1 frame_suback/1,
, frame_disconnect/0 frame_unsubscribe/1,
]). frame_unsuback/1,
frame_disconnect/0
]
).
-include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl").
@ -42,7 +45,7 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
all() -> all() ->
[{group, Name} || Name <- metrics()]. [{group, Name} || Name <- metrics()].
groups() -> groups() ->
Cases = emqx_common_test_helpers:all(?MODULE), Cases = emqx_common_test_helpers:all(?MODULE),
@ -67,11 +70,13 @@ end_per_group(_, Cfg) ->
set_special_cfg(emqx_gateway) -> set_special_cfg(emqx_gateway) ->
LisType = get(grpname), LisType = get(grpname),
emqx_config:put( emqx_config:put(
[gateway, exproto], [gateway, exproto],
#{server => #{bind => 9100}, #{
handler => #{address => "http://127.0.0.1:9001"}, server => #{bind => 9100},
listeners => listener_confs(LisType) handler => #{address => "http://127.0.0.1:9001"},
}); listeners => listener_confs(LisType)
}
);
set_special_cfg(_App) -> set_special_cfg(_App) ->
ok. ok.
@ -90,11 +95,12 @@ t_mountpoint_echo(Cfg) ->
SockType = proplists:get_value(listener_type, Cfg), SockType = proplists:get_value(listener_type, Cfg),
Sock = open(SockType), Sock = open(SockType),
Client = #{proto_name => <<"demo">>, Client = #{
proto_ver => <<"v0.1">>, proto_name => <<"demo">>,
clientid => <<"test_client_1">>, proto_ver => <<"v0.1">>,
mountpoint => <<"ct/">> clientid => <<"test_client_1">>,
}, mountpoint => <<"ct/">>
},
Password = <<"123456">>, Password = <<"123456">>,
ConnBin = frame_connect(Client, Password), ConnBin = frame_connect(Client, Password),
@ -124,7 +130,7 @@ t_mountpoint_echo(Cfg) ->
receive receive
{deliver, _, _} -> ok {deliver, _, _} -> ok
after 1000 -> after 1000 ->
error(echo_not_running) error(echo_not_running)
end, end,
close(Sock). close(Sock).
@ -132,15 +138,19 @@ t_auth_deny(Cfg) ->
SockType = proplists:get_value(listener_type, Cfg), SockType = proplists:get_value(listener_type, Cfg),
Sock = open(SockType), Sock = open(SockType),
Client = #{proto_name => <<"demo">>, Client = #{
proto_ver => <<"v0.1">>, proto_name => <<"demo">>,
clientid => <<"test_client_1">> proto_ver => <<"v0.1">>,
}, clientid => <<"test_client_1">>
},
Password = <<"123456">>, Password = <<"123456">>,
ok = meck:new(emqx_gateway_ctx, [passthrough, no_history, no_link]), ok = meck:new(emqx_gateway_ctx, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_gateway_ctx, authenticate, ok = meck:expect(
fun(_, _) -> {error, ?RC_NOT_AUTHORIZED} end), emqx_gateway_ctx,
authenticate,
fun(_, _) -> {error, ?RC_NOT_AUTHORIZED} end
),
ConnBin = frame_connect(Client, Password), ConnBin = frame_connect(Client, Password),
ConnAckBin = frame_connack(1), ConnAckBin = frame_connack(1),
@ -148,19 +158,21 @@ t_auth_deny(Cfg) ->
send(Sock, ConnBin), send(Sock, ConnBin),
{ok, ConnAckBin} = recv(Sock, 5000), {ok, ConnAckBin} = recv(Sock, 5000),
SockType =/= udp andalso begin SockType =/= udp andalso
{error, closed} = recv(Sock, 5000) begin
end, {error, closed} = recv(Sock, 5000)
end,
meck:unload([emqx_gateway_ctx]). meck:unload([emqx_gateway_ctx]).
t_acl_deny(Cfg) -> t_acl_deny(Cfg) ->
SockType = proplists:get_value(listener_type, Cfg), SockType = proplists:get_value(listener_type, Cfg),
Sock = open(SockType), Sock = open(SockType),
Client = #{proto_name => <<"demo">>, Client = #{
proto_ver => <<"v0.1">>, proto_name => <<"demo">>,
clientid => <<"test_client_1">> proto_ver => <<"v0.1">>,
}, clientid => <<"test_client_1">>
},
Password = <<"123456">>, Password = <<"123456">>,
ok = meck:new(emqx_gateway_ctx, [passthrough, no_history, no_link]), 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), SockType = proplists:get_value(listener_type, Cfg),
Sock = open(SockType), Sock = open(SockType),
Client = #{proto_name => <<"demo">>, Client = #{
proto_ver => <<"v0.1">>, proto_name => <<"demo">>,
clientid => <<"test_client_1">>, proto_ver => <<"v0.1">>,
keepalive => 2 clientid => <<"test_client_1">>,
}, keepalive => 2
},
Password = <<"123456">>, Password = <<"123456">>,
ConnBin = frame_connect(Client, Password), ConnBin = frame_connect(Client, Password),
@ -213,18 +226,21 @@ t_keepalive_timeout(Cfg) ->
DisconnectBin = frame_disconnect(), DisconnectBin = frame_disconnect(),
{ok, DisconnectBin} = recv(Sock, 10000), {ok, DisconnectBin} = recv(Sock, 10000),
SockType =/= udp andalso begin SockType =/= udp andalso
{error, closed} = recv(Sock, 5000) begin
end, ok. {error, closed} = recv(Sock, 5000)
end,
ok.
t_hook_connected_disconnected(Cfg) -> t_hook_connected_disconnected(Cfg) ->
SockType = proplists:get_value(listener_type, Cfg), SockType = proplists:get_value(listener_type, Cfg),
Sock = open(SockType), Sock = open(SockType),
Client = #{proto_name => <<"demo">>, Client = #{
proto_ver => <<"v0.1">>, proto_name => <<"demo">>,
clientid => <<"test_client_1">> proto_ver => <<"v0.1">>,
}, clientid => <<"test_client_1">>
},
Password = <<"123456">>, Password = <<"123456">>,
ConnBin = frame_connect(Client, Password), ConnBin = frame_connect(Client, Password),
@ -232,7 +248,7 @@ t_hook_connected_disconnected(Cfg) ->
Parent = self(), Parent = self(),
emqx:hook('client.connected', {?MODULE, hook_fun1, [Parent]}), 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), send(Sock, ConnBin),
{ok, ConnAckBin} = recv(Sock, 5000), {ok, ConnAckBin} = recv(Sock, 5000),
@ -252,9 +268,10 @@ t_hook_connected_disconnected(Cfg) ->
error(hook_is_not_running) error(hook_is_not_running)
end, end,
SockType =/= udp andalso begin SockType =/= udp andalso
{error, closed} = recv(Sock, 5000) begin
end, {error, closed} = recv(Sock, 5000)
end,
emqx:unhook('client.connected', {?MODULE, hook_fun1}), emqx:unhook('client.connected', {?MODULE, hook_fun1}),
emqx:unhook('client.disconnected', {?MODULE, hook_fun2}). emqx:unhook('client.disconnected', {?MODULE, hook_fun2}).
@ -262,10 +279,11 @@ t_hook_session_subscribed_unsubscribed(Cfg) ->
SockType = proplists:get_value(listener_type, Cfg), SockType = proplists:get_value(listener_type, Cfg),
Sock = open(SockType), Sock = open(SockType),
Client = #{proto_name => <<"demo">>, Client = #{
proto_ver => <<"v0.1">>, proto_name => <<"demo">>,
clientid => <<"test_client_1">> proto_ver => <<"v0.1">>,
}, clientid => <<"test_client_1">>
},
Password = <<"123456">>, Password = <<"123456">>,
ConnBin = frame_connect(Client, Password), ConnBin = frame_connect(Client, Password),
@ -310,10 +328,11 @@ t_hook_message_delivered(Cfg) ->
SockType = proplists:get_value(listener_type, Cfg), SockType = proplists:get_value(listener_type, Cfg),
Sock = open(SockType), Sock = open(SockType),
Client = #{proto_name => <<"demo">>, Client = #{
proto_ver => <<"v0.1">>, proto_name => <<"demo">>,
clientid => <<"test_client_1">> proto_ver => <<"v0.1">>,
}, clientid => <<"test_client_1">>
},
Password = <<"123456">>, Password = <<"123456">>,
ConnBin = frame_connect(Client, Password), ConnBin = frame_connect(Client, Password),
@ -340,11 +359,19 @@ t_hook_message_delivered(Cfg) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Utils %% Utils
hook_fun1(_, _, Parent) -> Parent ! connected, ok. hook_fun1(_, _, Parent) ->
hook_fun2(_, _, _, Parent) -> Parent ! disconnected, ok. Parent ! connected,
ok.
hook_fun2(_, _, _, Parent) ->
Parent ! disconnected,
ok.
hook_fun3(_, _, _, Parent) -> Parent ! subscribed, ok. hook_fun3(_, _, _, Parent) ->
hook_fun4(_, _, _, Parent) -> Parent ! unsubscribed, ok. Parent ! subscribed,
ok.
hook_fun4(_, _, _, Parent) ->
Parent ! unsubscribed,
ok.
hook_fun5(_, Msg) -> {ok, Msg#message{payload = <<"2">>}}. hook_fun5(_, Msg) -> {ok, Msg#message{payload = <<"2">>}}.
@ -403,41 +430,51 @@ close({dtls, Sock}) ->
socketopts(tcp) -> socketopts(tcp) ->
#{tcp => tcp_opts()}; #{tcp => tcp_opts()};
socketopts(ssl) -> socketopts(ssl) ->
#{tcp => tcp_opts(), #{
ssl => ssl_opts()}; tcp => tcp_opts(),
ssl => ssl_opts()
};
socketopts(udp) -> socketopts(udp) ->
#{udp => udp_opts()}; #{udp => udp_opts()};
socketopts(dtls) -> socketopts(dtls) ->
#{udp => udp_opts(), #{
dtls => dtls_opts()}. udp => udp_opts(),
dtls => dtls_opts()
}.
tcp_opts() -> tcp_opts() ->
maps:merge( maps:merge(
udp_opts(), udp_opts(),
#{send_timeout => 15000, #{
send_timeout_close => true, send_timeout => 15000,
backlog => 100, send_timeout_close => true,
nodelay => true} backlog => 100,
nodelay => true
}
). ).
udp_opts() -> udp_opts() ->
#{recbuf => 1024, #{
sndbuf => 1024, recbuf => 1024,
buffer => 1024, sndbuf => 1024,
reuseaddr => true}. buffer => 1024,
reuseaddr => true
}.
ssl_opts() -> ssl_opts() ->
Certs = certs("key.pem", "cert.pem", "cacert.pem"), Certs = certs("key.pem", "cert.pem", "cacert.pem"),
maps:merge( maps:merge(
Certs, Certs,
#{versions => emqx_tls_lib:default_versions(), #{
ciphers => emqx_tls_lib:default_ciphers(), versions => emqx_tls_lib:default_versions(),
verify => verify_peer, ciphers => emqx_tls_lib:default_ciphers(),
fail_if_no_peer_cert => true, verify => verify_peer,
secure_renegotiate => false, fail_if_no_peer_cert => true,
reuse_sessions => true, secure_renegotiate => false,
honor_cipher_order => true} reuse_sessions => true,
). honor_cipher_order => true
}
).
dtls_opts() -> dtls_opts() ->
maps:merge(ssl_opts(), #{versions => ['dtlsv1.2', 'dtlsv1']}). maps:merge(ssl_opts(), #{versions => ['dtlsv1.2', 'dtlsv1']}).
@ -450,7 +487,8 @@ client_ssl_opts() ->
certs(Key, Cert, CACert) -> certs(Key, Cert, CACert) ->
CertsPath = emqx_common_test_helpers:deps_path(emqx, "etc/certs"), CertsPath = emqx_common_test_helpers:deps_path(emqx, "etc/certs"),
#{keyfile => filename:join([ CertsPath, Key ]), #{
certfile => filename:join([ CertsPath, Cert ]), keyfile => filename:join([CertsPath, Key]),
cacertfile => filename:join([ CertsPath, CACert])}. certfile => filename:join([CertsPath, Cert]),
cacertfile => filename:join([CertsPath, CACert])
}.

View File

@ -18,76 +18,87 @@
-behaviour(emqx_exproto_v_1_connection_handler_bhvr). -behaviour(emqx_exproto_v_1_connection_handler_bhvr).
-export([ start/0 -export([
, stop/1 start/0,
]). stop/1
]).
-export([ frame_connect/2 -export([
, frame_connack/1 frame_connect/2,
, frame_publish/3 frame_connack/1,
, frame_puback/1 frame_publish/3,
, frame_subscribe/2 frame_puback/1,
, frame_suback/1 frame_subscribe/2,
, frame_unsubscribe/1 frame_suback/1,
, frame_unsuback/1 frame_unsubscribe/1,
, frame_disconnect/0 frame_unsuback/1,
]). frame_disconnect/0
]).
-export([ on_socket_created/2 -export([
, on_received_bytes/2 on_socket_created/2,
, on_socket_closed/2 on_received_bytes/2,
, on_timer_timeout/2 on_socket_closed/2,
, on_received_messages/2 on_timer_timeout/2,
]). on_received_messages/2
]).
-define(LOG(Fmt, Args), ct:pal(Fmt, Args)). -define(LOG(Fmt, Args), ct:pal(Fmt, Args)).
-define(HTTP, #{grpc_opts => #{service_protos => [emqx_exproto_pb], -define(HTTP, #{
services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}}, grpc_opts => #{
listen_opts => #{port => 9001, service_protos => [emqx_exproto_pb],
socket_options => []}, services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}
pool_opts => #{size => 8}, },
transport_opts => #{ssl => false}}). listen_opts => #{
port => 9001,
socket_options => []
},
pool_opts => #{size => 8},
transport_opts => #{ssl => false}
}).
-define(CLIENT, emqx_exproto_v_1_connection_adapter_client). -define(CLIENT, emqx_exproto_v_1_connection_adapter_client).
-define(send(Req), ?CLIENT:send(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(close(Req), ?CLIENT:close(Req, #{channel => ct_test_channel})).
-define(authenticate(Req), ?CLIENT:authenticate(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(start_timer(Req), ?CLIENT:start_timer(Req, #{channel => ct_test_channel})).
-define(publish(Req), ?CLIENT:publish(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(subscribe(Req), ?CLIENT:subscribe(Req, #{channel => ct_test_channel})).
-define(unsubscribe(Req), ?CLIENT:unsubscribe(Req, #{channel => ct_test_channel})). -define(unsubscribe(Req), ?CLIENT:unsubscribe(Req, #{channel => ct_test_channel})).
-define(TYPE_CONNECT, 1). -define(TYPE_CONNECT, 1).
-define(TYPE_CONNACK, 2). -define(TYPE_CONNACK, 2).
-define(TYPE_PUBLISH, 3). -define(TYPE_PUBLISH, 3).
-define(TYPE_PUBACK, 4). -define(TYPE_PUBACK, 4).
-define(TYPE_SUBSCRIBE, 5). -define(TYPE_SUBSCRIBE, 5).
-define(TYPE_SUBACK, 6). -define(TYPE_SUBACK, 6).
-define(TYPE_UNSUBSCRIBE, 7). -define(TYPE_UNSUBSCRIBE, 7).
-define(TYPE_UNSUBACK, 8). -define(TYPE_UNSUBACK, 8).
-define(TYPE_DISCONNECT, 9). -define(TYPE_DISCONNECT, 9).
-define(loop_recv_and_reply_empty_success(Stream), -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), -define(loop_recv_and_reply_empty_success(Stream, Fun), begin
begin LoopRecv = fun _Lp(_St) ->
LoopRecv = fun _Lp(_St) -> case grpc_stream:recv(_St) of
case grpc_stream:recv(_St) of {more, _Reqs, _NSt} ->
{more, _Reqs, _NSt} -> ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]),
?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]), Fun(_Reqs),
Fun(_Reqs), _Lp(_NSt); _Lp(_NSt);
{eos, _Reqs, _NSt} -> {eos, _Reqs, _NSt} ->
?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]), ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]),
Fun(_Reqs), _NSt Fun(_Reqs),
end _NSt
end, end
NStream = LoopRecv(Stream), end,
grpc_stream:reply(NStream, #{}), NStream = LoopRecv(Stream),
{ok, NStream} grpc_stream:reply(NStream, #{}),
end). {ok, NStream}
end).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% APIs %% APIs
@ -101,9 +112,10 @@ start_channel() ->
grpc_client_sup:create_channel_pool(ct_test_channel, "http://127.0.0.1:9100", #{}). grpc_client_sup:create_channel_pool(ct_test_channel, "http://127.0.0.1:9100", #{}).
start_server() -> start_server() ->
Services = #{protos => [emqx_exproto_pb], Services = #{
services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE} protos => [emqx_exproto_pb],
}, services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}
},
Options = [], Options = [],
grpc:start_server(?MODULE, 9001, Services, Options). grpc:start_server(?MODULE, 9001, Services, Options).
@ -115,53 +127,68 @@ stop([_ChannPid, _SvrPid]) ->
%% Protocol Adapter callbacks %% Protocol Adapter callbacks
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-spec on_socket_created(grpc_stream:stream(), grpc:metadata()) -spec on_socket_created(grpc_stream:stream(), grpc:metadata()) ->
-> {ok, grpc_stream:stream()}. {ok, grpc_stream:stream()}.
on_socket_created(Stream, _Md) -> on_socket_created(Stream, _Md) ->
?loop_recv_and_reply_empty_success(Stream). ?loop_recv_and_reply_empty_success(Stream).
-spec on_socket_closed(grpc_stream:stream(), grpc:metadata()) -spec on_socket_closed(grpc_stream:stream(), grpc:metadata()) ->
-> {ok, grpc_stream:stream()}. {ok, grpc_stream:stream()}.
on_socket_closed(Stream, _Md) -> on_socket_closed(Stream, _Md) ->
?loop_recv_and_reply_empty_success(Stream). ?loop_recv_and_reply_empty_success(Stream).
-spec on_received_bytes(grpc_stream:stream(), grpc:metadata()) -spec on_received_bytes(grpc_stream:stream(), grpc:metadata()) ->
-> {ok, grpc_stream:stream()}. {ok, grpc_stream:stream()}.
on_received_bytes(Stream, _Md) -> on_received_bytes(Stream, _Md) ->
?loop_recv_and_reply_empty_success(Stream, ?loop_recv_and_reply_empty_success(
fun(Reqs) -> Stream,
lists:foreach( fun(Reqs) ->
fun(#{conn := Conn, bytes := Bytes}) -> lists:foreach(
#{<<"type">> := Type} = Params = emqx_json:decode(Bytes, [return_maps]), fun(#{conn := Conn, bytes := Bytes}) ->
_ = handle_in(Conn, Type, Params) #{<<"type">> := Type} = Params = emqx_json:decode(Bytes, [return_maps]),
end, Reqs) _ = handle_in(Conn, Type, Params)
end). end,
Reqs
)
end
).
-spec on_timer_timeout(grpc_stream:stream(), grpc:metadata()) -spec on_timer_timeout(grpc_stream:stream(), grpc:metadata()) ->
-> {ok, grpc_stream:stream()}. {ok, grpc_stream:stream()}.
on_timer_timeout(Stream, _Md) -> on_timer_timeout(Stream, _Md) ->
?loop_recv_and_reply_empty_success(Stream, ?loop_recv_and_reply_empty_success(
fun(Reqs) -> Stream,
lists:foreach( fun(Reqs) ->
fun(#{conn := Conn, type := 'KEEPALIVE'}) -> lists:foreach(
?LOG("Close this connection ~p due to keepalive timeout", [Conn]), fun(#{conn := Conn, type := 'KEEPALIVE'}) ->
handle_out(Conn, ?TYPE_DISCONNECT), ?LOG("Close this connection ~p due to keepalive timeout", [Conn]),
?close(#{conn => Conn}) handle_out(Conn, ?TYPE_DISCONNECT),
end, Reqs) ?close(#{conn => Conn})
end). end,
Reqs
)
end
).
-spec on_received_messages(grpc_stream:stream(), grpc:metadata()) -spec on_received_messages(grpc_stream:stream(), grpc:metadata()) ->
-> {ok, grpc_stream:stream()}. {ok, grpc_stream:stream()}.
on_received_messages(Stream, _Md) -> on_received_messages(Stream, _Md) ->
?loop_recv_and_reply_empty_success(Stream, ?loop_recv_and_reply_empty_success(
fun(Reqs) -> Stream,
lists:foreach( fun(Reqs) ->
fun(#{conn := Conn, messages := Messages}) -> lists:foreach(
lists:foreach(fun(Message) -> fun(#{conn := Conn, messages := Messages}) ->
handle_out(Conn, ?TYPE_PUBLISH, Message) lists:foreach(
end, Messages) fun(Message) ->
end, Reqs) handle_out(Conn, ?TYPE_PUBLISH, Message)
end). end,
Messages
)
end,
Reqs
)
end
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% The Protocol Example: %% The Protocol Example:
@ -189,12 +216,16 @@ on_received_messages(Stream, _Md) ->
handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">> := Password}) -> handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">> := Password}) ->
NClientInfo = maps:from_list( 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 case ?authenticate(#{conn => Conn, clientinfo => NClientInfo, password => Password}) of
{ok, #{code := 'SUCCESS'}, _} -> {ok, #{code := 'SUCCESS'}, _} ->
case maps:get(keepalive, NClientInfo, 0) of case maps:get(keepalive, NClientInfo, 0) of
0 -> ok; 0 ->
ok;
Intv -> Intv ->
?LOG("Try call start_timer with ~ps", [Intv]), ?LOG("Try call start_timer with ~ps", [Intv]),
?start_timer(#{conn => Conn, type => 'KEEPALIVE', interval => 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), handle_out(Conn, ?TYPE_CONNACK, 1),
?close(#{conn => Conn}) ?close(#{conn => Conn})
end; end;
handle_in(Conn, ?TYPE_PUBLISH, #{<<"topic">> := Topic, handle_in(Conn, ?TYPE_PUBLISH, #{
<<"qos">> := Qos, <<"topic">> := Topic,
<<"payload">> := Payload}) -> <<"qos">> := Qos,
<<"payload">> := Payload
}) ->
case ?publish(#{conn => Conn, topic => Topic, qos => Qos, payload => Payload}) of case ?publish(#{conn => Conn, topic => Topic, qos => Qos, payload => Payload}) of
{ok, #{code := 'SUCCESS'}, _} -> {ok, #{code := 'SUCCESS'}, _} ->
handle_out(Conn, ?TYPE_PUBACK, 0); handle_out(Conn, ?TYPE_PUBACK, 0);
@ -227,7 +260,6 @@ handle_in(Conn, ?TYPE_UNSUBSCRIBE, #{<<"topic">> := Topic}) ->
_ -> _ ->
handle_out(Conn, ?TYPE_UNSUBACK, 1) handle_out(Conn, ?TYPE_UNSUBACK, 1)
end; end;
handle_in(Conn, ?TYPE_DISCONNECT, _) -> handle_in(Conn, ?TYPE_DISCONNECT, _) ->
?close(#{conn => Conn}). ?close(#{conn => Conn}).
@ -249,17 +281,21 @@ handle_out(Conn, ?TYPE_DISCONNECT) ->
%% Frame %% Frame
frame_connect(ClientInfo, Password) -> frame_connect(ClientInfo, Password) ->
emqx_json:encode(#{type => ?TYPE_CONNECT, emqx_json:encode(#{
clientinfo => ClientInfo, type => ?TYPE_CONNECT,
password => Password}). clientinfo => ClientInfo,
password => Password
}).
frame_connack(Code) -> frame_connack(Code) ->
emqx_json:encode(#{type => ?TYPE_CONNACK, code => Code}). emqx_json:encode(#{type => ?TYPE_CONNACK, code => Code}).
frame_publish(Topic, Qos, Payload) -> frame_publish(Topic, Qos, Payload) ->
emqx_json:encode(#{type => ?TYPE_PUBLISH, emqx_json:encode(#{
topic => Topic, type => ?TYPE_PUBLISH,
qos => Qos, topic => Topic,
payload => Payload}). qos => Qos,
payload => Payload
}).
frame_puback(Code) -> frame_puback(Code) ->
emqx_json:encode(#{type => ?TYPE_PUBACK, code => Code}). emqx_json:encode(#{type => ?TYPE_PUBACK, code => Code}).

View File

@ -44,25 +44,30 @@ end_per_suite(_Conf) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
t_registered_gateway(_) -> t_registered_gateway(_) ->
[{coap, #{cbkmod := emqx_coap_impl}}, [
{exproto, #{cbkmod := emqx_exproto_impl}}, {coap, #{cbkmod := emqx_coap_impl}},
{lwm2m, #{cbkmod := emqx_lwm2m_impl}}, {exproto, #{cbkmod := emqx_exproto_impl}},
{mqttsn, #{cbkmod := emqx_sn_impl}}, {lwm2m, #{cbkmod := emqx_lwm2m_impl}},
{stomp, #{cbkmod := emqx_stomp_impl}}] = emqx_gateway:registered_gateway(). {mqttsn, #{cbkmod := emqx_sn_impl}},
{stomp, #{cbkmod := emqx_stomp_impl}}
] = emqx_gateway:registered_gateway().
t_load_unload_list_lookup(_) -> t_load_unload_list_lookup(_) ->
{ok, _} = emqx_gateway:load(?GWNAME, #{idle_timeout => 1000}), {ok, _} = emqx_gateway:load(?GWNAME, #{idle_timeout => 1000}),
?assertEqual( ?assertEqual(
{error, alredy_existed}, {error, alredy_existed},
emqx_gateway:load(?GWNAME, #{})), emqx_gateway:load(?GWNAME, #{})
),
?assertEqual( ?assertEqual(
{error, {unknown_gateway_name, bad_gw_name}}, {error, {unknown_gateway_name, bad_gw_name}},
emqx_gateway:load(bad_gw_name, #{})), emqx_gateway:load(bad_gw_name, #{})
),
?assertEqual(1, length(emqx_gateway:list())), ?assertEqual(1, length(emqx_gateway:list())),
?assertEqual( ?assertEqual(
emqx_gateway:lookup(?GWNAME), emqx_gateway:lookup(?GWNAME),
lists:nth(1, emqx_gateway:list())), lists:nth(1, emqx_gateway:list())
),
?assertEqual(ok, emqx_gateway:unload(?GWNAME)), ?assertEqual(ok, emqx_gateway:unload(?GWNAME)),
?assertEqual({error, not_found}, 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), #{status := stopped} = emqx_gateway:lookup(?GWNAME),
ok = emqx_gateway:update( ok = emqx_gateway:update(
?GWNAME, #{enable => false, idle_timeout => 2000}), ?GWNAME, #{enable => false, idle_timeout => 2000}
#{status := stopped, ),
config := #{idle_timeout := 2000}} = emqx_gateway:lookup(?GWNAME), #{
status := stopped,
config := #{idle_timeout := 2000}
} = emqx_gateway:lookup(?GWNAME),
ok = emqx_gateway:update( ok = emqx_gateway:update(
?GWNAME, #{enable => true, idle_timeout => 3000}), ?GWNAME, #{enable => true, idle_timeout => 3000}
#{status := running, ),
config := #{idle_timeout := 3000}} = emqx_gateway:lookup(?GWNAME), #{
status := running,
config := #{idle_timeout := 3000}
} = emqx_gateway:lookup(?GWNAME),
ok = emqx_gateway:update( ok = emqx_gateway:update(
?GWNAME, #{enable => false, idle_timeout => 4000}), ?GWNAME, #{enable => false, idle_timeout => 4000}
#{status := stopped, ),
config := #{idle_timeout := 4000}} = emqx_gateway:lookup(?GWNAME), #{
status := stopped,
config := #{idle_timeout := 4000}
} = emqx_gateway:lookup(?GWNAME),
ok = emqx_gateway:start(?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), {error, already_started} = emqx_gateway:start(?GWNAME),
ok. ok.

View File

@ -19,12 +19,15 @@
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
-import(emqx_gateway_test_utils, -import(
[ assert_confs/2 emqx_gateway_test_utils,
, assert_feilds_apperence/2 [
, request/2 assert_confs/2,
, request/3 assert_feilds_apperence/2,
]). request/2,
request/3
]
).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -53,14 +56,16 @@ end_per_suite(Conf) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
t_gateway(_) -> t_gateway(_) ->
{200, Gateways}= request(get, "/gateway"), {200, Gateways} = request(get, "/gateway"),
lists:foreach(fun assert_gw_unloaded/1, Gateways), lists:foreach(fun assert_gw_unloaded/1, Gateways),
{400, BadReq} = request(get, "/gateway/uname_gateway"), {400, BadReq} = request(get, "/gateway/uname_gateway"),
assert_bad_request(BadReq), assert_bad_request(BadReq),
{201, _} = request(post, "/gateway", #{name => <<"stomp">>}), {201, _} = request(post, "/gateway", #{name => <<"stomp">>}),
{200, StompGw1} = request(get, "/gateway/stomp"), {200, StompGw1} = request(get, "/gateway/stomp"),
assert_feilds_apperence([name, status, enable, created_at, started_at], assert_feilds_apperence(
StompGw1), [name, status, enable, created_at, started_at],
StompGw1
),
{204, _} = request(delete, "/gateway/stomp"), {204, _} = request(delete, "/gateway/stomp"),
{200, StompGw2} = request(get, "/gateway/stomp"), {200, StompGw2} = request(get, "/gateway/stomp"),
assert_gw_unloaded(StompGw2), assert_gw_unloaded(StompGw2),
@ -70,15 +75,17 @@ t_gateway_stomp(_) ->
{200, Gw} = request(get, "/gateway/stomp"), {200, Gw} = request(get, "/gateway/stomp"),
assert_gw_unloaded(Gw), assert_gw_unloaded(Gw),
%% post %% post
GwConf = #{name => <<"stomp">>, GwConf = #{
frame => #{max_headers => 5, name => <<"stomp">>,
max_headers_length => 100, frame => #{
max_body_length => 100 max_headers => 5,
}, max_headers_length => 100,
listeners => [ max_body_length => 100
#{name => <<"def">>, type => <<"tcp">>, bind => <<"61613">>} },
] listeners => [
}, #{name => <<"def">>, type => <<"tcp">>, bind => <<"61613">>}
]
},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/stomp"), {200, ConfResp} = request(get, "/gateway/stomp"),
assert_confs(GwConf, ConfResp), assert_confs(GwConf, ConfResp),
@ -93,15 +100,16 @@ t_gateway_mqttsn(_) ->
{200, Gw} = request(get, "/gateway/mqttsn"), {200, Gw} = request(get, "/gateway/mqttsn"),
assert_gw_unloaded(Gw), assert_gw_unloaded(Gw),
%% post %% post
GwConf = #{name => <<"mqttsn">>, GwConf = #{
gateway_id => 1, name => <<"mqttsn">>,
broadcast => true, gateway_id => 1,
predefined => [#{id => 1, topic => <<"t/a">>}], broadcast => true,
enable_qos3 => true, predefined => [#{id => 1, topic => <<"t/a">>}],
listeners => [ enable_qos3 => true,
#{name => <<"def">>, type => <<"udp">>, bind => <<"1884">>} listeners => [
] #{name => <<"def">>, type => <<"udp">>, bind => <<"1884">>}
}, ]
},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/mqttsn"), {200, ConfResp} = request(get, "/gateway/mqttsn"),
assert_confs(GwConf, ConfResp), assert_confs(GwConf, ConfResp),
@ -116,13 +124,14 @@ t_gateway_coap(_) ->
{200, Gw} = request(get, "/gateway/coap"), {200, Gw} = request(get, "/gateway/coap"),
assert_gw_unloaded(Gw), assert_gw_unloaded(Gw),
%% post %% post
GwConf = #{name => <<"coap">>, GwConf = #{
heartbeat => <<"60s">>, name => <<"coap">>,
connection_required => true, heartbeat => <<"60s">>,
listeners => [ connection_required => true,
#{name => <<"def">>, type => <<"udp">>, bind => <<"5683">>} listeners => [
] #{name => <<"def">>, type => <<"udp">>, bind => <<"5683">>}
}, ]
},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/coap"), {200, ConfResp} = request(get, "/gateway/coap"),
assert_confs(GwConf, ConfResp), assert_confs(GwConf, ConfResp),
@ -137,23 +146,24 @@ t_gateway_lwm2m(_) ->
{200, Gw} = request(get, "/gateway/lwm2m"), {200, Gw} = request(get, "/gateway/lwm2m"),
assert_gw_unloaded(Gw), assert_gw_unloaded(Gw),
%% post %% post
GwConf = #{name => <<"lwm2m">>, GwConf = #{
xml_dir => <<"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml">>, name => <<"lwm2m">>,
lifetime_min => <<"1s">>, xml_dir => <<"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml">>,
lifetime_max => <<"1000s">>, lifetime_min => <<"1s">>,
qmode_time_window => <<"30s">>, lifetime_max => <<"1000s">>,
auto_observe => true, qmode_time_window => <<"30s">>,
translators => #{ auto_observe => true,
command => #{ topic => <<"dn/#">>}, translators => #{
response => #{ topic => <<"up/resp">>}, command => #{topic => <<"dn/#">>},
notify => #{ topic => <<"up/resp">>}, response => #{topic => <<"up/resp">>},
register => #{ topic => <<"up/resp">>}, notify => #{topic => <<"up/resp">>},
update => #{ topic => <<"up/resp">>} register => #{topic => <<"up/resp">>},
}, update => #{topic => <<"up/resp">>}
listeners => [ },
#{name => <<"def">>, type => <<"udp">>, bind => <<"5783">>} listeners => [
] #{name => <<"def">>, type => <<"udp">>, bind => <<"5783">>}
}, ]
},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/lwm2m"), {200, ConfResp} = request(get, "/gateway/lwm2m"),
assert_confs(GwConf, ConfResp), assert_confs(GwConf, ConfResp),
@ -168,13 +178,14 @@ t_gateway_exproto(_) ->
{200, Gw} = request(get, "/gateway/exproto"), {200, Gw} = request(get, "/gateway/exproto"),
assert_gw_unloaded(Gw), assert_gw_unloaded(Gw),
%% post %% post
GwConf = #{name => <<"exproto">>, GwConf = #{
server => #{bind => <<"9100">>}, name => <<"exproto">>,
handler => #{address => <<"http://127.0.0.1:9001">>}, server => #{bind => <<"9100">>},
listeners => [ handler => #{address => <<"http://127.0.0.1:9001">>},
#{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>} listeners => [
] #{name => <<"def">>, type => <<"tcp">>, bind => <<"7993">>}
}, ]
},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/exproto"), {200, ConfResp} = request(get, "/gateway/exproto"),
assert_confs(GwConf, ConfResp), assert_confs(GwConf, ConfResp),
@ -190,10 +201,11 @@ t_authn(_) ->
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{204, _} = request(get, "/gateway/stomp/authentication"), {204, _} = request(get, "/gateway/stomp/authentication"),
AuthConf = #{mechanism => <<"password_based">>, AuthConf = #{
backend => <<"built_in_database">>, mechanism => <<"password_based">>,
user_id_type => <<"clientid">> backend => <<"built_in_database">>,
}, user_id_type => <<"clientid">>
},
{201, _} = request(post, "/gateway/stomp/authentication", AuthConf), {201, _} = request(post, "/gateway/stomp/authentication", AuthConf),
{200, ConfResp} = request(get, "/gateway/stomp/authentication"), {200, ConfResp} = request(get, "/gateway/stomp/authentication"),
assert_confs(AuthConf, ConfResp), assert_confs(AuthConf, ConfResp),
@ -213,40 +225,52 @@ t_authn_data_mgmt(_) ->
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{204, _} = request(get, "/gateway/stomp/authentication"), {204, _} = request(get, "/gateway/stomp/authentication"),
AuthConf = #{mechanism => <<"password_based">>, AuthConf = #{
backend => <<"built_in_database">>, mechanism => <<"password_based">>,
user_id_type => <<"clientid">> backend => <<"built_in_database">>,
}, user_id_type => <<"clientid">>
},
{201, _} = request(post, "/gateway/stomp/authentication", AuthConf), {201, _} = request(post, "/gateway/stomp/authentication", AuthConf),
{200, ConfResp} = request(get, "/gateway/stomp/authentication"), {200, ConfResp} = request(get, "/gateway/stomp/authentication"),
assert_confs(AuthConf, ConfResp), assert_confs(AuthConf, ConfResp),
User1 = #{ user_id => <<"test">> User1 = #{
, password => <<"123456">> user_id => <<"test">>,
, is_superuser => false password => <<"123456">>,
}, is_superuser => false
},
{201, _} = request(post, "/gateway/stomp/authentication/users", User1), {201, _} = request(post, "/gateway/stomp/authentication/users", User1),
{200, #{data := [UserRespd1]}} = request(get, "/gateway/stomp/authentication/users"), {200, #{data := [UserRespd1]}} = request(get, "/gateway/stomp/authentication/users"),
assert_confs(UserRespd1, User1), assert_confs(UserRespd1, User1),
{200, UserRespd2} = request(get, {200, UserRespd2} = request(
"/gateway/stomp/authentication/users/test"), get,
"/gateway/stomp/authentication/users/test"
),
assert_confs(UserRespd2, User1), assert_confs(UserRespd2, User1),
{200, UserRespd3} = request(put, {200, UserRespd3} = request(
"/gateway/stomp/authentication/users/test", put,
#{password => <<"654321">>, "/gateway/stomp/authentication/users/test",
is_superuser => true}), #{
password => <<"654321">>,
is_superuser => true
}
),
assert_confs(UserRespd3, User1#{is_superuser => true}), assert_confs(UserRespd3, User1#{is_superuser => true}),
{200, UserRespd4} = request(get, {200, UserRespd4} = request(
"/gateway/stomp/authentication/users/test"), get,
"/gateway/stomp/authentication/users/test"
),
assert_confs(UserRespd4, User1#{is_superuser => true}), assert_confs(UserRespd4, User1#{is_superuser => true}),
{204, _} = request(delete, "/gateway/stomp/authentication/users/test"), {204, _} = request(delete, "/gateway/stomp/authentication/users/test"),
{200, #{data := []}} = request(get, {200, #{data := []}} = request(
"/gateway/stomp/authentication/users"), get,
"/gateway/stomp/authentication/users"
),
{204, _} = request(delete, "/gateway/stomp/authentication"), {204, _} = request(delete, "/gateway/stomp/authentication"),
{204, _} = request(get, "/gateway/stomp/authentication"), {204, _} = request(get, "/gateway/stomp/authentication"),
@ -256,10 +280,11 @@ t_listeners(_) ->
GwConf = #{name => <<"stomp">>}, GwConf = #{name => <<"stomp">>},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{404, _} = request(get, "/gateway/stomp/listeners"), {404, _} = request(get, "/gateway/stomp/listeners"),
LisConf = #{name => <<"def">>, LisConf = #{
type => <<"tcp">>, name => <<"def">>,
bind => <<"61613">> type => <<"tcp">>,
}, bind => <<"61613">>
},
{201, _} = request(post, "/gateway/stomp/listeners", LisConf), {201, _} = request(post, "/gateway/stomp/listeners", LisConf),
{200, ConfResp} = request(get, "/gateway/stomp/listeners"), {200, ConfResp} = request(get, "/gateway/stomp/listeners"),
assert_confs([LisConf], ConfResp), assert_confs([LisConf], ConfResp),
@ -268,10 +293,10 @@ t_listeners(_) ->
LisConf2 = maps:merge(LisConf, #{bind => <<"61614">>}), LisConf2 = maps:merge(LisConf, #{bind => <<"61614">>}),
{200, _} = request( {200, _} = request(
put, put,
"/gateway/stomp/listeners/stomp:tcp:def", "/gateway/stomp/listeners/stomp:tcp:def",
LisConf2 LisConf2
), ),
{200, ConfResp2} = request(get, "/gateway/stomp/listeners/stomp:tcp:def"), {200, ConfResp2} = request(get, "/gateway/stomp/listeners/stomp:tcp:def"),
assert_confs(LisConf2, ConfResp2), assert_confs(LisConf2, ConfResp2),
@ -281,20 +306,25 @@ t_listeners(_) ->
{204, _} = request(delete, "/gateway/stomp"). {204, _} = request(delete, "/gateway/stomp").
t_listeners_authn(_) -> t_listeners_authn(_) ->
GwConf = #{name => <<"stomp">>, GwConf = #{
listeners => [ name => <<"stomp">>,
#{name => <<"def">>, listeners => [
type => <<"tcp">>, #{
bind => <<"61613">> name => <<"def">>,
}]}, type => <<"tcp">>,
bind => <<"61613">>
}
]
},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/stomp"), {200, ConfResp} = request(get, "/gateway/stomp"),
assert_confs(GwConf, ConfResp), assert_confs(GwConf, ConfResp),
AuthConf = #{mechanism => <<"password_based">>, AuthConf = #{
backend => <<"built_in_database">>, mechanism => <<"password_based">>,
user_id_type => <<"clientid">> backend => <<"built_in_database">>,
}, user_id_type => <<"clientid">>
},
Path = "/gateway/stomp/listeners/stomp:tcp:def/authentication", Path = "/gateway/stomp/listeners/stomp:tcp:def/authentication",
{201, _} = request(post, Path, AuthConf), {201, _} = request(post, Path, AuthConf),
{200, ConfResp2} = request(get, Path), {200, ConfResp2} = request(get, Path),
@ -312,62 +342,75 @@ t_listeners_authn(_) ->
{204, _} = request(delete, "/gateway/stomp"). {204, _} = request(delete, "/gateway/stomp").
t_listeners_authn_data_mgmt(_) -> t_listeners_authn_data_mgmt(_) ->
GwConf = #{name => <<"stomp">>, GwConf = #{
listeners => [ name => <<"stomp">>,
#{name => <<"def">>, listeners => [
type => <<"tcp">>, #{
bind => <<"61613">> name => <<"def">>,
}]}, type => <<"tcp">>,
bind => <<"61613">>
}
]
},
{201, _} = request(post, "/gateway", GwConf), {201, _} = request(post, "/gateway", GwConf),
{200, ConfResp} = request(get, "/gateway/stomp"), {200, ConfResp} = request(get, "/gateway/stomp"),
assert_confs(GwConf, ConfResp), assert_confs(GwConf, ConfResp),
AuthConf = #{mechanism => <<"password_based">>, AuthConf = #{
backend => <<"built_in_database">>, mechanism => <<"password_based">>,
user_id_type => <<"clientid">> backend => <<"built_in_database">>,
}, user_id_type => <<"clientid">>
},
Path = "/gateway/stomp/listeners/stomp:tcp:def/authentication", Path = "/gateway/stomp/listeners/stomp:tcp:def/authentication",
{201, _} = request(post, Path, AuthConf), {201, _} = request(post, Path, AuthConf),
{200, ConfResp2} = request(get, Path), {200, ConfResp2} = request(get, Path),
assert_confs(AuthConf, ConfResp2), assert_confs(AuthConf, ConfResp2),
User1 = #{ user_id => <<"test">> User1 = #{
, password => <<"123456">> user_id => <<"test">>,
, is_superuser => false password => <<"123456">>,
}, is_superuser => false
{201, _} = request(post, },
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users", {201, _} = request(
User1), post,
"/gateway/stomp/listeners/stomp:tcp:def/authentication/users",
User1
),
{200, {200, #{data := [UserRespd1]}} = request(
#{data := [UserRespd1]} } = request( get,
get, Path ++ "/users"
Path ++ "/users"), ),
assert_confs(UserRespd1, User1), assert_confs(UserRespd1, User1),
{200, UserRespd2} = request( {200, UserRespd2} = request(
get, get,
Path ++ "/users/test"), Path ++ "/users/test"
),
assert_confs(UserRespd2, User1), assert_confs(UserRespd2, User1),
{200, UserRespd3} = request( {200, UserRespd3} = request(
put, put,
Path ++ "/users/test", Path ++ "/users/test",
#{password => <<"654321">>, is_superuser => true}), #{password => <<"654321">>, is_superuser => true}
),
assert_confs(UserRespd3, User1#{is_superuser => true}), assert_confs(UserRespd3, User1#{is_superuser => true}),
{200, UserRespd4} = request( {200, UserRespd4} = request(
get, get,
Path ++ "/users/test"), Path ++ "/users/test"
),
assert_confs(UserRespd4, User1#{is_superuser => true}), assert_confs(UserRespd4, User1#{is_superuser => true}),
{204, _} = request( {204, _} = request(
delete, delete,
Path ++ "/users/test"), Path ++ "/users/test"
),
{200, #{data := []}} = request( {200, #{data := []}} = request(
get, get,
Path ++ "/users"), Path ++ "/users"
),
{204, _} = request(delete, "/gateway/stomp"). {204, _} = request(delete, "/gateway/stomp").
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

View File

@ -21,30 +21,38 @@
-include_lib("eunit/include/eunit.hrl"). -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 %% this parses to #{}, will not cause config cleanup
%% so we will need call emqx_config:erase %% so we will need call emqx_config:erase
-define(CONF_DEFAULT, <<" -define(CONF_DEFAULT, <<
gateway {} "\n"
">>). "gateway {}\n"
>>).
%% The config with json format for mqtt-sn gateway %% The config with json format for mqtt-sn gateway
-define(CONF_MQTTSN, " -define(CONF_MQTTSN,
{\"idle_timeout\": \"30s\", "\n"
\"enable_stats\": true, "{\"idle_timeout\": \"30s\",\n"
\"mountpoint\": \"mqttsn/\", " \"enable_stats\": true,\n"
\"gateway_id\": 1, " \"mountpoint\": \"mqttsn/\",\n"
\"broadcast\": true, " \"gateway_id\": 1,\n"
\"enable_qos3\": true, " \"broadcast\": true,\n"
\"predefined\": [{\"id\": 1001, \"topic\": \"pred/a\"}], " \"enable_qos3\": true,\n"
\"listeners\": " \"predefined\": [{\"id\": 1001, \"topic\": \"pred/a\"}],\n"
[{\"type\": \"udp\", " \"listeners\":\n"
\"name\": \"ct\", " [{\"type\": \"udp\",\n"
\"bind\": \"1884\" " \"name\": \"ct\",\n"
}] " \"bind\": \"1884\"\n"
} " }]\n"
"). "}\n"
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Setup %% Setup
@ -65,16 +73,25 @@ end_per_suite(Conf) ->
init_per_testcase(_, Conf) -> init_per_testcase(_, Conf) ->
Self = self(), Self = self(),
ok = meck:new(emqx_ctl, [passthrough, no_history, no_link]), ok = meck:new(emqx_ctl, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_ctl, usage, ok = meck:expect(
fun(L) -> emqx_ctl:format_usage(L) end), emqx_ctl,
ok = meck:expect(emqx_ctl, print, usage,
fun(Fmt) -> fun(L) -> emqx_ctl:format_usage(L) end
Self ! {fmt, emqx_ctl:format(Fmt, [])} ),
end), ok = meck:expect(
ok = meck:expect(emqx_ctl, print, emqx_ctl,
fun(Fmt, Args) -> print,
Self ! {fmt, emqx_ctl:format(Fmt, Args)} fun(Fmt) ->
end), 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. Conf.
end_per_testcase(_, _) -> end_per_testcase(_, _) ->
@ -92,39 +109,44 @@ t_load_unload(_) ->
t_gateway_registry_usage(_) -> t_gateway_registry_usage(_) ->
?assertEqual( ?assertEqual(
["gateway-registry list # List all registered gateways\n"], ["gateway-registry list # List all registered gateways\n"],
emqx_gateway_cli:'gateway-registry'(usage)). emqx_gateway_cli:'gateway-registry'(usage)
).
t_gateway_registry_list(_) -> t_gateway_registry_list(_) ->
emqx_gateway_cli:'gateway-registry'(["list"]), emqx_gateway_cli:'gateway-registry'(["list"]),
?assertEqual( ?assertEqual(
"Registered Name: coap, Callback Module: emqx_coap_impl\n" "Registered Name: coap, Callback Module: emqx_coap_impl\n"
"Registered Name: exproto, Callback Module: emqx_exproto_impl\n" "Registered Name: exproto, Callback Module: emqx_exproto_impl\n"
"Registered Name: lwm2m, Callback Module: emqx_lwm2m_impl\n" "Registered Name: lwm2m, Callback Module: emqx_lwm2m_impl\n"
"Registered Name: mqttsn, Callback Module: emqx_sn_impl\n" "Registered Name: mqttsn, Callback Module: emqx_sn_impl\n"
"Registered Name: stomp, Callback Module: emqx_stomp_impl\n" "Registered Name: stomp, Callback Module: emqx_stomp_impl\n",
, acc_print()). acc_print()
).
t_gateway_usage(_) -> t_gateway_usage(_) ->
?assertEqual( ?assertEqual(
["gateway list # List all gateway\n", [
"gateway lookup <Name> # Lookup a gateway detailed information\n", "gateway list # List all gateway\n",
"gateway load <Name> <JsonConf> # Load a gateway with config\n", "gateway lookup <Name> # Lookup a gateway detailed information\n",
"gateway unload <Name> # Unload the gateway\n", "gateway load <Name> <JsonConf> # Load a gateway with config\n",
"gateway stop <Name> # Stop the gateway\n", "gateway unload <Name> # Unload the gateway\n",
"gateway start <Name> # Start the gateway\n"], "gateway stop <Name> # Stop the gateway\n",
emqx_gateway_cli:gateway(usage) "gateway start <Name> # Start the gateway\n"
). ],
emqx_gateway_cli:gateway(usage)
).
t_gateway_list(_) -> t_gateway_list(_) ->
emqx_gateway_cli:gateway(["list"]), emqx_gateway_cli:gateway(["list"]),
?assertEqual( ?assertEqual(
"Gateway(name=coap, status=unloaded)\n" "Gateway(name=coap, status=unloaded)\n"
"Gateway(name=exproto, status=unloaded)\n" "Gateway(name=exproto, status=unloaded)\n"
"Gateway(name=lwm2m, status=unloaded)\n" "Gateway(name=lwm2m, status=unloaded)\n"
"Gateway(name=mqttsn, status=unloaded)\n" "Gateway(name=mqttsn, status=unloaded)\n"
"Gateway(name=stomp, status=unloaded)\n" "Gateway(name=stomp, status=unloaded)\n",
, acc_print()), acc_print()
),
emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]), emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]),
?assertEqual("ok\n", acc_print()), ?assertEqual("ok\n", acc_print()),
@ -158,8 +180,9 @@ t_gateway_load_unload_lookup(_) ->
emqx_gateway_cli:gateway(["load", "mqttsn", "{}"]), emqx_gateway_cli:gateway(["load", "mqttsn", "{}"]),
?assertEqual( ?assertEqual(
"Error: The mqttsn gateway already loaded\n" "Error: The mqttsn gateway already loaded\n",
, acc_print()), acc_print()
),
emqx_gateway_cli:gateway(["load", "bad-gw-name", "{}"]), emqx_gateway_cli:gateway(["load", "bad-gw-name", "{}"]),
%% TODO: assert it. for example: %% TODO: assert it. for example:
@ -196,14 +219,16 @@ t_gateway_start_stop(_) ->
t_gateway_clients_usage(_) -> t_gateway_clients_usage(_) ->
?assertEqual( ?assertEqual(
["gateway-clients list <Name> " [
"gateway-clients list <Name> "
"# List all clients for a gateway\n", "# List all clients for a gateway\n",
"gateway-clients lookup <Name> <ClientId> " "gateway-clients lookup <Name> <ClientId> "
"# Lookup the Client Info for specified client\n", "# Lookup the Client Info for specified client\n",
"gateway-clients kick <Name> <ClientId> " "gateway-clients kick <Name> <ClientId> "
"# Kick out a client\n"], "# Kick out a client\n"
emqx_gateway_cli:'gateway-clients'(usage) ],
). emqx_gateway_cli:'gateway-clients'(usage)
).
t_gateway_clients(_) -> t_gateway_clients(_) ->
emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]), emqx_gateway_cli:gateway(["load", "mqttsn", ?CONF_MQTTSN]),
@ -258,10 +283,12 @@ t_gateway_clients_kick(_) ->
t_gateway_metrcis_usage(_) -> t_gateway_metrcis_usage(_) ->
?assertEqual( ?assertEqual(
[ "gateway-metrics <Name> " [
"# List all metrics for a gateway\n"], "gateway-metrics <Name> "
emqx_gateway_cli:'gateway-metrics'(usage) "# List all metrics for a gateway\n"
). ],
emqx_gateway_cli:'gateway-metrics'(usage)
).
t_gateway_metrcis(_) -> t_gateway_metrcis(_) ->
ok. ok.
@ -271,7 +298,7 @@ acc_print() ->
acc_print(Acc) -> acc_print(Acc) ->
receive receive
{fmt, S} -> acc_print([S|Acc]) {fmt, S} -> acc_print([S | Acc])
after 200 -> after 200 ->
Acc Acc
end. end.
@ -279,10 +306,13 @@ acc_print(Acc) ->
sn_client_connect(ClientId) -> sn_client_connect(ClientId) ->
{ok, Socket} = gen_udp:open(0, [binary]), {ok, Socket} = gen_udp:open(0, [binary]),
_ = emqx_sn_protocol_SUITE:send_connect_msg(Socket, ClientId), _ = emqx_sn_protocol_SUITE:send_connect_msg(Socket, ClientId),
?assertEqual(<<3, 16#05, 0>>, ?assertEqual(
emqx_sn_protocol_SUITE:receive_response(Socket)), <<3, 16#05, 0>>,
emqx_sn_protocol_SUITE:receive_response(Socket)
),
Socket. Socket.
sn_client_disconnect(Socket) -> sn_client_disconnect(Socket) ->
_ = emqx_sn_protocol_SUITE:send_disconnect_msg(Socket, undefined), _ = emqx_sn_protocol_SUITE:send_disconnect_msg(Socket, undefined),
gen_udp:close(Socket), ok. gen_udp:close(Socket),
ok.

View File

@ -61,40 +61,70 @@ end_per_testcase(_TestCase, Conf) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
t_open_session(_) -> t_open_session(_) ->
{ok, #{present := false, {ok, #{
session := #{}}} = emqx_gateway_cm:open_session( present := false,
?GWNAME, false, clientinfo(), conninfo(), session := #{}
fun(_, _) -> #{} end), }} = emqx_gateway_cm:open_session(
?GWNAME,
false,
clientinfo(),
conninfo(),
fun(_, _) -> #{} end
),
{ok, SessionRes} = emqx_gateway_cm:open_session( {ok, SessionRes} = emqx_gateway_cm:open_session(
?GWNAME, true, clientinfo(), conninfo(), ?GWNAME,
fun(_, _) -> #{no => 1} end), true,
?assertEqual(#{present => false, clientinfo(),
session => #{no => 1}}, SessionRes), conninfo(),
fun(_, _) -> #{no => 1} end
),
?assertEqual(
#{
present => false,
session => #{no => 1}
},
SessionRes
),
%% assert1. check channel infos in ets table %% assert1. check channel infos in ets table
Chann = {?CLIENTID, self()}, Chann = {?CLIENTID, self()},
?assertEqual( ?assertEqual(
[Chann], [Chann],
ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))), ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))
),
?assertEqual( ?assertEqual(
[{Chann, ?MODULE}], [{Chann, ?MODULE}],
ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))), ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))
),
%% assert2. discard the presented session %% assert2. discard the presented session
{ok, SessionRes2} = emqx_gateway_cm:open_session( {ok, SessionRes2} = emqx_gateway_cm:open_session(
?GWNAME, true, clientinfo(), conninfo(), ?GWNAME,
fun(_, _) -> #{no => 2} end), true,
?assertEqual(#{present => false, clientinfo(),
session => #{no => 2}}, SessionRes2), conninfo(),
fun(_, _) -> #{no => 2} end
),
?assertEqual(
#{
present => false,
session => #{no => 2}
},
SessionRes2
),
emqx_gateway_cm:insert_channel_info( emqx_gateway_cm:insert_channel_info(
?GWNAME, ?CLIENTID, ?GWNAME,
#{clientinfo => clientinfo(), conninfo => conninfo()}, []), ?CLIENTID,
#{clientinfo => clientinfo(), conninfo => conninfo()},
[]
),
?assertEqual( ?assertEqual(
1, 1,
ets:info(emqx_gateway_cm:tabname(info, ?GWNAME), size)), ets:info(emqx_gateway_cm:tabname(info, ?GWNAME), size)
),
receive receive
discard -> discard ->
@ -106,43 +136,62 @@ t_open_session(_) ->
%% assert3. no channel infos in ets table %% assert3. no channel infos in ets table
?assertEqual( ?assertEqual(
[], [],
ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))), ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))
),
?assertEqual( ?assertEqual(
[], [],
ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))), ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))
),
?assertEqual( ?assertEqual(
[], [],
ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))). ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))
).
t_get_set_chan_info_stats(_) -> t_get_set_chan_info_stats(_) ->
{ok, SessionRes} = emqx_gateway_cm:open_session( {ok, SessionRes} = emqx_gateway_cm:open_session(
?GWNAME, true, clientinfo(), conninfo(), ?GWNAME,
fun(_, _) -> #{no => 1} end), true,
?assertEqual(#{present => false, clientinfo(),
session => #{no => 1}}, SessionRes), conninfo(),
fun(_, _) -> #{no => 1} end
),
?assertEqual(
#{
present => false,
session => #{no => 1}
},
SessionRes
),
emqx_gateway_cm:insert_channel_info( emqx_gateway_cm:insert_channel_info(
?GWNAME, ?CLIENTID, ?GWNAME,
#{clientinfo => clientinfo(), conninfo => conninfo()}, []), ?CLIENTID,
#{clientinfo => clientinfo(), conninfo => conninfo()},
[]
),
%% Info: get/set %% Info: get/set
NInfo = #{newinfo => true, node => node()}, NInfo = #{newinfo => true, node => node()},
emqx_gateway_cm:set_chan_info(?GWNAME, ?CLIENTID, NInfo), emqx_gateway_cm:set_chan_info(?GWNAME, ?CLIENTID, NInfo),
?assertEqual( ?assertEqual(
NInfo, NInfo,
emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID)), emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID)
),
?assertEqual( ?assertEqual(
NInfo, NInfo,
emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID, self())), emqx_gateway_cm:get_chan_info(?GWNAME, ?CLIENTID, self())
),
%% Stats: get/set %% Stats: get/set
NStats = [{newstats, true}], NStats = [{newstats, true}],
emqx_gateway_cm:set_chan_stats(?GWNAME, ?CLIENTID, NStats), emqx_gateway_cm:set_chan_stats(?GWNAME, ?CLIENTID, NStats),
?assertEqual( ?assertEqual(
NStats, NStats,
emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID)), emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID)
),
?assertEqual( ?assertEqual(
NStats, NStats,
emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID, self())), emqx_gateway_cm:get_chan_stats(?GWNAME, ?CLIENTID, self())
),
emqx_gateway_cm:connection_closed(?GWNAME, ?CLIENTID), emqx_gateway_cm:connection_closed(?GWNAME, ?CLIENTID),
emqx_gateway_cm:unregister_channel(?GWNAME, ?CLIENTID). emqx_gateway_cm:unregister_channel(?GWNAME, ?CLIENTID).
@ -151,55 +200,83 @@ t_handle_process_down(Conf) ->
Pid = proplists:get_value(cm, Conf), Pid = proplists:get_value(cm, Conf),
{ok, SessionRes} = emqx_gateway_cm:open_session( {ok, SessionRes} = emqx_gateway_cm:open_session(
?GWNAME, true, clientinfo(), conninfo(), ?GWNAME,
fun(_, _) -> #{no => 1} end), true,
?assertEqual(#{present => false, clientinfo(),
session => #{no => 1}}, SessionRes), conninfo(),
fun(_, _) -> #{no => 1} end
),
?assertEqual(
#{
present => false,
session => #{no => 1}
},
SessionRes
),
emqx_gateway_cm:insert_channel_info( emqx_gateway_cm:insert_channel_info(
?GWNAME, ?CLIENTID, ?GWNAME,
#{clientinfo => clientinfo(), conninfo => conninfo()}, []), ?CLIENTID,
#{clientinfo => clientinfo(), conninfo => conninfo()},
[]
),
_ = Pid ! {'DOWN', mref, process, self(), normal}, _ = Pid ! {'DOWN', mref, process, self(), normal},
timer:sleep(200), %% wait the async clear task %% wait the async clear task
timer:sleep(200),
?assertEqual( ?assertEqual(
[], [],
ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))), ets:tab2list(emqx_gateway_cm:tabname(chan, ?GWNAME))
),
?assertEqual( ?assertEqual(
[], [],
ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))), ets:tab2list(emqx_gateway_cm:tabname(conn, ?GWNAME))
),
?assertEqual( ?assertEqual(
[], [],
ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))). ets:tab2list(emqx_gateway_cm:tabname(info, ?GWNAME))
).
t_kick_session(_) -> t_kick_session(_) ->
%% session1 %% session1
{ok, _} = emqx_gateway_cm:open_session( {ok, _} = emqx_gateway_cm:open_session(
?GWNAME, true, clientinfo(), conninfo(), ?GWNAME,
fun(_, _) -> #{no => 1} end), true,
clientinfo(),
conninfo(),
fun(_, _) -> #{no => 1} end
),
emqx_gateway_cm:insert_channel_info( emqx_gateway_cm:insert_channel_info(
?GWNAME, ?CLIENTID, ?GWNAME,
#{clientinfo => clientinfo(), conninfo => conninfo()}, []), ?CLIENTID,
#{clientinfo => clientinfo(), conninfo => conninfo()},
[]
),
%% meck `lookup_channels` %% meck `lookup_channels`
Self = self(), Self = self(),
ok = meck:new(emqx_gateway_cm_registry, ok = meck:new(
[passthrough, no_history, no_link]), emqx_gateway_cm_registry,
ok = meck:expect(emqx_gateway_cm_registry, lookup_channels, [passthrough, no_history, no_link]
fun(_, ?CLIENTID) -> [Self, Self] end), ),
ok = meck:expect(
emqx_gateway_cm_registry,
lookup_channels,
fun(_, ?CLIENTID) -> [Self, Self] end
),
ok = emqx_gateway_cm:kick_session(?GWNAME, ?CLIENTID), ok = emqx_gateway_cm:kick_session(?GWNAME, ?CLIENTID),
receive kick -> ok receive
kick -> ok
after 100 -> ?assert(false, "waiting discard msg timeout") after 100 -> ?assert(false, "waiting discard msg timeout")
end, end,
receive receive
kick -> kick ->
emqx_gateway_cm:connection_closed(?GWNAME, ?CLIENTID), emqx_gateway_cm:connection_closed(?GWNAME, ?CLIENTID),
emqx_gateway_cm:unregister_channel(?GWNAME, ?CLIENTID) emqx_gateway_cm:unregister_channel(?GWNAME, ?CLIENTID)
after after 100 ->
100 -> ?assert(false, "waiting kick msg timeout")
?assert(false, "waiting kick msg timeout")
end, end,
?assertMatch({error, not_found}, emqx_gateway_http:kickout_client(?GWNAME, <<"i-dont-exist">>)), ?assertMatch({error, not_found}, emqx_gateway_http:kickout_client(?GWNAME, <<"i-dont-exist">>)),
meck:unload(emqx_gateway_cm_registry). meck:unload(emqx_gateway_cm_registry).
@ -214,37 +291,41 @@ t_unexpected_handle(Conf) ->
%% helpers %% helpers
clientinfo() -> clientinfo() ->
#{ clientid => ?CLIENTID #{
, is_bridge => false clientid => ?CLIENTID,
, is_superuser => false is_bridge => false,
, listener => 'mqttsn:udp:default' is_superuser => false,
, mountpoint => <<"mqttsn/">> listener => 'mqttsn:udp:default',
, peerhost => {127, 0, 0, 1} mountpoint => <<"mqttsn/">>,
, protocol => 'mqtt-sn' peerhost => {127, 0, 0, 1},
, sockport => 1884 protocol => 'mqtt-sn',
, username => undefined sockport => 1884,
, zone => default username => undefined,
}. zone => default
}.
conninfo() -> conninfo() ->
#{ clean_start => true #{
, clientid => ?CLIENTID clean_start => true,
, conn_mod => ?MODULE clientid => ?CLIENTID,
, connected_at => 1641805544652 conn_mod => ?MODULE,
, expiry_interval => 0 connected_at => 1641805544652,
, keepalive => 10 expiry_interval => 0,
, peercert => nossl keepalive => 10,
, peername => {{127, 0, 0, 1}, 64810} peercert => nossl,
, proto_name => <<"MQTT-SN">> peername => {{127, 0, 0, 1}, 64810},
, proto_ver => <<"1.2">> proto_name => <<"MQTT-SN">>,
, sockname => {{0, 0, 0, 0}, 1884} proto_ver => <<"1.2">>,
, socktype => udp sockname => {{0, 0, 0, 0}, 1884},
}. socktype => udp
}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% connection module mock %% connection module mock
call(ConnPid, discard, _) -> call(ConnPid, discard, _) ->
ConnPid ! discard, ok; ConnPid ! discard,
ok;
call(ConnPid, kick, _) -> call(ConnPid, kick, _) ->
ConnPid ! kick, ok. ConnPid ! kick,
ok.

View File

@ -56,51 +56,60 @@ end_per_testcase(_TestCase, Conf) ->
t_tabname(_) -> t_tabname(_) ->
?assertEqual( ?assertEqual(
emqx_gateway_gw_name_channel_registry, emqx_gateway_gw_name_channel_registry,
emqx_gateway_cm_registry:tabname(gw_name)). emqx_gateway_cm_registry:tabname(gw_name)
).
t_register_unregister_channel(_) -> t_register_unregister_channel(_) ->
ok = emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID), ok = emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID),
?assertEqual( ?assertEqual(
[{channel, ?CLIENTID, self()}], [{channel, ?CLIENTID, self()}],
ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))), ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))
),
?assertEqual( ?assertEqual(
[self()], [self()],
emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)), emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
),
ok = emqx_gateway_cm_registry:unregister_channel(?GWNAME, ?CLIENTID), ok = emqx_gateway_cm_registry:unregister_channel(?GWNAME, ?CLIENTID),
?assertEqual( ?assertEqual(
[], [],
ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))), ets:tab2list(emqx_gateway_cm_registry:tabname(?GWNAME))
),
?assertEqual( ?assertEqual(
[], [],
emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)). emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
).
t_cleanup_channels_mnesia_down(Conf) -> t_cleanup_channels_mnesia_down(Conf) ->
Pid = proplists:get_value(registry, Conf), Pid = proplists:get_value(registry, Conf),
emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID), emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID),
?assertEqual( ?assertEqual(
[self()], [self()],
emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)), emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
),
Pid ! {membership, {mnesia, down, node()}}, Pid ! {membership, {mnesia, down, node()}},
ct:sleep(100), ct:sleep(100),
?assertEqual( ?assertEqual(
[], [],
emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)). emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
).
t_cleanup_channels_node_down(Conf) -> t_cleanup_channels_node_down(Conf) ->
Pid = proplists:get_value(registry, Conf), Pid = proplists:get_value(registry, Conf),
emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID), emqx_gateway_cm_registry:register_channel(?GWNAME, ?CLIENTID),
?assertEqual( ?assertEqual(
[self()], [self()],
emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)), emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
),
Pid ! {membership, {node, down, node()}}, Pid ! {membership, {node, down, node()}},
ct:sleep(100), ct:sleep(100),
?assertEqual( ?assertEqual(
[], [],
emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)). emqx_gateway_cm_registry:lookup_channels(?GWNAME, ?CLIENTID)
).
t_handle_unexpected_msg(Conf) -> t_handle_unexpected_msg(Conf) ->
Pid = proplists:get_value(registry, Conf), Pid = proplists:get_value(registry, Conf),

View File

@ -19,10 +19,13 @@
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
-import(emqx_gateway_test_utils, -import(
[ assert_confs/2 emqx_gateway_test_utils,
, maybe_unconvert_listeners/1 [
]). assert_confs/2,
maybe_unconvert_listeners/1
]
).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
@ -49,205 +52,210 @@ init_per_testcase(_CaseName, Conf) ->
%% Cases %% Cases
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
-define(SVR_CA, -define(SVR_CA, <<
<<"-----BEGIN CERTIFICATE----- "-----BEGIN CERTIFICATE-----\n"
MIIDUTCCAjmgAwIBAgIJAPPYCjTmxdt/MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV "MIIDUTCCAjmgAwIBAgIJAPPYCjTmxdt/MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV\n"
BAYTAkNOMREwDwYDVQQIDAhoYW5nemhvdTEMMAoGA1UECgwDRU1RMQ8wDQYDVQQD "BAYTAkNOMREwDwYDVQQIDAhoYW5nemhvdTEMMAoGA1UECgwDRU1RMQ8wDQYDVQQD\n"
DAZSb290Q0EwHhcNMjAwNTA4MDgwNjUyWhcNMzAwNTA2MDgwNjUyWjA/MQswCQYD "DAZSb290Q0EwHhcNMjAwNTA4MDgwNjUyWhcNMzAwNTA2MDgwNjUyWjA/MQswCQYD\n"
VQQGEwJDTjERMA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UE "VQQGEwJDTjERMA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UE\n"
AwwGUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcgVLex1 "AwwGUm9vdENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcgVLex1\n"
EZ9ON64EX8v+wcSjzOZpiEOsAOuSXOEN3wb8FKUxCdsGrsJYB7a5VM/Jot25Mod2 "EZ9ON64EX8v+wcSjzOZpiEOsAOuSXOEN3wb8FKUxCdsGrsJYB7a5VM/Jot25Mod2\n"
juS3OBMg6r85k2TWjdxUoUs+HiUB/pP/ARaaW6VntpAEokpij/przWMPgJnBF3Ur "juS3OBMg6r85k2TWjdxUoUs+HiUB/pP/ARaaW6VntpAEokpij/przWMPgJnBF3Ur\n"
MjtbLayH9hGmpQrI5c2vmHQ2reRZnSFbY+2b8SXZ+3lZZgz9+BaQYWdQWfaUWEHZ "MjtbLayH9hGmpQrI5c2vmHQ2reRZnSFbY+2b8SXZ+3lZZgz9+BaQYWdQWfaUWEHZ\n"
uDaNiViVO0OT8DRjCuiDp3yYDj3iLWbTA/gDL6Tf5XuHuEwcOQUrd+h0hyIphO8D "uDaNiViVO0OT8DRjCuiDp3yYDj3iLWbTA/gDL6Tf5XuHuEwcOQUrd+h0hyIphO8D\n"
tsrsHZ14j4AWYLk1CPA6pq1HIUvEl2rANx2lVUNv+nt64K/Mr3RnVQd9s8bK+TXQ "tsrsHZ14j4AWYLk1CPA6pq1HIUvEl2rANx2lVUNv+nt64K/Mr3RnVQd9s8bK+TXQ\n"
KGHd2Lv/PALYuwIDAQABo1AwTjAdBgNVHQ4EFgQUGBmW+iDzxctWAWxmhgdlE8Pj "KGHd2Lv/PALYuwIDAQABo1AwTjAdBgNVHQ4EFgQUGBmW+iDzxctWAWxmhgdlE8Pj\n"
EbQwHwYDVR0jBBgwFoAUGBmW+iDzxctWAWxmhgdlE8PjEbQwDAYDVR0TBAUwAwEB "EbQwHwYDVR0jBBgwFoAUGBmW+iDzxctWAWxmhgdlE8PjEbQwDAYDVR0TBAUwAwEB\n"
/zANBgkqhkiG9w0BAQsFAAOCAQEAGbhRUjpIred4cFAFJ7bbYD9hKu/yzWPWkMRa "/zANBgkqhkiG9w0BAQsFAAOCAQEAGbhRUjpIred4cFAFJ7bbYD9hKu/yzWPWkMRa\n"
ErlCKHmuYsYk+5d16JQhJaFy6MGXfLgo3KV2itl0d+OWNH0U9ULXcglTxy6+njo5 "ErlCKHmuYsYk+5d16JQhJaFy6MGXfLgo3KV2itl0d+OWNH0U9ULXcglTxy6+njo5\n"
CFqdUBPwN1jxhzo9yteDMKF4+AHIxbvCAJa17qcwUKR5MKNvv09C6pvQDJLzid7y "CFqdUBPwN1jxhzo9yteDMKF4+AHIxbvCAJa17qcwUKR5MKNvv09C6pvQDJLzid7y\n"
E2dkgSuggik3oa0427KvctFf8uhOV94RvEDyqvT5+pgNYZ2Yfga9pD/jjpoHEUlo "E2dkgSuggik3oa0427KvctFf8uhOV94RvEDyqvT5+pgNYZ2Yfga9pD/jjpoHEUlo\n"
88IGU8/wJCx3Ds2yc8+oBg/ynxG8f/HmCC1ET6EHHoe2jlo8FpU/SgGtghS1YL30 "88IGU8/wJCx3Ds2yc8+oBg/ynxG8f/HmCC1ET6EHHoe2jlo8FpU/SgGtghS1YL30\n"
IWxNsPrUP+XsZpBJy/mvOhE5QXo6Y35zDqqj8tI7AGmAWu22jg== "IWxNsPrUP+XsZpBJy/mvOhE5QXo6Y35zDqqj8tI7AGmAWu22jg==\n"
-----END CERTIFICATE----- "-----END CERTIFICATE-----\n"
">>). >>).
-define(SVR_CERT, -define(SVR_CERT, <<
<<"-----BEGIN CERTIFICATE----- "-----BEGIN CERTIFICATE-----\n"
MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER "MIIDEzCCAfugAwIBAgIBAjANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER\n"
MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB "MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB\n"
MB4XDTIwMDUwODA4MDcwNVoXDTMwMDUwNjA4MDcwNVowPzELMAkGA1UEBhMCQ04x "MB4XDTIwMDUwODA4MDcwNVoXDTMwMDUwNjA4MDcwNVowPzELMAkGA1UEBhMCQ04x\n"
ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBlNlcnZl "ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBlNlcnZl\n"
cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNeWT3pE+QFfiRJzKmn "cjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALNeWT3pE+QFfiRJzKmn\n"
AMUrWo3K2j/Tm3+Xnl6WLz67/0rcYrJbbKvS3uyRP/stXyXEKw9CepyQ1ViBVFkW "AMUrWo3K2j/Tm3+Xnl6WLz67/0rcYrJbbKvS3uyRP/stXyXEKw9CepyQ1ViBVFkW\n"
Aoy8qQEOWFDsZc/5UzhXUnb6LXr3qTkFEjNmhj+7uzv/lbBxlUG1NlYzSeOB6/RT "Aoy8qQEOWFDsZc/5UzhXUnb6LXr3qTkFEjNmhj+7uzv/lbBxlUG1NlYzSeOB6/RT\n"
8zH/lhOeKhLnWYPXdXKsa1FL6ij4X8DeDO1kY7fvAGmBn/THh1uTpDizM4YmeI+7 "8zH/lhOeKhLnWYPXdXKsa1FL6ij4X8DeDO1kY7fvAGmBn/THh1uTpDizM4YmeI+7\n"
4dmayA5xXvARte5h4Vu5SIze7iC057N+vymToMk2Jgk+ZZFpyXrnq+yo6RaD3ANc "4dmayA5xXvARte5h4Vu5SIze7iC057N+vymToMk2Jgk+ZZFpyXrnq+yo6RaD3ANc\n"
lrc4FbeUQZ5a5s5Sxgs9a0Y3WMG+7c5VnVXcbjBRz/aq2NtOnQQjikKKQA8GF080 "lrc4FbeUQZ5a5s5Sxgs9a0Y3WMG+7c5VnVXcbjBRz/aq2NtOnQQjikKKQA8GF080\n"
BQkCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL "BQkCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL\n"
BQADggEBAJefnMZpaRDHQSNUIEL3iwGXE9c6PmIsQVE2ustr+CakBp3TZ4l0enLt "BQADggEBAJefnMZpaRDHQSNUIEL3iwGXE9c6PmIsQVE2ustr+CakBp3TZ4l0enLt\n"
iGMfEVFju69cO4oyokWv+hl5eCMkHBf14Kv51vj448jowYnF1zmzn7SEzm5Uzlsa "iGMfEVFju69cO4oyokWv+hl5eCMkHBf14Kv51vj448jowYnF1zmzn7SEzm5Uzlsa\n"
sqjtAprnLyof69WtLU1j5rYWBuFX86yOTwRAFNjm9fvhAcrEONBsQtqipBWkMROp "sqjtAprnLyof69WtLU1j5rYWBuFX86yOTwRAFNjm9fvhAcrEONBsQtqipBWkMROp\n"
iUYMkRqbKcQMdwxov+lHBYKq9zbWRoqLROAn54SRqgQk6c15JdEfgOOjShbsOkIH "iUYMkRqbKcQMdwxov+lHBYKq9zbWRoqLROAn54SRqgQk6c15JdEfgOOjShbsOkIH\n"
UhqcwRkQic7n1zwHVGVDgNIZVgmJ2IdIWBlPEC7oLrRrBD/X1iEEXtKab6p5o22n "UhqcwRkQic7n1zwHVGVDgNIZVgmJ2IdIWBlPEC7oLrRrBD/X1iEEXtKab6p5o22n\n"
KB5mN+iQaE+Oe2cpGKZJiJRdM+IqDDQ= "KB5mN+iQaE+Oe2cpGKZJiJRdM+IqDDQ=\n"
-----END CERTIFICATE----- "-----END CERTIFICATE-----\n"
">>). >>).
-define(SVR_KEY, -define(SVR_KEY, <<
<<"-----BEGIN RSA PRIVATE KEY----- "-----BEGIN RSA PRIVATE KEY-----\n"
MIIEowIBAAKCAQEAs15ZPekT5AV+JEnMqacAxStajcraP9Obf5eeXpYvPrv/Stxi "MIIEowIBAAKCAQEAs15ZPekT5AV+JEnMqacAxStajcraP9Obf5eeXpYvPrv/Stxi\n"
sltsq9Le7JE/+y1fJcQrD0J6nJDVWIFUWRYCjLypAQ5YUOxlz/lTOFdSdvotevep "sltsq9Le7JE/+y1fJcQrD0J6nJDVWIFUWRYCjLypAQ5YUOxlz/lTOFdSdvotevep\n"
OQUSM2aGP7u7O/+VsHGVQbU2VjNJ44Hr9FPzMf+WE54qEudZg9d1cqxrUUvqKPhf "OQUSM2aGP7u7O/+VsHGVQbU2VjNJ44Hr9FPzMf+WE54qEudZg9d1cqxrUUvqKPhf\n"
wN4M7WRjt+8AaYGf9MeHW5OkOLMzhiZ4j7vh2ZrIDnFe8BG17mHhW7lIjN7uILTn "wN4M7WRjt+8AaYGf9MeHW5OkOLMzhiZ4j7vh2ZrIDnFe8BG17mHhW7lIjN7uILTn\n"
s36/KZOgyTYmCT5lkWnJeuer7KjpFoPcA1yWtzgVt5RBnlrmzlLGCz1rRjdYwb7t "s36/KZOgyTYmCT5lkWnJeuer7KjpFoPcA1yWtzgVt5RBnlrmzlLGCz1rRjdYwb7t\n"
zlWdVdxuMFHP9qrY206dBCOKQopADwYXTzQFCQIDAQABAoIBAQCuvCbr7Pd3lvI/ "zlWdVdxuMFHP9qrY206dBCOKQopADwYXTzQFCQIDAQABAoIBAQCuvCbr7Pd3lvI/\n"
n7VFQG+7pHRe1VKwAxDkx2t8cYos7y/QWcm8Ptwqtw58HzPZGWYrgGMCRpzzkRSF "n7VFQG+7pHRe1VKwAxDkx2t8cYos7y/QWcm8Ptwqtw58HzPZGWYrgGMCRpzzkRSF\n"
V9g3wP1S5Scu5C6dBu5YIGc157tqNGXB+SpdZddJQ4Nc6yGHXYERllT04ffBGc3N "V9g3wP1S5Scu5C6dBu5YIGc157tqNGXB+SpdZddJQ4Nc6yGHXYERllT04ffBGc3N\n"
WG/oYS/1cSteiSIrsDy/91FvGRCi7FPxH3wIgHssY/tw69s1Cfvaq5lr2NTFzxIG "WG/oYS/1cSteiSIrsDy/91FvGRCi7FPxH3wIgHssY/tw69s1Cfvaq5lr2NTFzxIG\n"
xCvpJKEdSfVfS9I7LYiymVjst3IOR/w76/ZFY9cRa8ZtmQSWWsm0TUpRC1jdcbkm "xCvpJKEdSfVfS9I7LYiymVjst3IOR/w76/ZFY9cRa8ZtmQSWWsm0TUpRC1jdcbkm\n"
ZoJptYWlP+gSwx/fpMYftrkJFGOJhHJHQhwxT5X/ajAISeqjjwkWSEJLwnHQd11C "ZoJptYWlP+gSwx/fpMYftrkJFGOJhHJHQhwxT5X/ajAISeqjjwkWSEJLwnHQd11C\n"
Zy2+29lBAoGBANlEAIK4VxCqyPXNKfoOOi5dS64NfvyH4A1v2+KaHWc7lqaqPN49 "Zy2+29lBAoGBANlEAIK4VxCqyPXNKfoOOi5dS64NfvyH4A1v2+KaHWc7lqaqPN49\n"
ezfN2n3X+KWx4cviDD914Yc2JQ1vVJjSaHci7yivocDo2OfZDmjBqzaMp/y+rX1R "ezfN2n3X+KWx4cviDD914Yc2JQ1vVJjSaHci7yivocDo2OfZDmjBqzaMp/y+rX1R\n"
/f3MmiTqMa468rjaxI9RRZu7vDgpTR+za1+OBCgMzjvAng8dJuN/5gjlAoGBANNY "/f3MmiTqMa468rjaxI9RRZu7vDgpTR+za1+OBCgMzjvAng8dJuN/5gjlAoGBANNY\n"
uYPKtearBmkqdrSV7eTUe49Nhr0XotLaVBH37TCW0Xv9wjO2xmbm5Ga/DCtPIsBb "uYPKtearBmkqdrSV7eTUe49Nhr0XotLaVBH37TCW0Xv9wjO2xmbm5Ga/DCtPIsBb\n"
yPeYwX9FjoasuadUD7hRvbFu6dBa0HGLmkXRJZTcD7MEX2Lhu4BuC72yDLLFd0r+ "yPeYwX9FjoasuadUD7hRvbFu6dBa0HGLmkXRJZTcD7MEX2Lhu4BuC72yDLLFd0r+\n"
Ep9WP7F5iJyagYqIZtz+4uf7gBvUDdmvXz3sGr1VAoGAdXTD6eeKeiI6PlhKBztF "Ep9WP7F5iJyagYqIZtz+4uf7gBvUDdmvXz3sGr1VAoGAdXTD6eeKeiI6PlhKBztF\n"
zOb3EQOO0SsLv3fnodu7ZaHbUgLaoTMPuB17r2jgrYM7FKQCBxTNdfGZmmfDjlLB "zOb3EQOO0SsLv3fnodu7ZaHbUgLaoTMPuB17r2jgrYM7FKQCBxTNdfGZmmfDjlLB\n"
0xZ5wL8ibU30ZXL8zTlWPElST9sto4B+FYVVF/vcG9sWeUUb2ncPcJ/Po3UAktDG "0xZ5wL8ibU30ZXL8zTlWPElST9sto4B+FYVVF/vcG9sWeUUb2ncPcJ/Po3UAktDG\n"
jYQTTyuNGtSJHpad/YOZctkCgYBtWRaC7bq3of0rJGFOhdQT9SwItN/lrfj8hyHA "jYQTTyuNGtSJHpad/YOZctkCgYBtWRaC7bq3of0rJGFOhdQT9SwItN/lrfj8hyHA\n"
OjpqTV4NfPmhsAtu6j96OZaeQc+FHvgXwt06cE6Rt4RG4uNPRluTFgO7XYFDfitP "OjpqTV4NfPmhsAtu6j96OZaeQc+FHvgXwt06cE6Rt4RG4uNPRluTFgO7XYFDfitP\n"
vCppnoIw6S5BBvHwPP+uIhUX2bsi/dm8vu8tb+gSvo4PkwtFhEr6I9HglBKmcmog "vCppnoIw6S5BBvHwPP+uIhUX2bsi/dm8vu8tb+gSvo4PkwtFhEr6I9HglBKmcmog\n"
q6waEQKBgHyecFBeM6Ls11Cd64vborwJPAuxIW7HBAFj/BS99oeG4TjBx4Sz2dFd "q6waEQKBgHyecFBeM6Ls11Cd64vborwJPAuxIW7HBAFj/BS99oeG4TjBx4Sz2dFd\n"
rzUibJt4ndnHIvCN8JQkjNG14i9hJln+H3mRss8fbZ9vQdqG+2vOWADYSzzsNI55 "rzUibJt4ndnHIvCN8JQkjNG14i9hJln+H3mRss8fbZ9vQdqG+2vOWADYSzzsNI55\n"
RFY7JjluKcVkp/zCDeUxTU3O6sS+v6/3VE11Cob6OYQx3lN5wrZ3 "RFY7JjluKcVkp/zCDeUxTU3O6sS+v6/3VE11Cob6OYQx3lN5wrZ3\n"
-----END RSA PRIVATE KEY----- "-----END RSA PRIVATE KEY-----\n"
">>). >>).
-define(SVR_CERT2, -define(SVR_CERT2, <<
<<"-----BEGIN CERTIFICATE----- "-----BEGIN CERTIFICATE-----\n"
MIIDEzCCAfugAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER "MIIDEzCCAfugAwIBAgIBATANBgkqhkiG9w0BAQsFADA/MQswCQYDVQQGEwJDTjER\n"
MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB "MA8GA1UECAwIaGFuZ3pob3UxDDAKBgNVBAoMA0VNUTEPMA0GA1UEAwwGUm9vdENB\n"
MB4XDTIwMDUwODA4MDY1N1oXDTMwMDUwNjA4MDY1N1owPzELMAkGA1UEBhMCQ04x "MB4XDTIwMDUwODA4MDY1N1oXDTMwMDUwNjA4MDY1N1owPzELMAkGA1UEBhMCQ04x\n"
ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBkNsaWVu "ETAPBgNVBAgMCGhhbmd6aG91MQwwCgYDVQQKDANFTVExDzANBgNVBAMMBkNsaWVu\n"
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMy4hoksKcZBDbY680u6 "dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMy4hoksKcZBDbY680u6\n"
TS25U51nuB1FBcGMlF9B/t057wPOlxF/OcmbxY5MwepS41JDGPgulE1V7fpsXkiW "TS25U51nuB1FBcGMlF9B/t057wPOlxF/OcmbxY5MwepS41JDGPgulE1V7fpsXkiW\n"
1LUimYV/tsqBfymIe0mlY7oORahKji7zKQ2UBIVFhdlvQxunlIDnw6F9popUgyHt "1LUimYV/tsqBfymIe0mlY7oORahKji7zKQ2UBIVFhdlvQxunlIDnw6F9popUgyHt\n"
dMhtlgZK8oqRwHxO5dbfoukYd6J/r+etS5q26sgVkf3C6dt0Td7B25H9qW+f7oLV "dMhtlgZK8oqRwHxO5dbfoukYd6J/r+etS5q26sgVkf3C6dt0Td7B25H9qW+f7oLV\n"
PbcHYCa+i73u9670nrpXsC+Qc7Mygwa2Kq/jwU+ftyLQnOeW07DuzOwsziC/fQZa "PbcHYCa+i73u9670nrpXsC+Qc7Mygwa2Kq/jwU+ftyLQnOeW07DuzOwsziC/fQZa\n"
nbxR+8U9FNftgRcC3uP/JMKYUqsiRAuaDokARZxVTV5hUElfpO6z6/NItSDvvh3i "nbxR+8U9FNftgRcC3uP/JMKYUqsiRAuaDokARZxVTV5hUElfpO6z6/NItSDvvh3i\n"
eikCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL "eikCAwEAAaMaMBgwCQYDVR0TBAIwADALBgNVHQ8EBAMCBeAwDQYJKoZIhvcNAQEL\n"
BQADggEBABchYxKo0YMma7g1qDswJXsR5s56Czx/I+B41YcpMBMTrRqpUC0nHtLk "BQADggEBABchYxKo0YMma7g1qDswJXsR5s56Czx/I+B41YcpMBMTrRqpUC0nHtLk\n"
M7/tZp592u/tT8gzEnQjZLKBAhFeZaR3aaKyknLqwiPqJIgg0pgsBGITrAK3Pv4z "M7/tZp592u/tT8gzEnQjZLKBAhFeZaR3aaKyknLqwiPqJIgg0pgsBGITrAK3Pv4z\n"
5/YvAJJKgTe5UdeTz6U4lvNEux/4juZ4pmqH4qSFJTOzQS7LmgSmNIdd072rwXBd "5/YvAJJKgTe5UdeTz6U4lvNEux/4juZ4pmqH4qSFJTOzQS7LmgSmNIdd072rwXBd\n"
UzcSHzsJgEMb88u/LDLjj1pQ7AtZ4Tta8JZTvcgBFmjB0QUi6fgkHY6oGat/W4kR "UzcSHzsJgEMb88u/LDLjj1pQ7AtZ4Tta8JZTvcgBFmjB0QUi6fgkHY6oGat/W4kR\n"
jSRUBlMUbM/drr2PVzRc2dwbFIl3X+ZE6n5Sl3ZwRAC/s92JU6CPMRW02muVu6xl "jSRUBlMUbM/drr2PVzRc2dwbFIl3X+ZE6n5Sl3ZwRAC/s92JU6CPMRW02muVu6xl\n"
goraNgPISnrbpR6KjxLZkVembXzjNNc= "goraNgPISnrbpR6KjxLZkVembXzjNNc=\n"
-----END CERTIFICATE----- "-----END CERTIFICATE-----\n"
">>). >>).
-define(SVR_KEY2, -define(SVR_KEY2, <<
<<"-----BEGIN RSA PRIVATE KEY----- "-----BEGIN RSA PRIVATE KEY-----\n"
MIIEpAIBAAKCAQEAzLiGiSwpxkENtjrzS7pNLblTnWe4HUUFwYyUX0H+3TnvA86X "MIIEpAIBAAKCAQEAzLiGiSwpxkENtjrzS7pNLblTnWe4HUUFwYyUX0H+3TnvA86X\n"
EX85yZvFjkzB6lLjUkMY+C6UTVXt+mxeSJbUtSKZhX+2yoF/KYh7SaVjug5FqEqO "EX85yZvFjkzB6lLjUkMY+C6UTVXt+mxeSJbUtSKZhX+2yoF/KYh7SaVjug5FqEqO\n"
LvMpDZQEhUWF2W9DG6eUgOfDoX2milSDIe10yG2WBkryipHAfE7l1t+i6Rh3on+v "LvMpDZQEhUWF2W9DG6eUgOfDoX2milSDIe10yG2WBkryipHAfE7l1t+i6Rh3on+v\n"
561LmrbqyBWR/cLp23RN3sHbkf2pb5/ugtU9twdgJr6Lve73rvSeulewL5BzszKD "561LmrbqyBWR/cLp23RN3sHbkf2pb5/ugtU9twdgJr6Lve73rvSeulewL5BzszKD\n"
BrYqr+PBT5+3ItCc55bTsO7M7CzOIL99BlqdvFH7xT0U1+2BFwLe4/8kwphSqyJE "BrYqr+PBT5+3ItCc55bTsO7M7CzOIL99BlqdvFH7xT0U1+2BFwLe4/8kwphSqyJE\n"
C5oOiQBFnFVNXmFQSV+k7rPr80i1IO++HeJ6KQIDAQABAoIBAGWgvPjfuaU3qizq "C5oOiQBFnFVNXmFQSV+k7rPr80i1IO++HeJ6KQIDAQABAoIBAGWgvPjfuaU3qizq\n"
uti/FY07USz0zkuJdkANH6LiSjlchzDmn8wJ0pApCjuIE0PV/g9aS8z4opp5q/gD "uti/FY07USz0zkuJdkANH6LiSjlchzDmn8wJ0pApCjuIE0PV/g9aS8z4opp5q/gD\n"
UBLM/a8mC/xf2EhTXOMrY7i9p/I3H5FZ4ZehEqIw9sWKK9YzC6dw26HabB2BGOnW "UBLM/a8mC/xf2EhTXOMrY7i9p/I3H5FZ4ZehEqIw9sWKK9YzC6dw26HabB2BGOnW\n"
5nozPSQ6cp2RGzJ7BIkxSZwPzPnVTgy3OAuPOiJytvK+hGLhsNaT+Y9bNDvplVT2 "5nozPSQ6cp2RGzJ7BIkxSZwPzPnVTgy3OAuPOiJytvK+hGLhsNaT+Y9bNDvplVT2\n"
ZwYTV8GlHZC+4b2wNROILm0O86v96O+Qd8nn3fXjGHbMsAnONBq10bZS16L4fvkH "ZwYTV8GlHZC+4b2wNROILm0O86v96O+Qd8nn3fXjGHbMsAnONBq10bZS16L4fvkH\n"
5G+W/1PeSXmtZFppdRRDxIW+DWcXK0D48WRliuxcV4eOOxI+a9N2ZJZZiNLQZGwg "5G+W/1PeSXmtZFppdRRDxIW+DWcXK0D48WRliuxcV4eOOxI+a9N2ZJZZiNLQZGwg\n"
w3A8+mECgYEA8HuJFrlRvdoBe2U/EwUtG74dcyy30L4yEBnN5QscXmEEikhaQCfX "w3A8+mECgYEA8HuJFrlRvdoBe2U/EwUtG74dcyy30L4yEBnN5QscXmEEikhaQCfX\n"
Wm6EieMcIB/5I5TQmSw0cmBMeZjSXYoFdoI16/X6yMMuATdxpvhOZGdUGXxhAH+x "Wm6EieMcIB/5I5TQmSw0cmBMeZjSXYoFdoI16/X6yMMuATdxpvhOZGdUGXxhAH+x\n"
xoTUavWZnEqW3fkUU71kT5E2f2i+0zoatFESXHeslJyz85aAYpP92H0CgYEA2e5A "xoTUavWZnEqW3fkUU71kT5E2f2i+0zoatFESXHeslJyz85aAYpP92H0CgYEA2e5A\n"
Yozt5eaA1Gyhd8SeptkEU4xPirNUnVQHStpMWUb1kzTNXrPmNWccQ7JpfpG6DcYl "Yozt5eaA1Gyhd8SeptkEU4xPirNUnVQHStpMWUb1kzTNXrPmNWccQ7JpfpG6DcYl\n"
zUF6p6mlzY+zkMiyPQjwEJlhiHM2NlL1QS7td0R8ewgsFoyn8WsBI4RejWrEG9td "zUF6p6mlzY+zkMiyPQjwEJlhiHM2NlL1QS7td0R8ewgsFoyn8WsBI4RejWrEG9td\n"
EDniuIw+pBFkcWthnTLHwECHdzgquToyTMjrBB0CgYEA28tdGbrZXhcyAZEhHAZA "EDniuIw+pBFkcWthnTLHwECHdzgquToyTMjrBB0CgYEA28tdGbrZXhcyAZEhHAZA\n"
Gzog+pKlkpEzeonLKIuGKzCrEKRecIK5jrqyQsCjhS0T7ZRnL4g6i0s+umiV5M5w "Gzog+pKlkpEzeonLKIuGKzCrEKRecIK5jrqyQsCjhS0T7ZRnL4g6i0s+umiV5M5w\n"
fcc292pEA1h45L3DD6OlKplSQVTv55/OYS4oY3YEJtf5mfm8vWi9lQeY8sxOlQpn "fcc292pEA1h45L3DD6OlKplSQVTv55/OYS4oY3YEJtf5mfm8vWi9lQeY8sxOlQpn\n"
O+VZTdBHmTC8PGeTAgZXHZUCgYA6Tyv88lYowB7SN2qQgBQu8jvdGtqhcs/99GCr "O+VZTdBHmTC8PGeTAgZXHZUCgYA6Tyv88lYowB7SN2qQgBQu8jvdGtqhcs/99GCr\n"
H3N0I69LPsKAR0QeH8OJPXBKhDUywESXAaEOwS5yrLNP1tMRz5Vj65YUCzeDG3kx "H3N0I69LPsKAR0QeH8OJPXBKhDUywESXAaEOwS5yrLNP1tMRz5Vj65YUCzeDG3kx\n"
gpvY4IMp7ArX0bSRvJ6mYSFnVxy3k174G3TVCfksrtagHioVBGQ7xUg5ltafjrms "gpvY4IMp7ArX0bSRvJ6mYSFnVxy3k174G3TVCfksrtagHioVBGQ7xUg5ltafjrms\n"
n8l55QKBgQDVzU8tQvBVqY8/1lnw11Vj4fkE/drZHJ5UkdC1eenOfSWhlSLfUJ8j "n8l55QKBgQDVzU8tQvBVqY8/1lnw11Vj4fkE/drZHJ5UkdC1eenOfSWhlSLfUJ8j\n"
ds7vEWpRPPoVuPZYeR1y78cyxKe1GBx6Wa2lF5c7xjmiu0xbRnrxYeLolce9/ntp "ds7vEWpRPPoVuPZYeR1y78cyxKe1GBx6Wa2lF5c7xjmiu0xbRnrxYeLolce9/ntp\n"
asClqpnHT8/VJYTD7Kqj0fouTTZf0zkig/y+2XERppd8k+pSKjUCPQ== "asClqpnHT8/VJYTD7Kqj0fouTTZf0zkig/y+2XERppd8k+pSKjUCPQ==\n"
-----END RSA PRIVATE KEY----- "-----END RSA PRIVATE KEY-----\n"
">>). >>).
-define(CONF_STOMP_BAISC_1, -define(CONF_STOMP_BAISC_1, #{
#{ <<"idle_timeout">> => <<"10s">>, <<"idle_timeout">> => <<"10s">>,
<<"mountpoint">> => <<"t/">>, <<"mountpoint">> => <<"t/">>,
<<"frame">> => <<"frame">> =>
#{ <<"max_headers">> => 20, #{
<<"max_headers_length">> => 2000, <<"max_headers">> => 20,
<<"max_body_length">> => 2000 <<"max_headers_length">> => 2000,
} <<"max_body_length">> => 2000
}). }
-define(CONF_STOMP_BAISC_2, }).
#{ <<"idle_timeout">> => <<"20s">>, -define(CONF_STOMP_BAISC_2, #{
<<"mountpoint">> => <<"t2/">>, <<"idle_timeout">> => <<"20s">>,
<<"frame">> => <<"mountpoint">> => <<"t2/">>,
#{ <<"max_headers">> => 30, <<"frame">> =>
<<"max_headers_length">> => 3000, #{
<<"max_body_length">> => 3000 <<"max_headers">> => 30,
} <<"max_headers_length">> => 3000,
}). <<"max_body_length">> => 3000
-define(CONF_STOMP_LISTENER_1, }
#{ <<"bind">> => <<"61613">> }).
}). -define(CONF_STOMP_LISTENER_1, #{<<"bind">> => <<"61613">>}).
-define(CONF_STOMP_LISTENER_2, -define(CONF_STOMP_LISTENER_2, #{<<"bind">> => <<"61614">>}).
#{ <<"bind">> => <<"61614">> -define(CONF_STOMP_LISTENER_SSL, #{
}). <<"bind">> => <<"61614">>,
-define(CONF_STOMP_LISTENER_SSL, <<"ssl">> =>
#{ <<"bind">> => <<"61614">>, #{
<<"ssl">> => <<"cacertfile">> => ?SVR_CA,
#{ <<"cacertfile">> => ?SVR_CA, <<"certfile">> => ?SVR_CERT,
<<"certfile">> => ?SVR_CERT, <<"keyfile">> => ?SVR_KEY
<<"keyfile">> => ?SVR_KEY }
} }).
}). -define(CONF_STOMP_LISTENER_SSL_2, #{
-define(CONF_STOMP_LISTENER_SSL_2, <<"bind">> => <<"61614">>,
#{ <<"bind">> => <<"61614">>, <<"ssl">> =>
<<"ssl">> => #{
#{ <<"cacertfile">> => ?SVR_CA, <<"cacertfile">> => ?SVR_CA,
<<"certfile">> => ?SVR_CERT2, <<"certfile">> => ?SVR_CERT2,
<<"keyfile">> => ?SVR_KEY2 <<"keyfile">> => ?SVR_KEY2
} }
}). }).
-define(CERTS_PATH(CertName), filename:join(["../../lib/emqx/etc/certs/", CertName])). -define(CERTS_PATH(CertName), filename:join(["../../lib/emqx/etc/certs/", CertName])).
-define(CONF_STOMP_LISTENER_SSL_PATH, -define(CONF_STOMP_LISTENER_SSL_PATH, #{
#{ <<"bind">> => <<"61614">>, <<"bind">> => <<"61614">>,
<<"ssl">> => <<"ssl">> =>
#{ <<"cacertfile">> => ?CERTS_PATH("cacert.pem"), #{
<<"certfile">> => ?CERTS_PATH("cert.pem"), <<"cacertfile">> => ?CERTS_PATH("cacert.pem"),
<<"keyfile">> => ?CERTS_PATH("key.pem") <<"certfile">> => ?CERTS_PATH("cert.pem"),
} <<"keyfile">> => ?CERTS_PATH("key.pem")
}). }
-define(CONF_STOMP_AUTHN_1, }).
#{ <<"mechanism">> => <<"password_based">>, -define(CONF_STOMP_AUTHN_1, #{
<<"backend">> => <<"built_in_database">>, <<"mechanism">> => <<"password_based">>,
<<"user_id_type">> => <<"clientid">> <<"backend">> => <<"built_in_database">>,
}). <<"user_id_type">> => <<"clientid">>
-define(CONF_STOMP_AUTHN_2, }).
#{ <<"mechanism">> => <<"password_based">>, -define(CONF_STOMP_AUTHN_2, #{
<<"backend">> => <<"built_in_database">>, <<"mechanism">> => <<"password_based">>,
<<"user_id_type">> => <<"username">> <<"backend">> => <<"built_in_database">>,
}). <<"user_id_type">> => <<"username">>
}).
t_load_unload_gateway(_) -> t_load_unload_gateway(_) ->
StompConf1 = compose(?CONF_STOMP_BAISC_1, StompConf1 = compose(
?CONF_STOMP_AUTHN_1, ?CONF_STOMP_BAISC_1,
?CONF_STOMP_LISTENER_1 ?CONF_STOMP_AUTHN_1,
), ?CONF_STOMP_LISTENER_1
StompConf2 = compose(?CONF_STOMP_BAISC_2, ),
?CONF_STOMP_AUTHN_1, StompConf2 = compose(
?CONF_STOMP_LISTENER_1), ?CONF_STOMP_BAISC_2,
?CONF_STOMP_AUTHN_1,
?CONF_STOMP_LISTENER_1
),
{ok, _} = emqx_gateway_conf:load_gateway(stomp, StompConf1), {ok, _} = emqx_gateway_conf:load_gateway(stomp, StompConf1),
?assertMatch( ?assertMatch(
{error, {badres, #{reason := already_exist}}}, {error, {badres, #{reason := already_exist}}},
emqx_gateway_conf:load_gateway(stomp, StompConf1)), emqx_gateway_conf:load_gateway(stomp, StompConf1)
),
assert_confs(StompConf1, emqx:get_raw_config([gateway, stomp])), assert_confs(StompConf1, emqx:get_raw_config([gateway, stomp])),
{ok, _} = emqx_gateway_conf:update_gateway(stomp, StompConf2), {ok, _} = emqx_gateway_conf:update_gateway(stomp, StompConf2),
@ -257,11 +265,15 @@ t_load_unload_gateway(_) ->
ok = emqx_gateway_conf:unload_gateway(stomp), ok = emqx_gateway_conf:unload_gateway(stomp),
?assertMatch( ?assertMatch(
{error, {badres, #{reason := not_found}}}, {error, {badres, #{reason := not_found}}},
emqx_gateway_conf:update_gateway(stomp, StompConf2)), emqx_gateway_conf:update_gateway(stomp, StompConf2)
),
?assertException(error, {config_not_found, [gateway, stomp]}, ?assertException(
emqx:get_raw_config([gateway, stomp])), error,
{config_not_found, [gateway, stomp]},
emqx:get_raw_config([gateway, stomp])
),
ok. ok.
t_load_remove_authn(_) -> t_load_remove_authn(_) ->
@ -272,24 +284,28 @@ t_load_remove_authn(_) ->
{ok, _} = emqx_gateway_conf:add_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_1), {ok, _} = emqx_gateway_conf:add_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_1),
assert_confs( assert_confs(
maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_1, StompConf), maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_1, StompConf),
emqx:get_raw_config([gateway, stomp])), emqx:get_raw_config([gateway, stomp])
),
{ok, _} = emqx_gateway_conf:update_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_2), {ok, _} = emqx_gateway_conf:update_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_2),
assert_confs( assert_confs(
maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_2, StompConf), maps:put(<<"authentication">>, ?CONF_STOMP_AUTHN_2, StompConf),
emqx:get_raw_config([gateway, stomp])), emqx:get_raw_config([gateway, stomp])
),
ok = emqx_gateway_conf:remove_authn(<<"stomp">>), ok = emqx_gateway_conf:remove_authn(<<"stomp">>),
?assertMatch( ?assertMatch(
{error, {badres, #{reason := not_found}}}, {error, {badres, #{reason := not_found}}},
emqx_gateway_conf:update_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_2)), emqx_gateway_conf:update_authn(<<"stomp">>, ?CONF_STOMP_AUTHN_2)
),
?assertException( ?assertException(
error, {config_not_found, [gateway, stomp, authentication]}, error,
emqx:get_raw_config([gateway, stomp, authentication]) {config_not_found, [gateway, stomp, authentication]},
), emqx:get_raw_config([gateway, stomp, authentication])
),
ok. ok.
t_load_remove_listeners(_) -> t_load_remove_listeners(_) ->
@ -299,90 +315,109 @@ t_load_remove_listeners(_) ->
assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])), assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
{ok, _} = emqx_gateway_conf:add_listener( {ok, _} = emqx_gateway_conf:add_listener(
<<"stomp">>, {<<"tcp">>, <<"default">>}, <<"stomp">>,
?CONF_STOMP_LISTENER_1), {<<"tcp">>, <<"default">>},
?CONF_STOMP_LISTENER_1
),
assert_confs( assert_confs(
maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_1)), maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_1)),
emqx:get_raw_config([gateway, stomp])), emqx:get_raw_config([gateway, stomp])
),
{ok, _} = emqx_gateway_conf:update_listener( {ok, _} = emqx_gateway_conf:update_listener(
<<"stomp">>, {<<"tcp">>, <<"default">>}, <<"stomp">>,
?CONF_STOMP_LISTENER_2), {<<"tcp">>, <<"default">>},
?CONF_STOMP_LISTENER_2
),
assert_confs( assert_confs(
maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_2)), maps:merge(StompConf, listener(?CONF_STOMP_LISTENER_2)),
emqx:get_raw_config([gateway, stomp])), emqx:get_raw_config([gateway, stomp])
),
ok = emqx_gateway_conf:remove_listener( ok = emqx_gateway_conf:remove_listener(
<<"stomp">>, {<<"tcp">>, <<"default">>}), <<"stomp">>, {<<"tcp">>, <<"default">>}
),
?assertMatch( ?assertMatch(
{error, {badres, #{reason := not_found}}}, {error, {badres, #{reason := not_found}}},
emqx_gateway_conf:update_listener( emqx_gateway_conf:update_listener(
<<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_LISTENER_2)), <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_LISTENER_2
)
),
?assertException( ?assertException(
error, {config_not_found, [gateway, stomp, listeners, tcp, default]}, error,
emqx:get_raw_config([gateway, stomp, listeners, tcp, default]) {config_not_found, [gateway, stomp, listeners, tcp, default]},
), emqx:get_raw_config([gateway, stomp, listeners, tcp, default])
),
ok. ok.
t_load_remove_listener_authn(_) -> t_load_remove_listener_authn(_) ->
StompConf = compose_listener( StompConf = compose_listener(
?CONF_STOMP_BAISC_1, ?CONF_STOMP_BAISC_1,
?CONF_STOMP_LISTENER_1 ?CONF_STOMP_LISTENER_1
), ),
StompConf1 = compose_listener_authn( StompConf1 = compose_listener_authn(
?CONF_STOMP_BAISC_1, ?CONF_STOMP_BAISC_1,
?CONF_STOMP_LISTENER_1, ?CONF_STOMP_LISTENER_1,
?CONF_STOMP_AUTHN_1 ?CONF_STOMP_AUTHN_1
), ),
StompConf2 = compose_listener_authn( StompConf2 = compose_listener_authn(
?CONF_STOMP_BAISC_1, ?CONF_STOMP_BAISC_1,
?CONF_STOMP_LISTENER_1, ?CONF_STOMP_LISTENER_1,
?CONF_STOMP_AUTHN_2 ?CONF_STOMP_AUTHN_2
), ),
{ok, _} = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf), {ok, _} = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf),
assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])), assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
{ok, _} = emqx_gateway_conf:add_authn( {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])), assert_confs(StompConf1, emqx:get_raw_config([gateway, stomp])),
{ok, _} = emqx_gateway_conf:update_authn( {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])), assert_confs(StompConf2, emqx:get_raw_config([gateway, stomp])),
ok = emqx_gateway_conf:remove_authn( ok = emqx_gateway_conf:remove_authn(
<<"stomp">>, {<<"tcp">>, <<"default">>}), <<"stomp">>, {<<"tcp">>, <<"default">>}
),
?assertMatch( ?assertMatch(
{error, {badres, #{reason := not_found}}}, {error, {badres, #{reason := not_found}}},
emqx_gateway_conf:update_authn( emqx_gateway_conf:update_authn(
<<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_2)), <<"stomp">>, {<<"tcp">>, <<"default">>}, ?CONF_STOMP_AUTHN_2
)
),
Path = [gateway, stomp, listeners, tcp, default, authentication], Path = [gateway, stomp, listeners, tcp, default, authentication],
?assertException( ?assertException(
error, {config_not_found, Path}, error,
emqx:get_raw_config(Path) {config_not_found, Path},
), emqx:get_raw_config(Path)
),
ok. ok.
t_load_gateway_with_certs_content(_) -> t_load_gateway_with_certs_content(_) ->
StompConf = compose_ssl_listener( StompConf = compose_ssl_listener(
?CONF_STOMP_BAISC_1, ?CONF_STOMP_BAISC_1,
?CONF_STOMP_LISTENER_SSL ?CONF_STOMP_LISTENER_SSL
), ),
{ok, _} = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf), {ok, _} = emqx_gateway_conf:load_gateway(<<"stomp">>, StompConf),
assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])), assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
SslConf = emqx_map_lib:deep_get( SslConf = emqx_map_lib:deep_get(
[<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>], [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
emqx:get_raw_config([gateway, stomp]) emqx:get_raw_config([gateway, stomp])
), ),
ok = emqx_gateway_conf:unload_gateway(<<"stomp">>), ok = emqx_gateway_conf:unload_gateway(<<"stomp">>),
assert_ssl_confs_files_deleted(SslConf), assert_ssl_confs_files_deleted(SslConf),
?assertException(error, {config_not_found, [gateway, stomp]}, ?assertException(
emqx:get_raw_config([gateway, stomp])), error,
{config_not_found, [gateway, stomp]},
emqx:get_raw_config([gateway, stomp])
),
ok. ok.
%% TODO: Comment out this test case for now, because emqx_tls_lib %% 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])), assert_confs(StompConf, emqx:get_raw_config([gateway, stomp])),
{ok, _} = emqx_gateway_conf:add_listener( {ok, _} = emqx_gateway_conf:add_listener(
<<"stomp">>, {<<"ssl">>, <<"default">>}, <<"stomp">>,
?CONF_STOMP_LISTENER_SSL), {<<"ssl">>, <<"default">>},
?CONF_STOMP_LISTENER_SSL
),
assert_confs( assert_confs(
maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL)), maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL)),
emqx:get_raw_config([gateway, stomp])), emqx:get_raw_config([gateway, stomp])
),
{ok, _} = emqx_gateway_conf:update_listener( {ok, _} = emqx_gateway_conf:update_listener(
<<"stomp">>, {<<"ssl">>, <<"default">>}, <<"stomp">>,
?CONF_STOMP_LISTENER_SSL_2), {<<"ssl">>, <<"default">>},
?CONF_STOMP_LISTENER_SSL_2
),
assert_confs( assert_confs(
maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL_2)), maps:merge(StompConf, ssl_listener(?CONF_STOMP_LISTENER_SSL_2)),
emqx:get_raw_config([gateway, stomp])), emqx:get_raw_config([gateway, stomp])
),
SslConf = emqx_map_lib:deep_get( SslConf = emqx_map_lib:deep_get(
[<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>], [<<"listeners">>, <<"ssl">>, <<"default">>, <<"ssl">>],
emqx:get_raw_config([gateway, stomp]) emqx:get_raw_config([gateway, stomp])
), ),
ok = emqx_gateway_conf:remove_listener( ok = emqx_gateway_conf:remove_listener(
<<"stomp">>, {<<"ssl">>, <<"default">>}), <<"stomp">>, {<<"ssl">>, <<"default">>}
),
assert_ssl_confs_files_deleted(SslConf), assert_ssl_confs_files_deleted(SslConf),
?assertMatch( ?assertMatch(
{error, {badres, #{reason := not_found}}}, {error, {badres, #{reason := not_found}}},
emqx_gateway_conf:update_listener( emqx_gateway_conf:update_listener(
<<"stomp">>, {<<"ssl">>, <<"default">>}, ?CONF_STOMP_LISTENER_SSL_2)), <<"stomp">>, {<<"ssl">>, <<"default">>}, ?CONF_STOMP_LISTENER_SSL_2
)
),
?assertException( ?assertException(
error, {config_not_found, [gateway, stomp, listeners, ssl, default]}, error,
emqx:get_raw_config([gateway, stomp, listeners, ssl, default]) {config_not_found, [gateway, stomp, listeners, ssl, default]},
), emqx:get_raw_config([gateway, stomp, listeners, ssl, default])
),
ok. ok.
assert_ssl_confs_files_deleted(SslConf) when is_map(SslConf) -> assert_ssl_confs_files_deleted(SslConf) when is_map(SslConf) ->
Ks = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>], Ks = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>],
lists:foreach(fun(K) -> lists:foreach(
Path = maps:get(K, SslConf), fun(K) ->
{error, enoent} = file:read_file(Path) Path = maps:get(K, SslConf),
end, Ks). {error, enoent} = file:read_file(Path)
end,
Ks
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Utils %% Utils
compose(Basic, Authn, Listener) -> compose(Basic, Authn, Listener) ->
maps:merge( maps:merge(
maps:merge(Basic, #{<<"authentication">> => Authn}), maps:merge(Basic, #{<<"authentication">> => Authn}),
listener(Listener)). listener(Listener)
).
compose_listener(Basic, Listener) -> compose_listener(Basic, Listener) ->
maps:merge(Basic, listener(Listener)). maps:merge(Basic, listener(Listener)).
@ -469,13 +518,26 @@ compose_authn(Basic, Authn) ->
compose_listener_authn(Basic, Listener, Authn) -> compose_listener_authn(Basic, Listener, Authn) ->
maps:merge( maps:merge(
Basic, Basic,
listener(maps:put(<<"authentication">>, Authn, Listener))). listener(maps:put(<<"authentication">>, Authn, Listener))
).
listener(L) -> listener(L) ->
#{<<"listeners">> => [L#{<<"type">> => <<"tcp">>, #{
<<"name">> => <<"default">>}]}. <<"listeners">> => [
L#{
<<"type">> => <<"tcp">>,
<<"name">> => <<"default">>
}
]
}.
ssl_listener(L) -> ssl_listener(L) ->
#{<<"listeners">> => [L#{<<"type">> => <<"ssl">>, #{
<<"name">> => <<"default">>}]}. <<"listeners">> => [
L#{
<<"type">> => <<"ssl">>,
<<"name">> => <<"default">>
}
]
}.

View File

@ -29,11 +29,16 @@ all() -> emqx_common_test_helpers:all(?MODULE).
init_per_suite(Conf) -> init_per_suite(Conf) ->
ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]),
ok = meck:expect(emqx_access_control, authenticate, ok = meck:expect(
fun(#{clientid := bad_client}) -> emqx_access_control,
{error, bad_username_or_password}; authenticate,
(ClientInfo) -> {ok, ClientInfo} fun
end), (#{clientid := bad_client}) ->
{error, bad_username_or_password};
(ClientInfo) ->
{ok, ClientInfo}
end
),
Conf. Conf.
end_per_suite(_Conf) -> end_per_suite(_Conf) ->
@ -45,23 +50,26 @@ end_per_suite(_Conf) ->
t_authenticate(_) -> t_authenticate(_) ->
Ctx = #{gwname => mqttsn, auth => [], cm => self()}, Ctx = #{gwname => mqttsn, auth => [], cm => self()},
Info1 = #{ mountpoint => undefined Info1 = #{
, clientid => <<"user1">> mountpoint => undefined,
}, clientid => <<"user1">>
},
NInfo1 = zone(Info1), NInfo1 = zone(Info1),
?assertEqual({ok, NInfo1}, emqx_gateway_ctx:authenticate(Ctx, Info1)), ?assertEqual({ok, NInfo1}, emqx_gateway_ctx:authenticate(Ctx, Info1)),
Info2 = #{ mountpoint => <<"mqttsn/${clientid}/">> Info2 = #{
, clientid => <<"user1">> mountpoint => <<"mqttsn/${clientid}/">>,
}, clientid => <<"user1">>
},
NInfo2 = zone(Info2#{mountpoint => <<"mqttsn/user1/">>}), NInfo2 = zone(Info2#{mountpoint => <<"mqttsn/user1/">>}),
?assertEqual({ok, NInfo2}, emqx_gateway_ctx:authenticate(Ctx, Info2)), ?assertEqual({ok, NInfo2}, emqx_gateway_ctx:authenticate(Ctx, Info2)),
Info3 = #{ mountpoint => <<"mqttsn/${clientid}/">> Info3 = #{
, clientid => bad_client mountpoint => <<"mqttsn/${clientid}/">>,
}, clientid => bad_client
{error, bad_username_or_password} },
= emqx_gateway_ctx:authenticate(Ctx, Info3), {error, bad_username_or_password} =
emqx_gateway_ctx:authenticate(Ctx, Info3),
ok. ok.
zone(Info) -> Info#{zone => default}. zone(Info) -> Info#{zone => default}.

View File

@ -58,15 +58,17 @@ t_inc_dec(_) ->
ok = emqx_gateway_metrics:inc(?GWNAME, ?METRIC), ok = emqx_gateway_metrics:inc(?GWNAME, ?METRIC),
?assertEqual( ?assertEqual(
[{?METRIC, 2}], [{?METRIC, 2}],
emqx_gateway_metrics:lookup(?GWNAME)), emqx_gateway_metrics:lookup(?GWNAME)
),
ok = emqx_gateway_metrics:dec(?GWNAME, ?METRIC), ok = emqx_gateway_metrics:dec(?GWNAME, ?METRIC),
ok = emqx_gateway_metrics:dec(?GWNAME, ?METRIC), ok = emqx_gateway_metrics:dec(?GWNAME, ?METRIC),
?assertEqual( ?assertEqual(
[{?METRIC, 0}], [{?METRIC, 0}],
emqx_gateway_metrics:lookup(?GWNAME)). emqx_gateway_metrics:lookup(?GWNAME)
).
t_handle_unexpected_msg(Conf) -> t_handle_unexpected_msg(Conf) ->
Pid = proplists:get_value(metrics, Conf), Pid = proplists:get_value(metrics, Conf),

View File

@ -21,11 +21,14 @@
-compile(export_all). -compile(export_all).
-compile(nowarn_export_all). -compile(nowarn_export_all).
-define(CONF_DEFAULT, <<""" -define(CONF_DEFAULT, <<
gateway: { ""
stomp {} "\n"
} "gateway: {\n"
""">>). " stomp {}\n"
"}\n"
""
>>).
all() -> emqx_common_test_helpers:all(?MODULE). all() -> emqx_common_test_helpers:all(?MODULE).
@ -50,10 +53,12 @@ t_load_unload(_) ->
OldCnt = length(emqx_gateway_registry:list()), OldCnt = length(emqx_gateway_registry:list()),
RgOpts = [{cbkmod, ?MODULE}], RgOpts = [{cbkmod, ?MODULE}],
ok = emqx_gateway_registry:reg(test, RgOpts), 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}]), {error, already_existed} = emqx_gateway_registry:reg(test, [{cbkmod, ?MODULE}]),

View File

@ -23,60 +23,81 @@ assert_confs(Expected0, Effected) ->
Expected = maybe_unconvert_listeners(Expected0), Expected = maybe_unconvert_listeners(Expected0),
case do_assert_confs(root, Expected, Effected) of case do_assert_confs(root, Expected, Effected) of
false -> false ->
io:format(standard_error, "Expected config: ~p,\n" io:format(
"Effected config: ~p", standard_error,
[Expected, Effected]), "Expected config: ~p,\n"
"Effected config: ~p",
[Expected, Effected]
),
exit(conf_not_match); exit(conf_not_match);
true -> true ->
ok ok
end. end.
do_assert_confs(_Key, Expected, Effected) when is_map(Expected), do_assert_confs(_Key, Expected, Effected) when
is_map(Effected) -> is_map(Expected),
is_map(Effected)
->
Ks1 = maps:keys(Expected), Ks1 = maps:keys(Expected),
lists:all(fun(K) -> lists:all(
do_assert_confs(K, fun(K) ->
maps:get(K, Expected), do_assert_confs(
maps:get(K, Effected, undefined)) K,
end, Ks1); maps:get(K, Expected),
maps:get(K, Effected, undefined)
do_assert_confs(Key, Expected, Effected) when Key == <<"cacertfile">>; )
Key == <<"certfile">>; end,
Key == <<"keyfile">> -> Ks1
);
do_assert_confs(Key, Expected, Effected) when
Key == <<"cacertfile">>;
Key == <<"certfile">>;
Key == <<"keyfile">>
->
case Expected == Effected of case Expected == Effected of
true -> true; true ->
true;
false -> false ->
case file:read_file(Effected) of case file:read_file(Effected) of
{ok, Content} -> Expected == Content; {ok, Content} -> Expected == Content;
_ -> false _ -> false
end end
end; end;
do_assert_confs(Key, [Expected|More1], [Effected|More2]) -> do_assert_confs(Key, [Expected | More1], [Effected | More2]) ->
do_assert_confs(Key, Expected, Effected) do_assert_confs(Key, Expected, Effected) andalso
andalso do_assert_confs(Key, More1, More2); do_assert_confs(Key, More1, More2);
do_assert_confs(_Key, [], []) -> do_assert_confs(_Key, [], []) ->
true; true;
do_assert_confs(Key, Expected, Effected) -> do_assert_confs(Key, Expected, Effected) ->
Res = Expected =:= Effected, Res = Expected =:= Effected,
Res == false andalso Res == false andalso
ct:pal("Errors: ~p value not match, " ct:pal(
"expected: ~p, got: ~p~n", [Key, Expected, Effected]), "Errors: ~p value not match, "
"expected: ~p, got: ~p~n",
[Key, Expected, Effected]
),
Res. Res.
maybe_unconvert_listeners(Conf) when is_map(Conf) -> maybe_unconvert_listeners(Conf) when is_map(Conf) ->
case maps:take(<<"listeners">>, Conf) of case maps:take(<<"listeners">>, Conf) of
error -> Conf; error ->
Conf;
{Ls, Conf1} -> {Ls, Conf1} ->
Conf1#{<<"listeners">> => Conf1#{
emqx_gateway_conf:unconvert_listeners(Ls)} <<"listeners">> =>
emqx_gateway_conf:unconvert_listeners(Ls)
}
end; end;
maybe_unconvert_listeners(Conf) -> maybe_unconvert_listeners(Conf) ->
Conf. Conf.
assert_feilds_apperence(Ks, Map) -> assert_feilds_apperence(Ks, Map) ->
lists:foreach(fun(K) -> lists:foreach(
_ = maps:get(K, Map) fun(K) ->
end, Ks). _ = maps:get(K, Map)
end,
Ks
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% http %% http
@ -100,12 +121,15 @@ request(post = Mth, Path, Body) ->
do_request(Mth, Req) -> do_request(Mth, Req) ->
case httpc:request(Mth, Req, [], [{body_format, binary}]) of case httpc:request(Mth, Req, [], [{body_format, binary}]) of
{ok, {{_Vsn, Code, _Text}, _, Resp}} -> {ok, {{_Vsn, Code, _Text}, _, Resp}} ->
NResp = case Resp of NResp =
<<>> -> #{}; case Resp of
_ -> <<>> ->
emqx_map_lib:unsafe_atom_key_map( #{};
emqx_json:decode(Resp, [return_maps])) _ ->
end, emqx_map_lib:unsafe_atom_key_map(
emqx_json:decode(Resp, [return_maps])
)
end,
{Code, NResp}; {Code, NResp};
{error, Reason} -> {error, Reason} ->
error({failed_to_request, Reason}) error({failed_to_request, Reason})

File diff suppressed because it is too large Load Diff

View File

@ -28,38 +28,50 @@
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl").
-define(CONF_DEFAULT, <<" -define(CONF_DEFAULT, <<
gateway.lwm2m { "\n"
xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\" "gateway.lwm2m {\n"
lifetime_min = 100s " xml_dir = \"../../lib/emqx_gateway/src/lwm2m/lwm2m_xml\"\n"
lifetime_max = 86400s " lifetime_min = 100s\n"
qmode_time_window = 200 " lifetime_max = 86400s\n"
auto_observe = false " qmode_time_window = 200\n"
mountpoint = \"lwm2m/${username}\" " auto_observe = false\n"
update_msg_publish_condition = contains_object_list " mountpoint = \"lwm2m/${username}\"\n"
translators { " update_msg_publish_condition = contains_object_list\n"
command = {topic = \"/dn/#\", qos = 0} " translators {\n"
response = {topic = \"/up/resp\", qos = 0} " command = {topic = \"/dn/#\", qos = 0}\n"
notify = {topic = \"/up/notify\", qos = 0} " response = {topic = \"/up/resp\", qos = 0}\n"
register = {topic = \"/up/resp\", qos = 0} " notify = {topic = \"/up/notify\", qos = 0}\n"
update = {topic = \"/up/resp\", qos = 0} " register = {topic = \"/up/resp\", qos = 0}\n"
} " update = {topic = \"/up/resp\", qos = 0}\n"
listeners.udp.default { " }\n"
bind = 5783 " listeners.udp.default {\n"
} " bind = 5783\n"
} " }\n"
">>). "}\n"
>>).
-define(assertExists(Map, Key), -define(assertExists(Map, Key),
?assertNotEqual(maps:get(Key, Map, undefined), undefined)). ?assertNotEqual(maps:get(Key, Map, undefined), undefined)
).
-record(coap_content, {content_format, payload = <<>>}). -record(coap_content, {content_format, payload = <<>>}).
-import(emqx_lwm2m_SUITE, [ request/4, response/3, test_send_coap_response/7 -import(emqx_lwm2m_SUITE, [
, test_recv_coap_request/1, test_recv_coap_response/1 request/4,
, test_send_coap_request/6, test_recv_mqtt_response/1 response/3,
, std_register/5, reslove_uri/1, split_path/1, split_query/1 test_send_coap_response/7,
, join_path/2, sprintf/2]). 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 %% Setups
@ -76,7 +88,7 @@ init_per_suite(Config) ->
end_per_suite(Config) -> end_per_suite(Config) ->
timer:sleep(300), timer:sleep(300),
{ok, _} = emqx_conf:remove([<<"gateway">>,<<"lwm2m">>], #{}), {ok, _} = emqx_conf:remove([<<"gateway">>, <<"lwm2m">>], #{}),
emqx_mgmt_api_test_util:end_suite([emqx_conf]), emqx_mgmt_api_test_util:end_suite([emqx_conf]),
Config. Config.
@ -85,7 +97,7 @@ init_per_testcase(_AllTestCase, Config) ->
{ok, _} = application:ensure_all_started(emqx_gateway), {ok, _} = application:ensure_all_started(emqx_gateway),
{ok, ClientUdpSock} = gen_udp:open(0, [binary, {active, false}]), {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), {ok, _} = emqtt:connect(C),
timer:sleep(100), timer:sleep(100),
@ -104,22 +116,26 @@ t_lookup_cmd_read(Config) ->
UdpSock = ?config(sock, Config), UdpSock = ?config(sock, Config),
Epn = "urn:oma:lwm2m:oma:1", Epn = "urn:oma:lwm2m:oma:1",
MsgId1 = 15, 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), emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
timer:sleep(200), timer:sleep(200),
%% step 1, device register ... %% step 1, device register ...
test_send_coap_request( test_send_coap_request(
UdpSock, UdpSock,
post, post,
sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]), sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]),
#coap_content{ #coap_content{
content_format = <<"text/plain">>, content_format = <<"text/plain">>,
payload = <<"</lwm2m>;rt=\"oma.lwm2m\";ct=11543," payload = <<
"</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>">>}, "</lwm2m>;rt=\"oma.lwm2m\";ct=11543,"
[], "</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>"
MsgId1), >>
},
[],
MsgId1
),
#coap_message{method = Method1} = test_recv_coap_response(UdpSock), #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
?assertEqual({ok,created}, Method1), ?assertEqual({ok, created}, Method1),
timer:sleep(100), timer:sleep(100),
test_recv_mqtt_response(RespTopic), test_recv_mqtt_response(RespTopic),
@ -127,13 +143,14 @@ t_lookup_cmd_read(Config) ->
%% step2, send a READ command to device %% step2, send a READ command to device
CmdId = 206, CmdId = 206,
CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
Command = #{ Command = #{
<<"requestID">> => CmdId, <<"cacheID">> => CmdId, <<"requestID">> => CmdId,
<<"msgType">> => <<"read">>, <<"cacheID">> => CmdId,
<<"data">> => #{ <<"msgType">> => <<"read">>,
<<"path">> => <<"/3/0/0">> <<"data">> => #{
} <<"path">> => <<"/3/0/0">>
}, }
},
CommandJson = emqx_json:encode(Command), CommandJson = emqx_json:encode(Command),
?LOGT("CommandJson=~p", [CommandJson]), ?LOGT("CommandJson=~p", [CommandJson]),
test_mqtt_broker:publish(CommandTopic, CommandJson, 0), test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
@ -146,13 +163,14 @@ t_lookup_cmd_read(Config) ->
timer:sleep(50), timer:sleep(50),
test_send_coap_response( test_send_coap_response(
UdpSock, UdpSock,
"127.0.0.1", "127.0.0.1",
?PORT, ?PORT,
{ok, content}, {ok, content},
#coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>},
Request2, Request2,
true), true
),
timer:sleep(200), timer:sleep(200),
normal_received_request(Epn, <<"/3/0/0">>, <<"read">>). normal_received_request(Epn, <<"/3/0/0">>, <<"read">>).
@ -163,7 +181,7 @@ t_lookup_cmd_discover(Config) ->
MsgId1 = 15, MsgId1 = 15,
UdpSock = ?config(sock, Config), UdpSock = ?config(sock, Config),
ObjectList = <<"</1>, </2>, </3/0>, </4>, </5>">>, ObjectList = <<"</1>, </2>, </3/0>, </4>, </5>">>,
RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), RespTopic = list_to_binary("lwm2m/" ++ Epn ++ "/up/resp"),
emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
timer:sleep(200), timer:sleep(200),
@ -172,11 +190,14 @@ t_lookup_cmd_discover(Config) ->
%% step2, send a WRITE command to device %% step2, send a WRITE command to device
CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>,
CmdId = 307, CmdId = 307,
Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, Command = #{
<<"msgType">> => <<"discover">>, <<"requestID">> => CmdId,
<<"data">> => #{ <<"cacheID">> => CmdId,
<<"path">> => <<"/3/0/7">> <<"msgType">> => <<"discover">>,
} }, <<"data">> => #{
<<"path">> => <<"/3/0/7">>
}
},
CommandJson = emqx_json:encode(Command), CommandJson = emqx_json:encode(Command),
test_mqtt_broker:publish(CommandTopic, CommandJson, 0), test_mqtt_broker:publish(CommandTopic, CommandJson, 0),
@ -189,14 +210,17 @@ t_lookup_cmd_discover(Config) ->
PayloadDiscover = <<"</3/0/7>;dim=8;pmin=10;pmax=60;gt=50;lt=42.2,</3/0/8>">>, PayloadDiscover = <<"</3/0/7>;dim=8;pmin=10;pmax=60;gt=50;lt=42.2,</3/0/8>">>,
test_send_coap_response( test_send_coap_response(
UdpSock, UdpSock,
"127.0.0.1", "127.0.0.1",
?PORT, ?PORT,
{ok, content}, {ok, content},
#coap_content{content_format = <<"application/link-format">>, #coap_content{
payload = PayloadDiscover}, content_format = <<"application/link-format">>,
Request2, payload = PayloadDiscover
true), },
Request2,
true
),
timer:sleep(200), timer:sleep(200),
discover_received_request(Epn, <<"/3/0/7">>, <<"discover">>). discover_received_request(Epn, <<"/3/0/7">>, <<"discover">>).
@ -204,21 +228,26 @@ t_read(Config) ->
UdpSock = ?config(sock, Config), UdpSock = ?config(sock, Config),
Epn = "urn:oma:lwm2m:oma:3", Epn = "urn:oma:lwm2m:oma:3",
MsgId1 = 15, 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), emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
timer:sleep(200), timer:sleep(200),
%% step 1, device register ... %% step 1, device register ...
test_send_coap_request( test_send_coap_request(
UdpSock, UdpSock,
post, post,
sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]), sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]),
#coap_content{content_format = <<"text/plain">>, #coap_content{
payload = <<"</lwm2m>;rt=\"oma.lwm2m\";ct=11543," content_format = <<"text/plain">>,
"</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>">>}, payload = <<
[], "</lwm2m>;rt=\"oma.lwm2m\";ct=11543,"
MsgId1), "</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>"
>>
},
[],
MsgId1
),
#coap_message{method = Method1} = test_recv_coap_response(UdpSock), #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
?assertEqual({ok,created}, Method1), ?assertEqual({ok, created}, Method1),
timer:sleep(100), timer:sleep(100),
test_recv_mqtt_response(RespTopic), test_recv_mqtt_response(RespTopic),
@ -231,26 +260,30 @@ t_read(Config) ->
?assertEqual(get, Method), ?assertEqual(get, Method),
?assertEqual([<<"lwm2m">>, <<"3">>, <<"0">>, <<"0">>], maps:get(uri_path, Opts)). ?assertEqual([<<"lwm2m">>, <<"3">>, <<"0">>, <<"0">>], maps:get(uri_path, Opts)).
t_write(Config) -> t_write(Config) ->
UdpSock = ?config(sock, Config), UdpSock = ?config(sock, Config),
Epn = "urn:oma:lwm2m:oma:4", Epn = "urn:oma:lwm2m:oma:4",
MsgId1 = 15, 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), emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
timer:sleep(200), timer:sleep(200),
%% step 1, device register ... %% step 1, device register ...
test_send_coap_request( test_send_coap_request(
UdpSock, UdpSock,
post, post,
sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]), sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]),
#coap_content{content_format = <<"text/plain">>, #coap_content{
payload = <<"</lwm2m>;rt=\"oma.lwm2m\";ct=11543," content_format = <<"text/plain">>,
"</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>">>}, payload = <<
[], "</lwm2m>;rt=\"oma.lwm2m\";ct=11543,"
MsgId1), "</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>"
>>
},
[],
MsgId1
),
#coap_message{method = Method1} = test_recv_coap_response(UdpSock), #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
?assertEqual({ok,created}, Method1), ?assertEqual({ok, created}, Method1),
timer:sleep(100), timer:sleep(100),
test_recv_mqtt_response(RespTopic), test_recv_mqtt_response(RespTopic),
@ -264,27 +297,30 @@ t_write(Config) ->
?assertEqual([<<"lwm2m">>, <<"3">>, <<"0">>, <<"13">>], maps:get(uri_path, Opts)), ?assertEqual([<<"lwm2m">>, <<"3">>, <<"0">>, <<"13">>], maps:get(uri_path, Opts)),
?assertEqual(<<"application/vnd.oma.lwm2m+tlv">>, maps:get(content_format, Opts)). ?assertEqual(<<"application/vnd.oma.lwm2m+tlv">>, maps:get(content_format, Opts)).
t_observe(Config) -> t_observe(Config) ->
UdpSock = ?config(sock, Config), UdpSock = ?config(sock, Config),
Epn = "urn:oma:lwm2m:oma:5", Epn = "urn:oma:lwm2m:oma:5",
MsgId1 = 15, 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), emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0),
timer:sleep(200), timer:sleep(200),
%% step 1, device register ... %% step 1, device register ...
test_send_coap_request( test_send_coap_request(
UdpSock, UdpSock,
post, post,
sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]), sprintf("coap://127.0.0.1:~b/rd?ep=~ts&lt=600&lwm2m=1", [?PORT, Epn]),
#coap_content{content_format = <<"text/plain">>, #coap_content{
payload = <<"</lwm2m>;rt=\"oma.lwm2m\";ct=11543," content_format = <<"text/plain">>,
"</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>">>}, payload = <<
[], "</lwm2m>;rt=\"oma.lwm2m\";ct=11543,"
MsgId1), "</lwm2m/1/0>,</lwm2m/2/0>,</lwm2m/3/0>"
>>
},
[],
MsgId1
),
#coap_message{method = Method1} = test_recv_coap_response(UdpSock), #coap_message{method = Method1} = test_recv_coap_response(UdpSock),
?assertEqual({ok,created}, Method1), ?assertEqual({ok, created}, Method1),
timer:sleep(100), timer:sleep(100),
test_recv_mqtt_response(RespTopic), test_recv_mqtt_response(RespTopic),
@ -318,11 +354,13 @@ call_send_api(ClientId, Cmd, Query) ->
no_received_request(ClientId, Path, Action) -> no_received_request(ClientId, Path, Action) ->
Response = call_lookup_api(ClientId, Path, Action), Response = call_lookup_api(ClientId, Path, Action),
NotReceived = #{<<"clientid">> => list_to_binary(ClientId), NotReceived = #{
<<"action">> => Action, <<"clientid">> => list_to_binary(ClientId),
<<"code">> => <<"6.01">>, <<"action">> => Action,
<<"codeMsg">> => <<"reply_not_received">>, <<"code">> => <<"6.01">>,
<<"path">> => Path}, <<"codeMsg">> => <<"reply_not_received">>,
<<"path">> => Path
},
?assertEqual(NotReceived, emqx_json:decode(Response, [return_maps])). ?assertEqual(NotReceived, emqx_json:decode(Response, [return_maps])).
normal_received_request(ClientId, Path, Action) -> normal_received_request(ClientId, Path, Action) ->
Response = call_lookup_api(ClientId, Path, Action), Response = call_lookup_api(ClientId, Path, Action),

View File

@ -96,15 +96,15 @@ t_puback(_) ->
?assertEqual(PubAck, parse(serialize_pkt(PubAck))). ?assertEqual(PubAck, parse(serialize_pkt(PubAck))).
t_pubrec(_) -> 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))). ?assertEqual(PubRec, parse(serialize_pkt(PubRec))).
t_pubrel(_) -> 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))). ?assertEqual(PubRel, parse(serialize_pkt(PubRel))).
t_pubcomp(_) -> 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))). ?assertEqual(PubComp, parse(serialize_pkt(PubComp))).
t_subscribe(_) -> t_subscribe(_) ->
@ -165,10 +165,12 @@ random_test_body() ->
Data = generate_random_binary(), Data = generate_random_binary(),
case catch parse(Data) of case catch parse(Data) of
Msg when is_record(Msg, mqtt_sn_message) -> ok; Msg when is_record(Msg, mqtt_sn_message) -> ok;
{'EXIT', {Err, _Stack}} {'EXIT', {Err, _Stack}} when
when Err =:= unkown_message_type; Err =:= unkown_message_type;
Err =:= malformed_message_len; Err =:= malformed_message_len;
Err =:= malformed_message_flags -> ok Err =:= malformed_message_flags
->
ok
end. end.
generate_random_binary() -> generate_random_binary() ->
@ -180,4 +182,4 @@ gen_next(0, Acc) ->
Acc; Acc;
gen_next(N, Acc) -> gen_next(N, Acc) ->
Byte = rand:uniform(256) - 1, Byte = rand:uniform(256) - 1,
gen_next(N-1, <<Acc/binary, Byte:8>>). gen_next(N - 1, <<Acc/binary, Byte:8>>).

File diff suppressed because it is too large Load Diff

View File

@ -23,8 +23,10 @@
-define(REGISTRY, emqx_sn_registry). -define(REGISTRY, emqx_sn_registry).
-define(MAX_PREDEF_ID, 2). -define(MAX_PREDEF_ID, 2).
-define(PREDEF_TOPICS, [#{id => 1, topic => <<"/predefined/topic/name/hello">>}, -define(PREDEF_TOPICS, [
#{id => 2, topic => <<"/predefined/topic/name/nice">>}]). #{id => 1, topic => <<"/predefined/topic/name/hello">>},
#{id => 2, topic => <<"/predefined/topic/name/nice">>}
]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Setups %% Setups
@ -58,60 +60,64 @@ end_per_testcase(_TestCase, Config) ->
t_register(Config) -> t_register(Config) ->
Reg = proplists:get_value(reg, Config), Reg = proplists:get_value(reg, Config),
?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)), ?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 + 2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic2">>)),
?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+1)), ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 1)),
?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)), ?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 + 1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)), ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)),
emqx_sn_registry:unregister_topic(Reg, <<"ClientId">>), 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 + 1)),
?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)), ?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">>, <<"Topic1">>)),
?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)). ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)).
t_register_case2(Config) -> t_register_case2(Config) ->
Reg = proplists:get_value(reg, Config), Reg = proplists:get_value(reg, Config),
?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)), ?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 + 2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic2">>)),
?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)), ?assertEqual(?MAX_PREDEF_ID + 1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"Topic1">>)),
?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+1)), ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID + 1)),
?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)), ?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 + 1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic1">>)),
?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)), ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)),
?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic3">>)), ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic3">>)),
?REGISTRY:unregister_topic(Reg, <<"ClientId">>), ?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 + 1)),
?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)), ?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">>, <<"Topic1">>)),
?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)). ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, <<"Topic2">>)).
t_reach_maximum(Config) -> t_reach_maximum(Config) ->
Reg = proplists:get_value(reg, 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">>)), ?assertEqual({error, too_large}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicABC">>)),
Topic1 = iolist_to_binary(io_lib:format("Topic~p", [?MAX_PREDEF_ID+1])), 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])), 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 + 1, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic1)),
?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic2)), ?assertEqual(?MAX_PREDEF_ID + 2, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic2)),
?REGISTRY:unregister_topic(Reg, <<"ClientId">>), ?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 + 1)),
?assertEqual(undefined, ?REGISTRY:lookup_topic(Reg, <<"ClientId">>, ?MAX_PREDEF_ID+2)), ?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">>, Topic1)),
?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic2)). ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Reg, <<"ClientId">>, Topic2)).
t_register_case4(Config) -> t_register_case4(Config) ->
Reg = proplists:get_value(reg, Config), Reg = proplists:get_value(reg, Config),
?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicA">>)), ?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 + 2, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicB">>)),
?assertEqual(?MAX_PREDEF_ID+3, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicC">>)), ?assertEqual(?MAX_PREDEF_ID + 3, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"TopicC">>)),
?REGISTRY:unregister_topic(Reg, <<"ClientId">>), ?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) -> t_deny_wildcard_topic(Config) ->
Reg = proplists:get_value(reg, Config), Reg = proplists:get_value(reg, Config),
?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/TopicA/#">>)), ?assertEqual(
?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/+/TopicB">>)). {error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/TopicA/#">>)
),
?assertEqual(
{error, wildcard_topic}, ?REGISTRY:register_topic(Reg, <<"ClientId">>, <<"/+/TopicB">>)
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Helper funcs %% Helper funcs
@ -122,4 +128,4 @@ register_a_lot(Max, Max, _Reg) ->
register_a_lot(N, Max, Reg) when N < Max -> register_a_lot(N, Max, Reg) when N < Max ->
Topic = iolist_to_binary(["Topic", integer_to_list(N)]), Topic = iolist_to_binary(["Topic", integer_to_list(N)]),
?assertEqual(N, ?REGISTRY:register_topic(Reg, <<"ClientId">>, Topic)), ?assertEqual(N, ?REGISTRY:register_topic(Reg, <<"ClientId">>, Topic)),
register_a_lot(N+1, Max, Reg). register_a_lot(N + 1, Max, Reg).

File diff suppressed because it is too large Load Diff

View File

@ -41,19 +41,24 @@ t_check_1(_) ->
t_check_2(_) -> t_check_2(_) ->
HrtBt = emqx_stomp_heartbeat:init({1, 0}), HrtBt = emqx_stomp_heartbeat:init({1, 0}),
#{incoming := _} = lists:foldl(fun(I, Acc) -> #{incoming := _} = lists:foldl(
{ok, NAcc} = emqx_stomp_heartbeat:check(incoming, I, Acc), fun(I, Acc) ->
NAcc {ok, NAcc} = emqx_stomp_heartbeat:check(incoming, I, Acc),
end, HrtBt, lists:seq(1,1000)), NAcc
end,
HrtBt,
lists:seq(1, 1000)
),
ok. ok.
t_info(_) -> t_info(_) ->
HrtBt = emqx_stomp_heartbeat:init({100, 100}), HrtBt = emqx_stomp_heartbeat:init({100, 100}),
#{incoming := _, #{
outgoing := _} = emqx_stomp_heartbeat:info(HrtBt). incoming := _,
outgoing := _
} = emqx_stomp_heartbeat:info(HrtBt).
t_interval(_) -> t_interval(_) ->
HrtBt = emqx_stomp_heartbeat:init({1, 0}), HrtBt = emqx_stomp_heartbeat:init({1, 0}),
1 = emqx_stomp_heartbeat:interval(incoming, HrtBt), 1 = emqx_stomp_heartbeat:interval(incoming, HrtBt),
undefined = emqx_stomp_heartbeat:interval(outgoing, HrtBt). undefined = emqx_stomp_heartbeat:interval(outgoing, HrtBt).

View File

@ -30,8 +30,18 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
all() -> 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) -> init_per_suite(Config) ->
Config. Config.
@ -44,9 +54,9 @@ end_per_suite(Config) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
case01(_Config) -> case01(_Config) ->
Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, Data =
16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, <<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#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65>>, 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), R = emqx_lwm2m_tlv:parse(Data),
Exp = [ Exp = [
#{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>} #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>}
@ -58,28 +68,32 @@ case01(_Config) ->
case02(_Config) -> case02(_Config) ->
Data = <<16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05>>, Data = <<16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05>>,
R = emqx_lwm2m_tlv:parse(Data), R = emqx_lwm2m_tlv:parse(Data),
Exp = [ #{tlv_multiple_resource => 16#06, Exp = [
value => [ #{tlv_resource_instance => 16#00, value => <<1>>}, #{
#{tlv_resource_instance => 16#01, value => <<5>>}]} tlv_multiple_resource => 16#06,
], value => [
#{tlv_resource_instance => 16#00, value => <<1>>},
#{tlv_resource_instance => 16#01, value => <<5>>}
]
}
],
?assertEqual(Exp, R), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).
case03(_Config) -> case03(_Config) ->
Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, Data =
16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, <<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#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 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#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 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#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 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#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33>>,
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), R = emqx_lwm2m_tlv:parse(Data),
Exp = [ #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>}, Exp = [
#{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>}, #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
#{tlv_resource_with_value => 16#02, value => <<"345000123">>} #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
], #{tlv_resource_with_value => 16#02, value => <<"345000123">>}
],
?assertEqual(Exp, R), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).
@ -87,50 +101,69 @@ case03(_Config) ->
case03_0(_Config) -> case03_0(_Config) ->
Data = <<16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 16#01, 16#36, 16#01>>, 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), R = emqx_lwm2m_tlv:parse(Data),
Exp = [ #{tlv_multiple_resource => 16#02, Exp = [
value => [ #{tlv_resource_instance => 16#7F, value => <<16#07>>}, #{
#{tlv_resource_instance => 16#0136, value => <<16#01>>} 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), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).
case04(_Config) -> case04(_Config) ->
% 6.4.3.1 Single Object Instance Request Example % 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, Data =
16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, <<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#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 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#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 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#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 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#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 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#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 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#30, 16#31, 16#32, 16#33, 16#C3, 16#03, 16#31, 16#2E, 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#30, 16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 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#05, 16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8, 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#42, 16#01, 16#13, 16#88, 16#87, 16#08, 16#41, 16#00, 16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30, 16#C1, 16#10, 16#55>>,
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), R = emqx_lwm2m_tlv:parse(Data),
Exp = [ #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>}, Exp = [
#{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>}, #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
#{tlv_resource_with_value => 16#02, value => <<"345000123">>}, #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
#{tlv_resource_with_value => 16#03, value => <<"1.0">>}, #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
#{tlv_multiple_resource => 16#06, value => [#{tlv_resource_instance => 16#00, value => <<1>>}, #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
#{tlv_resource_instance => 16#01, value => <<5>>}]}, #{
#{tlv_multiple_resource => 16#07, value => [#{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>}, tlv_multiple_resource => 16#06,
#{tlv_resource_instance => 16#01, value => <<16#1388:16>>}]}, value => [
#{tlv_multiple_resource => 16#08, value => [#{tlv_resource_instance => 16#00, value => <<16#7d>>}, #{tlv_resource_instance => 16#00, value => <<1>>},
#{tlv_resource_instance => 16#01, value => <<16#0384:16>>}]}, #{tlv_resource_instance => 16#01, value => <<5>>}
#{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_multiple_resource => 16#07,
#{tlv_resource_with_value => 16#0E, value => <<"+02:00">>}, value => [
#{tlv_resource_with_value => 16#10, value => <<"U">>} #{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), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).
@ -138,43 +171,59 @@ case04(_Config) ->
case05(_Config) -> case05(_Config) ->
% 6.4.3.2 Multiple Object Instance Request Examples % 6.4.3.2 Multiple Object Instance Request Examples
% A) Request on Single-Instance Object % A) Request on Single-Instance Object
Data = <<16#08, 16#00, 16#79, 16#C8, 16#00, 16#14, 16#4F, 16#70, Data =
16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, <<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#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 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#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 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#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 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#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 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#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 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#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33, 16#C3, 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#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00, 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#01, 16#41, 16#01, 16#05, 16#88, 16#07, 16#08, 16#42, 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#00, 16#0E, 16#D8, 16#42, 16#01, 16#13, 16#88, 16#87, 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#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1, 16#10, 16#55>>,
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), R = emqx_lwm2m_tlv:parse(Data),
Exp = [ Exp = [
#{tlv_object_instance => 16#00, value => [ #{
#{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>}, tlv_object_instance => 16#00,
#{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>}, value => [
#{tlv_resource_with_value => 16#02, value => <<"345000123">>}, #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
#{tlv_resource_with_value => 16#03, value => <<"1.0">>}, #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
#{tlv_multiple_resource => 16#06, value => [#{tlv_resource_instance => 16#00, value => <<1>>}, #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
#{tlv_resource_instance => 16#01, value => <<5>>}]}, #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
#{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#06,
]}, value => [
#{tlv_multiple_resource => 16#08, value => [#{tlv_resource_instance => 16#00, value => <<16#7d>>}, #{tlv_resource_instance => 16#00, value => <<1>>},
#{tlv_resource_instance => 16#01, value => <<16#0384:16>>}]}, #{tlv_resource_instance => 16#01, value => <<5>>}
#{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_multiple_resource => 16#07,
#{tlv_resource_with_value => 16#0E, value => <<"+02:00">>}, value => [
#{tlv_resource_with_value => 16#10, value => <<"U">>} #{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), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
@ -183,25 +232,41 @@ case05(_Config) ->
case06(_Config) -> case06(_Config) ->
% 6.4.3.2 Multiple Object Instance Request Examples % 6.4.3.2 Multiple Object Instance Request Examples
% B) Request on Multiple-Instances Object having 2 instances % 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, Data =
16#00, 16#83, 16#02, 16#41, 16#7F, 16#07, 16#C1, 16#03, <<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#7F, 16#08, 16#02, 16#12, 16#C1, 16#00, 16#03, 16#C1, 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#01, 16#00, 16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 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#01, 16#36, 16#01, 16#C1, 16#03, 16#7F>>, 16#7F>>,
R = emqx_lwm2m_tlv:parse(Data), R = emqx_lwm2m_tlv:parse(Data),
Exp = [#{tlv_object_instance => 16#00, value => [#{tlv_resource_with_value => 16#00, value => <<16#01>>}, Exp = [
#{tlv_resource_with_value => 16#01, value => <<16#00>>}, #{
#{tlv_multiple_resource => 16#02, tlv_object_instance => 16#00,
value => [#{tlv_resource_instance => 16#7F, value => <<16#07>>}]}, value => [
#{tlv_resource_with_value => 16#03, value => <<16#7F>>} #{tlv_resource_with_value => 16#00, value => <<16#01>>},
]}, #{tlv_resource_with_value => 16#01, value => <<16#00>>},
#{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,
#{tlv_multiple_resource => 16#02, value => [#{tlv_resource_instance => 16#7F, value => <<16#07>>}]
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>>}
#{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), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).
@ -209,16 +274,21 @@ case06(_Config) ->
case07(_Config) -> case07(_Config) ->
% 6.4.3.2 Multiple Object Instance Request Examples % 6.4.3.2 Multiple Object Instance Request Examples
% C) Request on Multiple-Instances Object having 1 instance only % 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, Data =
16#00, 16#01, 16#51, 16#80, 16#C1, 16#06, 16#01, 16#C1, <<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#07, 16#55>>, 16#06, 16#01, 16#C1, 16#07, 16#55>>,
R = emqx_lwm2m_tlv:parse(Data), R = emqx_lwm2m_tlv:parse(Data),
Exp = [#{tlv_object_instance => 16#00, Exp = [
value => [#{tlv_resource_with_value => 16#00, value => <<16#01>>}, #{
#{tlv_resource_with_value => 16#01, value => <<86400:32>>}, tlv_object_instance => 16#00,
#{tlv_resource_with_value => 16#06, value => <<16#01>>}, value => [
#{tlv_resource_with_value => 16#07, value => <<$U>>}]} #{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), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).
@ -226,17 +296,22 @@ case07(_Config) ->
case08(_Config) -> case08(_Config) ->
% 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource % 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 % 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, Data =
16#00, 16#44, 16#01, 16#00, 16#42, 16#00, 16#01, 16#C8, <<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#01, 16#0D, 16#38, 16#36, 16#31, 16#33, 16#38, 16#30, 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#30, 16#37, 16#35, 16#35, 16#35, 16#30, 16#30, 16#C4, 16#37, 16#35, 16#35, 16#35, 16#30, 16#30, 16#C4, 16#02, 16#12, 16#34, 16#56, 16#78>>,
16#02, 16#12, 16#34, 16#56, 16#78>>,
R = emqx_lwm2m_tlv:parse(Data), 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>>}, Exp = [
#{tlv_resource_instance => 16#01, value => <<16#00, 16#42, 16#00, 16#01>>}]}, #{
#{tlv_resource_with_value => 16#01, value => <<"8613800755500">>}, tlv_multiple_resource => 16#00,
#{tlv_resource_with_value => 16#02, value => <<16#12345678:32>>} 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), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).
@ -244,26 +319,33 @@ case08(_Config) ->
case09(_Config) -> case09(_Config) ->
% 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource % 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 % 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, Data =
16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, <<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#31, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#65, 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#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E, 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#32, 16#33, 16#34, 16#C4, 16#02, 16#00, 16#43, 16#00, 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#00, 16#08, 16#01, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 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#79, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 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#20, 16#32, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#32, 16#33, 16#35, 16#C4, 16#02, 16#FF, 16#FF, 16#FF, 16#FF>>,
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), R = emqx_lwm2m_tlv:parse(Data),
Exp = [#{tlv_object_instance => 16#00, value => [#{tlv_resource_with_value => 16#00, value => <<"myService 1">>}, Exp = [
#{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#00,
#{tlv_object_instance => 16#01, value => [#{tlv_resource_with_value => 16#00, value => <<"myService 2">>}, value => [
#{tlv_resource_with_value => 16#01, value => <<"Internet.15.235">>}, #{tlv_resource_with_value => 16#00, value => <<"myService 1">>},
#{tlv_resource_with_value => 16#02, value => <<16#FF, 16#FF, 16#FF, 16#FF>>} #{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), ?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp), EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data). ?assertEqual(EncodedBinary, Data).

View File

@ -31,20 +31,28 @@
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
'ADVERTISE'() -> 'ADVERTISE'() ->
?LET({GwId, Duration}, {gateway_id(), duration()}, ?LET(
?SN_ADVERTISE_MSG(GwId, Duration)). {GwId, Duration},
{gateway_id(), duration()},
?SN_ADVERTISE_MSG(GwId, Duration)
).
'SEARCHGW'() -> 'SEARCHGW'() ->
?LET(Radius, radius(), ?SN_SEARCHGW_MSG(Radius)). ?LET(Radius, radius(), ?SN_SEARCHGW_MSG(Radius)).
'GWINFO'() -> 'GWINFO'() ->
?LET({GwId, GwAddr}, {gateway_id(), gateway_addr()}, ?LET(
?SN_GWINFO_MSG(GwId, GwAddr)). {GwId, GwAddr},
{gateway_id(), gateway_addr()},
?SN_GWINFO_MSG(GwId, GwAddr)
).
'CONNECT'() -> 'CONNECT'() ->
?LET({Flags, ProtocolId, Duration, ClientId}, ?LET(
{conn_flags(), protocol_id(), duration(), clientid()}, {Flags, ProtocolId, Duration, ClientId},
?SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId)). {conn_flags(), protocol_id(), duration(), clientid()},
?SN_CONNECT_MSG(Flags, ProtocolId, Duration, ClientId)
).
'CONNACK'() -> 'CONNACK'() ->
?LET(Rc, return_code(), ?SN_CONNACK_MSG(Rc)). ?LET(Rc, return_code(), ?SN_CONNACK_MSG(Rc)).
@ -53,8 +61,11 @@
?SN_WILLTOPICREQ_MSG(). ?SN_WILLTOPICREQ_MSG().
'WILLTOPIC'() -> 'WILLTOPIC'() ->
?LET({Flags, Topic}, {will_topic_flags(), topic()}, ?LET(
?SN_WILLTOPIC_MSG(Flags, Topic)). {Flags, Topic},
{will_topic_flags(), topic()},
?SN_WILLTOPIC_MSG(Flags, Topic)
).
'WILLTOPCI_EMPTY'() -> 'WILLTOPCI_EMPTY'() ->
?SN_WILLTOPIC_EMPTY_MSG. ?SN_WILLTOPIC_EMPTY_MSG.
@ -66,48 +77,67 @@
?LET(Payload, binary(), ?SN_WILLMSG_MSG(Payload)). ?LET(Payload, binary(), ?SN_WILLMSG_MSG(Payload)).
'REGISTER'() -> 'REGISTER'() ->
?LET({MsgId, TopicName}, ?LET(
{message_id(), topic()}, {MsgId, TopicName},
?SN_REGISTER_MSG(16#0000, MsgId, TopicName)). {message_id(), topic()},
?SN_REGISTER_MSG(16#0000, MsgId, TopicName)
).
'REGACK'() -> 'REGACK'() ->
?LET({TopicId, MsgId, Rc}, ?LET(
{topic_id(), message_id(), return_code()}, {TopicId, MsgId, Rc},
?SN_REGACK_MSG(TopicId, MsgId, Rc)). {topic_id(), message_id(), return_code()},
?SN_REGACK_MSG(TopicId, MsgId, Rc)
).
'PUBLISH'() -> 'PUBLISH'() ->
?LET({Flags, MsgId, Data}, ?LET(
{publish_flags(), message_id(), binary()}, {Flags, MsgId, Data},
?SN_PUBLISH_MSG(Flags, pub_topic_by_type(Flags), MsgId, Data)). {publish_flags(), message_id(), binary()},
?SN_PUBLISH_MSG(Flags, pub_topic_by_type(Flags), MsgId, Data)
).
'PUBACK'() -> 'PUBACK'() ->
?LET({TopicId, MsgId, Rc}, ?LET(
{topic_id(), message_id(), return_code()}, {TopicId, MsgId, Rc},
?SN_PUBACK_MSG(TopicId, MsgId, Rc)). {topic_id(), message_id(), return_code()},
?SN_PUBACK_MSG(TopicId, MsgId, Rc)
).
'PUBCOMP_REC_REL'() -> 'PUBCOMP_REC_REL'() ->
?LET({Type, MsgId}, ?LET(
{oneof([?SN_PUBREC, ?SN_PUBREL, ?SN_PUBCOMP]), message_id()}, {Type, MsgId},
?SN_PUBREC_MSG(Type, MsgId)). {oneof([?SN_PUBREC, ?SN_PUBREL, ?SN_PUBCOMP]), message_id()},
?SN_PUBREC_MSG(Type, MsgId)
).
'SUBSCRIBE'() -> 'SUBSCRIBE'() ->
?LET({Flags, MsgId}, ?LET(
{subscribe_flags(), message_id()}, {Flags, MsgId},
?SN_SUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))). {subscribe_flags(), message_id()},
?SN_SUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))
).
'SUBACK'() -> 'SUBACK'() ->
?LET({Flags, TopicId, MsgId, Rc}, ?LET(
{suback_flags(), topic_id(), message_id(), return_code()}, {Flags, TopicId, MsgId, Rc},
?SN_SUBACK_MSG(Flags, TopicId, MsgId, Rc)). {suback_flags(), topic_id(), message_id(), return_code()},
?SN_SUBACK_MSG(Flags, TopicId, MsgId, Rc)
).
'UNSUBSCRIBE'() -> 'UNSUBSCRIBE'() ->
?LET({Flags, MsgId}, ?LET(
{unsubscribe_flags(), message_id()}, {Flags, MsgId},
?SN_UNSUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))). {unsubscribe_flags(), message_id()},
?SN_UNSUBSCRIBE_MSG(Flags, MsgId, topic_by_type(Flags))
).
'UNSUBACK'() -> 'UNSUBACK'() ->
?LET(MsgId, message_id(), ?LET(
?SN_UNSUBACK_MSG(MsgId)). MsgId,
message_id(),
?SN_UNSUBACK_MSG(MsgId)
).
'PINGREQ'() -> 'PINGREQ'() ->
?LET(ClientId, clientid(), ?SN_PINGREQ_MSG(ClientId)). ?LET(ClientId, clientid(), ?SN_PINGREQ_MSG(ClientId)).
@ -119,9 +149,11 @@
?LET(Duration, oneof([duration(), undefined]), ?SN_DISCONNECT_MSG(Duration)). ?LET(Duration, oneof([duration(), undefined]), ?SN_DISCONNECT_MSG(Duration)).
'WILLTOPICUPD'() -> 'WILLTOPICUPD'() ->
?LET({Flags, Topic}, ?LET(
{willtopic_upd_flags(), topic()}, {Flags, Topic},
?SN_WILLTOPICUPD_MSG(Flags, Topic)). {willtopic_upd_flags(), topic()},
?SN_WILLTOPICUPD_MSG(Flags, Topic)
).
'WILLTOPICRESP'() -> 'WILLTOPICRESP'() ->
?LET(Rc, return_code(), ?SN_WILLTOPICRESP_MSG(Rc)). ?LET(Rc, return_code(), ?SN_WILLTOPICRESP_MSG(Rc)).
@ -148,45 +180,69 @@ radius() ->
range(0, 16#ff). range(0, 16#ff).
gateway_addr() -> gateway_addr() ->
?LET(L, oneof([0, range(2, 16#ff)]), ?LET(
begin L,
oneof([0, range(2, 16#ff)]),
begin
Addr = list_to_binary([rand:uniform(256) - 1 || _ <- lists:seq(1, L)]), Addr = list_to_binary([rand:uniform(256) - 1 || _ <- lists:seq(1, L)]),
<<(byte_size(Addr)):8, Addr/binary>> <<(byte_size(Addr)):8, Addr/binary>>
end). end
).
mqtt_sn_flags() -> mqtt_sn_flags() ->
?LET({Dup, Qos, Retain, Will, CleanStart, IdType}, ?LET(
{boolean(), mqtt_sn_qos(), boolean(), boolean(), boolean(), topic_id_type()}, {Dup, Qos, Retain, Will, CleanStart, IdType},
#mqtt_sn_flags{dup = Dup, qos = Qos, retain = Retain, will = Will, {boolean(), mqtt_sn_qos(), boolean(), boolean(), boolean(), topic_id_type()},
clean_start = CleanStart, topic_id_type = IdType}). #mqtt_sn_flags{
dup = Dup,
qos = Qos,
retain = Retain,
will = Will,
clean_start = CleanStart,
topic_id_type = IdType
}
).
conn_flags() -> conn_flags() ->
?LET({Will, CleanStart}, {boolean(), boolean()}, ?LET(
#mqtt_sn_flags{will = Will,clean_start = CleanStart}). {Will, CleanStart},
{boolean(), boolean()},
#mqtt_sn_flags{will = Will, clean_start = CleanStart}
).
will_topic_flags() -> will_topic_flags() ->
?LET({Qos, Retain}, {mqtt_sn_qos(), boolean()}, ?LET(
#mqtt_sn_flags{qos = Qos, retain = Retain}). {Qos, Retain},
{mqtt_sn_qos(), boolean()},
#mqtt_sn_flags{qos = Qos, retain = Retain}
).
publish_flags() -> publish_flags() ->
?LET({Dup, Qos, Retain, IdType}, ?LET(
{boolean(), publish_qos(), boolean(), pub_topic_id_type()}, {Dup, Qos, Retain, IdType},
#mqtt_sn_flags{dup = Dup, qos = Qos, retain = Retain, topic_id_type = IdType}). {boolean(), publish_qos(), boolean(), pub_topic_id_type()},
#mqtt_sn_flags{dup = Dup, qos = Qos, retain = Retain, topic_id_type = IdType}
).
subscribe_flags() -> subscribe_flags() ->
?LET({Dup, Qos, IdType}, ?LET(
{boolean(), publish_qos(), topic_id_type()}, {Dup, Qos, IdType},
#mqtt_sn_flags{dup = Dup, qos = Qos, topic_id_type = IdType}). {boolean(), publish_qos(), topic_id_type()},
#mqtt_sn_flags{dup = Dup, qos = Qos, topic_id_type = IdType}
).
suback_flags() -> suback_flags() ->
?LET(Qos, mqtt_sn_qos(), #mqtt_sn_flags{qos = Qos}). ?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}). ?LET(IdType, topic_id_type(), #mqtt_sn_flags{topic_id_type = IdType}).
willtopic_upd_flags() -> willtopic_upd_flags() ->
?LET({Qos, Retain}, {mqtt_sn_qos(), boolean()}, ?LET(
#mqtt_sn_flags{qos = Qos, retain = Retain}). {Qos, Retain},
{mqtt_sn_qos(), boolean()},
#mqtt_sn_flags{qos = Qos, retain = Retain}
).
mqtt_sn_qos() -> mqtt_sn_qos() ->
oneof([0, 1, 2]). oneof([0, 1, 2]).
@ -209,10 +265,13 @@ protocol_id() ->
%% The ClientId field has a variable length and contains %% The ClientId field has a variable length and contains
%% a 1-23 character long string %% a 1-23 character long string
clientid() -> clientid() ->
?LET(L, range(1, 23), ?LET(
L,
range(1, 23),
begin begin
list_to_binary([rand_09_az_AZ() || _ <- lists:seq(1, L)]) list_to_binary([rand_09_az_AZ() || _ <- lists:seq(1, L)])
end). end
).
return_code() -> return_code() ->
range(0, 16#ff). range(0, 16#ff).
@ -231,14 +290,14 @@ message_id() ->
range(0, 16#ffff). range(0, 16#ffff).
topic_by_type(Flags) -> topic_by_type(Flags) ->
case Flags#mqtt_sn_flags.topic_id_type of case Flags#mqtt_sn_flags.topic_id_type of
0 -> topic(); 0 -> topic();
1 -> range(0, 16#ffff); 1 -> range(0, 16#ffff);
2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()]) 2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()])
end. end.
pub_topic_by_type(Flags) -> pub_topic_by_type(Flags) ->
case Flags#mqtt_sn_flags.topic_id_type of case Flags#mqtt_sn_flags.topic_id_type of
2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()]); 2 -> list_to_binary([rand_09_az_AZ(), rand_09_az_AZ()]);
_ -> range(0, 16#ffff) _ -> range(0, 16#ffff)
end. end.

View File

@ -22,10 +22,14 @@
-compile({no_auto_import, [register/1]}). -compile({no_auto_import, [register/1]}).
-define(ALL(Vars, Types, Exprs), -define(ALL(Vars, Types, Exprs),
?SETUP(fun() -> ?SETUP(
fun() ->
State = do_setup(), State = do_setup(),
fun() -> do_teardown(State) end fun() -> do_teardown(State) end
end, ?FORALL(Vars, Types, Exprs))). end,
?FORALL(Vars, Types, Exprs)
)
).
parse(D) -> parse(D) ->
{ok, P, _Rest, _State} = emqx_sn_frame:parse(D, #{}), {ok, P, _Rest, _State} = emqx_sn_frame:parse(D, #{}),
@ -39,11 +43,14 @@ serialize(P) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
prop_parse_and_serialize() -> prop_parse_and_serialize() ->
?ALL(Msg, mqtt_sn_message(), ?ALL(
begin Msg,
Msg = parse(serialize(Msg)), mqtt_sn_message(),
true begin
end). Msg = parse(serialize(Msg)),
true
end
).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Helper %% Helper
@ -61,18 +68,32 @@ do_teardown(_) ->
mqtt_sn_message() -> mqtt_sn_message() ->
M = emqx_sn_proper_types, M = emqx_sn_proper_types,
oneof([ M:'ADVERTISE'(), M:'SEARCHGW'() oneof([
, M:'GWINFO'(), M:'CONNECT'() M:'ADVERTISE'(),
, M:'CONNACK'(), M:'WILLTOTPICREQ'() M:'SEARCHGW'(),
, M:'WILLTOPIC'(), M:'WILLTOPCI_EMPTY'() M:'GWINFO'(),
, M:'WILLMESSAGEREQ'(), M:'WILLMESSAGE'() M:'CONNECT'(),
, M:'REGISTER'(), M:'REGACK'() M:'CONNACK'(),
, M:'PUBLISH'(), M:'PUBACK'() M:'WILLTOTPICREQ'(),
, M:'PUBCOMP_REC_REL'(), M:'SUBSCRIBE'() M:'WILLTOPIC'(),
, M:'SUBACK'(), M:'UNSUBSCRIBE'() M:'WILLTOPCI_EMPTY'(),
, M:'UNSUBACK'(), M:'PINGREQ'() M:'WILLMESSAGEREQ'(),
, M:'PINGRESP'(), M:'DISCONNECT'() M:'WILLMESSAGE'(),
, M:'DISCONNECT'(), M:'WILLTOPICUPD'() M:'REGISTER'(),
, M:'WILLTOPICRESP'(), M:'WILLMSGUPD'() M:'REGACK'(),
, M:'WILLMSGRESP'() 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'()
]).

View File

@ -33,8 +33,8 @@ start(_, <<"attacker">>, _, _, _) ->
{stop, auth_failure}; {stop, auth_failure};
start(ClientId, Username, Password, _Channel, KeepaliveInterval) -> start(ClientId, Username, Password, _Channel, KeepaliveInterval) ->
true = is_binary(ClientId), true = is_binary(ClientId),
(true = ( is_binary(Username)) orelse (Username == undefined) ), (true = (is_binary(Username)) orelse (Username == undefined)),
(true = ( is_binary(Password)) orelse (Password == undefined) ), (true = (is_binary(Password)) orelse (Password == undefined)),
self() ! {keepalive, start, KeepaliveInterval}, self() ! {keepalive, start, KeepaliveInterval},
{ok, []}. {ok, []}.
@ -61,40 +61,42 @@ stop() ->
init(_Param) -> init(_Param) ->
{ok, #state{subscriber = []}}. {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]), ?LOGT("test broker subscribe Topic=~p, Pid=~p~n", [Topic, Proc]),
is_binary(Topic) orelse error("Topic should be a binary"), is_binary(Topic) orelse error("Topic should be a binary"),
{reply, {ok, []}, State#state{subscriber = [{Topic, Proc}|SubList]}}; {reply, {ok, []}, State#state{subscriber = [{Topic, Proc} | SubList]}};
handle_call(get_subscribed_topics, _From, State = #state{subscriber = SubList}) ->
handle_call(get_subscribed_topics, _From, State=#state{subscriber = SubList}) ->
Response = subscribed_topics(SubList, []), Response = subscribed_topics(SubList, []),
?LOGT("test broker get subscribed topics=~p~n", [Response]), ?LOGT("test broker get subscribed topics=~p~n", [Response]),
{reply, Response, State}; {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]), ?LOGT("test broker unsubscribe Topic=~p~n", [Topic]),
is_binary(Topic) orelse error("Topic should be a binary"), is_binary(Topic) orelse error("Topic should be a binary"),
NewSubList = proplists:delete(Topic, SubList), NewSubList = proplists:delete(Topic, SubList),
{reply, {ok, []}, State#state{subscriber = NewSubList}}; {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"), (is_binary(Topic) and is_binary(Msg)) orelse error("Topic and Msg should be binary"),
Pid = proplists:get_value(MatchedTopicFilter, SubList), 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]), ?LOGT("test broker publish topic=~p, Msg=~p, Pid=~p, MatchedTopicFilter=~p, SubList=~p~n", [
(Pid == undefined) andalso ?LOGT("!!!!! this topic ~p has never been subscribed, please specify a valid topic filter", [MatchedTopicFilter]), 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), ?assertNotEqual(undefined, Pid),
Pid ! {deliver, #message{topic = Topic, payload = Msg}}, Pid ! {deliver, #message{topic = Topic, payload = Msg}},
{reply, ok, State}; {reply, ok, State};
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
{stop, normal, stopped, State}; {stop, normal, stopped, State};
handle_call(Req, _From, State) -> handle_call(Req, _From, State) ->
?LOGT("test_broker_server: ignore call Req=~p~n", [Req]), ?LOGT("test_broker_server: ignore call Req=~p~n", [Req]),
{reply, {error, badreq}, State}. {reply, {error, badreq}, State}.
handle_cast(Msg, State) -> handle_cast(Msg, State) ->
?LOGT("test_broker_server: ignore cast msg=~p~n", [Msg]), ?LOGT("test_broker_server: ignore cast msg=~p~n", [Msg]),
{noreply, State}. {noreply, State}.
@ -110,38 +112,37 @@ terminate(Reason, _State) ->
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
subscribed_topics([], Acc) -> subscribed_topics([], Acc) ->
Acc; Acc;
subscribed_topics([{Topic,_Pid}|T], Acc) -> subscribed_topics([{Topic, _Pid} | T], Acc) ->
subscribed_topics(T, [Topic|Acc]). subscribed_topics(T, [Topic | Acc]).
-record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}). -record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}).
-type(keepalive() :: #keepalive{}). -type keepalive() :: #keepalive{}.
%% @doc Start a keepalive %% @doc Start a keepalive
-spec(start(fun(), integer(), any()) -> undefined | keepalive()). -spec start(fun(), integer(), any()) -> undefined | keepalive().
start(_, 0, _) -> start(_, 0, _) ->
undefined; undefined;
start(StatFun, TimeoutSec, TimeoutMsg) -> start(StatFun, TimeoutSec, TimeoutMsg) ->
{ok, StatVal} = StatFun(), {ok, StatVal} = StatFun(),
#keepalive{statfun = StatFun, statval = StatVal, #keepalive{
tsec = TimeoutSec, tmsg = TimeoutMsg, statfun = StatFun,
tref = timer(TimeoutSec, TimeoutMsg)}. statval = StatVal,
tsec = TimeoutSec,
tmsg = TimeoutMsg,
tref = timer(TimeoutSec, TimeoutMsg)
}.
%% @doc Check keepalive, called when timeout. %% @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}) -> check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) ->
case StatFun() of case StatFun() of
{ok, NewVal} -> {ok, NewVal} ->
if NewVal =/= LastVal -> if
{ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})}; NewVal =/= LastVal ->
{ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})};
Repeat < 1 -> Repeat < 1 ->
{ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = Repeat + 1})}; {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = Repeat + 1})};
true -> true ->
@ -155,17 +156,16 @@ resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) ->
KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}. KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}.
%% @doc Cancel Keepalive %% @doc Cancel Keepalive
-spec(cancel(keepalive()) -> ok). -spec cancel(keepalive()) -> ok.
cancel(#keepalive{tref = TRef}) -> cancel(#keepalive{tref = TRef}) ->
cancel(TRef); cancel(TRef);
cancel(undefined) -> cancel(undefined) ->
ok; ok;
cancel(TRef) -> cancel(TRef) ->
catch erlang:cancel_timer(TRef). catch erlang:cancel_timer(TRef).
timer(Sec, Msg) -> timer(Sec, Msg) ->
erlang:send_after(timer:seconds(Sec), self(), Msg). erlang:send_after(timer:seconds(Sec), self(), Msg).
log(Format, Args) -> log(Format, Args) ->
logger:debug(Format, Args). logger:debug(Format, Args).

View File

@ -8,7 +8,7 @@ set -euo pipefail
cd -P -- "$(dirname -- "$0")/.." cd -P -- "$(dirname -- "$0")/.."
APPS=() 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' ) APPS+=( 'lib-ee/emqx_enterprise_conf' 'lib-ee/emqx_license' )
for app in "${APPS[@]}"; do for app in "${APPS[@]}"; do