style(gateway): format gateway application
This commit is contained in:
parent
4249d4345b
commit
3f6d78dda0
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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">>
|
||||||
|
)
|
||||||
|
}).
|
||||||
|
|
|
||||||
|
|
@ -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]}.
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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().
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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}}.
|
|
||||||
|
|
|
||||||
|
|
@ -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};
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) ->
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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 ->
|
||||||
|
|
|
||||||
|
|
@ -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{}.
|
||||||
|
|
|
||||||
|
|
@ -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, []}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
).
|
||||||
|
|
|
||||||
|
|
@ -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} ->
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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) ->
|
||||||
|
|
|
||||||
|
|
@ -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} ->
|
||||||
|
|
|
||||||
|
|
@ -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) ->
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}.
|
||||||
|
|
|
||||||
|
|
@ -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]))).
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)}.
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
).
|
||||||
|
|
|
||||||
|
|
@ -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>>).
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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}.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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) ->
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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)).
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
|
}.
|
||||||
|
|
|
||||||
|
|
@ -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}).
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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").
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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">>
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}.
|
||||||
|
|
|
||||||
|
|
@ -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}.
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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}]),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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<=600&lwm2m=1", [?PORT, Epn]),
|
sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=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<=600&lwm2m=1", [?PORT, Epn]),
|
sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=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<=600&lwm2m=1", [?PORT, Epn]),
|
sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=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<=600&lwm2m=1", [?PORT, Epn]),
|
sprintf("coap://127.0.0.1:~b/rd?ep=~ts<=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),
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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'()
|
||||||
|
]).
|
||||||
|
|
|
||||||
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue