diff --git a/apps/emqx_coap/.gitignore b/apps/emqx_coap/.gitignore deleted file mode 100644 index 67eaa0145..000000000 --- a/apps/emqx_coap/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -deps/ -ebin/ -_rel/ -.erlang.mk/ -*.d -*.o -*.exe -data/ -*.iml -.idea/ -logs/ -*.beam -emqx_coap.d -intergration_test/emqx-rel/ -intergration_test/libcoap/ -intergration_test/case*.txt -.DS_Store -_build/ -rebar.lock -rebar3.crashdump -*.swp -erlang.mk -.rebar3/ -etc/emqx_coap.conf.rendered -.tags* diff --git a/apps/emqx_coap/TODO b/apps/emqx_coap/TODO deleted file mode 100644 index a0a1c2aaf..000000000 --- a/apps/emqx_coap/TODO +++ /dev/null @@ -1,13 +0,0 @@ -1. Remove the test/test_mqtt_broker and use emqx-ct-helpers -> Done! - - Enhance all test case - -2. Remove the mqtt adaptor -3. Remove the emqx_coap_pubsub_topics.erl - - -### Problems - -1. The coap-client of libcoap does not support Fragment DTLS handshake frame - * So, the connection will be established failed, if the 'Server Hello' frame is too big - * Why is the 'Server Hello' too big when enable the 'coap.dtls.cacertfile' option? -2. diff --git a/apps/emqx_coap/docs/rfc7049.pdf b/apps/emqx_coap/docs/rfc7049.pdf deleted file mode 100644 index a16db36ef..000000000 Binary files a/apps/emqx_coap/docs/rfc7049.pdf and /dev/null differ diff --git a/apps/emqx_coap/docs/rfc7228.pdf b/apps/emqx_coap/docs/rfc7228.pdf deleted file mode 100644 index c9dc1b59f..000000000 Binary files a/apps/emqx_coap/docs/rfc7228.pdf and /dev/null differ diff --git a/apps/emqx_coap/docs/rfc7252.pdf b/apps/emqx_coap/docs/rfc7252.pdf deleted file mode 100644 index 6876fad3e..000000000 Binary files a/apps/emqx_coap/docs/rfc7252.pdf and /dev/null differ diff --git a/apps/emqx_coap/intergration_test/Makefile b/apps/emqx_coap/intergration_test/Makefile deleted file mode 100644 index 12a2081dd..000000000 --- a/apps/emqx_coap/intergration_test/Makefile +++ /dev/null @@ -1,129 +0,0 @@ -.PHONY: clean, clean_result, start_broker stop_broker case1 case2 case3 - -RELX_CONF = emqx-rel/relx.config -LIBCOAP_GIT = libcoap/README.md - -all: clean_result $(RELX_CONF) $(LIBCOAP_GIT) start_broker clean_result case1 case2 case3 case4 stop_broker - @echo " " - @echo " test complete" - @echo " " - -clean_result: - -rm -f case*.txt - - -start_broker: - -rm -f emqx-rel/_rel/emqx/log/* - -emqx-rel/_rel/emqx/bin/emqx stop - sleep 1 - emqx-rel/_rel/emqx/bin/emqx start - sleep 1 - emqx-rel/_rel/emqx/bin/emqx_ctl plugins load emqx_coap - -stop_broker: - -emqx-rel/_rel/emqx/bin/emqx stop - -case1: - libcoap/examples/coap-client -m get -s 5 "coap://127.0.0.1/mqtt/topic1?c=client1&u=tom&p=secret" > case1_output.txt & - sleep 1 - libcoap/examples/coap-client -m put -e w123G45 "coap://127.0.0.1/mqtt/topic1?c=client2&u=mike&p=pw12" - sleep 6 - python check_result.py case1 case1_output.txt==w123G45 - -case2: - # subscribe to topic="x/y" - libcoap/examples/coap-client -m get -s 5 "coap://127.0.0.1/mqtt/x%2Fy?c=client3&u=tom&p=secret" > case2_output1.txt & - # subscribe to topic="+/z" - libcoap/examples/coap-client -m get -s 5 "coap://127.0.0.1/mqtt/%2B%2Fz?c=client4&u=mike&p=pw12" > case2_output2.txt & - sleep 1 - # publish to topic="x/y" - libcoap/examples/coap-client -m put -e big9wolf "coap://127.0.0.1/mqtt/x%2Fy?c=client5&u=sun&p=pw3" - # publish to topic="p/z" - libcoap/examples/coap-client -m put -e black2ant "coap://127.0.0.1/mqtt/p%2Fz?c=client5&u=sun&p=pw3" - sleep 6 - python check_result.py case2 case2_output1.txt==big9wolf case2_output1.txt!=black2ant case2_output2.txt!=big9wolf case2_output2.txt==black2ant - -case3: - libcoap/examples/coap-client -m get -T tk12 -s 5 "coap://127.0.0.1/mqtt/a%2Fb?c=client3&u=tom&p=secret" > case3_output1.txt & - libcoap/examples/coap-client -m get -T tk34 -s 5 "coap://127.0.0.1/mqtt/c%2Fd?c=client3&u=tom&p=secret" > case3_output2.txt & - sleep 1 - libcoap/examples/coap-client -m put -e big9wolf "coap://127.0.0.1/mqtt/c%2Fd?c=client5&u=sun&p=pw3" - libcoap/examples/coap-client -m put -e black2ant "coap://127.0.0.1/mqtt/a%2Fb?c=client5&u=sun&p=pw3" - sleep 6 - python check_result.py case3 case3_output1.txt==black2ant case3_output2.txt==big9wolf case3_output2.txt!=black2ant - - - -case4: - # reload emqx_coap, does it work as expected? - sleep 1 - emqx-rel/_rel/emqx/bin/emqx_ctl plugins unload emqx_coap - sleep 1 - emqx-rel/_rel/emqx/bin/emqx_ctl plugins load emqx_coap - sleep 1 - libcoap/examples/coap-client -m get -s 5 "coap://127.0.0.1/mqtt/topic1?c=client1&u=tom&p=secret" > case4_output.txt & - sleep 1 - libcoap/examples/coap-client -m put -e w6J3G45 "coap://127.0.0.1/mqtt/topic1?c=client2&u=mike&p=pw12" - sleep 6 - python check_result.py case4 case4_output.txt==w6J3G45 - - - - -$(RELX_CONF): - git clone https://github.com/emqx/emqx-rel.git - git clone https://github.com/emqx/emq-coap.git - @echo "update emq-coap with this development code" - mv emq-coap emqx_coap - -rm -rf emqx_coap/etc - -rm -rf emqx_coap/include - -rm -rf emqx_coap/priv - -rm -rf emqx_coap/src - -rm -rf emqx_coap/Makefile - cp -rf ../etc emqx_coap/ - cp -rf ../include emqx_coap/ - cp -rf ../priv emqx_coap/ - cp -rf ../src emqx_coap/ - cp -rf ../Makefile emqx_coap/Makefile - -mkdir emqx-rel/deps - mv emqx_coap emqx-rel/deps/ - @echo "start building ..." - make -C emqx-rel -f Makefile - - -coap: $(LIBCOAP_GIT) - @echo "make coap" - -$(LIBCOAP_GIT): - git clone -b v4.1.2 http://github.com/obgm/libcoap - cd libcoap && ./autogen.sh && ./configure --enable-documentation=no --enable-tests=no - make -C libcoap -f Makefile - -r: rebuild_emq - # r short for rebuild_emq - @echo " rebuild complete " - -rebuild_emq: - -emqx-rel/_rel/emqx/bin/emqx stop - -rm -rf emqx-rel/deps/emqx_coap/etc - -rm -rf emqx-rel/deps/emqx_coap/include - -rm -rf emqx-rel/deps/emqx_coap/priv - -rm -rf emqx-rel/deps/emqx_coap/src - -rm -rf emqx-rel/deps/emqx_coap/Makefile - cp -rf ../etc emqx-rel/deps/emqx_coap/ - cp -rf ../include emqx-rel/deps/emqx_coap/ - cp -rf ../priv emqx-rel/deps/emqx_coap/ - cp -rf ../src emqx-rel/deps/emqx_coap/ - cp -rf ../Makefile emqx-rel/deps/emqx_coap/Makefile - make -C emqx-rel -f Makefile - -clean: clean_result - -rm -f client/*.exe - -rm -f client/*.o - -rm -rf emqx-rel - -rm -rf libcoap - -lazy: clean_result start_broker case2 stop_broker - # custom your command here - @echo "you are so lazy" - diff --git a/apps/emqx_coap/intergration_test/README.md b/apps/emqx_coap/intergration_test/README.md deleted file mode 100644 index eb3507923..000000000 --- a/apps/emqx_coap/intergration_test/README.md +++ /dev/null @@ -1,8 +0,0 @@ -Integration test for emq-coap -====== - -execute following command -``` -make -``` - diff --git a/apps/emqx_coap/intergration_test/check_result.py b/apps/emqx_coap/intergration_test/check_result.py deleted file mode 100644 index f9baaefae..000000000 --- a/apps/emqx_coap/intergration_test/check_result.py +++ /dev/null @@ -1,52 +0,0 @@ -import sys - - -def have_string(filename, text): - data = open(filename, "rb").read() - if data.find(text) > 0: - return True - else: - return False - - -def mark(case_number, result, description): - if result: - f = open(case_number+"_PASS.txt", "wb") - f.close() - print("\n\n"+case_number+" PASS\n\n") - else: - f = open(case_number+"_FAIL.txt", "wb") - f.write(description) - f.close() - print("\n\n"+case_number+" FAIL\n\n") - -def parse_condition(condition): - if condition.find("==") > 0: - r = condition.split("==") - return r[0], r[1], True - elif condition.find("!=") > 0: - r = condition.split("!=") - return r[0], r[1], False - else: - print("\ncondition syntax error\n\n\n") - sys.exit("condition syntax error") - - -def main(): - case_number = sys.argv[1] - description = "" - conclustion = True - for condition in sys.argv[2:]: - filename, text, result = parse_condition(condition) - if have_string(filename, text) == result: - pass - else: - conclustion = False - description = description + "\n" + condition + " failed\n" - - mark(case_number, conclustion, description) - - -if __name__ == "__main__": - main() - diff --git a/apps/emqx_coap/rebar.config b/apps/emqx_coap/rebar.config deleted file mode 100644 index 0f8759b8a..000000000 --- a/apps/emqx_coap/rebar.config +++ /dev/null @@ -1,4 +0,0 @@ -{deps, - [ - {gen_coap, {git, "https://github.com/emqx/gen_coap", {tag, "v0.3.2"}}} - ]}. diff --git a/apps/emqx_coap/test/emqx_coap_SUITE.erl b/apps/emqx_coap/test/emqx_coap_SUITE.erl deleted file mode 100644 index 9618425a3..000000000 --- a/apps/emqx_coap/test/emqx_coap_SUITE.erl +++ /dev/null @@ -1,319 +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_coap_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("gen_coap/include/coap.hrl"). --include_lib("eunit/include/eunit.hrl"). --include_lib("emqx/include/emqx.hrl"). - --define(LOGT(Format, Args), ct:pal(Format, Args)). - -all() -> emqx_ct:all(?MODULE). - -init_per_suite(Config) -> - emqx_ct_helpers:start_apps([emqx_coap], fun set_special_cfg/1), - Config. - -set_special_cfg(emqx_coap) -> - Opts = application:get_env(emqx_coap, dtls_opts,[]), - Opts2 = [{keyfile, emqx_ct_helpers:deps_path(emqx, "etc/certs/key.pem")}, - {certfile, emqx_ct_helpers:deps_path(emqx, "etc/certs/cert.pem")}], - application:set_env(emqx_coap, dtls_opts, emqx_misc:merge_opts(Opts, Opts2)), - application:set_env(emqx_coap, enable_stats, true); -set_special_cfg(_) -> - ok. - -end_per_suite(Config) -> - emqx_ct_helpers:stop_apps([emqx_coap]), - Config. - -%%-------------------------------------------------------------------- -%% Test Cases -%%-------------------------------------------------------------------- - -t_publish(_Config) -> - Topic = <<"abc">>, Payload = <<"123">>, - TopicStr = binary_to_list(Topic), - URI = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", - - %% Sub topic first - emqx:subscribe(Topic), - - Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - {ok, changed, _} = Reply, - - receive - {deliver, Topic, Msg} -> - ?assertEqual(Topic, Msg#message.topic), - ?assertEqual(Payload, Msg#message.payload) - after - 500 -> - ?assert(false) - end. - -t_publish_acl_deny(_Config) -> - Topic = <<"abc">>, Payload = <<"123">>, - TopicStr = binary_to_list(Topic), - URI = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", - - %% Sub topic first - emqx:subscribe(Topic), - - ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history]), - ok = meck:expect(emqx_access_control, authorize, 3, deny), - Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?assertEqual({error,forbidden}, Reply), - ok = meck:unload(emqx_access_control), - receive - {deliver, Topic, Msg} -> ct:fail({unexpected, {Topic, Msg}}) - after - 500 -> ok - end. - -t_observe(_Config) -> - Topic = <<"abc">>, TopicStr = binary_to_list(Topic), - Payload = <<"123">>, - Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", - {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri), - ?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]), - - [SubPid] = emqx:subscribers(Topic), - ?assert(is_pid(SubPid)), - - %% Publish a message - emqx:publish(emqx_message:make(Topic, Payload)), - - Notif = receive_notification(), - ?LOGT("observer get Notif=~p", [Notif]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv}} = Notif, - ?assertEqual(Payload, PayloadRecv), - - er_coap_observer:stop(Pid), - timer:sleep(100), - - [] = emqx:subscribers(Topic). - -t_observe_acl_deny(_Config) -> - Topic = <<"abc">>, TopicStr = binary_to_list(Topic), - Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", - ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history]), - ok = meck:expect(emqx_access_control, authorize, 3, deny), - ?assertEqual({error,forbidden}, er_coap_observer:observe(Uri)), - [] = emqx:subscribers(Topic), - ok = meck:unload(emqx_access_control). - -t_observe_wildcard(_Config) -> - Topic = <<"+/b">>, TopicStr = emqx_http_lib:uri_encode(binary_to_list(Topic)), - Payload = <<"123">>, - Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", - {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri), - ?LOGT("observer Uri=~p, Pid=~p, N=~p, Code=~p, Content=~p", [Uri, Pid, N, Code, Content]), - - [SubPid] = emqx:subscribers(Topic), - ?assert(is_pid(SubPid)), - - %% Publish a message - emqx:publish(emqx_message:make(<<"a/b">>, Payload)), - - Notif = receive_notification(), - ?LOGT("observer get Notif=~p", [Notif]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv}} = Notif, - ?assertEqual(Payload, PayloadRecv), - - er_coap_observer:stop(Pid), - timer:sleep(100), - - [] = emqx:subscribers(Topic). - -t_observe_pub(_Config) -> - Topic = <<"+/b">>, TopicStr = emqx_http_lib:uri_encode(binary_to_list(Topic)), - Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", - {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri), - ?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]), - - [SubPid] = emqx:subscribers(Topic), - ?assert(is_pid(SubPid)), - - Topic2 = <<"a/b">>, Payload2 = <<"UFO">>, - TopicStr2 = emqx_http_lib:uri_encode(binary_to_list(Topic2)), - URI2 = "coap://127.0.0.1/mqtt/"++TopicStr2++"?c=client1&u=tom&p=secret", - - Reply2 = er_coap_client:request(put, URI2, #coap_content{format = <<"application/octet-stream">>, payload = Payload2}), - {ok,changed, _} = Reply2, - - Notif2 = receive_notification(), - ?LOGT("observer get Notif2=~p", [Notif2]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv2}} = Notif2, - ?assertEqual(Payload2, PayloadRecv2), - - Topic3 = <<"j/b">>, Payload3 = <<"ET629">>, - TopicStr3 = emqx_http_lib:uri_encode(binary_to_list(Topic3)), - URI3 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?c=client2&u=mike&p=guess", - Reply3 = er_coap_client:request(put, URI3, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), - {ok,changed, _} = Reply3, - - Notif3 = receive_notification(), - ?LOGT("observer get Notif3=~p", [Notif3]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv3}} = Notif3, - ?assertEqual(Payload3, PayloadRecv3), - - er_coap_observer:stop(Pid). - -t_one_clientid_sub_2_topics(_Config) -> - Topic1 = <<"abc">>, TopicStr1 = binary_to_list(Topic1), - Payload1 = <<"123">>, - Uri1 = "coap://127.0.0.1/mqtt/"++TopicStr1++"?c=client1&u=tom&p=secret", - {ok, Pid1, N1, Code1, Content1} = er_coap_observer:observe(Uri1), - ?LOGT("observer 1 Pid=~p, N=~p, Code=~p, Content=~p", [Pid1, N1, Code1, Content1]), - - [SubPid] = emqx:subscribers(Topic1), - ?assert(is_pid(SubPid)), - - Topic2 = <<"x/y">>, TopicStr2 = emqx_http_lib:uri_encode(binary_to_list(Topic2)), - Payload2 = <<"456">>, - Uri2 = "coap://127.0.0.1/mqtt/"++TopicStr2++"?c=client1&u=tom&p=secret", - {ok, Pid2, N2, Code2, Content2} = er_coap_observer:observe(Uri2), - ?LOGT("observer 2 Pid=~p, N=~p, Code=~p, Content=~p", [Pid2, N2, Code2, Content2]), - - [SubPid] = emqx:subscribers(Topic2), - ?assert(is_pid(SubPid)), - - emqx:publish(emqx_message:make(Topic1, Payload1)), - - Notif1 = receive_notification(), - ?LOGT("observer 1 get Notif=~p", [Notif1]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv1}} = Notif1, - ?assertEqual(Payload1, PayloadRecv1), - - emqx:publish(emqx_message:make(Topic2, Payload2)), - - Notif2 = receive_notification(), - ?LOGT("observer 2 get Notif=~p", [Notif2]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv2}} = Notif2, - ?assertEqual(Payload2, PayloadRecv2), - - er_coap_observer:stop(Pid1), - er_coap_observer:stop(Pid2). - -t_invalid_parameter(_Config) -> - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %% "cid=client2" is invaid - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - Topic3 = <<"a/b">>, Payload3 = <<"ET629">>, - TopicStr3 = emqx_http_lib:uri_encode(binary_to_list(Topic3)), - URI3 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?cid=client2&u=tom&p=simple", - Reply3 = er_coap_client:request(put, URI3, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), - ?assertMatch({error,bad_request}, Reply3), - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %% "what=hello" is invaid - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - URI4 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?what=hello", - Reply4 = er_coap_client:request(put, URI4, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), - ?assertMatch({error, bad_request}, Reply4). - -t_invalid_topic(_Config) -> - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %% "a/b" is a valid topic string - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - Topic3 = <<"a/b">>, Payload3 = <<"ET629">>, - TopicStr3 = binary_to_list(Topic3), - URI3 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?c=client2&u=tom&p=simple", - Reply3 = er_coap_client:request(put, URI3, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), - ?assertMatch({ok,changed,_Content}, Reply3), - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - %% "+?#" is invaid topic string - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - URI4 = "coap://127.0.0.1/mqtt/"++"+?#"++"?what=hello", - Reply4 = er_coap_client:request(put, URI4, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), - ?assertMatch({error,bad_request}, Reply4). - -% mqtt connection kicked by coap with same client id -t_kick_1(_Config) -> - URI = "coap://127.0.0.1/mqtt/abc?c=clientid&u=tom&p=secret", - % workaround: emqx:subscribe does not kick same client id. - spawn_monitor(fun() -> - {ok, C} = emqtt:start_link([{host, "localhost"}, - {clientid, <<"clientid">>}, - {username, <<"plain">>}, - {password, <<"plain">>}]), - {ok, _} = emqtt:connect(C) end), - er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, - payload = <<"123">>}), - receive - {'DOWN', _, _, _, _} -> ok - after 2000 -> - ?assert(false) - end. - -% mqtt connection kicked by coap with same client id -t_acl(Config) -> - OldPath = emqx:get_env(plugins_etc_dir), - application:set_env(emqx, plugins_etc_dir, - emqx_ct_helpers:deps_path(emqx_authz, "test")), - Conf = #{<<"authz">> => - #{<<"rules">> => - [#{<<"principal">> =>#{<<"username">> => <<"coap">>}, - <<"permission">> => deny, - <<"topics">> => [<<"abc">>], - <<"action">> => <<"publish">>} - ]}}, - ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)), - application:ensure_all_started(emqx_authz), - - emqx:subscribe(<<"abc">>), - URI = "coap://127.0.0.1/mqtt/adbc?c=client1&u=coap&p=secret", - er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, - payload = <<"123">>}), - receive - _Something -> ?assert(false) - after 2000 -> - ok - end, - - ok = emqx_hooks:del('client.authorize', {emqx_authz, authorize}), - file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), - application:set_env(emqx, plugins_etc_dir, OldPath), - application:stop(emqx_authz). - -t_stats(_) -> - ok. - -t_auth_failure(_) -> - ok. - -t_qos_supprot(_) -> - ok. - -%%-------------------------------------------------------------------- -%% Helpers - -receive_notification() -> - receive - {coap_notify, Pid, N2, Code2, Content2} -> - {coap_notify, Pid, N2, Code2, Content2} - after 2000 -> - receive_notification_timeout - end. - -testdir(DataPath) -> - Ls = filename:split(DataPath), - filename:join(lists:sublist(Ls, 1, length(Ls) - 1)). diff --git a/apps/emqx_coap/test/emqx_coap_pubsub_SUITE.erl b/apps/emqx_coap/test/emqx_coap_pubsub_SUITE.erl deleted file mode 100644 index c018b9165..000000000 --- a/apps/emqx_coap/test/emqx_coap_pubsub_SUITE.erl +++ /dev/null @@ -1,677 +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_coap_pubsub_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("gen_coap/include/coap.hrl"). --include_lib("eunit/include/eunit.hrl"). --include_lib("emqx/include/emqx.hrl"). - --define(LOGT(Format, Args), ct:pal(Format, Args)). - -all() -> emqx_ct:all(?MODULE). - -init_per_suite(Config) -> - emqx_ct_helpers:start_apps([emqx_coap], fun set_special_cfg/1), - Config. - -set_special_cfg(emqx_coap) -> - application:set_env(emqx_coap, enable_stats, true); -set_special_cfg(_) -> - ok. - -end_per_suite(Config) -> - emqx_ct_helpers:stop_apps([emqx_coap]), - Config. - -%%-------------------------------------------------------------------- -%% Test Cases -%%-------------------------------------------------------------------- - -t_update_max_age(_Config) -> - TopicInPayload = <<"topic1">>, - Payload = <<";ct=42">>, - Payload1 = <<";ct=50">>, - URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", - URI2 = "coap://127.0.0.1/ps/topic1"++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?assertEqual(60, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - timer:sleep(50), - - %% post to create the same topic but with different max age and ct value in payload - Reply1 = er_coap_client:request(post, URI, #coap_content{max_age = 70, format = <<"application/link-format">>, payload = Payload1}), - {ok,created, #coap_content{location_path = LocPath}} = Reply1, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{TopicInPayload, MaxAge2, CT2, _ResPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), - ?assertEqual(70, MaxAge2), - ?assertEqual(<<"50">>, CT2), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI2). - -t_create_subtopic(_Config) -> - TopicInPayload = <<"topic1">>, - TopicInPayloadStr = "topic1", - Payload = <<";ct=42">>, - URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", - RealURI = "coap://127.0.0.1/ps/topic1"++"?c=client1&u=tom&p=secret", - - Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?assertEqual(60, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - timer:sleep(50), - - %% post to create the a sub topic - SubPayload = <<";ct=42">>, - SubTopicInPayloadStr = "subtopic", - SubURI = "coap://127.0.0.1/ps/"++TopicInPayloadStr++"?c=client1&u=tom&p=secret", - SubRealURI = "coap://127.0.0.1/ps/"++TopicInPayloadStr++"/"++SubTopicInPayloadStr++"?c=client1&u=tom&p=secret", - FullTopic = list_to_binary(TopicInPayloadStr++"/"++SubTopicInPayloadStr), - Reply1 = er_coap_client:request(post, SubURI, #coap_content{format = <<"application/link-format">>, payload = SubPayload}), - ?LOGT("Reply =~p", [Reply1]), - {ok,created, #coap_content{location_path = LocPath1}} = Reply1, - ?assertEqual([<<"/ps/topic1/subtopic">>] ,LocPath1), - [{FullTopic, MaxAge2, CT2, _ResPayload, _}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), - ?assertEqual(60, MaxAge2), - ?assertEqual(<<"42">>, CT2), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, SubRealURI), - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, RealURI). - -t_over_max_age(_Config) -> - TopicInPayload = <<"topic1">>, - Payload = <<";ct=42">>, - URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{max_age = 2, format = <<"application/link-format">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?assertEqual(2, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - timer:sleep(3000), - ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(TopicInPayload)). - -t_refreash_max_age(_Config) -> - TopicInPayload = <<"topic1">>, - Payload = <<";ct=42">>, - Payload1 = <<";ct=50">>, - URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", - RealURI = "coap://127.0.0.1/ps/topic1"++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/link-format">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?LOGT("TimeStamp=~p", [TimeStamp]), - ?assertEqual(5, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - timer:sleep(3000), - - %% post to create the same topic, the max age timer will be restarted with the new max age value - Reply1 = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/link-format">>, payload = Payload1}), - {ok,created, #coap_content{location_path = LocPath}} = Reply1, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{TopicInPayload, MaxAge2, CT2, _ResPayload, TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), - ?LOGT("TimeStamp1=~p", [TimeStamp1]), - ?assertEqual(5, MaxAge2), - ?assertEqual(<<"50">>, CT2), - - timer:sleep(3000), - ?assertEqual(false, emqx_coap_pubsub_topics:is_topic_timeout(TopicInPayload)), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, RealURI). - -t_case01_publish_post(_Config) -> - timer:sleep(100), - MainTopic = <<"maintopic">>, - TopicInPayload = <<"topic1">>, - Payload = <<";ct=42">>, - MainTopicStr = binary_to_list(MainTopic), - - %% post to create topic maintopic/topic1 - URI1 = "coap://127.0.0.1/ps/"++MainTopicStr++"?c=client1&u=tom&p=secret", - FullTopic = list_to_binary(MainTopicStr++"/"++binary_to_list(TopicInPayload)), - Reply1 = er_coap_client:request(post, URI1, #coap_content{format = <<"application/link-format">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply1]), - {ok,created, #coap_content{location_path = LocPath1}} = Reply1, - ?assertEqual([<<"/ps/maintopic/topic1">>] ,LocPath1), - [{FullTopic, MaxAge, CT2, <<>>, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), - ?assertEqual(60, MaxAge), - ?assertEqual(<<"42">>, CT2), - - %% post to publish message to topic maintopic/topic1 - FullTopicStr = emqx_http_lib:uri_encode(binary_to_list(FullTopic)), - URI2 = "coap://127.0.0.1/ps/"++FullTopicStr++"?c=client1&u=tom&p=secret", - PubPayload = <<"PUBLISH">>, - - %% Sub topic first - emqx:subscribe(FullTopic), - - Reply2 = er_coap_client:request(post, URI2, #coap_content{format = <<"application/octet-stream">>, payload = PubPayload}), - ?LOGT("Reply =~p", [Reply2]), - {ok,changed, _} = Reply2, - TopicInfo = [{FullTopic, MaxAge, CT2, PubPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), - ?LOGT("the topic info =~p", [TopicInfo]), - - assert_recv(FullTopic, PubPayload), - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI2). - -t_case02_publish_post(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"payload">>, - - %% Sub topic first - emqx:subscribe(Topic), - - %% post to publish a new topic "topic1", and the topic is created - URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(60, MaxAge), - ?assertEqual(<<"42">>, CT), - - assert_recv(Topic, Payload), - - %% post to publish a new message to the same topic "topic1" with different payload - NewPayload = <<"newpayload">>, - Reply1 = er_coap_client:request(post, URI, #coap_content{format = <<"application/octet-stream">>, payload = NewPayload}), - ?LOGT("Reply =~p", [Reply1]), - {ok,changed, _} = Reply1, - [{Topic, MaxAge, CT, NewPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - - assert_recv(Topic, NewPayload), - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case03_publish_post(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"payload">>, - - %% Sub topic first - emqx:subscribe(Topic), - - %% post to publish a new topic "topic1", and the topic is created - URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(60, MaxAge), - ?assertEqual(<<"42">>, CT), - - assert_recv(Topic, Payload), - - %% post to publish a new message to the same topic "topic1", but the ct is not same as created - NewPayload = <<"newpayload">>, - Reply1 = er_coap_client:request(post, URI, #coap_content{format = <<"application/exi">>, payload = NewPayload}), - ?LOGT("Reply =~p", [Reply1]), - ?assertEqual({error,bad_request}, Reply1), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case04_publish_post(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"payload">>, - - %% post to publish a new topic "topic1", and the topic is created - URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(5, MaxAge), - ?assertEqual(<<"42">>, CT), - - %% after max age timeout, the topic still exists but the status is timeout - timer:sleep(6000), - ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case01_publish_put(_Config) -> - MainTopic = <<"maintopic">>, - TopicInPayload = <<"topic1">>, - Payload = <<";ct=42">>, - MainTopicStr = binary_to_list(MainTopic), - - %% post to create topic maintopic/topic1 - URI1 = "coap://127.0.0.1/ps/"++MainTopicStr++"?c=client1&u=tom&p=secret", - FullTopic = list_to_binary(MainTopicStr++"/"++binary_to_list(TopicInPayload)), - Reply1 = er_coap_client:request(post, URI1, #coap_content{format = <<"application/link-format">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply1]), - {ok,created, #coap_content{location_path = LocPath1}} = Reply1, - ?assertEqual([<<"/ps/maintopic/topic1">>] ,LocPath1), - [{FullTopic, MaxAge, CT2, <<>>, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), - ?assertEqual(60, MaxAge), - ?assertEqual(<<"42">>, CT2), - - %% put to publish message to topic maintopic/topic1 - FullTopicStr = emqx_http_lib:uri_encode(binary_to_list(FullTopic)), - URI2 = "coap://127.0.0.1/ps/"++FullTopicStr++"?c=client1&u=tom&p=secret", - PubPayload = <<"PUBLISH">>, - - %% Sub topic first - emqx:subscribe(FullTopic), - - Reply2 = er_coap_client:request(put, URI2, #coap_content{format = <<"application/octet-stream">>, payload = PubPayload}), - ?LOGT("Reply =~p", [Reply2]), - {ok,changed, _} = Reply2, - [{FullTopic, MaxAge, CT2, PubPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), - - assert_recv(FullTopic, PubPayload), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI2). - -t_case02_publish_put(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"payload">>, - - %% Sub topic first - emqx:subscribe(Topic), - - %% put to publish a new topic "topic1", and the topic is created - URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(60, MaxAge), - ?assertEqual(<<"42">>, CT), - - assert_recv(Topic, Payload), - - %% put to publish a new message to the same topic "topic1" with different payload - NewPayload = <<"newpayload">>, - Reply1 = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = NewPayload}), - ?LOGT("Reply =~p", [Reply1]), - {ok,changed, _} = Reply1, - [{Topic, MaxAge, CT, NewPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - - assert_recv(Topic, NewPayload), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case03_publish_put(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"payload">>, - - %% Sub topic first - emqx:subscribe(Topic), - - %% put to publish a new topic "topic1", and the topic is created - URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(60, MaxAge), - ?assertEqual(<<"42">>, CT), - - assert_recv(Topic, Payload), - - %% put to publish a new message to the same topic "topic1", but the ct is not same as created - NewPayload = <<"newpayload">>, - Reply1 = er_coap_client:request(put, URI, #coap_content{format = <<"application/exi">>, payload = NewPayload}), - ?LOGT("Reply =~p", [Reply1]), - ?assertEqual({error,bad_request}, Reply1), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case04_publish_put(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"payload">>, - - %% put to publish a new topic "topic1", and the topic is created - URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(put, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/topic1">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(5, MaxAge), - ?assertEqual(<<"42">>, CT), - - %% after max age timeout, no publish message to the same topic, the topic info will be deleted - %%%%%%%%%%%%%%%%%%%%%%%%%% - % but there is one thing to do is we don't count in the publish message received from emqx(from other node).TBD!!!!!!!!!!!!! - %%%%%%%%%%%%%%%%%%%%%%%%%% - timer:sleep(6000), - ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case01_subscribe(_Config) -> - Topic = <<"topic1">>, - Payload1 = <<";ct=42">>, - timer:sleep(100), - - %% First post to create a topic "topic1" - Uri = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/link-format">>, payload = Payload1}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = [LocPath]}} = Reply, - ?assertEqual(<<"/ps/topic1">> ,LocPath), - TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?assertEqual(60, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - %% Subscribe the topic - Uri1 = "coap://127.0.0.1"++binary_to_list(LocPath)++"?c=client1&u=tom&p=secret", - {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri1), - ?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]), - - [SubPid] = emqx:subscribers(Topic), - ?assert(is_pid(SubPid)), - - %% Publish a message - Payload = <<"123">>, - emqx:publish(emqx_message:make(Topic, Payload)), - - Notif = receive_notification(), - ?LOGT("observer get Notif=~p", [Notif]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv}} = Notif, - - ?assertEqual(Payload, PayloadRecv), - - %% GET to read the publish message of the topic - Reply1 = er_coap_client:request(get, Uri1), - ?LOGT("Reply=~p", [Reply1]), - {ok,content, #coap_content{payload = <<"123">>}} = Reply1, - - er_coap_observer:stop(Pid), - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri1). - -t_case02_subscribe(_Config) -> - Topic = <<"a/b">>, - TopicStr = binary_to_list(Topic), - PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), - Payload = <<"payload">>, - - %% post to publish a new topic "a/b", and the topic is created - URI = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/a/b">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(5, MaxAge), - ?assertEqual(<<"42">>, CT), - - %% Wait for the max age of the timer expires - timer:sleep(6000), - ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), - - %% Subscribe to the timeout topic "a/b", still successfully,got {ok, nocontent} Method - Uri = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", - Reply1 = {ok, Pid, _N, nocontent, _} = er_coap_observer:observe(Uri), - ?LOGT("Subscribe Reply=~p", [Reply1]), - - [SubPid] = emqx:subscribers(Topic), - ?assert(is_pid(SubPid)), - - %% put to publish to topic "a/b" - Reply2 = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - {ok,changed, #coap_content{}} = Reply2, - [{Topic, MaxAge1, CT, Payload, TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(60, MaxAge1), - ?assertEqual(<<"42">>, CT), - ?assertEqual(false, TimeStamp =:= timeout), - - %% Publish a message - emqx:publish(emqx_message:make(Topic, Payload)), - - Notif = receive_notification(), - ?LOGT("observer get Notif=~p", [Notif]), - {coap_notify, _, _, {ok,content}, #coap_content{payload = Payload}} = Notif, - - er_coap_observer:stop(Pid), - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case03_subscribe(_Config) -> - %% Subscribe to the unexisted topic "a/b", got not_found - Topic = <<"a/b">>, - TopicStr = binary_to_list(Topic), - PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), - Uri = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", - {error, not_found} = er_coap_observer:observe(Uri), - - [] = emqx:subscribers(Topic). - -t_case04_subscribe(_Config) -> - %% Subscribe to the wildcad topic "+/b", got bad_request - Topic = <<"+/b">>, - TopicStr = binary_to_list(Topic), - PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), - Uri = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", - {error, bad_request} = er_coap_observer:observe(Uri), - - [] = emqx:subscribers(Topic). - -t_case01_read(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"PubPayload">>, - timer:sleep(100), - - %% First post to create a topic "topic1" - Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = [LocPath]}} = Reply, - ?assertEqual(<<"/ps/topic1">> ,LocPath), - TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?assertEqual(60, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - %% GET to read the publish message of the topic - timer:sleep(1000), - Reply1 = er_coap_client:request(get, Uri), - ?LOGT("Reply=~p", [Reply1]), - {ok,content, #coap_content{payload = Payload}} = Reply1, - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri). - -t_case02_read(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"PubPayload">>, - timer:sleep(100), - - %% First post to publish a topic "topic1" - Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = [LocPath]}} = Reply, - ?assertEqual(<<"/ps/topic1">> ,LocPath), - TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?assertEqual(60, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - %% GET to read the publish message of unmatched format, got bad_request - Reply1 = er_coap_client:request(get, Uri, #coap_content{format = <<"application/json">>}), - ?LOGT("Reply=~p", [Reply1]), - {error, bad_request} = Reply1, - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri). - -t_case03_read(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - timer:sleep(100), - - %% GET to read the nexisted topic "topic1", got not_found - Reply = er_coap_client:request(get, Uri), - ?LOGT("Reply=~p", [Reply]), - {error, not_found} = Reply. - -t_case04_read(_Config) -> - Topic = <<"topic1">>, - TopicStr = binary_to_list(Topic), - Payload = <<"PubPayload">>, - timer:sleep(100), - - %% First post to publish a topic "topic1" - Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = [LocPath]}} = Reply, - ?assertEqual(<<"/ps/topic1">> ,LocPath), - TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?LOGT("lookup topic info=~p", [TopicInfo]), - ?assertEqual(60, MaxAge1), - ?assertEqual(<<"42">>, CT1), - - %% GET to read the publish message of wildcard topic, got bad_request - WildTopic = binary_to_list(<<"+/topic1">>), - Uri1 = "coap://127.0.0.1/ps/"++WildTopic++"?c=client1&u=tom&p=secret", - Reply1 = er_coap_client:request(get, Uri1, #coap_content{format = <<"application/json">>}), - ?LOGT("Reply=~p", [Reply1]), - {error, bad_request} = Reply1, - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri). - -t_case05_read(_Config) -> - Topic = <<"a/b">>, - TopicStr = binary_to_list(Topic), - PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), - Payload = <<"payload">>, - - %% post to publish a new topic "a/b", and the topic is created - URI = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", - Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/a/b">>] ,LocPath), - [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), - ?assertEqual(5, MaxAge), - ?assertEqual(<<"42">>, CT), - - %% Wait for the max age of the timer expires - timer:sleep(6000), - ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), - - %% GET to read the expired publish message, supposed to get {ok, nocontent}, but now got {ok, content} - Reply1 = er_coap_client:request(get, URI), - ?LOGT("Reply=~p", [Reply1]), - {ok, content, #coap_content{payload = <<>>}}= Reply1, - - {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). - -t_case01_delete(_Config) -> - TopicInPayload = <<"a/b">>, - TopicStr = binary_to_list(TopicInPayload), - PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), - Payload = list_to_binary("<"++PercentEncodedTopic++">;ct=42"), - URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", - - %% Client post to CREATE topic "a/b" - Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload}), - ?LOGT("Reply =~p", [Reply]), - {ok,created, #coap_content{location_path = LocPath}} = Reply, - ?assertEqual([<<"/ps/a/b">>] ,LocPath), - - %% Client post to CREATE topic "a/b/c" - TopicInPayload1 = <<"a/b/c">>, - PercentEncodedTopic1 = emqx_http_lib:uri_encode(binary_to_list(TopicInPayload1)), - Payload1 = list_to_binary("<"++PercentEncodedTopic1++">;ct=42"), - Reply1 = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload1}), - ?LOGT("Reply =~p", [Reply1]), - {ok,created, #coap_content{location_path = LocPath1}} = Reply1, - ?assertEqual([<<"/ps/a/b/c">>] ,LocPath1), - - timer:sleep(50), - - %% DELETE the topic "a/b" - UriD = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", - ReplyD = er_coap_client:request(delete, UriD), - ?LOGT("Reply=~p", [ReplyD]), - {ok, deleted, #coap_content{}}= ReplyD, - - timer:sleep(300), %% Waiting gen_server:cast/2 for deleting operation - ?assertEqual(false, emqx_coap_pubsub_topics:is_topic_existed(TopicInPayload)), - ?assertEqual(false, emqx_coap_pubsub_topics:is_topic_existed(TopicInPayload1)). - -t_case02_delete(_Config) -> - TopicInPayload = <<"a/b">>, - TopicStr = binary_to_list(TopicInPayload), - PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), - - %% DELETE the unexisted topic "a/b" - Uri1 = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", - Reply1 = er_coap_client:request(delete, Uri1), - ?LOGT("Reply=~p", [Reply1]), - {error, not_found} = Reply1. - -t_case13_emit_stats_test(_Config) -> - ok. - -%%-------------------------------------------------------------------- -%% Internal functions - -receive_notification() -> - receive - {coap_notify, Pid, N2, Code2, Content2} -> - {coap_notify, Pid, N2, Code2, Content2} - after 2000 -> - receive_notification_timeout - end. - -assert_recv(Topic, Payload) -> - receive - {deliver, _, Msg} -> - ?assertEqual(Topic, Msg#message.topic), - ?assertEqual(Payload, Msg#message.payload) - after - 500 -> - ?assert(false) - end. - diff --git a/apps/emqx_exhook/.gitignore b/apps/emqx_exhook/.gitignore deleted file mode 100644 index da1f0db23..000000000 --- a/apps/emqx_exhook/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -.rebar3 -_* -.eunit -*.o -*.beam -*.plt -*.swp -*.swo -.erlang.cookie -ebin -log -erl_crash.dump -.rebar -logs -_build -.idea -*.iml -rebar3.crashdump -*~ -rebar.lock -data/ -*.conf.rendered -*.pyc -.DS_Store -*.class -Mnesia.nonode@nohost/ -src/emqx_exhook_pb.erl -src/emqx_exhook_v_1_hook_provider_client.erl -src/emqx_exhook_v_1_hook_provider_bhvr.erl diff --git a/apps/emqx_exhook/docs/design-cn.md b/apps/emqx_exhook/docs/design-cn.md deleted file mode 100644 index 6686e96e3..000000000 --- a/apps/emqx_exhook/docs/design-cn.md +++ /dev/null @@ -1,116 +0,0 @@ -# 设计 - -## 动机 - -在 EMQ X Broker v4.1-v4.2 中,我们发布了 2 个插件来扩展 emqx 的编程能力: - -1. `emqx-extension-hook` 提供了使用 Java, Python 向 Broker 挂载钩子的功能 -2. `emqx-exproto` 提供了使用 Java,Python 编写用户自定义协议接入插件的功能 - -但在后续的支持中发现许多难以处理的问题: - -1. 有大量的编程语言需要支持,需要编写和维护如 Go, JavaScript, Lua.. 等语言的驱动。 -2. `erlport` 使用的操作系统的管道进行通信,这让用户代码只能部署在和 emqx 同一个操作系统上。部署方式受到了极大的限制。 -3. 用户程序的启动参数直接打包到 Broker 中,导致用户开发无法实时的进行调试,单步跟踪等。 -4. `erlport` 会占用 `stdin` `stdout`。 - -因此,我们计划重构这部分的实现,其中主要的内容是: -1. 使用 `gRPC` 替换 `erlport`。 -2. 将 `emqx-extension-hook` 重命名为 `emqx-exhook` - - -旧版本的设计:[emqx-extension-hook design in v4.2.0](https://github.com/emqx/emqx-exhook/blob/v4.2.0/docs/design.md) - -## 设计 - -架构如下: - -``` - EMQ X -+========================+ +========+==========+ -| ExHook | | | | -| +----------------+ | gRPC | gRPC | User's | -| | gRPC Client | ------------------> | Server | Codes | -| +----------------+ | (HTTP/2) | | | -| | | | | -+========================+ +========+==========+ -``` - -`emqx-exhook` 通过 gRPC 的方式向用户部署的 gRPC 服务发送钩子的请求,并处理其返回的值。 - - -和 emqx 原生的钩子一致,emqx-exhook 也按照链式的方式执行: - - - -### gRPC 服务示例 - -用户需要实现的方法,和数据类型的定义在 `priv/protos/exhook.proto` 文件中: - -```protobuff -syntax = "proto3"; - -package emqx.exhook.v1; - -service HookProvider { - - rpc OnProviderLoaded(ProviderLoadedRequest) returns (LoadedResponse) {}; - - rpc OnProviderUnloaded(ProviderUnloadedRequest) returns (EmptySuccess) {}; - - rpc OnClientConnect(ClientConnectRequest) returns (EmptySuccess) {}; - - rpc OnClientConnack(ClientConnackRequest) returns (EmptySuccess) {}; - - rpc OnClientConnected(ClientConnectedRequest) returns (EmptySuccess) {}; - - rpc OnClientDisconnected(ClientDisconnectedRequest) returns (EmptySuccess) {}; - - rpc OnClientAuthenticate(ClientAuthenticateRequest) returns (ValuedResponse) {}; - - rpc OnClientCheckAcl(ClientCheckAclRequest) returns (ValuedResponse) {}; - - rpc OnClientSubscribe(ClientSubscribeRequest) returns (EmptySuccess) {}; - - rpc OnClientUnsubscribe(ClientUnsubscribeRequest) returns (EmptySuccess) {}; - - rpc OnSessionCreated(SessionCreatedRequest) returns (EmptySuccess) {}; - - rpc OnSessionSubscribed(SessionSubscribedRequest) returns (EmptySuccess) {}; - - rpc OnSessionUnsubscribed(SessionUnsubscribedRequest) returns (EmptySuccess) {}; - - rpc OnSessionResumed(SessionResumedRequest) returns (EmptySuccess) {}; - - rpc OnSessionDiscarded(SessionDiscardedRequest) returns (EmptySuccess) {}; - - rpc OnSessionTakeovered(SessionTakeoveredRequest) returns (EmptySuccess) {}; - - rpc OnSessionTerminated(SessionTerminatedRequest) returns (EmptySuccess) {}; - - rpc OnMessagePublish(MessagePublishRequest) returns (ValuedResponse) {}; - - rpc OnMessageDelivered(MessageDeliveredRequest) returns (EmptySuccess) {}; - - rpc OnMessageDropped(MessageDroppedRequest) returns (EmptySuccess) {}; - - rpc OnMessageAcked(MessageAckedRequest) returns (EmptySuccess) {}; -} -``` - -### 配置文件示例 - -``` -## 配置 gRPC 服务地址 (HTTP) -## -## s1 为服务器的名称 -exhook.server.s1.url = http://127.0.0.1:9001 - -## 配置 gRPC 服务地址 (HTTPS) -## -## s2 为服务器名称 -exhook.server.s2.url = https://127.0.0.1:9002 -exhook.server.s2.cacertfile = ca.pem -exhook.server.s2.certfile = cert.pem -exhook.server.s2.keyfile = key.pem -``` diff --git a/apps/emqx_exhook/rebar.config b/apps/emqx_exhook/rebar.config deleted file mode 100644 index eafa20d85..000000000 --- a/apps/emqx_exhook/rebar.config +++ /dev/null @@ -1,48 +0,0 @@ -%%-*- mode: erlang -*- -{plugins, - [rebar3_proper, - {grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}} -]}. - -{deps, - [{grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.2"}}} -]}. - -{grpc, - [{protos, ["priv/protos"]}, - {gpb_opts, [{module_name_prefix, "emqx_"}, - {module_name_suffix, "_pb"}]} -]}. - -{provider_hooks, - [{pre, [{compile, {grpc, gen}}, - {clean, {grpc, clean}}]} -]}. - -{edoc_opts, [{preprocess, true}]}. - -{erl_opts, [warn_unused_vars, - warn_shadow_vars, - warn_unused_import, - warn_obsolete_guard, - debug_info, - {parse_transform}]}. - -{xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, deprecated_function_calls, - warnings_as_errors, deprecated_functions]}. -{xref_ignores, [emqx_exhook_pb]}. - -{cover_enabled, true}. -{cover_opts, [verbose]}. -{cover_export_enabled, true}. -{cover_excl_mods, [emqx_exhook_pb, - emqx_exhook_v_1_hook_provider_bhvr, - emqx_exhook_v_1_hook_provider_client]}. - -{profiles, - [{test, - [{deps, - []} - ]} -]}. diff --git a/apps/emqx_exhook/src/emqx_exhook.appup.src b/apps/emqx_exhook/src/emqx_exhook.appup.src deleted file mode 100644 index 26e84d88f..000000000 --- a/apps/emqx_exhook/src/emqx_exhook.appup.src +++ /dev/null @@ -1,23 +0,0 @@ -%% -*-: erlang -*- -{VSN, - [ - {"4.3.1", [ - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} - ]}, - {"4.3.0", [ - {load_module, emqx_exhook_pb, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ], - [ - {"4.3.1", [ - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} - ]}, - {"4.3.0", [ - {load_module, emqx_exhook_pb, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ] -}. diff --git a/apps/emqx_exhook/test/emqx_exhook_SUITE.erl b/apps/emqx_exhook/test/emqx_exhook_SUITE.erl deleted file mode 100644 index 5d5a396a5..000000000 --- a/apps/emqx_exhook/test/emqx_exhook_SUITE.erl +++ /dev/null @@ -1,96 +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_exhook_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). - -%%-------------------------------------------------------------------- -%% Setups -%%-------------------------------------------------------------------- - -all() -> emqx_ct:all(?MODULE). - -init_per_suite(Cfg) -> - _ = emqx_exhook_demo_svr:start(), - emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1), - Cfg. - -end_per_suite(_Cfg) -> - emqx_ct_helpers:stop_apps([emqx_exhook]), - emqx_exhook_demo_svr:stop(). - -set_special_cfgs(emqx) -> - application:set_env(emqx, allow_anonymous, false), - application:set_env(emqx, enable_acl_cache, false), - application:set_env(emqx, plugins_loaded_file, undefined), - application:set_env(emqx, modules_loaded_file, undefined); -set_special_cfgs(emqx_exhook) -> - ok. - -%%-------------------------------------------------------------------- -%% Test cases -%%-------------------------------------------------------------------- - -t_noserver_nohook(_) -> - emqx_exhook:disable(default), - ?assertEqual([], ets:tab2list(emqx_hooks)), - - Opts = proplists:get_value( - default, - application:get_env(emqx_exhook, servers, []) - ), - ok = emqx_exhook:enable(default, Opts), - ?assertNotEqual([], ets:tab2list(emqx_hooks)). - -t_cli_list(_) -> - meck_print(), - ?assertEqual( [[emqx_exhook_server:format(Svr) || Svr <- emqx_exhook:list()]] - , emqx_exhook_cli:cli(["server", "list"]) - ), - unmeck_print(). - -t_cli_enable_disable(_) -> - meck_print(), - ?assertEqual([already_started], emqx_exhook_cli:cli(["server", "enable", "default"])), - ?assertEqual(ok, emqx_exhook_cli:cli(["server", "disable", "default"])), - ?assertEqual([], emqx_exhook_cli:cli(["server", "list"])), - - ?assertEqual([not_running], emqx_exhook_cli:cli(["server", "disable", "default"])), - ?assertEqual(ok, emqx_exhook_cli:cli(["server", "enable", "default"])), - unmeck_print(). - -t_cli_stats(_) -> - meck_print(), - _ = emqx_exhook_cli:cli(["server", "stats"]), - _ = emqx_exhook_cli:cli(x), - unmeck_print(). - -%%-------------------------------------------------------------------- -%% Utils -%%-------------------------------------------------------------------- - -meck_print() -> - meck:new(emqx_ctl, [passthrough, no_history, no_link]), - meck:expect(emqx_ctl, print, fun(_) -> ok end), - meck:expect(emqx_ctl, print, fun(_, Args) -> Args end). - -unmeck_print() -> - meck:unload(emqx_ctl). diff --git a/apps/emqx_exhook/test/emqx_exhook_demo_svr.erl b/apps/emqx_exhook/test/emqx_exhook_demo_svr.erl deleted file mode 100644 index 656788b5e..000000000 --- a/apps/emqx_exhook/test/emqx_exhook_demo_svr.erl +++ /dev/null @@ -1,339 +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_exhook_demo_svr). - --behavior(emqx_exhook_v_1_hook_provider_bhvr). - -%% --export([ start/0 - , stop/0 - , take/0 - , in/1 - ]). - -%% gRPC server HookProvider callbacks --export([ on_provider_loaded/2 - , on_provider_unloaded/2 - , on_client_connect/2 - , on_client_connack/2 - , on_client_connected/2 - , on_client_disconnected/2 - , on_client_authenticate/2 - , on_client_authorize/2 - , on_client_subscribe/2 - , on_client_unsubscribe/2 - , on_session_created/2 - , on_session_subscribed/2 - , on_session_unsubscribed/2 - , on_session_resumed/2 - , on_session_discarded/2 - , on_session_takeovered/2 - , on_session_terminated/2 - , on_message_publish/2 - , on_message_delivered/2 - , on_message_dropped/2 - , on_message_acked/2 - ]). - --define(PORT, 9000). --define(NAME, ?MODULE). - -%%-------------------------------------------------------------------- -%% Server APIs -%%-------------------------------------------------------------------- - -start() -> - Pid = spawn(fun mngr_main/0), - register(?MODULE, Pid), - {ok, Pid}. - -stop() -> - grpc:stop_server(?NAME), - ?MODULE ! stop. - -take() -> - ?MODULE ! {take, self()}, - receive {value, V} -> V - after 5000 -> error(timeout) end. - -in({FunName, Req}) -> - ?MODULE ! {in, FunName, Req}. - -mngr_main() -> - application:ensure_all_started(grpc), - Services = #{protos => [emqx_exhook_pb], - services => #{'emqx.exhook.v1.HookProvider' => emqx_exhook_demo_svr} - }, - Options = [], - Svr = grpc:start_server(?NAME, ?PORT, Services, Options), - mngr_loop([Svr, queue:new(), queue:new()]). - -mngr_loop([Svr, Q, Takes]) -> - receive - {in, FunName, Req} -> - {NQ1, NQ2} = reply(queue:in({FunName, Req}, Q), Takes), - mngr_loop([Svr, NQ1, NQ2]); - {take, From} -> - {NQ1, NQ2} = reply(Q, queue:in(From, Takes)), - mngr_loop([Svr, NQ1, NQ2]); - stop -> - exit(normal) - end. - -reply(Q1, Q2) -> - case queue:len(Q1) =:= 0 orelse - queue:len(Q2) =:= 0 of - true -> {Q1, Q2}; - _ -> - {{value, {Name, V}}, NQ1} = queue:out(Q1), - {{value, From}, NQ2} = queue:out(Q2), - From ! {value, {Name, V}}, - {NQ1, NQ2} - end. - -%%-------------------------------------------------------------------- -%% callbacks -%%-------------------------------------------------------------------- - --spec on_provider_loaded(emqx_exhook_pb:provider_loaded_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:loaded_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. - -on_provider_loaded(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{hooks => [ - #{name => <<"client.connect">>}, - #{name => <<"client.connack">>}, - #{name => <<"client.connected">>}, - #{name => <<"client.disconnected">>}, - #{name => <<"client.authenticate">>}, - #{name => <<"client.authorize">>}, - #{name => <<"client.subscribe">>}, - #{name => <<"client.unsubscribe">>}, - #{name => <<"session.created">>}, - #{name => <<"session.subscribed">>}, - #{name => <<"session.unsubscribed">>}, - #{name => <<"session.resumed">>}, - #{name => <<"session.discarded">>}, - #{name => <<"session.takeovered">>}, - #{name => <<"session.terminated">>}, - #{name => <<"message.publish">>}, - #{name => <<"message.delivered">>}, - #{name => <<"message.acked">>}, - #{name => <<"message.dropped">>}]}, Md}. --spec on_provider_unloaded(emqx_exhook_pb:provider_unloaded_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_provider_unloaded(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_client_connect(emqx_exhook_pb:client_connect_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_connect(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_client_connack(emqx_exhook_pb:client_connack_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_connack(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_client_connected(emqx_exhook_pb:client_connected_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_connected(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_client_disconnected(emqx_exhook_pb:client_disconnected_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_disconnected(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_client_authenticate(emqx_exhook_pb:client_authenticate_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_authenticate(#{clientinfo := #{username := Username}} = Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - %% some cases for testing - case Username of - <<"baduser">> -> - {ok, #{type => 'STOP_AND_RETURN', - value => {bool_result, false}}, Md}; - <<"gooduser">> -> - {ok, #{type => 'STOP_AND_RETURN', - value => {bool_result, true}}, Md}; - <<"normaluser">> -> - {ok, #{type => 'CONTINUE', - value => {bool_result, true}}, Md}; - _ -> - {ok, #{type => 'IGNORE'}, Md} - end. - --spec on_client_authorize(emqx_exhook_pb:client_authorize_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_authorize(#{clientinfo := #{username := Username}} = Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - %% some cases for testing - case Username of - <<"baduser">> -> - {ok, #{type => 'STOP_AND_RETURN', - value => {bool_result, false}}, Md}; - <<"gooduser">> -> - {ok, #{type => 'STOP_AND_RETURN', - value => {bool_result, true}}, Md}; - <<"normaluser">> -> - {ok, #{type => 'CONTINUE', - value => {bool_result, true}}, Md}; - _ -> - {ok, #{type => 'IGNORE'}, Md} - end. - --spec on_client_subscribe(emqx_exhook_pb:client_subscribe_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_subscribe(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_client_unsubscribe(emqx_exhook_pb:client_unsubscribe_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_client_unsubscribe(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_session_created(emqx_exhook_pb:session_created_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_session_created(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_session_subscribed(emqx_exhook_pb:session_subscribed_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_session_subscribed(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_session_unsubscribed(emqx_exhook_pb:session_unsubscribed_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_session_unsubscribed(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_session_resumed(emqx_exhook_pb:session_resumed_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_session_resumed(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_session_discarded(emqx_exhook_pb:session_discarded_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_session_discarded(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_session_takeovered(emqx_exhook_pb:session_takeovered_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_session_takeovered(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_session_terminated(emqx_exhook_pb:session_terminated_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_session_terminated(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_message_publish(emqx_exhook_pb:message_publish_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_message_publish(#{message := #{from := From} = Msg} = Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - %% some cases for testing - case From of - <<"baduser">> -> - NMsg = Msg#{qos => 0, - topic => <<"">>, - payload => <<"">> - }, - {ok, #{type => 'STOP_AND_RETURN', - value => {message, NMsg}}, Md}; - <<"gooduser">> -> - NMsg = Msg#{topic => From, - payload => From}, - {ok, #{type => 'STOP_AND_RETURN', - value => {message, NMsg}}, Md}; - _ -> - {ok, #{type => 'IGNORE'}, Md} - end. - --spec on_message_delivered(emqx_exhook_pb:message_delivered_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_message_delivered(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_message_dropped(emqx_exhook_pb:message_dropped_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_message_dropped(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. - --spec on_message_acked(emqx_exhook_pb:message_acked_request(), grpc:metadata()) - -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} - | {error, grpc_cowboy_h:error_response()}. -on_message_acked(Req, Md) -> - ?MODULE:in({?FUNCTION_NAME, Req}), - %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), - {ok, #{}, Md}. diff --git a/apps/emqx_exhook/test/props/prop_exhook_hooks.erl b/apps/emqx_exhook/test/props/prop_exhook_hooks.erl deleted file mode 100644 index 12f54eef6..000000000 --- a/apps/emqx_exhook/test/props/prop_exhook_hooks.erl +++ /dev/null @@ -1,531 +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(prop_exhook_hooks). - --include_lib("proper/include/proper.hrl"). --include_lib("eunit/include/eunit.hrl"). - --import(emqx_ct_proper_types, - [ conninfo/0 - , clientinfo/0 - , sessioninfo/0 - , message/0 - , connack_return_code/0 - , topictab/0 - , topic/0 - , subopts/0 - ]). - --define(ALL(Vars, Types, Exprs), - ?SETUP(fun() -> - State = do_setup(), - fun() -> do_teardown(State) end - end, ?FORALL(Vars, Types, Exprs))). - -%%-------------------------------------------------------------------- -%% Properties -%%-------------------------------------------------------------------- - -prop_client_connect() -> - ?ALL({ConnInfo, ConnProps}, - {conninfo(), conn_properties()}, - begin - ok = emqx_hooks:run('client.connect', [ConnInfo, ConnProps]), - {'on_client_connect', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{props => properties(ConnProps), - conninfo => from_conninfo(ConnInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_client_connack() -> - ?ALL({ConnInfo, Rc, AckProps}, - {conninfo(), connack_return_code(), ack_properties()}, - begin - ok = emqx_hooks:run('client.connack', [ConnInfo, Rc, AckProps]), - {'on_client_connack', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{props => properties(AckProps), - result_code => atom_to_binary(Rc, utf8), - conninfo => from_conninfo(ConnInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_client_authenticate() -> - ?ALL({ClientInfo0, AuthResult}, - {clientinfo(), authresult()}, - begin - ClientInfo = inject_magic_into(username, ClientInfo0), - OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult), - ExpectedAuthResult = case maps:get(username, ClientInfo) of - <<"baduser">> -> - AuthResult#{ - auth_result => not_authorized, - anonymous => false}; - <<"gooduser">> -> - AuthResult#{ - auth_result => success, - anonymous => false}; - <<"normaluser">> -> - AuthResult#{ - auth_result => success, - anonymous => false}; - _ -> - case maps:get(auth_result, AuthResult) of - success -> - #{auth_result => success, - anonymous => false}; - _ -> - #{auth_result => not_authorized, - anonymous => false} - end - end, - ?assertEqual(ExpectedAuthResult, OutAuthResult), - - {'on_client_authenticate', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{result => authresult_to_bool(AuthResult), - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_client_authorize() -> - ?ALL({ClientInfo0, PubSub, Topic, Result}, - {clientinfo(), oneof([publish, subscribe]), - topic(), oneof([allow, deny])}, - begin - ClientInfo = inject_magic_into(username, ClientInfo0), - OutResult = emqx_hooks:run_fold( - 'client.authorize', - [ClientInfo, PubSub, Topic], - Result), - ExpectedOutResult = case maps:get(username, ClientInfo) of - <<"baduser">> -> deny; - <<"gooduser">> -> allow; - <<"normaluser">> -> allow; - _ -> Result - end, - ?assertEqual(ExpectedOutResult, OutResult), - - {'on_client_authorize', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{result => aclresult_to_bool(Result), - type => pubsub_to_enum(PubSub), - topic => Topic, - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_client_connected() -> - ?ALL({ClientInfo, ConnInfo}, - {clientinfo(), conninfo()}, - begin - ok = emqx_hooks:run('client.connected', [ClientInfo, ConnInfo]), - {'on_client_connected', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_client_disconnected() -> - ?ALL({ClientInfo, Reason, ConnInfo}, - {clientinfo(), shutdown_reason(), conninfo()}, - begin - ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]), - {'on_client_disconnected', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{reason => stringfy(Reason), - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_client_subscribe() -> - ?ALL({ClientInfo, SubProps, TopicTab}, - {clientinfo(), sub_properties(), topictab()}, - begin - ok = emqx_hooks:run('client.subscribe', [ClientInfo, SubProps, TopicTab]), - {'on_client_subscribe', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{props => properties(SubProps), - topic_filters => topicfilters(TopicTab), - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_client_unsubscribe() -> - ?ALL({ClientInfo, UnSubProps, TopicTab}, - {clientinfo(), unsub_properties(), topictab()}, - begin - ok = emqx_hooks:run('client.unsubscribe', [ClientInfo, UnSubProps, TopicTab]), - {'on_client_unsubscribe', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{props => properties(UnSubProps), - topic_filters => topicfilters(TopicTab), - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_session_created() -> - ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, - begin - ok = emqx_hooks:run('session.created', [ClientInfo, SessInfo]), - {'on_session_created', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_session_subscribed() -> - ?ALL({ClientInfo, Topic, SubOpts}, - {clientinfo(), topic(), subopts()}, - begin - ok = emqx_hooks:run('session.subscribed', [ClientInfo, Topic, SubOpts]), - {'on_session_subscribed', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{topic => Topic, - subopts => subopts(SubOpts), - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_session_unsubscribed() -> - ?ALL({ClientInfo, Topic, SubOpts}, - {clientinfo(), topic(), subopts()}, - begin - ok = emqx_hooks:run('session.unsubscribed', [ClientInfo, Topic, SubOpts]), - {'on_session_unsubscribed', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{topic => Topic, - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_session_resumed() -> - ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, - begin - ok = emqx_hooks:run('session.resumed', [ClientInfo, SessInfo]), - {'on_session_resumed', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_session_discared() -> - ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, - begin - ok = emqx_hooks:run('session.discarded', [ClientInfo, SessInfo]), - {'on_session_discarded', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_session_takeovered() -> - ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, - begin - ok = emqx_hooks:run('session.takeovered', [ClientInfo, SessInfo]), - {'on_session_takeovered', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_session_terminated() -> - ?ALL({ClientInfo, Reason, SessInfo}, - {clientinfo(), shutdown_reason(), sessioninfo()}, - begin - ok = emqx_hooks:run('session.terminated', [ClientInfo, Reason, SessInfo]), - {'on_session_terminated', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{reason => stringfy(Reason), - clientinfo => from_clientinfo(ClientInfo) - }, - ?assertEqual(Expected, Resp), - true - end). - -prop_message_publish() -> - ?ALL(Msg0, message(), - begin - Msg = emqx_message:from_map( - inject_magic_into(from, emqx_message:to_map(Msg0))), - OutMsg= emqx_hooks:run_fold('message.publish', [], Msg), - case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of - true -> - ?assertEqual(Msg, OutMsg), - skip; - _ -> - ExpectedOutMsg = case emqx_message:from(Msg) of - <<"baduser">> -> - MsgMap = emqx_message:to_map(Msg), - emqx_message:from_map( - MsgMap#{qos => 0, - topic => <<"">>, - payload => <<"">> - }); - <<"gooduser">> = From -> - MsgMap = emqx_message:to_map(Msg), - emqx_message:from_map( - MsgMap#{topic => From, - payload => From - }); - _ -> Msg - end, - ?assertEqual(ExpectedOutMsg, OutMsg), - - {'on_message_publish', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{message => from_message(Msg) - }, - ?assertEqual(Expected, Resp) - end, - true - end). - -prop_message_dropped() -> - ?ALL({Msg, By, Reason}, {message(), hardcoded, shutdown_reason()}, - begin - ok = emqx_hooks:run('message.dropped', [Msg, By, Reason]), - case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of - true -> skip; - _ -> - {'on_message_dropped', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{reason => stringfy(Reason), - message => from_message(Msg) - }, - ?assertEqual(Expected, Resp) - end, - true - end). - -prop_message_delivered() -> - ?ALL({ClientInfo, Msg}, {clientinfo(), message()}, - begin - ok = emqx_hooks:run('message.delivered', [ClientInfo, Msg]), - case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of - true -> skip; - _ -> - {'on_message_delivered', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{clientinfo => from_clientinfo(ClientInfo), - message => from_message(Msg) - }, - ?assertEqual(Expected, Resp) - end, - true - end). - -prop_message_acked() -> - ?ALL({ClientInfo, Msg}, {clientinfo(), message()}, - begin - ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), - case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of - true -> skip; - _ -> - {'on_message_acked', Resp} = emqx_exhook_demo_svr:take(), - Expected = - #{clientinfo => from_clientinfo(ClientInfo), - message => from_message(Msg) - }, - ?assertEqual(Expected, Resp) - end, - true - end). - -nodestr() -> - stringfy(node()). - -peerhost(#{peername := {Host, _}}) -> - ntoa(Host). - -sockport(#{sockname := {_, Port}}) -> - Port. - -%% copied from emqx_exhook - -ntoa({0,0,0,0,0,16#ffff,AB,CD}) -> - list_to_binary(inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256})); -ntoa(IP) -> - list_to_binary(inet_parse:ntoa(IP)). - -maybe(undefined) -> <<>>; -maybe(B) -> B. - -properties(undefined) -> []; -properties(M) when is_map(M) -> - maps:fold(fun(K, V, Acc) -> - [#{name => stringfy(K), - value => stringfy(V)} | Acc] - end, [], M). - -topicfilters(Tfs) when is_list(Tfs) -> - [#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs]. - -%% @private -stringfy(Term) when is_binary(Term) -> - Term; -stringfy(Term) when is_integer(Term) -> - integer_to_binary(Term); -stringfy(Term) when is_atom(Term) -> - atom_to_binary(Term, utf8); -stringfy(Term) -> - unicode:characters_to_binary((io_lib:format("~0p", [Term]))). - -subopts(SubOpts) -> - #{qos => maps:get(qos, SubOpts, 0), - rh => maps:get(rh, SubOpts, 0), - rap => maps:get(rap, SubOpts, 0), - nl => maps:get(nl, SubOpts, 0), - share => maps:get(share, SubOpts, <<>>) - }. - -authresult_to_bool(AuthResult) -> - maps:get(auth_result, AuthResult, undefined) == success. - -aclresult_to_bool(Result) -> - Result == allow. - -pubsub_to_enum(publish) -> 'PUBLISH'; -pubsub_to_enum(subscribe) -> 'SUBSCRIBE'. - -from_conninfo(ConnInfo) -> - #{node => nodestr(), - clientid => maps:get(clientid, ConnInfo), - username => maybe(maps:get(username, ConnInfo, <<>>)), - peerhost => peerhost(ConnInfo), - sockport => sockport(ConnInfo), - proto_name => maps:get(proto_name, ConnInfo), - proto_ver => stringfy(maps:get(proto_ver, ConnInfo)), - keepalive => maps:get(keepalive, ConnInfo) - }. - -from_clientinfo(ClientInfo) -> - #{node => nodestr(), - clientid => maps:get(clientid, ClientInfo), - username => maybe(maps:get(username, ClientInfo, <<>>)), - password => maybe(maps:get(password, ClientInfo, <<>>)), - peerhost => ntoa(maps:get(peerhost, ClientInfo)), - sockport => maps:get(sockport, ClientInfo), - protocol => stringfy(maps:get(protocol, ClientInfo)), - mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)), - is_superuser => maps:get(is_superuser, ClientInfo, false), - anonymous => maps:get(anonymous, ClientInfo, true), - cn => maybe(maps:get(cn, ClientInfo, <<>>)), - dn => maybe(maps:get(dn, ClientInfo, <<>>)) - }. - -from_message(Msg) -> - #{node => nodestr(), - id => emqx_guid:to_hexstr(emqx_message:id(Msg)), - qos => emqx_message:qos(Msg), - from => stringfy(emqx_message:from(Msg)), - topic => emqx_message:topic(Msg), - payload => emqx_message:payload(Msg), - timestamp => emqx_message:timestamp(Msg) - }. - -%%-------------------------------------------------------------------- -%% Helper -%%-------------------------------------------------------------------- - -do_setup() -> - logger:set_primary_config(#{level => warning}), - _ = emqx_exhook_demo_svr:start(), - emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1), - %% waiting first loaded event - {'on_provider_loaded', _} = emqx_exhook_demo_svr:take(), - ok. - -do_teardown(_) -> - emqx_ct_helpers:stop_apps([emqx_exhook]), - %% waiting last unloaded event - {'on_provider_unloaded', _} = emqx_exhook_demo_svr:take(), - _ = emqx_exhook_demo_svr:stop(), - logger:set_primary_config(#{level => notice}), - timer:sleep(2000), - ok. - -set_special_cfgs(emqx) -> - application:set_env(emqx, allow_anonymous, false), - application:set_env(emqx, enable_acl_cache, false), - application:set_env(emqx, modules_loaded_file, undefined), - application:set_env(emqx, plugins_loaded_file, - emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins")); -set_special_cfgs(emqx_exhook) -> - ok. - -%%-------------------------------------------------------------------- -%% Generators -%%-------------------------------------------------------------------- - -conn_properties() -> - #{}. - -ack_properties() -> - #{}. - -sub_properties() -> - #{}. - -unsub_properties() -> - #{}. - -shutdown_reason() -> - oneof([utf8(), {shutdown, emqx_ct_proper_types:limited_atom()}]). - -authresult() -> - ?LET(RC, connack_return_code(), #{auth_result => RC}). - -inject_magic_into(Key, Object) -> - case castspell() of - muggles -> Object; - Spell -> - Object#{Key => Spell} - end. - -castspell() -> - L = [<<"baduser">>, <<"gooduser">>, <<"normaluser">>, muggles], - lists:nth(rand:uniform(length(L)), L). diff --git a/apps/emqx_exproto/.gitignore b/apps/emqx_exproto/.gitignore deleted file mode 100644 index 384f2255a..000000000 --- a/apps/emqx_exproto/.gitignore +++ /dev/null @@ -1,48 +0,0 @@ -.eunit -deps -!deps/.placeholder -*.o -*.beam -*.plt -erl_crash.dump -ebin -!ebin/.placeholder -.concrete/DEV_MODE -.rebar -test/ebin/*.beam -.exrc -plugins/*/ebin -log/ -*.swp -*.so -.erlang.mk/ -cover/ -emqx.d -eunit.coverdata -test/ct.cover.spec -logs -ct.coverdata -.idea/ -emqx.iml -_rel/ -data/ -_build -.rebar3 -rebar3.crashdump -.DS_Store -emqx.iml -bbmustache/ -etc/gen.emqx.conf -compile_commands.json -cuttlefish -rebar.lock -xrefr -erlang.mk -*.coverdata -etc/emqx_exproto.conf.rendered -Mnesia.*/ -src/emqx_exproto_pb.erl -src/emqx_exproto_v_1_connection_adapter_bhvr.erl -src/emqx_exproto_v_1_connection_adapter_client.erl -src/emqx_exproto_v_1_connection_handler_bhvr.erl -src/emqx_exproto_v_1_connection_handler_client.erl diff --git a/apps/emqx_exproto/docs/design-cn.md b/apps/emqx_exproto/docs/design-cn.md deleted file mode 100644 index 7af7dbdb3..000000000 --- a/apps/emqx_exproto/docs/design-cn.md +++ /dev/null @@ -1,127 +0,0 @@ -# 多语言 - 协议接入 - -`emqx-exproto` 插件用于协议解析的多语言支持。它能够允许其他编程语言(例如:Python,Java 等)直接处理数据流实现协议的解析,并提供 Pub/Sub 接口以实现与系统其它组件的通信。 - -该插件给 EMQ X 带来的扩展性十分的强大,它能以你熟悉语言处理任何的私有协议,并享受由 EMQ X 系统带来的高连接,和高并发的优点。 - -## 特性 - -- 极强的扩展能力。使用 gRPC 作为 RPC 通信框架,支持各个主流编程语言 -- 高吞吐。连接层以完全的异步非阻塞式 I/O 的方式实现 -- 连接层透明。完全的支持 TCP\TLS UDP\DTLS 类型的连接管理,并对上层提供统一的 API 接口 -- 连接层的管理能力。例如,最大连接数,连接和吞吐的速率限制,IP 黑名单 等 - -## 架构 - -![Extension-Protocol Arch](images/exproto-arch.jpg) - -该插件主要需要处理的内容包括: - -1. **连接层:** 该部分主要 **维持 Socket 的生命周期,和数据的收发**。它的功能要求包括: - - 监听某个端口。当有新的 TCP/UDP 连接到达后,启动一个连接进程,来维持连接的状态。 - - 调用 `OnSocketCreated` 回调。用于通知外部模块**已新建立了一个连接**。 - - 调用 `OnScoektClosed` 回调。用于通知外部模块连接**已关闭**。 - - 调用 `OnReceivedBytes` 回调。用于通知外部模块**该连接新收到的数据包**。 - - 提供 `Send` 接口。供外部模块调用,**用于发送数据包**。 - - 提供 `Close` 接口。供外部模块调用,**用于主动关闭连接**。 - -2. **协议/会话层:**该部分主要**提供 PUB/SUB 接口**,以实现与 EMQ X Broker 系统的消息互通。包括: - - - 提供 `Authenticate` 接口。供外部模块调用,用于向集群注册客户端。 - - 提供 `StartTimer` 接口。供外部模块调用,用于为该连接进程启动心跳等定时器。 - - 提供 `Publish` 接口。供外部模块调用,用于发布消息 EMQ X Broker 中。 - - 提供 `Subscribe` 接口。供外部模块调用,用于订阅某主题,以实现从 EMQ X Broker 中接收某些下行消息。 - - 提供 `Unsubscribe` 接口。供外部模块调用,用于取消订阅某主题。 - - 调用 `OnTimerTimeout` 回调。用于处理定时器超时的事件。 - - 调用 `OnReceivedMessages` 回调。用于接收下行消息(在订阅主题成功后,如果主题上有消息,便会回调该方法) - -## 接口设计 - -从 gRPC 上的逻辑来说,emqx-exproto 会作为客户端向用户的 `ConnectionHandler` 服务发送回调请求。同时,它也会作为服务端向用户提供 `ConnectionAdapter` 服务,以提供 emqx-exproto 各个接口的访问。如图: - -![Extension Protocol gRPC Arch](images/exproto-grpc-arch.jpg) - - -详情参见:`priv/protos/exproto.proto`,例如接口的定义有: - -```protobuff -syntax = "proto3"; - -package emqx.exproto.v1; - -// The Broker side serivce. It provides a set of APIs to -// handle a protcol access -service ConnectionAdapter { - - // -- socket layer - - rpc Send(SendBytesRequest) returns (CodeResponse) {}; - - rpc Close(CloseSocketRequest) returns (CodeResponse) {}; - - // -- protocol layer - - rpc Authenticate(AuthenticateRequest) returns (CodeResponse) {}; - - rpc StartTimer(TimerRequest) returns (CodeResponse) {}; - - // -- pub/sub layer - - rpc Publish(PublishRequest) returns (CodeResponse) {}; - - rpc Subscribe(SubscribeRequest) returns (CodeResponse) {}; - - rpc Unsubscribe(UnsubscribeRequest) returns (CodeResponse) {}; -} - -service ConnectionHandler { - - // -- socket layer - - rpc OnSocketCreated(stream SocketCreatedRequest) returns (EmptySuccess) {}; - - rpc OnSocketClosed(stream SocketClosedRequest) returns (EmptySuccess) {}; - - rpc OnReceivedBytes(stream ReceivedBytesRequest) returns (EmptySuccess) {}; - - // -- pub/sub layer - - rpc OnTimerTimeout(stream TimerTimeoutRequest) returns (EmptySuccess) {}; - - rpc OnReceivedMessages(stream ReceivedMessagesRequest) returns (EmptySuccess) {}; -} -``` - -## 配置项设计 - -1. 以 **监听器(Listener)** 为基础,提供 TCP/UDP 的监听。 - - Listener 目前仅支持:TCP、TLS、UDP、DTLS。(ws、wss、quic 暂不支持) -2. 每个监听器,会指定一个 `ConnectionHandler` 的服务地址,用于调用外部模块的接口。 -3. emqx-exproto 还会监听一个 gRPC 端口用于提供对 `ConnectionAdapter` 服务的访问。 - -例如: - -``` properties -## gRPC 服务监听地址 (HTTP) -## -exproto.server.http.url = http://127.0.0.1:9002 - -## gRPC 服务监听地址 (HTTPS) -## -exproto.server.https.url = https://127.0.0.1:9002 -exproto.server.https.cacertfile = ca.pem -exproto.server.https.certfile = cert.pem -exproto.server.https.keyfile = key.pem - -## Listener 配置 -## 例如,名称为 protoname 协议的 TCP 监听器配置 -exproto.listener.protoname = tcp://0.0.0.0:7993 - -## ConnectionHandler 服务地址及 https 的证书配置 -exproto.listener.protoname.connection_handler_url = http://127.0.0.1:9001 -#exproto.listener.protoname.connection_handler_certfile = -#exproto.listener.protoname.connection_handler_cacertfile = -#exproto.listener.protoname.connection_handler_keyfile = - -# ... -``` diff --git a/apps/emqx_exproto/docs/images/exproto-arch.jpg b/apps/emqx_exproto/docs/images/exproto-arch.jpg deleted file mode 100644 index dddf7996b..000000000 Binary files a/apps/emqx_exproto/docs/images/exproto-arch.jpg and /dev/null differ diff --git a/apps/emqx_exproto/docs/images/exproto-grpc-arch.jpg b/apps/emqx_exproto/docs/images/exproto-grpc-arch.jpg deleted file mode 100644 index 71efa76f9..000000000 Binary files a/apps/emqx_exproto/docs/images/exproto-grpc-arch.jpg and /dev/null differ diff --git a/apps/emqx_exproto/rebar.config b/apps/emqx_exproto/rebar.config deleted file mode 100644 index 3fa9f6f8a..000000000 --- a/apps/emqx_exproto/rebar.config +++ /dev/null @@ -1,51 +0,0 @@ -%%-*- mode: erlang -*- -{edoc_opts, [{preprocess, true}]}. - -{erl_opts, [warn_unused_vars, - warn_shadow_vars, - warn_unused_import, - warn_obsolete_guard, - debug_info, - {parse_transform}]}. -{plugins, - [rebar3_proper, - {grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}} -]}. - -{deps, - [{grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.2"}}} - ]}. - -{grpc, - [{type, all}, - {protos, ["priv/protos"]}, - {gpb_opts, [{module_name_prefix, "emqx_"}, - {module_name_suffix, "_pb"}]} -]}. - -{provider_hooks, - [{pre, [{compile, {grpc, gen}}, - {clean, {grpc, clean}}]} -]}. - -{xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, deprecated_function_calls, - warnings_as_errors, deprecated_functions]}. - -{xref_ignores, [emqx_exproto_pb]}. - -{cover_enabled, true}. -{cover_opts, [verbose]}. -{cover_export_enabled, true}. -{cover_excl_mods, [emqx_exproto_pb, - emqx_exproto_v_1_connection_adapter_client, - emqx_exproto_v_1_connection_adapter_bhvr, - emqx_exproto_v_1_connection_handler_client, - emqx_exproto_v_1_connection_handler_bhvr]}. - -{profiles, - [{test, - [{deps, - []} - ]} -]}. diff --git a/apps/emqx_exproto/test/emqx_exproto_SUITE.erl b/apps/emqx_exproto/test/emqx_exproto_SUITE.erl deleted file mode 100644 index e38347e5e..000000000 --- a/apps/emqx_exproto/test/emqx_exproto_SUITE.erl +++ /dev/null @@ -1,454 +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_exproto_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --import(emqx_exproto_echo_svr, - [ frame_connect/2 - , frame_connack/1 - , frame_publish/3 - , frame_puback/1 - , frame_subscribe/2 - , frame_suback/1 - , frame_unsubscribe/1 - , frame_unsuback/1 - , frame_disconnect/0 - ]). - --include_lib("emqx/include/emqx.hrl"). --include_lib("emqx/include/emqx_mqtt.hrl"). - --define(TCPOPTS, [binary, {active, false}]). --define(DTLSOPTS, [binary, {active, false}, {protocol, dtls}]). - -%%-------------------------------------------------------------------- -%% Setups -%%-------------------------------------------------------------------- - -all() -> - [{group, Name} || Name <- metrics()]. - -groups() -> - Cases = emqx_ct:all(?MODULE), - [{Name, Cases} || Name <- metrics()]. - -%% @private -metrics() -> - [tcp, ssl, udp, dtls]. - -init_per_group(GrpName, Cfg) -> - put(grpname, GrpName), - Svrs = emqx_exproto_echo_svr:start(), - emqx_ct_helpers:start_apps([emqx_exproto], fun set_special_cfg/1), - emqx_logger:set_log_level(debug), - [{servers, Svrs}, {listener_type, GrpName} | Cfg]. - -end_per_group(_, Cfg) -> - emqx_ct_helpers:stop_apps([emqx_exproto]), - emqx_exproto_echo_svr:stop(proplists:get_value(servers, Cfg)). - -set_special_cfg(emqx_exproto) -> - LisType = get(grpname), - Listeners = application:get_env(emqx_exproto, listeners, []), - SockOpts = socketopts(LisType), - UpgradeOpts = fun(Opts) -> - Opts2 = lists:keydelete(tcp_options, 1, Opts), - Opts3 = lists:keydelete(ssl_options, 1, Opts2), - Opts4 = lists:keydelete(udp_options, 1, Opts3), - Opts5 = lists:keydelete(dtls_options, 1, Opts4), - SockOpts ++ Opts5 - end, - NListeners = [{Proto, LisType, LisOn, UpgradeOpts(Opts)} - || {Proto, _Type, LisOn, Opts} <- Listeners], - application:set_env(emqx_exproto, listeners, NListeners); -set_special_cfg(emqx) -> - application:set_env(emqx, allow_anonymous, true), - application:set_env(emqx, enable_acl_cache, false), - ok. - -%%-------------------------------------------------------------------- -%% Tests cases -%%-------------------------------------------------------------------- - -t_start_stop(_) -> - ok. - -t_mountpoint_echo(Cfg) -> - SockType = proplists:get_value(listener_type, Cfg), - Sock = open(SockType), - - Client = #{proto_name => <<"demo">>, - proto_ver => <<"v0.1">>, - clientid => <<"test_client_1">>, - mountpoint => <<"ct/">> - }, - Password = <<"123456">>, - - ConnBin = frame_connect(Client, Password), - ConnAckBin = frame_connack(0), - - send(Sock, ConnBin), - {ok, ConnAckBin} = recv(Sock, 5000), - - SubBin = frame_subscribe(<<"t/dn">>, 1), - SubAckBin = frame_suback(0), - - send(Sock, SubBin), - {ok, SubAckBin} = recv(Sock, 5000), - - emqx:publish(emqx_message:make(<<"ct/t/dn">>, <<"echo">>)), - PubBin1 = frame_publish(<<"t/dn">>, 0, <<"echo">>), - {ok, PubBin1} = recv(Sock, 5000), - - PubBin2 = frame_publish(<<"t/up">>, 0, <<"echo">>), - PubAckBin = frame_puback(0), - - emqx:subscribe(<<"ct/t/up">>), - - send(Sock, PubBin2), - {ok, PubAckBin} = recv(Sock, 5000), - - receive - {deliver, _, _} -> ok - after 1000 -> - error(echo_not_running) - end, - close(Sock). - -t_auth_deny(Cfg) -> - SockType = proplists:get_value(listener_type, Cfg), - Sock = open(SockType), - - Client = #{proto_name => <<"demo">>, - proto_ver => <<"v0.1">>, - clientid => <<"test_client_1">> - }, - Password = <<"123456">>, - - ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), - ok = meck:expect(emqx_access_control, authenticate, - fun(_) -> {error, ?RC_NOT_AUTHORIZED} end), - - ConnBin = frame_connect(Client, Password), - ConnAckBin = frame_connack(1), - - send(Sock, ConnBin), - {ok, ConnAckBin} = recv(Sock, 5000), - - SockType =/= udp andalso begin - {error, closed} = recv(Sock, 5000) - end, - meck:unload([emqx_access_control]). - -t_acl_deny(Cfg) -> - SockType = proplists:get_value(listener_type, Cfg), - Sock = open(SockType), - - Client = #{proto_name => <<"demo">>, - proto_ver => <<"v0.1">>, - clientid => <<"test_client_1">> - }, - Password = <<"123456">>, - - ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), - ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end), - - ConnBin = frame_connect(Client, Password), - ConnAckBin = frame_connack(0), - - send(Sock, ConnBin), - {ok, ConnAckBin} = recv(Sock, 5000), - - SubBin = frame_subscribe(<<"t/#">>, 1), - SubAckBin = frame_suback(1), - - send(Sock, SubBin), - {ok, SubAckBin} = recv(Sock, 5000), - - emqx:publish(emqx_message:make(<<"t/dn">>, <<"echo">>)), - - PubBin = frame_publish(<<"t/dn">>, 0, <<"echo">>), - PubBinFailedAck = frame_puback(1), - PubBinSuccesAck = frame_puback(0), - - send(Sock, PubBin), - {ok, PubBinFailedAck} = recv(Sock, 5000), - - meck:unload([emqx_access_control]), - - send(Sock, PubBin), - {ok, PubBinSuccesAck} = recv(Sock, 5000), - close(Sock). - -t_keepalive_timeout(Cfg) -> - SockType = proplists:get_value(listener_type, Cfg), - Sock = open(SockType), - - Client = #{proto_name => <<"demo">>, - proto_ver => <<"v0.1">>, - clientid => <<"test_client_1">>, - keepalive => 2 - }, - Password = <<"123456">>, - - ConnBin = frame_connect(Client, Password), - ConnAckBin = frame_connack(0), - - send(Sock, ConnBin), - {ok, ConnAckBin} = recv(Sock, 5000), - - DisconnectBin = frame_disconnect(), - {ok, DisconnectBin} = recv(Sock, 10000), - - SockType =/= udp andalso begin - {error, closed} = recv(Sock, 5000) - end, ok. - -t_hook_connected_disconnected(Cfg) -> - SockType = proplists:get_value(listener_type, Cfg), - Sock = open(SockType), - - Client = #{proto_name => <<"demo">>, - proto_ver => <<"v0.1">>, - clientid => <<"test_client_1">> - }, - Password = <<"123456">>, - - ConnBin = frame_connect(Client, Password), - ConnAckBin = frame_connack(0), - - Parent = self(), - emqx:hook('client.connected', {?MODULE, hook_fun1, [Parent]}), - emqx:hook('client.disconnected',{?MODULE, hook_fun2, [Parent]}), - - send(Sock, ConnBin), - {ok, ConnAckBin} = recv(Sock, 5000), - - receive - connected -> ok - after 1000 -> - error(hook_is_not_running) - end, - - DisconnectBin = frame_disconnect(), - send(Sock, DisconnectBin), - - receive - disconnected -> ok - after 1000 -> - error(hook_is_not_running) - end, - - SockType =/= udp andalso begin - {error, closed} = recv(Sock, 5000) - end, - emqx:unhook('client.connected', {?MODULE, hook_fun1}), - emqx:unhook('client.disconnected', {?MODULE, hook_fun2}). - -t_hook_session_subscribed_unsubscribed(Cfg) -> - SockType = proplists:get_value(listener_type, Cfg), - Sock = open(SockType), - - Client = #{proto_name => <<"demo">>, - proto_ver => <<"v0.1">>, - clientid => <<"test_client_1">> - }, - Password = <<"123456">>, - - ConnBin = frame_connect(Client, Password), - ConnAckBin = frame_connack(0), - - send(Sock, ConnBin), - {ok, ConnAckBin} = recv(Sock, 5000), - - Parent = self(), - emqx:hook('session.subscribed', {?MODULE, hook_fun3, [Parent]}), - emqx:hook('session.unsubscribed', {?MODULE, hook_fun4, [Parent]}), - - SubBin = frame_subscribe(<<"t/#">>, 1), - SubAckBin = frame_suback(0), - - send(Sock, SubBin), - {ok, SubAckBin} = recv(Sock, 5000), - - receive - subscribed -> ok - after 1000 -> - error(hook_is_not_running) - end, - - UnsubBin = frame_unsubscribe(<<"t/#">>), - UnsubAckBin = frame_unsuback(0), - - send(Sock, UnsubBin), - {ok, UnsubAckBin} = recv(Sock, 5000), - - receive - unsubscribed -> ok - after 1000 -> - error(hook_is_not_running) - end, - - close(Sock), - emqx:unhook('session.subscribed', {?MODULE, hook_fun3}), - emqx:unhook('session.unsubscribed', {?MODULE, hook_fun4}). - -t_hook_message_delivered(Cfg) -> - SockType = proplists:get_value(listener_type, Cfg), - Sock = open(SockType), - - Client = #{proto_name => <<"demo">>, - proto_ver => <<"v0.1">>, - clientid => <<"test_client_1">> - }, - Password = <<"123456">>, - - ConnBin = frame_connect(Client, Password), - ConnAckBin = frame_connack(0), - - send(Sock, ConnBin), - {ok, ConnAckBin} = recv(Sock, 5000), - - SubBin = frame_subscribe(<<"t/#">>, 1), - SubAckBin = frame_suback(0), - - send(Sock, SubBin), - {ok, SubAckBin} = recv(Sock, 5000), - - emqx:hook('message.delivered', {?MODULE, hook_fun5, []}), - - emqx:publish(emqx_message:make(<<"t/dn">>, <<"1">>)), - PubBin1 = frame_publish(<<"t/dn">>, 0, <<"2">>), - {ok, PubBin1} = recv(Sock, 5000), - - close(Sock), - emqx:unhook('message.delivered', {?MODULE, hook_fun5}). - -%%-------------------------------------------------------------------- -%% Utils - -hook_fun1(_, _, Parent) -> Parent ! connected, ok. -hook_fun2(_, _, _, Parent) -> Parent ! disconnected, ok. - -hook_fun3(_, _, _, Parent) -> Parent ! subscribed, ok. -hook_fun4(_, _, _, Parent) -> Parent ! unsubscribed, ok. - -hook_fun5(_, Msg) -> {ok, Msg#message{payload = <<"2">>}}. - -rand_bytes() -> - crypto:strong_rand_bytes(rand:uniform(256)). - -%%-------------------------------------------------------------------- -%% Sock funcs - -open(tcp) -> - {ok, Sock} = gen_tcp:connect("127.0.0.1", 7993, ?TCPOPTS), - {tcp, Sock}; -open(udp) -> - {ok, Sock} = gen_udp:open(0, ?TCPOPTS), - {udp, Sock}; -open(ssl) -> - SslOpts = client_ssl_opts(), - {ok, SslSock} = ssl:connect("127.0.0.1", 7993, ?TCPOPTS ++ SslOpts), - {ssl, SslSock}; -open(dtls) -> - SslOpts = client_ssl_opts(), - {ok, SslSock} = ssl:connect("127.0.0.1", 7993, ?DTLSOPTS ++ SslOpts), - {dtls, SslSock}. - -send({tcp, Sock}, Bin) -> - gen_tcp:send(Sock, Bin); -send({udp, Sock}, Bin) -> - gen_udp:send(Sock, "127.0.0.1", 7993, Bin); -send({ssl, Sock}, Bin) -> - ssl:send(Sock, Bin); -send({dtls, Sock}, Bin) -> - ssl:send(Sock, Bin). - -recv({tcp, Sock}, Ts) -> - gen_tcp:recv(Sock, 0, Ts); -recv({udp, Sock}, Ts) -> - {ok, {_, _, Bin}} = gen_udp:recv(Sock, 0, Ts), - {ok, Bin}; -recv({ssl, Sock}, Ts) -> - ssl:recv(Sock, 0, Ts); -recv({dtls, Sock}, Ts) -> - ssl:recv(Sock, 0, Ts). - -close({tcp, Sock}) -> - gen_tcp:close(Sock); -close({udp, Sock}) -> - gen_udp:close(Sock); -close({ssl, Sock}) -> - ssl:close(Sock); -close({dtls, Sock}) -> - ssl:close(Sock). - -%%-------------------------------------------------------------------- -%% Server-Opts - -socketopts(tcp) -> - [{tcp_options, tcp_opts()}]; -socketopts(ssl) -> - [{tcp_options, tcp_opts()}, - {ssl_options, ssl_opts()}]; -socketopts(udp) -> - [{udp_options, udp_opts()}]; -socketopts(dtls) -> - [{udp_options, udp_opts()}, - {dtls_options, dtls_opts()}]. - -tcp_opts() -> - [{send_timeout, 15000}, - {send_timeout_close, true}, - {backlog, 100}, - {nodelay, true} | udp_opts()]. - -udp_opts() -> - [{recbuf, 1024}, - {sndbuf, 1024}, - {buffer, 1024}, - {reuseaddr, true}]. - -ssl_opts() -> - Certs = certs("key.pem", "cert.pem", "cacert.pem"), - [{versions, emqx_tls_lib:default_versions()}, - {ciphers, emqx_tls_lib:default_ciphers()}, - {verify, verify_peer}, - {fail_if_no_peer_cert, true}, - {secure_renegotiate, false}, - {reuse_sessions, true}, - {honor_cipher_order, true}]++Certs. - -dtls_opts() -> - Opts = ssl_opts(), - lists:keyreplace(versions, 1, Opts, {versions, ['dtlsv1.2', 'dtlsv1']}). - -%%-------------------------------------------------------------------- -%% Client-Opts - -client_ssl_opts() -> - certs( "client-key.pem", "client-cert.pem", "cacert.pem" ). - -certs( Key, Cert, CACert ) -> - CertsPath = emqx_ct_helpers:deps_path(emqx, "etc/certs"), - [ { keyfile, filename:join([ CertsPath, Key ]) }, - { certfile, filename:join([ CertsPath, Cert ]) }, - { cacertfile, filename:join([ CertsPath, CACert ]) } ]. - diff --git a/apps/emqx_exproto/test/emqx_exproto_echo_svr.erl b/apps/emqx_exproto/test/emqx_exproto_echo_svr.erl deleted file mode 100644 index 69c64d4ca..000000000 --- a/apps/emqx_exproto/test/emqx_exproto_echo_svr.erl +++ /dev/null @@ -1,278 +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_exproto_echo_svr). - --behavior(emqx_exproto_v_1_connection_handler_bhvr). - --export([ start/0 - , stop/1 - ]). - --export([ frame_connect/2 - , frame_connack/1 - , frame_publish/3 - , frame_puback/1 - , frame_subscribe/2 - , frame_suback/1 - , frame_unsubscribe/1 - , frame_unsuback/1 - , frame_disconnect/0 - ]). - --export([ on_socket_created/2 - , on_received_bytes/2 - , on_socket_closed/2 - , on_timer_timeout/2 - , on_received_messages/2 - ]). - --define(LOG(Fmt, Args), io:format(standard_error, Fmt, Args)). - --define(HTTP, #{grpc_opts => #{service_protos => [emqx_exproto_pb], - services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}}, - listen_opts => #{port => 9001, - socket_options => []}, - pool_opts => #{size => 8}, - transport_opts => #{ssl => false}}). - --define(CLIENT, emqx_exproto_v_1_connection_adapter_client). - --define(send(Req), ?CLIENT:send(Req, #{channel => ct_test_channel})). --define(close(Req), ?CLIENT:close(Req, #{channel => ct_test_channel})). --define(authenticate(Req), ?CLIENT:authenticate(Req, #{channel => ct_test_channel})). --define(start_timer(Req), ?CLIENT:start_timer(Req, #{channel => ct_test_channel})). --define(publish(Req), ?CLIENT:publish(Req, #{channel => ct_test_channel})). --define(subscribe(Req), ?CLIENT:subscribe(Req, #{channel => ct_test_channel})). --define(unsubscribe(Req), ?CLIENT:unsubscribe(Req, #{channel => ct_test_channel})). - --define(TYPE_CONNECT, 1). --define(TYPE_CONNACK, 2). --define(TYPE_PUBLISH, 3). --define(TYPE_PUBACK, 4). --define(TYPE_SUBSCRIBE, 5). --define(TYPE_SUBACK, 6). --define(TYPE_UNSUBSCRIBE, 7). --define(TYPE_UNSUBACK, 8). --define(TYPE_DISCONNECT, 9). - --define(loop_recv_and_reply_empty_success(Stream), - ?loop_recv_and_reply_empty_success(Stream, fun(_) -> ok end)). - --define(loop_recv_and_reply_empty_success(Stream, Fun), - begin - LoopRecv = fun _Lp(_St) -> - case grpc_stream:recv(_St) of - {more, _Reqs, _NSt} -> - ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]), - Fun(_Reqs), _Lp(_NSt); - {eos, _Reqs, _NSt} -> - ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]), - Fun(_Reqs), _NSt - end - end, - NStream = LoopRecv(Stream), - grpc_stream:reply(NStream, #{}), - {ok, NStream} - end). - -%%-------------------------------------------------------------------- -%% APIs -%%-------------------------------------------------------------------- - -start() -> - application:ensure_all_started(grpc), - [start_channel(), start_server()]. - -start_channel() -> - grpc_client_sup:create_channel_pool(ct_test_channel, "http://127.0.0.1:9100", #{}). - -start_server() -> - Services = #{protos => [emqx_exproto_pb], - services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE} - }, - Options = [], - grpc:start_server(?MODULE, 9001, Services, Options). - -stop([_ChannPid, _SvrPid]) -> - grpc:stop_server(?MODULE), - grpc_client_sup:stop_channel_pool(ct_test_channel). - -%%-------------------------------------------------------------------- -%% Protocol Adapter callbacks -%%-------------------------------------------------------------------- - --spec on_socket_created(grpc_stream:stream(), grpc:metadata()) - -> {ok, grpc_stream:stream()}. -on_socket_created(Stream, _Md) -> - ?loop_recv_and_reply_empty_success(Stream). - --spec on_socket_closed(grpc_stream:stream(), grpc:metadata()) - -> {ok, grpc_stream:stream()}. -on_socket_closed(Stream, _Md) -> - ?loop_recv_and_reply_empty_success(Stream). - --spec on_received_bytes(grpc_stream:stream(), grpc:metadata()) - -> {ok, grpc_stream:stream()}. -on_received_bytes(Stream, _Md) -> - ?loop_recv_and_reply_empty_success(Stream, - fun(Reqs) -> - lists:foreach( - fun(#{conn := Conn, bytes := Bytes}) -> - #{<<"type">> := Type} = Params = emqx_json:decode(Bytes, [return_maps]), - _ = handle_in(Conn, Type, Params) - end, Reqs) - end). - --spec on_timer_timeout(grpc_stream:stream(), grpc:metadata()) - -> {ok, grpc_stream:stream()}. -on_timer_timeout(Stream, _Md) -> - ?loop_recv_and_reply_empty_success(Stream, - fun(Reqs) -> - lists:foreach( - fun(#{conn := Conn, type := 'KEEPALIVE'}) -> - ?LOG("Close this connection ~p due to keepalive timeout", [Conn]), - handle_out(Conn, ?TYPE_DISCONNECT), - ?close(#{conn => Conn}) - end, Reqs) - end). - --spec on_received_messages(grpc_stream:stream(), grpc:metadata()) - -> {ok, grpc_stream:stream()}. -on_received_messages(Stream, _Md) -> - ?loop_recv_and_reply_empty_success(Stream, - fun(Reqs) -> - lists:foreach( - fun(#{conn := Conn, messages := Messages}) -> - lists:foreach(fun(Message) -> - handle_out(Conn, ?TYPE_PUBLISH, Message) - end, Messages) - end, Reqs) - end). - -%%-------------------------------------------------------------------- -%% The Protocol Example: -%% CONN: -%% {"type": 1, "clientinfo": {...}} -%% -%% CONNACK: -%% {"type": 2, "code": 0} -%% -%% PUBLISH: -%% {"type": 3, "topic": "xxx", "payload": "", "qos": 0} -%% -%% PUBACK: -%% {"type": 4, "code": 0} -%% -%% SUBSCRIBE: -%% {"type": 5, "topic": "xxx", "qos": 1} -%% -%% SUBACK: -%% {"type": 6, "code": 0} -%% -%% DISCONNECT: -%% {"type": 7, "code": 1} -%%-------------------------------------------------------------------- - -handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">> := Password}) -> - NClientInfo = maps:from_list([{binary_to_atom(K, utf8), V} || {K, V} <- maps:to_list(ClientInfo)]), - case ?authenticate(#{conn => Conn, clientinfo => NClientInfo, password => Password}) of - {ok, #{code := 'SUCCESS'}, _} -> - case maps:get(keepalive, NClientInfo, 0) of - 0 -> ok; - Intv -> - io:format("Try call start_timer with ~ps", [Intv]), - ?start_timer(#{conn => Conn, type => 'KEEPALIVE', interval => Intv}) - end, - handle_out(Conn, ?TYPE_CONNACK, 0); - _ -> - handle_out(Conn, ?TYPE_CONNACK, 1), - ?close(#{conn => Conn}) - end; -handle_in(Conn, ?TYPE_PUBLISH, #{<<"topic">> := Topic, - <<"qos">> := Qos, - <<"payload">> := Payload}) -> - case ?publish(#{conn => Conn, topic => Topic, qos => Qos, payload => Payload}) of - {ok, #{code := 'SUCCESS'}, _} -> - handle_out(Conn, ?TYPE_PUBACK, 0); - _ -> - handle_out(Conn, ?TYPE_PUBACK, 1) - end; -handle_in(Conn, ?TYPE_SUBSCRIBE, #{<<"qos">> := Qos, <<"topic">> := Topic}) -> - case ?subscribe(#{conn => Conn, topic => Topic, qos => Qos}) of - {ok, #{code := 'SUCCESS'}, _} -> - handle_out(Conn, ?TYPE_SUBACK, 0); - _ -> - handle_out(Conn, ?TYPE_SUBACK, 1) - end; -handle_in(Conn, ?TYPE_UNSUBSCRIBE, #{<<"topic">> := Topic}) -> - case ?unsubscribe(#{conn => Conn, topic => Topic}) of - {ok, #{code := 'SUCCESS'}, _} -> - handle_out(Conn, ?TYPE_UNSUBACK, 0); - _ -> - handle_out(Conn, ?TYPE_UNSUBACK, 1) - end; - -handle_in(Conn, ?TYPE_DISCONNECT, _) -> - ?close(#{conn => Conn}). - -handle_out(Conn, ?TYPE_CONNACK, Code) -> - ?send(#{conn => Conn, bytes => frame_connack(Code)}); -handle_out(Conn, ?TYPE_PUBACK, Code) -> - ?send(#{conn => Conn, bytes => frame_puback(Code)}); -handle_out(Conn, ?TYPE_SUBACK, Code) -> - ?send(#{conn => Conn, bytes => frame_suback(Code)}); -handle_out(Conn, ?TYPE_UNSUBACK, Code) -> - ?send(#{conn => Conn, bytes => frame_unsuback(Code)}); -handle_out(Conn, ?TYPE_PUBLISH, #{qos := Qos, topic := Topic, payload := Payload}) -> - ?send(#{conn => Conn, bytes => frame_publish(Topic, Qos, Payload)}). - -handle_out(Conn, ?TYPE_DISCONNECT) -> - ?send(#{conn => Conn, bytes => frame_disconnect()}). - -%%-------------------------------------------------------------------- -%% Frame - -frame_connect(ClientInfo, Password) -> - emqx_json:encode(#{type => ?TYPE_CONNECT, - clientinfo => ClientInfo, - password => Password}). -frame_connack(Code) -> - emqx_json:encode(#{type => ?TYPE_CONNACK, code => Code}). - -frame_publish(Topic, Qos, Payload) -> - emqx_json:encode(#{type => ?TYPE_PUBLISH, - topic => Topic, - qos => Qos, - payload => Payload}). - -frame_puback(Code) -> - emqx_json:encode(#{type => ?TYPE_PUBACK, code => Code}). - -frame_subscribe(Topic, Qos) -> - emqx_json:encode(#{type => ?TYPE_SUBSCRIBE, topic => Topic, qos => Qos}). - -frame_suback(Code) -> - emqx_json:encode(#{type => ?TYPE_SUBACK, code => Code}). - -frame_unsubscribe(Topic) -> - emqx_json:encode(#{type => ?TYPE_UNSUBSCRIBE, topic => Topic}). - -frame_unsuback(Code) -> - emqx_json:encode(#{type => ?TYPE_UNSUBACK, code => Code}). - -frame_disconnect() -> - emqx_json:encode(#{type => ?TYPE_DISCONNECT}). diff --git a/apps/emqx_coap/etc/emqx_coap.conf b/apps/emqx_gateway/etc/emqx_coap.conf similarity index 100% rename from apps/emqx_coap/etc/emqx_coap.conf rename to apps/emqx_gateway/etc/emqx_coap.conf diff --git a/apps/emqx_exhook/etc/emqx_exhook.conf b/apps/emqx_gateway/etc/emqx_exhook.conf similarity index 100% rename from apps/emqx_exhook/etc/emqx_exhook.conf rename to apps/emqx_gateway/etc/emqx_exhook.conf diff --git a/apps/emqx_exproto/etc/emqx_exproto.conf b/apps/emqx_gateway/etc/emqx_exproto.conf similarity index 100% rename from apps/emqx_exproto/etc/emqx_exproto.conf rename to apps/emqx_gateway/etc/emqx_exproto.conf diff --git a/apps/emqx_lwm2m/etc/emqx_lwm2m.conf b/apps/emqx_gateway/etc/emqx_lwm2m.conf similarity index 100% rename from apps/emqx_lwm2m/etc/emqx_lwm2m.conf rename to apps/emqx_gateway/etc/emqx_lwm2m.conf diff --git a/apps/emqx_coap/priv/emqx_coap.schema b/apps/emqx_gateway/etc/priv/emqx_coap.schema similarity index 100% rename from apps/emqx_coap/priv/emqx_coap.schema rename to apps/emqx_gateway/etc/priv/emqx_coap.schema diff --git a/apps/emqx_exhook/priv/emqx_exhook.schema b/apps/emqx_gateway/etc/priv/emqx_exhook.schema similarity index 100% rename from apps/emqx_exhook/priv/emqx_exhook.schema rename to apps/emqx_gateway/etc/priv/emqx_exhook.schema diff --git a/apps/emqx_exproto/priv/emqx_exproto.schema b/apps/emqx_gateway/etc/priv/emqx_exproto.schema similarity index 100% rename from apps/emqx_exproto/priv/emqx_exproto.schema rename to apps/emqx_gateway/etc/priv/emqx_exproto.schema diff --git a/apps/emqx_lwm2m/priv/emqx_lwm2m.schema b/apps/emqx_gateway/etc/priv/emqx_lwm2m.schema similarity index 100% rename from apps/emqx_lwm2m/priv/emqx_lwm2m.schema rename to apps/emqx_gateway/etc/priv/emqx_lwm2m.schema diff --git a/apps/emqx_exhook/priv/protos/exhook.proto b/apps/emqx_gateway/etc/priv/exhook.proto similarity index 100% rename from apps/emqx_exhook/priv/protos/exhook.proto rename to apps/emqx_gateway/etc/priv/exhook.proto diff --git a/apps/emqx_exproto/priv/protos/exproto.proto b/apps/emqx_gateway/etc/priv/exproto.proto similarity index 100% rename from apps/emqx_exproto/priv/protos/exproto.proto rename to apps/emqx_gateway/etc/priv/exproto.proto diff --git a/apps/emqx_gateway/rebar.config b/apps/emqx_gateway/rebar.config index 71fc61330..93b3f858a 100644 --- a/apps/emqx_gateway/rebar.config +++ b/apps/emqx_gateway/rebar.config @@ -1,7 +1,22 @@ {erl_opts, [debug_info]}. -{deps, []}. +{deps, [ + {gen_coap, {git, "https://github.com/emqx/gen_coap", {tag, "v0.3.2"}}}, + {lwm2m_coap, {git, "https://github.com/emqx/lwm2m-coap", {tag, "v2.0.0"}}}, + {grpc, {git, "https://github.com/emqx/grpc-erl", {tag, "0.6.2"}}} +]}. {shell, [ % {config, "config/sys.config"}, {apps, [emqx_gateway]} ]}. + +% {plugins, +% [rebar3_proper, +% {grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.2"}}} +% ]}. + +% {grpc, +% [{protos, ["priv/protos"]}, +% {gpb_opts, [{module_name_prefix, "emqx_"}, +% {module_name_suffix, "_pb"}]} +% ]}. diff --git a/apps/emqx_coap/README.md b/apps/emqx_gateway/src/coap/README.md similarity index 100% rename from apps/emqx_coap/README.md rename to apps/emqx_gateway/src/coap/README.md diff --git a/apps/emqx_coap/src/emqx_coap.app.src b/apps/emqx_gateway/src/coap/emqx_coap.app.src similarity index 100% rename from apps/emqx_coap/src/emqx_coap.app.src rename to apps/emqx_gateway/src/coap/emqx_coap.app.src diff --git a/apps/emqx_coap/src/emqx_coap_app.erl b/apps/emqx_gateway/src/coap/emqx_coap_app.erl similarity index 97% rename from apps/emqx_coap/src/emqx_coap_app.erl rename to apps/emqx_gateway/src/coap/emqx_coap_app.erl index 16f74faf7..b73c92269 100644 --- a/apps/emqx_coap/src/emqx_coap_app.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_app.erl @@ -20,7 +20,7 @@ -emqx_plugin(protocol). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -export([ start/2 , stop/1 diff --git a/apps/emqx_coap/src/emqx_coap_mqtt_adapter.erl b/apps/emqx_gateway/src/coap/emqx_coap_mqtt_adapter.erl similarity index 99% rename from apps/emqx_coap/src/emqx_coap_mqtt_adapter.erl rename to apps/emqx_gateway/src/coap/emqx_coap_mqtt_adapter.erl index a76dc904e..da3d5eafa 100644 --- a/apps/emqx_coap/src/emqx_coap_mqtt_adapter.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_mqtt_adapter.erl @@ -18,7 +18,7 @@ -behaviour(gen_server). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). diff --git a/apps/emqx_coap/src/emqx_coap_pubsub_resource.erl b/apps/emqx_gateway/src/coap/emqx_coap_pubsub_resource.erl similarity index 99% rename from apps/emqx_coap/src/emqx_coap_pubsub_resource.erl rename to apps/emqx_gateway/src/coap/emqx_coap_pubsub_resource.erl index d87a26173..b7eb65f55 100644 --- a/apps/emqx_coap/src/emqx_coap_pubsub_resource.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_pubsub_resource.erl @@ -16,9 +16,9 @@ -module(emqx_coap_pubsub_resource). --behaviour(coap_resource). +% -behaviour(coap_resource). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -include_lib("gen_coap/include/coap.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). diff --git a/apps/emqx_coap/src/emqx_coap_pubsub_topics.erl b/apps/emqx_gateway/src/coap/emqx_coap_pubsub_topics.erl similarity index 99% rename from apps/emqx_coap/src/emqx_coap_pubsub_topics.erl rename to apps/emqx_gateway/src/coap/emqx_coap_pubsub_topics.erl index 57d0c3ae6..c8afc294e 100644 --- a/apps/emqx_coap/src/emqx_coap_pubsub_topics.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_pubsub_topics.erl @@ -18,7 +18,7 @@ -behaviour(gen_server). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/logger.hrl"). diff --git a/apps/emqx_coap/src/emqx_coap_registry.erl b/apps/emqx_gateway/src/coap/emqx_coap_registry.erl similarity index 98% rename from apps/emqx_coap/src/emqx_coap_registry.erl rename to apps/emqx_gateway/src/coap/emqx_coap_registry.erl index 066d37f1e..18faa6673 100644 --- a/apps/emqx_coap/src/emqx_coap_registry.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_registry.erl @@ -18,7 +18,7 @@ -author("Feng Lee "). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -include_lib("emqx/include/logger.hrl"). -logger_header("[CoAP-Registry]"). diff --git a/apps/emqx_coap/src/emqx_coap_resource.erl b/apps/emqx_gateway/src/coap/emqx_coap_resource.erl similarity index 98% rename from apps/emqx_coap/src/emqx_coap_resource.erl rename to apps/emqx_gateway/src/coap/emqx_coap_resource.erl index 739037a42..26bcd6fcd 100644 --- a/apps/emqx_coap/src/emqx_coap_resource.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_resource.erl @@ -16,9 +16,9 @@ -module(emqx_coap_resource). --behaviour(coap_resource). +% -behaviour(coap_resource). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). diff --git a/apps/emqx_coap/src/emqx_coap_server.erl b/apps/emqx_gateway/src/coap/emqx_coap_server.erl similarity index 98% rename from apps/emqx_coap/src/emqx_coap_server.erl rename to apps/emqx_gateway/src/coap/emqx_coap_server.erl index ebdc1a0fe..73625f65a 100644 --- a/apps/emqx_coap/src/emqx_coap_server.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_server.erl @@ -16,7 +16,7 @@ -module(emqx_coap_server). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -export([ start/1 , stop/1 diff --git a/apps/emqx_coap/src/emqx_coap_sup.erl b/apps/emqx_gateway/src/coap/emqx_coap_sup.erl similarity index 100% rename from apps/emqx_coap/src/emqx_coap_sup.erl rename to apps/emqx_gateway/src/coap/emqx_coap_sup.erl diff --git a/apps/emqx_coap/src/emqx_coap_timer.erl b/apps/emqx_gateway/src/coap/emqx_coap_timer.erl similarity index 97% rename from apps/emqx_coap/src/emqx_coap_timer.erl rename to apps/emqx_gateway/src/coap/emqx_coap_timer.erl index 92b0ddb2f..7e090a74d 100644 --- a/apps/emqx_coap/src/emqx_coap_timer.erl +++ b/apps/emqx_gateway/src/coap/emqx_coap_timer.erl @@ -16,7 +16,7 @@ -module(emqx_coap_timer). --include("emqx_coap.hrl"). +-include("src/coap/include/emqx_coap.hrl"). -export([ cancel_timer/1 , start_timer/2 diff --git a/apps/emqx_coap/include/emqx_coap.hrl b/apps/emqx_gateway/src/coap/include/emqx_coap.hrl similarity index 100% rename from apps/emqx_coap/include/emqx_coap.hrl rename to apps/emqx_gateway/src/coap/include/emqx_coap.hrl diff --git a/apps/emqx_gateway/src/coap/test/emqx_coap_SUITE.erl b/apps/emqx_gateway/src/coap/test/emqx_coap_SUITE.erl new file mode 100644 index 000000000..ee8e4015e --- /dev/null +++ b/apps/emqx_gateway/src/coap/test/emqx_coap_SUITE.erl @@ -0,0 +1,319 @@ +%%-------------------------------------------------------------------- +%% 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_coap_SUITE). + +% -compile(export_all). +% -compile(nowarn_export_all). + +% -include_lib("gen_coap/include/coap.hrl"). +% -include_lib("eunit/include/eunit.hrl"). +% -include_lib("emqx/include/emqx.hrl"). + +% -define(LOGT(Format, Args), ct:pal(Format, Args)). + +% all() -> emqx_ct:all(?MODULE). + +% init_per_suite(Config) -> +% emqx_ct_helpers:start_apps([emqx_coap], fun set_special_cfg/1), +% Config. + +% set_special_cfg(emqx_coap) -> +% Opts = application:get_env(emqx_coap, dtls_opts,[]), +% Opts2 = [{keyfile, emqx_ct_helpers:deps_path(emqx, "etc/certs/key.pem")}, +% {certfile, emqx_ct_helpers:deps_path(emqx, "etc/certs/cert.pem")}], +% application:set_env(emqx_coap, dtls_opts, emqx_misc:merge_opts(Opts, Opts2)), +% application:set_env(emqx_coap, enable_stats, true); +% set_special_cfg(_) -> +% ok. + +% end_per_suite(Config) -> +% emqx_ct_helpers:stop_apps([emqx_coap]), +% Config. + +% %%-------------------------------------------------------------------- +% %% Test Cases +% %%-------------------------------------------------------------------- + +% t_publish(_Config) -> +% Topic = <<"abc">>, Payload = <<"123">>, +% TopicStr = binary_to_list(Topic), +% URI = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", + +% %% Sub topic first +% emqx:subscribe(Topic), + +% Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% {ok, changed, _} = Reply, + +% receive +% {deliver, Topic, Msg} -> +% ?assertEqual(Topic, Msg#message.topic), +% ?assertEqual(Payload, Msg#message.payload) +% after +% 500 -> +% ?assert(false) +% end. + +% t_publish_acl_deny(_Config) -> +% Topic = <<"abc">>, Payload = <<"123">>, +% TopicStr = binary_to_list(Topic), +% URI = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", + +% %% Sub topic first +% emqx:subscribe(Topic), + +% ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history]), +% ok = meck:expect(emqx_access_control, authorize, 3, deny), +% Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?assertEqual({error,forbidden}, Reply), +% ok = meck:unload(emqx_access_control), +% receive +% {deliver, Topic, Msg} -> ct:fail({unexpected, {Topic, Msg}}) +% after +% 500 -> ok +% end. + +% t_observe(_Config) -> +% Topic = <<"abc">>, TopicStr = binary_to_list(Topic), +% Payload = <<"123">>, +% Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", +% {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri), +% ?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]), + +% [SubPid] = emqx:subscribers(Topic), +% ?assert(is_pid(SubPid)), + +% %% Publish a message +% emqx:publish(emqx_message:make(Topic, Payload)), + +% Notif = receive_notification(), +% ?LOGT("observer get Notif=~p", [Notif]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv}} = Notif, +% ?assertEqual(Payload, PayloadRecv), + +% er_coap_observer:stop(Pid), +% timer:sleep(100), + +% [] = emqx:subscribers(Topic). + +% t_observe_acl_deny(_Config) -> +% Topic = <<"abc">>, TopicStr = binary_to_list(Topic), +% Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", +% ok = meck:new(emqx_access_control, [non_strict, passthrough, no_history]), +% ok = meck:expect(emqx_access_control, authorize, 3, deny), +% ?assertEqual({error,forbidden}, er_coap_observer:observe(Uri)), +% [] = emqx:subscribers(Topic), +% ok = meck:unload(emqx_access_control). + +% t_observe_wildcard(_Config) -> +% Topic = <<"+/b">>, TopicStr = emqx_http_lib:uri_encode(binary_to_list(Topic)), +% Payload = <<"123">>, +% Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", +% {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri), +% ?LOGT("observer Uri=~p, Pid=~p, N=~p, Code=~p, Content=~p", [Uri, Pid, N, Code, Content]), + +% [SubPid] = emqx:subscribers(Topic), +% ?assert(is_pid(SubPid)), + +% %% Publish a message +% emqx:publish(emqx_message:make(<<"a/b">>, Payload)), + +% Notif = receive_notification(), +% ?LOGT("observer get Notif=~p", [Notif]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv}} = Notif, +% ?assertEqual(Payload, PayloadRecv), + +% er_coap_observer:stop(Pid), +% timer:sleep(100), + +% [] = emqx:subscribers(Topic). + +% t_observe_pub(_Config) -> +% Topic = <<"+/b">>, TopicStr = emqx_http_lib:uri_encode(binary_to_list(Topic)), +% Uri = "coap://127.0.0.1/mqtt/"++TopicStr++"?c=client1&u=tom&p=secret", +% {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri), +% ?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]), + +% [SubPid] = emqx:subscribers(Topic), +% ?assert(is_pid(SubPid)), + +% Topic2 = <<"a/b">>, Payload2 = <<"UFO">>, +% TopicStr2 = emqx_http_lib:uri_encode(binary_to_list(Topic2)), +% URI2 = "coap://127.0.0.1/mqtt/"++TopicStr2++"?c=client1&u=tom&p=secret", + +% Reply2 = er_coap_client:request(put, URI2, #coap_content{format = <<"application/octet-stream">>, payload = Payload2}), +% {ok,changed, _} = Reply2, + +% Notif2 = receive_notification(), +% ?LOGT("observer get Notif2=~p", [Notif2]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv2}} = Notif2, +% ?assertEqual(Payload2, PayloadRecv2), + +% Topic3 = <<"j/b">>, Payload3 = <<"ET629">>, +% TopicStr3 = emqx_http_lib:uri_encode(binary_to_list(Topic3)), +% URI3 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?c=client2&u=mike&p=guess", +% Reply3 = er_coap_client:request(put, URI3, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), +% {ok,changed, _} = Reply3, + +% Notif3 = receive_notification(), +% ?LOGT("observer get Notif3=~p", [Notif3]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv3}} = Notif3, +% ?assertEqual(Payload3, PayloadRecv3), + +% er_coap_observer:stop(Pid). + +% t_one_clientid_sub_2_topics(_Config) -> +% Topic1 = <<"abc">>, TopicStr1 = binary_to_list(Topic1), +% Payload1 = <<"123">>, +% Uri1 = "coap://127.0.0.1/mqtt/"++TopicStr1++"?c=client1&u=tom&p=secret", +% {ok, Pid1, N1, Code1, Content1} = er_coap_observer:observe(Uri1), +% ?LOGT("observer 1 Pid=~p, N=~p, Code=~p, Content=~p", [Pid1, N1, Code1, Content1]), + +% [SubPid] = emqx:subscribers(Topic1), +% ?assert(is_pid(SubPid)), + +% Topic2 = <<"x/y">>, TopicStr2 = emqx_http_lib:uri_encode(binary_to_list(Topic2)), +% Payload2 = <<"456">>, +% Uri2 = "coap://127.0.0.1/mqtt/"++TopicStr2++"?c=client1&u=tom&p=secret", +% {ok, Pid2, N2, Code2, Content2} = er_coap_observer:observe(Uri2), +% ?LOGT("observer 2 Pid=~p, N=~p, Code=~p, Content=~p", [Pid2, N2, Code2, Content2]), + +% [SubPid] = emqx:subscribers(Topic2), +% ?assert(is_pid(SubPid)), + +% emqx:publish(emqx_message:make(Topic1, Payload1)), + +% Notif1 = receive_notification(), +% ?LOGT("observer 1 get Notif=~p", [Notif1]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv1}} = Notif1, +% ?assertEqual(Payload1, PayloadRecv1), + +% emqx:publish(emqx_message:make(Topic2, Payload2)), + +% Notif2 = receive_notification(), +% ?LOGT("observer 2 get Notif=~p", [Notif2]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv2}} = Notif2, +% ?assertEqual(Payload2, PayloadRecv2), + +% er_coap_observer:stop(Pid1), +% er_coap_observer:stop(Pid2). + +% t_invalid_parameter(_Config) -> +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% %% "cid=client2" is invaid +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Topic3 = <<"a/b">>, Payload3 = <<"ET629">>, +% TopicStr3 = emqx_http_lib:uri_encode(binary_to_list(Topic3)), +% URI3 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?cid=client2&u=tom&p=simple", +% Reply3 = er_coap_client:request(put, URI3, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), +% ?assertMatch({error,bad_request}, Reply3), + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% %% "what=hello" is invaid +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% URI4 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?what=hello", +% Reply4 = er_coap_client:request(put, URI4, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), +% ?assertMatch({error, bad_request}, Reply4). + +% t_invalid_topic(_Config) -> +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% %% "a/b" is a valid topic string +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Topic3 = <<"a/b">>, Payload3 = <<"ET629">>, +% TopicStr3 = binary_to_list(Topic3), +% URI3 = "coap://127.0.0.1/mqtt/"++TopicStr3++"?c=client2&u=tom&p=simple", +% Reply3 = er_coap_client:request(put, URI3, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), +% ?assertMatch({ok,changed,_Content}, Reply3), + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% %% "+?#" is invaid topic string +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% URI4 = "coap://127.0.0.1/mqtt/"++"+?#"++"?what=hello", +% Reply4 = er_coap_client:request(put, URI4, #coap_content{format = <<"application/octet-stream">>, payload = Payload3}), +% ?assertMatch({error,bad_request}, Reply4). + +% % mqtt connection kicked by coap with same client id +% t_kick_1(_Config) -> +% URI = "coap://127.0.0.1/mqtt/abc?c=clientid&u=tom&p=secret", +% % workaround: emqx:subscribe does not kick same client id. +% spawn_monitor(fun() -> +% {ok, C} = emqtt:start_link([{host, "localhost"}, +% {clientid, <<"clientid">>}, +% {username, <<"plain">>}, +% {password, <<"plain">>}]), +% {ok, _} = emqtt:connect(C) end), +% er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, +% payload = <<"123">>}), +% receive +% {'DOWN', _, _, _, _} -> ok +% after 2000 -> +% ?assert(false) +% end. + +% % mqtt connection kicked by coap with same client id +% t_acl(_Config) -> +% OldPath = emqx:get_env(plugins_etc_dir), +% application:set_env(emqx, plugins_etc_dir, +% emqx_ct_helpers:deps_path(emqx_authz, "test")), +% Conf = #{<<"authz">> => +% #{<<"rules">> => +% [#{<<"principal">> =>#{<<"username">> => <<"coap">>}, +% <<"permission">> => deny, +% <<"topics">> => [<<"abc">>], +% <<"action">> => <<"publish">>} +% ]}}, +% ok = file:write_file(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf'), jsx:encode(Conf)), +% application:ensure_all_started(emqx_authz), + +% emqx:subscribe(<<"abc">>), +% URI = "coap://127.0.0.1/mqtt/adbc?c=client1&u=coap&p=secret", +% er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, +% payload = <<"123">>}), +% receive +% _Something -> ?assert(false) +% after 2000 -> +% ok +% end, + +% ok = emqx_hooks:del('client.authorize', {emqx_authz, authorize}), +% file:delete(filename:join(emqx:get_env(plugins_etc_dir), 'authz.conf')), +% application:set_env(emqx, plugins_etc_dir, OldPath), +% application:stop(emqx_authz). + +% t_stats(_) -> +% ok. + +% t_auth_failure(_) -> +% ok. + +% t_qos_supprot(_) -> +% ok. + +% %%-------------------------------------------------------------------- +% %% Helpers + +% receive_notification() -> +% receive +% {coap_notify, Pid, N2, Code2, Content2} -> +% {coap_notify, Pid, N2, Code2, Content2} +% after 2000 -> +% receive_notification_timeout +% end. + +% testdir(DataPath) -> +% Ls = filename:split(DataPath), +% filename:join(lists:sublist(Ls, 1, length(Ls) - 1)). diff --git a/apps/emqx_gateway/src/coap/test/emqx_coap_pubsub_SUITE.erl b/apps/emqx_gateway/src/coap/test/emqx_coap_pubsub_SUITE.erl new file mode 100644 index 000000000..403cd8b2b --- /dev/null +++ b/apps/emqx_gateway/src/coap/test/emqx_coap_pubsub_SUITE.erl @@ -0,0 +1,678 @@ +%%-------------------------------------------------------------------- +%% 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_coap_pubsub_SUITE). + +% -compile(export_all). +% -compile(nowarn_export_all). + + +% -include_lib("gen_coap/include/coap.hrl"). +% -include_lib("eunit/include/eunit.hrl"). +% -include_lib("emqx/include/emqx.hrl"). + +% -define(LOGT(Format, Args), ct:pal(Format, Args)). + +% all() -> emqx_ct:all(?MODULE). + +% init_per_suite(Config) -> +% emqx_ct_helpers:start_apps([emqx_coap], fun set_special_cfg/1), +% Config. + +% set_special_cfg(emqx_coap) -> +% application:set_env(emqx_coap, enable_stats, true); +% set_special_cfg(_) -> +% ok. + +% end_per_suite(Config) -> +% emqx_ct_helpers:stop_apps([emqx_coap]), +% Config. + +% %%-------------------------------------------------------------------- +% %% Test Cases +% %%-------------------------------------------------------------------- + +% t_update_max_age(_Config) -> +% TopicInPayload = <<"topic1">>, +% Payload = <<";ct=42">>, +% Payload1 = <<";ct=50">>, +% URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", +% URI2 = "coap://127.0.0.1/ps/topic1"++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?assertEqual(60, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% timer:sleep(50), + +% %% post to create the same topic but with different max age and ct value in payload +% Reply1 = er_coap_client:request(post, URI, #coap_content{max_age = 70, format = <<"application/link-format">>, payload = Payload1}), +% {ok,created, #coap_content{location_path = LocPath}} = Reply1, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{TopicInPayload, MaxAge2, CT2, _ResPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), +% ?assertEqual(70, MaxAge2), +% ?assertEqual(<<"50">>, CT2), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI2). + +% t_create_subtopic(_Config) -> +% TopicInPayload = <<"topic1">>, +% TopicInPayloadStr = "topic1", +% Payload = <<";ct=42">>, +% URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", +% RealURI = "coap://127.0.0.1/ps/topic1"++"?c=client1&u=tom&p=secret", + +% Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?assertEqual(60, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% timer:sleep(50), + +% %% post to create the a sub topic +% SubPayload = <<";ct=42">>, +% SubTopicInPayloadStr = "subtopic", +% SubURI = "coap://127.0.0.1/ps/"++TopicInPayloadStr++"?c=client1&u=tom&p=secret", +% SubRealURI = "coap://127.0.0.1/ps/"++TopicInPayloadStr++"/"++SubTopicInPayloadStr++"?c=client1&u=tom&p=secret", +% FullTopic = list_to_binary(TopicInPayloadStr++"/"++SubTopicInPayloadStr), +% Reply1 = er_coap_client:request(post, SubURI, #coap_content{format = <<"application/link-format">>, payload = SubPayload}), +% ?LOGT("Reply =~p", [Reply1]), +% {ok,created, #coap_content{location_path = LocPath1}} = Reply1, +% ?assertEqual([<<"/ps/topic1/subtopic">>] ,LocPath1), +% [{FullTopic, MaxAge2, CT2, _ResPayload, _}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), +% ?assertEqual(60, MaxAge2), +% ?assertEqual(<<"42">>, CT2), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, SubRealURI), +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, RealURI). + +% t_over_max_age(_Config) -> +% TopicInPayload = <<"topic1">>, +% Payload = <<";ct=42">>, +% URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{max_age = 2, format = <<"application/link-format">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?assertEqual(2, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% timer:sleep(3000), +% ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(TopicInPayload)). + +% t_refreash_max_age(_Config) -> +% TopicInPayload = <<"topic1">>, +% Payload = <<";ct=42">>, +% Payload1 = <<";ct=50">>, +% URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", +% RealURI = "coap://127.0.0.1/ps/topic1"++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/link-format">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% TopicInfo = [{TopicInPayload, MaxAge1, CT1, _ResPayload, TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?LOGT("TimeStamp=~p", [TimeStamp]), +% ?assertEqual(5, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% timer:sleep(3000), + +% %% post to create the same topic, the max age timer will be restarted with the new max age value +% Reply1 = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/link-format">>, payload = Payload1}), +% {ok,created, #coap_content{location_path = LocPath}} = Reply1, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{TopicInPayload, MaxAge2, CT2, _ResPayload, TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(TopicInPayload), +% ?LOGT("TimeStamp1=~p", [TimeStamp1]), +% ?assertEqual(5, MaxAge2), +% ?assertEqual(<<"50">>, CT2), + +% timer:sleep(3000), +% ?assertEqual(false, emqx_coap_pubsub_topics:is_topic_timeout(TopicInPayload)), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, RealURI). + +% t_case01_publish_post(_Config) -> +% timer:sleep(100), +% MainTopic = <<"maintopic">>, +% TopicInPayload = <<"topic1">>, +% Payload = <<";ct=42">>, +% MainTopicStr = binary_to_list(MainTopic), + +% %% post to create topic maintopic/topic1 +% URI1 = "coap://127.0.0.1/ps/"++MainTopicStr++"?c=client1&u=tom&p=secret", +% FullTopic = list_to_binary(MainTopicStr++"/"++binary_to_list(TopicInPayload)), +% Reply1 = er_coap_client:request(post, URI1, #coap_content{format = <<"application/link-format">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply1]), +% {ok,created, #coap_content{location_path = LocPath1}} = Reply1, +% ?assertEqual([<<"/ps/maintopic/topic1">>] ,LocPath1), +% [{FullTopic, MaxAge, CT2, <<>>, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), +% ?assertEqual(60, MaxAge), +% ?assertEqual(<<"42">>, CT2), + +% %% post to publish message to topic maintopic/topic1 +% FullTopicStr = emqx_http_lib:uri_encode(binary_to_list(FullTopic)), +% URI2 = "coap://127.0.0.1/ps/"++FullTopicStr++"?c=client1&u=tom&p=secret", +% PubPayload = <<"PUBLISH">>, + +% %% Sub topic first +% emqx:subscribe(FullTopic), + +% Reply2 = er_coap_client:request(post, URI2, #coap_content{format = <<"application/octet-stream">>, payload = PubPayload}), +% ?LOGT("Reply =~p", [Reply2]), +% {ok,changed, _} = Reply2, +% TopicInfo = [{FullTopic, MaxAge, CT2, PubPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), +% ?LOGT("the topic info =~p", [TopicInfo]), + +% assert_recv(FullTopic, PubPayload), +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI2). + +% t_case02_publish_post(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"payload">>, + +% %% Sub topic first +% emqx:subscribe(Topic), + +% %% post to publish a new topic "topic1", and the topic is created +% URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(60, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% assert_recv(Topic, Payload), + +% %% post to publish a new message to the same topic "topic1" with different payload +% NewPayload = <<"newpayload">>, +% Reply1 = er_coap_client:request(post, URI, #coap_content{format = <<"application/octet-stream">>, payload = NewPayload}), +% ?LOGT("Reply =~p", [Reply1]), +% {ok,changed, _} = Reply1, +% [{Topic, MaxAge, CT, NewPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), + +% assert_recv(Topic, NewPayload), +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case03_publish_post(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"payload">>, + +% %% Sub topic first +% emqx:subscribe(Topic), + +% %% post to publish a new topic "topic1", and the topic is created +% URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(60, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% assert_recv(Topic, Payload), + +% %% post to publish a new message to the same topic "topic1", but the ct is not same as created +% NewPayload = <<"newpayload">>, +% Reply1 = er_coap_client:request(post, URI, #coap_content{format = <<"application/exi">>, payload = NewPayload}), +% ?LOGT("Reply =~p", [Reply1]), +% ?assertEqual({error,bad_request}, Reply1), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case04_publish_post(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"payload">>, + +% %% post to publish a new topic "topic1", and the topic is created +% URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(5, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% %% after max age timeout, the topic still exists but the status is timeout +% timer:sleep(6000), +% ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case01_publish_put(_Config) -> +% MainTopic = <<"maintopic">>, +% TopicInPayload = <<"topic1">>, +% Payload = <<";ct=42">>, +% MainTopicStr = binary_to_list(MainTopic), + +% %% post to create topic maintopic/topic1 +% URI1 = "coap://127.0.0.1/ps/"++MainTopicStr++"?c=client1&u=tom&p=secret", +% FullTopic = list_to_binary(MainTopicStr++"/"++binary_to_list(TopicInPayload)), +% Reply1 = er_coap_client:request(post, URI1, #coap_content{format = <<"application/link-format">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply1]), +% {ok,created, #coap_content{location_path = LocPath1}} = Reply1, +% ?assertEqual([<<"/ps/maintopic/topic1">>] ,LocPath1), +% [{FullTopic, MaxAge, CT2, <<>>, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), +% ?assertEqual(60, MaxAge), +% ?assertEqual(<<"42">>, CT2), + +% %% put to publish message to topic maintopic/topic1 +% FullTopicStr = emqx_http_lib:uri_encode(binary_to_list(FullTopic)), +% URI2 = "coap://127.0.0.1/ps/"++FullTopicStr++"?c=client1&u=tom&p=secret", +% PubPayload = <<"PUBLISH">>, + +% %% Sub topic first +% emqx:subscribe(FullTopic), + +% Reply2 = er_coap_client:request(put, URI2, #coap_content{format = <<"application/octet-stream">>, payload = PubPayload}), +% ?LOGT("Reply =~p", [Reply2]), +% {ok,changed, _} = Reply2, +% [{FullTopic, MaxAge, CT2, PubPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(FullTopic), + +% assert_recv(FullTopic, PubPayload), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI2). + +% t_case02_publish_put(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"payload">>, + +% %% Sub topic first +% emqx:subscribe(Topic), + +% %% put to publish a new topic "topic1", and the topic is created +% URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(60, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% assert_recv(Topic, Payload), + +% %% put to publish a new message to the same topic "topic1" with different payload +% NewPayload = <<"newpayload">>, +% Reply1 = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = NewPayload}), +% ?LOGT("Reply =~p", [Reply1]), +% {ok,changed, _} = Reply1, +% [{Topic, MaxAge, CT, NewPayload, _TimeStamp1}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), + +% assert_recv(Topic, NewPayload), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case03_publish_put(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"payload">>, + +% %% Sub topic first +% emqx:subscribe(Topic), + +% %% put to publish a new topic "topic1", and the topic is created +% URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(60, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% assert_recv(Topic, Payload), + +% %% put to publish a new message to the same topic "topic1", but the ct is not same as created +% NewPayload = <<"newpayload">>, +% Reply1 = er_coap_client:request(put, URI, #coap_content{format = <<"application/exi">>, payload = NewPayload}), +% ?LOGT("Reply =~p", [Reply1]), +% ?assertEqual({error,bad_request}, Reply1), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case04_publish_put(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"payload">>, + +% %% put to publish a new topic "topic1", and the topic is created +% URI = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(put, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/topic1">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(5, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% %% after max age timeout, no publish message to the same topic, the topic info will be deleted +% %%%%%%%%%%%%%%%%%%%%%%%%%% +% % but there is one thing to do is we don't count in the publish message received from emqx(from other node).TBD!!!!!!!!!!!!! +% %%%%%%%%%%%%%%%%%%%%%%%%%% +% timer:sleep(6000), +% ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case01_subscribe(_Config) -> +% Topic = <<"topic1">>, +% Payload1 = <<";ct=42">>, +% timer:sleep(100), + +% %% First post to create a topic "topic1" +% Uri = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/link-format">>, payload = Payload1}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = [LocPath]}} = Reply, +% ?assertEqual(<<"/ps/topic1">> ,LocPath), +% TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?assertEqual(60, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% %% Subscribe the topic +% Uri1 = "coap://127.0.0.1"++binary_to_list(LocPath)++"?c=client1&u=tom&p=secret", +% {ok, Pid, N, Code, Content} = er_coap_observer:observe(Uri1), +% ?LOGT("observer Pid=~p, N=~p, Code=~p, Content=~p", [Pid, N, Code, Content]), + +% [SubPid] = emqx:subscribers(Topic), +% ?assert(is_pid(SubPid)), + +% %% Publish a message +% Payload = <<"123">>, +% emqx:publish(emqx_message:make(Topic, Payload)), + +% Notif = receive_notification(), +% ?LOGT("observer get Notif=~p", [Notif]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = PayloadRecv}} = Notif, + +% ?assertEqual(Payload, PayloadRecv), + +% %% GET to read the publish message of the topic +% Reply1 = er_coap_client:request(get, Uri1), +% ?LOGT("Reply=~p", [Reply1]), +% {ok,content, #coap_content{payload = <<"123">>}} = Reply1, + +% er_coap_observer:stop(Pid), +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri1). + +% t_case02_subscribe(_Config) -> +% Topic = <<"a/b">>, +% TopicStr = binary_to_list(Topic), +% PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), +% Payload = <<"payload">>, + +% %% post to publish a new topic "a/b", and the topic is created +% URI = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/a/b">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(5, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% %% Wait for the max age of the timer expires +% timer:sleep(6000), +% ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), + +% %% Subscribe to the timeout topic "a/b", still successfully,got {ok, nocontent} Method +% Uri = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", +% Reply1 = {ok, Pid, _N, nocontent, _} = er_coap_observer:observe(Uri), +% ?LOGT("Subscribe Reply=~p", [Reply1]), + +% [SubPid] = emqx:subscribers(Topic), +% ?assert(is_pid(SubPid)), + +% %% put to publish to topic "a/b" +% Reply2 = er_coap_client:request(put, URI, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% {ok,changed, #coap_content{}} = Reply2, +% [{Topic, MaxAge1, CT, Payload, TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(60, MaxAge1), +% ?assertEqual(<<"42">>, CT), +% ?assertEqual(false, TimeStamp =:= timeout), + +% %% Publish a message +% emqx:publish(emqx_message:make(Topic, Payload)), + +% Notif = receive_notification(), +% ?LOGT("observer get Notif=~p", [Notif]), +% {coap_notify, _, _, {ok,content}, #coap_content{payload = Payload}} = Notif, + +% er_coap_observer:stop(Pid), +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case03_subscribe(_Config) -> +% %% Subscribe to the unexisted topic "a/b", got not_found +% Topic = <<"a/b">>, +% TopicStr = binary_to_list(Topic), +% PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), +% Uri = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", +% {error, not_found} = er_coap_observer:observe(Uri), + +% [] = emqx:subscribers(Topic). + +% t_case04_subscribe(_Config) -> +% %% Subscribe to the wildcad topic "+/b", got bad_request +% Topic = <<"+/b">>, +% TopicStr = binary_to_list(Topic), +% PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), +% Uri = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", +% {error, bad_request} = er_coap_observer:observe(Uri), + +% [] = emqx:subscribers(Topic). + +% t_case01_read(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"PubPayload">>, +% timer:sleep(100), + +% %% First post to create a topic "topic1" +% Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = [LocPath]}} = Reply, +% ?assertEqual(<<"/ps/topic1">> ,LocPath), +% TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?assertEqual(60, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% %% GET to read the publish message of the topic +% timer:sleep(1000), +% Reply1 = er_coap_client:request(get, Uri), +% ?LOGT("Reply=~p", [Reply1]), +% {ok,content, #coap_content{payload = Payload}} = Reply1, + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri). + +% t_case02_read(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"PubPayload">>, +% timer:sleep(100), + +% %% First post to publish a topic "topic1" +% Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = [LocPath]}} = Reply, +% ?assertEqual(<<"/ps/topic1">> ,LocPath), +% TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?assertEqual(60, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% %% GET to read the publish message of unmatched format, got bad_request +% Reply1 = er_coap_client:request(get, Uri, #coap_content{format = <<"application/json">>}), +% ?LOGT("Reply=~p", [Reply1]), +% {error, bad_request} = Reply1, + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri). + +% t_case03_read(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% timer:sleep(100), + +% %% GET to read the nexisted topic "topic1", got not_found +% Reply = er_coap_client:request(get, Uri), +% ?LOGT("Reply=~p", [Reply]), +% {error, not_found} = Reply. + +% t_case04_read(_Config) -> +% Topic = <<"topic1">>, +% TopicStr = binary_to_list(Topic), +% Payload = <<"PubPayload">>, +% timer:sleep(100), + +% %% First post to publish a topic "topic1" +% Uri = "coap://127.0.0.1/ps/"++TopicStr++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, Uri, #coap_content{format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = [LocPath]}} = Reply, +% ?assertEqual(<<"/ps/topic1">> ,LocPath), +% TopicInfo = [{Topic, MaxAge1, CT1, _ResPayload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?LOGT("lookup topic info=~p", [TopicInfo]), +% ?assertEqual(60, MaxAge1), +% ?assertEqual(<<"42">>, CT1), + +% %% GET to read the publish message of wildcard topic, got bad_request +% WildTopic = binary_to_list(<<"+/topic1">>), +% Uri1 = "coap://127.0.0.1/ps/"++WildTopic++"?c=client1&u=tom&p=secret", +% Reply1 = er_coap_client:request(get, Uri1, #coap_content{format = <<"application/json">>}), +% ?LOGT("Reply=~p", [Reply1]), +% {error, bad_request} = Reply1, + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, Uri). + +% t_case05_read(_Config) -> +% Topic = <<"a/b">>, +% TopicStr = binary_to_list(Topic), +% PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), +% Payload = <<"payload">>, + +% %% post to publish a new topic "a/b", and the topic is created +% URI = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", +% Reply = er_coap_client:request(post, URI, #coap_content{max_age = 5, format = <<"application/octet-stream">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/a/b">>] ,LocPath), +% [{Topic, MaxAge, CT, Payload, _TimeStamp}] = emqx_coap_pubsub_topics:lookup_topic_info(Topic), +% ?assertEqual(5, MaxAge), +% ?assertEqual(<<"42">>, CT), + +% %% Wait for the max age of the timer expires +% timer:sleep(6000), +% ?assertEqual(true, emqx_coap_pubsub_topics:is_topic_timeout(Topic)), + +% %% GET to read the expired publish message, supposed to get {ok, nocontent}, but now got {ok, content} +% Reply1 = er_coap_client:request(get, URI), +% ?LOGT("Reply=~p", [Reply1]), +% {ok, content, #coap_content{payload = <<>>}}= Reply1, + +% {ok, deleted, #coap_content{}} = er_coap_client:request(delete, URI). + +% t_case01_delete(_Config) -> +% TopicInPayload = <<"a/b">>, +% TopicStr = binary_to_list(TopicInPayload), +% PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), +% Payload = list_to_binary("<"++PercentEncodedTopic++">;ct=42"), +% URI = "coap://127.0.0.1/ps/"++"?c=client1&u=tom&p=secret", + +% %% Client post to CREATE topic "a/b" +% Reply = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload}), +% ?LOGT("Reply =~p", [Reply]), +% {ok,created, #coap_content{location_path = LocPath}} = Reply, +% ?assertEqual([<<"/ps/a/b">>] ,LocPath), + +% %% Client post to CREATE topic "a/b/c" +% TopicInPayload1 = <<"a/b/c">>, +% PercentEncodedTopic1 = emqx_http_lib:uri_encode(binary_to_list(TopicInPayload1)), +% Payload1 = list_to_binary("<"++PercentEncodedTopic1++">;ct=42"), +% Reply1 = er_coap_client:request(post, URI, #coap_content{format = <<"application/link-format">>, payload = Payload1}), +% ?LOGT("Reply =~p", [Reply1]), +% {ok,created, #coap_content{location_path = LocPath1}} = Reply1, +% ?assertEqual([<<"/ps/a/b/c">>] ,LocPath1), + +% timer:sleep(50), + +% %% DELETE the topic "a/b" +% UriD = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", +% ReplyD = er_coap_client:request(delete, UriD), +% ?LOGT("Reply=~p", [ReplyD]), +% {ok, deleted, #coap_content{}}= ReplyD, + +% timer:sleep(300), %% Waiting gen_server:cast/2 for deleting operation +% ?assertEqual(false, emqx_coap_pubsub_topics:is_topic_existed(TopicInPayload)), +% ?assertEqual(false, emqx_coap_pubsub_topics:is_topic_existed(TopicInPayload1)). + +% t_case02_delete(_Config) -> +% TopicInPayload = <<"a/b">>, +% TopicStr = binary_to_list(TopicInPayload), +% PercentEncodedTopic = emqx_http_lib:uri_encode(TopicStr), + +% %% DELETE the unexisted topic "a/b" +% Uri1 = "coap://127.0.0.1/ps/"++PercentEncodedTopic++"?c=client1&u=tom&p=secret", +% Reply1 = er_coap_client:request(delete, Uri1), +% ?LOGT("Reply=~p", [Reply1]), +% {error, not_found} = Reply1. + +% t_case13_emit_stats_test(_Config) -> +% ok. + +% %%-------------------------------------------------------------------- +% %% Internal functions + +% receive_notification() -> +% receive +% {coap_notify, Pid, N2, Code2, Content2} -> +% {coap_notify, Pid, N2, Code2, Content2} +% after 2000 -> +% receive_notification_timeout +% end. + +% assert_recv(Topic, Payload) -> +% receive +% {deliver, _, Msg} -> +% ?assertEqual(Topic, Msg#message.topic), +% ?assertEqual(Payload, Msg#message.payload) +% after +% 500 -> +% ?assert(false) +% end. + diff --git a/apps/emqx_exhook/README.md b/apps/emqx_gateway/src/exhook/README.md similarity index 100% rename from apps/emqx_exhook/README.md rename to apps/emqx_gateway/src/exhook/README.md diff --git a/apps/emqx_exhook/src/emqx_exhook.app.src b/apps/emqx_gateway/src/exhook/emqx_exhook.app.src similarity index 100% rename from apps/emqx_exhook/src/emqx_exhook.app.src rename to apps/emqx_gateway/src/exhook/emqx_exhook.app.src diff --git a/apps/emqx_exhook/src/emqx_exhook.erl b/apps/emqx_gateway/src/exhook/emqx_exhook.erl similarity index 98% rename from apps/emqx_exhook/src/emqx_exhook.erl rename to apps/emqx_gateway/src/exhook/emqx_exhook.erl index 032d7f91a..b3b3057b6 100644 --- a/apps/emqx_exhook/src/emqx_exhook.erl +++ b/apps/emqx_gateway/src/exhook/emqx_exhook.erl @@ -16,7 +16,7 @@ -module(emqx_exhook). --include("emqx_exhook.hrl"). +-include("src/exhook/include/emqx_exhook.hrl"). -include_lib("emqx/include/logger.hrl"). -logger_header("[ExHook]"). diff --git a/apps/emqx_exhook/src/emqx_exhook_app.erl b/apps/emqx_gateway/src/exhook/emqx_exhook_app.erl similarity index 98% rename from apps/emqx_exhook/src/emqx_exhook_app.erl rename to apps/emqx_gateway/src/exhook/emqx_exhook_app.erl index 2988be6d2..d4621f85e 100644 --- a/apps/emqx_exhook/src/emqx_exhook_app.erl +++ b/apps/emqx_gateway/src/exhook/emqx_exhook_app.erl @@ -18,7 +18,7 @@ -behaviour(application). --include("emqx_exhook.hrl"). +-include("src/exhook/include/emqx_exhook.hrl"). -emqx_plugin(extension). diff --git a/apps/emqx_exhook/src/emqx_exhook_cli.erl b/apps/emqx_gateway/src/exhook/emqx_exhook_cli.erl similarity index 98% rename from apps/emqx_exhook/src/emqx_exhook_cli.erl rename to apps/emqx_gateway/src/exhook/emqx_exhook_cli.erl index a8dc43b16..efce962d9 100644 --- a/apps/emqx_exhook/src/emqx_exhook_cli.erl +++ b/apps/emqx_gateway/src/exhook/emqx_exhook_cli.erl @@ -16,7 +16,7 @@ -module(emqx_exhook_cli). --include("emqx_exhook.hrl"). +-include("src/exhook/include/emqx_exhook.hrl"). -export([cli/1]). diff --git a/apps/emqx_exhook/src/emqx_exhook_handler.erl b/apps/emqx_gateway/src/exhook/emqx_exhook_handler.erl similarity index 99% rename from apps/emqx_exhook/src/emqx_exhook_handler.erl rename to apps/emqx_gateway/src/exhook/emqx_exhook_handler.erl index db653c52b..9033fdacc 100644 --- a/apps/emqx_exhook/src/emqx_exhook_handler.erl +++ b/apps/emqx_gateway/src/exhook/emqx_exhook_handler.erl @@ -16,7 +16,7 @@ -module(emqx_exhook_handler). --include("emqx_exhook.hrl"). +-include("src/exhook/include/emqx_exhook.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/logger.hrl"). diff --git a/apps/emqx_exhook/src/emqx_exhook_server.erl b/apps/emqx_gateway/src/exhook/emqx_exhook_server.erl similarity index 99% rename from apps/emqx_exhook/src/emqx_exhook_server.erl rename to apps/emqx_gateway/src/exhook/emqx_exhook_server.erl index a3b132065..79bc52b4e 100644 --- a/apps/emqx_exhook/src/emqx_exhook_server.erl +++ b/apps/emqx_gateway/src/exhook/emqx_exhook_server.erl @@ -16,7 +16,7 @@ -module(emqx_exhook_server). --include("emqx_exhook.hrl"). +-include("src/exhook/include/emqx_exhook.hrl"). -include_lib("emqx/include/logger.hrl"). -logger_header("[ExHook Svr]"). diff --git a/apps/emqx_exhook/src/emqx_exhook_sup.erl b/apps/emqx_gateway/src/exhook/emqx_exhook_sup.erl similarity index 100% rename from apps/emqx_exhook/src/emqx_exhook_sup.erl rename to apps/emqx_gateway/src/exhook/emqx_exhook_sup.erl diff --git a/apps/emqx_exhook/include/emqx_exhook.hrl b/apps/emqx_gateway/src/exhook/include/emqx_exhook.hrl similarity index 100% rename from apps/emqx_exhook/include/emqx_exhook.hrl rename to apps/emqx_gateway/src/exhook/include/emqx_exhook.hrl diff --git a/apps/emqx_gateway/src/exhook/prop_exhook_hooks.erl b/apps/emqx_gateway/src/exhook/prop_exhook_hooks.erl new file mode 100644 index 000000000..d2ee478c3 --- /dev/null +++ b/apps/emqx_gateway/src/exhook/prop_exhook_hooks.erl @@ -0,0 +1,531 @@ +%%-------------------------------------------------------------------- +%% 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(prop_exhook_hooks). + +% -include_lib("proper/include/proper.hrl"). +% -include_lib("eunit/include/eunit.hrl"). + +% -import(emqx_ct_proper_types, +% [ conninfo/0 +% , clientinfo/0 +% , sessioninfo/0 +% , message/0 +% , connack_return_code/0 +% , topictab/0 +% , topic/0 +% , subopts/0 +% ]). + +% -define(ALL(Vars, Types, Exprs), +% ?SETUP(fun() -> +% State = do_setup(), +% fun() -> do_teardown(State) end +% end, ?FORALL(Vars, Types, Exprs))). + +% %%-------------------------------------------------------------------- +% %% Properties +% %%-------------------------------------------------------------------- + +% prop_client_connect() -> +% ?ALL({ConnInfo, ConnProps}, +% {conninfo(), conn_properties()}, +% begin +% ok = emqx_hooks:run('client.connect', [ConnInfo, ConnProps]), +% {'on_client_connect', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{props => properties(ConnProps), +% conninfo => from_conninfo(ConnInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_client_connack() -> +% ?ALL({ConnInfo, Rc, AckProps}, +% {conninfo(), connack_return_code(), ack_properties()}, +% begin +% ok = emqx_hooks:run('client.connack', [ConnInfo, Rc, AckProps]), +% {'on_client_connack', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{props => properties(AckProps), +% result_code => atom_to_binary(Rc, utf8), +% conninfo => from_conninfo(ConnInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_client_authenticate() -> +% ?ALL({ClientInfo0, AuthResult}, +% {clientinfo(), authresult()}, +% begin +% ClientInfo = inject_magic_into(username, ClientInfo0), +% OutAuthResult = emqx_hooks:run_fold('client.authenticate', [ClientInfo], AuthResult), +% ExpectedAuthResult = case maps:get(username, ClientInfo) of +% <<"baduser">> -> +% AuthResult#{ +% auth_result => not_authorized, +% anonymous => false}; +% <<"gooduser">> -> +% AuthResult#{ +% auth_result => success, +% anonymous => false}; +% <<"normaluser">> -> +% AuthResult#{ +% auth_result => success, +% anonymous => false}; +% _ -> +% case maps:get(auth_result, AuthResult) of +% success -> +% #{auth_result => success, +% anonymous => false}; +% _ -> +% #{auth_result => not_authorized, +% anonymous => false} +% end +% end, +% ?assertEqual(ExpectedAuthResult, OutAuthResult), + +% {'on_client_authenticate', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{result => authresult_to_bool(AuthResult), +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_client_authorize() -> +% ?ALL({ClientInfo0, PubSub, Topic, Result}, +% {clientinfo(), oneof([publish, subscribe]), +% topic(), oneof([allow, deny])}, +% begin +% ClientInfo = inject_magic_into(username, ClientInfo0), +% OutResult = emqx_hooks:run_fold( +% 'client.authorize', +% [ClientInfo, PubSub, Topic], +% Result), +% ExpectedOutResult = case maps:get(username, ClientInfo) of +% <<"baduser">> -> deny; +% <<"gooduser">> -> allow; +% <<"normaluser">> -> allow; +% _ -> Result +% end, +% ?assertEqual(ExpectedOutResult, OutResult), + +% {'on_client_authorize', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{result => aclresult_to_bool(Result), +% type => pubsub_to_enum(PubSub), +% topic => Topic, +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_client_connected() -> +% ?ALL({ClientInfo, ConnInfo}, +% {clientinfo(), conninfo()}, +% begin +% ok = emqx_hooks:run('client.connected', [ClientInfo, ConnInfo]), +% {'on_client_connected', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_client_disconnected() -> +% ?ALL({ClientInfo, Reason, ConnInfo}, +% {clientinfo(), shutdown_reason(), conninfo()}, +% begin +% ok = emqx_hooks:run('client.disconnected', [ClientInfo, Reason, ConnInfo]), +% {'on_client_disconnected', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{reason => stringfy(Reason), +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_client_subscribe() -> +% ?ALL({ClientInfo, SubProps, TopicTab}, +% {clientinfo(), sub_properties(), topictab()}, +% begin +% ok = emqx_hooks:run('client.subscribe', [ClientInfo, SubProps, TopicTab]), +% {'on_client_subscribe', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{props => properties(SubProps), +% topic_filters => topicfilters(TopicTab), +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_client_unsubscribe() -> +% ?ALL({ClientInfo, UnSubProps, TopicTab}, +% {clientinfo(), unsub_properties(), topictab()}, +% begin +% ok = emqx_hooks:run('client.unsubscribe', [ClientInfo, UnSubProps, TopicTab]), +% {'on_client_unsubscribe', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{props => properties(UnSubProps), +% topic_filters => topicfilters(TopicTab), +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_session_created() -> +% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, +% begin +% ok = emqx_hooks:run('session.created', [ClientInfo, SessInfo]), +% {'on_session_created', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_session_subscribed() -> +% ?ALL({ClientInfo, Topic, SubOpts}, +% {clientinfo(), topic(), subopts()}, +% begin +% ok = emqx_hooks:run('session.subscribed', [ClientInfo, Topic, SubOpts]), +% {'on_session_subscribed', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{topic => Topic, +% subopts => subopts(SubOpts), +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_session_unsubscribed() -> +% ?ALL({ClientInfo, Topic, SubOpts}, +% {clientinfo(), topic(), subopts()}, +% begin +% ok = emqx_hooks:run('session.unsubscribed', [ClientInfo, Topic, SubOpts]), +% {'on_session_unsubscribed', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{topic => Topic, +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_session_resumed() -> +% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, +% begin +% ok = emqx_hooks:run('session.resumed', [ClientInfo, SessInfo]), +% {'on_session_resumed', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_session_discared() -> +% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, +% begin +% ok = emqx_hooks:run('session.discarded', [ClientInfo, SessInfo]), +% {'on_session_discarded', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_session_takeovered() -> +% ?ALL({ClientInfo, SessInfo}, {clientinfo(), sessioninfo()}, +% begin +% ok = emqx_hooks:run('session.takeovered', [ClientInfo, SessInfo]), +% {'on_session_takeovered', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_session_terminated() -> +% ?ALL({ClientInfo, Reason, SessInfo}, +% {clientinfo(), shutdown_reason(), sessioninfo()}, +% begin +% ok = emqx_hooks:run('session.terminated', [ClientInfo, Reason, SessInfo]), +% {'on_session_terminated', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{reason => stringfy(Reason), +% clientinfo => from_clientinfo(ClientInfo) +% }, +% ?assertEqual(Expected, Resp), +% true +% end). + +% prop_message_publish() -> +% ?ALL(Msg0, message(), +% begin +% Msg = emqx_message:from_map( +% inject_magic_into(from, emqx_message:to_map(Msg0))), +% OutMsg= emqx_hooks:run_fold('message.publish', [], Msg), +% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of +% true -> +% ?assertEqual(Msg, OutMsg), +% skip; +% _ -> +% ExpectedOutMsg = case emqx_message:from(Msg) of +% <<"baduser">> -> +% MsgMap = emqx_message:to_map(Msg), +% emqx_message:from_map( +% MsgMap#{qos => 0, +% topic => <<"">>, +% payload => <<"">> +% }); +% <<"gooduser">> = From -> +% MsgMap = emqx_message:to_map(Msg), +% emqx_message:from_map( +% MsgMap#{topic => From, +% payload => From +% }); +% _ -> Msg +% end, +% ?assertEqual(ExpectedOutMsg, OutMsg), + +% {'on_message_publish', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{message => from_message(Msg) +% }, +% ?assertEqual(Expected, Resp) +% end, +% true +% end). + +% prop_message_dropped() -> +% ?ALL({Msg, By, Reason}, {message(), hardcoded, shutdown_reason()}, +% begin +% ok = emqx_hooks:run('message.dropped', [Msg, By, Reason]), +% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of +% true -> skip; +% _ -> +% {'on_message_dropped', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{reason => stringfy(Reason), +% message => from_message(Msg) +% }, +% ?assertEqual(Expected, Resp) +% end, +% true +% end). + +% prop_message_delivered() -> +% ?ALL({ClientInfo, Msg}, {clientinfo(), message()}, +% begin +% ok = emqx_hooks:run('message.delivered', [ClientInfo, Msg]), +% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of +% true -> skip; +% _ -> +% {'on_message_delivered', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{clientinfo => from_clientinfo(ClientInfo), +% message => from_message(Msg) +% }, +% ?assertEqual(Expected, Resp) +% end, +% true +% end). + +% prop_message_acked() -> +% ?ALL({ClientInfo, Msg}, {clientinfo(), message()}, +% begin +% ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), +% case emqx_topic:match(emqx_message:topic(Msg), <<"$SYS/#">>) of +% true -> skip; +% _ -> +% {'on_message_acked', Resp} = emqx_exhook_demo_svr:take(), +% Expected = +% #{clientinfo => from_clientinfo(ClientInfo), +% message => from_message(Msg) +% }, +% ?assertEqual(Expected, Resp) +% end, +% true +% end). + +% nodestr() -> +% stringfy(node()). + +% peerhost(#{peername := {Host, _}}) -> +% ntoa(Host). + +% sockport(#{sockname := {_, Port}}) -> +% Port. + +% %% copied from emqx_exhook + +% ntoa({0,0,0,0,0,16#ffff,AB,CD}) -> +% list_to_binary(inet_parse:ntoa({AB bsr 8, AB rem 256, CD bsr 8, CD rem 256})); +% ntoa(IP) -> +% list_to_binary(inet_parse:ntoa(IP)). + +% maybe(undefined) -> <<>>; +% maybe(B) -> B. + +% properties(undefined) -> []; +% properties(M) when is_map(M) -> +% maps:fold(fun(K, V, Acc) -> +% [#{name => stringfy(K), +% value => stringfy(V)} | Acc] +% end, [], M). + +% topicfilters(Tfs) when is_list(Tfs) -> +% [#{name => Topic, qos => Qos} || {Topic, #{qos := Qos}} <- Tfs]. + +% %% @private +% stringfy(Term) when is_binary(Term) -> +% Term; +% stringfy(Term) when is_integer(Term) -> +% integer_to_binary(Term); +% stringfy(Term) when is_atom(Term) -> +% atom_to_binary(Term, utf8); +% stringfy(Term) -> +% unicode:characters_to_binary((io_lib:format("~0p", [Term]))). + +% subopts(SubOpts) -> +% #{qos => maps:get(qos, SubOpts, 0), +% rh => maps:get(rh, SubOpts, 0), +% rap => maps:get(rap, SubOpts, 0), +% nl => maps:get(nl, SubOpts, 0), +% share => maps:get(share, SubOpts, <<>>) +% }. + +% authresult_to_bool(AuthResult) -> +% maps:get(auth_result, AuthResult, undefined) == success. + +% aclresult_to_bool(Result) -> +% Result == allow. + +% pubsub_to_enum(publish) -> 'PUBLISH'; +% pubsub_to_enum(subscribe) -> 'SUBSCRIBE'. + +% from_conninfo(ConnInfo) -> +% #{node => nodestr(), +% clientid => maps:get(clientid, ConnInfo), +% username => maybe(maps:get(username, ConnInfo, <<>>)), +% peerhost => peerhost(ConnInfo), +% sockport => sockport(ConnInfo), +% proto_name => maps:get(proto_name, ConnInfo), +% proto_ver => stringfy(maps:get(proto_ver, ConnInfo)), +% keepalive => maps:get(keepalive, ConnInfo) +% }. + +% from_clientinfo(ClientInfo) -> +% #{node => nodestr(), +% clientid => maps:get(clientid, ClientInfo), +% username => maybe(maps:get(username, ClientInfo, <<>>)), +% password => maybe(maps:get(password, ClientInfo, <<>>)), +% peerhost => ntoa(maps:get(peerhost, ClientInfo)), +% sockport => maps:get(sockport, ClientInfo), +% protocol => stringfy(maps:get(protocol, ClientInfo)), +% mountpoint => maybe(maps:get(mountpoint, ClientInfo, <<>>)), +% is_superuser => maps:get(is_superuser, ClientInfo, false), +% anonymous => maps:get(anonymous, ClientInfo, true), +% cn => maybe(maps:get(cn, ClientInfo, <<>>)), +% dn => maybe(maps:get(dn, ClientInfo, <<>>)) +% }. + +% from_message(Msg) -> +% #{node => nodestr(), +% id => emqx_guid:to_hexstr(emqx_message:id(Msg)), +% qos => emqx_message:qos(Msg), +% from => stringfy(emqx_message:from(Msg)), +% topic => emqx_message:topic(Msg), +% payload => emqx_message:payload(Msg), +% timestamp => emqx_message:timestamp(Msg) +% }. + +% %%-------------------------------------------------------------------- +% %% Helper +% %%-------------------------------------------------------------------- + +% do_setup() -> +% logger:set_primary_config(#{level => warning}), +% _ = emqx_exhook_demo_svr:start(), +% emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1), +% %% waiting first loaded event +% {'on_provider_loaded', _} = emqx_exhook_demo_svr:take(), +% ok. + +% do_teardown(_) -> +% emqx_ct_helpers:stop_apps([emqx_exhook]), +% %% waiting last unloaded event +% {'on_provider_unloaded', _} = emqx_exhook_demo_svr:take(), +% _ = emqx_exhook_demo_svr:stop(), +% logger:set_primary_config(#{level => notice}), +% timer:sleep(2000), +% ok. + +% set_special_cfgs(emqx) -> +% application:set_env(emqx, allow_anonymous, false), +% application:set_env(emqx, enable_acl_cache, false), +% application:set_env(emqx, modules_loaded_file, undefined), +% application:set_env(emqx, plugins_loaded_file, +% emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins")); +% set_special_cfgs(emqx_exhook) -> +% ok. + +% %%-------------------------------------------------------------------- +% %% Generators +% %%-------------------------------------------------------------------- + +% conn_properties() -> +% #{}. + +% ack_properties() -> +% #{}. + +% sub_properties() -> +% #{}. + +% unsub_properties() -> +% #{}. + +% shutdown_reason() -> +% oneof([utf8(), {shutdown, emqx_ct_proper_types:limited_atom()}]). + +% authresult() -> +% ?LET(RC, connack_return_code(), #{auth_result => RC}). + +% inject_magic_into(Key, Object) -> +% case castspell() of +% muggles -> Object; +% Spell -> +% Object#{Key => Spell} +% end. + +% castspell() -> +% L = [<<"baduser">>, <<"gooduser">>, <<"normaluser">>, muggles], +% lists:nth(rand:uniform(length(L)), L). diff --git a/apps/emqx_gateway/src/exhook/test/emqx_exhook_SUITE.erl b/apps/emqx_gateway/src/exhook/test/emqx_exhook_SUITE.erl new file mode 100644 index 000000000..d3b859930 --- /dev/null +++ b/apps/emqx_gateway/src/exhook/test/emqx_exhook_SUITE.erl @@ -0,0 +1,97 @@ +%%-------------------------------------------------------------------- +%% 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_exhook_SUITE). + +% -compile(export_all). +% -compile(nowarn_export_all). + + +% -include_lib("eunit/include/eunit.hrl"). +% -include_lib("common_test/include/ct.hrl"). + +% %%-------------------------------------------------------------------- +% %% Setups +% %%-------------------------------------------------------------------- + +% all() -> emqx_ct:all(?MODULE). + +% init_per_suite(Cfg) -> +% _ = emqx_exhook_demo_svr:start(), +% emqx_ct_helpers:start_apps([emqx_exhook], fun set_special_cfgs/1), +% Cfg. + +% end_per_suite(_Cfg) -> +% emqx_ct_helpers:stop_apps([emqx_exhook]), +% emqx_exhook_demo_svr:stop(). + +% set_special_cfgs(emqx) -> +% application:set_env(emqx, allow_anonymous, false), +% application:set_env(emqx, enable_acl_cache, false), +% application:set_env(emqx, plugins_loaded_file, undefined), +% application:set_env(emqx, modules_loaded_file, undefined); +% set_special_cfgs(emqx_exhook) -> +% ok. + +% %%-------------------------------------------------------------------- +% %% Test cases +% %%-------------------------------------------------------------------- + +% t_noserver_nohook(_) -> +% emqx_exhook:disable(default), +% ?assertEqual([], ets:tab2list(emqx_hooks)), + +% Opts = proplists:get_value( +% default, +% application:get_env(emqx_exhook, servers, []) +% ), +% ok = emqx_exhook:enable(default, Opts), +% ?assertNotEqual([], ets:tab2list(emqx_hooks)). + +% t_cli_list(_) -> +% meck_print(), +% ?assertEqual( [[emqx_exhook_server:format(Svr) || Svr <- emqx_exhook:list()]] +% , emqx_exhook_cli:cli(["server", "list"]) +% ), +% unmeck_print(). + +% t_cli_enable_disable(_) -> +% meck_print(), +% ?assertEqual([already_started], emqx_exhook_cli:cli(["server", "enable", "default"])), +% ?assertEqual(ok, emqx_exhook_cli:cli(["server", "disable", "default"])), +% ?assertEqual([], emqx_exhook_cli:cli(["server", "list"])), + +% ?assertEqual([not_running], emqx_exhook_cli:cli(["server", "disable", "default"])), +% ?assertEqual(ok, emqx_exhook_cli:cli(["server", "enable", "default"])), +% unmeck_print(). + +% t_cli_stats(_) -> +% meck_print(), +% _ = emqx_exhook_cli:cli(["server", "stats"]), +% _ = emqx_exhook_cli:cli(x), +% unmeck_print(). + +% %%-------------------------------------------------------------------- +% %% Utils +% %%-------------------------------------------------------------------- + +% meck_print() -> +% meck:new(emqx_ctl, [passthrough, no_history, no_link]), +% meck:expect(emqx_ctl, print, fun(_) -> ok end), +% meck:expect(emqx_ctl, print, fun(_, Args) -> Args end). + +% unmeck_print() -> +% meck:unload(emqx_ctl). diff --git a/apps/emqx_gateway/src/exhook/test/emqx_exhook_demo_svr.erl b/apps/emqx_gateway/src/exhook/test/emqx_exhook_demo_svr.erl new file mode 100644 index 000000000..bcc8865ab --- /dev/null +++ b/apps/emqx_gateway/src/exhook/test/emqx_exhook_demo_svr.erl @@ -0,0 +1,339 @@ +%%-------------------------------------------------------------------- +%% 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_exhook_demo_svr). + +% -behavior(emqx_exhook_v_1_hook_provider_bhvr). + +% %% +% -export([ start/0 +% , stop/0 +% , take/0 +% , in/1 +% ]). + +% %% gRPC server HookProvider callbacks +% -export([ on_provider_loaded/2 +% , on_provider_unloaded/2 +% , on_client_connect/2 +% , on_client_connack/2 +% , on_client_connected/2 +% , on_client_disconnected/2 +% , on_client_authenticate/2 +% , on_client_authorize/2 +% , on_client_subscribe/2 +% , on_client_unsubscribe/2 +% , on_session_created/2 +% , on_session_subscribed/2 +% , on_session_unsubscribed/2 +% , on_session_resumed/2 +% , on_session_discarded/2 +% , on_session_takeovered/2 +% , on_session_terminated/2 +% , on_message_publish/2 +% , on_message_delivered/2 +% , on_message_dropped/2 +% , on_message_acked/2 +% ]). + +% -define(PORT, 9000). +% -define(NAME, ?MODULE). + +% %%-------------------------------------------------------------------- +% %% Server APIs +% %%-------------------------------------------------------------------- + +% start() -> +% Pid = spawn(fun mngr_main/0), +% register(?MODULE, Pid), +% {ok, Pid}. + +% stop() -> +% grpc:stop_server(?NAME), +% ?MODULE ! stop. + +% take() -> +% ?MODULE ! {take, self()}, +% receive {value, V} -> V +% after 5000 -> error(timeout) end. + +% in({FunName, Req}) -> +% ?MODULE ! {in, FunName, Req}. + +% mngr_main() -> +% application:ensure_all_started(grpc), +% Services = #{protos => [emqx_exhook_pb], +% services => #{'emqx.exhook.v1.HookProvider' => emqx_exhook_demo_svr} +% }, +% Options = [], +% Svr = grpc:start_server(?NAME, ?PORT, Services, Options), +% mngr_loop([Svr, queue:new(), queue:new()]). + +% mngr_loop([Svr, Q, Takes]) -> +% receive +% {in, FunName, Req} -> +% {NQ1, NQ2} = reply(queue:in({FunName, Req}, Q), Takes), +% mngr_loop([Svr, NQ1, NQ2]); +% {take, From} -> +% {NQ1, NQ2} = reply(Q, queue:in(From, Takes)), +% mngr_loop([Svr, NQ1, NQ2]); +% stop -> +% exit(normal) +% end. + +% reply(Q1, Q2) -> +% case queue:len(Q1) =:= 0 orelse +% queue:len(Q2) =:= 0 of +% true -> {Q1, Q2}; +% _ -> +% {{value, {Name, V}}, NQ1} = queue:out(Q1), +% {{value, From}, NQ2} = queue:out(Q2), +% From ! {value, {Name, V}}, +% {NQ1, NQ2} +% end. + +% %%-------------------------------------------------------------------- +% %% callbacks +% %%-------------------------------------------------------------------- + +% -spec on_provider_loaded(emqx_exhook_pb:provider_loaded_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:loaded_response(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. + +% on_provider_loaded(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{hooks => [ +% #{name => <<"client.connect">>}, +% #{name => <<"client.connack">>}, +% #{name => <<"client.connected">>}, +% #{name => <<"client.disconnected">>}, +% #{name => <<"client.authenticate">>}, +% #{name => <<"client.authorize">>}, +% #{name => <<"client.subscribe">>}, +% #{name => <<"client.unsubscribe">>}, +% #{name => <<"session.created">>}, +% #{name => <<"session.subscribed">>}, +% #{name => <<"session.unsubscribed">>}, +% #{name => <<"session.resumed">>}, +% #{name => <<"session.discarded">>}, +% #{name => <<"session.takeovered">>}, +% #{name => <<"session.terminated">>}, +% #{name => <<"message.publish">>}, +% #{name => <<"message.delivered">>}, +% #{name => <<"message.acked">>}, +% #{name => <<"message.dropped">>}]}, Md}. +% -spec on_provider_unloaded(emqx_exhook_pb:provider_unloaded_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_provider_unloaded(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_client_connect(emqx_exhook_pb:client_connect_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_connect(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_client_connack(emqx_exhook_pb:client_connack_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_connack(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_client_connected(emqx_exhook_pb:client_connected_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_connected(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_client_disconnected(emqx_exhook_pb:client_disconnected_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_disconnected(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_client_authenticate(emqx_exhook_pb:client_authenticate_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_authenticate(#{clientinfo := #{username := Username}} = Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% %% some cases for testing +% case Username of +% <<"baduser">> -> +% {ok, #{type => 'STOP_AND_RETURN', +% value => {bool_result, false}}, Md}; +% <<"gooduser">> -> +% {ok, #{type => 'STOP_AND_RETURN', +% value => {bool_result, true}}, Md}; +% <<"normaluser">> -> +% {ok, #{type => 'CONTINUE', +% value => {bool_result, true}}, Md}; +% _ -> +% {ok, #{type => 'IGNORE'}, Md} +% end. + +% -spec on_client_authorize(emqx_exhook_pb:client_authorize_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_authorize(#{clientinfo := #{username := Username}} = Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% %% some cases for testing +% case Username of +% <<"baduser">> -> +% {ok, #{type => 'STOP_AND_RETURN', +% value => {bool_result, false}}, Md}; +% <<"gooduser">> -> +% {ok, #{type => 'STOP_AND_RETURN', +% value => {bool_result, true}}, Md}; +% <<"normaluser">> -> +% {ok, #{type => 'CONTINUE', +% value => {bool_result, true}}, Md}; +% _ -> +% {ok, #{type => 'IGNORE'}, Md} +% end. + +% -spec on_client_subscribe(emqx_exhook_pb:client_subscribe_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_subscribe(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_client_unsubscribe(emqx_exhook_pb:client_unsubscribe_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_client_unsubscribe(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_session_created(emqx_exhook_pb:session_created_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_session_created(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_session_subscribed(emqx_exhook_pb:session_subscribed_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_session_subscribed(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_session_unsubscribed(emqx_exhook_pb:session_unsubscribed_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_session_unsubscribed(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_session_resumed(emqx_exhook_pb:session_resumed_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_session_resumed(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_session_discarded(emqx_exhook_pb:session_discarded_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_session_discarded(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_session_takeovered(emqx_exhook_pb:session_takeovered_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_session_takeovered(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_session_terminated(emqx_exhook_pb:session_terminated_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_session_terminated(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_message_publish(emqx_exhook_pb:message_publish_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:valued_response(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_message_publish(#{message := #{from := From} = Msg} = Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% %% some cases for testing +% case From of +% <<"baduser">> -> +% NMsg = Msg#{qos => 0, +% topic => <<"">>, +% payload => <<"">> +% }, +% {ok, #{type => 'STOP_AND_RETURN', +% value => {message, NMsg}}, Md}; +% <<"gooduser">> -> +% NMsg = Msg#{topic => From, +% payload => From}, +% {ok, #{type => 'STOP_AND_RETURN', +% value => {message, NMsg}}, Md}; +% _ -> +% {ok, #{type => 'IGNORE'}, Md} +% end. + +% -spec on_message_delivered(emqx_exhook_pb:message_delivered_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_message_delivered(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_message_dropped(emqx_exhook_pb:message_dropped_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_message_dropped(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. + +% -spec on_message_acked(emqx_exhook_pb:message_acked_request(), grpc:metadata()) +% -> {ok, emqx_exhook_pb:empty_success(), grpc:metadata()} +% | {error, grpc_cowboy_h:error_response()}. +% on_message_acked(Req, Md) -> +% ?MODULE:in({?FUNCTION_NAME, Req}), +% %io:format("fun: ~p, req: ~0p~n", [?FUNCTION_NAME, Req]), +% {ok, #{}, Md}. diff --git a/apps/emqx_exproto/README.md b/apps/emqx_gateway/src/exproto/README.md similarity index 100% rename from apps/emqx_exproto/README.md rename to apps/emqx_gateway/src/exproto/README.md diff --git a/apps/emqx_exproto/src/emqx_exproto.app.src b/apps/emqx_gateway/src/exproto/emqx_exproto.app.src similarity index 100% rename from apps/emqx_exproto/src/emqx_exproto.app.src rename to apps/emqx_gateway/src/exproto/emqx_exproto.app.src diff --git a/apps/emqx_exproto/src/emqx_exproto.erl b/apps/emqx_gateway/src/exproto/emqx_exproto.erl similarity index 99% rename from apps/emqx_exproto/src/emqx_exproto.erl rename to apps/emqx_gateway/src/exproto/emqx_exproto.erl index 453b4989b..c8bff2c72 100644 --- a/apps/emqx_exproto/src/emqx_exproto.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto.erl @@ -16,7 +16,7 @@ -module(emqx_exproto). --include("emqx_exproto.hrl"). +-include("src/exproto/include/emqx_exproto.hrl"). -export([ start_listeners/0 , stop_listeners/0 diff --git a/apps/emqx_exproto/src/emqx_exproto_app.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_app.erl similarity index 100% rename from apps/emqx_exproto/src/emqx_exproto_app.erl rename to apps/emqx_gateway/src/exproto/emqx_exproto_app.erl diff --git a/apps/emqx_exproto/src/emqx_exproto_channel.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_channel.erl similarity index 99% rename from apps/emqx_exproto/src/emqx_exproto_channel.erl rename to apps/emqx_gateway/src/exproto/emqx_exproto_channel.erl index 5b66cacbc..9f2e9c364 100644 --- a/apps/emqx_exproto/src/emqx_exproto_channel.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_channel.erl @@ -15,8 +15,7 @@ %%-------------------------------------------------------------------- -module(emqx_exproto_channel). - --include("emqx_exproto.hrl"). +-include("src/exproto/include/emqx_exproto.hrl"). -include_lib("emqx/include/emqx.hrl"). -include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("emqx/include/types.hrl"). diff --git a/apps/emqx_exproto/src/emqx_exproto_conn.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_conn.erl similarity index 100% rename from apps/emqx_exproto/src/emqx_exproto_conn.erl rename to apps/emqx_gateway/src/exproto/emqx_exproto_conn.erl diff --git a/apps/emqx_exproto/src/emqx_exproto_gcli.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_gcli.erl similarity index 100% rename from apps/emqx_exproto/src/emqx_exproto_gcli.erl rename to apps/emqx_gateway/src/exproto/emqx_exproto_gcli.erl diff --git a/apps/emqx_exproto/src/emqx_exproto_gsvr.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_gsvr.erl similarity index 98% rename from apps/emqx_exproto/src/emqx_exproto_gsvr.erl rename to apps/emqx_gateway/src/exproto/emqx_exproto_gsvr.erl index a4ad5b2e4..3b3d54f71 100644 --- a/apps/emqx_exproto/src/emqx_exproto_gsvr.erl +++ b/apps/emqx_gateway/src/exproto/emqx_exproto_gsvr.erl @@ -17,9 +17,9 @@ %% The gRPC server for ConnectionAdapter -module(emqx_exproto_gsvr). --behavior(emqx_exproto_v_1_connection_adapter_bhvr). +% -behavior(emqx_exproto_v_1_connection_adapter_bhvr). --include("emqx_exproto.hrl"). +-include("src/exproto/include/emqx_exproto.hrl"). -include_lib("emqx/include/logger.hrl"). -logger_header("[ExProto gServer]"). diff --git a/apps/emqx_exproto/src/emqx_exproto_sup.erl b/apps/emqx_gateway/src/exproto/emqx_exproto_sup.erl similarity index 100% rename from apps/emqx_exproto/src/emqx_exproto_sup.erl rename to apps/emqx_gateway/src/exproto/emqx_exproto_sup.erl diff --git a/apps/emqx_exproto/include/emqx_exproto.hrl b/apps/emqx_gateway/src/exproto/include/emqx_exproto.hrl similarity index 100% rename from apps/emqx_exproto/include/emqx_exproto.hrl rename to apps/emqx_gateway/src/exproto/include/emqx_exproto.hrl diff --git a/apps/emqx_gateway/src/exproto/test/emqx_exproto_SUITE.erl b/apps/emqx_gateway/src/exproto/test/emqx_exproto_SUITE.erl new file mode 100644 index 000000000..802204c14 --- /dev/null +++ b/apps/emqx_gateway/src/exproto/test/emqx_exproto_SUITE.erl @@ -0,0 +1,454 @@ +%%-------------------------------------------------------------------- +%% 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_exproto_SUITE). + +% -compile(export_all). +% -compile(nowarn_export_all). + +% -import(emqx_exproto_echo_svr, +% [ frame_connect/2 +% , frame_connack/1 +% , frame_publish/3 +% , frame_puback/1 +% , frame_subscribe/2 +% , frame_suback/1 +% , frame_unsubscribe/1 +% , frame_unsuback/1 +% , frame_disconnect/0 +% ]). + +% -include_lib("emqx/include/emqx.hrl"). +% -include_lib("emqx/include/emqx_mqtt.hrl"). + +% -define(TCPOPTS, [binary, {active, false}]). +% -define(DTLSOPTS, [binary, {active, false}, {protocol, dtls}]). + +% %%-------------------------------------------------------------------- +% %% Setups +% %%-------------------------------------------------------------------- + +% all() -> +% [{group, Name} || Name <- metrics()]. + +% groups() -> +% Cases = emqx_ct:all(?MODULE), +% [{Name, Cases} || Name <- metrics()]. + +% %% @private +% metrics() -> +% [tcp, ssl, udp, dtls]. + +% init_per_group(GrpName, Cfg) -> +% put(grpname, GrpName), +% Svrs = emqx_exproto_echo_svr:start(), +% emqx_ct_helpers:start_apps([emqx_exproto], fun set_special_cfg/1), +% emqx_logger:set_log_level(debug), +% [{servers, Svrs}, {listener_type, GrpName} | Cfg]. + +% end_per_group(_, Cfg) -> +% emqx_ct_helpers:stop_apps([emqx_exproto]), +% emqx_exproto_echo_svr:stop(proplists:get_value(servers, Cfg)). + +% set_special_cfg(emqx_exproto) -> +% LisType = get(grpname), +% Listeners = application:get_env(emqx_exproto, listeners, []), +% SockOpts = socketopts(LisType), +% UpgradeOpts = fun(Opts) -> +% Opts2 = lists:keydelete(tcp_options, 1, Opts), +% Opts3 = lists:keydelete(ssl_options, 1, Opts2), +% Opts4 = lists:keydelete(udp_options, 1, Opts3), +% Opts5 = lists:keydelete(dtls_options, 1, Opts4), +% SockOpts ++ Opts5 +% end, +% NListeners = [{Proto, LisType, LisOn, UpgradeOpts(Opts)} +% || {Proto, _Type, LisOn, Opts} <- Listeners], +% application:set_env(emqx_exproto, listeners, NListeners); +% set_special_cfg(emqx) -> +% application:set_env(emqx, allow_anonymous, true), +% application:set_env(emqx, enable_acl_cache, false), +% ok. + +% %%-------------------------------------------------------------------- +% %% Tests cases +% %%-------------------------------------------------------------------- + +% t_start_stop(_) -> +% ok. + +% t_mountpoint_echo(Cfg) -> +% SockType = proplists:get_value(listener_type, Cfg), +% Sock = open(SockType), + +% Client = #{proto_name => <<"demo">>, +% proto_ver => <<"v0.1">>, +% clientid => <<"test_client_1">>, +% mountpoint => <<"ct/">> +% }, +% Password = <<"123456">>, + +% ConnBin = frame_connect(Client, Password), +% ConnAckBin = frame_connack(0), + +% send(Sock, ConnBin), +% {ok, ConnAckBin} = recv(Sock, 5000), + +% SubBin = frame_subscribe(<<"t/dn">>, 1), +% SubAckBin = frame_suback(0), + +% send(Sock, SubBin), +% {ok, SubAckBin} = recv(Sock, 5000), + +% emqx:publish(emqx_message:make(<<"ct/t/dn">>, <<"echo">>)), +% PubBin1 = frame_publish(<<"t/dn">>, 0, <<"echo">>), +% {ok, PubBin1} = recv(Sock, 5000), + +% PubBin2 = frame_publish(<<"t/up">>, 0, <<"echo">>), +% PubAckBin = frame_puback(0), + +% emqx:subscribe(<<"ct/t/up">>), + +% send(Sock, PubBin2), +% {ok, PubAckBin} = recv(Sock, 5000), + +% receive +% {deliver, _, _} -> ok +% after 1000 -> +% error(echo_not_running) +% end, +% close(Sock). + +% t_auth_deny(Cfg) -> +% SockType = proplists:get_value(listener_type, Cfg), +% Sock = open(SockType), + +% Client = #{proto_name => <<"demo">>, +% proto_ver => <<"v0.1">>, +% clientid => <<"test_client_1">> +% }, +% Password = <<"123456">>, + +% ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), +% ok = meck:expect(emqx_access_control, authenticate, +% fun(_) -> {error, ?RC_NOT_AUTHORIZED} end), + +% ConnBin = frame_connect(Client, Password), +% ConnAckBin = frame_connack(1), + +% send(Sock, ConnBin), +% {ok, ConnAckBin} = recv(Sock, 5000), + +% SockType =/= udp andalso begin +% {error, closed} = recv(Sock, 5000) +% end, +% meck:unload([emqx_access_control]). + +% t_acl_deny(Cfg) -> +% SockType = proplists:get_value(listener_type, Cfg), +% Sock = open(SockType), + +% Client = #{proto_name => <<"demo">>, +% proto_ver => <<"v0.1">>, +% clientid => <<"test_client_1">> +% }, +% Password = <<"123456">>, + +% ok = meck:new(emqx_access_control, [passthrough, no_history, no_link]), +% ok = meck:expect(emqx_access_control, authorize, fun(_, _, _) -> deny end), + +% ConnBin = frame_connect(Client, Password), +% ConnAckBin = frame_connack(0), + +% send(Sock, ConnBin), +% {ok, ConnAckBin} = recv(Sock, 5000), + +% SubBin = frame_subscribe(<<"t/#">>, 1), +% SubAckBin = frame_suback(1), + +% send(Sock, SubBin), +% {ok, SubAckBin} = recv(Sock, 5000), + +% emqx:publish(emqx_message:make(<<"t/dn">>, <<"echo">>)), + +% PubBin = frame_publish(<<"t/dn">>, 0, <<"echo">>), +% PubBinFailedAck = frame_puback(1), +% PubBinSuccesAck = frame_puback(0), + +% send(Sock, PubBin), +% {ok, PubBinFailedAck} = recv(Sock, 5000), + +% meck:unload([emqx_access_control]), + +% send(Sock, PubBin), +% {ok, PubBinSuccesAck} = recv(Sock, 5000), +% close(Sock). + +% t_keepalive_timeout(Cfg) -> +% SockType = proplists:get_value(listener_type, Cfg), +% Sock = open(SockType), + +% Client = #{proto_name => <<"demo">>, +% proto_ver => <<"v0.1">>, +% clientid => <<"test_client_1">>, +% keepalive => 2 +% }, +% Password = <<"123456">>, + +% ConnBin = frame_connect(Client, Password), +% ConnAckBin = frame_connack(0), + +% send(Sock, ConnBin), +% {ok, ConnAckBin} = recv(Sock, 5000), + +% DisconnectBin = frame_disconnect(), +% {ok, DisconnectBin} = recv(Sock, 10000), + +% SockType =/= udp andalso begin +% {error, closed} = recv(Sock, 5000) +% end, ok. + +% t_hook_connected_disconnected(Cfg) -> +% SockType = proplists:get_value(listener_type, Cfg), +% Sock = open(SockType), + +% Client = #{proto_name => <<"demo">>, +% proto_ver => <<"v0.1">>, +% clientid => <<"test_client_1">> +% }, +% Password = <<"123456">>, + +% ConnBin = frame_connect(Client, Password), +% ConnAckBin = frame_connack(0), + +% Parent = self(), +% emqx:hook('client.connected', {?MODULE, hook_fun1, [Parent]}), +% emqx:hook('client.disconnected',{?MODULE, hook_fun2, [Parent]}), + +% send(Sock, ConnBin), +% {ok, ConnAckBin} = recv(Sock, 5000), + +% receive +% connected -> ok +% after 1000 -> +% error(hook_is_not_running) +% end, + +% DisconnectBin = frame_disconnect(), +% send(Sock, DisconnectBin), + +% receive +% disconnected -> ok +% after 1000 -> +% error(hook_is_not_running) +% end, + +% SockType =/= udp andalso begin +% {error, closed} = recv(Sock, 5000) +% end, +% emqx:unhook('client.connected', {?MODULE, hook_fun1}), +% emqx:unhook('client.disconnected', {?MODULE, hook_fun2}). + +% t_hook_session_subscribed_unsubscribed(Cfg) -> +% SockType = proplists:get_value(listener_type, Cfg), +% Sock = open(SockType), + +% Client = #{proto_name => <<"demo">>, +% proto_ver => <<"v0.1">>, +% clientid => <<"test_client_1">> +% }, +% Password = <<"123456">>, + +% ConnBin = frame_connect(Client, Password), +% ConnAckBin = frame_connack(0), + +% send(Sock, ConnBin), +% {ok, ConnAckBin} = recv(Sock, 5000), + +% Parent = self(), +% emqx:hook('session.subscribed', {?MODULE, hook_fun3, [Parent]}), +% emqx:hook('session.unsubscribed', {?MODULE, hook_fun4, [Parent]}), + +% SubBin = frame_subscribe(<<"t/#">>, 1), +% SubAckBin = frame_suback(0), + +% send(Sock, SubBin), +% {ok, SubAckBin} = recv(Sock, 5000), + +% receive +% subscribed -> ok +% after 1000 -> +% error(hook_is_not_running) +% end, + +% UnsubBin = frame_unsubscribe(<<"t/#">>), +% UnsubAckBin = frame_unsuback(0), + +% send(Sock, UnsubBin), +% {ok, UnsubAckBin} = recv(Sock, 5000), + +% receive +% unsubscribed -> ok +% after 1000 -> +% error(hook_is_not_running) +% end, + +% close(Sock), +% emqx:unhook('session.subscribed', {?MODULE, hook_fun3}), +% emqx:unhook('session.unsubscribed', {?MODULE, hook_fun4}). + +% t_hook_message_delivered(Cfg) -> +% SockType = proplists:get_value(listener_type, Cfg), +% Sock = open(SockType), + +% Client = #{proto_name => <<"demo">>, +% proto_ver => <<"v0.1">>, +% clientid => <<"test_client_1">> +% }, +% Password = <<"123456">>, + +% ConnBin = frame_connect(Client, Password), +% ConnAckBin = frame_connack(0), + +% send(Sock, ConnBin), +% {ok, ConnAckBin} = recv(Sock, 5000), + +% SubBin = frame_subscribe(<<"t/#">>, 1), +% SubAckBin = frame_suback(0), + +% send(Sock, SubBin), +% {ok, SubAckBin} = recv(Sock, 5000), + +% emqx:hook('message.delivered', {?MODULE, hook_fun5, []}), + +% emqx:publish(emqx_message:make(<<"t/dn">>, <<"1">>)), +% PubBin1 = frame_publish(<<"t/dn">>, 0, <<"2">>), +% {ok, PubBin1} = recv(Sock, 5000), + +% close(Sock), +% emqx:unhook('message.delivered', {?MODULE, hook_fun5}). + +% %%-------------------------------------------------------------------- +% %% Utils + +% hook_fun1(_, _, Parent) -> Parent ! connected, ok. +% hook_fun2(_, _, _, Parent) -> Parent ! disconnected, ok. + +% hook_fun3(_, _, _, Parent) -> Parent ! subscribed, ok. +% hook_fun4(_, _, _, Parent) -> Parent ! unsubscribed, ok. + +% hook_fun5(_, Msg) -> {ok, Msg#message{payload = <<"2">>}}. + +% rand_bytes() -> +% crypto:strong_rand_bytes(rand:uniform(256)). + +% %%-------------------------------------------------------------------- +% %% Sock funcs + +% open(tcp) -> +% {ok, Sock} = gen_tcp:connect("127.0.0.1", 7993, ?TCPOPTS), +% {tcp, Sock}; +% open(udp) -> +% {ok, Sock} = gen_udp:open(0, ?TCPOPTS), +% {udp, Sock}; +% open(ssl) -> +% SslOpts = client_ssl_opts(), +% {ok, SslSock} = ssl:connect("127.0.0.1", 7993, ?TCPOPTS ++ SslOpts), +% {ssl, SslSock}; +% open(dtls) -> +% SslOpts = client_ssl_opts(), +% {ok, SslSock} = ssl:connect("127.0.0.1", 7993, ?DTLSOPTS ++ SslOpts), +% {dtls, SslSock}. + +% send({tcp, Sock}, Bin) -> +% gen_tcp:send(Sock, Bin); +% send({udp, Sock}, Bin) -> +% gen_udp:send(Sock, "127.0.0.1", 7993, Bin); +% send({ssl, Sock}, Bin) -> +% ssl:send(Sock, Bin); +% send({dtls, Sock}, Bin) -> +% ssl:send(Sock, Bin). + +% recv({tcp, Sock}, Ts) -> +% gen_tcp:recv(Sock, 0, Ts); +% recv({udp, Sock}, Ts) -> +% {ok, {_, _, Bin}} = gen_udp:recv(Sock, 0, Ts), +% {ok, Bin}; +% recv({ssl, Sock}, Ts) -> +% ssl:recv(Sock, 0, Ts); +% recv({dtls, Sock}, Ts) -> +% ssl:recv(Sock, 0, Ts). + +% close({tcp, Sock}) -> +% gen_tcp:close(Sock); +% close({udp, Sock}) -> +% gen_udp:close(Sock); +% close({ssl, Sock}) -> +% ssl:close(Sock); +% close({dtls, Sock}) -> +% ssl:close(Sock). + +% %%-------------------------------------------------------------------- +% %% Server-Opts + +% socketopts(tcp) -> +% [{tcp_options, tcp_opts()}]; +% socketopts(ssl) -> +% [{tcp_options, tcp_opts()}, +% {ssl_options, ssl_opts()}]; +% socketopts(udp) -> +% [{udp_options, udp_opts()}]; +% socketopts(dtls) -> +% [{udp_options, udp_opts()}, +% {dtls_options, dtls_opts()}]. + +% tcp_opts() -> +% [{send_timeout, 15000}, +% {send_timeout_close, true}, +% {backlog, 100}, +% {nodelay, true} | udp_opts()]. + +% udp_opts() -> +% [{recbuf, 1024}, +% {sndbuf, 1024}, +% {buffer, 1024}, +% {reuseaddr, true}]. + +% ssl_opts() -> +% Certs = certs("key.pem", "cert.pem", "cacert.pem"), +% [{versions, emqx_tls_lib:default_versions()}, +% {ciphers, emqx_tls_lib:default_ciphers()}, +% {verify, verify_peer}, +% {fail_if_no_peer_cert, true}, +% {secure_renegotiate, false}, +% {reuse_sessions, true}, +% {honor_cipher_order, true}]++Certs. + +% dtls_opts() -> +% Opts = ssl_opts(), +% lists:keyreplace(versions, 1, Opts, {versions, ['dtlsv1.2', 'dtlsv1']}). + +% %%-------------------------------------------------------------------- +% %% Client-Opts + +% client_ssl_opts() -> +% certs( "client-key.pem", "client-cert.pem", "cacert.pem" ). + +% certs( Key, Cert, CACert ) -> +% CertsPath = emqx_ct_helpers:deps_path(emqx, "etc/certs"), +% [ { keyfile, filename:join([ CertsPath, Key ]) }, +% { certfile, filename:join([ CertsPath, Cert ]) }, +% { cacertfile, filename:join([ CertsPath, CACert ]) } ]. + diff --git a/apps/emqx_gateway/src/exproto/test/emqx_exproto_echo_svr.erl b/apps/emqx_gateway/src/exproto/test/emqx_exproto_echo_svr.erl new file mode 100644 index 000000000..653b481ef --- /dev/null +++ b/apps/emqx_gateway/src/exproto/test/emqx_exproto_echo_svr.erl @@ -0,0 +1,278 @@ +%%-------------------------------------------------------------------- +%% 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_exproto_echo_svr). + +% -behavior(emqx_exproto_v_1_connection_handler_bhvr). + +% -export([ start/0 +% , stop/1 +% ]). + +% -export([ frame_connect/2 +% , frame_connack/1 +% , frame_publish/3 +% , frame_puback/1 +% , frame_subscribe/2 +% , frame_suback/1 +% , frame_unsubscribe/1 +% , frame_unsuback/1 +% , frame_disconnect/0 +% ]). + +% -export([ on_socket_created/2 +% , on_received_bytes/2 +% , on_socket_closed/2 +% , on_timer_timeout/2 +% , on_received_messages/2 +% ]). + +% -define(LOG(Fmt, Args), io:format(standard_error, Fmt, Args)). + +% -define(HTTP, #{grpc_opts => #{service_protos => [emqx_exproto_pb], +% services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE}}, +% listen_opts => #{port => 9001, +% socket_options => []}, +% pool_opts => #{size => 8}, +% transport_opts => #{ssl => false}}). + +% -define(CLIENT, emqx_exproto_v_1_connection_adapter_client). + +% -define(send(Req), ?CLIENT:send(Req, #{channel => ct_test_channel})). +% -define(close(Req), ?CLIENT:close(Req, #{channel => ct_test_channel})). +% -define(authenticate(Req), ?CLIENT:authenticate(Req, #{channel => ct_test_channel})). +% -define(start_timer(Req), ?CLIENT:start_timer(Req, #{channel => ct_test_channel})). +% -define(publish(Req), ?CLIENT:publish(Req, #{channel => ct_test_channel})). +% -define(subscribe(Req), ?CLIENT:subscribe(Req, #{channel => ct_test_channel})). +% -define(unsubscribe(Req), ?CLIENT:unsubscribe(Req, #{channel => ct_test_channel})). + +% -define(TYPE_CONNECT, 1). +% -define(TYPE_CONNACK, 2). +% -define(TYPE_PUBLISH, 3). +% -define(TYPE_PUBACK, 4). +% -define(TYPE_SUBSCRIBE, 5). +% -define(TYPE_SUBACK, 6). +% -define(TYPE_UNSUBSCRIBE, 7). +% -define(TYPE_UNSUBACK, 8). +% -define(TYPE_DISCONNECT, 9). + +% -define(loop_recv_and_reply_empty_success(Stream), +% ?loop_recv_and_reply_empty_success(Stream, fun(_) -> ok end)). + +% -define(loop_recv_and_reply_empty_success(Stream, Fun), +% begin +% LoopRecv = fun _Lp(_St) -> +% case grpc_stream:recv(_St) of +% {more, _Reqs, _NSt} -> +% ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]), +% Fun(_Reqs), _Lp(_NSt); +% {eos, _Reqs, _NSt} -> +% ?LOG("~p: ~p~n", [?FUNCTION_NAME, _Reqs]), +% Fun(_Reqs), _NSt +% end +% end, +% NStream = LoopRecv(Stream), +% grpc_stream:reply(NStream, #{}), +% {ok, NStream} +% end). + +% %%-------------------------------------------------------------------- +% %% APIs +% %%-------------------------------------------------------------------- + +% start() -> +% application:ensure_all_started(grpc), +% [start_channel(), start_server()]. + +% start_channel() -> +% grpc_client_sup:create_channel_pool(ct_test_channel, "http://127.0.0.1:9100", #{}). + +% start_server() -> +% Services = #{protos => [emqx_exproto_pb], +% services => #{'emqx.exproto.v1.ConnectionHandler' => ?MODULE} +% }, +% Options = [], +% grpc:start_server(?MODULE, 9001, Services, Options). + +% stop([_ChannPid, _SvrPid]) -> +% grpc:stop_server(?MODULE), +% grpc_client_sup:stop_channel_pool(ct_test_channel). + +% %%-------------------------------------------------------------------- +% %% Protocol Adapter callbacks +% %%-------------------------------------------------------------------- + +% -spec on_socket_created(grpc_stream:stream(), grpc:metadata()) +% -> {ok, grpc_stream:stream()}. +% on_socket_created(Stream, _Md) -> +% ?loop_recv_and_reply_empty_success(Stream). + +% -spec on_socket_closed(grpc_stream:stream(), grpc:metadata()) +% -> {ok, grpc_stream:stream()}. +% on_socket_closed(Stream, _Md) -> +% ?loop_recv_and_reply_empty_success(Stream). + +% -spec on_received_bytes(grpc_stream:stream(), grpc:metadata()) +% -> {ok, grpc_stream:stream()}. +% on_received_bytes(Stream, _Md) -> +% ?loop_recv_and_reply_empty_success(Stream, +% fun(Reqs) -> +% lists:foreach( +% fun(#{conn := Conn, bytes := Bytes}) -> +% #{<<"type">> := Type} = Params = emqx_json:decode(Bytes, [return_maps]), +% _ = handle_in(Conn, Type, Params) +% end, Reqs) +% end). + +% -spec on_timer_timeout(grpc_stream:stream(), grpc:metadata()) +% -> {ok, grpc_stream:stream()}. +% on_timer_timeout(Stream, _Md) -> +% ?loop_recv_and_reply_empty_success(Stream, +% fun(Reqs) -> +% lists:foreach( +% fun(#{conn := Conn, type := 'KEEPALIVE'}) -> +% ?LOG("Close this connection ~p due to keepalive timeout", [Conn]), +% handle_out(Conn, ?TYPE_DISCONNECT), +% ?close(#{conn => Conn}) +% end, Reqs) +% end). + +% -spec on_received_messages(grpc_stream:stream(), grpc:metadata()) +% -> {ok, grpc_stream:stream()}. +% on_received_messages(Stream, _Md) -> +% ?loop_recv_and_reply_empty_success(Stream, +% fun(Reqs) -> +% lists:foreach( +% fun(#{conn := Conn, messages := Messages}) -> +% lists:foreach(fun(Message) -> +% handle_out(Conn, ?TYPE_PUBLISH, Message) +% end, Messages) +% end, Reqs) +% end). + +% %%-------------------------------------------------------------------- +% %% The Protocol Example: +% %% CONN: +% %% {"type": 1, "clientinfo": {...}} +% %% +% %% CONNACK: +% %% {"type": 2, "code": 0} +% %% +% %% PUBLISH: +% %% {"type": 3, "topic": "xxx", "payload": "", "qos": 0} +% %% +% %% PUBACK: +% %% {"type": 4, "code": 0} +% %% +% %% SUBSCRIBE: +% %% {"type": 5, "topic": "xxx", "qos": 1} +% %% +% %% SUBACK: +% %% {"type": 6, "code": 0} +% %% +% %% DISCONNECT: +% %% {"type": 7, "code": 1} +% %%-------------------------------------------------------------------- + +% handle_in(Conn, ?TYPE_CONNECT, #{<<"clientinfo">> := ClientInfo, <<"password">> := Password}) -> +% NClientInfo = maps:from_list([{binary_to_atom(K, utf8), V} || {K, V} <- maps:to_list(ClientInfo)]), +% case ?authenticate(#{conn => Conn, clientinfo => NClientInfo, password => Password}) of +% {ok, #{code := 'SUCCESS'}, _} -> +% case maps:get(keepalive, NClientInfo, 0) of +% 0 -> ok; +% Intv -> +% io:format("Try call start_timer with ~ps", [Intv]), +% ?start_timer(#{conn => Conn, type => 'KEEPALIVE', interval => Intv}) +% end, +% handle_out(Conn, ?TYPE_CONNACK, 0); +% _ -> +% handle_out(Conn, ?TYPE_CONNACK, 1), +% ?close(#{conn => Conn}) +% end; +% handle_in(Conn, ?TYPE_PUBLISH, #{<<"topic">> := Topic, +% <<"qos">> := Qos, +% <<"payload">> := Payload}) -> +% case ?publish(#{conn => Conn, topic => Topic, qos => Qos, payload => Payload}) of +% {ok, #{code := 'SUCCESS'}, _} -> +% handle_out(Conn, ?TYPE_PUBACK, 0); +% _ -> +% handle_out(Conn, ?TYPE_PUBACK, 1) +% end; +% handle_in(Conn, ?TYPE_SUBSCRIBE, #{<<"qos">> := Qos, <<"topic">> := Topic}) -> +% case ?subscribe(#{conn => Conn, topic => Topic, qos => Qos}) of +% {ok, #{code := 'SUCCESS'}, _} -> +% handle_out(Conn, ?TYPE_SUBACK, 0); +% _ -> +% handle_out(Conn, ?TYPE_SUBACK, 1) +% end; +% handle_in(Conn, ?TYPE_UNSUBSCRIBE, #{<<"topic">> := Topic}) -> +% case ?unsubscribe(#{conn => Conn, topic => Topic}) of +% {ok, #{code := 'SUCCESS'}, _} -> +% handle_out(Conn, ?TYPE_UNSUBACK, 0); +% _ -> +% handle_out(Conn, ?TYPE_UNSUBACK, 1) +% end; + +% handle_in(Conn, ?TYPE_DISCONNECT, _) -> +% ?close(#{conn => Conn}). + +% handle_out(Conn, ?TYPE_CONNACK, Code) -> +% ?send(#{conn => Conn, bytes => frame_connack(Code)}); +% handle_out(Conn, ?TYPE_PUBACK, Code) -> +% ?send(#{conn => Conn, bytes => frame_puback(Code)}); +% handle_out(Conn, ?TYPE_SUBACK, Code) -> +% ?send(#{conn => Conn, bytes => frame_suback(Code)}); +% handle_out(Conn, ?TYPE_UNSUBACK, Code) -> +% ?send(#{conn => Conn, bytes => frame_unsuback(Code)}); +% handle_out(Conn, ?TYPE_PUBLISH, #{qos := Qos, topic := Topic, payload := Payload}) -> +% ?send(#{conn => Conn, bytes => frame_publish(Topic, Qos, Payload)}). + +% handle_out(Conn, ?TYPE_DISCONNECT) -> +% ?send(#{conn => Conn, bytes => frame_disconnect()}). + +% %%-------------------------------------------------------------------- +% %% Frame + +% frame_connect(ClientInfo, Password) -> +% emqx_json:encode(#{type => ?TYPE_CONNECT, +% clientinfo => ClientInfo, +% password => Password}). +% frame_connack(Code) -> +% emqx_json:encode(#{type => ?TYPE_CONNACK, code => Code}). + +% frame_publish(Topic, Qos, Payload) -> +% emqx_json:encode(#{type => ?TYPE_PUBLISH, +% topic => Topic, +% qos => Qos, +% payload => Payload}). + +% frame_puback(Code) -> +% emqx_json:encode(#{type => ?TYPE_PUBACK, code => Code}). + +% frame_subscribe(Topic, Qos) -> +% emqx_json:encode(#{type => ?TYPE_SUBSCRIBE, topic => Topic, qos => Qos}). + +% frame_suback(Code) -> +% emqx_json:encode(#{type => ?TYPE_SUBACK, code => Code}). + +% frame_unsubscribe(Topic) -> +% emqx_json:encode(#{type => ?TYPE_UNSUBSCRIBE, topic => Topic}). + +% frame_unsuback(Code) -> +% emqx_json:encode(#{type => ?TYPE_UNSUBACK, code => Code}). + +% frame_disconnect() -> +% emqx_json:encode(#{type => ?TYPE_DISCONNECT}). diff --git a/apps/emqx_lwm2m/.gitignore b/apps/emqx_gateway/src/lwm2m/.gitignore similarity index 100% rename from apps/emqx_lwm2m/.gitignore rename to apps/emqx_gateway/src/lwm2m/.gitignore diff --git a/apps/emqx_lwm2m/README.md b/apps/emqx_gateway/src/lwm2m/README.md similarity index 100% rename from apps/emqx_lwm2m/README.md rename to apps/emqx_gateway/src/lwm2m/README.md diff --git a/apps/emqx_lwm2m/src/binary_util.erl b/apps/emqx_gateway/src/lwm2m/binary_util.erl similarity index 100% rename from apps/emqx_lwm2m/src/binary_util.erl rename to apps/emqx_gateway/src/lwm2m/binary_util.erl diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m.app.src b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m.app.src similarity index 100% rename from apps/emqx_lwm2m/src/emqx_lwm2m.app.src rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m.app.src diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_api.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_api.erl similarity index 100% rename from apps/emqx_lwm2m/src/emqx_lwm2m_api.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_api.erl diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_app.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_app.erl similarity index 96% rename from apps/emqx_lwm2m/src/emqx_lwm2m_app.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_app.erl index 14d042681..46f29c208 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_app.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_app.erl @@ -25,8 +25,7 @@ , prep_stop/1 ]). --include("emqx_lwm2m.hrl"). - +-include("src/lwm2m/include/emqx_lwm2m.hrl"). start(_Type, _Args) -> Pid = emqx_lwm2m_sup:start_link(), diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_cm.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cm.erl similarity index 100% rename from apps/emqx_lwm2m/src/emqx_lwm2m_cm.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cm.erl diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_cm_sup.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cm_sup.erl similarity index 100% rename from apps/emqx_lwm2m/src/emqx_lwm2m_cm_sup.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cm_sup.erl diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_cmd_handler.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd_handler.erl similarity index 99% rename from apps/emqx_lwm2m/src/emqx_lwm2m_cmd_handler.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd_handler.erl index b3251a275..318328e3c 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_cmd_handler.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_cmd_handler.erl @@ -16,7 +16,7 @@ -module(emqx_lwm2m_cmd_handler). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include_lib("lwm2m_coap/include/coap.hrl"). diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_coap_resource.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_coap_resource.erl similarity index 99% rename from apps/emqx_lwm2m/src/emqx_lwm2m_coap_resource.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_coap_resource.erl index f94c2bc72..8a7b41291 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_coap_resource.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_coap_resource.erl @@ -22,7 +22,7 @@ -include_lib("lwm2m_coap/include/coap.hrl"). --behaviour(lwm2m_coap_resource). +% -behaviour(lwm2m_coap_resource). -export([ coap_discover/2 , coap_get/5 @@ -41,7 +41,7 @@ -export([parse_object_list/1]). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -define(PREFIX, <<"rd">>). diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_coap_server.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_coap_server.erl similarity index 98% rename from apps/emqx_lwm2m/src/emqx_lwm2m_coap_server.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_coap_server.erl index 60d5f4b85..7b6aa86af 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_coap_server.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_coap_server.erl @@ -16,7 +16,7 @@ -module(emqx_lwm2m_coap_server). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -export([ start/1 , stop/1 diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_json.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_json.erl similarity index 99% rename from apps/emqx_lwm2m/src/emqx_lwm2m_json.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_json.erl index 641cf7d97..295c68085 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_json.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_json.erl @@ -22,7 +22,7 @@ , opaque_to_json/2 ]). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -define(LOG(Level, Format, Args), logger:Level("LWM2M-JSON: " ++ Format, Args)). diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_message.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_message.erl similarity index 99% rename from apps/emqx_lwm2m/src/emqx_lwm2m_message.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_message.erl index 6b8bc8d50..6d155f9bd 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_message.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_message.erl @@ -23,7 +23,7 @@ , translate_json/1 ]). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -define(LOG(Level, Format, Args), logger:Level("LWM2M-JSON: " ++ Format, Args)). diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_protocol.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_protocol.erl similarity index 99% rename from apps/emqx_lwm2m/src/emqx_lwm2m_protocol.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_protocol.erl index 51c278b56..729404bb5 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_protocol.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_protocol.erl @@ -16,7 +16,7 @@ -module(emqx_lwm2m_protocol). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include_lib("emqx/include/emqx.hrl"). diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_sup.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_sup.erl similarity index 100% rename from apps/emqx_lwm2m/src/emqx_lwm2m_sup.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_sup.erl diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_timer.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_timer.erl similarity index 97% rename from apps/emqx_lwm2m/src/emqx_lwm2m_timer.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_timer.erl index 75ab2d42a..b86000292 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_timer.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_timer.erl @@ -16,7 +16,7 @@ -module(emqx_lwm2m_timer). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -export([ cancel_timer/1 , start_timer/2 diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_tlv.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_tlv.erl similarity index 99% rename from apps/emqx_lwm2m/src/emqx_lwm2m_tlv.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_tlv.erl index 8576595f8..dd1ecddda 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_tlv.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_tlv.erl @@ -25,7 +25,7 @@ -export([binary_to_hex_string/1]). -endif. --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -define(LOG(Level, Format, Args), logger:Level("LWM2M-TLV: " ++ Format, Args)). diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_xml_object.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object.erl similarity index 98% rename from apps/emqx_lwm2m/src/emqx_lwm2m_xml_object.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object.erl index dd9911407..96a80735f 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_xml_object.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object.erl @@ -16,7 +16,7 @@ -module(emqx_lwm2m_xml_object). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include_lib("xmerl/include/xmerl.hrl"). -export([ get_obj_def/2 diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m_xml_object_db.erl b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl similarity index 99% rename from apps/emqx_lwm2m/src/emqx_lwm2m_xml_object_db.erl rename to apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl index 012d0a649..646fa26b5 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m_xml_object_db.erl +++ b/apps/emqx_gateway/src/lwm2m/emqx_lwm2m_xml_object_db.erl @@ -16,7 +16,7 @@ -module(emqx_lwm2m_xml_object_db). --include("emqx_lwm2m.hrl"). +-include("src/lwm2m/include/emqx_lwm2m.hrl"). -include_lib("xmerl/include/xmerl.hrl"). % This module is for future use. Disabled now. diff --git a/apps/emqx_lwm2m/include/emqx_lwm2m.hrl b/apps/emqx_gateway/src/lwm2m/include/emqx_lwm2m.hrl similarity index 100% rename from apps/emqx_lwm2m/include/emqx_lwm2m.hrl rename to apps/emqx_gateway/src/lwm2m/include/emqx_lwm2m.hrl diff --git a/apps/emqx_lwm2m/lwm2m_xml/LWM2M_Access_Control-v1_0_1.xml b/apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Access_Control-v1_0_1.xml similarity index 100% rename from apps/emqx_lwm2m/lwm2m_xml/LWM2M_Access_Control-v1_0_1.xml rename to apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Access_Control-v1_0_1.xml diff --git a/apps/emqx_lwm2m/lwm2m_xml/LWM2M_Connectivity_Statistics-v1_0_1.xml b/apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Connectivity_Statistics-v1_0_1.xml similarity index 100% rename from apps/emqx_lwm2m/lwm2m_xml/LWM2M_Connectivity_Statistics-v1_0_1.xml rename to apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Connectivity_Statistics-v1_0_1.xml diff --git a/apps/emqx_lwm2m/lwm2m_xml/LWM2M_Device-v1_0_1.xml b/apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Device-v1_0_1.xml similarity index 100% rename from apps/emqx_lwm2m/lwm2m_xml/LWM2M_Device-v1_0_1.xml rename to apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Device-v1_0_1.xml diff --git a/apps/emqx_lwm2m/lwm2m_xml/LWM2M_Firmware_Update-v1_0_1.xml b/apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Firmware_Update-v1_0_1.xml similarity index 100% rename from apps/emqx_lwm2m/lwm2m_xml/LWM2M_Firmware_Update-v1_0_1.xml rename to apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Firmware_Update-v1_0_1.xml diff --git a/apps/emqx_lwm2m/lwm2m_xml/LWM2M_Location-v1_0.xml b/apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Location-v1_0.xml similarity index 100% rename from apps/emqx_lwm2m/lwm2m_xml/LWM2M_Location-v1_0.xml rename to apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Location-v1_0.xml diff --git a/apps/emqx_lwm2m/lwm2m_xml/LWM2M_Security-v1_0.xml b/apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Security-v1_0.xml similarity index 100% rename from apps/emqx_lwm2m/lwm2m_xml/LWM2M_Security-v1_0.xml rename to apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Security-v1_0.xml diff --git a/apps/emqx_lwm2m/lwm2m_xml/LWM2M_Server-v1_0.xml b/apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Server-v1_0.xml similarity index 100% rename from apps/emqx_lwm2m/lwm2m_xml/LWM2M_Server-v1_0.xml rename to apps/emqx_gateway/src/lwm2m/lwm2m_xml/LWM2M_Server-v1_0.xml diff --git a/apps/emqx_gateway/src/lwm2m/test/emqx_lwm2m_SUITE.erl b/apps/emqx_gateway/src/lwm2m/test/emqx_lwm2m_SUITE.erl new file mode 100644 index 000000000..1e9c8f43c --- /dev/null +++ b/apps/emqx_gateway/src/lwm2m/test/emqx_lwm2m_SUITE.erl @@ -0,0 +1,1953 @@ +%%-------------------------------------------------------------------- +%% 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_lwm2m_SUITE). + +% -compile(export_all). +% -compile(nowarn_export_all). + +% -define(PORT, 5683). + +% -define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)). + +% -include("src/lwm2m/include/emqx_lwm2m.hrl"). +% -include_lib("lwm2m_coap/include/coap.hrl"). +% -include_lib("eunit/include/eunit.hrl"). +% -include_lib("common_test/include/ct.hrl"). + +% %%-------------------------------------------------------------------- +% %% Setups +% %%-------------------------------------------------------------------- + +% all() -> +% [ {group, test_grp_0_register} +% , {group, test_grp_1_read} +% , {group, test_grp_2_write} +% , {group, test_grp_3_execute} +% , {group, test_grp_4_discover} +% , {group, test_grp_5_write_attr} +% , {group, test_grp_6_observe} +% , {group, test_grp_8_object_19} +% ]. + +% suite() -> [{timetrap, {seconds, 90}}]. + +% groups() -> +% RepeatOpt = {repeat_until_all_ok, 1}, +% [ +% {test_grp_0_register, [RepeatOpt], [ +% case01_register, +% case01_register_additional_opts, +% case01_register_incorrect_opts, +% case01_register_report, +% case02_update_deregister, +% case03_register_wrong_version, +% case04_register_and_lifetime_timeout, +% case05_register_wrong_epn, +% case06_register_wrong_lifetime, +% case07_register_alternate_path_01, +% case07_register_alternate_path_02, +% case08_reregister +% ]}, +% {test_grp_1_read, [RepeatOpt], [ +% case10_read, +% case10_read_separate_ack, +% case11_read_object_tlv, +% case11_read_object_json, +% case12_read_resource_opaque, +% case13_read_no_xml +% ]}, +% {test_grp_2_write, [RepeatOpt], [ +% case20_write, +% case21_write_object, +% case22_write_error, +% case20_single_write +% ]}, +% {test_grp_create, [RepeatOpt], [ +% case_create_basic +% ]}, +% {test_grp_delete, [RepeatOpt], [ +% case_delete_basic +% ]}, +% {test_grp_3_execute, [RepeatOpt], [ +% case30_execute, case31_execute_error +% ]}, +% {test_grp_4_discover, [RepeatOpt], [ +% case40_discover +% ]}, +% {test_grp_5_write_attr, [RepeatOpt], [ +% case50_write_attribute +% ]}, +% {test_grp_6_observe, [RepeatOpt], [ +% case60_observe +% ]}, +% {test_grp_7_block_wize_transfer, [RepeatOpt], [ +% case70_read_large, case70_write_large +% ]}, +% {test_grp_8_object_19, [RepeatOpt], [ +% case80_specail_object_19_1_0_write, +% case80_specail_object_19_0_0_notify +% %case80_specail_object_19_0_0_response, +% %case80_normal_object_19_0_0_read +% ]}, +% {test_grp_9_psm_queue_mode, [RepeatOpt], [ +% case90_psm_mode, +% case90_queue_mode +% ]} +% ]. + +% init_per_suite(Config) -> +% emqx_ct_helpers:start_apps([emqx]), +% Config. + +% end_per_suite(Config) -> +% timer:sleep(300), +% emqx_ct_helpers:stop_apps([emqx]), +% Config. + +% init_per_testcase(_AllTestCase, Config) -> +% application:set_env(emqx_lwm2m, bind_udp, [{5683, []}]), +% application:set_env(emqx_lwm2m, bind_dtls, [{5684, []}]), +% application:set_env(emqx_lwm2m, xml_dir, emqx_ct_helpers:deps_path(emqx_lwm2m, "lwm2m_xml")), +% application:set_env(emqx_lwm2m, lifetime_max, 86400), +% application:set_env(emqx_lwm2m, lifetime_min, 1), +% application:set_env(emqx_lwm2m, mountpoint, "lwm2m/%e/"), +% {ok, _Started} = application:ensure_all_started(emqx_lwm2m), +% {ok, ClientUdpSock} = gen_udp:open(0, [binary, {active, false}]), + +% {ok, C} = emqtt:start_link([{host, "localhost"},{port, 1883},{clientid, <<"c1">>}]), +% {ok, _} = emqtt:connect(C), +% timer:sleep(100), + +% [{sock, ClientUdpSock}, {emqx_c, C} | Config]. + +% end_per_testcase(_AllTestCase, Config) -> +% timer:sleep(300), +% gen_udp:close(?config(sock, Config)), +% emqtt:disconnect(?config(emqx_c, Config)), +% ok = application:stop(emqx_lwm2m), +% ok = application:stop(lwm2m_coap). + +% %%-------------------------------------------------------------------- +% %% Cases +% %%-------------------------------------------------------------------- + +% case01_register(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), + +% %% checkpoint 1 - response +% #coap_message{type = Type, method = Method, id = RspId, options = Opts} = +% test_recv_coap_response(UdpSock), +% ack = Type, +% {ok, created} = Method, +% RspId = MsgId, +% Location = proplists:get_value(location_path, Opts), +% ?assertNotEqual(undefined, Location), + +% %% checkpoint 2 - verify subscribed topics +% timer:sleep(50), +% ?LOGT("all topics: ~p", [test_mqtt_broker:get_subscrbied_topics()]), +% true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), + +% % ---------------------------------------- +% % DE-REGISTER command +% % ---------------------------------------- +% ?LOGT("start to send DE-REGISTER command", []), +% MsgId3 = 52, +% test_send_coap_request( UdpSock, +% delete, +% sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), +% #coap_content{payload = <<>>}, +% [], +% MsgId3), +% #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), +% {ok,deleted} = Method3, +% MsgId3 = RspId3, +% timer:sleep(50), +% false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case01_register_additional_opts(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), + +% AddOpts = "ep=~s<=345&lwm2m=1&apn=psmA.eDRX0.ctnb&cust_opt=shawn&im=123&ct=1.4&mt=mdm9620&mv=1.2", +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), + +% %% checkpoint 1 - response +% #coap_message{type = Type, method = Method, id = RspId, options = Opts} = +% test_recv_coap_response(UdpSock), +% Type = ack, +% Method = {ok, created}, +% RspId = MsgId, +% Location = proplists:get_value(location_path, Opts), +% ?assertNotEqual(undefined, Location), + +% %% checkpoint 2 - verify subscribed topics +% timer:sleep(50), + +% true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), + +% % ---------------------------------------- +% % DE-REGISTER command +% % ---------------------------------------- +% ?LOGT("start to send DE-REGISTER command", []), +% MsgId3 = 52, +% test_send_coap_request( UdpSock, +% delete, +% sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), +% #coap_content{payload = <<>>}, +% [], +% MsgId3), +% #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), +% {ok,deleted} = Method3, +% MsgId3 = RspId3, +% timer:sleep(50), +% false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case01_register_incorrect_opts(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, + + +% AddOpts = "ep=~s<=345&lwm2m=1&incorrect_opt", +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), + +% %% checkpoint 1 - response +% #coap_message{type = ack, method = Method, id = MsgId} = +% test_recv_coap_response(UdpSock), +% ?assertEqual({error,bad_request}, Method). + +% case01_register_report(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), +% ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), + +% #coap_message{type = Type, method = Method, id = RspId, options = Opts} = +% test_recv_coap_response(UdpSock), +% Type = ack, +% Method = {ok, created}, +% RspId = MsgId, +% Location = proplists:get_value(location_path, Opts), +% ?assertNotEqual(undefined, Location), + +% timer:sleep(50), +% true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), + +% ReadResult = emqx_json:encode(#{ +% <<"msgType">> => <<"register">>, +% <<"data">> => #{ +% <<"alternatePath">> => <<"/">>, +% <<"ep">> => list_to_binary(Epn), +% <<"lt">> => 345, +% <<"lwm2m">> => <<"1">>, +% <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)), + +% % ---------------------------------------- +% % DE-REGISTER command +% % ---------------------------------------- +% ?LOGT("start to send DE-REGISTER command", []), +% MsgId3 = 52, +% test_send_coap_request( UdpSock, +% delete, +% sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), +% #coap_content{payload = <<>>}, +% [], +% MsgId3), +% #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), +% {ok,deleted} = Method3, +% MsgId3 = RspId3, +% timer:sleep(50), +% false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case02_update_deregister(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), +% ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), +% timer:sleep(100), +% #coap_message{type = ack, method = Method, options = Opts} = test_recv_coap_response(UdpSock), +% ?assertEqual({ok,created}, Method), + +% ?LOGT("Options got: ~p", [Opts]), +% Location = proplists:get_value(location_path, Opts), +% Register = emqx_json:encode(#{ +% <<"msgType">> => <<"register">>, +% <<"data">> => #{ +% <<"alternatePath">> => <<"/">>, +% <<"ep">> => list_to_binary(Epn), +% <<"lt">> => 345, +% <<"lwm2m">> => <<"1">>, +% <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>] +% } +% }), +% ?assertEqual(Register, test_recv_mqtt_response(ReportTopic)), + +% % ---------------------------------------- +% % UPDATE command +% % ---------------------------------------- +% ?LOGT("start to send UPDATE command", []), +% MsgId2 = 27, +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b~s?lt=789", [?PORT, join_path(Location, <<>>)]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , , ">>}, +% [], +% MsgId2), +% #coap_message{type = ack, id = RspId2, method = Method2} = test_recv_coap_response(UdpSock), +% {ok,changed} = Method2, +% MsgId2 = RspId2, +% Update = emqx_json:encode(#{ +% <<"msgType">> => <<"update">>, +% <<"data">> => #{ +% <<"alternatePath">> => <<"/">>, +% <<"ep">> => list_to_binary(Epn), +% <<"lt">> => 789, +% <<"lwm2m">> => <<"1">>, +% <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>, <<"/6">>] +% } +% }), +% ?assertEqual(Update, test_recv_mqtt_response(ReportTopic)), + +% % ---------------------------------------- +% % DE-REGISTER command +% % ---------------------------------------- +% ?LOGT("start to send DE-REGISTER command", []), +% MsgId3 = 52, +% test_send_coap_request( UdpSock, +% delete, +% sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), +% #coap_content{payload = <<>>}, +% [], +% MsgId3), +% #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), +% {ok,deleted} = Method3, +% MsgId3 = RspId3, + +% timer:sleep(50), +% false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case03_register_wrong_version(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=8.3", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), +% #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), +% ?assertEqual({error,precondition_failed}, Method), +% timer:sleep(50), + +% false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case04_register_and_lifetime_timeout(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=2&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), +% timer:sleep(100), +% #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), +% ?assertEqual({ok,created}, Method), + +% true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), + +% % ---------------------------------------- +% % lifetime timeout +% % ---------------------------------------- +% timer:sleep(4000), + +% false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case05_register_wrong_epn(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% MsgId = 12, +% UdpSock = ?config(sock, Config), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?lt=345&lwm2m=1.0", [?PORT]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), +% #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), +% ?assertEqual({error,bad_request}, Method). + +% case06_register_wrong_lifetime(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId), +% #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), +% ?assertEqual({error,bad_request}, Method), +% timer:sleep(50), +% ?assertEqual([], test_mqtt_broker:get_subscrbied_topics()). + +% case07_register_alternate_path_01(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), +% ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, +% payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, +% [], +% MsgId), +% timer:sleep(50), +% true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case07_register_alternate_path_02(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), +% ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, +% payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, +% [], +% MsgId), +% timer:sleep(50), +% true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). + +% case08_reregister(Config) -> +% % ---------------------------------------- +% % REGISTER command +% % ---------------------------------------- +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId = 12, +% SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), +% ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, +% payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, +% [], +% MsgId), +% timer:sleep(50), +% true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), + +% ReadResult = emqx_json:encode( +% #{ +% <<"msgType">> => <<"register">>, +% <<"data">> => #{ +% <<"alternatePath">> => <<"/lwm2m">>, +% <<"ep">> => list_to_binary(Epn), +% <<"lt">> => 345, +% <<"lwm2m">> => <<"1">>, +% <<"objectList">> => [<<"/1/0">>, <<"/2/0">>, <<"/3/0">>] +% } +% } +% ), +% ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)), +% timer:sleep(1000), + +% %% the same lwm2mc client registers to server again +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, +% payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, +% [], +% MsgId + 1), +% %% verify the lwm2m client is still online +% ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)). + +% case10_read(Config) -> +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), +% % step 1, device register ... +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, +% payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, +% [], +% MsgId1), +% #coap_message{method = Method1} = test_recv_coap_response(UdpSock), +% ?assertEqual({ok,created}, Method1), +% test_recv_mqtt_response(RespTopic), + +% % step2, send a READ command to device +% CmdId = 206, +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% Command = #{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% ?LOGT("CommandJson=~p", [CommandJson]), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% ?LOGT("LwM2M client got ~p", [Request2]), + +% ?assertEqual(get, Method2), +% ?assertEqual(<<"/lwm2m/3/0/0">>, get_coap_path(Options2)), +% ?assertEqual(<<>>, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"reqPath">> => <<"/3/0/0">>, +% <<"content">> => [#{ +% path => <<"/3/0/0">>, +% value => <<"EMQ">> +% }] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case10_read_separate_ack(Config) -> +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), + +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% % step 1, device register ... +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a READ command to device +% CmdId = 206, +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% Command = #{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% ?LOGT("CommandJson=~p", [CommandJson]), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% ?LOGT("LwM2M client got ~p", [Request2]), + +% ?assertEqual(get, Method2), +% ?assertEqual(<<"/3/0/0">>, get_coap_path(Options2)), +% ?assertEqual(<<>>, Payload2), + +% test_send_empty_ack(UdpSock, "127.0.0.1", ?PORT, Request2), +% ReadResultACK = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"ack">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/0">> +% } +% }), +% ?assertEqual(ReadResultACK, test_recv_mqtt_response(RespTopic)), +% timer:sleep(100), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request2, false), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"reqPath">> => <<"/3/0/0">>, +% <<"content">> => [#{ +% path => <<"/3/0/0">>, +% value => <<"EMQ">> +% }] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case11_read_object_tlv(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a READ command to device +% CmdId = 207, +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% Command = #{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% ?LOGT("CommandJson=~p", [CommandJson]), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2} = Request2, +% ?LOGT("LwM2M client got ~p", [Request2]), + +% ?assertEqual(get, Method2), +% timer:sleep(50), + +% Tlv = <<16#08, 16#00, 16#3C, 16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33>>, +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"application/vnd.oma.lwm2m+tlv">>, payload = Tlv}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"reqPath">> => <<"/3/0">>, +% <<"content">> => [ +% #{ +% path => <<"/3/0/0">>, +% value => <<"Open Mobile Alliance">> +% }, +% #{ +% path => <<"/3/0/1">>, +% value => <<"Lightweight M2M Client">> +% }, +% #{ +% path => <<"/3/0/2">>, +% value => <<"345000123">> +% } +% ] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case11_read_object_json(Config) -> +% % step 1, device register ... +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, + +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% ObjectList = <<", , , , ">>, +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a READ command to device +% CmdId = 206, +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% Command = #{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% ?LOGT("CommandJson=~p", [CommandJson]), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2} = Request2, +% ?LOGT("LwM2M client got ~p", [Request2]), + +% ?assertEqual(get, Method2), +% timer:sleep(50), + +% Json = <<"{\"bn\":\"/3/0\",\"e\":[{\"n\":\"0\",\"sv\":\"Open Mobile Alliance\"},{\"n\":\"1\",\"sv\":\"Lightweight M2M Client\"},{\"n\":\"2\",\"sv\":\"345000123\"}]}">>, +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"application/vnd.oma.lwm2m+json">>, payload = Json}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"reqPath">> => <<"/3/0">>, +% <<"content">> => [ +% #{ +% path => <<"/3/0/0">>, +% value => <<"Open Mobile Alliance">> +% }, +% #{ +% path => <<"/3/0/1">>, +% value => <<"Lightweight M2M Client">> +% }, +% #{ +% path => <<"/3/0/2">>, +% value => <<"345000123">> +% } +% ] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case12_read_resource_opaque(Config) -> +% % step 1, device register ... +% UdpSock = ?config(sock, Config), +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a READ command to device +% CmdId = 206, +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% Command = #{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/8">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% ?LOGT("CommandJson=~p", [CommandJson]), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2} = Request2, +% ?LOGT("LwM2M client got ~p", [Request2]), + +% ?assertEqual(get, Method2), +% timer:sleep(50), + +% Opaque = <<20, 21, 22, 23>>, +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"application/octet-stream">>, payload = Opaque}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"reqPath">> => <<"/3/0/8">>, +% <<"content">> => [ +% #{ +% path => <<"/3/0/8">>, +% value => base64:encode(Opaque) +% } +% ] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case13_read_no_xml(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a READ command to device +% CmdId = 206, +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% Command = #{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"path">> => <<"/9723/0/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% ?LOGT("CommandJson=~p", [CommandJson]), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2} = Request2, +% ?LOGT("LwM2M client got ~p", [Request2]), + +% ?assertEqual(get, Method2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"reqPath">> => <<"/9723/0/0">>, +% <<"code">> => <<"4.00">>, +% <<"codeMsg">> => <<"bad_request">> +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case20_single_write(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"write">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/13">>, +% <<"type">> => <<"Integer">>, +% <<"value">> => <<"12345">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(put, Method2), +% ?assertEqual(<<"/3/0/13">>, Path2), +% Tlv_Value = <<3:2, 0:1, 0:2, 2:3, 13, 12345:16>>, +% ?assertEqual(Tlv_Value, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/13">>, +% <<"code">> => <<"2.04">>, +% <<"codeMsg">> => <<"changed">> +% }, +% <<"msgType">> => <<"write">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case20_write(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"write">>, +% <<"data">> => #{ +% <<"basePath">> => <<"/3/0/13">>, +% <<"content">> => [#{ +% type => <<"Float">>, +% value => <<"12345.0">> +% }] +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(put, Method2), +% ?assertEqual(<<"/3/0/13">>, Path2), +% Tlv_Value = <<200, 13, 8, 64,200,28,128,0,0,0,0>>, +% ?assertEqual(Tlv_Value, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% WriteResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/13">>, +% <<"code">> => <<"2.04">>, +% <<"codeMsg">> => <<"changed">> +% }, +% <<"msgType">> => <<"write">> +% }), +% ?assertEqual(WriteResult, test_recv_mqtt_response(RespTopic)). + +% case21_write_object(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"write">>, +% <<"data">> => #{ +% <<"basePath">> => <<"/3/0/">>, +% <<"content">> => [#{ +% path => <<"13">>, +% type => <<"Integer">>, +% value => <<"12345">> +% },#{ +% path => <<"14">>, +% type => <<"String">>, +% value => <<"87x">> +% }] +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(post, Method2), +% ?assertEqual(<<"/3/0">>, Path2), +% Tlv_Value = <<3:2, 0:1, 0:2, 2:3, 13, 12345:16, +% 3:2, 0:1, 0:2, 3:3, 14, "87x">>, +% ?assertEqual(Tlv_Value, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), +% timer:sleep(100), + + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"write">>, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/">>, +% <<"code">> => <<"2.04">>, +% <<"codeMsg">> => <<"changed">> +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case22_write_error(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"write">>, +% <<"data">> => #{ +% <<"basePath">> => <<"/3/0/1">>, +% <<"content">> => [ +% #{ +% type => <<"Integer">>, +% value => <<"12345">> +% } +% ] +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(put, Method2), +% ?assertEqual(<<"/3/0/1">>, Path2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {error, bad_request}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/1">>, +% <<"code">> => <<"4.00">>, +% <<"codeMsg">> => <<"bad_request">> +% }, +% <<"msgType">> => <<"write">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case_create_basic(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a CREATE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"create">>, +% <<"data">> => #{ +% <<"path">> => <<"/5">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(post, Method2), +% ?assertEqual(<<"/5">>, Path2), +% ?assertEqual(<<"">>, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, created}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/5">>, +% <<"code">> => <<"2.01">>, +% <<"codeMsg">> => <<"created">> +% }, +% <<"msgType">> => <<"create">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case_delete_basic(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a CREATE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"delete">>, +% <<"data">> => #{ +% <<"path">> => <<"/5/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(delete, Method2), +% ?assertEqual(<<"/5/0">>, Path2), +% ?assertEqual(<<"">>, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, deleted}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/5/0">>, +% <<"code">> => <<"2.02">>, +% <<"codeMsg">> => <<"deleted">> +% }, +% <<"msgType">> => <<"delete">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case30_execute(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"execute">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/4">>, +% %% "args" should not be present for "/3/0/4", only for testing the encoding here +% <<"args">> => <<"2,7">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(post, Method2), +% ?assertEqual(<<"/3/0/4">>, Path2), +% ?assertEqual(<<"2,7">>, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/4">>, +% <<"code">> => <<"2.04">>, +% <<"codeMsg">> => <<"changed">> +% }, +% <<"msgType">> => <<"execute">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case31_execute_error(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"execute">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/4">>, +% <<"args">> => <<"2,7">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(post, Method2), +% ?assertEqual(<<"/3/0/4">>, Path2), +% ?assertEqual(<<"2,7">>, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {error, uauthorized}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/4">>, +% <<"code">> => <<"4.01">>, +% <<"codeMsg">> => <<"uauthorized">> +% }, +% <<"msgType">> => <<"execute">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case40_discover(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"discover">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/7">> +% } }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(get, Method2), +% ?assertEqual(<<"/3/0/7">>, Path2), +% ?assertEqual(<<>>, Payload2), +% timer:sleep(50), + +% PayloadDiscover = <<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2,">>, +% test_send_coap_response(UdpSock, +% "127.0.0.1", +% ?PORT, +% {ok, content}, +% #coap_content{content_format = <<"application/link-format">>, payload = PayloadDiscover}, +% Request2, +% true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"discover">>, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/7">>, +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"content">> => +% [<<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2">>, <<"">>] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case50_write_attribute(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"write-attr">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/9">>, +% <<"pmin">> => <<"1">>, +% <<"pmax">> => <<"5">>, +% <<"lt">> => <<"5">> +% } }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(100), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% ?LOGT("got options: ~p", [Options2]), +% Path2 = get_coap_path(Options2), +% Query2 = lists:sort(get_coap_query(Options2)), +% ?assertEqual(put, Method2), +% ?assertEqual(<<"/3/0/9">>, Path2), +% ?assertEqual(lists:sort([<<"pmax=5">>,<<"lt=5">>,<<"pmin=1">>]), Query2), +% ?assertEqual(<<>>, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, +% "127.0.0.1", +% ?PORT, +% {ok, changed}, +% #coap_content{}, +% Request2, +% true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/9">>, +% <<"code">> => <<"2.04">>, +% <<"codeMsg">> => <<"changed">> +% }, +% <<"msgType">> => <<"write-attr">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case60_observe(Config) -> +% % step 1, device register ... +% Epn = "urn:oma:lwm2m:oma:3", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% ObjectList = <<", , , , ">>, +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% RespTopicAD = list_to_binary("lwm2m/"++Epn++"/up/notify"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% emqtt:subscribe(?config(emqx_c, Config), RespTopicAD, qos0), +% timer:sleep(200), + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), + +% % step2, send a OBSERVE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"observe">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/10">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% Observe = get_coap_observe(Options2), +% ?assertEqual(get, Method2), +% ?assertEqual(<<"/3/0/10">>, Path2), +% ?assertEqual(Observe, 0), +% ?assertEqual(<<>>, Payload2), +% timer:sleep(50), + +% test_send_coap_observe_ack( UdpSock, +% "127.0.0.1", +% ?PORT, +% {ok, content}, +% #coap_content{content_format = <<"text/plain">>, payload = <<"2048">>}, +% Request2), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"observe">>, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/10">>, +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"content">> => [#{ +% path => <<"/3/0/10">>, +% value => 2048 +% }] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)), + +% %% step3 the notifications +% timer:sleep(200), +% ObSeq = 3, +% test_send_coap_notif( UdpSock, +% "127.0.0.1", +% ?PORT, +% #coap_content{content_format = <<"text/plain">>, payload = <<"4096">>}, +% ObSeq, +% Request2), +% timer:sleep(100), +% #coap_message{} = test_recv_coap_response(UdpSock), + +% ReadResult2 = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"notify">>, +% <<"seqNum">> => ObSeq, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/10">>, +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"content">> => [#{ +% path => <<"/3/0/10">>, +% value => 4096 +% }] +% } +% }), +% ?assertEqual(ReadResult2, test_recv_mqtt_response(RespTopicAD)), + +% %% Step3. cancel observe +% CmdId3 = 308, +% Command3 = #{<<"requestID">> => CmdId3, <<"cacheID">> => CmdId3, +% <<"msgType">> => <<"cancel-observe">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/10">> +% } +% }, +% CommandJson3 = emqx_json:encode(Command3), +% test_mqtt_broker:publish(CommandTopic, CommandJson3, 0), +% timer:sleep(50), +% Request3 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method3, options=Options3, payload=Payload3} = Request3, +% Path3 = get_coap_path(Options3), +% Observe3 = get_coap_observe(Options3), +% ?assertEqual(get, Method3), +% ?assertEqual(<<"/3/0/10">>, Path3), +% ?assertEqual(Observe3, 1), +% ?assertEqual(<<>>, Payload3), +% timer:sleep(50), + +% test_send_coap_observe_ack( UdpSock, +% "127.0.0.1", +% ?PORT, +% {ok, content}, +% #coap_content{content_format = <<"text/plain">>, payload = <<"1150">>}, +% Request3), +% timer:sleep(100), + +% ReadResult3 = emqx_json:encode(#{ +% <<"requestID">> => CmdId3, <<"cacheID">> => CmdId3, +% <<"msgType">> => <<"cancel-observe">>, +% <<"data">> => #{ +% <<"reqPath">> => <<"/3/0/10">>, +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"content">> => [#{ +% path => <<"/3/0/10">>, +% value => 1150 +% }] +% } +% }), +% ?assertEqual(ReadResult3, test_recv_mqtt_response(RespTopic)). + +% case80_specail_object_19_0_0_notify(Config) -> +% % step 1, device register, with extra register options +% Epn = "urn:oma:lwm2m:oma:3", +% RegOptionWangYi = "&apn=psmA.eDRX0.ctnb&im=13456&ct=2.0&mt=MDM9206&mv=4.0", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), + +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1"++RegOptionWangYi, [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId1), +% #coap_message{method = Method1} = test_recv_coap_response(UdpSock), +% ?assertEqual({ok,created}, Method1), +% ReadResult = emqx_json:encode(#{ +% <<"msgType">> => <<"register">>, +% <<"data">> => #{ +% <<"alternatePath">> => <<"/">>, +% <<"ep">> => list_to_binary(Epn), +% <<"lt">> => 345, +% <<"lwm2m">> => <<"1">>, +% <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>], +% <<"apn">> => <<"psmA.eDRX0.ctnb">>, +% <<"im">> => <<"13456">>, +% <<"ct">> => <<"2.0">>, +% <<"mt">> => <<"MDM9206">>, +% <<"mv">> => <<"4.0">> +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)), + +% % step2, send a OBSERVE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"observe">>, +% <<"data">> => #{ +% <<"path">> => <<"/19/0/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% Observe = get_coap_observe(Options2), +% ?assertEqual(get, Method2), +% ?assertEqual(<<"/19/0/0">>, Path2), +% ?assertEqual(Observe, 0), +% ?assertEqual(<<>>, Payload2), +% timer:sleep(50), + +% test_send_coap_observe_ack( UdpSock, +% "127.0.0.1", +% ?PORT, +% {ok, content}, +% #coap_content{content_format = <<"text/plain">>, payload = <<"2048">>}, +% Request2), +% timer:sleep(100). + +% %% step 3, device send uplink data notifications + +% case80_specail_object_19_1_0_write(Config) -> +% Epn = "urn:oma:lwm2m:oma:3", +% RegOptionWangYi = "&apn=psmA.eDRX0.ctnb&im=13456&ct=2.0&mt=MDM9206&mv=4.0", +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1"++RegOptionWangYi, [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId1), +% #coap_message{method = Method1} = test_recv_coap_response(UdpSock), +% ?assertEqual({ok,created}, Method1), +% test_recv_mqtt_response(RespTopic), + +% % step2, send a WRITE command to device +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% CmdId = 307, +% Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"write">>, +% <<"data">> => #{ +% <<"path">> => <<"/19/1/0">>, +% <<"type">> => <<"Opaque">>, +% <<"value">> => base64:encode(<<12345:32>>) +% } +% }, + +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50), +% Request2 = test_recv_coap_request(UdpSock), +% #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, +% Path2 = get_coap_path(Options2), +% ?assertEqual(put, Method2), +% ?assertEqual(<<"/19/1/0">>, Path2), +% ?assertEqual(<<3:2, 0:1, 0:2, 4:3, 0, 12345:32>>, Payload2), +% timer:sleep(50), + +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), +% timer:sleep(100), + +% ReadResult = emqx_json:encode(#{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"data">> => #{ +% <<"reqPath">> => <<"/19/1/0">>, +% <<"code">> => <<"2.04">>, +% <<"codeMsg">> => <<"changed">> +% }, +% <<"msgType">> => <<"write">> +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% case90_psm_mode(Config) -> +% server_cache_mode(Config, "ep=~s<=345&lwm2m=1&apn=psmA.eDRX0.ctnb"). + +% case90_queue_mode(Config) -> +% server_cache_mode(Config, "ep=~s<=345&lwm2m=1&b=UQ"). + +% server_cache_mode(Config, RegOption) -> +% application:set_env(?APP, qmode_time_window, 2), + +% % step 1, device register, with apn indicates "PSM" mode +% Epn = "urn:oma:lwm2m:oma:3", + +% MsgId1 = 15, +% UdpSock = ?config(sock, Config), +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), +% timer:sleep(200), + +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?"++RegOption, [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, +% [], +% MsgId1), +% #coap_message{type = ack, method = Method1, options = Opts} = test_recv_coap_response(UdpSock), +% ?assertEqual({ok,created}, Method1), +% ?LOGT("Options got: ~p", [Opts]), +% Location = proplists:get_value(location_path, Opts), +% test_recv_mqtt_response(RespTopic), + +% %% server not in PSM mode +% send_read_command_1(0, UdpSock), +% verify_read_response_1(0, UdpSock), + +% %% server inters into PSM mode +% timer:sleep(2), + +% %% verify server caches downlink commands +% send_read_command_1(1, UdpSock), +% send_read_command_1(2, UdpSock), +% send_read_command_1(3, UdpSock), + +% ?assertEqual(timeout_test_recv_coap_request, test_recv_coap_request(UdpSock)), + +% device_update_1(UdpSock, Location), + +% verify_read_response_1(1, UdpSock), +% verify_read_response_1(2, UdpSock), +% verify_read_response_1(3, UdpSock). + +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% %%% Internal Functions +% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% send_read_command_1(CmdId, _UdpSock) -> +% Epn = "urn:oma:lwm2m:oma:3", +% CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, +% Command = #{ +% <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"path">> => <<"/3/0/0">> +% } +% }, +% CommandJson = emqx_json:encode(Command), +% test_mqtt_broker:publish(CommandTopic, CommandJson, 0), +% timer:sleep(50). + +% verify_read_response_1(CmdId, UdpSock) -> +% Epn = "urn:oma:lwm2m:oma:3", +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), + +% %% device receives a command +% Request = test_recv_coap_request(UdpSock), +% ?LOGT("LwM2M client got ~p", [Request]), + +% %% device replies the commond +% test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request, true), + +% ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, +% <<"msgType">> => <<"read">>, +% <<"data">> => #{ +% <<"code">> => <<"2.05">>, +% <<"codeMsg">> => <<"content">>, +% <<"content">> => [#{ +% path => <<"/3/0/0">>, +% value => <<"EMQ">> +% }] +% } +% }), +% ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). + +% device_update_1(UdpSock, Location) -> +% Epn = "urn:oma:lwm2m:oma:3", +% RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), +% ?LOGT("send UPDATE command", []), +% MsgId2 = 27, +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b~s?lt=789", [?PORT, join_path(Location, <<>>)]), +% #coap_content{payload = <<>>}, +% [], +% MsgId2), +% #coap_message{type = ack, id = MsgId2, method = Method2} = test_recv_coap_response(UdpSock), +% {ok,changed} = Method2, +% test_recv_mqtt_response(RespTopic). + +% test_recv_mqtt_response(RespTopic) -> +% receive +% {publish, #{topic := RespTopic, payload := RM}} -> +% ?LOGT("test_recv_mqtt_response Response=~p", [RM]), +% RM +% after 1000 -> timeout_test_recv_mqtt_response +% end. + +% test_send_coap_request(UdpSock, Method, Uri, Content, Options, MsgId) -> +% is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), +% is_list(Options) orelse error("Options must be a list"), +% case resolve_uri(Uri) of +% {coap, {IpAddr, Port}, Path, Query} -> +% Request0 = lwm2m_coap_message:request(con, Method, Content, [{uri_path, Path}, {uri_query, Query} | Options]), +% Request = Request0#coap_message{id = MsgId}, +% ?LOGT("send_coap_request Request=~p", [Request]), +% RequestBinary = lwm2m_coap_message_parser:encode(Request), +% ?LOGT("test udp socket send to ~p:~p, data=~p", [IpAddr, Port, RequestBinary]), +% ok = gen_udp:send(UdpSock, IpAddr, Port, RequestBinary); +% {SchemeDiff, ChIdDiff, _, _} -> +% error(lists:flatten(io_lib:format("scheme ~s or ChId ~s does not match with socket", [SchemeDiff, ChIdDiff]))) +% end. + +% test_recv_coap_response(UdpSock) -> +% {ok, {Address, Port, Packet}} = gen_udp:recv(UdpSock, 0, 2000), +% Response = lwm2m_coap_message_parser:decode(Packet), +% ?LOGT("test udp receive from ~p:~p, data1=~p, Response=~p", [Address, Port, Packet, Response]), +% #coap_message{type = ack, method = Method, id=Id, token = Token, options = Options, payload = Payload} = Response, +% ?LOGT("receive coap response Method=~p, Id=~p, Token=~p, Options=~p, Payload=~p", [Method, Id, Token, Options, Payload]), +% Response. + +% test_recv_coap_request(UdpSock) -> +% case gen_udp:recv(UdpSock, 0, 2000) of +% {ok, {_Address, _Port, Packet}} -> +% Request = lwm2m_coap_message_parser:decode(Packet), +% #coap_message{type = con, method = Method, id=Id, token = Token, payload = Payload, options = Options} = Request, +% ?LOGT("receive coap request Method=~p, Id=~p, Token=~p, Options=~p, Payload=~p", [Method, Id, Token, Options, Payload]), +% Request; +% {error, Reason} -> +% ?LOGT("test_recv_coap_request failed, Reason=~p", [Reason]), +% timeout_test_recv_coap_request +% end. + +% test_send_coap_response(UdpSock, Host, Port, Code, Content, Request, Ack) -> +% is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), +% is_list(Host) orelse error("Host is not a string"), + +% {ok, IpAddr} = inet:getaddr(Host, inet), +% Response = lwm2m_coap_message:response(Code, Content, Request), +% Response2 = case Ack of +% true -> Response#coap_message{type = ack}; +% false -> Response +% end, +% ?LOGT("test_send_coap_response Response=~p", [Response2]), +% ok = gen_udp:send(UdpSock, IpAddr, Port, lwm2m_coap_message_parser:encode(Response2)). + +% test_send_empty_ack(UdpSock, Host, Port, Request) -> +% is_list(Host) orelse error("Host is not a string"), +% {ok, IpAddr} = inet:getaddr(Host, inet), +% EmptyACK = lwm2m_coap_message:ack(Request), +% ?LOGT("test_send_empty_ack EmptyACK=~p", [EmptyACK]), +% ok = gen_udp:send(UdpSock, IpAddr, Port, lwm2m_coap_message_parser:encode(EmptyACK)). + +% test_send_coap_observe_ack(UdpSock, Host, Port, Code, Content, Request) -> +% is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), +% is_list(Host) orelse error("Host is not a string"), + +% {ok, IpAddr} = inet:getaddr(Host, inet), +% Response = lwm2m_coap_message:response(Code, Content, Request), +% Response1 = lwm2m_coap_message:set(observe, 0, Response), +% Response2 = Response1#coap_message{type = ack}, + +% ?LOGT("test_send_coap_observe_ack Response=~p", [Response2]), +% ResponseBinary = lwm2m_coap_message_parser:encode(Response2), +% ok = gen_udp:send(UdpSock, IpAddr, Port, ResponseBinary). + +% test_send_coap_notif(UdpSock, Host, Port, Content, ObSeq, Request) -> +% is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), +% is_list(Host) orelse error("Host is not a string"), + +% {ok, IpAddr} = inet:getaddr(Host, inet), +% Notif = lwm2m_coap_message:response({ok, content}, Content, Request), +% NewNotif = lwm2m_coap_message:set(observe, ObSeq, Notif), +% ?LOGT("test_send_coap_notif Response=~p", [NewNotif]), +% NotifBinary = lwm2m_coap_message_parser:encode(NewNotif), +% ?LOGT("test udp socket send to ~p:~p, data=~p", [IpAddr, Port, NotifBinary]), +% ok = gen_udp:send(UdpSock, IpAddr, Port, NotifBinary). + +% std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic) -> +% test_send_coap_request( UdpSock, +% post, +% sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), +% #coap_content{content_format = <<"text/plain">>, payload = ObjectList}, +% [], +% MsgId1), +% #coap_message{method = {ok,created}} = test_recv_coap_response(UdpSock), +% test_recv_mqtt_response(RespTopic), +% timer:sleep(100). + +% resolve_uri(Uri) -> +% {ok, #{scheme := Scheme, +% host := Host, +% port := PortNo, +% path := Path} = URIMap} = emqx_http_lib:uri_parse(Uri), +% Query = maps:get(query, URIMap, ""), +% {ok, PeerIP} = inet:getaddr(Host, inet), +% {Scheme, {PeerIP, PortNo}, split_path(Path), split_query(Query)}. + +% split_path([]) -> []; +% split_path([$/]) -> []; +% split_path([$/ | Path]) -> split_segments(Path, $/, []). + +% split_query([]) -> []; +% split_query(Path) -> split_segments(Path, $&, []). + +% split_segments(Path, Char, Acc) -> +% case string:rchr(Path, Char) of +% 0 -> +% [make_segment(Path) | Acc]; +% N when N > 0 -> +% split_segments(string:substr(Path, 1, N-1), Char, +% [make_segment(string:substr(Path, N+1)) | Acc]) +% end. + +% make_segment(Seg) -> +% list_to_binary(emqx_http_lib:uri_decode(Seg)). + + +% get_coap_path(Options) -> +% get_path(Options, <<>>). + +% get_coap_query(Options) -> +% proplists:get_value(uri_query, Options, []). + +% get_coap_observe(Options) -> +% get_observe(Options). + + +% get_path([], Acc) -> +% %?LOGT("get_path Acc=~p", [Acc]), +% Acc; +% get_path([{uri_path, Path1}|T], Acc) -> +% %?LOGT("Path=~p, Acc=~p", [Path1, Acc]), +% get_path(T, join_path(Path1, Acc)); +% get_path([{_, _}|T], Acc) -> +% get_path(T, Acc). + +% get_observe([]) -> +% undefined; +% get_observe([{observe, V}|_T]) -> +% V; +% get_observe([{_, _}|T]) -> +% get_observe(T). + +% join_path([], Acc) -> Acc; +% join_path([<<"/">>|T], Acc) -> +% join_path(T, Acc); +% join_path([H|T], Acc) -> +% join_path(T, <>). + +% sprintf(Format, Args) -> +% lists:flatten(io_lib:format(Format, Args)). diff --git a/apps/emqx_gateway/src/lwm2m/test/emqx_tlv_SUITE.erl b/apps/emqx_gateway/src/lwm2m/test/emqx_tlv_SUITE.erl new file mode 100644 index 000000000..d4d3c70cf --- /dev/null +++ b/apps/emqx_gateway/src/lwm2m/test/emqx_tlv_SUITE.erl @@ -0,0 +1,240 @@ +%%-------------------------------------------------------------------- +%% 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). + diff --git a/apps/emqx_gateway/src/lwm2m/test/test_mqtt_broker.erl b/apps/emqx_gateway/src/lwm2m/test/test_mqtt_broker.erl new file mode 100644 index 000000000..4a6c77444 --- /dev/null +++ b/apps/emqx_gateway/src/lwm2m/test/test_mqtt_broker.erl @@ -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). diff --git a/apps/emqx_lwm2m/integration_test/Makefile b/apps/emqx_lwm2m/integration_test/Makefile deleted file mode 100644 index dcad28900..000000000 --- a/apps/emqx_lwm2m/integration_test/Makefile +++ /dev/null @@ -1,128 +0,0 @@ - -.PHONY: clean, clean_result, start_broker stop_broker case1 case2 case3 - -EMQX_DIR = emqx-enterprise-rel -EMQ = $(EMQX_DIR)/relx.config -WAKAAMA = build_wakaama/lightclient -PAHO_PYTHON = paho/mqtt/client.py - -all: clean_result $(EMQ) $(WAKAAMA) $(PAHO_PYTHON) start_broker clean_result case1 case2 case3 stop_broker - @echo " " - @echo " test complete" - @echo " " - -clean_result: - -rm -f case*.txt - -start_broker: - -rm -f $(EMQX_DIR)/_rel/emqx/log/* - -$(EMQX_DIR)/_rel/emqx/bin/emqx stop - sleep 3 - $(EMQX_DIR)/_rel/emqx/bin/emqx start - sleep 1 - $(EMQX_DIR)/_rel/emqx/bin/emqx_ctl plugins load emqx_lwm2m - - -stop_broker: - -$(EMQX_DIR)/_rel/emqx/bin/emqx stop - - -case1: - -build_wakaama/lightclient -4 -n jXtestlwm2m & - python case1.py - -ps aux|grep lightclient|awk '{print $$2}'|xargs kill -2 - -case2: - -build_wakaama/lightclient -4 -n jXtestlwm2m & - python case2.py - -ps aux|grep lightclient|awk '{print $$2}'|xargs kill -2 - -case3: - -build_wakaama/lightclient -4 -n jXtestlwm2m & - python case3.py - -ps aux|grep lightclient|awk '{print $$2}'|xargs kill -2 - - -$(EMQ): - git clone https://github.com/emqx/emqx-enterprise-rel - git clone https://github.com/emqx/emqx-lwm2m.git - @echo "update emqx-lwm2m with this development code" - mv emqx-lwm2m emqx_lwm2m - -rm -rf emqx_lwm2m/etc - -rm -rf emqx_lwm2m/include - -rm -rf emqx_lwm2m/priv - -rm -rf emqx_lwm2m/src - -rm -rf emqx_lwm2m/Makefile - -rm -rf emqx_lwm2m/erlang.mk - cp -rf ../etc emqx_lwm2m/ - cp -rf ../include emqx_lwm2m/ - cp -rf ../priv emqx_lwm2m/ - cp -rf ../src emqx_lwm2m/ - cp -rf ../Makefile emqx_lwm2m/Makefile - cp -rf ../erlang.mk emqx_lwm2m/erlang.mk - -mkdir $(EMQX_DIR)/deps - mv emqx_lwm2m $(EMQX_DIR)/deps/ - @echo "start building ..." - python insert_lwm2m_plugin.py - make -C emqx-rel -f Makefile - -cp -rf ../lwm2m_xml $(EMQX_DIR)/_rel/emqx/etc/ - - -w: - cd build_wakaama && cmake -DCMAKE_BUILD_TYPE=Debug ../wakaama/examples/lightclient && make - -$(WAKAAMA): - git clone https://github.com/eclipse/wakaama - -mkdir build_wakaama - # replace lightclient's source code, change port from 5683 to 5683, since 5683 is the default port of emqx-lwm2m - cp -f object_security.c wakaama/examples/lightclient/object_security.c - cd build_wakaama && cmake -DCMAKE_BUILD_TYPE=Debug ../wakaama/examples/lightclient && make - - -mqtt: $(PAHO_PYTHON) - # short for paho python client - -$(PAHO_PYTHON): - git clone https://github.com/eclipse/paho.mqtt.python.git - mv paho.mqtt.python/src/paho ./ - rm -rf paho.mqtt.python - - -r: rebuild_emq - # r short for rebuild_emq - @echo " rebuild complete " - - -rebuild_emq: - -$(EMQX_DIR)/_rel/emqx/bin/emqx stop - -mkdir $(EMQX_DIR)/deps - -rm -rf $(EMQX_DIR)/deps/emqx_lwm2m/etc - -rm -rf $(EMQX_DIR)/deps/emqx_lwm2m/include - -rm -rf $(EMQX_DIR)/deps/emqx_lwm2m/priv - -rm -rf $(EMQX_DIR)/deps/emqx_lwm2m/src - -rm -rf $(EMQX_DIR)/deps/emqx_lwm2m/Makefile - -rm -rf $(EMQX_DIR)/deps/emqx_lwm2m/erlang.mk - cp -rf ../etc $(EMQX_DIR)/deps/emqx_lwm2m/ - cp -rf ../include $(EMQX_DIR)/deps/emqx_lwm2m/ - cp -rf ../priv $(EMQX_DIR)/deps/emqx_lwm2m/ - cp -rf ../src $(EMQX_DIR)/deps/emqx_lwm2m/ - cp -rf ../Makefile $(EMQX_DIR)/deps/emqx_lwm2m/Makefile - cp -rf ../erlang.mk $(EMQX_DIR)/deps/emqx_lwm2m/erlang.mk - make -C $(EMQX_DIR) -f Makefile - - -clean: clean_result - -rm -f client/*.exe - -rm -f client/*.o - -rm -rf emqx-rel - -rm -rf build_wakaama - -rm -rf wakaama - - - -lazy: clean_result start_broker case1 case2 case3 stop_broker - # custom your command here - @echo "you are so lazy" - - - diff --git a/apps/emqx_lwm2m/integration_test/case1.py b/apps/emqx_lwm2m/integration_test/case1.py deleted file mode 100644 index b3f3a3df6..000000000 --- a/apps/emqx_lwm2m/integration_test/case1.py +++ /dev/null @@ -1,65 +0,0 @@ - - - -import sys, time -import paho.mqtt.client as mqtt - - -quit_now = False -DeviceId = "jXtestlwm2m" -test_step = 0 - -def on_connect(mqttc, userdata, flags, rc): - global DeviceId - json = "{\"CmdID\":5,\"Command\":\"Discover\",\"BaseName\":\"/3/0\"}" - mqttc.publish("lwm2m/"+DeviceId+"/command", json) - -def on_message(mqttc, userdata, msg): - global quit_now, conclusion, test_step - if msg: - print(["incoming message topic ", msg.topic, " payload ", msg.payload]) - - if msg.topic == 'lwm2m/jXtestlwm2m/response': - if test_step == 0: - if msg.payload == '{"CmdID":5,"Command":"Discover","Result":",,,,"}': - json = "{\"CmdID\":6,\"Command\":\"Read\",\"BaseName\":\"/3/0\"}" - mqttc.publish("lwm2m/"+DeviceId+"/command", json) - test_step = 1 - elif test_step == 1: - if msg.payload == '{"CmdID":6,"Command":"Read","Result":{"bn":"/3/0","e":[{"n":"0","sv":"Open Mobile Alliance"},{"n":"1","sv":"Lightweight M2M Client"},{"n":"16","sv":"U"}]}}': - test_step = 2 - quit_now = True - - -def on_publish(mqttc, userdata, mid): - pass - - - -def main(): - global DeviceId, test_step - timeout = 7 - mqttc = mqtt.Client("test_coap_lwm2m_c02334") - mqttc.on_message = on_message - mqttc.on_publish = on_publish - mqttc.on_connect = on_connect - - mqttc.connect("127.0.0.1", 1883, 120) - mqttc.subscribe("lwm2m/"+DeviceId+"/response", qos=1) - mqttc.loop_start() - while quit_now == False and timeout > 0: - time.sleep(1) - timeout = timeout - 1 - mqttc.disconnect() - mqttc.loop_stop() - if test_step == 2: - print("\n\n CASE1 PASS\n\n") - else: - print("\n\n CASE1 FAIL\n\n") - - -if __name__ == "__main__": - main() - - - diff --git a/apps/emqx_lwm2m/integration_test/case2.py b/apps/emqx_lwm2m/integration_test/case2.py deleted file mode 100644 index d4a5013a0..000000000 --- a/apps/emqx_lwm2m/integration_test/case2.py +++ /dev/null @@ -1,60 +0,0 @@ - - - -import sys, time -import paho.mqtt.client as mqtt - - -quit_now = False -DeviceId = "jXtestlwm2m" -test_step = 0 - -def on_connect(mqttc, userdata, flags, rc): - global DeviceId - json = "{\"CmdID\":5,\"Command\":\"Execute\",\"BaseName\":\"/3/0/4\"}" - mqttc.publish("lwm2m/"+DeviceId+"/command", json) - -def on_message(mqttc, userdata, msg): - global quit_now, conclusion, test_step - if msg: - print(["incoming message topic ", msg.topic, " payload ", msg.payload]) - - if msg.topic == 'lwm2m/jXtestlwm2m/response': - if test_step == 0: - if msg.payload == '{"CmdID":5,"Command":"Execute","Result":"Changed"}': - test_step = 1 - quit_now = True - - -def on_publish(mqttc, userdata, mid): - pass - - - -def main(): - global DeviceId, test_step - timeout = 7 - mqttc = mqtt.Client("test_coap_lwm2m_c02334") - mqttc.on_message = on_message - mqttc.on_publish = on_publish - mqttc.on_connect = on_connect - - mqttc.connect("127.0.0.1", 1883, 120) - mqttc.subscribe("lwm2m/"+DeviceId+"/response", qos=1) - mqttc.loop_start() - while quit_now == False and timeout > 0: - time.sleep(1) - timeout = timeout - 1 - mqttc.disconnect() - mqttc.loop_stop() - if test_step == 1: - print("\n\n CASE2 PASS\n\n") - else: - print("\n\n CASE2 FAIL\n\n") - - -if __name__ == "__main__": - main() - - - diff --git a/apps/emqx_lwm2m/integration_test/case3.py b/apps/emqx_lwm2m/integration_test/case3.py deleted file mode 100644 index 2ce2c99f8..000000000 --- a/apps/emqx_lwm2m/integration_test/case3.py +++ /dev/null @@ -1,60 +0,0 @@ - - - -import sys, time -import paho.mqtt.client as mqtt - - -quit_now = False -DeviceId = "jXtestlwm2m" -test_step = 0 - -def on_connect(mqttc, userdata, flags, rc): - global DeviceId - json = '{"CmdID":5,"Command":"Write","Value":{"bn":"/1/0/1","e":[{"v":123}]}}' - mqttc.publish("lwm2m/"+DeviceId+"/command", json) - -def on_message(mqttc, userdata, msg): - global quit_now, conclusion, test_step - if msg: - print(["incoming message topic ", msg.topic, " payload ", msg.payload]) - - if msg.topic == 'lwm2m/jXtestlwm2m/response': - if test_step == 0: - if msg.payload == '{"CmdID":5,"Command":"Write","Result":"Changed"}': - test_step = 1 - quit_now = True - - -def on_publish(mqttc, userdata, mid): - pass - - - -def main(): - global DeviceId, test_step - timeout = 7 - mqttc = mqtt.Client("test_coap_lwm2m_c02334") - mqttc.on_message = on_message - mqttc.on_publish = on_publish - mqttc.on_connect = on_connect - - mqttc.connect("127.0.0.1", 1883, 120) - mqttc.subscribe("lwm2m/"+DeviceId+"/response", qos=1) - mqttc.loop_start() - while quit_now == False and timeout > 0: - time.sleep(1) - timeout = timeout - 1 - mqttc.disconnect() - mqttc.loop_stop() - if test_step == 1: - print("\n\n CASE3 PASS\n\n") - else: - print("\n\n CASE3 FAIL\n\n") - - -if __name__ == "__main__": - main() - - - diff --git a/apps/emqx_lwm2m/integration_test/insert_lwm2m_plugin.py b/apps/emqx_lwm2m/integration_test/insert_lwm2m_plugin.py deleted file mode 100644 index 7769a3af7..000000000 --- a/apps/emqx_lwm2m/integration_test/insert_lwm2m_plugin.py +++ /dev/null @@ -1,52 +0,0 @@ - - -def change_makefile(): - f = open("emqx-rel/Makefile", "rb") - data = f.read() - f.close() - - if data.find("emqx_lwm2m") < 0: - data = data.replace("emqx_auth_pgsql emqx_auth_mongo", "emqx_auth_pgsql emqx_auth_mongo emqx_lwm2m\n\ndep_emqx_lwm2m = git https://github.com/emqx/emqx-lwm2m\n\n") - f = open("emqx-rel/Makefile", "wb") - f.write(data) - f.close() - - - f = open("emqx-rel/relx.config", "rb") - data = f.read() - f.close() - - if data.find("emq_lwm2m") < 0: - f = open("emqx-rel/relx.config", "wb") - data = data.replace("{emqx_auth_mongo, load}", "{emqx_auth_mongo, load},\n{emqx_lwm2m, load}") - data = data.replace('{template, "rel/conf/emqx.conf", "etc/emqx.conf"},', \ - '{template, "rel/conf/emqx.conf", "etc/emqx.conf"},'+ \ - '\n {template, "rel/conf/plugins/emqx_lwm2m.conf", "etc/plugins/emqx_lwm2m.conf"},'+ \ - '\n {copy, "deps/emqx_lwm2m/lwm2m_xml", "etc/"},') - f.write(data) - f.close() - - - -def change_lwm2m_config(): - f = open("emqx-rel/deps/emqx_lwm2m/etc/emqx_lwm2m.conf", "rb") - data = f.read() - f.close() - - if data.find("5683") > 0: - data = data.replace("5683", "5683") - f = open("emqx-rel/deps/emqx_lwm2m/etc/emqx_lwm2m.conf", "wb") - f.write(data) - f.close() - - - -def main(): - change_makefile() - change_lwm2m_config() - - -if __name__ == "__main__": - main() - - \ No newline at end of file diff --git a/apps/emqx_lwm2m/integration_test/object_security.c b/apps/emqx_lwm2m/integration_test/object_security.c deleted file mode 100644 index ee12ea7b7..000000000 --- a/apps/emqx_lwm2m/integration_test/object_security.c +++ /dev/null @@ -1,253 +0,0 @@ -/******************************************************************************* - * - * Copyright (c) 2013, 2014, 2015 Intel Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * and Eclipse Distribution License v1.0 which accompany this distribution. - * - * The Eclipse Public License is available at - * http://www.eclipse.org/legal/epl-v10.html - * The Eclipse Distribution License is available at - * http://www.eclipse.org/org/documents/edl-v10.php. - * - * Contributors: - * David Navarro, Intel Corporation - initial API and implementation - * Bosch Software Innovations GmbH - Please refer to git log - * Pascal Rieux - Please refer to git log - * - *******************************************************************************/ - -/* - * Resources: - * - * Name | ID | Operations | Instances | Mandatory | Type | Range | Units | - * Server URI | 0 | | Single | Yes | String | | | - * Bootstrap Server | 1 | | Single | Yes | Boolean | | | - * Security Mode | 2 | | Single | Yes | Integer | 0-3 | | - * Public Key or ID | 3 | | Single | Yes | Opaque | | | - * Server Public Key or ID | 4 | | Single | Yes | Opaque | | | - * Secret Key | 5 | | Single | Yes | Opaque | | | - * SMS Security Mode | 6 | | Single | Yes | Integer | 0-255 | | - * SMS Binding Key Param. | 7 | | Single | Yes | Opaque | 6 B | | - * SMS Binding Secret Keys | 8 | | Single | Yes | Opaque | 32-48 B | | - * Server SMS Number | 9 | | Single | Yes | Integer | | | - * Short Server ID | 10 | | Single | No | Integer | 1-65535 | | - * Client Hold Off Time | 11 | | Single | Yes | Integer | | s | - * - */ - -/* - * Here we implement a very basic LWM2M Security Object which only knows NoSec security mode. - */ - -#include "liblwm2m.h" - -#include -#include -#include - - -typedef struct _security_instance_ -{ - struct _security_instance_ * next; // matches lwm2m_list_t::next - uint16_t instanceId; // matches lwm2m_list_t::id - char * uri; - bool isBootstrap; - uint16_t shortID; - uint32_t clientHoldOffTime; -} security_instance_t; - -static uint8_t prv_get_value(lwm2m_data_t * dataP, - security_instance_t * targetP) -{ - - switch (dataP->id) - { - case LWM2M_SECURITY_URI_ID: - lwm2m_data_encode_string(targetP->uri, dataP); - return COAP_205_CONTENT; - - case LWM2M_SECURITY_BOOTSTRAP_ID: - lwm2m_data_encode_bool(targetP->isBootstrap, dataP); - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SECURITY_ID: - lwm2m_data_encode_int(LWM2M_SECURITY_MODE_NONE, dataP); - return COAP_205_CONTENT; - - case LWM2M_SECURITY_PUBLIC_KEY_ID: - // Here we return an opaque of 1 byte containing 0 - { - uint8_t value = 0; - - lwm2m_data_encode_opaque(&value, 1, dataP); - } - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SERVER_PUBLIC_KEY_ID: - // Here we return an opaque of 1 byte containing 0 - { - uint8_t value = 0; - - lwm2m_data_encode_opaque(&value, 1, dataP); - } - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SECRET_KEY_ID: - // Here we return an opaque of 1 byte containing 0 - { - uint8_t value = 0; - - lwm2m_data_encode_opaque(&value, 1, dataP); - } - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SMS_SECURITY_ID: - lwm2m_data_encode_int(LWM2M_SECURITY_MODE_NONE, dataP); - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SMS_KEY_PARAM_ID: - // Here we return an opaque of 6 bytes containing a buggy value - { - char * value = "12345"; - lwm2m_data_encode_opaque((uint8_t *)value, 6, dataP); - } - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SMS_SECRET_KEY_ID: - // Here we return an opaque of 32 bytes containing a buggy value - { - char * value = "1234567890abcdefghijklmnopqrstu"; - lwm2m_data_encode_opaque((uint8_t *)value, 32, dataP); - } - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SMS_SERVER_NUMBER_ID: - lwm2m_data_encode_int(0, dataP); - return COAP_205_CONTENT; - - case LWM2M_SECURITY_SHORT_SERVER_ID: - lwm2m_data_encode_int(targetP->shortID, dataP); - return COAP_205_CONTENT; - - case LWM2M_SECURITY_HOLD_OFF_ID: - lwm2m_data_encode_int(targetP->clientHoldOffTime, dataP); - return COAP_205_CONTENT; - - default: - return COAP_404_NOT_FOUND; - } -} - -static uint8_t prv_security_read(uint16_t instanceId, - int * numDataP, - lwm2m_data_t ** dataArrayP, - lwm2m_object_t * objectP) -{ - security_instance_t * targetP; - uint8_t result; - int i; - - targetP = (security_instance_t *)lwm2m_list_find(objectP->instanceList, instanceId); - if (NULL == targetP) return COAP_404_NOT_FOUND; - - // is the server asking for the full instance ? - if (*numDataP == 0) - { - uint16_t resList[] = {LWM2M_SECURITY_URI_ID, - LWM2M_SECURITY_BOOTSTRAP_ID, - LWM2M_SECURITY_SECURITY_ID, - LWM2M_SECURITY_PUBLIC_KEY_ID, - LWM2M_SECURITY_SERVER_PUBLIC_KEY_ID, - LWM2M_SECURITY_SECRET_KEY_ID, - LWM2M_SECURITY_SMS_SECURITY_ID, - LWM2M_SECURITY_SMS_KEY_PARAM_ID, - LWM2M_SECURITY_SMS_SECRET_KEY_ID, - LWM2M_SECURITY_SMS_SERVER_NUMBER_ID, - LWM2M_SECURITY_SHORT_SERVER_ID, - LWM2M_SECURITY_HOLD_OFF_ID}; - int nbRes = sizeof(resList)/sizeof(uint16_t); - - *dataArrayP = lwm2m_data_new(nbRes); - if (*dataArrayP == NULL) return COAP_500_INTERNAL_SERVER_ERROR; - *numDataP = nbRes; - for (i = 0 ; i < nbRes ; i++) - { - (*dataArrayP)[i].id = resList[i]; - } - } - - i = 0; - do - { - result = prv_get_value((*dataArrayP) + i, targetP); - i++; - } while (i < *numDataP && result == COAP_205_CONTENT); - - return result; -} - -lwm2m_object_t * get_security_object() -{ - lwm2m_object_t * securityObj; - - securityObj = (lwm2m_object_t *)lwm2m_malloc(sizeof(lwm2m_object_t)); - - if (NULL != securityObj) - { - security_instance_t * targetP; - - memset(securityObj, 0, sizeof(lwm2m_object_t)); - - securityObj->objID = 0; - - // Manually create an hardcoded instance - targetP = (security_instance_t *)lwm2m_malloc(sizeof(security_instance_t)); - if (NULL == targetP) - { - lwm2m_free(securityObj); - return NULL; - } - - memset(targetP, 0, sizeof(security_instance_t)); - targetP->instanceId = 0; - targetP->uri = strdup("coap://localhost:5683"); - targetP->isBootstrap = false; - targetP->shortID = 123; - targetP->clientHoldOffTime = 10; - - securityObj->instanceList = LWM2M_LIST_ADD(securityObj->instanceList, targetP); - - securityObj->readFunc = prv_security_read; - } - - return securityObj; -} - -void free_security_object(lwm2m_object_t * objectP) -{ - while (objectP->instanceList != NULL) - { - security_instance_t * securityInstance = (security_instance_t *)objectP->instanceList; - objectP->instanceList = objectP->instanceList->next; - if (NULL != securityInstance->uri) - { - lwm2m_free(securityInstance->uri); - } - lwm2m_free(securityInstance); - } - lwm2m_free(objectP); -} - -char * get_server_uri(lwm2m_object_t * objectP, - uint16_t secObjInstID) -{ - security_instance_t * targetP = (security_instance_t *)LWM2M_LIST_FIND(objectP->instanceList, secObjInstID); - - if (NULL != targetP) - { - return lwm2m_strdup(targetP->uri); - } - - return NULL; -} diff --git a/apps/emqx_lwm2m/rebar.config b/apps/emqx_lwm2m/rebar.config deleted file mode 100644 index 7700071ed..000000000 --- a/apps/emqx_lwm2m/rebar.config +++ /dev/null @@ -1,29 +0,0 @@ -{deps, - %% lwm2m-coap v.1.* is for emqx v4.3.* - %% lwm2m-coap v.2.* is for emqx v5.* - [{lwm2m_coap, {git, "https://github.com/emqx/lwm2m-coap", {tag, "v2.0.0"}}} - ]}. - -{profiles, - [{test, - [{deps, [{er_coap_client, {git, "https://github.com/emqx/er_coap_client", {tag, "v1.0"}}}, - {emqtt, {git, "https://github.com/emqx/emqtt", {tag, "1.2.0"}}} - ]} - ]} - ]}. - -{edoc_opts, [{preprocess, true}]}. -{erl_opts, [warn_unused_vars, - warn_shadow_vars, - warn_unused_import, - warn_obsolete_guard, - debug_info, - {parse_transform}]}. - -{xref_checks, [undefined_function_calls, undefined_functions, - locals_not_used, deprecated_function_calls, - warnings_as_errors, deprecated_functions]}. -{cover_enabled, true}. -{cover_opts, [verbose]}. -{cover_export_enabled, true}. -{extra_src_dirs, [{"lwm2m_xml", [{recursive,true}]}]}. diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m.appup.src b/apps/emqx_lwm2m/src/emqx_lwm2m.appup.src deleted file mode 100644 index b6d49302f..000000000 --- a/apps/emqx_lwm2m/src/emqx_lwm2m.appup.src +++ /dev/null @@ -1,13 +0,0 @@ -%% -*-: erlang -*- -{VSN, - [ - {<<"4.3.[0-1]">>, [ - {restart_application, emqx_lwm2m} - ]} - ], - [ - {<<"4.3.[0-1]">>, [ - {restart_application, emqx_lwm2m} - ]} - ] -}. diff --git a/apps/emqx_lwm2m/test/emqx_lwm2m_SUITE.erl b/apps/emqx_lwm2m/test/emqx_lwm2m_SUITE.erl deleted file mode 100644 index 7f2f37466..000000000 --- a/apps/emqx_lwm2m/test/emqx_lwm2m_SUITE.erl +++ /dev/null @@ -1,1953 +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_lwm2m_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --define(PORT, 5683). - --define(LOGT(Format, Args), ct:pal("TEST_SUITE: " ++ Format, Args)). - --include("emqx_lwm2m.hrl"). --include_lib("lwm2m_coap/include/coap.hrl"). --include_lib("eunit/include/eunit.hrl"). --include_lib("common_test/include/ct.hrl"). - -%%-------------------------------------------------------------------- -%% Setups -%%-------------------------------------------------------------------- - -all() -> - [ {group, test_grp_0_register} - , {group, test_grp_1_read} - , {group, test_grp_2_write} - , {group, test_grp_3_execute} - , {group, test_grp_4_discover} - , {group, test_grp_5_write_attr} - , {group, test_grp_6_observe} - , {group, test_grp_8_object_19} - ]. - -suite() -> [{timetrap, {seconds, 90}}]. - -groups() -> - RepeatOpt = {repeat_until_all_ok, 1}, - [ - {test_grp_0_register, [RepeatOpt], [ - case01_register, - case01_register_additional_opts, - case01_register_incorrect_opts, - case01_register_report, - case02_update_deregister, - case03_register_wrong_version, - case04_register_and_lifetime_timeout, - case05_register_wrong_epn, - case06_register_wrong_lifetime, - case07_register_alternate_path_01, - case07_register_alternate_path_02, - case08_reregister - ]}, - {test_grp_1_read, [RepeatOpt], [ - case10_read, - case10_read_separate_ack, - case11_read_object_tlv, - case11_read_object_json, - case12_read_resource_opaque, - case13_read_no_xml - ]}, - {test_grp_2_write, [RepeatOpt], [ - case20_write, - case21_write_object, - case22_write_error, - case20_single_write - ]}, - {test_grp_create, [RepeatOpt], [ - case_create_basic - ]}, - {test_grp_delete, [RepeatOpt], [ - case_delete_basic - ]}, - {test_grp_3_execute, [RepeatOpt], [ - case30_execute, case31_execute_error - ]}, - {test_grp_4_discover, [RepeatOpt], [ - case40_discover - ]}, - {test_grp_5_write_attr, [RepeatOpt], [ - case50_write_attribute - ]}, - {test_grp_6_observe, [RepeatOpt], [ - case60_observe - ]}, - {test_grp_7_block_wize_transfer, [RepeatOpt], [ - case70_read_large, case70_write_large - ]}, - {test_grp_8_object_19, [RepeatOpt], [ - case80_specail_object_19_1_0_write, - case80_specail_object_19_0_0_notify - %case80_specail_object_19_0_0_response, - %case80_normal_object_19_0_0_read - ]}, - {test_grp_9_psm_queue_mode, [RepeatOpt], [ - case90_psm_mode, - case90_queue_mode - ]} - ]. - -init_per_suite(Config) -> - emqx_ct_helpers:start_apps([emqx]), - Config. - -end_per_suite(Config) -> - timer:sleep(300), - emqx_ct_helpers:stop_apps([emqx]), - Config. - -init_per_testcase(_AllTestCase, Config) -> - application:set_env(emqx_lwm2m, bind_udp, [{5683, []}]), - application:set_env(emqx_lwm2m, bind_dtls, [{5684, []}]), - application:set_env(emqx_lwm2m, xml_dir, emqx_ct_helpers:deps_path(emqx_lwm2m, "lwm2m_xml")), - application:set_env(emqx_lwm2m, lifetime_max, 86400), - application:set_env(emqx_lwm2m, lifetime_min, 1), - application:set_env(emqx_lwm2m, mountpoint, "lwm2m/%e/"), - {ok, _Started} = application:ensure_all_started(emqx_lwm2m), - {ok, ClientUdpSock} = gen_udp:open(0, [binary, {active, false}]), - - {ok, C} = emqtt:start_link([{host, "localhost"},{port, 1883},{clientid, <<"c1">>}]), - {ok, _} = emqtt:connect(C), - timer:sleep(100), - - [{sock, ClientUdpSock}, {emqx_c, C} | Config]. - -end_per_testcase(_AllTestCase, Config) -> - timer:sleep(300), - gen_udp:close(?config(sock, Config)), - emqtt:disconnect(?config(emqx_c, Config)), - ok = application:stop(emqx_lwm2m), - ok = application:stop(lwm2m_coap). - -%%-------------------------------------------------------------------- -%% Cases -%%-------------------------------------------------------------------- - -case01_register(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - - %% checkpoint 1 - response - #coap_message{type = Type, method = Method, id = RspId, options = Opts} = - test_recv_coap_response(UdpSock), - ack = Type, - {ok, created} = Method, - RspId = MsgId, - Location = proplists:get_value(location_path, Opts), - ?assertNotEqual(undefined, Location), - - %% checkpoint 2 - verify subscribed topics - timer:sleep(50), - ?LOGT("all topics: ~p", [test_mqtt_broker:get_subscrbied_topics()]), - true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), - - % ---------------------------------------- - % DE-REGISTER command - % ---------------------------------------- - ?LOGT("start to send DE-REGISTER command", []), - MsgId3 = 52, - test_send_coap_request( UdpSock, - delete, - sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), - #coap_content{payload = <<>>}, - [], - MsgId3), - #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), - {ok,deleted} = Method3, - MsgId3 = RspId3, - timer:sleep(50), - false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case01_register_additional_opts(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - - AddOpts = "ep=~s<=345&lwm2m=1&apn=psmA.eDRX0.ctnb&cust_opt=shawn&im=123&ct=1.4&mt=mdm9620&mv=1.2", - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - - %% checkpoint 1 - response - #coap_message{type = Type, method = Method, id = RspId, options = Opts} = - test_recv_coap_response(UdpSock), - Type = ack, - Method = {ok, created}, - RspId = MsgId, - Location = proplists:get_value(location_path, Opts), - ?assertNotEqual(undefined, Location), - - %% checkpoint 2 - verify subscribed topics - timer:sleep(50), - - true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), - - % ---------------------------------------- - % DE-REGISTER command - % ---------------------------------------- - ?LOGT("start to send DE-REGISTER command", []), - MsgId3 = 52, - test_send_coap_request( UdpSock, - delete, - sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), - #coap_content{payload = <<>>}, - [], - MsgId3), - #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), - {ok,deleted} = Method3, - MsgId3 = RspId3, - timer:sleep(50), - false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case01_register_incorrect_opts(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - - - AddOpts = "ep=~s<=345&lwm2m=1&incorrect_opt", - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?" ++ AddOpts, [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - - %% checkpoint 1 - response - #coap_message{type = ack, method = Method, id = MsgId} = - test_recv_coap_response(UdpSock), - ?assertEqual({error,bad_request}, Method). - -case01_register_report(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - - #coap_message{type = Type, method = Method, id = RspId, options = Opts} = - test_recv_coap_response(UdpSock), - Type = ack, - Method = {ok, created}, - RspId = MsgId, - Location = proplists:get_value(location_path, Opts), - ?assertNotEqual(undefined, Location), - - timer:sleep(50), - true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), - - ReadResult = emqx_json:encode(#{ - <<"msgType">> => <<"register">>, - <<"data">> => #{ - <<"alternatePath">> => <<"/">>, - <<"ep">> => list_to_binary(Epn), - <<"lt">> => 345, - <<"lwm2m">> => <<"1">>, - <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)), - - % ---------------------------------------- - % DE-REGISTER command - % ---------------------------------------- - ?LOGT("start to send DE-REGISTER command", []), - MsgId3 = 52, - test_send_coap_request( UdpSock, - delete, - sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), - #coap_content{payload = <<>>}, - [], - MsgId3), - #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), - {ok,deleted} = Method3, - MsgId3 = RspId3, - timer:sleep(50), - false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case02_update_deregister(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - timer:sleep(100), - #coap_message{type = ack, method = Method, options = Opts} = test_recv_coap_response(UdpSock), - ?assertEqual({ok,created}, Method), - - ?LOGT("Options got: ~p", [Opts]), - Location = proplists:get_value(location_path, Opts), - Register = emqx_json:encode(#{ - <<"msgType">> => <<"register">>, - <<"data">> => #{ - <<"alternatePath">> => <<"/">>, - <<"ep">> => list_to_binary(Epn), - <<"lt">> => 345, - <<"lwm2m">> => <<"1">>, - <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>] - } - }), - ?assertEqual(Register, test_recv_mqtt_response(ReportTopic)), - - % ---------------------------------------- - % UPDATE command - % ---------------------------------------- - ?LOGT("start to send UPDATE command", []), - MsgId2 = 27, - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b~s?lt=789", [?PORT, join_path(Location, <<>>)]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , , ">>}, - [], - MsgId2), - #coap_message{type = ack, id = RspId2, method = Method2} = test_recv_coap_response(UdpSock), - {ok,changed} = Method2, - MsgId2 = RspId2, - Update = emqx_json:encode(#{ - <<"msgType">> => <<"update">>, - <<"data">> => #{ - <<"alternatePath">> => <<"/">>, - <<"ep">> => list_to_binary(Epn), - <<"lt">> => 789, - <<"lwm2m">> => <<"1">>, - <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>, <<"/6">>] - } - }), - ?assertEqual(Update, test_recv_mqtt_response(ReportTopic)), - - % ---------------------------------------- - % DE-REGISTER command - % ---------------------------------------- - ?LOGT("start to send DE-REGISTER command", []), - MsgId3 = 52, - test_send_coap_request( UdpSock, - delete, - sprintf("coap://127.0.0.1:~b~s", [?PORT, join_path(Location, <<>>)]), - #coap_content{payload = <<>>}, - [], - MsgId3), - #coap_message{type = ack, id = RspId3, method = Method3} = test_recv_coap_response(UdpSock), - {ok,deleted} = Method3, - MsgId3 = RspId3, - - timer:sleep(50), - false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case03_register_wrong_version(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=8.3", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), - ?assertEqual({error,precondition_failed}, Method), - timer:sleep(50), - - false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case04_register_and_lifetime_timeout(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=2&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - timer:sleep(100), - #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), - ?assertEqual({ok,created}, Method), - - true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), - - % ---------------------------------------- - % lifetime timeout - % ---------------------------------------- - timer:sleep(4000), - - false = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case05_register_wrong_epn(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - MsgId = 12, - UdpSock = ?config(sock, Config), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?lt=345&lwm2m=1.0", [?PORT]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), - ?assertEqual({error,bad_request}, Method). - -case06_register_wrong_lifetime(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId), - #coap_message{type = ack, method = Method} = test_recv_coap_response(UdpSock), - ?assertEqual({error,bad_request}, Method), - timer:sleep(50), - ?assertEqual([], test_mqtt_broker:get_subscrbied_topics()). - -case07_register_alternate_path_01(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, - payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, - [], - MsgId), - timer:sleep(50), - true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case07_register_alternate_path_02(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, - payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, - [], - MsgId), - timer:sleep(50), - true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()). - -case08_reregister(Config) -> - % ---------------------------------------- - % REGISTER command - % ---------------------------------------- - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId = 12, - SubTopic = list_to_binary("lwm2m/"++Epn++"/dn/#"), - ReportTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), ReportTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, - payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, - [], - MsgId), - timer:sleep(50), - true = lists:member(SubTopic, test_mqtt_broker:get_subscrbied_topics()), - - ReadResult = emqx_json:encode( - #{ - <<"msgType">> => <<"register">>, - <<"data">> => #{ - <<"alternatePath">> => <<"/lwm2m">>, - <<"ep">> => list_to_binary(Epn), - <<"lt">> => 345, - <<"lwm2m">> => <<"1">>, - <<"objectList">> => [<<"/1/0">>, <<"/2/0">>, <<"/3/0">>] - } - } - ), - ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)), - timer:sleep(1000), - - %% the same lwm2mc client registers to server again - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, - payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, - [], - MsgId + 1), - %% verify the lwm2m client is still online - ?assertEqual(ReadResult, test_recv_mqtt_response(ReportTopic)). - -case10_read(Config) -> - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - % step 1, device register ... - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, - payload = <<";rt=\"oma.lwm2m\";ct=11543,,,">>}, - [], - MsgId1), - #coap_message{method = Method1} = test_recv_coap_response(UdpSock), - ?assertEqual({ok,created}, Method1), - test_recv_mqtt_response(RespTopic), - - % step2, send a READ command to device - CmdId = 206, - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - Command = #{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/0">> - } - }, - CommandJson = emqx_json:encode(Command), - ?LOGT("CommandJson=~p", [CommandJson]), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - ?LOGT("LwM2M client got ~p", [Request2]), - - ?assertEqual(get, Method2), - ?assertEqual(<<"/lwm2m/3/0/0">>, get_coap_path(Options2)), - ?assertEqual(<<>>, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"reqPath">> => <<"/3/0/0">>, - <<"content">> => [#{ - path => <<"/3/0/0">>, - value => <<"EMQ">> - }] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case10_read_separate_ack(Config) -> - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - % step 1, device register ... - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a READ command to device - CmdId = 206, - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - Command = #{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/0">> - } - }, - CommandJson = emqx_json:encode(Command), - ?LOGT("CommandJson=~p", [CommandJson]), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - ?LOGT("LwM2M client got ~p", [Request2]), - - ?assertEqual(get, Method2), - ?assertEqual(<<"/3/0/0">>, get_coap_path(Options2)), - ?assertEqual(<<>>, Payload2), - - test_send_empty_ack(UdpSock, "127.0.0.1", ?PORT, Request2), - ReadResultACK = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"ack">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/0">> - } - }), - ?assertEqual(ReadResultACK, test_recv_mqtt_response(RespTopic)), - timer:sleep(100), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request2, false), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"reqPath">> => <<"/3/0/0">>, - <<"content">> => [#{ - path => <<"/3/0/0">>, - value => <<"EMQ">> - }] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case11_read_object_tlv(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a READ command to device - CmdId = 207, - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - Command = #{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"path">> => <<"/3/0">> - } - }, - CommandJson = emqx_json:encode(Command), - ?LOGT("CommandJson=~p", [CommandJson]), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2} = Request2, - ?LOGT("LwM2M client got ~p", [Request2]), - - ?assertEqual(get, Method2), - timer:sleep(50), - - Tlv = <<16#08, 16#00, 16#3C, 16#C8, 16#00, 16#14, 16#4F, 16#70, 16#65, 16#6E, 16#20, 16#4D, 16#6F, 16#62, 16#69, 16#6C, 16#65, 16#20, 16#41, 16#6C, 16#6C, 16#69, 16#61, 16#6E, 16#63, 16#65, 16#C8, 16#01, 16#16, 16#4C, 16#69, 16#67, 16#68, 16#74, 16#77, 16#65, 16#69, 16#67, 16#68, 16#74, 16#20, 16#4D, 16#32, 16#4D, 16#20, 16#43, 16#6C, 16#69, 16#65, 16#6E, 16#74, 16#C8, 16#02, 16#09, 16#33, 16#34, 16#35, 16#30, 16#30, 16#30, 16#31, 16#32, 16#33>>, - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"application/vnd.oma.lwm2m+tlv">>, payload = Tlv}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"reqPath">> => <<"/3/0">>, - <<"content">> => [ - #{ - path => <<"/3/0/0">>, - value => <<"Open Mobile Alliance">> - }, - #{ - path => <<"/3/0/1">>, - value => <<"Lightweight M2M Client">> - }, - #{ - path => <<"/3/0/2">>, - value => <<"345000123">> - } - ] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case11_read_object_json(Config) -> - % step 1, device register ... - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - ObjectList = <<", , , , ">>, - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a READ command to device - CmdId = 206, - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - Command = #{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"path">> => <<"/3/0">> - } - }, - CommandJson = emqx_json:encode(Command), - ?LOGT("CommandJson=~p", [CommandJson]), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2} = Request2, - ?LOGT("LwM2M client got ~p", [Request2]), - - ?assertEqual(get, Method2), - timer:sleep(50), - - Json = <<"{\"bn\":\"/3/0\",\"e\":[{\"n\":\"0\",\"sv\":\"Open Mobile Alliance\"},{\"n\":\"1\",\"sv\":\"Lightweight M2M Client\"},{\"n\":\"2\",\"sv\":\"345000123\"}]}">>, - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"application/vnd.oma.lwm2m+json">>, payload = Json}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"reqPath">> => <<"/3/0">>, - <<"content">> => [ - #{ - path => <<"/3/0/0">>, - value => <<"Open Mobile Alliance">> - }, - #{ - path => <<"/3/0/1">>, - value => <<"Lightweight M2M Client">> - }, - #{ - path => <<"/3/0/2">>, - value => <<"345000123">> - } - ] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case12_read_resource_opaque(Config) -> - % step 1, device register ... - UdpSock = ?config(sock, Config), - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a READ command to device - CmdId = 206, - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - Command = #{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/8">> - } - }, - CommandJson = emqx_json:encode(Command), - ?LOGT("CommandJson=~p", [CommandJson]), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2} = Request2, - ?LOGT("LwM2M client got ~p", [Request2]), - - ?assertEqual(get, Method2), - timer:sleep(50), - - Opaque = <<20, 21, 22, 23>>, - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"application/octet-stream">>, payload = Opaque}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"reqPath">> => <<"/3/0/8">>, - <<"content">> => [ - #{ - path => <<"/3/0/8">>, - value => base64:encode(Opaque) - } - ] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case13_read_no_xml(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a READ command to device - CmdId = 206, - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - Command = #{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"path">> => <<"/9723/0/0">> - } - }, - CommandJson = emqx_json:encode(Command), - ?LOGT("CommandJson=~p", [CommandJson]), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2} = Request2, - ?LOGT("LwM2M client got ~p", [Request2]), - - ?assertEqual(get, Method2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"reqPath">> => <<"/9723/0/0">>, - <<"code">> => <<"4.00">>, - <<"codeMsg">> => <<"bad_request">> - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case20_single_write(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"write">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/13">>, - <<"type">> => <<"Integer">>, - <<"value">> => <<"12345">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(put, Method2), - ?assertEqual(<<"/3/0/13">>, Path2), - Tlv_Value = <<3:2, 0:1, 0:2, 2:3, 13, 12345:16>>, - ?assertEqual(Tlv_Value, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/13">>, - <<"code">> => <<"2.04">>, - <<"codeMsg">> => <<"changed">> - }, - <<"msgType">> => <<"write">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case20_write(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"write">>, - <<"data">> => #{ - <<"basePath">> => <<"/3/0/13">>, - <<"content">> => [#{ - type => <<"Float">>, - value => <<"12345.0">> - }] - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(put, Method2), - ?assertEqual(<<"/3/0/13">>, Path2), - Tlv_Value = <<200, 13, 8, 64,200,28,128,0,0,0,0>>, - ?assertEqual(Tlv_Value, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), - timer:sleep(100), - - WriteResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/13">>, - <<"code">> => <<"2.04">>, - <<"codeMsg">> => <<"changed">> - }, - <<"msgType">> => <<"write">> - }), - ?assertEqual(WriteResult, test_recv_mqtt_response(RespTopic)). - -case21_write_object(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"write">>, - <<"data">> => #{ - <<"basePath">> => <<"/3/0/">>, - <<"content">> => [#{ - path => <<"13">>, - type => <<"Integer">>, - value => <<"12345">> - },#{ - path => <<"14">>, - type => <<"String">>, - value => <<"87x">> - }] - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(post, Method2), - ?assertEqual(<<"/3/0">>, Path2), - Tlv_Value = <<3:2, 0:1, 0:2, 2:3, 13, 12345:16, - 3:2, 0:1, 0:2, 3:3, 14, "87x">>, - ?assertEqual(Tlv_Value, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), - timer:sleep(100), - - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"write">>, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/">>, - <<"code">> => <<"2.04">>, - <<"codeMsg">> => <<"changed">> - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case22_write_error(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"write">>, - <<"data">> => #{ - <<"basePath">> => <<"/3/0/1">>, - <<"content">> => [ - #{ - type => <<"Integer">>, - value => <<"12345">> - } - ] - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(put, Method2), - ?assertEqual(<<"/3/0/1">>, Path2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {error, bad_request}, #coap_content{}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/1">>, - <<"code">> => <<"4.00">>, - <<"codeMsg">> => <<"bad_request">> - }, - <<"msgType">> => <<"write">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case_create_basic(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a CREATE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"create">>, - <<"data">> => #{ - <<"path">> => <<"/5">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(post, Method2), - ?assertEqual(<<"/5">>, Path2), - ?assertEqual(<<"">>, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, created}, #coap_content{}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/5">>, - <<"code">> => <<"2.01">>, - <<"codeMsg">> => <<"created">> - }, - <<"msgType">> => <<"create">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case_delete_basic(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a CREATE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"delete">>, - <<"data">> => #{ - <<"path">> => <<"/5/0">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(delete, Method2), - ?assertEqual(<<"/5/0">>, Path2), - ?assertEqual(<<"">>, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, deleted}, #coap_content{}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/5/0">>, - <<"code">> => <<"2.02">>, - <<"codeMsg">> => <<"deleted">> - }, - <<"msgType">> => <<"delete">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case30_execute(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"execute">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/4">>, - %% "args" should not be present for "/3/0/4", only for testing the encoding here - <<"args">> => <<"2,7">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(post, Method2), - ?assertEqual(<<"/3/0/4">>, Path2), - ?assertEqual(<<"2,7">>, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/4">>, - <<"code">> => <<"2.04">>, - <<"codeMsg">> => <<"changed">> - }, - <<"msgType">> => <<"execute">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case31_execute_error(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"execute">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/4">>, - <<"args">> => <<"2,7">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(post, Method2), - ?assertEqual(<<"/3/0/4">>, Path2), - ?assertEqual(<<"2,7">>, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {error, uauthorized}, #coap_content{}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/4">>, - <<"code">> => <<"4.01">>, - <<"codeMsg">> => <<"uauthorized">> - }, - <<"msgType">> => <<"execute">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case40_discover(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"discover">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/7">> - } }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(get, Method2), - ?assertEqual(<<"/3/0/7">>, Path2), - ?assertEqual(<<>>, Payload2), - timer:sleep(50), - - PayloadDiscover = <<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2,">>, - test_send_coap_response(UdpSock, - "127.0.0.1", - ?PORT, - {ok, content}, - #coap_content{content_format = <<"application/link-format">>, payload = PayloadDiscover}, - Request2, - true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"discover">>, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/7">>, - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"content">> => - [<<";dim=8;pmin=10;pmax=60;gt=50;lt=42.2">>, <<"">>] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case50_write_attribute(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"write-attr">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/9">>, - <<"pmin">> => <<"1">>, - <<"pmax">> => <<"5">>, - <<"lt">> => <<"5">> - } }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(100), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - ?LOGT("got options: ~p", [Options2]), - Path2 = get_coap_path(Options2), - Query2 = lists:sort(get_coap_query(Options2)), - ?assertEqual(put, Method2), - ?assertEqual(<<"/3/0/9">>, Path2), - ?assertEqual(lists:sort([<<"pmax=5">>,<<"lt=5">>,<<"pmin=1">>]), Query2), - ?assertEqual(<<>>, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, - "127.0.0.1", - ?PORT, - {ok, changed}, - #coap_content{}, - Request2, - true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/9">>, - <<"code">> => <<"2.04">>, - <<"codeMsg">> => <<"changed">> - }, - <<"msgType">> => <<"write-attr">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case60_observe(Config) -> - % step 1, device register ... - Epn = "urn:oma:lwm2m:oma:3", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - ObjectList = <<", , , , ">>, - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - RespTopicAD = list_to_binary("lwm2m/"++Epn++"/up/notify"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - emqtt:subscribe(?config(emqx_c, Config), RespTopicAD, qos0), - timer:sleep(200), - - std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic), - - % step2, send a OBSERVE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"observe">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/10">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - Observe = get_coap_observe(Options2), - ?assertEqual(get, Method2), - ?assertEqual(<<"/3/0/10">>, Path2), - ?assertEqual(Observe, 0), - ?assertEqual(<<>>, Payload2), - timer:sleep(50), - - test_send_coap_observe_ack( UdpSock, - "127.0.0.1", - ?PORT, - {ok, content}, - #coap_content{content_format = <<"text/plain">>, payload = <<"2048">>}, - Request2), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"observe">>, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/10">>, - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"content">> => [#{ - path => <<"/3/0/10">>, - value => 2048 - }] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)), - - %% step3 the notifications - timer:sleep(200), - ObSeq = 3, - test_send_coap_notif( UdpSock, - "127.0.0.1", - ?PORT, - #coap_content{content_format = <<"text/plain">>, payload = <<"4096">>}, - ObSeq, - Request2), - timer:sleep(100), - #coap_message{} = test_recv_coap_response(UdpSock), - - ReadResult2 = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"notify">>, - <<"seqNum">> => ObSeq, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/10">>, - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"content">> => [#{ - path => <<"/3/0/10">>, - value => 4096 - }] - } - }), - ?assertEqual(ReadResult2, test_recv_mqtt_response(RespTopicAD)), - - %% Step3. cancel observe - CmdId3 = 308, - Command3 = #{<<"requestID">> => CmdId3, <<"cacheID">> => CmdId3, - <<"msgType">> => <<"cancel-observe">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/10">> - } - }, - CommandJson3 = emqx_json:encode(Command3), - test_mqtt_broker:publish(CommandTopic, CommandJson3, 0), - timer:sleep(50), - Request3 = test_recv_coap_request(UdpSock), - #coap_message{method = Method3, options=Options3, payload=Payload3} = Request3, - Path3 = get_coap_path(Options3), - Observe3 = get_coap_observe(Options3), - ?assertEqual(get, Method3), - ?assertEqual(<<"/3/0/10">>, Path3), - ?assertEqual(Observe3, 1), - ?assertEqual(<<>>, Payload3), - timer:sleep(50), - - test_send_coap_observe_ack( UdpSock, - "127.0.0.1", - ?PORT, - {ok, content}, - #coap_content{content_format = <<"text/plain">>, payload = <<"1150">>}, - Request3), - timer:sleep(100), - - ReadResult3 = emqx_json:encode(#{ - <<"requestID">> => CmdId3, <<"cacheID">> => CmdId3, - <<"msgType">> => <<"cancel-observe">>, - <<"data">> => #{ - <<"reqPath">> => <<"/3/0/10">>, - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"content">> => [#{ - path => <<"/3/0/10">>, - value => 1150 - }] - } - }), - ?assertEqual(ReadResult3, test_recv_mqtt_response(RespTopic)). - -case80_specail_object_19_0_0_notify(Config) -> - % step 1, device register, with extra register options - Epn = "urn:oma:lwm2m:oma:3", - RegOptionWangYi = "&apn=psmA.eDRX0.ctnb&im=13456&ct=2.0&mt=MDM9206&mv=4.0", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1"++RegOptionWangYi, [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId1), - #coap_message{method = Method1} = test_recv_coap_response(UdpSock), - ?assertEqual({ok,created}, Method1), - ReadResult = emqx_json:encode(#{ - <<"msgType">> => <<"register">>, - <<"data">> => #{ - <<"alternatePath">> => <<"/">>, - <<"ep">> => list_to_binary(Epn), - <<"lt">> => 345, - <<"lwm2m">> => <<"1">>, - <<"objectList">> => [<<"/1">>, <<"/2">>, <<"/3">>, <<"/4">>, <<"/5">>], - <<"apn">> => <<"psmA.eDRX0.ctnb">>, - <<"im">> => <<"13456">>, - <<"ct">> => <<"2.0">>, - <<"mt">> => <<"MDM9206">>, - <<"mv">> => <<"4.0">> - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)), - - % step2, send a OBSERVE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"observe">>, - <<"data">> => #{ - <<"path">> => <<"/19/0/0">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - Observe = get_coap_observe(Options2), - ?assertEqual(get, Method2), - ?assertEqual(<<"/19/0/0">>, Path2), - ?assertEqual(Observe, 0), - ?assertEqual(<<>>, Payload2), - timer:sleep(50), - - test_send_coap_observe_ack( UdpSock, - "127.0.0.1", - ?PORT, - {ok, content}, - #coap_content{content_format = <<"text/plain">>, payload = <<"2048">>}, - Request2), - timer:sleep(100). - - %% step 3, device send uplink data notifications - -case80_specail_object_19_1_0_write(Config) -> - Epn = "urn:oma:lwm2m:oma:3", - RegOptionWangYi = "&apn=psmA.eDRX0.ctnb&im=13456&ct=2.0&mt=MDM9206&mv=4.0", - MsgId1 = 15, - UdpSock = ?config(sock, Config), - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1"++RegOptionWangYi, [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId1), - #coap_message{method = Method1} = test_recv_coap_response(UdpSock), - ?assertEqual({ok,created}, Method1), - test_recv_mqtt_response(RespTopic), - - % step2, send a WRITE command to device - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - CmdId = 307, - Command = #{<<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"write">>, - <<"data">> => #{ - <<"path">> => <<"/19/1/0">>, - <<"type">> => <<"Opaque">>, - <<"value">> => base64:encode(<<12345:32>>) - } - }, - - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50), - Request2 = test_recv_coap_request(UdpSock), - #coap_message{method = Method2, options=Options2, payload=Payload2} = Request2, - Path2 = get_coap_path(Options2), - ?assertEqual(put, Method2), - ?assertEqual(<<"/19/1/0">>, Path2), - ?assertEqual(<<3:2, 0:1, 0:2, 4:3, 0, 12345:32>>, Payload2), - timer:sleep(50), - - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, changed}, #coap_content{}, Request2, true), - timer:sleep(100), - - ReadResult = emqx_json:encode(#{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"data">> => #{ - <<"reqPath">> => <<"/19/1/0">>, - <<"code">> => <<"2.04">>, - <<"codeMsg">> => <<"changed">> - }, - <<"msgType">> => <<"write">> - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -case90_psm_mode(Config) -> - server_cache_mode(Config, "ep=~s<=345&lwm2m=1&apn=psmA.eDRX0.ctnb"). - -case90_queue_mode(Config) -> - server_cache_mode(Config, "ep=~s<=345&lwm2m=1&b=UQ"). - -server_cache_mode(Config, RegOption) -> - application:set_env(?APP, qmode_time_window, 2), - - % step 1, device register, with apn indicates "PSM" mode - Epn = "urn:oma:lwm2m:oma:3", - - MsgId1 = 15, - UdpSock = ?config(sock, Config), - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - emqtt:subscribe(?config(emqx_c, Config), RespTopic, qos0), - timer:sleep(200), - - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?"++RegOption, [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = <<", , , , ">>}, - [], - MsgId1), - #coap_message{type = ack, method = Method1, options = Opts} = test_recv_coap_response(UdpSock), - ?assertEqual({ok,created}, Method1), - ?LOGT("Options got: ~p", [Opts]), - Location = proplists:get_value(location_path, Opts), - test_recv_mqtt_response(RespTopic), - - %% server not in PSM mode - send_read_command_1(0, UdpSock), - verify_read_response_1(0, UdpSock), - - %% server inters into PSM mode - timer:sleep(2), - - %% verify server caches downlink commands - send_read_command_1(1, UdpSock), - send_read_command_1(2, UdpSock), - send_read_command_1(3, UdpSock), - - ?assertEqual(timeout_test_recv_coap_request, test_recv_coap_request(UdpSock)), - - device_update_1(UdpSock, Location), - - verify_read_response_1(1, UdpSock), - verify_read_response_1(2, UdpSock), - verify_read_response_1(3, UdpSock). - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Internal Functions -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -send_read_command_1(CmdId, _UdpSock) -> - Epn = "urn:oma:lwm2m:oma:3", - CommandTopic = <<"lwm2m/", (list_to_binary(Epn))/binary, "/dn/dm">>, - Command = #{ - <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"path">> => <<"/3/0/0">> - } - }, - CommandJson = emqx_json:encode(Command), - test_mqtt_broker:publish(CommandTopic, CommandJson, 0), - timer:sleep(50). - -verify_read_response_1(CmdId, UdpSock) -> - Epn = "urn:oma:lwm2m:oma:3", - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - - %% device receives a command - Request = test_recv_coap_request(UdpSock), - ?LOGT("LwM2M client got ~p", [Request]), - - %% device replies the commond - test_send_coap_response(UdpSock, "127.0.0.1", ?PORT, {ok, content}, #coap_content{content_format = <<"text/plain">>, payload = <<"EMQ">>}, Request, true), - - ReadResult = emqx_json:encode(#{ <<"requestID">> => CmdId, <<"cacheID">> => CmdId, - <<"msgType">> => <<"read">>, - <<"data">> => #{ - <<"code">> => <<"2.05">>, - <<"codeMsg">> => <<"content">>, - <<"content">> => [#{ - path => <<"/3/0/0">>, - value => <<"EMQ">> - }] - } - }), - ?assertEqual(ReadResult, test_recv_mqtt_response(RespTopic)). - -device_update_1(UdpSock, Location) -> - Epn = "urn:oma:lwm2m:oma:3", - RespTopic = list_to_binary("lwm2m/"++Epn++"/up/resp"), - ?LOGT("send UPDATE command", []), - MsgId2 = 27, - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b~s?lt=789", [?PORT, join_path(Location, <<>>)]), - #coap_content{payload = <<>>}, - [], - MsgId2), - #coap_message{type = ack, id = MsgId2, method = Method2} = test_recv_coap_response(UdpSock), - {ok,changed} = Method2, - test_recv_mqtt_response(RespTopic). - -test_recv_mqtt_response(RespTopic) -> - receive - {publish, #{topic := RespTopic, payload := RM}} -> - ?LOGT("test_recv_mqtt_response Response=~p", [RM]), - RM - after 1000 -> timeout_test_recv_mqtt_response - end. - -test_send_coap_request(UdpSock, Method, Uri, Content, Options, MsgId) -> - is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), - is_list(Options) orelse error("Options must be a list"), - case resolve_uri(Uri) of - {coap, {IpAddr, Port}, Path, Query} -> - Request0 = lwm2m_coap_message:request(con, Method, Content, [{uri_path, Path}, {uri_query, Query} | Options]), - Request = Request0#coap_message{id = MsgId}, - ?LOGT("send_coap_request Request=~p", [Request]), - RequestBinary = lwm2m_coap_message_parser:encode(Request), - ?LOGT("test udp socket send to ~p:~p, data=~p", [IpAddr, Port, RequestBinary]), - ok = gen_udp:send(UdpSock, IpAddr, Port, RequestBinary); - {SchemeDiff, ChIdDiff, _, _} -> - error(lists:flatten(io_lib:format("scheme ~s or ChId ~s does not match with socket", [SchemeDiff, ChIdDiff]))) - end. - -test_recv_coap_response(UdpSock) -> - {ok, {Address, Port, Packet}} = gen_udp:recv(UdpSock, 0, 2000), - Response = lwm2m_coap_message_parser:decode(Packet), - ?LOGT("test udp receive from ~p:~p, data1=~p, Response=~p", [Address, Port, Packet, Response]), - #coap_message{type = ack, method = Method, id=Id, token = Token, options = Options, payload = Payload} = Response, - ?LOGT("receive coap response Method=~p, Id=~p, Token=~p, Options=~p, Payload=~p", [Method, Id, Token, Options, Payload]), - Response. - -test_recv_coap_request(UdpSock) -> - case gen_udp:recv(UdpSock, 0, 2000) of - {ok, {_Address, _Port, Packet}} -> - Request = lwm2m_coap_message_parser:decode(Packet), - #coap_message{type = con, method = Method, id=Id, token = Token, payload = Payload, options = Options} = Request, - ?LOGT("receive coap request Method=~p, Id=~p, Token=~p, Options=~p, Payload=~p", [Method, Id, Token, Options, Payload]), - Request; - {error, Reason} -> - ?LOGT("test_recv_coap_request failed, Reason=~p", [Reason]), - timeout_test_recv_coap_request - end. - -test_send_coap_response(UdpSock, Host, Port, Code, Content, Request, Ack) -> - is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), - is_list(Host) orelse error("Host is not a string"), - - {ok, IpAddr} = inet:getaddr(Host, inet), - Response = lwm2m_coap_message:response(Code, Content, Request), - Response2 = case Ack of - true -> Response#coap_message{type = ack}; - false -> Response - end, - ?LOGT("test_send_coap_response Response=~p", [Response2]), - ok = gen_udp:send(UdpSock, IpAddr, Port, lwm2m_coap_message_parser:encode(Response2)). - -test_send_empty_ack(UdpSock, Host, Port, Request) -> - is_list(Host) orelse error("Host is not a string"), - {ok, IpAddr} = inet:getaddr(Host, inet), - EmptyACK = lwm2m_coap_message:ack(Request), - ?LOGT("test_send_empty_ack EmptyACK=~p", [EmptyACK]), - ok = gen_udp:send(UdpSock, IpAddr, Port, lwm2m_coap_message_parser:encode(EmptyACK)). - -test_send_coap_observe_ack(UdpSock, Host, Port, Code, Content, Request) -> - is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), - is_list(Host) orelse error("Host is not a string"), - - {ok, IpAddr} = inet:getaddr(Host, inet), - Response = lwm2m_coap_message:response(Code, Content, Request), - Response1 = lwm2m_coap_message:set(observe, 0, Response), - Response2 = Response1#coap_message{type = ack}, - - ?LOGT("test_send_coap_observe_ack Response=~p", [Response2]), - ResponseBinary = lwm2m_coap_message_parser:encode(Response2), - ok = gen_udp:send(UdpSock, IpAddr, Port, ResponseBinary). - -test_send_coap_notif(UdpSock, Host, Port, Content, ObSeq, Request) -> - is_record(Content, coap_content) orelse error("Content must be a #coap_content!"), - is_list(Host) orelse error("Host is not a string"), - - {ok, IpAddr} = inet:getaddr(Host, inet), - Notif = lwm2m_coap_message:response({ok, content}, Content, Request), - NewNotif = lwm2m_coap_message:set(observe, ObSeq, Notif), - ?LOGT("test_send_coap_notif Response=~p", [NewNotif]), - NotifBinary = lwm2m_coap_message_parser:encode(NewNotif), - ?LOGT("test udp socket send to ~p:~p, data=~p", [IpAddr, Port, NotifBinary]), - ok = gen_udp:send(UdpSock, IpAddr, Port, NotifBinary). - -std_register(UdpSock, Epn, ObjectList, MsgId1, RespTopic) -> - test_send_coap_request( UdpSock, - post, - sprintf("coap://127.0.0.1:~b/rd?ep=~s<=345&lwm2m=1", [?PORT, Epn]), - #coap_content{content_format = <<"text/plain">>, payload = ObjectList}, - [], - MsgId1), - #coap_message{method = {ok,created}} = test_recv_coap_response(UdpSock), - test_recv_mqtt_response(RespTopic), - timer:sleep(100). - -resolve_uri(Uri) -> - {ok, #{scheme := Scheme, - host := Host, - port := PortNo, - path := Path} = URIMap} = emqx_http_lib:uri_parse(Uri), - Query = maps:get(query, URIMap, ""), - {ok, PeerIP} = inet:getaddr(Host, inet), - {Scheme, {PeerIP, PortNo}, split_path(Path), split_query(Query)}. - -split_path([]) -> []; -split_path([$/]) -> []; -split_path([$/ | Path]) -> split_segments(Path, $/, []). - -split_query([]) -> []; -split_query(Path) -> split_segments(Path, $&, []). - -split_segments(Path, Char, Acc) -> - case string:rchr(Path, Char) of - 0 -> - [make_segment(Path) | Acc]; - N when N > 0 -> - split_segments(string:substr(Path, 1, N-1), Char, - [make_segment(string:substr(Path, N+1)) | Acc]) - end. - -make_segment(Seg) -> - list_to_binary(emqx_http_lib:uri_decode(Seg)). - - -get_coap_path(Options) -> - get_path(Options, <<>>). - -get_coap_query(Options) -> - proplists:get_value(uri_query, Options, []). - -get_coap_observe(Options) -> - get_observe(Options). - - -get_path([], Acc) -> - %?LOGT("get_path Acc=~p", [Acc]), - Acc; -get_path([{uri_path, Path1}|T], Acc) -> - %?LOGT("Path=~p, Acc=~p", [Path1, Acc]), - get_path(T, join_path(Path1, Acc)); -get_path([{_, _}|T], Acc) -> - get_path(T, Acc). - -get_observe([]) -> - undefined; -get_observe([{observe, V}|_T]) -> - V; -get_observe([{_, _}|T]) -> - get_observe(T). - -join_path([], Acc) -> Acc; -join_path([<<"/">>|T], Acc) -> - join_path(T, Acc); -join_path([H|T], Acc) -> - join_path(T, <>). - -sprintf(Format, Args) -> - lists:flatten(io_lib:format(Format, Args)). diff --git a/apps/emqx_lwm2m/test/emqx_tlv_SUITE.erl b/apps/emqx_lwm2m/test/emqx_tlv_SUITE.erl deleted file mode 100644 index bfb85d832..000000000 --- a/apps/emqx_lwm2m/test/emqx_tlv_SUITE.erl +++ /dev/null @@ -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("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). - diff --git a/apps/emqx_lwm2m/test/test_mqtt_broker.erl b/apps/emqx_lwm2m/test/test_mqtt_broker.erl deleted file mode 100644 index dd85340b6..000000000 --- a/apps/emqx_lwm2m/test/test_mqtt_broker.erl +++ /dev/null @@ -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).