refactor(gw-lwm2m): refine lwm2m

This commit is contained in:
JianBo He 2021-08-02 09:30:54 +08:00
parent 83630982bc
commit b16cf44bf6
15 changed files with 2651 additions and 2391 deletions

View File

@ -127,4 +127,34 @@ gateway: {
#listener.udp.1: {} #listener.udp.1: {}
#listener.dtls.1: {} #listener.dtls.1: {}
} }
lwm2m_xml_dir: "{{ platform_etc_dir }}/lwm2m_xml"
lwm2m.1: {
lifetime_min: 1s
lifetime_max: 86400s
qmode_time_windonw: 22
auto_observe: false
mountpoint: "lwm2m/%e/"
## always | contains_object_list
update_msg_publish_condition: contains_object_list
translators: {
command: "dn/#"
response: "up/resp"
notify: "up/notify"
register: "up/resp"
update: "up/resp"
}
listener.udp.1 {
bind: 5783
}
}
} }

View File

@ -3,7 +3,7 @@
{vsn, "0.1.0"}, {vsn, "0.1.0"},
{registered, []}, {registered, []},
{mod, {emqx_gateway_app, []}}, {mod, {emqx_gateway_app, []}},
{applications, [kernel, stdlib, grpc]}, {applications, [kernel, stdlib, grpc, lwm2m_coap]},
{env, []}, {env, []},
{modules, []}, {modules, []},
{licenses, ["Apache 2.0"]}, {licenses, ["Apache 2.0"]},

View File

@ -44,7 +44,8 @@ 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_stomp_impl, emqx_sn_impl, emqx_exproto_impl,
emqx_coap_impl, emqx_lwm2m_impl].
load(Mod) -> load(Mod) ->
try try
@ -81,10 +82,14 @@ create_gateway_by_default([{Type, Name, Confs}|More]) ->
create_gateway_by_default(More). create_gateway_by_default(More).
zipped_confs() -> zipped_confs() ->
All = maps:to_list(emqx_config:get([gateway])), All = maps:to_list(
maps:without(exclude_options(), emqx_config:get([gateway]))),
lists:append(lists:foldr( lists:append(lists:foldr(
fun({Type, Gws}, Acc) -> fun({Type, Gws}, Acc) ->
{Names, Confs} = lists:unzip(maps:to_list(Gws)), {Names, Confs} = lists:unzip(maps:to_list(Gws)),
Types = [ Type || _ <- lists:seq(1, length(Names))], Types = [ Type || _ <- lists:seq(1, length(Names))],
[lists:zip3(Types, Names, Confs) | Acc] [lists:zip3(Types, Names, Confs) | Acc]
end, [], All)). end, [], All)).
exclude_options() ->
[lwm2m_xml_dir].

View File

