chore: merge coap/lwm2m/exhook/exproto to emqx_gateway dir
This commit is contained in:
parent
69f06b3631
commit
ed1cf33b9d
|
@ -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*
|
|
@ -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.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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"
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
Integration test for emq-coap
|
||||
======
|
||||
|
||||
execute following command
|
||||
```
|
||||
make
|
||||
```
|
||||
|
|
@ -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()
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
{deps,
|
||||
[
|
||||
{gen_coap, {git, "https://github.com/emqx/gen_coap", {tag, "v0.3.2"}}}
|
||||
]}.
|
|
@ -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)).
|
|
@ -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 = <<"<topic1>;ct=42">>,
|
||||
Payload1 = <<"<topic1>;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 = <<"<topic1>;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 = <<"<subtopic>;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 = <<"<topic1>;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 = <<"<topic1>;ct=42">>,
|
||||
Payload1 = <<"<topic1>;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 = <<"<topic1>;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 = <<"<topic1>;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 = <<"<topic1>;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.
|
||||
|
|
@ -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
|
|
@ -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 也按照链式的方式执行:
|
||||
|
||||
<img src="https://docs.emqx.net/broker/latest/cn/advanced/assets/chain_of_responsiblity.png" style="zoom:50%;" />
|
||||
|
||||
### 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
|
||||
```
|
|
@ -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,
|
||||
[]}
|
||||
]}
|
||||
]}.
|
|
@ -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, []}
|
||||
]},
|
||||
{<<".*">>, []}
|
||||
]
|
||||
}.
|
|
@ -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).
|
|
@ -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}.
|
|
@ -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).
|
|
@ -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
|
|
@ -1,127 +0,0 @@
|
|||
# 多语言 - 协议接入
|
||||
|
||||
`emqx-exproto` 插件用于协议解析的多语言支持。它能够允许其他编程语言(例如:Python,Java 等)直接处理数据流实现协议的解析,并提供 Pub/Sub 接口以实现与系统其它组件的通信。
|
||||
|
||||
该插件给 EMQ X 带来的扩展性十分的强大,它能以你熟悉语言处理任何的私有协议,并享受由 EMQ X 系统带来的高连接,和高并发的优点。
|
||||
|
||||
## 特性
|
||||
|
||||
- 极强的扩展能力。使用 gRPC 作为 RPC 通信框架,支持各个主流编程语言
|
||||
- 高吞吐。连接层以完全的异步非阻塞式 I/O 的方式实现
|
||||
- 连接层透明。完全的支持 TCP\TLS UDP\DTLS 类型的连接管理,并对上层提供统一的 API 接口
|
||||
- 连接层的管理能力。例如,最大连接数,连接和吞吐的速率限制,IP 黑名单 等
|
||||
|
||||
## 架构
|
||||
|
||||

|
||||
|
||||
该插件主要需要处理的内容包括:
|
||||
|
||||
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 各个接口的访问。如图:
|
||||
|
||||

|
||||
|
||||
|
||||
详情参见:`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 =
|
||||
|
||||
# ...
|
||||
```
|
Binary file not shown.
Before Width: | Height: | Size: 71 KiB |
Binary file not shown.
Before Width: | Height: | Size: 95 KiB |
|
@ -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,
|
||||
[]}
|
||||
]}
|
||||
]}.
|
|
@ -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 ]) } ].
|
||||
|
|
@ -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}).
|
|
@ -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"}]}
|
||||
% ]}.
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
|
||||
-emqx_plugin(protocol).
|
||||
|
||||
-include("emqx_coap.hrl").
|
||||
-include("src/coap/include/emqx_coap.hrl").
|
||||
|
||||
-export([ start/2
|
||||
, stop/1
|
|
@ -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").
|
|
@ -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").
|
|
@ -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").
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
-author("Feng Lee <feng@emqx.io>").
|
||||
|
||||
-include("emqx_coap.hrl").
|
||||
-include("src/coap/include/emqx_coap.hrl").
|
||||
-include_lib("emqx/include/logger.hrl").
|
||||
|
||||
-logger_header("[CoAP-Registry]").
|
|
@ -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").
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
-module(emqx_coap_server).
|
||||
|
||||
-include("emqx_coap.hrl").
|
||||
-include("src/coap/include/emqx_coap.hrl").
|
||||
|
||||
-export([ start/1
|
||||
, stop/1
|
|
@ -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
|
|
@ -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)).
|
|
@ -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 = <<"<topic1>;ct=42">>,
|
||||
% Payload1 = <<"<topic1>;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 = <<"<topic1>;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 = <<"<subtopic>;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 = <<"<topic1>;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 = <<"<topic1>;ct=42">>,
|
||||
% Payload1 = <<"<topic1>;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 = <<"<topic1>;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 = <<"<topic1>;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 = <<"<topic1>;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.
|
||||
|
|
@ -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]").
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
-behaviour(application).
|
||||
|
||||
-include("emqx_exhook.hrl").
|
||||
-include("src/exhook/include/emqx_exhook.hrl").
|
||||
|
||||
-emqx_plugin(extension).
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
-module(emqx_exhook_cli).
|
||||
|
||||
-include("emqx_exhook.hrl").
|
||||
-include("src/exhook/include/emqx_exhook.hrl").
|
||||
|
||||
-export([cli/1]).
|
||||
|
|
@ -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").
|
||||
|
|
@ -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]").
|
|
@ -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).
|
|
@ -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).
|
|
@ -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}.
|
|
@ -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
|
|
@ -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").
|
|
@ -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]").
|
|
@ -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 ]) } ].
|
||||
|
|
@ -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}).
|
|
@ -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(),
|
|
@ -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").
|
||||
|
|
@ -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">>).
|
||||
|
|
@ -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
|
|
@ -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)).
|
||||
|
|
@ -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)).
|
||||
|
|
@ -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").
|
||||
|
|
@ -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
|
|
@ -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)).
|
||||
|
|
@ -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
|
|
@ -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.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue