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]}.
|
{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, [
|
{shell, [
|
||||||
% {config, "config/sys.config"},
|
% {config, "config/sys.config"},
|
||||||
{apps, [emqx_gateway]}
|
{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).
|
-emqx_plugin(protocol).
|
||||||
|
|
||||||
-include("emqx_coap.hrl").
|
-include("src/coap/include/emqx_coap.hrl").
|
||||||
|
|
||||||
-export([ start/2
|
-export([ start/2
|
||||||
, stop/1
|
, stop/1
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-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.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
-module(emqx_coap_pubsub_resource).
|
-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("gen_coap/include/coap.hrl").
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-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.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqx.io>").
|
-author("Feng Lee <feng@emqx.io>").
|
||||||
|
|
||||||
-include("emqx_coap.hrl").
|
-include("src/coap/include/emqx_coap.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
-logger_header("[CoAP-Registry]").
|
-logger_header("[CoAP-Registry]").
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
-module(emqx_coap_resource).
|
-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/emqx.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_coap_server).
|
-module(emqx_coap_server).
|
||||||
|
|
||||||
-include("emqx_coap.hrl").
|
-include("src/coap/include/emqx_coap.hrl").
|
||||||
|
|
||||||
-export([ start/1
|
-export([ start/1
|
||||||
, stop/1
|
, stop/1
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_coap_timer).
|
-module(emqx_coap_timer).
|
||||||
|
|
||||||
-include("emqx_coap.hrl").
|
-include("src/coap/include/emqx_coap.hrl").
|
||||||
|
|
||||||
-export([ cancel_timer/1
|
-export([ cancel_timer/1
|
||||||
, start_timer/2
|
, 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).
|
-module(emqx_exhook).
|
||||||
|
|
||||||
-include("emqx_exhook.hrl").
|
-include("src/exhook/include/emqx_exhook.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
-logger_header("[ExHook]").
|
-logger_header("[ExHook]").
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
-behaviour(application).
|
-behaviour(application).
|
||||||
|
|
||||||
-include("emqx_exhook.hrl").
|
-include("src/exhook/include/emqx_exhook.hrl").
|
||||||
|
|
||||||
-emqx_plugin(extension).
|
-emqx_plugin(extension).
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_exhook_cli).
|
-module(emqx_exhook_cli).
|
||||||
|
|
||||||
-include("emqx_exhook.hrl").
|
-include("src/exhook/include/emqx_exhook.hrl").
|
||||||
|
|
||||||
-export([cli/1]).
|
-export([cli/1]).
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_exhook_handler).
|
-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/emqx.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_exhook_server).
|
-module(emqx_exhook_server).
|
||||||
|
|
||||||
-include("emqx_exhook.hrl").
|
-include("src/exhook/include/emqx_exhook.hrl").
|
||||||
-include_lib("emqx/include/logger.hrl").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
-logger_header("[ExHook Svr]").
|
-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).
|
-module(emqx_exproto).
|
||||||
|
|
||||||
-include("emqx_exproto.hrl").
|
-include("src/exproto/include/emqx_exproto.hrl").
|
||||||
|
|
||||||
-export([ start_listeners/0
|
-export([ start_listeners/0
|
||||||
, stop_listeners/0
|
, stop_listeners/0
|
|
@ -15,8 +15,7 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_exproto_channel).
|
-module(emqx_exproto_channel).
|
||||||
|
-include("src/exproto/include/emqx_exproto.hrl").
|
||||||
-include("emqx_exproto.hrl").
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
-include_lib("emqx/include/emqx_mqtt.hrl").
|
-include_lib("emqx/include/emqx_mqtt.hrl").
|
||||||
-include_lib("emqx/include/types.hrl").
|
-include_lib("emqx/include/types.hrl").
|
|
@ -17,9 +17,9 @@
|
||||||
%% The gRPC server for ConnectionAdapter
|
%% The gRPC server for ConnectionAdapter
|
||||||
-module(emqx_exproto_gsvr).
|
-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").
|
-include_lib("emqx/include/logger.hrl").
|
||||||
|
|
||||||
-logger_header("[ExProto gServer]").
|
-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
|
, prep_stop/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
|
|
||||||
|
|
||||||
start(_Type, _Args) ->
|
start(_Type, _Args) ->
|
||||||
Pid = emqx_lwm2m_sup:start_link(),
|
Pid = emqx_lwm2m_sup:start_link(),
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_lwm2m_cmd_handler).
|
-module(emqx_lwm2m_cmd_handler).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
|
|
||||||
-include_lib("lwm2m_coap/include/coap.hrl").
|
-include_lib("lwm2m_coap/include/coap.hrl").
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
-include_lib("lwm2m_coap/include/coap.hrl").
|
-include_lib("lwm2m_coap/include/coap.hrl").
|
||||||
|
|
||||||
-behaviour(lwm2m_coap_resource).
|
% -behaviour(lwm2m_coap_resource).
|
||||||
|
|
||||||
-export([ coap_discover/2
|
-export([ coap_discover/2
|
||||||
, coap_get/5
|
, coap_get/5
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
|
|
||||||
-export([parse_object_list/1]).
|
-export([parse_object_list/1]).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
|
|
||||||
-define(PREFIX, <<"rd">>).
|
-define(PREFIX, <<"rd">>).
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_lwm2m_coap_server).
|
-module(emqx_lwm2m_coap_server).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
|
|
||||||
-export([ start/1
|
-export([ start/1
|
||||||
, stop/1
|
, stop/1
|
|
@ -22,7 +22,7 @@
|
||||||
, opaque_to_json/2
|
, 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)).
|
-define(LOG(Level, Format, Args), logger:Level("LWM2M-JSON: " ++ Format, Args)).
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
, translate_json/1
|
, 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)).
|
-define(LOG(Level, Format, Args), logger:Level("LWM2M-JSON: " ++ Format, Args)).
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_lwm2m_protocol).
|
-module(emqx_lwm2m_protocol).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
|
|
||||||
-include_lib("emqx/include/emqx.hrl").
|
-include_lib("emqx/include/emqx.hrl").
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_lwm2m_timer).
|
-module(emqx_lwm2m_timer).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
|
|
||||||
-export([ cancel_timer/1
|
-export([ cancel_timer/1
|
||||||
, start_timer/2
|
, start_timer/2
|
|
@ -25,7 +25,7 @@
|
||||||
-export([binary_to_hex_string/1]).
|
-export([binary_to_hex_string/1]).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
|
|
||||||
-define(LOG(Level, Format, Args), logger:Level("LWM2M-TLV: " ++ Format, Args)).
|
-define(LOG(Level, Format, Args), logger:Level("LWM2M-TLV: " ++ Format, Args)).
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_lwm2m_xml_object).
|
-module(emqx_lwm2m_xml_object).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
-include_lib("xmerl/include/xmerl.hrl").
|
-include_lib("xmerl/include/xmerl.hrl").
|
||||||
|
|
||||||
-export([ get_obj_def/2
|
-export([ get_obj_def/2
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
-module(emqx_lwm2m_xml_object_db).
|
-module(emqx_lwm2m_xml_object_db).
|
||||||
|
|
||||||
-include("emqx_lwm2m.hrl").
|
-include("src/lwm2m/include/emqx_lwm2m.hrl").
|
||||||
-include_lib("xmerl/include/xmerl.hrl").
|
-include_lib("xmerl/include/xmerl.hrl").
|
||||||
|
|
||||||
% This module is for future use. Disabled now.
|
% 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