@ -34,8 +34,10 @@ structs() -> ["gateway"].
fields("gateway") -> fields("gateway") ->
[{stomp, t(ref(stomp))}, [{stomp, t(ref(stomp))},
{mqttsn, t(ref(mqttsn))}, {mqttsn, t(ref(mqttsn))},
{exproto, t(ref(exproto))}, {coap, t(ref(coap))},
{coap, t(ref(coap))} {lwm2m, t(ref(lwm2m))},
{lwm2m_xml_dir, t(string())},
{exproto, t(ref(exproto))}
]; ];
fields(stomp) -> fields(stomp) ->
@ -74,6 +76,21 @@ fields(mqttsn_predefined) ->
, {topic, t(string())} , {topic, t(string())}
]; ];
fields(lwm2m) ->
[{"$id", t(ref(lwm2m_structs))}
];
fields(lwm2m_structs) ->
[ {lifetime_min, t(duration())}
, {lifetime_max, t(duration())}
, {qmode_time_windonw, t(integer())}
, {auto_observe, t(boolean())}
, {mountpoint, t(string())}
, {update_msg_publish_condition, t(union([always, contains_object_list]))}
, {translators, t(ref(translators))}
, {listener, t(ref(udp_listener_group))}
];
fields(exproto) -> fields(exproto) ->
[{"$id", t(ref(exproto_structs))}]; [{"$id", t(ref(exproto_structs))}];
@ -100,6 +117,9 @@ fields(clientinfo_override) ->
, {clientid, t(string())} , {clientid, t(string())}
]; ];
fields(translators) ->
[{"$name", t(string())}];
fields(udp_listener_group) -> fields(udp_listener_group) ->
[ {udp, t(ref(udp_listener))} [ {udp, t(ref(udp_listener))}
, {dtls, t(ref(dtls_listener))} , {dtls, t(ref(dtls_listener))}

View File

@ -363,9 +363,12 @@ check_epn(undefined) -> false;
check_epn(_) -> true. check_epn(_) -> true.
check_lifetime(undefined) -> false; check_lifetime(undefined) -> false;
check_lifetime(LifeTime) when is_integer(LifeTime) -> check_lifetime(LifeTime0) when is_integer(LifeTime0) ->
Max = proplists:get_value(lifetime_max, lwm2m_coap_responder:options(), 315360000), LifeTime = timer:seconds(LifeTime0),
Min = proplists:get_value(lifetime_min, lwm2m_coap_responder:options(), 0), Envs = proplists:get_value(config, lwm2m_coap_responder:options(), #{}),
Max = maps:get(lifetime_max, Envs, 315360000),
Min = maps:get(lifetime_min, Envs, 0),
if if
LifeTime >= Min, LifeTime =< Max -> LifeTime >= Min, LifeTime =< Max ->
true; true;

View File

@ -0,0 +1,173 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
%% @doc The LwM2M Gateway Implement interface
-module(emqx_lwm2m_impl).
-behavior(emqx_gateway_impl).
%% APIs
-export([ load/0
, unload/0
]).
-export([]).
-export([ init/1
, on_insta_create/3
, on_insta_update/4
, on_insta_destroy/3
]).
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
load() ->
RegistryOptions = [ {cbkmod, ?MODULE}
],
emqx_gateway_registry:load(lwm2m, RegistryOptions, []).
unload() ->
%% XXX:
lwm2m_coap_server_registry:remove_handler(
[<<"rd">>],
emqx_lwm2m_coap_resource, undefined
),
emqx_gateway_registry:unload(lwm2m).
init(_) ->
%% Handler
_ = lwm2m_coap_server:start_registry(),
lwm2m_coap_server_registry:add_handler(
[<<"rd">>],
emqx_lwm2m_coap_resource, undefined
),
%% Xml registry
{ok, _} = emqx_lwm2m_xml_object_db:start_link(
emqx_config:get([gateway, lwm2m_xml_dir])
),
%% XXX: Self managed table?
%% TODO: Improve it later
{ok, _} = emqx_lwm2m_cm:start_link(),
GwState = #{},
{ok, GwState}.
%% TODO: deinit
%%--------------------------------------------------------------------
%% emqx_gateway_registry callbacks
%%--------------------------------------------------------------------
on_insta_create(_Insta = #{ id := InstaId,
rawconf := RawConf
}, Ctx, _GwState) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
ListenerPids = lists:map(fun(Lis) ->
start_listener(InstaId, Ctx, Lis)
end, Listeners),
{ok, ListenerPids, _InstaState = #{ctx => Ctx}}.
on_insta_update(NewInsta, OldInsta, GwInstaState = #{ctx := Ctx}, GwState) ->
InstaId = maps:get(id, NewInsta),
try
%% XXX: 1. How hot-upgrade the changes ???
%% XXX: 2. Check the New confs first before destroy old instance ???
on_insta_destroy(OldInsta, GwInstaState, GwState),
on_insta_create(NewInsta, Ctx, GwState)
catch
Class : Reason : Stk ->
logger:error("Failed to update stomp instance ~s; "
"reason: {~0p, ~0p} stacktrace: ~0p",
[InstaId, Class, Reason, Stk]),
{error, {Class, Reason}}
end.
on_insta_destroy(_Insta = #{ id := InstaId,
rawconf := RawConf
}, _GwInstaState, _GwState) ->
Listeners = emqx_gateway_utils:normalize_rawconf(RawConf),
lists:foreach(fun(Lis) ->
stop_listener(InstaId, Lis)
end, Listeners).
%%--------------------------------------------------------------------
%% Internal funcs
%%--------------------------------------------------------------------
start_listener(InstaId, Ctx, {Type, ListenOn, SocketOpts, Cfg}) ->
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) of
{ok, Pid} ->
io:format("Start lwm2m ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]),
Pid;
{error, Reason} ->
io:format(standard_error,
"Failed to start lwm2m ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]),
throw({badconf, Reason})
end.
start_listener(InstaId, Ctx, Type, ListenOn, SocketOpts, Cfg) ->
Name = name(InstaId, udp),
NCfg = Cfg#{ctx => Ctx},
NSocketOpts = merge_default(SocketOpts),
Options = [{config, NCfg}|NSocketOpts],
case Type of
udp ->
lwm2m_coap_server:start_udp(Name, ListenOn, Options);
dtls ->
lwm2m_coap_server:start_dtls(Name, ListenOn, Options)
end.
name(InstaId, Type) ->
list_to_atom(lists:concat([InstaId, ":", Type])).
merge_default(Options) ->
Default = emqx_gateway_utils:default_udp_options(),
case lists:keytake(udp_options, 1, Options) of
{value, {udp_options, TcpOpts}, Options1} ->
[{udp_options, emqx_misc:merge_opts(Default, TcpOpts)}
| Options1];
false ->
[{udp_options, Default} | Options]
end.
stop_listener(InstaId, {Type, ListenOn, SocketOpts, Cfg}) ->
StopRet = stop_listener(InstaId, Type, ListenOn, SocketOpts, Cfg),
ListenOnStr = emqx_gateway_utils:format_listenon(ListenOn),
case StopRet of
ok -> io:format("Stop lwm2m ~s:~s listener on ~s successfully.~n",
[InstaId, Type, ListenOnStr]);
{error, Reason} ->
io:format(standard_error,
"Failed to stop lwm2m ~s:~s listener on ~s: ~0p~n",
[InstaId, Type, ListenOnStr, Reason]
)
end,
StopRet.
stop_listener(InstaId, Type, ListenOn, _SocketOpts, _Cfg) ->
Name = name(InstaId, Type),
case Type of
udp ->
lwm2m_coap_server:stop_udp(Name, ListenOn);
dtls ->
lwm2m_coap_server:stop_dtls(Name, ListenOn)
end.

View File

@ -75,7 +75,8 @@ call(Pid, Msg, Timeout) ->
end. end.
init(CoapPid, EndpointName, Peername = {_Peerhost, _Port}, RegInfo = #{<<"lt">> := LifeTime, <<"lwm2m">> := Ver}) -> init(CoapPid, EndpointName, Peername = {_Peerhost, _Port}, RegInfo = #{<<"lt">> := LifeTime, <<"lwm2m">> := Ver}) ->
Mountpoint = proplists:get_value(mountpoint, lwm2m_coap_responder:options(), ""), Envs = proplists:get_value(config, lwm2m_coap_responder:options(), #{}),
Mountpoint = iolist_to_binary(maps:get(mountpoint, Envs, "")),
Lwm2mState = #lwm2m_state{peername = Peername, Lwm2mState = #lwm2m_state{peername = Peername,
endpoint_name = EndpointName, endpoint_name = EndpointName,
version = Ver, version = Ver,
@ -89,7 +90,10 @@ init(CoapPid, EndpointName, Peername = {_Peerhost, _Port}, RegInfo = #{<<"lt">>
ok -> ok ->
_ = run_hooks('client.connack', [conninfo(Lwm2mState), success], undefined), _ = run_hooks('client.connack', [conninfo(Lwm2mState), success], undefined),
Sockport = proplists:get_value(port, lwm2m_coap_responder:options(), 5683), %% FIXME:
Sockport = 5683,
%Sockport = proplists:get_value(port, lwm2m_coap_responder:options(), 5683),
ClientInfo1 = maps:put(sockport, Sockport, ClientInfo), ClientInfo1 = maps:put(sockport, Sockport, ClientInfo),
Lwm2mState1 = Lwm2mState#lwm2m_state{started_at = time_now(), Lwm2mState1 = Lwm2mState#lwm2m_state{started_at = time_now(),
mountpoint = maps:get(mountpoint, ClientInfo1)}, mountpoint = maps:get(mountpoint, ClientInfo1)},
@ -124,8 +128,10 @@ update_reg_info(NewRegInfo, Lwm2mState=#lwm2m_state{life_timer = LifeTimer, regi
coap_pid = CoapPid, endpoint_name = Epn}) -> coap_pid = CoapPid, endpoint_name = Epn}) ->
UpdatedRegInfo = maps:merge(RegInfo, NewRegInfo), UpdatedRegInfo = maps:merge(RegInfo, NewRegInfo),
_ = case proplists:get_value(update_msg_publish_condition, Envs = proplists:get_value(config, lwm2m_coap_responder:options(), #{}),
lwm2m_coap_responder:options(), contains_object_list) of
_ = case maps:get(update_msg_publish_condition,
Envs, contains_object_list) of
always -> always ->
send_to_broker(<<"update">>, #{<<"data">> => UpdatedRegInfo}, Lwm2mState); send_to_broker(<<"update">>, #{<<"data">> => UpdatedRegInfo}, Lwm2mState);
contains_object_list -> contains_object_list ->
@ -294,7 +300,8 @@ auto_observe_object_list(Expected, Registered) ->
send_auto_observe(CoapPid, RegInfo, EndpointName) -> send_auto_observe(CoapPid, RegInfo, EndpointName) ->
%% - auto observe the objects %% - auto observe the objects
case proplists:get_value(auto_observe, lwm2m_coap_responder:options(), false) of Envs = proplists:get_value(config, lwm2m_coap_responder:options(), #{}),
case proplists:get_value(auto_observe, Envs, false) of
false -> false ->
?LOG(info, "Auto Observe Disabled", []); ?LOG(info, "Auto Observe Disabled", []);
TrueOrObjList -> TrueOrObjList ->
@ -379,7 +386,12 @@ get_cached_downlink_messages() ->
is_cache_mode(RegInfo, StartedAt) -> is_cache_mode(RegInfo, StartedAt) ->
case is_psm(RegInfo) orelse is_qmode(RegInfo) of case is_psm(RegInfo) orelse is_qmode(RegInfo) of
true -> true ->
QModeTimeWind = proplists:get_value(qmode_time_window, lwm2m_coap_responder:options(), 22), Envs = proplists:get_value(
config,
lwm2m_coap_responder:options(),
#{}
),
QModeTimeWind = maps:get(qmode_time_window, Envs, 22),
Now = time_now(), Now = time_now(),
if (Now - StartedAt) >= QModeTimeWind -> true; if (Now - StartedAt) >= QModeTimeWind -> true;
true -> false true -> false
@ -400,15 +412,17 @@ is_qmode(_) -> false.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
downlink_topic(EventType, Lwm2mState = #lwm2m_state{mountpoint = Mountpoint}) -> downlink_topic(EventType, Lwm2mState = #lwm2m_state{mountpoint = Mountpoint}) ->
Topics = proplists:get_value(topics, lwm2m_coap_responder:options(), []), Envs = proplists:get_value(config, lwm2m_coap_responder:options(), #{}),
DnTopic = proplists:get_value(downlink_topic_key(EventType), Topics, Topics = maps:get(translators, Envs, #{}),
default_downlink_topic(EventType)), DnTopic = maps:get(downlink_topic_key(EventType), Topics,
default_downlink_topic(EventType)),
take_place(mountpoint(iolist_to_binary(DnTopic), Mountpoint), Lwm2mState). take_place(mountpoint(iolist_to_binary(DnTopic), Mountpoint), Lwm2mState).
uplink_topic(EventType, Lwm2mState = #lwm2m_state{mountpoint = Mountpoint}) -> uplink_topic(EventType, Lwm2mState = #lwm2m_state{mountpoint = Mountpoint}) ->
Topics = proplists:get_value(topics, lwm2m_coap_responder:options(), []), Envs = proplists:get_value(config, lwm2m_coap_responder:options(), #{}),
UpTopic = proplists:get_value(uplink_topic_key(EventType), Topics, Topics = maps:get(translators, Envs, #{}),
default_uplink_topic(EventType)), UpTopic = maps:get(uplink_topic_key(EventType), Topics,
default_uplink_topic(EventType)),
take_place(mountpoint(iolist_to_binary(UpTopic), Mountpoint), Lwm2mState). take_place(mountpoint(iolist_to_binary(UpTopic), Mountpoint), Lwm2mState).
downlink_topic_key(EventType) when is_binary(EventType) -> downlink_topic_key(EventType) when is_binary(EventType) ->

View File

@ -22,7 +22,7 @@
% This module is for future use. Disabled now. % This module is for future use. Disabled now.
%% API %% API
-export([ start_link/0 -export([ start_link/1
, stop/0 , stop/0
, find_name/1 , find_name/1
, find_objectid/1 , find_objectid/1
@ -49,8 +49,8 @@
%% API Function Definitions %% API Function Definitions
%% ------------------------------------------------------------------ %% ------------------------------------------------------------------
start_link() -> start_link(XmlDir) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). gen_server:start_link({local, ?MODULE}, ?MODULE, [XmlDir], []).
find_objectid(ObjectId) -> find_objectid(ObjectId) ->
ObjectIdInt = case is_list(ObjectId) of ObjectIdInt = case is_list(ObjectId) of
@ -85,10 +85,10 @@ stop() ->
%% gen_server Function Definitions %% gen_server Function Definitions
%% ------------------------------------------------------------------ %% ------------------------------------------------------------------
init([]) -> init([XmlDir]) ->
_ = ets:new(?LWM2M_OBJECT_DEF_TAB, [set, named_table, protected]), _ = ets:new(?LWM2M_OBJECT_DEF_TAB, [set, named_table, protected]),
_ = ets:new(?LWM2M_OBJECT_NAME_TO_ID_TAB, [set, named_table, protected]), _ = ets:new(?LWM2M_OBJECT_NAME_TO_ID_TAB, [set, named_table, protected]),
load(emqx_config:get([emqx_lwm2m, xml_dir])), load(XmlDir),
{ok, #state{}}. {ok, #state{}}.
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
@ -108,8 +108,6 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) -> code_change(_OldVsn, State, _Extra) ->
{ok, State}. {ok, State}.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Internal functions %% Internal functions
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -1,240 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_tlv_SUITE).
% -compile(export_all).
% -compile(nowarn_export_all).
% -define(LOGT(Format, Args), logger:debug("TEST_SUITE: " ++ Format, Args)).
% -include("src/lwm2m/include/emqx_lwm2m.hrl").
% -include_lib("lwm2m_coap/include/coap.hrl").
% -include_lib("eunit/include/eunit.hrl").
% all() -> [case01, case02, case03, case03_0, case04, case05, case06, case07, case08, case09].
% init_per_suite(Config) ->
% Config.
% end_per_suite(Config) ->
% Config.
% case01(_Config) ->
% Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case02(_Config) ->
% Data = <<16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_multiple_resource => 16#06, value => [
% #{tlv_resource_instance => 16#00, value => <<1>>},
% #{tlv_resource_instance => 16#01, value => <<5>>}
% ]}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case03(_Config) ->
% Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
% #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
% #{tlv_resource_with_value => 16#02, value => <<"345000123">>}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case03_0(_Config) ->
% Data = <<16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 16#01, 16#36, 16#01>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_multiple_resource => 16#02, value => [
% #{tlv_resource_instance => 16#7F, value => <<16#07>>},
% #{tlv_resource_instance => 16#0136, value => <<16#01>>}
% ]}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case04(_Config) ->
% % 6.4.3.1 Single Object Instance Request Example
% Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33, 16#C3, 16#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05, 16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8, 16#42, 16#01, 16#13, 16#88, 16#87, 16#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1, 16#09, 16#64, 16#C1, 16#0A, 16#0F, 16#83, 16#0B, 16#41, 16#00, 16#00, 16#C4, 16#0D, 16#51, 16#82, 16#42, 16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30, 16#C1, 16#10, 16#55>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
% #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
% #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
% #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
% #{tlv_multiple_resource => 16#06, value => [
% #{tlv_resource_instance => 16#00, value => <<1>>},
% #{tlv_resource_instance => 16#01, value => <<5>>}
% ]},
% #{tlv_multiple_resource => 16#07, value => [
% #{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
% #{tlv_resource_instance => 16#01, value => <<16#1388:16>>}
% ]},
% #{tlv_multiple_resource => 16#08, value => [
% #{tlv_resource_instance => 16#00, value => <<16#7d>>},
% #{tlv_resource_instance => 16#01, value => <<16#0384:16>>}
% ]},
% #{tlv_resource_with_value => 16#09, value => <<16#64>>},
% #{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
% #{tlv_multiple_resource => 16#0B, value => [
% #{tlv_resource_instance => 16#00, value => <<16#00>>}
% ]},
% #{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
% #{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
% #{tlv_resource_with_value => 16#10, value => <<"U">>}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case05(_Config) ->
% % 6.4.3.2 Multiple Object Instance Request Examples
% % A) Request on Single-Instance Object
% Data = <<16#08, 16#00, 16#79, 16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33, 16#C3, 16#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05, 16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8, 16#42, 16#01, 16#13, 16#88, 16#87, 16#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1, 16#09, 16#64, 16#C1, 16#0A, 16#0F, 16#83, 16#0B, 16#41, 16#00, 16#00, 16#C4, 16#0D, 16#51, 16#82, 16#42, 16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30, 16#C1, 16#10, 16#55>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_object_instance => 16#00, value => [
% #{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
% #{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
% #{tlv_resource_with_value => 16#02, value => <<"345000123">>},
% #{tlv_resource_with_value => 16#03, value => <<"1.0">>},
% #{tlv_multiple_resource => 16#06, value => [
% #{tlv_resource_instance => 16#00, value => <<1>>},
% #{tlv_resource_instance => 16#01, value => <<5>>}
% ]},
% #{tlv_multiple_resource => 16#07, value => [
% #{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
% #{tlv_resource_instance => 16#01, value => <<16#1388:16>>}
% ]},
% #{tlv_multiple_resource => 16#08, value => [
% #{tlv_resource_instance => 16#00, value => <<16#7d>>},
% #{tlv_resource_instance => 16#01, value => <<16#0384:16>>}
% ]},
% #{tlv_resource_with_value => 16#09, value => <<16#64>>},
% #{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
% #{tlv_multiple_resource => 16#0B, value => [
% #{tlv_resource_instance => 16#00, value => <<16#00>>}
% ]},
% #{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
% #{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
% #{tlv_resource_with_value => 16#10, value => <<"U">>}
% ]}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case06(_Config) ->
% % 6.4.3.2 Multiple Object Instance Request Examples
% % B) Request on Multiple-Instances Object having 2 instances
% Data = <<16#08, 16#00, 16#0E, 16#C1, 16#00, 16#01, 16#C1, 16#01, 16#00, 16#83, 16#02, 16#41, 16#7F, 16#07, 16#C1, 16#03, 16#7F, 16#08, 16#02, 16#12, 16#C1, 16#00, 16#03, 16#C1, 16#01, 16#00, 16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 16#01, 16#36, 16#01, 16#C1, 16#03, 16#7F>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_object_instance => 16#00, value => [
% #{tlv_resource_with_value => 16#00, value => <<16#01>>},
% #{tlv_resource_with_value => 16#01, value => <<16#00>>},
% #{tlv_multiple_resource => 16#02, value => [
% #{tlv_resource_instance => 16#7F, value => <<16#07>>}
% ]},
% #{tlv_resource_with_value => 16#03, value => <<16#7F>>}
% ]},
% #{tlv_object_instance => 16#02, value => [
% #{tlv_resource_with_value => 16#00, value => <<16#03>>},
% #{tlv_resource_with_value => 16#01, value => <<16#00>>},
% #{tlv_multiple_resource => 16#02, value => [
% #{tlv_resource_instance => 16#7F, value => <<16#07>>},
% #{tlv_resource_instance => 16#0136, value => <<16#01>>}
% ]},
% #{tlv_resource_with_value => 16#03, value => <<16#7F>>}
% ]}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case07(_Config) ->
% % 6.4.3.2 Multiple Object Instance Request Examples
% % C) Request on Multiple-Instances Object having 1 instance only
% Data = <<16#08, 16#00, 16#0F, 16#C1, 16#00, 16#01, 16#C4, 16#01, 16#00, 16#01, 16#51, 16#80, 16#C1, 16#06, 16#01, 16#C1, 16#07, 16#55>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_object_instance => 16#00, value => [
% #{tlv_resource_with_value => 16#00, value => <<16#01>>},
% #{tlv_resource_with_value => 16#01, value => <<86400:32>>},
% #{tlv_resource_with_value => 16#06, value => <<16#01>>},
% #{tlv_resource_with_value => 16#07, value => <<$U>>}]}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case08(_Config) ->
% % 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource
% % Example 1) request to Object 65 Instance 0: Read /65/0
% Data = <<16#88, 16#00, 16#0C, 16#44, 16#00, 16#00, 16#42, 16#00, 16#00, 16#44, 16#01, 16#00, 16#42, 16#00, 16#01, 16#C8, 16#01, 16#0D, 16#38, 16#36, 16#31, 16#33, 16#38, 16#30, 16#30, 16#37, 16#35, 16#35, 16#35, 16#30, 16#30, 16#C4, 16#02, 16#12, 16#34, 16#56, 16#78>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_multiple_resource => 16#00, value => [
% #{tlv_resource_instance => 16#00, value => <<16#00, 16#42, 16#00, 16#00>>},
% #{tlv_resource_instance => 16#01, value => <<16#00, 16#42, 16#00, 16#01>>}
% ]},
% #{tlv_resource_with_value => 16#01, value => <<"8613800755500">>},
% #{tlv_resource_with_value => 16#02, value => <<16#12345678:32>>}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).
% case09(_Config) ->
% % 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource
% % Example 2) request to Object 66: Read /66: TLV payload will contain 2 Object Instances
% Data = <<16#08, 16#00, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 16#79, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, 16#31, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#65, 16#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E, 16#32, 16#33, 16#34, 16#C4, 16#02, 16#00, 16#43, 16#00, 16#00, 16#08, 16#01, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 16#79, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, 16#32, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#65, 16#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E, 16#32, 16#33, 16#35, 16#C4, 16#02, 16#FF, 16#FF, 16#FF, 16#FF>>,
% R = emqx_lwm2m_tlv:parse(Data),
% Exp = [
% #{tlv_object_instance => 16#00, value => [
% #{tlv_resource_with_value => 16#00, value => <<"myService 1">>},
% #{tlv_resource_with_value => 16#01, value => <<"Internet.15.234">>},
% #{tlv_resource_with_value => 16#02, value => <<16#00, 16#43, 16#00, 16#00>>}
% ]},
% #{tlv_object_instance => 16#01, value => [
% #{tlv_resource_with_value => 16#00, value => <<"myService 2">>},
% #{tlv_resource_with_value => 16#01, value => <<"Internet.15.235">>},
% #{tlv_resource_with_value => 16#02, value => <<16#FF, 16#FF, 16#FF, 16#FF>>}
% ]}
% ],
% ?assertEqual(Exp, R),
% EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
% ?assertEqual(EncodedBinary, Data).

View File

@ -1,171 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(test_mqtt_broker).
% -compile(nowarn_export_all).
% -compile(export_all).
% -define(LOGT(Format, Args), logger:debug("TEST_BROKER: " ++ Format, Args)).
% -record(state, {subscriber}).
% -include_lib("emqx/include/emqx.hrl").
% -include_lib("emqx/include/emqx_mqtt.hrl").
% -include_lib("eunit/include/eunit.hrl").
% start(_, <<"attacker">>, _, _, _) ->
% {stop, auth_failure};
% start(ClientId, Username, Password, _Channel, KeepaliveInterval) ->
% true = is_binary(ClientId),
% (true = ( is_binary(Username)) orelse (Username == undefined) ),
% (true = ( is_binary(Password)) orelse (Password == undefined) ),
% self() ! {keepalive, start, KeepaliveInterval},
% {ok, []}.
% publish(Topic, Payload, Qos) ->
% ClientId = <<"lwm2m_test_suite">>,
% Msg = emqx_message:make(ClientId, Qos, Topic, Payload),
% emqx:publish(Msg).
% subscribe(Topic) ->
% gen_server:call(?MODULE, {subscribe, Topic, self()}).
% unsubscribe(Topic) ->
% gen_server:call(?MODULE, {unsubscribe, Topic}).
% get_subscrbied_topics() ->
% [Topic || {_Client, Topic} <- ets:tab2list(emqx_subscription)].
% start_link() ->
% gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
% stop() ->
% gen_server:stop(?MODULE).
% init(_Param) ->
% {ok, #state{subscriber = []}}.
% handle_call({subscribe, Topic, Proc}, _From, State=#state{subscriber = SubList}) ->
% ?LOGT("test broker subscribe Topic=~p, Pid=~p~n", [Topic, Proc]),
% is_binary(Topic) orelse error("Topic should be a binary"),
% {reply, {ok, []}, State#state{subscriber = [{Topic, Proc}|SubList]}};
% handle_call(get_subscribed_topics, _From, State=#state{subscriber = SubList}) ->
% Response = subscribed_topics(SubList, []),
% ?LOGT("test broker get subscribed topics=~p~n", [Response]),
% {reply, Response, State};
% handle_call({unsubscribe, Topic}, _From, State=#state{subscriber = SubList}) ->
% ?LOGT("test broker unsubscribe Topic=~p~n", [Topic]),
% is_binary(Topic) orelse error("Topic should be a binary"),
% NewSubList = proplists:delete(Topic, SubList),
% {reply, {ok, []}, State#state{subscriber = NewSubList}};
% handle_call({publish, {Topic, Msg, MatchedTopicFilter}}, _From, State=#state{subscriber = SubList}) ->
% (is_binary(Topic) and is_binary(Msg)) orelse error("Topic and Msg should be binary"),
% Pid = proplists:get_value(MatchedTopicFilter, SubList),
% ?LOGT("test broker publish topic=~p, Msg=~p, Pid=~p, MatchedTopicFilter=~p, SubList=~p~n", [Topic, Msg, Pid, MatchedTopicFilter, SubList]),
% (Pid == undefined) andalso ?LOGT("!!!!! this topic ~p has never been subscribed, please specify a valid topic filter", [MatchedTopicFilter]),
% ?assertNotEqual(undefined, Pid),
% Pid ! {deliver, #message{topic = Topic, payload = Msg}},
% {reply, ok, State};
% handle_call(stop, _From, State) ->
% {stop, normal, stopped, State};
% handle_call(Req, _From, State) ->
% ?LOGT("test_broker_server: ignore call Req=~p~n", [Req]),
% {reply, {error, badreq}, State}.
% handle_cast(Msg, State) ->
% ?LOGT("test_broker_server: ignore cast msg=~p~n", [Msg]),
% {noreply, State}.
% handle_info(Info, State) ->
% ?LOGT("test_broker_server: ignore info=~p~n", [Info]),
% {noreply, State}.
% terminate(Reason, _State) ->
% ?LOGT("test_broker_server: terminate Reason=~p~n", [Reason]),
% ok.
% code_change(_OldVsn, State, _Extra) ->
% {ok, State}.
% subscribed_topics([], Acc) ->
% Acc;
% subscribed_topics([{Topic,_Pid}|T], Acc) ->
% subscribed_topics(T, [Topic|Acc]).
% -record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}).
% -type(keepalive() :: #keepalive{}).
% %% @doc Start a keepalive
% -spec(start(fun(), integer(), any()) -> undefined | keepalive()).
% start(_, 0, _) ->
% undefined;
% start(StatFun, TimeoutSec, TimeoutMsg) ->
% {ok, StatVal} = StatFun(),
% #keepalive{statfun = StatFun, statval = StatVal,
% tsec = TimeoutSec, tmsg = TimeoutMsg,
% tref = timer(TimeoutSec, TimeoutMsg)}.
% %% @doc Check keepalive, called when timeout.
% -spec(check(keepalive()) -> {ok, keepalive()} | {error, any()}).
% check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) ->
% case StatFun() of
% {ok, NewVal} ->
% if NewVal =/= LastVal ->
% {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})};
% Repeat < 1 ->
% {ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = Repeat + 1})};
% true ->
% {error, timeout}
% end;
% {error, Error} ->
% {error, Error}
% end.
% resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) ->
% KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}.
% %% @doc Cancel Keepalive
% -spec(cancel(keepalive()) -> ok).
% cancel(#keepalive{tref = TRef}) ->
% cancel(TRef);
% cancel(undefined) ->
% ok;
% cancel(TRef) ->
% catch erlang:cancel_timer(TRef).
% timer(Sec, Msg) ->
% erlang:send_after(timer:seconds(Sec), self(), Msg).
% log(Format, Args) ->
% logger:debug(Format, Args).

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,238 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_tlv_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-define(LOGT(Format, Args), logger:debug("TEST_SUITE: " ++ Format, Args)).
-include("src/lwm2m/include/emqx_lwm2m.hrl").
-include_lib("lwm2m_coap/include/coap.hrl").
-include_lib("eunit/include/eunit.hrl").
all() -> [case01, case02, case03, case03_0, case04, case05, case06, case07, case08, case09].
init_per_suite(Config) ->
Config.
end_per_suite(Config) ->
Config.
case01(_Config) ->
Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case02(_Config) ->
Data = <<16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_multiple_resource => 16#06, value => [
#{tlv_resource_instance => 16#00, value => <<1>>},
#{tlv_resource_instance => 16#01, value => <<5>>}
]}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case03(_Config) ->
Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
#{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
#{tlv_resource_with_value => 16#02, value => <<"345000123">>}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case03_0(_Config) ->
Data = <<16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 16#01, 16#36, 16#01>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_multiple_resource => 16#02, value => [
#{tlv_resource_instance => 16#7F, value => <<16#07>>},
#{tlv_resource_instance => 16#0136, value => <<16#01>>}
]}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case04(_Config) ->
% 6.4.3.1 Single Object Instance Request Example
Data = <<16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33, 16#C3, 16#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05, 16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8, 16#42, 16#01, 16#13, 16#88, 16#87, 16#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1, 16#09, 16#64, 16#C1, 16#0A, 16#0F, 16#83, 16#0B, 16#41, 16#00, 16#00, 16#C4, 16#0D, 16#51, 16#82, 16#42, 16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30, 16#C1, 16#10, 16#55>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
#{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
#{tlv_resource_with_value => 16#02, value => <<"345000123">>},
#{tlv_resource_with_value => 16#03, value => <<"1.0">>},
#{tlv_multiple_resource => 16#06, value => [
#{tlv_resource_instance => 16#00, value => <<1>>},
#{tlv_resource_instance => 16#01, value => <<5>>}
]},
#{tlv_multiple_resource => 16#07, value => [
#{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
#{tlv_resource_instance => 16#01, value => <<16#1388:16>>}
]},
#{tlv_multiple_resource => 16#08, value => [
#{tlv_resource_instance => 16#00, value => <<16#7d>>},
#{tlv_resource_instance => 16#01, value => <<16#0384:16>>}
]},
#{tlv_resource_with_value => 16#09, value => <<16#64>>},
#{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
#{tlv_multiple_resource => 16#0B, value => [
#{tlv_resource_instance => 16#00, value => <<16#00>>}
]},
#{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
#{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
#{tlv_resource_with_value => 16#10, value => <<"U">>}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case05(_Config) ->
% 6.4.3.2 Multiple Object Instance Request Examples
% A) Request on Single-Instance Object
Data = <<16#08, 16#00, 16#79, 16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33, 16#C3, 16#03, 16#31, 16#2E, 16#30, 16#86, 16#06, 16#41, 16#00, 16#01, 16#41, 16#01, 16#05, 16#88, 16#07, 16#08, 16#42, 16#00, 16#0E, 16#D8, 16#42, 16#01, 16#13, 16#88, 16#87, 16#08, 16#41, 16#00, 16#7D, 16#42, 16#01, 16#03, 16#84, 16#C1, 16#09, 16#64, 16#C1, 16#0A, 16#0F, 16#83, 16#0B, 16#41, 16#00, 16#00, 16#C4, 16#0D, 16#51, 16#82, 16#42, 16#8F, 16#C6, 16#0E, 16#2B, 16#30, 16#32, 16#3A, 16#30, 16#30, 16#C1, 16#10, 16#55>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_object_instance => 16#00, value => [
#{tlv_resource_with_value => 16#00, value => <<"Open Mobile Alliance">>},
#{tlv_resource_with_value => 16#01, value => <<"Lightweight M2M Client">>},
#{tlv_resource_with_value => 16#02, value => <<"345000123">>},
#{tlv_resource_with_value => 16#03, value => <<"1.0">>},
#{tlv_multiple_resource => 16#06, value => [
#{tlv_resource_instance => 16#00, value => <<1>>},
#{tlv_resource_instance => 16#01, value => <<5>>}
]},
#{tlv_multiple_resource => 16#07, value => [
#{tlv_resource_instance => 16#00, value => <<16#0ED8:16>>},
#{tlv_resource_instance => 16#01, value => <<16#1388:16>>}
]},
#{tlv_multiple_resource => 16#08, value => [
#{tlv_resource_instance => 16#00, value => <<16#7d>>},
#{tlv_resource_instance => 16#01, value => <<16#0384:16>>}
]},
#{tlv_resource_with_value => 16#09, value => <<16#64>>},
#{tlv_resource_with_value => 16#0A, value => <<16#0F>>},
#{tlv_multiple_resource => 16#0B, value => [
#{tlv_resource_instance => 16#00, value => <<16#00>>}
]},
#{tlv_resource_with_value => 16#0D, value => <<16#5182428F:32>>},
#{tlv_resource_with_value => 16#0E, value => <<"+02:00">>},
#{tlv_resource_with_value => 16#10, value => <<"U">>}
]}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case06(_Config) ->
% 6.4.3.2 Multiple Object Instance Request Examples
% B) Request on Multiple-Instances Object having 2 instances
Data = <<16#08, 16#00, 16#0E, 16#C1, 16#00, 16#01, 16#C1, 16#01, 16#00, 16#83, 16#02, 16#41, 16#7F, 16#07, 16#C1, 16#03, 16#7F, 16#08, 16#02, 16#12, 16#C1, 16#00, 16#03, 16#C1, 16#01, 16#00, 16#87, 16#02, 16#41, 16#7F, 16#07, 16#61, 16#01, 16#36, 16#01, 16#C1, 16#03, 16#7F>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_object_instance => 16#00, value => [
#{tlv_resource_with_value => 16#00, value => <<16#01>>},
#{tlv_resource_with_value => 16#01, value => <<16#00>>},
#{tlv_multiple_resource => 16#02, value => [
#{tlv_resource_instance => 16#7F, value => <<16#07>>}
]},
#{tlv_resource_with_value => 16#03, value => <<16#7F>>}
]},
#{tlv_object_instance => 16#02, value => [
#{tlv_resource_with_value => 16#00, value => <<16#03>>},
#{tlv_resource_with_value => 16#01, value => <<16#00>>},
#{tlv_multiple_resource => 16#02, value => [
#{tlv_resource_instance => 16#7F, value => <<16#07>>},
#{tlv_resource_instance => 16#0136, value => <<16#01>>}
]},
#{tlv_resource_with_value => 16#03, value => <<16#7F>>}
]}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case07(_Config) ->
% 6.4.3.2 Multiple Object Instance Request Examples
% C) Request on Multiple-Instances Object having 1 instance only
Data = <<16#08, 16#00, 16#0F, 16#C1, 16#00, 16#01, 16#C4, 16#01, 16#00, 16#01, 16#51, 16#80, 16#C1, 16#06, 16#01, 16#C1, 16#07, 16#55>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_object_instance => 16#00, value => [
#{tlv_resource_with_value => 16#00, value => <<16#01>>},
#{tlv_resource_with_value => 16#01, value => <<86400:32>>},
#{tlv_resource_with_value => 16#06, value => <<16#01>>},
#{tlv_resource_with_value => 16#07, value => <<$U>>}]}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case08(_Config) ->
% 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource
% Example 1) request to Object 65 Instance 0: Read /65/0
Data = <<16#88, 16#00, 16#0C, 16#44, 16#00, 16#00, 16#42, 16#00, 16#00, 16#44, 16#01, 16#00, 16#42, 16#00, 16#01, 16#C8, 16#01, 16#0D, 16#38, 16#36, 16#31, 16#33, 16#38, 16#30, 16#30, 16#37, 16#35, 16#35, 16#35, 16#30, 16#30, 16#C4, 16#02, 16#12, 16#34, 16#56, 16#78>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_multiple_resource => 16#00, value => [
#{tlv_resource_instance => 16#00, value => <<16#00, 16#42, 16#00, 16#00>>},
#{tlv_resource_instance => 16#01, value => <<16#00, 16#42, 16#00, 16#01>>}
]},
#{tlv_resource_with_value => 16#01, value => <<"8613800755500">>},
#{tlv_resource_with_value => 16#02, value => <<16#12345678:32>>}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).
case09(_Config) ->
% 6.4.3.3 Example of Request on an Object Instance containing an Object Link Resource
% Example 2) request to Object 66: Read /66: TLV payload will contain 2 Object Instances
Data = <<16#08, 16#00, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 16#79, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, 16#31, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#65, 16#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E, 16#32, 16#33, 16#34, 16#C4, 16#02, 16#00, 16#43, 16#00, 16#00, 16#08, 16#01, 16#26, 16#C8, 16#00, 16#0B, 16#6D, 16#79, 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20, 16#32, 16#C8, 16#01, 16#0F, 16#49, 16#6E, 16#74, 16#65, 16#72, 16#6E, 16#65, 16#74, 16#2E, 16#31, 16#35, 16#2E, 16#32, 16#33, 16#35, 16#C4, 16#02, 16#FF, 16#FF, 16#FF, 16#FF>>,
R = emqx_lwm2m_tlv:parse(Data),
Exp = [
#{tlv_object_instance => 16#00, value => [
#{tlv_resource_with_value => 16#00, value => <<"myService 1">>},
#{tlv_resource_with_value => 16#01, value => <<"Internet.15.234">>},
#{tlv_resource_with_value => 16#02, value => <<16#00, 16#43, 16#00, 16#00>>}
]},
#{tlv_object_instance => 16#01, value => [
#{tlv_resource_with_value => 16#00, value => <<"myService 2">>},
#{tlv_resource_with_value => 16#01, value => <<"Internet.15.235">>},
#{tlv_resource_with_value => 16#02, value => <<16#FF, 16#FF, 16#FF, 16#FF>>}
]}
],
?assertEqual(Exp, R),
EncodedBinary = emqx_lwm2m_tlv:encode(Exp),
?assertEqual(EncodedBinary, Data).

View File

@ -0,0 +1,171 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%--------------------------------------------------------------------
-module(test_mqtt_broker).
-compile(nowarn_export_all).
-compile(export_all).
-define(LOGT(Format, Args), logger:debug("TEST_BROKER: " ++ Format, Args)).
-record(state, {subscriber}).
-include_lib("emqx/include/emqx.hrl").
-include_lib("emqx/include/emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
start(_, <<"attacker">>, _, _, _) ->
{stop, auth_failure};
start(ClientId, Username, Password, _Channel, KeepaliveInterval) ->
true = is_binary(ClientId),
(true = ( is_binary(Username)) orelse (Username == undefined) ),
(true = ( is_binary(Password)) orelse (Password == undefined) ),
self() ! {keepalive, start, KeepaliveInterval},
{ok, []}.
publish(Topic, Payload, Qos) ->
ClientId = <<"lwm2m_test_suite">>,
Msg = emqx_message:make(ClientId, Qos, Topic, Payload),
emqx:publish(Msg).
subscribe(Topic) ->
gen_server:call(?MODULE, {subscribe, Topic, self()}).
unsubscribe(Topic) ->
gen_server:call(?MODULE, {unsubscribe, Topic}).
get_subscrbied_topics() ->
[Topic || {_Client, Topic} <- ets:tab2list(emqx_subscription)].
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() ->
gen_server:stop(?MODULE).
init(_Param) ->
{ok, #state{subscriber = []}}.
handle_call({subscribe, Topic, Proc}, _From, State=#state{subscriber = SubList}) ->
?LOGT("test broker subscribe Topic=~p, Pid=~p~n", [Topic, Proc]),
is_binary(Topic) orelse error("Topic should be a binary"),
{reply, {ok, []}, State#state{subscriber = [{Topic, Proc}|SubList]}};
handle_call(get_subscribed_topics, _From, State=#state{subscriber = SubList}) ->
Response = subscribed_topics(SubList, []),
?LOGT("test broker get subscribed topics=~p~n", [Response]),
{reply, Response, State};
handle_call({unsubscribe, Topic}, _From, State=#state{subscriber = SubList}) ->
?LOGT("test broker unsubscribe Topic=~p~n", [Topic]),
is_binary(Topic) orelse error("Topic should be a binary"),
NewSubList = proplists:delete(Topic, SubList),
{reply, {ok, []}, State#state{subscriber = NewSubList}};
handle_call({publish, {Topic, Msg, MatchedTopicFilter}}, _From, State=#state{subscriber = SubList}) ->
(is_binary(Topic) and is_binary(Msg)) orelse error("Topic and Msg should be binary"),
Pid = proplists:get_value(MatchedTopicFilter, SubList),
?LOGT("test broker publish topic=~p, Msg=~p, Pid=~p, MatchedTopicFilter=~p, SubList=~p~n", [Topic, Msg, Pid, MatchedTopicFilter, SubList]),
(Pid == undefined) andalso ?LOGT("!!!!! this topic ~p has never been subscribed, please specify a valid topic filter", [MatchedTopicFilter]),
?assertNotEqual(undefined, Pid),
Pid ! {deliver, #message{topic = Topic, payload = Msg}},
{reply, ok, State};
handle_call(stop, _From, State) ->
{stop, normal, stopped, State};
handle_call(Req, _From, State) ->
?LOGT("test_broker_server: ignore call Req=~p~n", [Req]),
{reply, {error, badreq}, State}.
handle_cast(Msg, State) ->
?LOGT("test_broker_server: ignore cast msg=~p~n", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
?LOGT("test_broker_server: ignore info=~p~n", [Info]),
{noreply, State}.
terminate(Reason, _State) ->
?LOGT("test_broker_server: terminate Reason=~p~n", [Reason]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
subscribed_topics([], Acc) ->
Acc;
subscribed_topics([{Topic,_Pid}|T], Acc) ->
subscribed_topics(T, [Topic|Acc]).
-record(keepalive, {statfun, statval, tsec, tmsg, tref, repeat = 0}).
-type(keepalive() :: #keepalive{}).
%% @doc Start a keepalive
-spec(start(fun(), integer(), any()) -> undefined | keepalive()).
start(_, 0, _) ->
undefined;
start(StatFun, TimeoutSec, TimeoutMsg) ->
{ok, StatVal} = StatFun(),
#keepalive{statfun = StatFun, statval = StatVal,
tsec = TimeoutSec, tmsg = TimeoutMsg,
tref = timer(TimeoutSec, TimeoutMsg)}.
%% @doc Check keepalive, called when timeout.
-spec(check(keepalive()) -> {ok, keepalive()} | {error, any()}).
check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) ->
case StatFun() of
{ok, NewVal} ->
if NewVal =/= LastVal ->
{ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = 0})};
Repeat < 1 ->
{ok, resume(KeepAlive#keepalive{statval = NewVal, repeat = Repeat + 1})};
true ->
{error, timeout}
end;
{error, Error} ->
{error, Error}
end.
resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) ->
KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}.
%% @doc Cancel Keepalive
-spec(cancel(keepalive()) -> ok).
cancel(#keepalive{tref = TRef}) ->
cancel(TRef);
cancel(undefined) ->
ok;
cancel(TRef) ->
catch erlang:cancel_timer(TRef).
timer(Sec, Msg) ->
erlang:send_after(timer:seconds(Sec), self(), Msg).
log(Format, Args) ->
logger:debug(Format, Args).

View File

@ -346,6 +346,7 @@ relx_overlay(ReleaseType) ->
, {copy, "bin/emqx", "bin/emqx-{{release_version}}"} %% for relup , {copy, "bin/emqx", "bin/emqx-{{release_version}}"} %% for relup
, {copy, "bin/emqx_ctl", "bin/emqx_ctl-{{release_version}}"} %% for relup , {copy, "bin/emqx_ctl", "bin/emqx_ctl-{{release_version}}"} %% for relup
, {copy, "bin/install_upgrade.escript", "bin/install_upgrade.escript-{{release_version}}"} %% for relup , {copy, "bin/install_upgrade.escript", "bin/install_upgrade.escript-{{release_version}}"} %% for relup
, {copy, "apps/emqx_gateway/src/lwm2m/lwm2m_xml", "etc/lwm2m_xml"}
, {template, "bin/emqx.cmd", "bin/emqx.cmd"} , {template, "bin/emqx.cmd", "bin/emqx.cmd"}
, {template, "bin/emqx_ctl.cmd", "bin/emqx_ctl.cmd"} , {template, "bin/emqx_ctl.cmd", "bin/emqx_ctl.cmd"}
, {copy, "bin/nodetool", "bin/nodetool"} , {copy, "bin/nodetool", "bin/nodetool"}