From 533badecdf64f17c224f296603db761fdb4e562c Mon Sep 17 00:00:00 2001 From: lsxredrain <34489690@qq.com> Date: Mon, 3 Jan 2022 01:08:42 +0800 Subject: [PATCH] sync 4.3.10 --- Makefile | 4 + apps/dgiot/conf/rebar.config | 43 +- apps/dgiot_api/rebar.config | 4 +- apps/dgiot_api/src/dgiot_api.app.src | 2 +- apps/dgiot_device/rebar.config | 1 + .../python3/exhook-svr-python/README.md | 2 +- apps/dgiot_grpc/rebar.config | 4 +- apps/dgiot_grpc/src/dgiot_grpc_channel.erl | 14 +- apps/dgiot_http/rebar.config | 4 +- apps/dgiot_location/rebar.config | 2 +- .../src/dgiot_tdengine_channel.erl | 5 +- apps/emqx_auth_mnesia/.gitignore | 26 + apps/emqx_auth_mnesia/README.md | 2 + .../etc/emqx_auth_mnesia.conf | 30 + .../include/emqx_auth_mnesia.hrl | 64 ++ .../priv/emqx_auth_mnesia.schema | 43 ++ apps/emqx_auth_mnesia/rebar.config | 17 + apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl | 95 +++ .../src/emqx_acl_mnesia_api.erl | 224 +++++++ .../src/emqx_acl_mnesia_cli.erl | 157 +++++ .../src/emqx_acl_mnesia_db.erl | 339 ++++++++++ .../src/emqx_acl_mnesia_migrator.erl | 215 +++++++ .../src/emqx_auth_mnesia.app.src | 14 + .../src/emqx_auth_mnesia.appup.src | 31 + .../emqx_auth_mnesia/src/emqx_auth_mnesia.erl | 109 ++++ .../src/emqx_auth_mnesia_api.erl | 294 +++++++++ .../src/emqx_auth_mnesia_app.erl | 68 ++ .../src/emqx_auth_mnesia_cli.erl | 194 ++++++ .../src/emqx_auth_mnesia_sup.erl | 48 ++ .../test/emqx_acl_mnesia_SUITE.erl | 451 ++++++++++++++ .../test/emqx_auth_mnesia_SUITE.erl | 318 ++++++++++ .../src/emqx_bridge_mqtt.app.src | 4 +- .../test/emqx_bridge_worker_SUITE.erl | 2 +- apps/emqx_coap/intergration_test/Makefile | 22 +- apps/emqx_coap/src/emqx_coap.app.src | 2 +- apps/emqx_exhook/rebar.config | 2 +- apps/emqx_exhook/src/emqx_exhook.app.src | 2 +- apps/emqx_exhook/src/emqx_exhook.appup.src | 26 +- apps/emqx_exhook/src/emqx_exhook.erl | 130 ++-- apps/emqx_exhook/src/emqx_exhook_app.erl | 61 +- apps/emqx_exhook/src/emqx_exhook_cli.erl | 28 +- apps/emqx_exhook/src/emqx_exhook_handler.erl | 11 +- apps/emqx_exhook/src/emqx_exhook_mngr.erl | 311 ++++++++++ apps/emqx_exhook/src/emqx_exhook_server.erl | 43 +- apps/emqx_exhook/src/emqx_exhook_sup.erl | 29 +- apps/emqx_exhook/test/emqx_exhook_SUITE.erl | 31 +- apps/emqx_exproto/README.md | 2 +- apps/emqx_exproto/etc/emqx_exproto.conf | 2 +- apps/emqx_exproto/rebar.config | 2 +- apps/emqx_exproto/src/emqx_exproto.app.src | 2 +- apps/emqx_exproto/src/emqx_exproto.appup.src | 41 +- .../emqx_exproto/src/emqx_exproto_channel.erl | 43 +- apps/emqx_exproto/src/emqx_exproto_conn.erl | 20 +- apps/emqx_exproto/src/emqx_exproto_gcli.erl | 13 +- apps/emqx_exproto/src/emqx_exproto_gsvr.erl | 12 +- apps/emqx_lua_hook/src/emqx_lua_hook.app.src | 2 +- apps/emqx_lwm2m/etc/emqx_lwm2m.conf | 2 +- .../integration_test/insert_lwm2m_plugin.py | 19 +- apps/emqx_lwm2m/rebar.config | 2 +- apps/emqx_lwm2m/src/emqx_lwm2m.app.src | 2 +- apps/emqx_lwm2m/src/emqx_lwm2m.appup.src | 12 +- .../src/emqx_management.app.src | 4 +- .../src/emqx_management.appup.src | 4 +- .../src/emqx_mgmt_api_clients.erl | 10 +- .../src/emqx_mgmt_api_pubsub.erl | 6 + apps/emqx_management/src/emqx_mgmt_cli.erl | 6 +- .../src/emqx_mgmt_data_backup.erl | 30 +- .../test/emqx_auth_mnesia_migration_SUITE.erl | 54 +- apps/emqx_management/test/emqx_mgmt_SUITE.erl | 4 +- .../test/emqx_mgmt_api_SUITE.erl | 41 +- .../src/emqx_plugin_libs.app.src | 2 +- .../src/emqx_plugin_libs.appup.src | 16 + .../src/emqx_plugin_libs_ssl.erl | 6 +- .../src/emqx_prometheus.app.src | 2 +- apps/emqx_psk_file/src/emqx_psk_file.app.src | 2 +- apps/emqx_recon/src/emqx_recon.app.src | 2 +- apps/emqx_retainer/etc/emqx_retainer.conf | 8 + apps/emqx_retainer/priv/emqx_retainer.schema | 7 + apps/emqx_retainer/src/emqx_retainer.app.src | 4 +- .../emqx_retainer/src/emqx_retainer.appup.src | 4 +- apps/emqx_retainer/src/emqx_retainer.erl | 12 +- .../test/emqx_retainer_SUITE.erl | 15 + apps/emqx_rule_engine/include/rule_engine.hrl | 2 +- .../src/emqx_rule_actions.erl | 2 +- .../src/emqx_rule_engine.app.src | 4 +- .../src/emqx_rule_engine.appup.src | 92 ++- .../emqx_rule_engine/src/emqx_rule_engine.erl | 6 +- apps/emqx_sasl/src/emqx_sasl.app.src | 2 +- .../add_emqx_sn_to_project.py | 2 +- apps/emqx_sn/src/emqx_sn.app.src | 4 +- apps/emqx_sn/src/emqx_sn.appup.src | 14 + apps/emqx_sn/src/emqx_sn_app.erl | 15 +- apps/emqx_sn/src/emqx_sn_asleep_timer.erl | 15 +- apps/emqx_sn/src/emqx_sn_gateway.erl | 135 ++-- apps/emqx_sn/src/emqx_sn_registry.erl | 124 ++-- apps/emqx_sn/src/emqx_sn_sup.erl | 28 +- apps/emqx_sn/test/emqx_sn_protocol_SUITE.erl | 160 ++--- apps/emqx_sn/test/emqx_sn_registry_SUITE.erl | 125 ++-- apps/emqx_stomp/src/emqx_stomp.app.src | 4 +- apps/emqx_stomp/src/emqx_stomp.appup.src | 17 + apps/emqx_stomp/src/emqx_stomp.erl | 14 + apps/emqx_stomp/src/emqx_stomp_connection.erl | 440 ++++++++++--- apps/emqx_stomp/src/emqx_stomp_frame.erl | 7 + apps/emqx_stomp/src/emqx_stomp_heartbeat.erl | 30 +- apps/emqx_stomp/src/emqx_stomp_protocol.erl | 553 ++++++++++++++--- apps/emqx_stomp/test/emqx_stomp_SUITE.erl | 2 +- .../test/emqx_stomp_heartbeat_SUITE.erl | 3 +- apps/emqx_web_hook/etc/emqx_web_hook.conf | 5 + apps/emqx_web_hook/priv/emqx_web_hook.schema | 5 + apps/emqx_web_hook/src/emqx_web_hook.app.src | 4 +- .../emqx_web_hook/src/emqx_web_hook.appup.src | 28 +- apps/emqx_web_hook/src/emqx_web_hook.erl | 2 +- .../src/emqx_web_hook_actions.erl | 72 ++- apps/emqx_web_hook/src/emqx_web_hook_app.erl | 17 +- bin/install_upgrade.escript | 19 +- ...Klht7ERlYn_companyinfo_backgroundimage.jpg | Bin 0 -> 385221 bytes .../profile/Klht7ERlYn_userinfo_avatar.jpeg | Bin 0 -> 26290 bytes etc/emqx.conf | 22 +- .../emqx_dashboard/src/emqx_dashboard.app.src | 2 +- .../src/emqx_dashboard.appup.src | 10 +- .../src/emqx_dashboard_admin.erl | 4 +- .../test/emqx_dashboard_SUITE.erl | 7 +- priv/emqx.schema | 4 +- rebar.config | 44 +- rebar.config.erl | 3 + scripts/get-distro.sh | 19 + scripts/one-more-emqx-ee.sh | 106 ++++ scripts/one-more-emqx.sh | 102 +++ scripts/update_appup.escript | 580 ++++++++++++++++-- src/emqx.app.src | 2 +- src/emqx.appup.src | 278 +++++++-- src/emqx_alarm_handler.erl | 2 +- src/emqx_app.erl | 5 +- src/emqx_channel.erl | 7 +- src/emqx_cm.erl | 150 +++-- src/emqx_connection.erl | 2 +- src/emqx_frame.erl | 7 +- src/emqx_logger_jsonfmt.erl | 112 ++-- src/emqx_misc.erl | 23 +- src/emqx_mqueue.erl | 106 +++- src/emqx_pqueue.erl | 10 +- src/emqx_rpc.erl | 4 +- src/emqx_tracer.erl | 62 +- src/emqx_types.erl | 5 +- src/emqx_ws_connection.erl | 2 +- test/emqx_broker_SUITE.erl | 153 +++-- test/emqx_cm_SUITE.erl | 159 +++-- test/emqx_frame_SUITE.erl | 5 + test/emqx_mqtt_SUITE.erl | 116 ++-- test/emqx_mqueue_SUITE.erl | 133 +++- test/emqx_ws_connection_SUITE.erl | 2 +- 151 files changed, 6674 insertions(+), 1469 deletions(-) create mode 100644 apps/emqx_auth_mnesia/.gitignore create mode 100644 apps/emqx_auth_mnesia/README.md create mode 100644 apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf create mode 100644 apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl create mode 100644 apps/emqx_auth_mnesia/priv/emqx_auth_mnesia.schema create mode 100644 apps/emqx_auth_mnesia/rebar.config create mode 100644 apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_acl_mnesia_api.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_acl_mnesia_db.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_acl_mnesia_migrator.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia.appup.src create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia_api.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl create mode 100644 apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl create mode 100644 apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl create mode 100644 apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl create mode 100644 apps/emqx_exhook/src/emqx_exhook_mngr.erl create mode 100644 apps/emqx_plugin_libs/src/emqx_plugin_libs.appup.src create mode 100644 apps/emqx_sn/src/emqx_sn.appup.src create mode 100644 apps/emqx_stomp/src/emqx_stomp.appup.src create mode 100644 dgiot_file/user/profile/Klht7ERlYn_companyinfo_backgroundimage.jpg create mode 100644 dgiot_file/user/profile/Klht7ERlYn_userinfo_avatar.jpeg create mode 100644 scripts/get-distro.sh create mode 100644 scripts/one-more-emqx-ee.sh create mode 100644 scripts/one-more-emqx.sh diff --git a/Makefile b/Makefile index dc826e321e..73b8595a76 100644 --- a/Makefile +++ b/Makefile @@ -93,8 +93,10 @@ $(REL_PROFILES:%=%): $(REBAR) get-dashboard clean: $(PROFILES:%=clean-%) $(PROFILES:%=clean-%): @if [ -d _build/$(@:clean-%=%) ]; then \ + rm rebar.lock \ rm -rf _build/$(@:clean-%=%)/rel; \ find _build/$(@:clean-%=%) -name '*.beam' -o -name '*.so' -o -name '*.app' -o -name '*.appup' -o -name '*.o' -o -name '*.d' -type f | xargs rm -f; \ + find _build/$(@:clean-%=%) -type l -delete; \ fi .PHONY: clean-all @@ -103,6 +105,7 @@ clean-all: .PHONY: deps-all deps-all: $(REBAR) $(PROFILES:%=deps-%) + @make clean # ensure clean at the end ## deps- is used in CI scripts to download deps and the ## share downloads between CI steps and/or copied into containers @@ -110,6 +113,7 @@ deps-all: $(REBAR) $(PROFILES:%=deps-%) .PHONY: $(PROFILES:%=deps-%) $(PROFILES:%=deps-%): $(REBAR) get-dashboard @$(REBAR) as $(@:deps-%=%) get-deps + @rm -f rebar.lock .PHONY: xref xref: $(REBAR) diff --git a/apps/dgiot/conf/rebar.config b/apps/dgiot/conf/rebar.config index 350bb9f9f4..b37f074cfd 100644 --- a/apps/dgiot/conf/rebar.config +++ b/apps/dgiot/conf/rebar.config @@ -36,42 +36,13 @@ {erl_first_files, ["src/emqx_logger.erl", "src/emqx_rule_actions_trans.erl"]}. {deps, - [ - {getopt, {git, "https://gitee.com/fastdgiot/getopt.git", {tag, "v1.0.2"}}} - , {grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {tag, "0.6.4"}}} - , {grpc_plugin, {git, "https://gitee.com/fastdgiot/grpc_plugin.git", {tag, "v0.10.4"}}} - , {recon, {git, "https://gitee.com/fastdgiot/recon.git", {tag, "2.5.1"}}} - , {rebar3_proper, {git, "https://gitee.com/fastdgiot/rebar3_proper.git", {tag, "0.12.1"}}} - , {gpb, {git, "https://gitee.com/fastdgiot/gpb", {tag, "4.17.6"}}} - , {cuttlefish, {git, "https://gitee.com/fastdgiot/cuttlefish", {tag, "v4.3.4"}}} - , {cowboy, {git, "https://gitee.com/fastdgiot/cowboy", {tag, "2.8.3"}}} - , {minirest, {git, "https://gitee.com/fastdgiot/minirest", {tag, "0.3.5"}}} - , {ecpool, {git, "https://gitee.com/fastdgiot/ecpool", {tag, "0.5.1"}}} - , {replayq, {git, "https://gitee.com/fastdgiot/replayq", {tag, "0.3.2"}}} - , {pbkdf2, {git, "https://gitee.com/fastdgiot/erlang-pbkdf2.git", {branch, "2.0.4"}}} - , {emqtt, {git, "https://gitee.com/fastdgiot/emqtt", {tag, "1.2.3"}}} - , {rulesql, {git, "https://gitee.com/fastdgiot/rulesql", {tag, "0.1.2"}}} - , {recon, {git, "https://gitee.com/fastdgiot/recon", {tag, "2.5.1"}}} - , {ranch, {git, "https://gitee.com/fastdgiot/ranch", {tag, "1.7.1"}}} - , {gproc, {git, "https://gitee.com/fastdgiot/gproc", {tag, "0.8.0"}}} - , {gen_rpc, {git, "https://gitee.com/fastdgiot/gen_rpc", {tag, "2.5.1"}}} - , {gun, {git, "https://gitee.com/fastdgiot/gun", {tag, "1.3.5"}}} - , {ssl_verify_fun, {git, "https://gitee.com/fastdgiot/ssl_verify_fun.erl.git", {tag, "1.1.4"}}} - , {jiffy, {git, "https://gitee.com/fastdgiot/jiffy", {tag, "1.0.5"}}} - , {esockd, {git, "https://gitee.com/fastdgiot/esockd", {tag, "5.8.0"}}} - , {ekka, {git, "https://gitee.com/fastdgiot/ekka", {tag, "0.8.1"}}} - , {ehttpc, {git, "https://gitee.com/fastdgiot/ehttpc", {tag, "0.1.5"}}} - , {erlydtl, {git, "https://gitee.com/fastdgiot/erlydtl.git", {tag, "0.12.1"}}} - , {erlport, {git, "https://gitee.com/fastdgiot/erlport", {tag, "v1.2.2"}}} - , {poolboy, {git, "https://gitee.com/fastdgiot/poolboy.git", {tag, "1.5.3"}}} - , {websocket_client, {git, "https://gitee.com/fastdgiot/websocket_client", {tag, "v0.7"}}} - , {observer_cli, "1.6.1"} % NOTE: depends on recon 2.5.1 - , {ejdbc, {git, "https://gitee.com/fastdgiot/ejdbc", {tag, "1.0.1"}}} - , {snabbkaffe, {git, "https://gitee.com/fastdgiot/snabbkaffe.git", {tag, "0.12.2"}}} - , {cowlib, {git, "https://gitee.com/fastdgiot/cowlib",{tag,"2.8.0"}}} - , {ibrowse, {git, "https://gitee.com/fastdgiot/ibrowse.git", {tag, "v4.4.2"}}} - , {jesse, {git, "https://gitee.com/fastdgiot/jesse.git", {tag, "1.6.1"}}} - , {jwerl, {git, "https://gitee.com/fastdgiot/jwerl.git", {tag, "1.1.1"}}} + [{gun, {git, "https://gitee.com/fastdgiot/gun", {tag, "1.3.5"}}} + , {ssl_verify_fun, {git, "https://gitee.com/fastdgiot/ssl_verify_fun.erl.git", {tag, "1.1.4"}}} + , {erlydtl, {git, "https://gitee.com/fastdgiot/erlydtl.git", {tag, "0.12.1"}}} + , {erlport, {git, "https://gitee.com/fastdgiot/erlport", {tag, "v1.2.2"}}} + , {poolboy, {git, "https://gitee.com/fastdgiot/poolboy.git", {tag, "1.5.3"}}} + , {ibrowse, {git, "https://gitee.com/fastdgiot/ibrowse.git", {tag, "v4.4.2"}}} + , {jwerl, {git, "https://gitee.com/fastdgiot/jwerl.git", {tag, "1.1.1"}}} ]}. {xref_ignores, diff --git a/apps/dgiot_api/rebar.config b/apps/dgiot_api/rebar.config index 10a0fc19c1..b9d8bab193 100644 --- a/apps/dgiot_api/rebar.config +++ b/apps/dgiot_api/rebar.config @@ -1,4 +1,6 @@ -{deps, []}. +{deps, [ + {jesse, {git, "https://gitee.com/fastdgiot/jesse.git", {tag, "1.6.1"}}} +]}. {shell, [ % {config, "config/sys.config"}, diff --git a/apps/dgiot_api/src/dgiot_api.app.src b/apps/dgiot_api/src/dgiot_api.app.src index 8f5966e65d..c2f15d6434 100644 --- a/apps/dgiot_api/src/dgiot_api.app.src +++ b/apps/dgiot_api/src/dgiot_api.app.src @@ -3,7 +3,7 @@ {vsn, "4.3.0"}, {registered, []}, {mod, {dgiot_api_app, []}}, - {applications,[kernel, stdlib, dgiot, dgiot_parse]}, + {applications,[kernel, stdlib, jesse, dgiot, dgiot_parse]}, {env, []}, {modules, []}, {licenses, ["Apache-2.0"]}, diff --git a/apps/dgiot_device/rebar.config b/apps/dgiot_device/rebar.config index 9780e57204..0e038ed028 100644 --- a/apps/dgiot_device/rebar.config +++ b/apps/dgiot_device/rebar.config @@ -1,2 +1,3 @@ {deps, [ + {ibrowse, {git, "https://gitee.com/fastdgiot/ibrowse.git", {tag, "v4.4.2"}}} ]}. diff --git a/apps/dgiot_grpc/priv/example/python3/exhook-svr-python/README.md b/apps/dgiot_grpc/priv/example/python3/exhook-svr-python/README.md index 5409566f9a..9f1f3ccc5f 100644 --- a/apps/dgiot_grpc/priv/example/python3/exhook-svr-python/README.md +++ b/apps/dgiot_grpc/priv/example/python3/exhook-svr-python/README.md @@ -4,7 +4,7 @@ This is a demo server written in python for exhook ## Prerequisites -- [Python](https://www.python.org) 3.5 or higher +- [Python]() 3.5 or higher - pip version 9.0.1 or higher ## Run diff --git a/apps/dgiot_grpc/rebar.config b/apps/dgiot_grpc/rebar.config index 42ebc756c5..0ac6b388d0 100644 --- a/apps/dgiot_grpc/rebar.config +++ b/apps/dgiot_grpc/rebar.config @@ -1,9 +1,9 @@ {plugins, - [{grpc_plugin, {git, "https://github.com/HJianBo/grpc_plugin", {tag, "v0.10.1"}}} + [{grpc_plugin, {git, "https://gitee.com/fastdgiot/grpc_plugin", {tag, "v0.10.1"}}} ]}. {deps, - [{grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {branch, "master"}}} + [{grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {tag, "0.6.4"}}} ]}. {grpc, diff --git a/apps/dgiot_grpc/src/dgiot_grpc_channel.erl b/apps/dgiot_grpc/src/dgiot_grpc_channel.erl index 556a6d7fa1..19897423d1 100644 --- a/apps/dgiot_grpc/src/dgiot_grpc_channel.erl +++ b/apps/dgiot_grpc/src/dgiot_grpc_channel.erl @@ -120,17 +120,17 @@ init(?TYPE, ChannelId, Env) -> env = Env }, Port = maps:get(<<"port">>, Env), - Scheme = maps:get(<<"scheme">>, Env), - Host = dgiot_utils:to_list(maps:get(<<"host">>, Env)), - Opts0 = [{scheme, dgiot_utils:to_atom(Scheme)}, {host, Host}, {port, Port}], +%% Scheme = maps:get(<<"scheme">>, Env), +%% Host = dgiot_utils:to_list(maps:get(<<"host">>, Env)), + %%Opts0 = [{scheme, dgiot_utils:to_atom(Scheme)}, {host, Host}, {port, Port}], case maps:get(<<"model">>, Env) of <<"both">> -> dgiot_grpc_server:start(ChannelId, Port, []), - application:ensure_all_started(emqx_hooks), - emqx_exhook:enable(ChannelId, Opts0); + application:ensure_all_started(emqx_hooks); + %%emqx_exhook:enable(ChannelId, Opts0); <<"client">> -> - application:ensure_all_started(emqx_hooks), - emqx_exhook:enable(ChannelId, Opts0); + application:ensure_all_started(emqx_hooks); + %%emqx_exhook:enable(ChannelId, Opts0); <<"server">> -> dgiot_grpc_server:start(ChannelId, Port, []) end, diff --git a/apps/dgiot_http/rebar.config b/apps/dgiot_http/rebar.config index 1f72d26ede..8d8f2282eb 100644 --- a/apps/dgiot_http/rebar.config +++ b/apps/dgiot_http/rebar.config @@ -1,4 +1,6 @@ -{deps, []}. +{deps, [ + {jwerl, {git, "https://gitee.com/fastdgiot/jwerl.git", {tag, "1.1.1"}}} +]}. {shell, [ % {config, "config/sys.config"}, diff --git a/apps/dgiot_location/rebar.config b/apps/dgiot_location/rebar.config index 6d6bfe1a49..7d2e3f6876 100644 --- a/apps/dgiot_location/rebar.config +++ b/apps/dgiot_location/rebar.config @@ -1,3 +1,3 @@ {deps, [ - + {locus, {git, "https://gitee.com/fastdgiot/locus.git", {tag, "2.2.2"}}} ]}. diff --git a/apps/dgiot_tdengine/src/dgiot_tdengine_channel.erl b/apps/dgiot_tdengine/src/dgiot_tdengine_channel.erl index d337367cbf..77367e7e03 100644 --- a/apps/dgiot_tdengine/src/dgiot_tdengine_channel.erl +++ b/apps/dgiot_tdengine/src/dgiot_tdengine_channel.erl @@ -678,9 +678,10 @@ run_sql(#{<<"driver">> := <<"HTTP">>, <<"url">> := Url, <<"username">> := UserNa ?LOG(info, "Execute Fail ~p (~ts) ~p", [Url, unicode:characters_to_list(Sql), Reason]), {error, Reason} end; -run_sql(#{<<"driver">> := <<"JDBC">>, <<"url">> := _Url}, Action, Sql) when Action == execute_update; Action == execute_query -> +run_sql(#{<<"driver">> := <<"JDBC">>, <<"url">> := _Url}, Action, _Sql) when Action == execute_update; Action == execute_query -> %% ?LOG(info,"Execute ~p (~p) ~p", [Url, byte_size(Sql), Sql]), - apply(ejdbc, Action, [<<"com.taosdata.jdbc.TSDBDriver">>, Sql]). +%% apply(ejdbc, Action, [<<"com.taosdata.jdbc.TSDBDriver">>, Sql]). + ok. %% 先缓存定时存库 diff --git a/apps/emqx_auth_mnesia/.gitignore b/apps/emqx_auth_mnesia/.gitignore new file mode 100644 index 0000000000..a4d9fea0ae --- /dev/null +++ b/apps/emqx_auth_mnesia/.gitignore @@ -0,0 +1,26 @@ +.eunit +deps +*.o +*.beam +*.plt +erl_crash.dump +ebin +rel/example_project +.concrete/DEV_MODE +.rebar +.erlang.mk/ +emqx_auth_mnesia.d +data/ +_build/ +.DS_Store +cover/ +ct.coverdata +eunit.coverdata +logs/ +test/ct.cover.spec +rebar.lock +rebar3.crashdump +erlang.mk +.*.swp +.rebar3/ +etc/emqx_auth_mnesia.conf.rendered diff --git a/apps/emqx_auth_mnesia/README.md b/apps/emqx_auth_mnesia/README.md new file mode 100644 index 0000000000..8b4c145a89 --- /dev/null +++ b/apps/emqx_auth_mnesia/README.md @@ -0,0 +1,2 @@ +emqx_auth_mnesia +=============== diff --git a/apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf b/apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf new file mode 100644 index 0000000000..ff74656cb8 --- /dev/null +++ b/apps/emqx_auth_mnesia/etc/emqx_auth_mnesia.conf @@ -0,0 +1,30 @@ +## Password hash. +## +## Value: plain | md5 | sha | sha256 | sha512 +auth.mnesia.password_hash = sha256 + +##-------------------------------------------------------------------- +## ClientId Authentication +##-------------------------------------------------------------------- + +## Examples +##auth.client.1.clientid = id +##auth.client.1.password = passwd +##auth.client.2.clientid = dev:devid +##auth.client.2.password = passwd2 +##auth.client.3.clientid = app:appid +##auth.client.3.password = passwd3 +##auth.client.4.clientid = client~!@#$%^&*()_+ +##auth.client.4.password = passwd~!@#$%^&*()_+ + +##-------------------------------------------------------------------- +## Username Authentication +##-------------------------------------------------------------------- + +## Examples: +##auth.user.1.username = admin +##auth.user.1.password = public +##auth.user.2.username = feng@emqtt.io +##auth.user.2.password = public +##auth.user.3.username = name~!@#$%^&*()_+ +##auth.user.3.password = pwsswd~!@#$%^&*()_+ diff --git a/apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl b/apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl new file mode 100644 index 0000000000..143f6b61eb --- /dev/null +++ b/apps/emqx_auth_mnesia/include/emqx_auth_mnesia.hrl @@ -0,0 +1,64 @@ +-define(APP, emqx_auth_mnesia). + +-type(login() :: {clientid, binary()} + | {username, binary()}). + +-type(acl_target() :: login() | all). + +-type(acl_target_type() :: clientid | username | all). + +-type(access():: allow | deny). +-type(action():: pub | sub). +-type(legacy_action():: action() | pubsub). +-type(created_at():: integer()). + +-record(emqx_user, { + login :: login(), + password :: binary(), + created_at :: created_at() + }). + +-define(ACL_TABLE, emqx_acl). + +-define(MIGRATION_MARK_KEY, emqx_acl2_migration_started). + +-record(?ACL_TABLE, { + filter :: {acl_target(), emqx_topic:topic()} | ?MIGRATION_MARK_KEY, + action :: legacy_action(), + access :: access(), + created_at :: created_at() + }). + +-define(MIGRATION_MARK_RECORD, #?ACL_TABLE{filter = ?MIGRATION_MARK_KEY, action = pub, access = deny, created_at = 0}). + +-type(rule() :: {access(), action(), emqx_topic:topic(), created_at()}). + +-define(ACL_TABLE2, emqx_acl2). + +-record(?ACL_TABLE2, { + who :: acl_target(), + rules :: [ rule() ] + }). + +-type(acl_record() :: {acl_target(), emqx_topic:topic(), action(), access(), created_at()}). + +-record(auth_metrics, { + success = 'client.auth.success', + failure = 'client.auth.failure', + ignore = 'client.auth.ignore' + }). + +-record(acl_metrics, { + allow = 'client.acl.allow', + deny = 'client.acl.deny', + ignore = 'client.acl.ignore' + }). + +-define(METRICS(Type), tl(tuple_to_list(#Type{}))). +-define(METRICS(Type, K), #Type{}#Type.K). + +-define(AUTH_METRICS, ?METRICS(auth_metrics)). +-define(AUTH_METRICS(K), ?METRICS(auth_metrics, K)). + +-define(ACL_METRICS, ?METRICS(acl_metrics)). +-define(ACL_METRICS(K), ?METRICS(acl_metrics, K)). diff --git a/apps/emqx_auth_mnesia/priv/emqx_auth_mnesia.schema b/apps/emqx_auth_mnesia/priv/emqx_auth_mnesia.schema new file mode 100644 index 0000000000..87d6bf47fc --- /dev/null +++ b/apps/emqx_auth_mnesia/priv/emqx_auth_mnesia.schema @@ -0,0 +1,43 @@ +%%-*- mode: erlang -*- +%% emqx_auth_mnesia config mapping + +{mapping, "auth.mnesia.password_hash", "emqx_auth_mnesia.password_hash", [ + {default, sha256}, + {datatype, {enum, [plain, md5, sha, sha256, sha512]}} +]}. + +{mapping, "auth.client.$id.clientid", "emqx_auth_mnesia.clientid_list", [ + {datatype, string} +]}. + +{mapping, "auth.client.$id.password", "emqx_auth_mnesia.clientid_list", [ + {datatype, string} +]}. + +{translation, "emqx_auth_mnesia.clientid_list", fun(Conf) -> + ClientList = cuttlefish_variable:filter_by_prefix("auth.client", Conf), + lists:foldl( + fun({["auth", "client", Id, "clientid"], ClientId}, AccIn) -> + [{ClientId, cuttlefish:conf_get("auth.client." ++ Id ++ ".password", Conf)} | AccIn]; + (_, AccIn) -> + AccIn + end, [], ClientList) +end}. + +{mapping, "auth.user.$id.username", "emqx_auth_mnesia.username_list", [ + {datatype, string} +]}. + +{mapping, "auth.user.$id.password", "emqx_auth_mnesia.username_list", [ + {datatype, string} +]}. + +{translation, "emqx_auth_mnesia.username_list", fun(Conf) -> + Userlist = cuttlefish_variable:filter_by_prefix("auth.user", Conf), + lists:foldl( + fun({["auth", "user", Id, "username"], Username}, AccIn) -> + [{Username, cuttlefish:conf_get("auth.user." ++ Id ++ ".password", Conf)} | AccIn]; + (_, AccIn) -> + AccIn + end, [], Userlist) +end}. diff --git a/apps/emqx_auth_mnesia/rebar.config b/apps/emqx_auth_mnesia/rebar.config new file mode 100644 index 0000000000..4c695ec698 --- /dev/null +++ b/apps/emqx_auth_mnesia/rebar.config @@ -0,0 +1,17 @@ +{deps, + [ ]}. + +{erl_opts, [warn_unused_vars, + warn_shadow_vars, + warn_unused_import, + warn_obsolete_guard, + debug_info, + {parse_transform}]}. + +{xref_checks, [undefined_function_calls, undefined_functions, + locals_not_used, deprecated_function_calls, + warnings_as_errors, deprecated_functions]}. +{cover_enabled, true}. +{cover_opts, [verbose]}. +{cover_export_enabled, true}. + diff --git a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl new file mode 100644 index 0000000000..1e29d9121e --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia.erl @@ -0,0 +1,95 @@ +%%-------------------------------------------------------------------- +%% 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_acl_mnesia). + +-include("emqx_auth_mnesia.hrl"). + +%% ACL Callbacks +-export([ init/0 + , register_metrics/0 + , check_acl/5 + , description/0 + ]). + +init() -> + ok = emqx_acl_mnesia_db:create_table(), + ok = emqx_acl_mnesia_db:create_table2(). + +-spec(register_metrics() -> ok). +register_metrics() -> + lists:foreach(fun emqx_metrics:ensure/1, ?ACL_METRICS). + +check_acl(ClientInfo = #{ clientid := Clientid }, PubSub, Topic, _NoMatchAction, _Params) -> + Username = maps:get(username, ClientInfo, undefined), + + Acls = case Username of + undefined -> + emqx_acl_mnesia_db:lookup_acl({clientid, Clientid}) ++ + emqx_acl_mnesia_db:lookup_acl(all); + _ -> + emqx_acl_mnesia_db:lookup_acl({clientid, Clientid}) ++ + emqx_acl_mnesia_db:lookup_acl({username, Username}) ++ + emqx_acl_mnesia_db:lookup_acl(all) + end, + + case match(ClientInfo, PubSub, Topic, Acls) of + allow -> + emqx_metrics:inc(?ACL_METRICS(allow)), + {stop, allow}; + deny -> + emqx_metrics:inc(?ACL_METRICS(deny)), + {stop, deny}; + _ -> + emqx_metrics:inc(?ACL_METRICS(ignore)), + ok + end. + +description() -> "Acl with Mnesia". + +%%-------------------------------------------------------------------- +%% Internal functions +%%------------------------------------------------------------------- + +match(_ClientInfo, _PubSub, _Topic, []) -> + nomatch; +match(ClientInfo, PubSub, Topic, [ {_, ACLTopic, Action, Access, _} | Acls]) -> + case match_actions(PubSub, Action) andalso match_topic(ClientInfo, Topic, ACLTopic) of + true -> Access; + false -> match(ClientInfo, PubSub, Topic, Acls) + end. + +match_topic(ClientInfo, Topic, ACLTopic) when is_binary(Topic) -> + emqx_topic:match(Topic, feed_var(ClientInfo, ACLTopic)). + +match_actions(subscribe, sub) -> true; +match_actions(publish, pub) -> true; +match_actions(_, _) -> false. + +feed_var(ClientInfo, Pattern) -> + feed_var(ClientInfo, emqx_topic:words(Pattern), []). +feed_var(_ClientInfo, [], Acc) -> + emqx_topic:join(lists:reverse(Acc)); +feed_var(ClientInfo = #{clientid := undefined}, [<<"%c">>|Words], Acc) -> + feed_var(ClientInfo, Words, [<<"%c">>|Acc]); +feed_var(ClientInfo = #{clientid := ClientId}, [<<"%c">>|Words], Acc) -> + feed_var(ClientInfo, Words, [ClientId |Acc]); +feed_var(ClientInfo = #{username := undefined}, [<<"%u">>|Words], Acc) -> + feed_var(ClientInfo, Words, [<<"%u">>|Acc]); +feed_var(ClientInfo = #{username := Username}, [<<"%u">>|Words], Acc) -> + feed_var(ClientInfo, Words, [Username|Acc]); +feed_var(ClientInfo, [W|Words], Acc) -> + feed_var(ClientInfo, Words, [W|Acc]). diff --git a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_api.erl b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_api.erl new file mode 100644 index 0000000000..10615b3e01 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_api.erl @@ -0,0 +1,224 @@ +%c%-------------------------------------------------------------------- +%% 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_acl_mnesia_api). + +-include_lib("stdlib/include/ms_transform.hrl"). + +-import(proplists, [ get_value/2 + , get_value/3 + ]). + +-import(minirest, [return/1]). + +-rest_api(#{name => list_clientid, + method => 'GET', + path => "/acl/clientid", + func => list_clientid, + descr => "List available mnesia in the cluster" + }). + +-rest_api(#{name => list_username, + method => 'GET', + path => "/acl/username", + func => list_username, + descr => "List available mnesia in the cluster" + }). + +-rest_api(#{name => list_all, + method => 'GET', + path => "/acl/$all", + func => list_all, + descr => "List available mnesia in the cluster" + }). + +-rest_api(#{name => lookup_clientid, + method => 'GET', + path => "/acl/clientid/:bin:clientid", + func => lookup, + descr => "Lookup mnesia in the cluster" + }). + +-rest_api(#{name => lookup_username, + method => 'GET', + path => "/acl/username/:bin:username", + func => lookup, + descr => "Lookup mnesia in the cluster" + }). + +-rest_api(#{name => add, + method => 'POST', + path => "/acl", + func => add, + descr => "Add mnesia in the cluster" + }). + +-rest_api(#{name => delete_clientid, + method => 'DELETE', + path => "/acl/clientid/:bin:clientid/topic/:bin:topic", + func => delete, + descr => "Delete mnesia in the cluster" + }). + +-rest_api(#{name => delete_username, + method => 'DELETE', + path => "/acl/username/:bin:username/topic/:bin:topic", + func => delete, + descr => "Delete mnesia in the cluster" + }). + +-rest_api(#{name => delete_all, + method => 'DELETE', + path => "/acl/$all/topic/:bin:topic", + func => delete, + descr => "Delete mnesia in the cluster" + }). + + +-export([ list_clientid/2 + , list_username/2 + , list_all/2 + , lookup/2 + , add/2 + , delete/2 + ]). + +list_clientid(_Bindings, Params) -> + Table = emqx_acl_mnesia_db:login_acl_table(clientid), + return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}). + +list_username(_Bindings, Params) -> + Table = emqx_acl_mnesia_db:login_acl_table(username), + return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}). + +list_all(_Bindings, Params) -> + Table = emqx_acl_mnesia_db:login_acl_table(all), + return({ok, emqx_auth_mnesia_api:paginate_qh(Table, count(Table), Params, fun emqx_acl_mnesia_db:comparing/2, fun format/1)}). + + +lookup(#{clientid := Clientid}, _Params) -> + return({ok, format(emqx_acl_mnesia_db:lookup_acl({clientid, urldecode(Clientid)}))}); +lookup(#{username := Username}, _Params) -> + return({ok, format(emqx_acl_mnesia_db:lookup_acl({username, urldecode(Username)}))}). + +add(_Bindings, Params) -> + [ P | _] = Params, + case is_list(P) of + true -> return(do_add(Params, [])); + false -> + Re = do_add(Params), + case Re of + #{result := ok} -> return({ok, Re}); + #{result := <<"ok">>} -> return({ok, Re}); + _ -> return({error, {add, Re}}) + end + end. + +do_add([ Params | ParamsN ], ReList) -> + do_add(ParamsN, [do_add(Params) | ReList]); + +do_add([], ReList) -> + {ok, ReList}. + +do_add(Params) -> + Clientid = get_value(<<"clientid">>, Params, undefined), + Username = get_value(<<"username">>, Params, undefined), + Login = case {Clientid, Username} of + {undefined, undefined} -> all; + {_, undefined} -> {clientid, Clientid}; + {undefined, _} -> {username, Username} + end, + Topic = get_value(<<"topic">>, Params), + Action = get_value(<<"action">>, Params), + Access = get_value(<<"access">>, Params), + Re = case validate([login, topic, action, access], [Login, Topic, Action, Access]) of + ok -> + emqx_acl_mnesia_db:add_acl(Login, Topic, erlang:binary_to_atom(Action, utf8), erlang:binary_to_atom(Access, utf8)); + Err -> Err + end, + maps:merge(#{topic => Topic, + action => Action, + access => Access, + result => format_msg(Re) + }, case Login of + all -> #{all => '$all'}; + _ -> maps:from_list([Login]) + end). + +delete(#{clientid := Clientid, topic := Topic}, _) -> + return(emqx_acl_mnesia_db:remove_acl({clientid, urldecode(Clientid)}, urldecode(Topic))); +delete(#{username := Username, topic := Topic}, _) -> + return(emqx_acl_mnesia_db:remove_acl({username, urldecode(Username)}, urldecode(Topic))); +delete(#{topic := Topic}, _) -> + return(emqx_acl_mnesia_db:remove_acl(all, urldecode(Topic))). + +%%------------------------------------------------------------------------------ +%% Interval Funcs +%%------------------------------------------------------------------------------ + +count(QH) -> + qlc:fold(fun(_, Count) -> Count + 1 end, 0, QH). + +format({{clientid, Clientid}, Topic, Action, Access, _CreatedAt}) -> + #{clientid => Clientid, topic => Topic, action => Action, access => Access}; +format({{username, Username}, Topic, Action, Access, _CreatedAt}) -> + #{username => Username, topic => Topic, action => Action, access => Access}; +format({all, Topic, Action, Access, _CreatedAt}) -> + #{all => '$all', topic => Topic, action => Action, access => Access}; +format(List) when is_list(List) -> + format(List, []). + +format([L | List], Relist) -> + format(List, [format(L) | Relist]); +format([], ReList) -> lists:reverse(ReList). + +validate([], []) -> + ok; +validate([K|Keys], [V|Values]) -> + case do_validation(K, V) of + false -> {error, K}; + true -> validate(Keys, Values) + end. +do_validation(login, all) -> + true; +do_validation(login, {clientid, V}) when is_binary(V) + andalso byte_size(V) > 0 -> + true; +do_validation(login, {username, V}) when is_binary(V) + andalso byte_size(V) > 0 -> + true; +do_validation(topic, V) when is_binary(V) + andalso byte_size(V) > 0 -> + true; +do_validation(action, V) when is_binary(V) -> + case V =:= <<"pub">> orelse V =:= <<"sub">> orelse V =:= <<"pubsub">> of + true -> true; + false -> false + end; +do_validation(access, V) when V =:= <<"allow">> orelse V =:= <<"deny">> -> + true; +do_validation(_, _) -> + false. + +format_msg(Message) + when is_atom(Message); + is_binary(Message) -> Message; + +format_msg(Message) when is_tuple(Message) -> + iolist_to_binary(io_lib:format("~p", [Message])). + +urldecode(S) -> + emqx_http_lib:uri_decode(S). diff --git a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl new file mode 100644 index 0000000000..145f0ede80 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_cli.erl @@ -0,0 +1,157 @@ +%%-------------------------------------------------------------------- +%% 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_acl_mnesia_cli). + +-export([cli/1]). + +%%-------------------------------------------------------------------- +%% ACL Cli +%%-------------------------------------------------------------------- + +cli(["list"]) -> + [print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls()]; + +cli(["list", "clientid"]) -> + [print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls(clientid)]; + +cli(["list", "username"]) -> + [print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls(username)]; + +cli(["list", "_all"]) -> + [print_acl(Acl) || Acl <- emqx_acl_mnesia_db:all_acls(all)]; + +cli(["add", "clientid", Clientid, Topic, Action, Access]) -> + case validate(action, Action) andalso validate(access, Access) of + true -> + case emqx_acl_mnesia_db:add_acl( + {clientid, iolist_to_binary(Clientid)}, + iolist_to_binary(Topic), + list_to_existing_atom(Action), + list_to_existing_atom(Access) + ) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + _ -> + emqx_ctl:print("Error: Input is illegal~n") + end; + +cli(["add", "username", Username, Topic, Action, Access]) -> + case validate(action, Action) andalso validate(access, Access) of + true -> + case emqx_acl_mnesia_db:add_acl( + {username, iolist_to_binary(Username)}, + iolist_to_binary(Topic), + list_to_existing_atom(Action), + list_to_existing_atom(Access) + ) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + _ -> + emqx_ctl:print("Error: Input is illegal~n") + end; + +cli(["add", "_all", Topic, Action, Access]) -> + case validate(action, Action) andalso validate(access, Access) of + true -> + case emqx_acl_mnesia_db:add_acl( + all, + iolist_to_binary(Topic), + list_to_existing_atom(Action), + list_to_existing_atom(Access) + ) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + _ -> + emqx_ctl:print("Error: Input is illegal~n") + end; + +cli(["show", "clientid", Clientid]) -> + [print_acl(Acl) || Acl <- emqx_acl_mnesia_db:lookup_acl({clientid, iolist_to_binary(Clientid)})]; + +cli(["show", "username", Username]) -> + [print_acl(Acl) || Acl <- emqx_acl_mnesia_db:lookup_acl({username, iolist_to_binary(Username)})]; + +cli(["del", "clientid", Clientid, Topic])-> + cli(["delete", "clientid", Clientid, Topic]); + +cli(["delete", "clientid", Clientid, Topic])-> + case emqx_acl_mnesia_db:remove_acl({clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Topic)) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +cli(["del", "username", Username, Topic])-> + cli(["delete", "username", Username, Topic]); + +cli(["delete", "username", Username, Topic])-> + case emqx_acl_mnesia_db:remove_acl({username, iolist_to_binary(Username)}, iolist_to_binary(Topic)) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +cli(["del", "_all", Topic])-> + cli(["delete", "_all", Topic]); + +cli(["delete", "_all", Topic])-> + case emqx_acl_mnesia_db:remove_acl(all, iolist_to_binary(Topic)) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +cli(_) -> + emqx_ctl:usage([ {"acl list clientid", "List clientid acls"} + , {"acl list username", "List username acls"} + , {"acl list _all", "List $all acls"} + , {"acl show clientid ", "Lookup clientid acl detail"} + , {"acl show username ", "Lookup username acl detail"} + , {"acl aad clientid ", "Add clientid acl"} + , {"acl add Username ", "Add username acl"} + , {"acl add _all ", "Add $all acl"} + , {"acl delete clientid ", "Delete clientid acl"} + , {"acl delete username ", "Delete username acl"} + , {"acl delete _all ", "Delete $all acl"} + ]). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +validate(action, "pub") -> true; +validate(action, "sub") -> true; +validate(action, "pubsub") -> true; +validate(access, "allow") -> true; +validate(access, "deny") -> true; +validate(_, _) -> false. + +print_acl({{clientid, Clientid}, Topic, Action, Access, _}) -> + emqx_ctl:print( + "Acl(clientid = ~p topic = ~p action = ~p access = ~p)~n", + [Clientid, Topic, Action, Access] + ); +print_acl({{username, Username}, Topic, Action, Access, _}) -> + emqx_ctl:print( + "Acl(username = ~p topic = ~p action = ~p access = ~p)~n", + [Username, Topic, Action, Access] + ); +print_acl({all, Topic, Action, Access, _}) -> + emqx_ctl:print( + "Acl($all topic = ~p action = ~p access = ~p)~n", + [Topic, Action, Access] + ). diff --git a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_db.erl b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_db.erl new file mode 100644 index 0000000000..b483e59dfa --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_db.erl @@ -0,0 +1,339 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +-module(emqx_acl_mnesia_db). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). +-include_lib("stdlib/include/qlc.hrl"). + +%% ACL APIs +-export([ create_table/0 + , create_table2/0 + ]). + +-export([ add_acl/4 + , lookup_acl/1 + , all_acls_export/0 + , all_acls/0 + , all_acls/1 + , remove_acl/2 + , merge_acl_records/3 + , login_acl_table/1 + , is_migration_started/0 + ]). + +-export([comparing/2]). + +%%-------------------------------------------------------------------- +%% ACL API +%%-------------------------------------------------------------------- + +%% @doc Create table `emqx_acl` of old format rules +-spec(create_table() -> ok). +create_table() -> + ok = ekka_mnesia:create_table(?ACL_TABLE, [ + {type, bag}, + {disc_copies, [node()]}, + {attributes, record_info(fields, ?ACL_TABLE)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]}]), + ok = ekka_mnesia:copy_table(?ACL_TABLE, disc_copies). + +%% @doc Create table `emqx_acl2` of new format rules +-spec(create_table2() -> ok). +create_table2() -> + ok = ekka_mnesia:create_table(?ACL_TABLE2, [ + {type, ordered_set}, + {disc_copies, [node()]}, + {attributes, record_info(fields, ?ACL_TABLE2)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]}]), + ok = ekka_mnesia:copy_table(?ACL_TABLE2, disc_copies). + +%% @doc Add Acls +-spec(add_acl(acl_target(), emqx_topic:topic(), legacy_action(), access()) -> + ok | {error, any()}). +add_acl(Login, Topic, Action, Access) -> + ret(mnesia:transaction(fun() -> + case is_migration_started() of + true -> add_acl_new(Login, Topic, Action, Access); + false -> add_acl_old(Login, Topic, Action, Access) + end + end)). + +%% @doc Lookup acl by login +-spec(lookup_acl(acl_target()) -> list(acl_record())). +lookup_acl(undefined) -> []; +lookup_acl(Login) -> + % After migration to ?ACL_TABLE2, ?ACL_TABLE never has any rules. This lookup should be removed later. + MatchSpec = ets:fun2ms(fun(#?ACL_TABLE{filter = {Filter, _}} = Rec) + when Filter =:= Login -> Rec + end), + OldRecs = ets:select(?ACL_TABLE, MatchSpec), + + NewAcls = ets:lookup(?ACL_TABLE2, Login), + MergedAcl = merge_acl_records(Login, OldRecs, NewAcls), + lists:sort(fun comparing/2, acl_to_list(MergedAcl)). + +%% @doc Remove ACL +-spec remove_acl(acl_target(), emqx_topic:topic()) -> ok | {error, any()}. +remove_acl(Login, Topic) -> + ret(mnesia:transaction(fun() -> + mnesia:delete({?ACL_TABLE, {Login, Topic}}), + case mnesia:wread({?ACL_TABLE2, Login}) of + [] -> ok; + [#?ACL_TABLE2{rules = Rules} = Acl] -> + case delete_topic_rules(Topic, Rules) of + [] -> mnesia:delete({?ACL_TABLE2, Login}); + [_ | _] = RemainingRules -> + mnesia:write(Acl#?ACL_TABLE2{rules = RemainingRules}) + end + end + end)). + +%% @doc All ACL rules +-spec(all_acls() -> list(acl_record())). +all_acls() -> + all_acls(username) ++ + all_acls(clientid) ++ + all_acls(all). + +%% @doc All ACL rules of specified type +-spec(all_acls(acl_target_type()) -> list(acl_record())). +all_acls(AclTargetType) -> + lists:sort(fun comparing/2, qlc:eval(login_acl_table(AclTargetType))). + +%% @doc All ACL rules fetched transactionally +-spec(all_acls_export() -> list(acl_record())). +all_acls_export() -> + AclTargetTypes = [username, clientid, all], + MatchSpecNew = lists:flatmap(fun login_match_spec_new/1, AclTargetTypes), + MatchSpecOld = lists:flatmap(fun login_match_spec_old/1, AclTargetTypes), + + {atomic, Records} = mnesia:transaction( + fun() -> + QH = acl_table(MatchSpecNew, MatchSpecOld, fun mnesia:table/2, fun lookup_mnesia/2), + qlc:eval(QH) + end), + Records. + +%% @doc QLC table of logins matching spec +-spec(login_acl_table(acl_target_type()) -> qlc:query_handle()). +login_acl_table(AclTargetType) -> + MatchSpecNew = login_match_spec_new(AclTargetType), + MatchSpecOld = login_match_spec_old(AclTargetType), + acl_table(MatchSpecNew, MatchSpecOld, fun ets:table/2, fun lookup_ets/2). + +%% @doc Combine old `emqx_acl` ACL records with a new `emqx_acl2` ACL record for a given login +-spec(merge_acl_records(acl_target(), [#?ACL_TABLE{}], [#?ACL_TABLE2{}]) -> #?ACL_TABLE2{}). +merge_acl_records(Login, OldRecs, Acls) -> + OldRules = old_recs_to_rules(OldRecs), + NewRules = case Acls of + [] -> []; + [#?ACL_TABLE2{rules = Rules}] -> Rules + end, + #?ACL_TABLE2{who = Login, rules = merge_rules(NewRules, OldRules)}. + +%% @doc Checks if background migration of ACL rules from `emqx_acl` to `emqx_acl2` format started. +%% Should be run in transaction +-spec(is_migration_started() -> boolean()). +is_migration_started() -> + case mnesia:read({?ACL_TABLE, ?MIGRATION_MARK_KEY}) of + [?MIGRATION_MARK_RECORD | _] -> true; + [] -> false + end. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +add_acl_new(Login, Topic, Action, Access) -> + Rule = {Access, Action, Topic, erlang:system_time(millisecond)}, + Rules = normalize_rule(Rule), + OldAcl = mnesia:wread({?ACL_TABLE2, Login}), + NewAcl = case OldAcl of + [#?ACL_TABLE2{rules = OldRules} = Acl] -> + Acl#?ACL_TABLE2{rules = merge_rules(Rules, OldRules)}; + [] -> + #?ACL_TABLE2{who = Login, rules = Rules} + end, + mnesia:write(NewAcl). + +add_acl_old(Login, Topic, Action, Access) -> + Filter = {Login, Topic}, + Acl = #?ACL_TABLE{ + filter = Filter, + action = Action, + access = Access, + created_at = erlang:system_time(millisecond) + }, + OldRecords = mnesia:wread({?ACL_TABLE, Filter}), + case Action of + pubsub -> + update_permission(pub, Acl, OldRecords), + update_permission(sub, Acl, OldRecords); + _ -> + update_permission(Action, Acl, OldRecords) + end. + +old_recs_to_rules(OldRecs) -> + lists:flatmap(fun old_rec_to_rules/1, OldRecs). + +old_rec_to_rules(#?ACL_TABLE{filter = {_, Topic}, action = Action, access = Access, created_at = CreatedAt}) -> + normalize_rule({Access, Action, Topic, CreatedAt}). + +normalize_rule({Access, pubsub, Topic, CreatedAt}) -> + [{Access, pub, Topic, CreatedAt}, {Access, sub, Topic, CreatedAt}]; +normalize_rule({Access, Action, Topic, CreatedAt}) -> + [{Access, Action, Topic, CreatedAt}]. + +merge_rules([], OldRules) -> OldRules; +merge_rules([NewRule | RestNewRules], OldRules) -> + merge_rules(RestNewRules, merge_rule(NewRule, OldRules)). + +merge_rule({_, Action, Topic, _ } = NewRule, OldRules) -> + [NewRule | lists:filter( + fun({_, OldAction, OldTopic, _}) -> + {Action, Topic} =/= {OldAction, OldTopic} + end, OldRules)]. + +acl_to_list(#?ACL_TABLE2{who = Login, rules = Rules}) -> + [{Login, Topic, Action, Access, CreatedAt} || {Access, Action, Topic, CreatedAt} <- Rules]. + +delete_topic_rules(Topic, Rules) -> + [Rule || {_, _, T, _} = Rule <- Rules, T =/= Topic]. + +comparing({_, _, _, _, CreatedAt} = Rec1, + {_, _, _, _, CreatedAt} = Rec2) -> + Rec1 >= Rec2; + +comparing({_, _, _, _, CreatedAt1}, + {_, _, _, _, CreatedAt2}) -> + CreatedAt1 >= CreatedAt2. + +login_match_spec_old(all) -> + ets:fun2ms(fun(#?ACL_TABLE{filter = {all, _}} = Record) -> + Record + end); + +login_match_spec_old(Type) when (Type =:= username) or (Type =:= clientid) -> + ets:fun2ms(fun(#?ACL_TABLE{filter = {{RecordType, _}, _}} = Record) + when RecordType =:= Type -> Record + end). + +login_match_spec_new(all) -> + ets:fun2ms(fun(#?ACL_TABLE2{who = all} = Record) -> + Record + end); + +login_match_spec_new(Type) when (Type =:= username) or (Type =:= clientid) -> + ets:fun2ms(fun(#?ACL_TABLE2{who = {RecordType, _}} = Record) + when RecordType =:= Type -> Record + end). + +acl_table(MatchSpecNew, MatchSpecOld, TableFun, LookupFun) -> + TraverseFun = + fun() -> + CursorNew = + qlc:cursor( + TableFun(?ACL_TABLE2, [{traverse, {select, MatchSpecNew}}])), + CursorOld = + qlc:cursor( + TableFun(?ACL_TABLE, [{traverse, {select, MatchSpecOld}}])), + traverse_new(CursorNew, CursorOld, #{}, LookupFun) + end, + + qlc:table(TraverseFun, []). + + +% These are traverse funs for qlc table created by `acl_table/4`. +% Traversing consumes memory: it collects logins present in `?ACL_TABLE` and +% at the same time having rules in `?ACL_TABLE2`. +% Such records appear if ACLs are inserted before migration started. +% After migration, number of such logins is zero, so traversing starts working in +% constant memory. + +traverse_new(CursorNew, CursorOld, FoundKeys, LookupFun) -> + Acls = qlc:next_answers(CursorNew, 1), + case Acls of + [] -> + qlc:delete_cursor(CursorNew), + traverse_old(CursorOld, FoundKeys); + [#?ACL_TABLE2{who = Login, rules = Rules} = Acl] -> + Keys = lists:usort([{Login, Topic} || {_, _, Topic, _} <- Rules]), + OldRecs = lists:flatmap(fun(Key) -> LookupFun(?ACL_TABLE, Key) end, Keys), + MergedAcl = merge_acl_records(Login, OldRecs, [Acl]), + NewFoundKeys = + lists:foldl(fun(#?ACL_TABLE{filter = Key}, Found) -> maps:put(Key, true, Found) end, + FoundKeys, + OldRecs), + case acl_to_list(MergedAcl) of + [] -> + traverse_new(CursorNew, CursorOld, NewFoundKeys, LookupFun); + List -> + List ++ fun() -> traverse_new(CursorNew, CursorOld, NewFoundKeys, LookupFun) end + end + end. + +traverse_old(CursorOld, FoundKeys) -> + OldAcls = qlc:next_answers(CursorOld), + case OldAcls of + [] -> + qlc:delete_cursor(CursorOld), + []; + _ -> + Records = [ {Login, Topic, Action, Access, CreatedAt} + || #?ACL_TABLE{filter = {Login, Topic}, action = LegacyAction, access = Access, created_at = CreatedAt} <- OldAcls, + {_, Action, _, _} <- normalize_rule({Access, LegacyAction, Topic, CreatedAt}), + not maps:is_key({Login, Topic}, FoundKeys) + ], + case Records of + [] -> traverse_old(CursorOld, FoundKeys); + List -> List ++ fun() -> traverse_old(CursorOld, FoundKeys) end + end + end. + +lookup_mnesia(Tab, Key) -> + mnesia:read({Tab, Key}). + +lookup_ets(Tab, Key) -> + ets:lookup(Tab, Key). + +update_permission(Action, Acl0, OldRecords) -> + Acl = Acl0 #?ACL_TABLE{action = Action}, + maybe_delete_shadowed_records(Action, OldRecords), + mnesia:write(Acl). + +maybe_delete_shadowed_records(_, []) -> + ok; +maybe_delete_shadowed_records(Action1, [Rec = #emqx_acl{action = Action2} | Rest]) -> + if Action1 =:= Action2 -> + ok = mnesia:delete_object(Rec); + Action2 =:= pubsub -> + %% Perform migration from the old data format on the + %% fly. This is needed only for the enterprise version, + %% delete this branch on 5.0 + mnesia:delete_object(Rec), + mnesia:write(Rec#?ACL_TABLE{action = other_action(Action1)}); + true -> + ok + end, + maybe_delete_shadowed_records(Action1, Rest). + +other_action(pub) -> sub; +other_action(sub) -> pub. + +ret({atomic, ok}) -> ok; +ret({aborted, Error}) -> {error, Error}. diff --git a/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_migrator.erl b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_migrator.erl new file mode 100644 index 0000000000..864f008842 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_acl_mnesia_migrator.erl @@ -0,0 +1,215 @@ +%%-------------------------------------------------------------------- +%% 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_acl_mnesia_migrator). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("emqx/include/logger.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-behaviour(gen_statem). + +-define(CHECK_ALL_NODES_INTERVAL, 60000). + +-type(migration_delay_reason() :: old_nodes | bad_nodes). + +-export([ + callback_mode/0, + init/1 +]). + +-export([ + waiting_all_nodes/3, + checking_old_table/3, + migrating/3 +]). + +-export([ + start_link/0, + start_link/1, + start_supervised/0, + stop_supervised/0, + migrate_records/0, + is_migrating_on_node/1, + is_old_table_migrated/0 +]). + +%%-------------------------------------------------------------------- +%% External interface +%%-------------------------------------------------------------------- + +start_link() -> + start_link(?MODULE). + +start_link(Name) when is_atom(Name) -> + start_link(#{ + name => Name + }); + +start_link(#{name := Name} = Opts) -> + gen_statem:start_link({local, Name}, ?MODULE, Opts, []). + +start_supervised() -> + try + {ok, _} = supervisor:restart_child(emqx_auth_mnesia_sup, ?MODULE), + ok + catch + exit:{noproc, _} -> ok + end. + +stop_supervised() -> + try + ok = supervisor:terminate_child(emqx_auth_mnesia_sup, ?MODULE), + ok = supervisor:delete_child(emqx_auth_mnesia_sup, ?MODULE) + catch + exit:{noproc, _} -> ok + end. + +%%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- + +callback_mode() -> state_functions. + +init(Opts) -> + ok = emqx_acl_mnesia_db:create_table(), + ok = emqx_acl_mnesia_db:create_table2(), + Name = maps:get(name, Opts, ?MODULE), + CheckNodesInterval = maps:get(check_nodes_interval, Opts, ?CHECK_ALL_NODES_INTERVAL), + GetNodes = maps:get(get_nodes, Opts, fun all_nodes/0), + Data = + #{name => Name, + check_nodes_interval => CheckNodesInterval, + get_nodes => GetNodes}, + {ok, waiting_all_nodes, Data, [{state_timeout, 0, check_nodes}]}. + +%%-------------------------------------------------------------------- +%% state callbacks +%%-------------------------------------------------------------------- + +waiting_all_nodes(state_timeout, check_nodes, Data) -> + #{name := Name, check_nodes_interval := CheckNodesInterval, get_nodes := GetNodes} = Data, + case is_all_nodes_migrating(Name, GetNodes()) of + true -> + ?tp(info, emqx_acl_mnesia_migrator_check_old_table, #{}), + {next_state, checking_old_table, Data, [{next_event, internal, check_old_table}]}; + {false, Reason, Nodes} -> + ?tp(info, + emqx_acl_mnesia_migrator_bad_nodes_delay, + #{delay => CheckNodesInterval, + reason => Reason, + name => Name, + nodes => Nodes}), + {keep_state_and_data, [{state_timeout, CheckNodesInterval, check_nodes}]} + end. + +checking_old_table(internal, check_old_table, Data) -> + case is_old_table_migrated() of + true -> + ?tp(info, emqx_acl_mnesia_migrator_finish, #{}), + {next_state, finished, Data, [{hibernate, true}]}; + false -> + ?tp(info, emqx_acl_mnesia_migrator_start_migration, #{}), + {next_state, migrating, Data, [{next_event, internal, start_migration}]} + end. + +migrating(internal, start_migration, Data) -> + ok = migrate_records(), + {next_state, checking_old_table, Data, [{next_event, internal, check_old_table}]}. + +%% @doc Returns `true` if migration is started in the local node, otherwise crash. +-spec(is_migrating_on_node(atom()) -> true). +is_migrating_on_node(Name) -> + true = is_pid(erlang:whereis(Name)). + +%% @doc Run migration of records +-spec(migrate_records() -> ok). +migrate_records() -> + ok = add_migration_mark(), + Key = peek_record(), + do_migrate_records(Key). + +%% @doc Run migration of records +-spec(is_all_nodes_migrating(atom(), list(node())) -> true | {false, migration_delay_reason(), list(node())}). +is_all_nodes_migrating(Name, Nodes) -> + case rpc:multicall(Nodes, ?MODULE, is_migrating_on_node, [Name]) of + {Results, []} -> + OldNodes = [ Node || {Node, Result} <- lists:zip(Nodes, Results), Result =/= true ], + case OldNodes of + [] -> true; + _ -> {false, old_nodes, OldNodes} + end; + {_, [_BadNode | _] = BadNodes} -> + {false, bad_nodes, BadNodes} + end. + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +all_nodes() -> + ekka_mnesia:cluster_nodes(all). + +is_old_table_migrated() -> + Result = + mnesia:transaction(fun() -> + case mnesia:first(?ACL_TABLE) of + ?MIGRATION_MARK_KEY -> + case mnesia:next(?ACL_TABLE, ?MIGRATION_MARK_KEY) of + '$end_of_table' -> true; + _OtherKey -> false + end; + '$end_of_table' -> false; + _OtherKey -> false + end + end), + case Result of + {atomic, true} -> + true; + _ -> + false + end. + +add_migration_mark() -> + {atomic, ok} = mnesia:transaction(fun() -> mnesia:write(?MIGRATION_MARK_RECORD) end), + ok. + +peek_record() -> + Key = mnesia:dirty_first(?ACL_TABLE), + case Key of + ?MIGRATION_MARK_KEY -> + mnesia:dirty_next(?ACL_TABLE, Key); + _ -> Key + end. + +do_migrate_records('$end_of_table') -> ok; +do_migrate_records({_Login, _Topic} = Key) -> + ?tp(emqx_acl_mnesia_migrator_record_selected, #{key => Key}), + _ = mnesia:transaction(fun migrate_one_record/1, [Key]), + do_migrate_records(peek_record()). + +migrate_one_record({Login, _Topic} = Key) -> + case mnesia:wread({?ACL_TABLE, Key}) of + [] -> + ?tp(emqx_acl_mnesia_migrator_record_missed, #{key => Key}), + record_missing; + OldRecs -> + Acls = mnesia:wread({?ACL_TABLE2, Login}), + UpdatedAcl = emqx_acl_mnesia_db:merge_acl_records(Login, OldRecs, Acls), + ok = mnesia:write(UpdatedAcl), + ok = mnesia:delete({?ACL_TABLE, Key}), + ?tp(emqx_acl_mnesia_migrator_record_migrated, #{key => Key}) + end. diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src new file mode 100644 index 0000000000..b15c7fdd33 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.app.src @@ -0,0 +1,14 @@ +{application, emqx_auth_mnesia, + [{description, "EMQ X Authentication with Mnesia"}, + {vsn, "4.3.4"}, % strict semver, bump manually + {modules, []}, + {registered, []}, + {applications, [kernel,stdlib,mnesia]}, + {mod, {emqx_auth_mnesia_app,[]}}, + {env, []}, + {licenses, ["Apache-2.0"]}, + {maintainers, ["EMQ X Team "]}, + {links, [{"Homepage", "https://emqx.io/"}, + {"Github", "https://github.com/emqx/emqx-auth-mnesia"} + ]} + ]}. diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.appup.src b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.appup.src new file mode 100644 index 0000000000..82df99b3a5 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.appup.src @@ -0,0 +1,31 @@ +%% -*- mode: erlang -*- +{VSN, + [ + {<<"4.3.[0-3]">>, [ + {add_module,emqx_acl_mnesia_db}, + {add_module,emqx_acl_mnesia_migrator, [emqx_acl_mnesia_db]}, + {update, emqx_auth_mnesia_sup, supervisor}, + {apply, {emqx_acl_mnesia_migrator, start_supervised, []}}, + {load_module,emqx_auth_mnesia_api, brutal_purge,soft_purge,[]}, + {load_module,emqx_acl_mnesia, brutal_purge,soft_purge,[]}, + {load_module,emqx_acl_mnesia_api, brutal_purge,soft_purge,[]}, + {load_module,emqx_acl_mnesia_cli, brutal_purge,soft_purge,[]} + ]}, + {<<".*">>, [ + ]} + ], + [ + {<<"4.3.[0-3]">>, [ + {apply, {emqx_acl_mnesia_migrator, stop_supervised, []}}, + {update, emqx_auth_mnesia_sup, supervisor}, + {load_module,emqx_acl_mnesia_cli, brutal_purge,soft_purge,[]}, + {load_module,emqx_acl_mnesia_api, brutal_purge,soft_purge,[]}, + {load_module,emqx_auth_mnesia_api, brutal_purge,soft_purge,[]}, + {load_module,emqx_acl_mnesia, brutal_purge,soft_purge,[]}, + {delete_module,emqx_acl_mnesia_migrator}, + {delete_module,emqx_acl_mnesia_db} + ]}, + {<<".*">>, [ + ]} + ] +}. diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.erl b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.erl new file mode 100644 index 0000000000..905bcaaf04 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia.erl @@ -0,0 +1,109 @@ +%%-------------------------------------------------------------------- +%% 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_auth_mnesia). + +-include("emqx_auth_mnesia.hrl"). + +-include_lib("emqx/include/emqx.hrl"). +-include_lib("emqx/include/logger.hrl"). +-include_lib("emqx/include/types.hrl"). + +-include_lib("stdlib/include/ms_transform.hrl"). + +-define(TABLE, emqx_user). +%% Auth callbacks +-export([ init/1 + , register_metrics/0 + , check/3 + , description/0 + ]). + +init(#{clientid_list := ClientidList, username_list := UsernameList}) -> + ok = ekka_mnesia:create_table(?TABLE, [ + {disc_copies, [node()]}, + {attributes, record_info(fields, emqx_user)}, + {storage_properties, [{ets, [{read_concurrency, true}]}]}]), + _ = [ add_default_user({{clientid, iolist_to_binary(Clientid)}, iolist_to_binary(Password)}) + || {Clientid, Password} <- ClientidList], + _ = [ add_default_user({{username, iolist_to_binary(Username)}, iolist_to_binary(Password)}) + || {Username, Password} <- UsernameList], + ok = ekka_mnesia:copy_table(?TABLE, disc_copies). + +%% @private +add_default_user({Login, Password}) when is_tuple(Login) -> + emqx_auth_mnesia_cli:add_user(Login, Password). + +-spec(register_metrics() -> ok). +register_metrics() -> + lists:foreach(fun emqx_metrics:ensure/1, ?AUTH_METRICS). + +check(ClientInfo = #{ clientid := Clientid + , password := NPassword + }, AuthResult, #{hash_type := HashType}) -> + Username = maps:get(username, ClientInfo, undefined), + MatchSpec = ets:fun2ms(fun({?TABLE, {clientid, X}, Password, InterTime}) when X =:= Clientid-> Password; + ({?TABLE, {username, X}, Password, InterTime}) when X =:= Username andalso X =/= undefined -> Password + end), + case ets:select(?TABLE, MatchSpec) of + [] -> + emqx_metrics:inc(?AUTH_METRICS(ignore)), + ok; + List -> + case match_password(NPassword, HashType, List) of + false -> + ?LOG(error, "[Mnesia] Auth from mnesia failed: ~p", [ClientInfo]), + emqx_metrics:inc(?AUTH_METRICS(failure)), + {stop, AuthResult#{anonymous => false, auth_result => password_error}}; + _ -> + emqx_metrics:inc(?AUTH_METRICS(success)), + {stop, AuthResult#{anonymous => false, auth_result => success}} + end + end. + +description() -> "Authentication with Mnesia". + +match_password(Password, HashType, HashList) -> + lists:any( + fun(Secret) -> + case is_salt_hash(Secret, HashType) of + true -> + <> = Secret, + Hash =:= hash(Password, Salt, HashType); + _ -> + Secret =:= hash(Password, HashType) + end + end, HashList). + +hash(undefined, HashType) -> + hash(<<>>, HashType); +hash(Password, HashType) -> + emqx_passwd:hash(HashType, Password). + +hash(undefined, SaltBin, HashType) -> + hash(<<>>, SaltBin, HashType); +hash(Password, SaltBin, HashType) -> + emqx_passwd:hash(HashType, <>). + +is_salt_hash(_, plain) -> + true; +is_salt_hash(Secret, HashType) -> + not (byte_size(Secret) == len(HashType)). + +len(md5) -> 32; +len(sha) -> 40; +len(sha256) -> 64; +len(sha512) -> 128. diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_api.erl b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_api.erl new file mode 100644 index 0000000000..da24ddd536 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_api.erl @@ -0,0 +1,294 @@ +%%-------------------------------------------------------------------- +%% 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_auth_mnesia_api). + +-include_lib("stdlib/include/qlc.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). + +-define(TABLE, emqx_user). + +-import(proplists, [get_value/2]). +-import(minirest, [return/1]). +-export([paginate_qh/5]). + +-export([ list_clientid/2 + , lookup_clientid/2 + , add_clientid/2 + , update_clientid/2 + , delete_clientid/2 + ]). + +-rest_api(#{name => list_clientid, + method => 'GET', + path => "/auth_clientid", + func => list_clientid, + descr => "List available clientid in the cluster" + }). + +-rest_api(#{name => lookup_clientid, + method => 'GET', + path => "/auth_clientid/:bin:clientid", + func => lookup_clientid, + descr => "Lookup clientid in the cluster" + }). + +-rest_api(#{name => add_clientid, + method => 'POST', + path => "/auth_clientid", + func => add_clientid, + descr => "Add clientid in the cluster" + }). + +-rest_api(#{name => update_clientid, + method => 'PUT', + path => "/auth_clientid/:bin:clientid", + func => update_clientid, + descr => "Update clientid in the cluster" + }). + +-rest_api(#{name => delete_clientid, + method => 'DELETE', + path => "/auth_clientid/:bin:clientid", + func => delete_clientid, + descr => "Delete clientid in the cluster" + }). + +-export([ list_username/2 + , lookup_username/2 + , add_username/2 + , update_username/2 + , delete_username/2 + ]). + +-rest_api(#{name => list_username, + method => 'GET', + path => "/auth_username", + func => list_username, + descr => "List available username in the cluster" + }). + +-rest_api(#{name => lookup_username, + method => 'GET', + path => "/auth_username/:bin:username", + func => lookup_username, + descr => "Lookup username in the cluster" + }). + +-rest_api(#{name => add_username, + method => 'POST', + path => "/auth_username", + func => add_username, + descr => "Add username in the cluster" + }). + +-rest_api(#{name => update_username, + method => 'PUT', + path => "/auth_username/:bin:username", + func => update_username, + descr => "Update username in the cluster" + }). + +-rest_api(#{name => delete_username, + method => 'DELETE', + path => "/auth_username/:bin:username", + func => delete_username, + descr => "Delete username in the cluster" + }). + +%%------------------------------------------------------------------------------ +%% Auth Clientid Api +%%------------------------------------------------------------------------------ + +list_clientid(_Bindings, Params) -> + MatchSpec = ets:fun2ms(fun({?TABLE, {clientid, Clientid}, Password, CreatedAt}) -> {?TABLE, {clientid, Clientid}, Password, CreatedAt} end), + return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {clientid, X}, _, _}) -> #{clientid => X} end)}). + +lookup_clientid(#{clientid := Clientid}, _Params) -> + return({ok, format(emqx_auth_mnesia_cli:lookup_user({clientid, urldecode(Clientid)}))}). + +add_clientid(_Bindings, Params) -> + [ P | _] = Params, + case is_list(P) of + true -> return(do_add_clientid(Params, [])); + false -> + Re = do_add_clientid(Params), + case Re of + ok -> return(ok); + {error, Error} -> return({error, format_msg(Error)}) + end + end. + +do_add_clientid([ Params | ParamsN ], ReList ) -> + Clientid = get_value(<<"clientid">>, Params), + do_add_clientid(ParamsN, [{Clientid, format_msg(do_add_clientid(Params))} | ReList]); + +do_add_clientid([], ReList) -> + {ok, ReList}. + +do_add_clientid(Params) -> + Clientid = get_value(<<"clientid">>, Params), + Password = get_value(<<"password">>, Params), + Login = {clientid, Clientid}, + case validate([login, password], [Login, Password]) of + ok -> + emqx_auth_mnesia_cli:add_user(Login, Password); + Err -> Err + end. + +update_clientid(#{clientid := Clientid}, Params) -> + Password = get_value(<<"password">>, Params), + case validate([password], [Password]) of + ok -> return(emqx_auth_mnesia_cli:update_user({clientid, urldecode(Clientid)}, Password)); + Err -> return(Err) + end. + +delete_clientid(#{clientid := Clientid}, _) -> + return(emqx_auth_mnesia_cli:remove_user({clientid, urldecode(Clientid)})). + +%%------------------------------------------------------------------------------ +%% Auth Username Api +%%------------------------------------------------------------------------------ + +list_username(_Bindings, Params) -> + MatchSpec = ets:fun2ms(fun({?TABLE, {username, Username}, Password, CreatedAt}) -> {?TABLE, {username, Username}, Password, CreatedAt} end), + return({ok, paginate(?TABLE, MatchSpec, Params, fun emqx_auth_mnesia_cli:comparing/2, fun({?TABLE, {username, X}, _, _}) -> #{username => X} end)}). + +lookup_username(#{username := Username}, _Params) -> + return({ok, format(emqx_auth_mnesia_cli:lookup_user({username, urldecode(Username)}))}). + +add_username(_Bindings, Params) -> + [ P | _] = Params, + case is_list(P) of + true -> return(do_add_username(Params, [])); + false -> + case do_add_username(Params) of + ok -> return(ok); + {error, Error} -> return({error, format_msg(Error)}) + end + end. + +do_add_username([ Params | ParamsN ], ReList ) -> + Username = get_value(<<"username">>, Params), + do_add_username(ParamsN, [{Username, format_msg(do_add_username(Params))} | ReList]); + +do_add_username([], ReList) -> + {ok, ReList}. + +do_add_username(Params) -> + Username = get_value(<<"username">>, Params), + Password = get_value(<<"password">>, Params), + Login = {username, Username}, + case validate([login, password], [Login, Password]) of + ok -> + emqx_auth_mnesia_cli:add_user(Login, Password); + Err -> Err + end. + +update_username(#{username := Username}, Params) -> + Password = get_value(<<"password">>, Params), + case validate([password], [Password]) of + ok -> return(emqx_auth_mnesia_cli:update_user({username, urldecode(Username)}, Password)); + Err -> return(Err) + end. + +delete_username(#{username := Username}, _) -> + return(emqx_auth_mnesia_cli:remove_user({username, urldecode(Username)})). + +%%------------------------------------------------------------------------------ +%% Paging Query +%%------------------------------------------------------------------------------ + +paginate(Table, MatchSpec, Params, ComparingFun, RowFun) -> + Qh = query_handle(Table, MatchSpec), + Count = count(Table, MatchSpec), + paginate_qh(Qh, Count, Params, ComparingFun, RowFun). + +paginate_qh(Qh, Count, Params, ComparingFun, RowFun) -> + Page = page(Params), + Limit = limit(Params), + Cursor = qlc:cursor(Qh), + case Page > 1 of + true -> + _ = qlc:next_answers(Cursor, (Page - 1) * Limit), + ok; + false -> ok + end, + Rows = qlc:next_answers(Cursor, Limit), + qlc:delete_cursor(Cursor), + #{meta => #{page => Page, limit => Limit, count => Count}, + data => [RowFun(Row) || Row <- lists:sort(ComparingFun, Rows)]}. + +query_handle(Table, MatchSpec) when is_atom(Table) -> + Options = {traverse, {select, MatchSpec}}, + qlc:q([R || R <- ets:table(Table, Options)]). + +count(Table, MatchSpec) when is_atom(Table) -> + [{MatchPattern, Where, _Re}] = MatchSpec, + NMatchSpec = [{MatchPattern, Where, [true]}], + ets:select_count(Table, NMatchSpec). + +page(Params) -> + binary_to_integer(proplists:get_value(<<"_page">>, Params, <<"1">>)). + +limit(Params) -> + case proplists:get_value(<<"_limit">>, Params) of + undefined -> 10; + Size -> binary_to_integer(Size) + end. + +%%------------------------------------------------------------------------------ +%% Interval Funcs +%%------------------------------------------------------------------------------ + +format([{?TABLE, {clientid, ClientId}, _Password, _InterTime}]) -> + #{clientid => ClientId}; + +format([{?TABLE, {username, Username}, _Password, _InterTime}]) -> + #{username => Username}; + +format([]) -> + #{}. + +validate([], []) -> + ok; +validate([K|Keys], [V|Values]) -> + case do_validation(K, V) of + false -> {error, K}; + true -> validate(Keys, Values) + end. + +do_validation(login, {clientid, V}) when is_binary(V) + andalso byte_size(V) > 0 -> + true; +do_validation(login, {username, V}) when is_binary(V) + andalso byte_size(V) > 0 -> + true; +do_validation(password, V) when is_binary(V) + andalso byte_size(V) > 0 -> + true; +do_validation(_, _) -> + false. + +format_msg(Message) + when is_atom(Message); + is_binary(Message) -> Message; + +format_msg(Message) when is_tuple(Message) -> + iolist_to_binary(io_lib:format("~p", [Message])). + +urldecode(S) -> + emqx_http_lib:uri_decode(S). diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl new file mode 100644 index 0000000000..09de5640d5 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_app.erl @@ -0,0 +1,68 @@ +%%-------------------------------------------------------------------- +%% 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_auth_mnesia_app). + +-behaviour(application). + +-emqx_plugin(auth). + +-include("emqx_auth_mnesia.hrl"). + +%% Application callbacks +-export([ start/2 + , prep_stop/1 + , stop/1 + ]). + +%%-------------------------------------------------------------------- +%% Application callbacks +%%-------------------------------------------------------------------- + +start(_StartType, _StartArgs) -> + {ok, Sup} = emqx_auth_mnesia_sup:start_link(), + emqx_ctl:register_command(clientid, {emqx_auth_mnesia_cli, auth_clientid_cli}, []), + emqx_ctl:register_command(user, {emqx_auth_mnesia_cli, auth_username_cli}, []), + emqx_ctl:register_command(acl, {emqx_acl_mnesia_cli, cli}, []), + _ = load_auth_hook(), + _ = load_acl_hook(), + {ok, Sup}. + +prep_stop(State) -> + emqx:unhook('client.authenticate', fun emqx_auth_mnesia:check/3), + emqx:unhook('client.check_acl', fun emqx_acl_mnesia:check_acl/5), + emqx_ctl:unregister_command(clientid), + emqx_ctl:unregister_command(user), + emqx_ctl:unregister_command(acl), + State. + +stop(_State) -> + ok. + +load_auth_hook() -> + ClientidList = application:get_env(?APP, clientid_list, []), + UsernameList = application:get_env(?APP, username_list, []), + ok = emqx_auth_mnesia:init(#{clientid_list => ClientidList, username_list => UsernameList}), + ok = emqx_auth_mnesia:register_metrics(), + Params = #{ + hash_type => application:get_env(emqx_auth_mnesia, password_hash, sha256) + }, + emqx:hook('client.authenticate', fun emqx_auth_mnesia:check/3, [Params]). + +load_acl_hook() -> + ok = emqx_acl_mnesia:init(), + ok = emqx_acl_mnesia:register_metrics(), + emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{}]). diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl new file mode 100644 index 0000000000..d89e6836c3 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_cli.erl @@ -0,0 +1,194 @@ +%%-------------------------------------------------------------------- +%% 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_auth_mnesia_cli). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("emqx/include/logger.hrl"). +-include_lib("stdlib/include/ms_transform.hrl"). +-define(TABLE, emqx_user). +%% Auth APIs +-export([ add_user/2 + , update_user/2 + , remove_user/1 + , lookup_user/1 + , all_users/0 + , all_users/1 + ]). +%% Cli +-export([ auth_clientid_cli/1 + , auth_username_cli/1 + ]). + +%% Helper +-export([comparing/2]). + +%%-------------------------------------------------------------------- +%% Auth APIs +%%-------------------------------------------------------------------- + +%% @doc Add User +-spec(add_user(tuple(), binary()) -> ok | {error, any()}). +add_user(Login, Password) -> + User = #emqx_user{ + login = Login, + password = encrypted_data(Password), + created_at = erlang:system_time(millisecond) + }, + ret(mnesia:transaction(fun insert_user/1, [User])). + +insert_user(User = #emqx_user{login = Login}) -> + case mnesia:read(?TABLE, Login) of + [] -> mnesia:write(User); + [_|_] -> mnesia:abort(existed) + end. + +%% @doc Update User +-spec(update_user(tuple(), binary()) -> ok | {error, any()}). +update_user(Login, NewPassword) -> + ret(mnesia:transaction(fun do_update_user/2, [Login, encrypted_data(NewPassword)])). + +do_update_user(Login, NewPassword) -> + case mnesia:read(?TABLE, Login) of + [#emqx_user{} = User] -> + mnesia:write(User#emqx_user{password = NewPassword}); + [] -> mnesia:abort(noexisted) + end. + +%% @doc Lookup user by login +-spec(lookup_user(tuple()) -> list()). +lookup_user(undefined) -> []; +lookup_user(Login) -> + Re = mnesia:dirty_read(?TABLE, Login), + lists:sort(fun comparing/2, Re). + +%% @doc Remove user +-spec(remove_user(tuple()) -> ok | {error, any()}). +remove_user(Login) -> + ret(mnesia:transaction(fun mnesia:delete/1, [{?TABLE, Login}])). + +%% @doc All logins +-spec(all_users() -> list()). +all_users() -> mnesia:dirty_all_keys(?TABLE). + +all_users(clientid) -> + MatchSpec = ets:fun2ms( + fun({?TABLE, {clientid, Clientid}, Password, CreatedAt}) -> + {?TABLE, {clientid, Clientid}, Password, CreatedAt} + end), + lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec)); +all_users(username) -> + MatchSpec = ets:fun2ms( + fun({?TABLE, {username, Username}, Password, CreatedAt}) -> + {?TABLE, {username, Username}, Password, CreatedAt} + end), + lists:sort(fun comparing/2, ets:select(?TABLE, MatchSpec)). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +comparing({?TABLE, _, _, CreatedAt1}, + {?TABLE, _, _, CreatedAt2}) -> + CreatedAt1 >= CreatedAt2. + +ret({atomic, ok}) -> ok; +ret({aborted, Error}) -> {error, Error}. + +encrypted_data(Password) -> + HashType = application:get_env(emqx_auth_mnesia, password_hash, sha256), + SaltBin = salt(), + <>. + +hash(undefined, SaltBin, HashType) -> + hash(<<>>, SaltBin, HashType); +hash(Password, SaltBin, HashType) -> + emqx_passwd:hash(HashType, <>). + +salt() -> + {_AlgHandler, _AlgState} = rand:seed(exsplus, erlang:timestamp()), + Salt = rand:uniform(16#ffffffff), <>. + +%%-------------------------------------------------------------------- +%% Auth Clientid Cli +%%-------------------------------------------------------------------- + +auth_clientid_cli(["list"]) -> + [emqx_ctl:print("~s~n", [ClientId]) + || {?TABLE, {clientid, ClientId}, _Password, _CreatedAt} <- all_users(clientid) + ]; + +auth_clientid_cli(["add", ClientId, Password]) -> + case add_user({clientid, iolist_to_binary(ClientId)}, iolist_to_binary(Password)) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +auth_clientid_cli(["update", ClientId, NewPassword]) -> + case update_user({clientid, iolist_to_binary(ClientId)}, iolist_to_binary(NewPassword)) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +auth_clientid_cli(["del", ClientId]) -> + auth_clientid_cli(["delete", ClientId]); + +auth_clientid_cli(["delete", ClientId]) -> + case remove_user({clientid, iolist_to_binary(ClientId)}) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +auth_clientid_cli(_) -> + emqx_ctl:usage([{"clientid list", "List clientid auth rules"}, + {"clientid add ", "Add clientid auth rule"}, + {"clientid update ", "Update clientid auth rule"}, + {"clientid delete ", "Delete clientid auth rule"}]). + +%%-------------------------------------------------------------------- +%% Auth Username Cli +%%-------------------------------------------------------------------- + +auth_username_cli(["list"]) -> + [emqx_ctl:print("~s~n", [Username]) + || {?TABLE, {username, Username}, _Password, _CreatedAt} <- all_users(username) + ]; + +auth_username_cli(["add", Username, Password]) -> + case add_user({username, iolist_to_binary(Username)}, iolist_to_binary(Password)) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +auth_username_cli(["update", Username, NewPassword]) -> + case update_user({username, iolist_to_binary(Username)}, iolist_to_binary(NewPassword)) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; +auth_username_cli(["del", Username]) -> + auth_username_cli(["delete", Username]); + +auth_username_cli(["delete", Username]) -> + case remove_user({username, iolist_to_binary(Username)}) of + ok -> emqx_ctl:print("ok~n"); + {error, Reason} -> emqx_ctl:print("Error: ~p~n", [Reason]) + end; + +auth_username_cli(_) -> + emqx_ctl:usage([{"user list", "List username auth rules"}, + {"user add ", "Add username auth rule"}, + {"user update ", "Update username auth rule"}, + {"user delete ", "Delete username auth rule"}]). diff --git a/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl new file mode 100644 index 0000000000..2099eba8c1 --- /dev/null +++ b/apps/emqx_auth_mnesia/src/emqx_auth_mnesia_sup.erl @@ -0,0 +1,48 @@ +%%-------------------------------------------------------------------- +%% 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_auth_mnesia_sup). + +-behaviour(supervisor). + +-include("emqx_auth_mnesia.hrl"). + +-export([start_link/0]). + +%% Supervisor callbacks +-export([init/1]). + +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%%-------------------------------------------------------------------- +%% Supervisor callbacks +%%-------------------------------------------------------------------- + +init([]) -> + {ok, {{one_for_one, 10, 100}, [ + child_spec(emqx_acl_mnesia_migrator, worker, []) + ]}}. + +child_spec(M, worker, Args) -> + #{id => M, + start => {M, start_link, Args}, + restart => permanent, + shutdown => 5000, + type => worker, + modules => [M] + }. + diff --git a/apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl new file mode 100644 index 0000000000..eb1ea74f3f --- /dev/null +++ b/apps/emqx_auth_mnesia/test/emqx_acl_mnesia_SUITE.erl @@ -0,0 +1,451 @@ +%%-------------------------------------------------------------------- +%% 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_acl_mnesia_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). + +-import(emqx_ct_http, [ request_api/3 + , request_api/5 + , get_http_data/1 + , create_default_app/0 + , delete_default_app/0 + , default_auth_header/0 + ]). + +-define(HOST, "http://127.0.0.1:8081/"). +-define(API_VERSION, "v4"). +-define(BASE_PATH, "api"). + +all() -> + emqx_ct:all(?MODULE). + +groups() -> + [{async_migration_tests, [sequence], [ + t_old_and_new_acl_migration_by_migrator, + t_old_and_new_acl_migration_repeated_by_migrator, + t_migration_concurrency + ]}]. + +init_per_suite(Config) -> + emqx_ct_helpers:start_apps([emqx_modules, emqx_management, emqx_auth_mnesia], fun set_special_configs/1), + supervisor:terminate_child(emqx_auth_mnesia_sup, emqx_acl_mnesia_migrator), + create_default_app(), + Config. + +end_per_suite(_Config) -> + delete_default_app(), + emqx_ct_helpers:stop_apps([emqx_modules, emqx_management, emqx_auth_mnesia]). + +init_per_testcase_clean(_, Config) -> + mnesia:clear_table(?ACL_TABLE), + mnesia:clear_table(?ACL_TABLE2), + Config. + +init_per_testcase_emqx_hook(t_check_acl_as_clientid, Config) -> + emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{key_as => clientid}]), + Config; +init_per_testcase_emqx_hook(_, Config) -> + emqx:hook('client.check_acl', fun emqx_acl_mnesia:check_acl/5, [#{key_as => username}]), + Config. + +init_per_testcase_migration(t_management_before_migration, Config) -> + Config; +init_per_testcase_migration(_, Config) -> + emqx_acl_mnesia_migrator:migrate_records(), + Config. + +init_per_testcase(Case, Config) -> + PerTestInitializers = [ + fun init_per_testcase_clean/2, + fun init_per_testcase_migration/2, + fun init_per_testcase_emqx_hook/2 + ], + lists:foldl(fun(Init, Conf) -> Init(Case, Conf) end, Config, PerTestInitializers). + +end_per_testcase(_, Config) -> + emqx:unhook('client.check_acl', fun emqx_acl_mnesia:check_acl/5), + Config. + +set_special_configs(emqx) -> + application:set_env(emqx, allow_anonymous, true), + application:set_env(emqx, enable_acl_cache, false), + LoadedPluginPath = filename:join(["test", "emqx_SUITE_data", "loaded_plugins"]), + application:set_env(emqx, plugins_loaded_file, + emqx_ct_helpers:deps_path(emqx, LoadedPluginPath)); + +set_special_configs(_App) -> + ok. + +%%------------------------------------------------------------------------------ +%% Testcases +%%------------------------------------------------------------------------------ + +t_management_before_migration(_Config) -> + {atomic, IsStarted} = mnesia:transaction(fun emqx_acl_mnesia_db:is_migration_started/0), + ?assertNot(IsStarted), + run_acl_tests(). + +t_management_after_migration(_Config) -> + {atomic, IsStarted} = mnesia:transaction(fun emqx_acl_mnesia_db:is_migration_started/0), + ?assert(IsStarted), + run_acl_tests(). + +run_acl_tests() -> + ?assertEqual("Acl with Mnesia", emqx_acl_mnesia:description()), + ?assertEqual([], emqx_acl_mnesia_db:all_acls()), + + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>, sub, allow), + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/+">>, pub, deny), + ok = emqx_acl_mnesia_db:add_acl({username, <<"test_username">>}, <<"topic/%u">>, sub, deny), + ok = emqx_acl_mnesia_db:add_acl({username, <<"test_username">>}, <<"topic/+">>, pub, allow), + ok = emqx_acl_mnesia_db:add_acl(all, <<"#">>, pubsub, deny), + %% Sleeps below are needed to hide the race condition between + %% mnesia and ets dirty select in check_acl, that make this test + %% flaky + timer:sleep(100), + + ?assertEqual(2, length(emqx_acl_mnesia_db:lookup_acl({clientid, <<"test_clientid">>}))), + ?assertEqual(2, length(emqx_acl_mnesia_db:lookup_acl({username, <<"test_username">>}))), + ?assertEqual(2, length(emqx_acl_mnesia_db:lookup_acl(all))), + ?assertEqual(6, length(emqx_acl_mnesia_db:all_acls())), + + User1 = #{zone => external, clientid => <<"test_clientid">>}, + User2 = #{zone => external, clientid => <<"no_exist">>, username => <<"test_username">>}, + User3 = #{zone => external, clientid => <<"test_clientid">>, username => <<"test_username">>}, + allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/test_clientid">>), + deny = emqx_access_control:check_acl(User1, publish, <<"topic/A">>), + deny = emqx_access_control:check_acl(User2, subscribe, <<"topic/test_username">>), + allow = emqx_access_control:check_acl(User2, publish, <<"topic/A">>), + allow = emqx_access_control:check_acl(User3, subscribe, <<"topic/test_clientid">>), + deny = emqx_access_control:check_acl(User3, subscribe, <<"topic/test_username">>), + deny = emqx_access_control:check_acl(User3, publish, <<"topic/A">>), + deny = emqx_access_control:check_acl(User3, subscribe, <<"topic/A/B">>), + deny = emqx_access_control:check_acl(User3, publish, <<"topic/A/B">>), + + %% Test merging of pubsub capability: + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, deny), + timer:sleep(100), + deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, allow), + timer:sleep(100), + deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pubsub, allow), + timer:sleep(100), + allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny), + timer:sleep(100), + deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny), + timer:sleep(100), + deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + + %% Test implicit migration of pubsub to pub and sub: + ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>), + ok = mnesia:dirty_write(#?ACL_TABLE{ + filter = {{clientid, <<"test_clientid">>}, <<"topic/mix">>}, + action = pubsub, + access = allow, + created_at = erlang:system_time(millisecond) + }), + timer:sleep(100), + allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + allow = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, pub, deny), + timer:sleep(100), + allow = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>, sub, deny), + timer:sleep(100), + deny = emqx_access_control:check_acl(User1, subscribe, <<"topic/mix">>), + deny = emqx_access_control:check_acl(User1, publish, <<"topic/mix">>), + + ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>), + ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/+">>), + ok = emqx_acl_mnesia_db:remove_acl({clientid, <<"test_clientid">>}, <<"topic/mix">>), + ok = emqx_acl_mnesia_db:remove_acl({username, <<"test_username">>}, <<"topic/%u">>), + ok = emqx_acl_mnesia_db:remove_acl({username, <<"test_username">>}, <<"topic/+">>), + ok = emqx_acl_mnesia_db:remove_acl(all, <<"#">>), + timer:sleep(100), + + ?assertEqual([], emqx_acl_mnesia_db:all_acls()). + +t_old_and_new_acl_combination(_Config) -> + create_conflicting_records(), + + ?assertEqual(combined_conflicting_records(), emqx_acl_mnesia_db:all_acls()), + ?assertEqual( + lists:usort(combined_conflicting_records()), + lists:usort(emqx_acl_mnesia_db:all_acls_export())). + +t_old_and_new_acl_migration(_Config) -> + create_conflicting_records(), + emqx_acl_mnesia_migrator:migrate_records(), + + ?assertEqual(combined_conflicting_records(), emqx_acl_mnesia_db:all_acls()), + ?assertEqual( + lists:usort(combined_conflicting_records()), + lists:usort(emqx_acl_mnesia_db:all_acls_export())), + + % check that old table is not popoulated anymore + ok = emqx_acl_mnesia_db:add_acl({clientid, <<"test_clientid">>}, <<"topic/%c">>, sub, allow), + ?assert(emqx_acl_mnesia_migrator:is_old_table_migrated()). + + +t_migration_concurrency(_Config) -> + Key = {{clientid,<<"client6">>}, <<"t">>}, + Record = #?ACL_TABLE{filter = Key, action = pubsub, access = deny, created_at = 0}, + {atomic, ok} = mnesia:transaction(fun mnesia:write/1, [Record]), + + LockWaitAndDelete = + fun() -> + [_Rec] = mnesia:wread({?ACL_TABLE, Key}), + {{Pid, Ref}, _} = + ?wait_async_action(spawn_monitor(fun emqx_acl_mnesia_migrator:migrate_records/0), + #{?snk_kind := emqx_acl_mnesia_migrator_record_selected}, + 1000), + mnesia:delete({?ACL_TABLE, Key}), + {Pid, Ref} + end, + + ?check_trace( + begin + {atomic, {Pid, Ref}} = mnesia:transaction(LockWaitAndDelete), + receive {'DOWN', Ref, process, Pid, _} -> ok end + end, + fun(_, Trace) -> + ?assertMatch([_], ?of_kind(emqx_acl_mnesia_migrator_record_missed, Trace)) + end), + + ?assert(emqx_acl_mnesia_migrator:is_old_table_migrated()), + ?assertEqual([], emqx_acl_mnesia_db:all_acls()). + + +t_old_and_new_acl_migration_by_migrator(_Config) -> + create_conflicting_records(), + + meck:new(fake_nodes, [non_strict]), + meck:expect(fake_nodes, all, fun() -> [node(), 'somebadnode@127.0.0.1'] end), + + ?check_trace( + begin + % check all nodes every 30 ms + {ok, _} = emqx_acl_mnesia_migrator:start_link(#{ + name => ct_migrator, + check_nodes_interval => 30, + get_nodes => fun fake_nodes:all/0 + }), + timer:sleep(100) + end, + fun(_, Trace) -> + ?assertEqual([], ?of_kind(emqx_acl_mnesia_migrator_start_migration, Trace)) + end), + + ?check_trace( + begin + meck:expect(fake_nodes, all, fun() -> [node()] end), + timer:sleep(100) + end, + fun(_, Trace) -> + ?assertMatch([_], ?of_kind(emqx_acl_mnesia_migrator_finish, Trace)) + end), + + meck:unload(fake_nodes), + + ?assertEqual(combined_conflicting_records(), emqx_acl_mnesia_db:all_acls()), + ?assert(emqx_acl_mnesia_migrator:is_old_table_migrated()). + +t_old_and_new_acl_migration_repeated_by_migrator(_Config) -> + create_conflicting_records(), + emqx_acl_mnesia_migrator:migrate_records(), + + ?check_trace( + begin + {ok, _} = emqx_acl_mnesia_migrator:start_link(ct_migrator), + timer:sleep(100) + end, + fun(_, Trace) -> + ?assertEqual([], ?of_kind(emqx_acl_mnesia_migrator_start_migration, Trace)), + ?assertMatch([_], ?of_kind(emqx_acl_mnesia_migrator_finish, Trace)) + end). + +t_start_stop_supervised(_Config) -> + ?assertEqual(undefined, whereis(emqx_acl_mnesia_migrator)), + ok = emqx_acl_mnesia_migrator:start_supervised(), + ?assert(is_pid(whereis(emqx_acl_mnesia_migrator))), + ok = emqx_acl_mnesia_migrator:stop_supervised(), + ?assertEqual(undefined, whereis(emqx_acl_mnesia_migrator)). + +t_acl_cli(_Config) -> + meck:new(emqx_ctl, [non_strict, passthrough]), + meck:expect(emqx_ctl, print, fun(Arg) -> emqx_ctl:format(Arg) end), + meck:expect(emqx_ctl, print, fun(Msg, Arg) -> emqx_ctl:format(Msg, Arg) end), + meck:expect(emqx_ctl, usage, fun(Usages) -> emqx_ctl:format_usage(Usages) end), + meck:expect(emqx_ctl, usage, fun(Cmd, Descr) -> emqx_ctl:format_usage(Cmd, Descr) end), + + ?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))), + + emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "deny"]), + emqx_acl_mnesia_cli:cli(["add", "clientid", "test_clientid", "topic/A", "pub", "allow"]), + R1 = emqx_ctl:format("Acl(clientid = ~p topic = ~p action = ~p access = ~p)~n", + [<<"test_clientid">>, <<"topic/A">>, pub, allow]), + ?assertEqual([R1], emqx_acl_mnesia_cli:cli(["show", "clientid", "test_clientid"])), + ?assertEqual([R1], emqx_acl_mnesia_cli:cli(["list", "clientid"])), + + emqx_acl_mnesia_cli:cli(["add", "username", "test_username", "topic/B", "sub", "deny"]), + R2 = emqx_ctl:format("Acl(username = ~p topic = ~p action = ~p access = ~p)~n", + [<<"test_username">>, <<"topic/B">>, sub, deny]), + ?assertEqual([R2], emqx_acl_mnesia_cli:cli(["show", "username", "test_username"])), + ?assertEqual([R2], emqx_acl_mnesia_cli:cli(["list", "username"])), + + emqx_acl_mnesia_cli:cli(["add", "_all", "#", "pub", "allow"]), + emqx_acl_mnesia_cli:cli(["add", "_all", "#", "pubsub", "deny"]), + ?assertMatch(["", + "Acl($all topic = <<\"#\">> action = pub access = deny)", + "Acl($all topic = <<\"#\">> action = sub access = deny)"], + lists:sort(string:split(emqx_acl_mnesia_cli:cli(["list", "_all"]), "\n", all)) + ), + ?assertEqual(4, length(emqx_acl_mnesia_cli:cli(["list"]))), + + emqx_acl_mnesia_cli:cli(["del", "clientid", "test_clientid", "topic/A"]), + emqx_acl_mnesia_cli:cli(["del", "username", "test_username", "topic/B"]), + emqx_acl_mnesia_cli:cli(["del", "_all", "#"]), + ?assertEqual(0, length(emqx_acl_mnesia_cli:cli(["list"]))), + + meck:unload(emqx_ctl). + +t_rest_api(_Config) -> + Params1 = [#{<<"clientid">> => <<"test_clientid">>, + <<"topic">> => <<"topic/A">>, + <<"action">> => <<"pub">>, + <<"access">> => <<"allow">> + }, + #{<<"clientid">> => <<"test_clientid">>, + <<"topic">> => <<"topic/B">>, + <<"action">> => <<"sub">>, + <<"access">> => <<"allow">> + }, + #{<<"clientid">> => <<"test_clientid">>, + <<"topic">> => <<"topic/C">>, + <<"action">> => <<"pubsub">>, + <<"access">> => <<"deny">> + }], + {ok, _} = request_http_rest_add([], Params1), + {ok, Re1} = request_http_rest_list(["clientid", "test_clientid"]), + ?assertMatch(4, length(get_http_data(Re1))), + {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/A"]), + {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/B"]), + {ok, _} = request_http_rest_delete(["clientid", "test_clientid", "topic", "topic/C"]), + {ok, Res1} = request_http_rest_list(["clientid"]), + ?assertMatch([], get_http_data(Res1)), + + Params2 = [#{<<"username">> => <<"test_username">>, + <<"topic">> => <<"topic/A">>, + <<"action">> => <<"pub">>, + <<"access">> => <<"allow">> + }, + #{<<"username">> => <<"test_username">>, + <<"topic">> => <<"topic/B">>, + <<"action">> => <<"sub">>, + <<"access">> => <<"allow">> + }, + #{<<"username">> => <<"test_username">>, + <<"topic">> => <<"topic/C">>, + <<"action">> => <<"pubsub">>, + <<"access">> => <<"deny">> + }], + {ok, _} = request_http_rest_add([], Params2), + {ok, Re2} = request_http_rest_list(["username", "test_username"]), + ?assertMatch(4, length(get_http_data(Re2))), + {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/A"]), + {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/B"]), + {ok, _} = request_http_rest_delete(["username", "test_username", "topic", "topic/C"]), + {ok, Res2} = request_http_rest_list(["username"]), + ?assertMatch([], get_http_data(Res2)), + + Params3 = [#{<<"topic">> => <<"topic/A">>, + <<"action">> => <<"pub">>, + <<"access">> => <<"allow">> + }, + #{<<"topic">> => <<"topic/B">>, + <<"action">> => <<"sub">>, + <<"access">> => <<"allow">> + }, + #{<<"topic">> => <<"topic/C">>, + <<"action">> => <<"pubsub">>, + <<"access">> => <<"deny">> + }], + {ok, _} = request_http_rest_add([], Params3), + {ok, Re3} = request_http_rest_list(["$all"]), + ?assertMatch(4, length(get_http_data(Re3))), + {ok, _} = request_http_rest_delete(["$all", "topic", "topic/A"]), + {ok, _} = request_http_rest_delete(["$all", "topic", "topic/B"]), + {ok, _} = request_http_rest_delete(["$all", "topic", "topic/C"]), + {ok, Res3} = request_http_rest_list(["$all"]), + ?assertMatch([], get_http_data(Res3)). + + +create_conflicting_records() -> + Records = [ + #?ACL_TABLE{filter = {{clientid,<<"client6">>}, <<"t">>}, action = pubsub, access = deny, created_at = 0}, + #?ACL_TABLE{filter = {{clientid,<<"client5">>}, <<"t">>}, action = pubsub, access = deny, created_at = 1}, + #?ACL_TABLE2{who = {clientid,<<"client5">>}, rules = [{allow, sub, <<"t">>, 2}]} + ], + mnesia:transaction(fun() -> lists:foreach(fun mnesia:write/1, Records) end). + + +combined_conflicting_records() -> + % pubsub's are split, ACL_TABLE2 rules shadow ACL_TABLE rules + [ + {{clientid,<<"client5">>},<<"t">>,sub,allow,2}, + {{clientid,<<"client5">>},<<"t">>,pub,deny,1}, + {{clientid,<<"client6">>},<<"t">>,sub,deny,0}, + {{clientid,<<"client6">>},<<"t">>,pub,deny,0} + ]. + +%%-------------------------------------------------------------------- +%% HTTP Request +%%-------------------------------------------------------------------- + +request_http_rest_list(Path) -> + request_api(get, uri(Path), default_auth_header()). + +request_http_rest_lookup(Path) -> + request_api(get, uri(Path), default_auth_header()). + +request_http_rest_add(Path, Params) -> + request_api(post, uri(Path), [], default_auth_header(), Params). + +request_http_rest_delete(Path) -> + request_api(delete, uri(Path), default_auth_header()). + +uri() -> uri([]). +uri(Parts) when is_list(Parts) -> + NParts = [b2l(E) || E <- Parts], + ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION, "acl"| NParts]). + +b2l(B) -> binary_to_list(emqx_http_lib:uri_encode(iolist_to_binary(B))). diff --git a/apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl b/apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl new file mode 100644 index 0000000000..c5c0eb7270 --- /dev/null +++ b/apps/emqx_auth_mnesia/test/emqx_auth_mnesia_SUITE.erl @@ -0,0 +1,318 @@ +%%-------------------------------------------------------------------- +%% 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_auth_mnesia_SUITE). + +-compile(nowarn_export_all). +-compile(export_all). + +-include("emqx_auth_mnesia.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-import(emqx_ct_http, [ request_api/3 + , request_api/5 + , get_http_data/1 + , create_default_app/0 + , delete_default_app/0 + , default_auth_header/0 + ]). + +-define(HOST, "http://127.0.0.1:8081/"). +-define(API_VERSION, "v4"). +-define(BASE_PATH, "api"). + +-define(TABLE, emqx_user). +-define(CLIENTID, <<"clientid_for_ct">>). +-define(USERNAME, <<"username_for_ct">>). +-define(PASSWORD, <<"password">>). +-define(NPASSWORD, <<"new_password">>). + +all() -> + emqx_ct:all(?MODULE). + +groups() -> + []. + +init_per_suite(Config) -> + ok = emqx_ct_helpers:start_apps([emqx_management, emqx_auth_mnesia], fun set_special_configs/1), + create_default_app(), + Config. + +end_per_suite(_Config) -> + delete_default_app(), + emqx_ct_helpers:stop_apps([emqx_management, emqx_auth_mnesia]). + +set_special_configs(emqx) -> + application:set_env(emqx, allow_anonymous, true), + application:set_env(emqx, enable_acl_cache, false), + LoadedPluginPath = filename:join(["test", "emqx_SUITE_data", "loaded_plugins"]), + application:set_env(emqx, plugins_loaded_file, + emqx_ct_helpers:deps_path(emqx, LoadedPluginPath)); + +set_special_configs(_App) -> + ok. + +%%------------------------------------------------------------------------------ +%% Testcases +%%------------------------------------------------------------------------------ + +t_management(_Config) -> + clean_all_users(), + + ok = emqx_auth_mnesia_cli:add_user({username, ?USERNAME}, ?PASSWORD), + {error, existed} = emqx_auth_mnesia_cli:add_user({username, ?USERNAME}, ?PASSWORD), + ?assertMatch([{?TABLE, {username, ?USERNAME}, _, _}], + emqx_auth_mnesia_cli:all_users(username) + ), + + ok = emqx_auth_mnesia_cli:add_user({clientid, ?CLIENTID}, ?PASSWORD), + {error, existed} = emqx_auth_mnesia_cli:add_user({clientid, ?CLIENTID}, ?PASSWORD), + ?assertMatch([{?TABLE, {clientid, ?CLIENTID}, _, _}], + emqx_auth_mnesia_cli:all_users(clientid) + ), + + ?assertEqual(2, length(emqx_auth_mnesia_cli:all_users())), + + ok = emqx_auth_mnesia_cli:update_user({username, ?USERNAME}, ?NPASSWORD), + {error, noexisted} = emqx_auth_mnesia_cli:update_user( + {username, <<"no_existed_user">>}, ?PASSWORD + ), + + ok = emqx_auth_mnesia_cli:update_user({clientid, ?CLIENTID}, ?NPASSWORD), + {error, noexisted} = emqx_auth_mnesia_cli:update_user( + {clientid, <<"no_existed_user">>}, ?PASSWORD + ), + + ?assertMatch([{?TABLE, {username, ?USERNAME}, _, _}], + emqx_auth_mnesia_cli:lookup_user({username, ?USERNAME}) + ), + ?assertMatch([{?TABLE, {clientid, ?CLIENTID}, _, _}], + emqx_auth_mnesia_cli:lookup_user({clientid, ?CLIENTID}) + ), + + User1 = #{username => ?USERNAME, + clientid => undefined, + password => ?NPASSWORD, + zone => external}, + + {ok, #{auth_result := success, + anonymous := false}} = emqx_access_control:authenticate(User1), + + {error, password_error} = emqx_access_control:authenticate( + User1#{password => <<"error_password">>} + ), + + ok = emqx_auth_mnesia_cli:remove_user({username, ?USERNAME}), + {ok, #{auth_result := success, + anonymous := true }} = emqx_access_control:authenticate(User1), + + User2 = #{clientid => ?CLIENTID, + password => ?NPASSWORD, + zone => external}, + + {ok, #{auth_result := success, + anonymous := false}} = emqx_access_control:authenticate(User2), + + {error, password_error} = emqx_access_control:authenticate( + User2#{password => <<"error_password">>} + ), + + ok = emqx_auth_mnesia_cli:remove_user({clientid, ?CLIENTID}), + {ok, #{auth_result := success, + anonymous := true }} = emqx_access_control:authenticate(User2), + + [] = emqx_auth_mnesia_cli:all_users(). + +t_auth_clientid_cli(_) -> + clean_all_users(), + + HashType = application:get_env(emqx_auth_mnesia, password_hash, sha256), + + emqx_auth_mnesia_cli:auth_clientid_cli(["add", ?CLIENTID, ?PASSWORD]), + [{_, {clientid, ?CLIENTID}, + <>, + _}] = emqx_auth_mnesia_cli:lookup_user({clientid, ?CLIENTID}), + ?assertEqual(Hash, emqx_passwd:hash(HashType, <>)), + + emqx_auth_mnesia_cli:auth_clientid_cli(["update", ?CLIENTID, ?NPASSWORD]), + [{_, {clientid, ?CLIENTID}, + <>, + _}] = emqx_auth_mnesia_cli:lookup_user({clientid, ?CLIENTID}), + ?assertEqual(Hash1, emqx_passwd:hash(HashType, <>)), + + emqx_auth_mnesia_cli:auth_clientid_cli(["del", ?CLIENTID]), + ?assertEqual([], emqx_auth_mnesia_cli:lookup_user(?CLIENTID)), + + emqx_auth_mnesia_cli:auth_clientid_cli(["add", "user1", "pass1"]), + emqx_auth_mnesia_cli:auth_clientid_cli(["add", "user2", "pass2"]), + ?assertEqual(2, length(emqx_auth_mnesia_cli:auth_clientid_cli(["list"]))), + + emqx_auth_mnesia_cli:auth_clientid_cli(usage). + +t_auth_username_cli(_) -> + clean_all_users(), + + HashType = application:get_env(emqx_auth_mnesia, password_hash, sha256), + + emqx_auth_mnesia_cli:auth_username_cli(["add", ?USERNAME, ?PASSWORD]), + [{_, {username, ?USERNAME}, + <>, + _}] = emqx_auth_mnesia_cli:lookup_user({username, ?USERNAME}), + ?assertEqual(Hash, emqx_passwd:hash(HashType, <>)), + + emqx_auth_mnesia_cli:auth_username_cli(["update", ?USERNAME, ?NPASSWORD]), + [{_, {username, ?USERNAME}, + <>, + _}] = emqx_auth_mnesia_cli:lookup_user({username, ?USERNAME}), + ?assertEqual(Hash1, emqx_passwd:hash(HashType, <>)), + + emqx_auth_mnesia_cli:auth_username_cli(["del", ?USERNAME]), + ?assertEqual([], emqx_auth_mnesia_cli:lookup_user(?USERNAME)), + + emqx_auth_mnesia_cli:auth_username_cli(["add", "user1", "pass1"]), + emqx_auth_mnesia_cli:auth_username_cli(["add", "user2", "pass2"]), + ?assertEqual(2, length(emqx_auth_mnesia_cli:auth_username_cli(["list"]))), + + emqx_auth_mnesia_cli:auth_username_cli(usage). + + +t_clientid_rest_api(_Config) -> + clean_all_users(), + + {ok, Result1} = request_http_rest_list(["auth_clientid"]), + [] = get_http_data(Result1), + + Params1 = #{<<"clientid">> => ?CLIENTID, <<"password">> => ?PASSWORD}, + {ok, _} = request_http_rest_add(["auth_clientid"], Params1), + + Path = ["auth_clientid/" ++ binary_to_list(?CLIENTID)], + Params2 = #{<<"clientid">> => ?CLIENTID, <<"password">> => ?NPASSWORD}, + {ok, _} = request_http_rest_update(Path, Params2), + + {ok, Result2} = request_http_rest_lookup(Path), + ?assertMatch(#{<<"clientid">> := ?CLIENTID}, get_http_data(Result2)), + + Params3 = [ #{<<"clientid">> => ?CLIENTID, <<"password">> => ?PASSWORD} + , #{<<"clientid">> => <<"clientid1">>, <<"password">> => ?PASSWORD} + , #{<<"clientid">> => <<"clientid2">>, <<"password">> => ?PASSWORD} + ], + {ok, Result3} = request_http_rest_add(["auth_clientid"], Params3), + ?assertMatch(#{ ?CLIENTID := <<"{error,existed}">> + , <<"clientid1">> := <<"ok">> + , <<"clientid2">> := <<"ok">> + }, get_http_data(Result3)), + + {ok, Result4} = request_http_rest_list(["auth_clientid"]), + ?assertEqual(3, length(get_http_data(Result4))), + + {ok, _} = request_http_rest_delete(Path), + {ok, Result5} = request_http_rest_lookup(Path), + ?assertMatch(#{}, get_http_data(Result5)). + +t_username_rest_api(_Config) -> + clean_all_users(), + + {ok, Result1} = request_http_rest_list(["auth_username"]), + [] = get_http_data(Result1), + + Params1 = #{<<"username">> => ?USERNAME, <<"password">> => ?PASSWORD}, + {ok, _} = request_http_rest_add(["auth_username"], Params1), + + Path = ["auth_username/" ++ binary_to_list(?USERNAME)], + Params2 = #{<<"username">> => ?USERNAME, <<"password">> => ?NPASSWORD}, + {ok, _} = request_http_rest_update(Path, Params2), + + {ok, Result2} = request_http_rest_lookup(Path), + ?assertMatch(#{<<"username">> := ?USERNAME}, get_http_data(Result2)), + + Params3 = [ #{<<"username">> => ?USERNAME, <<"password">> => ?PASSWORD} + , #{<<"username">> => <<"username1">>, <<"password">> => ?PASSWORD} + , #{<<"username">> => <<"username2">>, <<"password">> => ?PASSWORD} + ], + {ok, Result3} = request_http_rest_add(["auth_username"], Params3), + ?assertMatch(#{ ?USERNAME := <<"{error,existed}">> + , <<"username1">> := <<"ok">> + , <<"username2">> := <<"ok">> + }, get_http_data(Result3)), + + {ok, Result4} = request_http_rest_list(["auth_username"]), + ?assertEqual(3, length(get_http_data(Result4))), + + {ok, _} = request_http_rest_delete(Path), + {ok, Result5} = request_http_rest_lookup([Path]), + ?assertMatch(#{}, get_http_data(Result5)). + +t_password_hash(_) -> + clean_all_users(), + {ok, Default} = application:get_env(emqx_auth_mnesia, password_hash), + application:set_env(emqx_auth_mnesia, password_hash, plain), + + %% change the password_hash to 'plain' + application:stop(emqx_auth_mnesia), + ok = application:start(emqx_auth_mnesia), + + Params = #{<<"username">> => ?USERNAME, <<"password">> => ?PASSWORD}, + {ok, _} = request_http_rest_add(["auth_username"], Params), + + %% check + User = #{username => ?USERNAME, + clientid => undefined, + password => ?PASSWORD, + zone => external}, + {ok, #{auth_result := success, + anonymous := false}} = emqx_access_control:authenticate(User), + + application:set_env(emqx_auth_mnesia, password_hash, Default), + application:stop(emqx_auth_mnesia), + ok = application:start(emqx_auth_mnesia). + +%%------------------------------------------------------------------------------ +%% Helpers +%%------------------------------------------------------------------------------ + +clean_all_users() -> + [ mnesia:dirty_delete({emqx_user, Login}) + || Login <- mnesia:dirty_all_keys(emqx_user)]. + +%%-------------------------------------------------------------------- +%% HTTP Request +%%-------------------------------------------------------------------- + +request_http_rest_list(Path) -> + request_api(get, uri(Path), default_auth_header()). + +request_http_rest_lookup(Path) -> + request_api(get, uri([Path]), default_auth_header()). + +request_http_rest_add(Path, Params) -> + request_api(post, uri(Path), [], default_auth_header(), Params). + +request_http_rest_update(Path, Params) -> + request_api(put, uri([Path]), [], default_auth_header(), Params). + +request_http_rest_delete(Login) -> + request_api(delete, uri([Login]), default_auth_header()). + +uri() -> uri([]). +uri(Parts) when is_list(Parts) -> + NParts = [b2l(E) || E <- Parts], + ?HOST ++ filename:join([?BASE_PATH, ?API_VERSION | NParts]). + +%% @private +b2l(B) when is_binary(B) -> + binary_to_list(B); +b2l(L) when is_list(L) -> + L. diff --git a/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src index 9ffd2b7f71..650c9d7fa7 100644 --- a/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src +++ b/apps/emqx_bridge_mqtt/src/emqx_bridge_mqtt.app.src @@ -1,6 +1,6 @@ {application, emqx_bridge_mqtt, [{description, "EMQ X Bridge to MQTT Broker"}, - {vsn, "4.3.0"}, % strict semver, bump manually! + {vsn, "4.3.2"}, % strict semver, bump manually! {modules, []}, {registered, []}, {applications, [kernel,stdlib,replayq,emqtt]}, @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-bridge-mqtt"} + {"Github", "https://github.com/emqx/emqx-bridge-mqtt"} ]} ]}. diff --git a/apps/emqx_bridge_mqtt/test/emqx_bridge_worker_SUITE.erl b/apps/emqx_bridge_mqtt/test/emqx_bridge_worker_SUITE.erl index 680756742a..5c6061819b 100644 --- a/apps/emqx_bridge_mqtt/test/emqx_bridge_worker_SUITE.erl +++ b/apps/emqx_bridge_mqtt/test/emqx_bridge_worker_SUITE.erl @@ -27,7 +27,7 @@ -define(wait(For, Timeout), emqx_ct_helpers:wait_for(?FUNCTION_NAME, ?LINE, fun() -> For end, Timeout)). --define(SNK_WAIT(WHAT), ?assertMatch({ok, _}, ?block_until(#{?snk_kind := WHAT}, 2000, 1000))). +-define(SNK_WAIT(WHAT), ?assertMatch({ok, _}, ?block_until(#{?snk_kind := WHAT}, 5000, 5000))). receive_messages(Count) -> receive_messages(Count, []). diff --git a/apps/emqx_coap/intergration_test/Makefile b/apps/emqx_coap/intergration_test/Makefile index 1705f48e7b..14018740c3 100644 --- a/apps/emqx_coap/intergration_test/Makefile +++ b/apps/emqx_coap/intergration_test/Makefile @@ -7,11 +7,11 @@ all: clean_result $(RELX_CONF) $(LIBCOAP_GIT) start_broker clean_result case1 @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 @@ -51,7 +51,7 @@ case3: 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: @@ -89,20 +89,20 @@ $(RELX_CONF): 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 + 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 @@ -116,14 +116,14 @@ rebuild_emq: cp -rf ../src emqx-rel/deps/emqx_coap/ cp -rf ../Makefile emqx-rel/deps/emqx_coap/Makefile make -C emqx-rel -f Makefile - + clean: clean_result -rm -f client/*.exe -rm -f client/*.o -rm -rf emqx-rel -rm -rf libcoap - + lazy: clean_result start_broker case2 stop_broker # custom your command here @echo "you are so lazy" - + diff --git a/apps/emqx_coap/src/emqx_coap.app.src b/apps/emqx_coap/src/emqx_coap.app.src index 4f4c3804e1..2b5fcbb6a2 100644 --- a/apps/emqx_coap/src/emqx_coap.app.src +++ b/apps/emqx_coap/src/emqx_coap.app.src @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-coap"} + {"Github", "https://github.com/emqx/emqx-coap"} ]} ]}. diff --git a/apps/emqx_exhook/rebar.config b/apps/emqx_exhook/rebar.config index 9c0a98251d..c3f06bb3a1 100644 --- a/apps/emqx_exhook/rebar.config +++ b/apps/emqx_exhook/rebar.config @@ -5,7 +5,7 @@ ]}. {deps, - [{grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {tag, "0.6.3"}}} + [{grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {tag, "0.6.4"}}} ]}. {grpc, diff --git a/apps/emqx_exhook/src/emqx_exhook.app.src b/apps/emqx_exhook/src/emqx_exhook.app.src index 39c408e203..46223d2128 100644 --- a/apps/emqx_exhook/src/emqx_exhook.app.src +++ b/apps/emqx_exhook/src/emqx_exhook.app.src @@ -1,6 +1,6 @@ {application, emqx_exhook, [{description, "EMQ X Extension for Hook"}, - {vsn, "4.3.3"}, + {vsn, "4.3.4"}, {modules, []}, {registered, []}, {mod, {emqx_exhook_app, []}}, diff --git a/apps/emqx_exhook/src/emqx_exhook.appup.src b/apps/emqx_exhook/src/emqx_exhook.appup.src index 1da31dbbc9..d6a699c33d 100644 --- a/apps/emqx_exhook/src/emqx_exhook.appup.src +++ b/apps/emqx_exhook/src/emqx_exhook.appup.src @@ -1,32 +1,14 @@ %% -*-: erlang -*- {VSN, [ - {"4.3.2", [ - {load_module, emqx_exhook_app, brutal_purge, soft_purge, []} - ]}, - {"4.3.1", [ - {load_module, emqx_exhook_app, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} - ]}, - {"4.3.0", [ - {load_module, emqx_exhook_app, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_pb, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} + {<<"4.3.[0-3]">>, [ + {restart_application, emqx_exhook} ]}, {<<".*">>, []} ], [ - {"4.3.2", [ - {load_module, emqx_exhook_app, brutal_purge, soft_purge, []} - ]}, - {"4.3.1", [ - {load_module, emqx_exhook_app, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} - ]}, - {"4.3.0", [ - {load_module, emqx_exhook_app, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_pb, brutal_purge, soft_purge, []}, - {load_module, emqx_exhook_server, brutal_purge, soft_purge, []} + {<<"4.3.[0-3]">>, [ + {restart_application, emqx_exhook} ]}, {<<".*">>, []} ] diff --git a/apps/emqx_exhook/src/emqx_exhook.erl b/apps/emqx_exhook/src/emqx_exhook.erl index 032d7f91a7..b27710e4ae 100644 --- a/apps/emqx_exhook/src/emqx_exhook.erl +++ b/apps/emqx_exhook/src/emqx_exhook.erl @@ -21,10 +21,8 @@ -logger_header("[ExHook]"). -%% Mgmt APIs --export([ enable/2 +-export([ enable/1 , disable/1 - , disable_all/0 , list/0 ]). @@ -36,101 +34,85 @@ %% Mgmt APIs %%-------------------------------------------------------------------- -%% XXX: Only return the running servers --spec list() -> [emqx_exhook_server:server()]. -list() -> - [server(Name) || Name <- running()]. - --spec enable(atom()|string(), list()) -> ok | {error, term()}. -enable(Name, Opts) -> - case lists:member(Name, running()) of - true -> - {error, already_started}; - _ -> - case emqx_exhook_server:load(Name, Opts) of - {ok, ServiceState} -> - save(Name, ServiceState); - {error, Reason} -> - ?LOG(error, "Load server ~p failed: ~p", [Name, Reason]), - {error, Reason} - end - end. +-spec enable(atom()|string()) -> ok | {error, term()}. +enable(Name) -> + with_mngr(fun(Pid) -> emqx_exhook_mngr:enable(Pid, Name) end). -spec disable(atom()|string()) -> ok | {error, term()}. disable(Name) -> - case server(Name) of - undefined -> {error, not_running}; - Service -> - ok = emqx_exhook_server:unload(Service), - unsave(Name) - end. + with_mngr(fun(Pid) -> emqx_exhook_mngr:disable(Pid, Name) end). --spec disable_all() -> ok. -disable_all() -> - lists:foreach(fun disable/1, running()). +-spec list() -> [atom() | string()]. +list() -> + with_mngr(fun(Pid) -> emqx_exhook_mngr:list(Pid) end). -%%---------------------------------------------------------- +with_mngr(Fun) -> + case lists:keyfind(emqx_exhook_mngr, 1, + supervisor:which_children(emqx_exhook_sup)) of + {_, Pid, _, _} -> + Fun(Pid); + _ -> + {error, no_manager_svr} + end. + +%%-------------------------------------------------------------------- %% Dispatch APIs -%%---------------------------------------------------------- +%%-------------------------------------------------------------------- -spec cast(atom(), map()) -> ok. cast(Hookpoint, Req) -> - cast(Hookpoint, Req, running()). + cast(Hookpoint, Req, emqx_exhook_mngr:running()). cast(_, _, []) -> ok; -cast(Hookpoint, Req, [ServiceName|More]) -> +cast(Hookpoint, Req, [ServerName|More]) -> %% XXX: Need a real asynchronous running - _ = emqx_exhook_server:call(Hookpoint, Req, server(ServiceName)), + _ = emqx_exhook_server:call(Hookpoint, Req, + emqx_exhook_mngr:server(ServerName)), cast(Hookpoint, Req, More). -spec call_fold(atom(), term(), function()) -> {ok, term()} | {stop, term()}. call_fold(Hookpoint, Req, AccFun) -> - call_fold(Hookpoint, Req, AccFun, running()). + FailedAction = emqx_exhook_mngr:get_request_failed_action(), + ServerNames = emqx_exhook_mngr:running(), + case ServerNames == [] andalso FailedAction == deny of + true -> + {stop, deny_action_result(Hookpoint, Req)}; + _ -> + call_fold(Hookpoint, Req, FailedAction, AccFun, ServerNames) + end. -call_fold(_, Req, _, []) -> +call_fold(_, Req, _, _, []) -> {ok, Req}; -call_fold(Hookpoint, Req, AccFun, [ServiceName|More]) -> - case emqx_exhook_server:call(Hookpoint, Req, server(ServiceName)) of +call_fold(Hookpoint, Req, FailedAction, AccFun, [ServerName|More]) -> + Server = emqx_exhook_mngr:server(ServerName), + case emqx_exhook_server:call(Hookpoint, Req, Server) of {ok, Resp} -> case AccFun(Req, Resp) of - {stop, NReq} -> {stop, NReq}; - {ok, NReq} -> call_fold(Hookpoint, NReq, AccFun, More); - _ -> call_fold(Hookpoint, Req, AccFun, More) + {stop, NReq} -> + {stop, NReq}; + {ok, NReq} -> + call_fold(Hookpoint, NReq, FailedAction, AccFun, More); + _ -> + call_fold(Hookpoint, Req, FailedAction, AccFun, More) end; _ -> - call_fold(Hookpoint, Req, AccFun, More) + case FailedAction of + deny -> + {stop, deny_action_result(Hookpoint, Req)}; + _ -> + call_fold(Hookpoint, Req, FailedAction, AccFun, More) + end end. -%%---------------------------------------------------------- -%% Storage - --compile({inline, [save/2]}). -save(Name, ServiceState) -> - Saved = persistent_term:get(?APP, []), - persistent_term:put(?APP, lists:reverse([Name | Saved])), - persistent_term:put({?APP, Name}, ServiceState). - --compile({inline, [unsave/1]}). -unsave(Name) -> - case persistent_term:get(?APP, []) of - [] -> - persistent_term:erase(?APP); - Saved -> - persistent_term:put(?APP, lists:delete(Name, Saved)) - end, - persistent_term:erase({?APP, Name}), - ok. - --compile({inline, [running/0]}). -running() -> - persistent_term:get(?APP, []). - --compile({inline, [server/1]}). -server(Name) -> - case catch persistent_term:get({?APP, Name}) of - {'EXIT', {badarg,_}} -> undefined; - Service -> Service - end. +%% XXX: Hard-coded the deny response +deny_action_result('client.authenticate', _) -> + #{result => false}; +deny_action_result('client.check_acl', _) -> + #{result => false}; +deny_action_result('message.publish', Msg) -> + %% TODO: Not support to deny a message + %% maybe we can put the 'allow_publish' into message header + Msg. diff --git a/apps/emqx_exhook/src/emqx_exhook_app.erl b/apps/emqx_exhook/src/emqx_exhook_app.erl index d2c630a9b5..c1fcdc6ab1 100644 --- a/apps/emqx_exhook/src/emqx_exhook_app.erl +++ b/apps/emqx_exhook/src/emqx_exhook_app.erl @@ -22,22 +22,10 @@ -emqx_plugin(extension). --define(CNTER, emqx_exhook_counter). - --export([start/2 - , stop/1 - , prep_stop/1 -]). - -%% Internal export --export([ - load_all_servers/0 - , load_server/2 - , unload_all_servers/0 - , unload_server/1 - , unload_exhooks/0 - , init_hooks_cnter/0 -]). +-export([ start/2 + , stop/1 + , prep_stop/1 + ]). %%-------------------------------------------------------------------- %% Application callbacks @@ -45,53 +33,12 @@ start(_StartType, _StartArgs) -> {ok, Sup} = emqx_exhook_sup:start_link(), - - %% Init counter - init_hooks_cnter(), - - %% Load all dirvers -%% load_all_servers(), - - %% Register CLI emqx_ctl:register_command(exhook, {emqx_exhook_cli, cli}, []), {ok, Sup}. prep_stop(State) -> emqx_ctl:unregister_command(exhook), - _ = unload_exhooks(), - ok = unload_all_servers(), State. stop(_State) -> ok. - -%%-------------------------------------------------------------------- -%% Internal funcs -%%-------------------------------------------------------------------- - -load_all_servers() -> - lists:foreach(fun({Name, Options}) -> - load_server(Name, Options) - end, application:get_env(?APP, servers, [])). - -unload_all_servers() -> - emqx_exhook:disable_all(). - -load_server(Name, Options) -> - emqx_exhook:enable(Name, Options). - -unload_server(Name) -> - emqx_exhook:disable(Name). - -unload_exhooks() -> - [emqx:unhook(Name, {M, F}) || - {Name, {M, F, _A}} <- ?ENABLED_HOOKS]. - -init_hooks_cnter() -> - try - _ = ets:new(?CNTER, [named_table, public]), ok - catch - error:badarg:_ -> - ok - end. - diff --git a/apps/emqx_exhook/src/emqx_exhook_cli.erl b/apps/emqx_exhook/src/emqx_exhook_cli.erl index a8dc43b163..804d05a2d8 100644 --- a/apps/emqx_exhook/src/emqx_exhook_cli.erl +++ b/apps/emqx_exhook/src/emqx_exhook_cli.erl @@ -22,25 +22,18 @@ cli(["server", "list"]) -> if_enabled(fun() -> - Services = emqx_exhook:list(), - [emqx_ctl:print("HookServer(~s)~n", - [emqx_exhook_server:format(Service)]) || Service <- Services] + ServerNames = emqx_exhook:list(), + [emqx_ctl:print("Server(~s)~n", [format(Name)]) || Name <- ServerNames] end); -cli(["server", "enable", Name0]) -> +cli(["server", "enable", Name]) -> if_enabled(fun() -> - Name = list_to_atom(Name0), - case proplists:get_value(Name, application:get_env(?APP, servers, [])) of - undefined -> - emqx_ctl:print("not_found~n"); - Opts -> - print(emqx_exhook:enable(Name, Opts)) - end + print(emqx_exhook:enable(list_to_existing_atom(Name))) end); cli(["server", "disable", Name]) -> if_enabled(fun() -> - print(emqx_exhook:disable(list_to_atom(Name))) + print(emqx_exhook:disable(list_to_existing_atom(Name))) end); cli(["server", "stats"]) -> @@ -65,7 +58,8 @@ print({error, Reason}) -> if_enabled(Fun) -> case lists:keymember(?APP, 1, application:which_applications()) of - true -> Fun(); + true -> + Fun(); _ -> hint() end. @@ -79,3 +73,11 @@ stats() -> _ -> Acc end end, [], emqx_metrics:all())). + +format(Name) -> + case emqx_exhook_mngr:server(Name) of + undefined -> + io_lib:format("name=~s, hooks=#{}, active=false", [Name]); + Server -> + emqx_exhook_server:format(Server) + end. diff --git a/apps/emqx_exhook/src/emqx_exhook_handler.erl b/apps/emqx_exhook/src/emqx_exhook_handler.erl index 74d854efe4..f3964dc423 100644 --- a/apps/emqx_exhook/src/emqx_exhook_handler.erl +++ b/apps/emqx_exhook/src/emqx_exhook_handler.erl @@ -192,8 +192,7 @@ on_message_publish(Message) -> fun emqx_exhook_handler:merge_responsed_message/2) of {StopOrOk, #{message := NMessage}} -> {StopOrOk, assign_to_message(NMessage, Message)}; - _ -> - {ok, Message} + _ -> {ok, Message} end. on_message_dropped(#message{topic = <<"$SYS/", _/binary>>}, _By, _Reason) -> @@ -303,9 +302,7 @@ merge_responsed_bool(Req, #{type := Type, value := {bool_result, NewBool}}) NReq = Req#{result => NewBool}, case Type of 'CONTINUE' -> {ok, NReq}; - 'STOP_AND_RETURN' -> - ?LOG(error,"NReq ~p",[NReq]), - {stop, NReq} + 'STOP_AND_RETURN' -> {stop, NReq} end; merge_responsed_bool(_Req, Resp) -> ?LOG(warning, "Unknown responsed value ~0p to merge to callback chain", [Resp]), @@ -317,9 +314,7 @@ merge_responsed_message(Req, #{type := Type, value := {message, NMessage}}) -> NReq = Req#{message => NMessage}, case Type of 'CONTINUE' -> {ok, NReq}; - 'STOP_AND_RETURN' -> - ?LOG(error,"NReq ~p",[NReq]), - {stop, NReq} + 'STOP_AND_RETURN' -> {stop, NReq} end; merge_responsed_message(_Req, Resp) -> ?LOG(warning, "Unknown responsed value ~0p to merge to callback chain", [Resp]), diff --git a/apps/emqx_exhook/src/emqx_exhook_mngr.erl b/apps/emqx_exhook/src/emqx_exhook_mngr.erl new file mode 100644 index 0000000000..cadd5eb373 --- /dev/null +++ b/apps/emqx_exhook/src/emqx_exhook_mngr.erl @@ -0,0 +1,311 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- + +%% @doc Manage the server status and reload strategy +-module(emqx_exhook_mngr). + +-behaviour(gen_server). + +-include("emqx_exhook.hrl"). +-include_lib("emqx/include/logger.hrl"). + +%% APIs +-export([start_link/3]). + +%% Mgmt API +-export([ enable/2 + , disable/2 + , list/1 + ]). + +%% Helper funcs +-export([ running/0 + , server/1 + , put_request_failed_action/1 + , get_request_failed_action/0 + ]). + +%% gen_server callbacks +-export([ init/1 + , handle_call/3 + , handle_cast/2 + , handle_info/2 + , terminate/2 + , code_change/3 + ]). + +-record(state, { + %% Running servers + running :: map(), %% XXX: server order? + %% Wait to reload servers + waiting :: map(), + %% Marked stopped servers + stopped :: map(), + %% Auto reconnect timer interval + auto_reconnect :: false | non_neg_integer(), + %% Request options + request_options :: grpc_client:options(), + %% Timer references + trefs :: map() + }). + +-type servers() :: [{Name :: atom(), server_options()}]. + +-type server_options() :: [ {scheme, http | https} + | {host, string()} + | {port, inet:port_number()} + ]. + +-define(DEFAULT_TIMEOUT, 60000). + +-define(CNTER, emqx_exhook_counter). + +%%-------------------------------------------------------------------- +%% APIs +%%-------------------------------------------------------------------- + +-spec start_link(servers(), false | non_neg_integer(), grpc_client:options()) + ->ignore + | {ok, pid()} + | {error, any()}. +start_link(Servers, AutoReconnect, ReqOpts) -> + gen_server:start_link(?MODULE, [Servers, AutoReconnect, ReqOpts], []). + +-spec enable(pid(), atom()|string()) -> ok | {error, term()}. +enable(Pid, Name) -> + call(Pid, {load, Name}). + +-spec disable(pid(), atom()|string()) -> ok | {error, term()}. +disable(Pid, Name) -> + call(Pid, {unload, Name}). + +list(Pid) -> + call(Pid, list). + +call(Pid, Req) -> + gen_server:call(Pid, Req, ?DEFAULT_TIMEOUT). + +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- + +init([Servers, AutoReconnect, ReqOpts0]) -> + process_flag(trap_exit, true), + %% XXX: Due to the ExHook Module in the enterprise, + %% this process may start multiple times and they will share this table + try + _ = ets:new(?CNTER, [named_table, public]), ok + catch + error:badarg:_ -> + ok + end, + + %% put the global option + put_request_failed_action( + maps:get(request_failed_action, ReqOpts0, deny) + ), + + %% Load the hook servers + ReqOpts = maps:without([request_failed_action], ReqOpts0), + {Waiting, Running} = load_all_servers(Servers, ReqOpts), + {ok, ensure_reload_timer( + #state{waiting = Waiting, + running = Running, + stopped = #{}, + request_options = ReqOpts, + auto_reconnect = AutoReconnect, + trefs = #{} + } + )}. + +%% @private +load_all_servers(Servers, ReqOpts) -> + load_all_servers(Servers, ReqOpts, #{}, #{}). +load_all_servers([], _Request, Waiting, Running) -> + {Waiting, Running}; +load_all_servers([{Name, Options}|More], ReqOpts, Waiting, Running) -> + {NWaiting, NRunning} = + case emqx_exhook_server:load(Name, Options, ReqOpts) of + {ok, ServerState} -> + save(Name, ServerState), + {Waiting, Running#{Name => Options}}; + {error, _} -> + {Waiting#{Name => Options}, Running} + end, + load_all_servers(More, ReqOpts, NWaiting, NRunning). + +handle_call({load, Name}, _From, State) -> + {Result, NState} = do_load_server(Name, State), + {reply, Result, NState}; + +handle_call({unload, Name}, _From, State) -> + case do_unload_server(Name, State) of + {error, Reason} -> + {reply, {error, Reason}, State}; + {ok, NState} -> + {reply, ok, NState} + end; + +handle_call(list, _From, State = #state{ + running = Running, + waiting = Waiting, + stopped = Stopped}) -> + ServerNames = maps:keys(Running) + ++ maps:keys(Waiting) + ++ maps:keys(Stopped), + {reply, ServerNames, State}; + +handle_call(_Request, _From, State) -> + Reply = ok, + {reply, Reply, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info({timeout, _Ref, {reload, Name}}, State) -> + {Result, NState} = do_load_server(Name, State), + case Result of + ok -> + {noreply, NState}; + {error, not_found} -> + {noreply, NState}; + {error, Reason} -> + ?LOG(warning, "Failed to reload exhook callback server \"~s\", " + "Reason: ~0p", [Name, Reason]), + {noreply, ensure_reload_timer(NState)} + end; + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, State = #state{running = Running}) -> + _ = maps:fold(fun(Name, _, AccIn) -> + {ok, NAccIn} = do_unload_server(Name, AccIn), + NAccIn + end, State, Running), + _ = unload_exhooks(), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%% Internal funcs +%%-------------------------------------------------------------------- + +unload_exhooks() -> + [emqx:unhook(Name, {M, F}) || + {Name, {M, F, _A}} <- ?ENABLED_HOOKS]. + +do_load_server(Name, State0 = #state{ + waiting = Waiting, + running = Running, + stopped = Stopped, + request_options = ReqOpts}) -> + State = clean_reload_timer(Name, State0), + case maps:get(Name, Running, undefined) of + undefined -> + case maps:get(Name, Stopped, + maps:get(Name, Waiting, undefined)) of + undefined -> + {{error, not_found}, State}; + Options -> + case emqx_exhook_server:load(Name, Options, ReqOpts) of + {ok, ServerState} -> + save(Name, ServerState), + ?LOG(info, "Load exhook callback server " + "\"~s\" successfully!", [Name]), + {ok, State#state{ + running = maps:put(Name, Options, Running), + waiting = maps:remove(Name, Waiting), + stopped = maps:remove(Name, Stopped) + } + }; + {error, Reason} -> + {{error, Reason}, State} + end + end; + _ -> + {{error, already_started}, State} + end. + +do_unload_server(Name, State = #state{running = Running, stopped = Stopped}) -> + case maps:take(Name, Running) of + error -> {error, not_running}; + {Options, NRunning} -> + ok = emqx_exhook_server:unload(server(Name)), + ok = unsave(Name), + {ok, State#state{running = NRunning, + stopped = maps:put(Name, Options, Stopped) + }} + end. + +ensure_reload_timer(State = #state{auto_reconnect = false}) -> + State; +ensure_reload_timer(State = #state{waiting = Waiting, + trefs = TRefs, + auto_reconnect = Intv}) -> + NRefs = maps:fold(fun(Name, _, AccIn) -> + case maps:get(Name, AccIn, undefined) of + undefined -> + Ref = erlang:start_timer(Intv, self(), {reload, Name}), + AccIn#{Name => Ref}; + _HasRef -> + AccIn + end + end, TRefs, Waiting), + State#state{trefs = NRefs}. + +clean_reload_timer(Name, State = #state{trefs = TRefs}) -> + case maps:take(Name, TRefs) of + error -> State; + {TRef, NTRefs} -> + _ = erlang:cancel_timer(TRef), + State#state{trefs = NTRefs} + end. + +%%-------------------------------------------------------------------- +%% Server state persistent + +put_request_failed_action(Val) -> + persistent_term:put({?APP, request_failed_action}, Val). + +get_request_failed_action() -> + persistent_term:get({?APP, request_failed_action}). + +save(Name, ServerState) -> + Saved = persistent_term:get(?APP, []), + persistent_term:put(?APP, lists:reverse([Name | Saved])), + persistent_term:put({?APP, Name}, ServerState). + +unsave(Name) -> + case persistent_term:get(?APP, []) of + [] -> + persistent_term:erase(?APP); + Saved -> + persistent_term:put(?APP, lists:delete(Name, Saved)) + end, + persistent_term:erase({?APP, Name}), + ok. + +running() -> + persistent_term:get(?APP, []). + +server(Name) -> + case catch persistent_term:get({?APP, Name}) of + {'EXIT', {badarg,_}} -> undefined; + Service -> Service + end. diff --git a/apps/emqx_exhook/src/emqx_exhook_server.erl b/apps/emqx_exhook/src/emqx_exhook_server.erl index b96bf85dbc..7df5b643cb 100644 --- a/apps/emqx_exhook/src/emqx_exhook_server.erl +++ b/apps/emqx_exhook/src/emqx_exhook_server.erl @@ -25,7 +25,7 @@ -define(PB_CLIENT_MOD, emqx_exhook_v_1_hook_provider_client). %% Load/Unload --export([ load/2 +-export([ load/3 , unload/1 ]). @@ -40,8 +40,8 @@ -record(server, { %% Server name (equal to grpc client channel name) name :: server_name(), - %% The server started options - options :: list(), + %% The function options + options :: map(), %% gRPC channel pid channel :: pid(), %% Registered hook names and options @@ -81,8 +81,8 @@ %% Load/Unload APIs %%-------------------------------------------------------------------- --spec load(atom(), list()) -> {ok, server()} | {error, term()} . -load(Name0, Opts0) -> +-spec load(atom(), list(), map()) -> {ok, server()} | {error, term()} . +load(Name0, Opts0, ReqOpts) -> Name = to_list(Name0), {SvrAddr, ClientOpts} = channel_opts(Opts0), case emqx_exhook_sup:start_grpc_client_channel( @@ -90,7 +90,7 @@ load(Name0, Opts0) -> SvrAddr, ClientOpts) of {ok, _ChannPoolPid} -> - case do_init(Name) of + case do_init(Name, ReqOpts) of {ok, HookSpecs} -> %% Reigster metrics Prefix = lists:flatten( @@ -99,7 +99,7 @@ load(Name0, Opts0) -> %% Ensure hooks ensure_hooks(HookSpecs), {ok, #server{name = Name, - options = Opts0, + options = ReqOpts, channel = _ChannPoolPid, hookspec = HookSpecs, prefix = Prefix }}; @@ -141,19 +141,19 @@ format_http_uri(Scheme, Host0, Port) -> lists:flatten(io_lib:format("~s://~s:~w", [Scheme, Host, Port])). -spec unload(server()) -> ok. -unload(#server{name = Name, hookspec = HookSpecs}) -> - _ = do_deinit(Name), +unload(#server{name = Name, options = ReqOpts, hookspec = HookSpecs}) -> + _ = do_deinit(Name, ReqOpts), _ = may_unload_hooks(HookSpecs), _ = emqx_exhook_sup:stop_grpc_client_channel(Name), ok. -do_deinit(Name) -> - _ = do_call(Name, 'on_provider_unloaded', #{}), +do_deinit(Name, ReqOpts) -> + _ = do_call(Name, 'on_provider_unloaded', #{}, ReqOpts), ok. -do_init(ChannName) -> +do_init(ChannName, ReqOpts) -> Req = #{broker => maps:from_list(emqx_sys:info())}, - case do_call(ChannName, 'on_provider_loaded', Req) of + case do_call(ChannName, 'on_provider_loaded', Req, ReqOpts) of {ok, InitialResp} -> try {ok, resovle_hookspec(maps:get(hooks, InitialResp, []))} @@ -219,7 +219,7 @@ may_unload_hooks(HookSpecs) -> end, maps:keys(HookSpecs)). format(#server{name = Name, hookspec = Hooks}) -> - io_lib:format("name=~p, hooks=~0p", [Name, Hooks]). + io_lib:format("name=~s, hooks=~0p, active=true", [Name, Hooks]). %%-------------------------------------------------------------------- %% APIs @@ -232,7 +232,8 @@ name(#server{name = Name}) -> -> ignore | {ok, Resp :: term()} | {error, term()}. -call(Hookpoint, Req, #server{name = ChannName, hookspec = Hooks, prefix = Prefix}) -> +call(Hookpoint, Req, #server{name = ChannName, options = ReqOpts, + hookspec = Hooks, prefix = Prefix}) -> GrpcFunc = hk2func(Hookpoint), case maps:get(Hookpoint, Hooks, undefined) of undefined -> ignore; @@ -247,7 +248,7 @@ call(Hookpoint, Req, #server{name = ChannName, hookspec = Hooks, prefix = Prefix false -> ignore; _ -> inc_metrics(Prefix, Hookpoint), - do_call(ChannName, GrpcFunc, Req) + do_call(ChannName, GrpcFunc, Req, ReqOpts) end end. @@ -265,13 +266,13 @@ match_topic_filter(_, []) -> match_topic_filter(TopicName, TopicFilter) -> lists:any(fun(F) -> emqx_topic:match(TopicName, F) end, TopicFilter). --spec do_call(string(), atom(), map()) -> {ok, map()} | {error, term()}. -do_call(ChannName, Fun, Req) -> - Options = #{channel => ChannName}, - ?LOG(info, "Call ~0p: ~0p ~0p(~0p, ~0p)", [?PB_CLIENT_MOD, ChannName, Fun, Req, Options]), +-spec do_call(string(), atom(), map(), map()) -> {ok, map()} | {error, term()}. +do_call(ChannName, Fun, Req, ReqOpts) -> + Options = ReqOpts#{channel => ChannName}, + ?LOG(debug, "Call ~0p:~0p(~0p, ~0p)", [?PB_CLIENT_MOD, Fun, Req, Options]), case catch apply(?PB_CLIENT_MOD, Fun, [Req, Options]) of {ok, Resp, _Metadata} -> - ?LOG(info, "Response {ok, ~0p, ~0p}", [Resp, _Metadata]), + ?LOG(debug, "Response {ok, ~0p, ~0p}", [Resp, _Metadata]), {ok, Resp}; {error, {Code, Msg}, _Metadata} -> ?LOG(error, "CALL ~0p:~0p(~0p, ~0p) response errcode: ~0p, errmsg: ~0p", diff --git a/apps/emqx_exhook/src/emqx_exhook_sup.erl b/apps/emqx_exhook/src/emqx_exhook_sup.erl index fab3e9d701..e9c405de0d 100644 --- a/apps/emqx_exhook/src/emqx_exhook_sup.erl +++ b/apps/emqx_exhook/src/emqx_exhook_sup.erl @@ -15,7 +15,7 @@ %%-------------------------------------------------------------------- -module(emqx_exhook_sup). --include_lib("emqx/include/logger.hrl"). + -behaviour(supervisor). -export([ start_link/0 @@ -26,6 +26,14 @@ , stop_grpc_client_channel/1 ]). +-define(CHILD(Mod, Type, Args), + #{ id => Mod + , start => {Mod, start_link, Args} + , type => Type + , shutdown => 15000 + } + ). + %%-------------------------------------------------------------------- %% Supervisor APIs & Callbacks %%-------------------------------------------------------------------- @@ -34,7 +42,23 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - {ok, {{one_for_one, 10, 100}, []}}. + Mngr = ?CHILD(emqx_exhook_mngr, worker, + [servers(), auto_reconnect(), request_options()]), + {ok, {{one_for_one, 10, 100}, [Mngr]}}. + +servers() -> + env(servers, []). + +auto_reconnect() -> + env(auto_reconnect, 60000). + +request_options() -> + #{timeout => env(request_timeout, 5000), + request_failed_action => env(request_failed_action, deny) + }. + +env(Key, Def) -> + application:get_env(emqx_exhook, Key, Def). %%-------------------------------------------------------------------- %% APIs @@ -45,7 +69,6 @@ init([]) -> uri_string:uri_string(), grpc_client:options()) -> {ok, pid()} | {error, term()}. start_grpc_client_channel(Name, SvrAddr, Options) -> - ?LOG(info,"Name ~p , SvrAddr ~p , Options ~p",[Name, SvrAddr, Options]), grpc_client_sup:create_channel_pool(Name, SvrAddr, Options). -spec stop_grpc_client_channel(string()) -> ok. diff --git a/apps/emqx_exhook/test/emqx_exhook_SUITE.erl b/apps/emqx_exhook/test/emqx_exhook_SUITE.erl index 5d5a396a50..b4c58fe625 100644 --- a/apps/emqx_exhook/test/emqx_exhook_SUITE.erl +++ b/apps/emqx_exhook/test/emqx_exhook_SUITE.erl @@ -52,17 +52,32 @@ set_special_cfgs(emqx_exhook) -> 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), + ok = emqx_exhook:enable(default), ?assertNotEqual([], ets:tab2list(emqx_hooks)). +t_access_failed_if_no_server_running(_) -> + emqx_exhook:disable(default), + ClientInfo = #{clientid => <<"user-id-1">>, + username => <<"usera">>, + peerhost => {127,0,0,1}, + sockport => 1883, + protocol => mqtt, + mountpoint => undefined + }, + ?assertMatch({stop, #{auth_result := not_authorized}}, + emqx_exhook_handler:on_client_authenticate(ClientInfo, #{auth_result => success})), + + ?assertMatch({stop, deny}, + emqx_exhook_handler:on_client_check_acl(ClientInfo, publish, <<"t/1">>, allow)), + + Message = emqx_message:make(<<"t/1">>, <<"abc">>), + ?assertMatch({stop, Message}, + emqx_exhook_handler:on_message_publish(Message)), + emqx_exhook:enable(default). + t_cli_list(_) -> meck_print(), - ?assertEqual( [[emqx_exhook_server:format(Svr) || Svr <- emqx_exhook:list()]] + ?assertEqual( [[emqx_exhook_server:format(emqx_exhook_mngr:server(Name)) || Name <- emqx_exhook:list()]] , emqx_exhook_cli:cli(["server", "list"]) ), unmeck_print(). @@ -71,7 +86,7 @@ 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([["name=default, hooks=#{}, active=false"]], 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"])), diff --git a/apps/emqx_exproto/README.md b/apps/emqx_exproto/README.md index 6792b33848..a9375e5d3e 100644 --- a/apps/emqx_exproto/README.md +++ b/apps/emqx_exproto/README.md @@ -9,7 +9,7 @@ The `emqx_exproto` extremly enhance the extensibility for EMQ X. It allow using ## Architecture -![](./docs/images/exproto-arch.jpg) +![EMQ X ExProto Arch](./docs/images/exproto-arch.jpg) ## Usage diff --git a/apps/emqx_exproto/etc/emqx_exproto.conf b/apps/emqx_exproto/etc/emqx_exproto.conf index 6a7401cc49..7136857345 100644 --- a/apps/emqx_exproto/etc/emqx_exproto.conf +++ b/apps/emqx_exproto/etc/emqx_exproto.conf @@ -54,7 +54,7 @@ exproto.listener.protoname.active_n = 100 ## Idle timeout ## ## Value: Duration -exproto.listener.protoname.idle_timeout = 180s +exproto.listener.protoname.idle_timeout = 30s ## The access control rules for the MQTT/TCP listener. ## diff --git a/apps/emqx_exproto/rebar.config b/apps/emqx_exproto/rebar.config index dfdb6ee4cc..29c55e19f9 100644 --- a/apps/emqx_exproto/rebar.config +++ b/apps/emqx_exproto/rebar.config @@ -13,7 +13,7 @@ ]}. {deps, - [{grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {tag, "0.6.3"}}} + [{grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {tag, "0.6.4"}}} ]}. {grpc, diff --git a/apps/emqx_exproto/src/emqx_exproto.app.src b/apps/emqx_exproto/src/emqx_exproto.app.src index b450bf16f9..f7cab4c2e2 100644 --- a/apps/emqx_exproto/src/emqx_exproto.app.src +++ b/apps/emqx_exproto/src/emqx_exproto.app.src @@ -1,6 +1,6 @@ {application, emqx_exproto, [{description, "EMQ X Extension for Protocol"}, - {vsn, "4.3.1"}, %% strict semver + {vsn, "4.3.4"}, %% 4.3.3 is used by ee {modules, []}, {registered, []}, {mod, {emqx_exproto_app, []}}, diff --git a/apps/emqx_exproto/src/emqx_exproto.appup.src b/apps/emqx_exproto/src/emqx_exproto.appup.src index 7055e80647..e0a021af5a 100644 --- a/apps/emqx_exproto/src/emqx_exproto.appup.src +++ b/apps/emqx_exproto/src/emqx_exproto.appup.src @@ -1,17 +1,26 @@ -%% -*-: erlang -*- +%% -*- mode: erlang -*- {VSN, - [ - {"4.3.0", [ - {load_module, emqx_exproto_conn, brutal_purge, soft_purge, []}, - {load_module, emqx_exproto_channel, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ], - [ - {"4.3.0", [ - {load_module, emqx_exproto_conn, brutal_purge, soft_purge, []}, - {load_module, emqx_exproto_channel, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ] -}. + [{"4.3.3", + [{load_module,emqx_exproto_conn,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_channel,brutal_purge,soft_purge,[]}]}, + {"4.3.2", + [{load_module,emqx_exproto_conn,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_channel,brutal_purge,soft_purge,[]}]}, + {<<"4.3.[0-1]">>, + [{load_module,emqx_exproto_gsvr,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_gcli,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_conn,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_channel,brutal_purge,soft_purge,[]}]}, + {<<".*">>,[]}], + [{"4.3.3", + [{load_module,emqx_exproto_conn,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_channel,brutal_purge,soft_purge,[]}]}, + {"4.3.2", + [{load_module,emqx_exproto_conn,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_channel,brutal_purge,soft_purge,[]}]}, + {<<"4.3.[0-1]">>, + [{load_module,emqx_exproto_gsvr,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_gcli,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_conn,brutal_purge,soft_purge,[]}, + {load_module,emqx_exproto_channel,brutal_purge,soft_purge,[]}]}, + {<<".*">>,[]}]}. diff --git a/apps/emqx_exproto/src/emqx_exproto_channel.erl b/apps/emqx_exproto/src/emqx_exproto_channel.erl index cc0d6690b7..67f85f932a 100644 --- a/apps/emqx_exproto/src/emqx_exproto_channel.erl +++ b/apps/emqx_exproto/src/emqx_exproto_channel.erl @@ -94,6 +94,9 @@ awaiting_rel_max ]). +-define(CHANMOCK(P), {exproto_anonymous_client, P}). +-define(CHAN_CONN_TAB, emqx_channel_conn). + %%-------------------------------------------------------------------- %% Info, Attrs and Caps %%-------------------------------------------------------------------- @@ -103,7 +106,7 @@ info(Channel) -> maps:from_list(info(?INFO_KEYS, Channel)). --spec(info(list(atom())|atom(), channel()) -> term()). +-spec(info(list(atom()) | atom(), channel()) -> term()). info(Keys, Channel) when is_list(Keys) -> [{Key, info(Key, Channel)} || Key <- Keys]; info(conninfo, #channel{conninfo = ConnInfo}) -> @@ -155,13 +158,19 @@ init(ConnInfo = #{socktype := Socktype, conn_state = connecting, timers = #{} }, - - Req = #{conninfo => - peercert(Peercert, - #{socktype => socktype(Socktype), - peername => address(Peername), - sockname => address(Sockname)})}, - try_dispatch(on_socket_created, wrap(Req), Channel). + case emqx_hooks:run_fold('client.connect', [NConnInfo], #{}) of + {error, _Reason} -> + throw(nopermission); + _ -> + ConnMod = maps:get(conn_mod, NConnInfo), + true = ets:insert(?CHAN_CONN_TAB, {?CHANMOCK(self()), ConnMod}), + Req = #{conninfo => + peercert(Peercert, + #{socktype => socktype(Socktype), + peername => address(Peername), + sockname => address(Sockname)})}, + try_dispatch(on_socket_created, wrap(Req), Channel) + end. %% @private peercert(NoSsl, ConnInfo) when NoSsl == nossl; @@ -243,7 +252,7 @@ handle_timeout(_TRef, {keepalive, StatVal}, end; handle_timeout(_TRef, force_close, Channel = #channel{closed_reason = Reason}) -> - {shutdown, {error, {force_close, Reason}}, Channel}; + {shutdown, Reason, Channel}; handle_timeout(_TRef, Msg, Channel) -> ?WARN("Unexpected timeout: ~p", [Msg]), @@ -269,7 +278,7 @@ handle_call({auth, ClientInfo0, Password}, Channel = #channel{conninfo = ConnInfo, clientinfo = ClientInfo}) -> ClientInfo1 = enrich_clientinfo(ClientInfo0, ClientInfo), - NConnInfo = enrich_conninfo(ClientInfo1, ConnInfo), + NConnInfo = enrich_conninfo(ClientInfo0, ConnInfo), Channel1 = Channel#channel{conninfo = NConnInfo, clientinfo = ClientInfo1}, @@ -283,6 +292,7 @@ handle_call({auth, ClientInfo0, Password}, emqx_metrics:inc('client.auth.anonymous'), NClientInfo = maps:merge(ClientInfo1, AuthResult), NChannel = Channel1#channel{clientinfo = NClientInfo}, + clean_anonymous_clients(), case emqx_cm:open_session(true, NClientInfo, NConnInfo) of {ok, _Session} -> ?LOG(debug, "Client ~s (Username: '~s') authorized successfully!", @@ -373,13 +383,13 @@ handle_info({sock_closed, Reason}, case queue:len(Queue) =:= 0 andalso Inflight =:= undefined of true -> - Channel1 = ensure_disconnected({sock_closed, Reason}, Channel), - {shutdown, {sock_closed, Reason}, Channel1}; + Channel1 = ensure_disconnected(Reason, Channel), + {shutdown, Reason, Channel1}; _ -> %% delayed close process for flushing all callback funcs to gRPC server - Channel1 = Channel#channel{closed_reason = {sock_closed, Reason}}, + Channel1 = Channel#channel{closed_reason = Reason}, Channel2 = ensure_timer(force_timer, Channel1), - {ok, ensure_disconnected({sock_closed, Reason}, Channel2)} + {ok, ensure_disconnected(Reason, Channel2)} end; handle_info({hreply, on_socket_created, ok}, Channel) -> @@ -399,12 +409,16 @@ handle_info(Info, Channel) -> -spec(terminate(any(), channel()) -> channel()). terminate(Reason, Channel) -> + clean_anonymous_clients(), Req = #{reason => stringfy(Reason)}, try_dispatch(on_socket_closed, wrap(Req), Channel). is_anonymous(#{anonymous := true}) -> true; is_anonymous(_AuthResult) -> false. +clean_anonymous_clients() -> + ets:delete(?CHAN_CONN_TAB, ?CHANMOCK(self())). + %%-------------------------------------------------------------------- %% Sub/UnSub %%-------------------------------------------------------------------- @@ -577,7 +591,6 @@ default_conninfo(ConnInfo) -> ConnInfo#{clean_start => true, clientid => undefined, username => undefined, - conn_mod => undefined, conn_props => #{}, connected => true, connected_at => erlang:system_time(millisecond), diff --git a/apps/emqx_exproto/src/emqx_exproto_conn.erl b/apps/emqx_exproto/src/emqx_exproto_conn.erl index c7f73e104b..02c0b31d6b 100644 --- a/apps/emqx_exproto/src/emqx_exproto_conn.erl +++ b/apps/emqx_exproto/src/emqx_exproto_conn.erl @@ -115,7 +115,7 @@ start_link(esockd_transport, Sock, Options) -> %%-------------------------------------------------------------------- %% @doc Get infos of the connection/channel. --spec(info(pid()|state()) -> emqx_types:infos()). +-spec(info(pid() | state()) -> emqx_types:infos()). info(CPid) when is_pid(CPid) -> call(CPid, info); info(State = #state{channel = Channel}) -> @@ -137,7 +137,7 @@ info(sockstate, #state{sockstate = SockSt}) -> info(active_n, #state{active_n = ActiveN}) -> ActiveN. --spec(stats(pid()|state()) -> emqx_types:stats()). +-spec(stats(pid() | state()) -> emqx_types:stats()). stats(CPid) when is_pid(CPid) -> call(CPid, stats); stats(#state{socket = Socket, @@ -233,7 +233,11 @@ init(Parent, WrappedSock, Peername0, Options) -> case esockd_wait(WrappedSock) of {ok, NWrappedSock} -> Peername = esockd_peername(NWrappedSock, Peername0), - run_loop(Parent, init_state(NWrappedSock, Peername, Options)); + try + run_loop(Parent, init_state(NWrappedSock, Peername, Options)) + catch + throw : nopermission -> erlang:exit(normal) + end; {error, Reason} -> ok = esockd_close(WrappedSock), exit_on_sock_error(Reason) @@ -337,7 +341,7 @@ cancel_stats_timer(State) -> State. process_msg([], Parent, State) -> recvloop(Parent, State); -process_msg([Msg|More], Parent, State) -> +process_msg([Msg | More], Parent, State) -> case catch handle_msg(Msg, State) of ok -> process_msg(More, Parent, State); @@ -413,7 +417,7 @@ handle_msg({Passive, _Sock}, State) handle_msg(Deliver = {deliver, _Topic, _Msg}, State = #state{active_n = ActiveN}) -> - Delivers = [Deliver|emqx_misc:drain_deliver(ActiveN)], + Delivers = [Deliver | emqx_misc:drain_deliver(ActiveN)], with_channel(handle_deliver, [Delivers], State); %% Something sent @@ -601,9 +605,9 @@ handle_outgoing(IoData, State = #state{socket = Socket}) -> handle_info(activate_socket, State = #state{sockstate = OldSst}) -> case activate_socket(State) of {ok, NState = #state{sockstate = NewSst}} -> - if OldSst =/= NewSst -> - {ok, {event, NewSst}, NState}; - true -> {ok, NState} + case OldSst =/= NewSst of + true -> {ok, {event, NewSst}, NState}; + false -> {ok, NState} end; {error, Reason} -> handle_info({sock_error, Reason}, State) diff --git a/apps/emqx_exproto/src/emqx_exproto_gcli.erl b/apps/emqx_exproto/src/emqx_exproto_gcli.erl index 650922c4b8..26b0e0d35b 100644 --- a/apps/emqx_exproto/src/emqx_exproto_gcli.erl +++ b/apps/emqx_exproto/src/emqx_exproto_gcli.erl @@ -89,15 +89,22 @@ handle_cast({rpc, Fun, Req, Options, From}, State = #state{streams = Streams}) - {ok, Stream} -> case catch grpc_client:send(Stream, Req) of ok -> - ?LOG(debug, "Send to ~p method successfully, request: ~0p", [Fun, Req]), + ?LOG(debug, "Send to ~s method successfully, request: ~0p", [Fun, Req]), reply(From, Fun, ok), {noreply, State#state{streams = Streams#{Fun => Stream}}}; + {'EXIT', {not_found, _Stk}} -> + %% Not found the stream, reopen it + ?LOG(info, "Can not find the old stream ref for ~s; " + "re-try with a new stream!", [Fun]), + handle_cast({rpc, Fun, Req, Options, From}, + State#state{streams = maps:remove(Fun, Streams)}); {'EXIT', {timeout, _Stk}} -> - ?LOG(error, "Send to ~p method timeout, request: ~0p", [Fun, Req]), + ?LOG(error, "Send to ~s method timeout, request: ~0p", [Fun, Req]), reply(From, Fun, {error, timeout}), {noreply, State#state{streams = Streams#{Fun => Stream}}}; {'EXIT', {Reason1, _Stk}} -> - ?LOG(error, "Send to ~p method failure, request: ~0p, stacktrace: ~0p", [Fun, Req, _Stk]), + ?LOG(error, "Send to ~s method failure, request: ~0p, reason: ~p, " + "stacktrace: ~0p", [Fun, Req, Reason1, _Stk]), reply(From, Fun, {error, Reason1}), {noreply, State#state{streams = Streams#{Fun => undefined}}} end diff --git a/apps/emqx_exproto/src/emqx_exproto_gsvr.erl b/apps/emqx_exproto/src/emqx_exproto_gsvr.erl index a4ad5b2e4f..bc4abfc4d1 100644 --- a/apps/emqx_exproto/src/emqx_exproto_gsvr.erl +++ b/apps/emqx_exproto/src/emqx_exproto_gsvr.erl @@ -123,12 +123,14 @@ call(ConnStr, Req) -> {error, ?RESP_PARAMS_TYPE_ERROR, <<"The conn type error">>}; Pid when is_pid(Pid) -> - case erlang:is_process_alive(Pid) of - true -> - emqx_exproto_conn:call(Pid, Req); - false -> + case catch emqx_exproto_conn:call(Pid, Req) of + {'EXIT',{noproc, _}} -> {error, ?RESP_CONN_PROCESS_NOT_ALIVE, - <<"Connection process is not alive">>} + <<"Connection process is not alive">>}; + {'EXIT',{timeout, _}} -> + {error, ?RESP_UNKNOWN, <<"Connection is not answered">>}; + Result -> + Result end end. diff --git a/apps/emqx_lua_hook/src/emqx_lua_hook.app.src b/apps/emqx_lua_hook/src/emqx_lua_hook.app.src index a84ef8823a..627c8e29d3 100644 --- a/apps/emqx_lua_hook/src/emqx_lua_hook.app.src +++ b/apps/emqx_lua_hook/src/emqx_lua_hook.app.src @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-lua-hook"} + {"Github", "https://github.com/emqx/emqx-lua-hook"} ]} ]}. diff --git a/apps/emqx_lwm2m/etc/emqx_lwm2m.conf b/apps/emqx_lwm2m/etc/emqx_lwm2m.conf index 968b8fd19f..0aa061b1c9 100644 --- a/apps/emqx_lwm2m/etc/emqx_lwm2m.conf +++ b/apps/emqx_lwm2m/etc/emqx_lwm2m.conf @@ -146,4 +146,4 @@ lwm2m.dtls.ciphers = ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,E ## Note that 'lwm2m.dtls.ciphers' and 'lwm2m.dtls.psk_ciphers' cannot ## be configured at the same time. ## See 'https://tools.ietf.org/html/rfc4279#section-2'. -#lwm2m.dtls.psk_ciphers = PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA +#lwm2m.dtls.psk_ciphers = RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384,RSA-PSK-AES128-GCM-SHA256,RSA-PSK-AES128-CBC-SHA256,RSA-PSK-AES256-CBC-SHA,RSA-PSK-AES128-CBC-SHA diff --git a/apps/emqx_lwm2m/integration_test/insert_lwm2m_plugin.py b/apps/emqx_lwm2m/integration_test/insert_lwm2m_plugin.py index 043d1f6a03..7769a3af7c 100644 --- a/apps/emqx_lwm2m/integration_test/insert_lwm2m_plugin.py +++ b/apps/emqx_lwm2m/integration_test/insert_lwm2m_plugin.py @@ -25,27 +25,28 @@ def change_makefile(): '\n {copy, "deps/emqx_lwm2m/lwm2m_xml", "etc/"},') f.write(data) f.close() - - - + + + def change_lwm2m_config(): f = open("emqx-rel/deps/emqx_lwm2m/etc/emqx_lwm2m.conf", "rb") data = f.read() f.close() - + if data.find("5683") > 0: data = data.replace("5683", "5683") f = open("emqx-rel/deps/emqx_lwm2m/etc/emqx_lwm2m.conf", "wb") f.write(data) f.close() - - + + def main(): change_makefile() change_lwm2m_config() - - + + if __name__ == "__main__": main() - + + \ No newline at end of file diff --git a/apps/emqx_lwm2m/rebar.config b/apps/emqx_lwm2m/rebar.config index ba5d99adc8..a914ea7bf5 100644 --- a/apps/emqx_lwm2m/rebar.config +++ b/apps/emqx_lwm2m/rebar.config @@ -1,5 +1,5 @@ {deps, - [{lwm2m_coap, {git, "https://gitee.com/fastdgiot/lwm2m-coap", {tag, "v1.1.2"}}} + [{lwm2m_coap, {git, "https://gitee.com/fastdgiot/lwm2m-coap", {tag, "v1.1.5"}}} ]}. {profiles, diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m.app.src b/apps/emqx_lwm2m/src/emqx_lwm2m.app.src index f4afe8fbc9..551cf8d079 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m.app.src +++ b/apps/emqx_lwm2m/src/emqx_lwm2m.app.src @@ -1,6 +1,6 @@ {application,emqx_lwm2m, [{description,"EMQ X LwM2M Gateway"}, - {vsn, "4.3.3"}, % strict semver, bump manually! + {vsn, "4.3.4"}, % strict semver, bump manually! {modules,[]}, {registered,[emqx_lwm2m_sup]}, {applications,[kernel,stdlib,lwm2m_coap]}, diff --git a/apps/emqx_lwm2m/src/emqx_lwm2m.appup.src b/apps/emqx_lwm2m/src/emqx_lwm2m.appup.src index 07af339fd6..600cf236be 100644 --- a/apps/emqx_lwm2m/src/emqx_lwm2m.appup.src +++ b/apps/emqx_lwm2m/src/emqx_lwm2m.appup.src @@ -1,19 +1,21 @@ %% -*-: erlang -*- -{"4.3.3", +{"4.3.4", [ - {<<"4.3.[0-1]">>, [ + {<<"4\\.3\\.[0-1]">>, [ {restart_application, emqx_lwm2m} ]}, {"4.3.2", [ {load_module, emqx_lwm2m_message, brutal_purge, soft_purge, []} - ]} + ]}, + {"4.3.3", []} %% only config change ], [ - {<<"4.3.[0-1]">>, [ + {<<"4\\.3\\.[0-1]">>, [ {restart_application, emqx_lwm2m} ]}, {"4.3.2", [ {load_module, emqx_lwm2m_message, brutal_purge, soft_purge, []} - ]} + ]}, + {"4.3.3", []} %% only config change ] }. diff --git a/apps/emqx_management/src/emqx_management.app.src b/apps/emqx_management/src/emqx_management.app.src index e86a2c78b2..405b4c244b 100644 --- a/apps/emqx_management/src/emqx_management.app.src +++ b/apps/emqx_management/src/emqx_management.app.src @@ -1,6 +1,6 @@ {application, emqx_management, [{description, "EMQ X Management API and CLI"}, - {vsn, "4.3.5"}, % strict semver, bump manually! + {vsn, "4.3.8"}, % strict semver, bump manually! {modules, []}, {registered, [emqx_management_sup]}, {applications, [kernel,stdlib,minirest]}, @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-management"} + {"Github", "https://github.com/emqx/emqx-management"} ]} ]}. diff --git a/apps/emqx_management/src/emqx_management.appup.src b/apps/emqx_management/src/emqx_management.appup.src index 09406c1c35..e50724d6d5 100644 --- a/apps/emqx_management/src/emqx_management.appup.src +++ b/apps/emqx_management/src/emqx_management.appup.src @@ -1,13 +1,13 @@ %% -*- mode: erlang -*- {VSN, - [ {<<"4.3.[0-4]">>, + [ {<<"4\\.3\\.[0-7]+">>, [ {apply,{minirest,stop_http,['http:management']}}, {apply,{minirest,stop_http,['https:management']}}, {restart_application, emqx_management} ]}, {<<".*">>, []} ], - [ {<<"4.3.[0-4]">>, + [ {<<"4\\.3\\.[0-7]+">>, [ {apply,{minirest,stop_http,['http:management']}}, {apply,{minirest,stop_http,['https:management']}}, {restart_application, emqx_management} diff --git a/apps/emqx_management/src/emqx_mgmt_api_clients.erl b/apps/emqx_management/src/emqx_mgmt_api_clients.erl index 2fe6a5ccb0..1ddd87a3d7 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_clients.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_clients.erl @@ -334,7 +334,7 @@ query({Qs, Fuzzy}, Start, Limit) -> match_fun(Ms, Fuzzy) -> MsC = ets:match_spec_compile(Ms), REFuzzy = lists:map(fun({K, like, S}) -> - {ok, RE} = re:compile(S), + {ok, RE} = re:compile(escape(S)), {K, like, RE} end, Fuzzy), fun(Rows) -> @@ -347,6 +347,9 @@ match_fun(Ms, Fuzzy) -> end end. +escape(B) when is_binary(B) -> + re:replace(B, <<"\\\\">>, <<"\\\\\\\\">>, [{return, binary}, global]). + run_fuzzy_match(_, []) -> true; run_fuzzy_match(E = {_, #{clientinfo := ClientInfo}, _}, [{Key, _, RE}|Fuzzy]) -> @@ -450,4 +453,9 @@ params2qs_test() -> [{{'$1', #{}, '_'}, [], ['$_']}] = qs2ms([]). +escape_test() -> + Str = <<"\\n">>, + {ok, Re} = re:compile(escape(Str)), + {match, _} = re:run(<<"\\name">>, Re). + -endif. diff --git a/apps/emqx_management/src/emqx_mgmt_api_pubsub.erl b/apps/emqx_management/src/emqx_mgmt_api_pubsub.erl index e5a3e9d779..53ca022bb2 100644 --- a/apps/emqx_management/src/emqx_mgmt_api_pubsub.erl +++ b/apps/emqx_management/src/emqx_mgmt_api_pubsub.erl @@ -147,6 +147,8 @@ loop_unsubscribe([Params | ParamsN], Acc) -> code => Code}, loop_unsubscribe(ParamsN, [Result | Acc]). +do_subscribe(ClientId, _Topics, _QoS) when not is_binary(ClientId) -> + {ok, ?ERROR8, <<"bad clientid: must be string">>}; do_subscribe(_ClientId, [], _QoS) -> {ok, ?ERROR15, bad_topic}; do_subscribe(ClientId, Topics, QoS) -> @@ -156,6 +158,8 @@ do_subscribe(ClientId, Topics, QoS) -> _ -> ok end. +do_publish(ClientId, _Topics, _Qos, _Retain, _Payload) when not (is_binary(ClientId) or (ClientId =:= undefined)) -> + {ok, ?ERROR8, <<"bad clientid: must be string">>}; do_publish(_ClientId, [], _Qos, _Retain, _Payload) -> {ok, ?ERROR15, bad_topic}; do_publish(ClientId, Topics, Qos, Retain, Payload) -> @@ -166,6 +170,8 @@ do_publish(ClientId, Topics, Qos, Retain, Payload) -> end, Topics), {ok, MsgIds}. +do_unsubscribe(ClientId, _Topic) when not is_binary(ClientId) -> + {ok, ?ERROR8, <<"bad clientid: must be string">>}; do_unsubscribe(ClientId, Topic) -> case validate_by_filter(Topic) of true -> diff --git a/apps/emqx_management/src/emqx_mgmt_cli.erl b/apps/emqx_management/src/emqx_mgmt_cli.erl index db5dd47d0b..95f5121cd0 100644 --- a/apps/emqx_management/src/emqx_mgmt_cli.erl +++ b/apps/emqx_management/src/emqx_mgmt_cli.erl @@ -191,10 +191,8 @@ clients(["show", ClientId]) -> if_client(ClientId, fun print/1); clients(["kick", ClientId]) -> - case emqx_cm:kick_session(bin(ClientId)) of - ok -> emqx_ctl:print("ok~n"); - _ -> emqx_ctl:print("Not Found.~n") - end; + ok = emqx_cm:kick_session(bin(ClientId)), + emqx_ctl:print("ok~n"); clients(_) -> emqx_ctl:usage([{"clients list", "List all clients"}, diff --git a/apps/emqx_management/src/emqx_mgmt_data_backup.erl b/apps/emqx_management/src/emqx_mgmt_data_backup.erl index 71a92686ed..6e467a8ba0 100644 --- a/apps/emqx_management/src/emqx_mgmt_data_backup.erl +++ b/apps/emqx_management/src/emqx_mgmt_data_backup.erl @@ -118,18 +118,18 @@ export_auth_mnesia() -> end. export_acl_mnesia() -> - case ets:info(emqx_acl) of + case ets:info(emqx_acl2) of undefined -> []; _ -> - lists:map(fun({_, Filter, Action, Access, CreatedAt}) -> - Filter1 = case Filter of - {{Type, TypeValue}, Topic} -> + lists:map(fun({Login, Topic, Action, Access, CreatedAt}) -> + Filter1 = case Login of + {Type, TypeValue} -> [{type, Type}, {type_value, TypeValue}, {topic, Topic}]; - {Type, Topic} -> + Type -> [{type, Type}, {topic, Topic}] end, Filter1 ++ [{action, Action}, {access, Access}, {created_at, CreatedAt}] - end, ets:tab2list(emqx_acl)) + end, emqx_acl_mnesia_db:all_acls_export()) end. -ifdef(EMQX_ENTERPRISE). @@ -185,6 +185,7 @@ confs_to_binary(Confs) -> -endif. +-dialyzer([{nowarn_function, [import_rules/1, import_rule/1]}]). import_rule(#{<<"id">> := RuleId, <<"rawsql">> := RawSQL, <<"actions">> := Actions, @@ -195,9 +196,11 @@ import_rule(#{<<"id">> := RuleId, actions => map_to_actions(Actions), enabled => Enabled, description => Desc}, - try emqx_rule_engine:create_rule(Rule) - catch throw:{resource_not_initialized, _ResId} -> - emqx_rule_engine:create_rule(Rule#{enabled => false}) + case emqx_rule_engine:create_rule(Rule) of + {ok, _} -> ok; + {error, _} -> + _ = emqx_rule_engine:create_rule(Rule#{enabled => false}), + ok end. map_to_actions(Maps) -> @@ -470,10 +473,9 @@ do_import_auth_mnesia(Auths) -> end. do_import_acl_mnesia_by_old_data(Acls) -> - case ets:info(emqx_acl) of + case ets:info(emqx_acl2) of undefined -> ok; _ -> - CreatedAt = erlang:system_time(millisecond), lists:foreach(fun(#{<<"login">> := Login, <<"topic">> := Topic, <<"allow">> := Allow, @@ -482,11 +484,11 @@ do_import_acl_mnesia_by_old_data(Acls) -> true -> allow; false -> deny end, - mnesia:dirty_write({emqx_acl, {{get_old_type(), Login}, Topic}, any_to_atom(Action), Allow1, CreatedAt}) + emqx_acl_mnesia_db:add_acl({get_old_type(), Login}, Topic, any_to_atom(Action), Allow1) end, Acls) end. do_import_acl_mnesia(Acls) -> - case ets:info(emqx_acl) of + case ets:info(emqx_acl2) of undefined -> ok; _ -> lists:foreach(fun(Map = #{<<"action">> := Action, @@ -498,7 +500,7 @@ do_import_acl_mnesia(Acls) -> Value -> {any_to_atom(maps:get(<<"type">>, Map)), Value} end, - emqx_acl_mnesia_cli:add_acl(Login, Topic, any_to_atom(Action), any_to_atom(Access)) + emqx_acl_mnesia_db:add_acl(Login, Topic, any_to_atom(Action), any_to_atom(Access)) end, Acls) end. diff --git a/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl index 838529f03e..3a5a89aa33 100644 --- a/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl +++ b/apps/emqx_management/test/emqx_auth_mnesia_migration_SUITE.erl @@ -30,7 +30,7 @@ matrix() -> , Version <- ["v4.2.10", "v4.1.5"]]. all() -> - [t_import_4_0, t_import_4_1, t_import_4_2]. + [t_import_4_0, t_import_4_1, t_import_4_2, t_export_import]. groups() -> [{username, [], cases()}, {clientid, [], cases()}]. @@ -52,7 +52,8 @@ init_per_testcase(_, Config) -> Config. end_per_testcase(_, _Config) -> - {atomic,ok} = mnesia:clear_table(emqx_acl), + {atomic,ok} = mnesia:clear_table(?ACL_TABLE), + {atomic,ok} = mnesia:clear_table(?ACL_TABLE2), {atomic,ok} = mnesia:clear_table(emqx_user), ok. -ifdef(EMQX_ENTERPRISE). @@ -138,25 +139,50 @@ t_import_4_2(Config) -> test_import(clientid, {<<"client_for_test">>, <<"public">>}), test_import(username, {<<"user_for_test">>, <<"public">>}), - ?assertMatch([#emqx_acl{ - filter = {{Type,<<"emqx_c">>}, <<"Topic/A">>}, - action = pub, - access = allow - }, - #emqx_acl{ - filter = {{Type,<<"emqx_c">>}, <<"Topic/A">>}, - action = sub, - access = allow - }], - lists:sort(ets:tab2list(emqx_acl))). + ?assertMatch([ + {{username, <<"emqx_c">>}, <<"Topic/A">>, pub, allow, _}, + {{username, <<"emqx_c">>}, <<"Topic/A">>, sub, allow, _} + ], + lists:sort(emqx_acl_mnesia_db:all_acls())). -endif. +t_export_import(_Config) -> + emqx_acl_mnesia_migrator:migrate_records(), + + Records = [ + #?ACL_TABLE2{who = {clientid,<<"client1">>}, rules = [{allow, sub, <<"t1">>, 1}]}, + #?ACL_TABLE2{who = {clientid,<<"client2">>}, rules = [{allow, pub, <<"t2">>, 2}]} + ], + mnesia:transaction(fun() -> lists:foreach(fun mnesia:write/1, Records) end), + timer:sleep(100), + + AclData = emqx_json:encode(emqx_mgmt_data_backup:export_acl_mnesia()), + + mnesia:transaction(fun() -> + lists:foreach(fun(#?ACL_TABLE2{who = Who}) -> + mnesia:delete({?ACL_TABLE2, Who}) + end, + Records) + end), + + ?assertEqual([], emqx_acl_mnesia_db:all_acls()), + + emqx_mgmt_data_backup:import_acl_mnesia(emqx_json:decode(AclData, [return_maps]), "4.3"), + timer:sleep(100), + + ?assertMatch([ + {{clientid, <<"client1">>}, <<"t1">>, sub, allow, _}, + {{clientid, <<"client2">>}, <<"t2">>, pub, allow, _} + ], lists:sort(emqx_acl_mnesia_db:all_acls())). + do_import(File, Config) -> do_import(File, Config, "{}"). do_import(File, Config, Overrides) -> - mnesia:clear_table(emqx_acl), + mnesia:clear_table(?ACL_TABLE), + mnesia:clear_table(?ACL_TABLE2), mnesia:clear_table(emqx_user), + emqx_acl_mnesia_migrator:migrate_records(), Filename = filename:join(proplists:get_value(data_dir, Config), File), emqx_mgmt_data_backup:import(Filename, Overrides). diff --git a/apps/emqx_management/test/emqx_mgmt_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_SUITE.erl index 6bac9b4c7a..77d46b744b 100644 --- a/apps/emqx_management/test/emqx_mgmt_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_SUITE.erl @@ -158,9 +158,9 @@ t_clients_cmd(_) -> timer:sleep(300), emqx_mgmt_cli:clients(["list"]), ?assertMatch({match, _}, re:run(emqx_mgmt_cli:clients(["show", "client12"]), "client12")), - ?assertEqual((emqx_mgmt_cli:clients(["kick", "client12"])), "ok~n"), + ?assertEqual("ok~n", emqx_mgmt_cli:clients(["kick", "client12"])), timer:sleep(500), - ?assertMatch({match, _}, re:run(emqx_mgmt_cli:clients(["show", "client12"]), "Not Found")), + ?assertEqual("ok~n", emqx_mgmt_cli:clients(["kick", "client12"])), receive {'EXIT', T, _} -> ok diff --git a/apps/emqx_management/test/emqx_mgmt_api_SUITE.erl b/apps/emqx_management/test/emqx_mgmt_api_SUITE.erl index 2749edc150..e45acfd42c 100644 --- a/apps/emqx_management/test/emqx_mgmt_api_SUITE.erl +++ b/apps/emqx_management/test/emqx_mgmt_api_SUITE.erl @@ -223,8 +223,8 @@ t_clients(_) -> timer:sleep(300), - {ok, NotFound0} = request_api(delete, api_path(["clients", binary_to_list(ClientId1)]), auth_header_()), - ?assertEqual(?ERROR12, get(<<"code">>, NotFound0)), + {ok, Ok1} = request_api(delete, api_path(["clients", binary_to_list(ClientId1)]), auth_header_()), + ?assertEqual(?SUCCESS, get(<<"code">>, Ok1)), {ok, Clients6} = request_api(get, api_path(["clients"]), "_limit=100&_page=1", auth_header_()), ?assertEqual(1, maps:get(<<"count">>, get(<<"meta">>, Clients6))), @@ -403,6 +403,26 @@ t_pubsub(_) -> <<"topic">> => <<"">>}), ?assertEqual(?ERROR15, get(<<"code">>, BadTopic3)), + + {ok, BadClient1} = request_api(post, api_path(["mqtt/subscribe"]), [], auth_header_(), + #{<<"clientid">> => 1, + <<"topics">> => <<"mytopic">>, + <<"qos">> => 2}), + ?assertEqual(?ERROR8, get(<<"code">>, BadClient1)), + + {ok, BadClient2} = request_api(post, api_path(["mqtt/publish"]), [], auth_header_(), + #{<<"clientid">> => 1, + <<"topics">> => <<"mytopic">>, + <<"qos">> => 1, + <<"payload">> => <<"hello">>}), + ?assertEqual(?ERROR8, get(<<"code">>, BadClient2)), + + {ok, BadClient3} = request_api(post, api_path(["mqtt/unsubscribe"]), [], auth_header_(), + #{<<"clientid">> => 1, + <<"topic">> => <<"mytopic">>}), + ?assertEqual(?ERROR8, get(<<"code">>, BadClient3)), + + meck:new(emqx_mgmt, [passthrough, no_history]), meck:expect(emqx_mgmt, unsubscribe, 2, fun(_, _) -> {error, undefined} end), {ok, NotFound2} = request_api(post, api_path(["mqtt/unsubscribe"]), [], auth_header_(), @@ -427,6 +447,19 @@ t_pubsub(_) -> after 100 -> false end), + + % no clientid + {ok, Code} = request_api(post, api_path(["mqtt/publish"]), [], auth_header_(), + #{<<"topic">> => <<"mytopic">>, + <<"qos">> => 1, + <<"payload">> => <<"hello">>}), + ?assert(receive + {publish, #{payload := <<"hello">>}} -> + true + after 100 -> + false + end), + %% json payload {ok, Code} = request_api(post, api_path(["mqtt/publish"]), [], auth_header_(), #{<<"clientid">> => ClientId, @@ -471,9 +504,9 @@ t_pubsub(_) -> ok = emqtt:disconnect(C1), - ?assertEqual(2, emqx_metrics:val('messages.qos1.received') - Qos1Received), + ?assertEqual(3, emqx_metrics:val('messages.qos1.received') - Qos1Received), ?assertEqual(2, emqx_metrics:val('messages.qos2.received') - Qos2Received), - ?assertEqual(4, emqx_metrics:val('messages.received') - Received). + ?assertEqual(5, emqx_metrics:val('messages.received') - Received). loop([]) -> []; diff --git a/apps/emqx_plugin_libs/src/emqx_plugin_libs.app.src b/apps/emqx_plugin_libs/src/emqx_plugin_libs.app.src index 9b773e80cd..82937d033d 100644 --- a/apps/emqx_plugin_libs/src/emqx_plugin_libs.app.src +++ b/apps/emqx_plugin_libs/src/emqx_plugin_libs.app.src @@ -1,6 +1,6 @@ {application, emqx_plugin_libs, [{description, "EMQ X Plugin utility libs"}, - {vsn, "4.3.0"}, + {vsn, "4.3.1"}, {modules, []}, {applications, [kernel,stdlib]}, {env, []} diff --git a/apps/emqx_plugin_libs/src/emqx_plugin_libs.appup.src b/apps/emqx_plugin_libs/src/emqx_plugin_libs.appup.src new file mode 100644 index 0000000000..9cd66269c7 --- /dev/null +++ b/apps/emqx_plugin_libs/src/emqx_plugin_libs.appup.src @@ -0,0 +1,16 @@ +%% -*-: erlang -*- + +{VSN, + [ + {<<"4.3.0">>, [ + {load_module, emqx_plugin_libs_ssl, brutal_purge, soft_purge, []} + ]}, + {<<".*">>, []} + ], + [ + {<<"4.3.0">>, [ + {load_module, emqx_plugin_libs_ssl, brutal_purge, soft_purge, []} + ]}, + {<<".*">>, []} + ] +}. diff --git a/apps/emqx_plugin_libs/src/emqx_plugin_libs_ssl.erl b/apps/emqx_plugin_libs/src/emqx_plugin_libs_ssl.erl index 9fc9e66ef7..2c80dfcfb8 100644 --- a/apps/emqx_plugin_libs/src/emqx_plugin_libs_ssl.erl +++ b/apps/emqx_plugin_libs/src/emqx_plugin_libs_ssl.erl @@ -65,7 +65,10 @@ save_files_return_opts(Options, Dir) -> false -> verify_none; _ -> verify_peer end, - SNI = Get(<<"server_name_indication">>), + SNI = case Get(<<"server_name_indication">>) of + undefined -> undefined; + SNI0 -> ensure_str(SNI0) + end, Versions = emqx_tls_lib:integral_versions(Get(<<"tls_versions">>)), Ciphers = emqx_tls_lib:integral_ciphers(Versions, Get(<<"ciphers">>)), filter([{keyfile, Key}, {certfile, Cert}, {cacertfile, CA}, @@ -81,6 +84,7 @@ save_file(Param, SubDir) -> filter([]) -> []; filter([{_, ""} | T]) -> filter(T); +filter([{_, undefined} | T]) -> filter(T); filter([H | T]) -> [H | filter(T)]. do_save_file(#{<<"filename">> := FileName, <<"file">> := Content}, Dir) diff --git a/apps/emqx_prometheus/src/emqx_prometheus.app.src b/apps/emqx_prometheus/src/emqx_prometheus.app.src index 2f3cdff9d0..b96608edb2 100644 --- a/apps/emqx_prometheus/src/emqx_prometheus.app.src +++ b/apps/emqx_prometheus/src/emqx_prometheus.app.src @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-prometheus"} + {"Github", "https://github.com/emqx/emqx-prometheus"} ]} ]}. diff --git a/apps/emqx_psk_file/src/emqx_psk_file.app.src b/apps/emqx_psk_file/src/emqx_psk_file.app.src index dd4ce4067a..b8a6f08a07 100644 --- a/apps/emqx_psk_file/src/emqx_psk_file.app.src +++ b/apps/emqx_psk_file/src/emqx_psk_file.app.src @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-psk-file"} + {"Github", "https://github.com/emqx/emqx-psk-file"} ]} ]}. diff --git a/apps/emqx_recon/src/emqx_recon.app.src b/apps/emqx_recon/src/emqx_recon.app.src index ffc6bb456b..061ed8e932 100644 --- a/apps/emqx_recon/src/emqx_recon.app.src +++ b/apps/emqx_recon/src/emqx_recon.app.src @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-recon"} + {"Github", "https://github.com/emqx/emqx-recon"} ]} ]}. diff --git a/apps/emqx_retainer/etc/emqx_retainer.conf b/apps/emqx_retainer/etc/emqx_retainer.conf index 0a883cee56..8b5c251c6b 100644 --- a/apps/emqx_retainer/etc/emqx_retainer.conf +++ b/apps/emqx_retainer/etc/emqx_retainer.conf @@ -39,3 +39,11 @@ retainer.max_payload_size = 1MB ## ## Defaut: 0 retainer.expiry_interval = 0 + +## When the retained flag of the PUBLISH message is set and Payload is empty, +## whether to continue to publish the message. +## see: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718038 +## +## Value: Boolean +## Default: false +#retainer.stop_publish_clear_msg = false diff --git a/apps/emqx_retainer/priv/emqx_retainer.schema b/apps/emqx_retainer/priv/emqx_retainer.schema index e598864e19..5e998aa162 100644 --- a/apps/emqx_retainer/priv/emqx_retainer.schema +++ b/apps/emqx_retainer/priv/emqx_retainer.schema @@ -28,3 +28,10 @@ {default, 0}, {datatype, [integer, {duration, ms}]} ]}. + +%% Stop publish clear message +%% {$configurable} +{mapping, "retainer.stop_publish_clear_msg", "emqx_retainer.stop_publish_clear_msg", [ + {default, false}, + {datatype, {enum, [true, false]}} +]}. diff --git a/apps/emqx_retainer/src/emqx_retainer.app.src b/apps/emqx_retainer/src/emqx_retainer.app.src index ac55f2dbd3..c5ca7599db 100644 --- a/apps/emqx_retainer/src/emqx_retainer.app.src +++ b/apps/emqx_retainer/src/emqx_retainer.app.src @@ -1,6 +1,6 @@ {application, emqx_retainer, [{description, "EMQ X Retainer"}, - {vsn, "4.3.1"}, % strict semver, bump manually! + {vsn, "4.3.2"}, % strict semver, bump manually! {modules, []}, {registered, [emqx_retainer_sup]}, {applications, [kernel,stdlib]}, @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-retainer"} + {"Github", "https://github.com/emqx/emqx-retainer"} ]} ]}. diff --git a/apps/emqx_retainer/src/emqx_retainer.appup.src b/apps/emqx_retainer/src/emqx_retainer.appup.src index a17e6ee2fa..19e8e835f1 100644 --- a/apps/emqx_retainer/src/emqx_retainer.appup.src +++ b/apps/emqx_retainer/src/emqx_retainer.appup.src @@ -1,13 +1,13 @@ %% -*-: erlang -*- {VSN, [ - {"4.3.0", [ + {<<"4\\.3\\.[0-1]+">>, [ {load_module, emqx_retainer, brutal_purge, soft_purge, []} ]}, {<<".*">>, []} ], [ - {"4.3.0", [ + {<<"4\\.3\\.[0-1]+">>, [ {load_module, emqx_retainer, brutal_purge, soft_purge, []} ]}, {<<".*">>, []} diff --git a/apps/emqx_retainer/src/emqx_retainer.erl b/apps/emqx_retainer/src/emqx_retainer.erl index 340e6929dc..33220eb1f0 100644 --- a/apps/emqx_retainer/src/emqx_retainer.erl +++ b/apps/emqx_retainer/src/emqx_retainer.erl @@ -83,9 +83,14 @@ dispatch(Pid, Topic) -> %% RETAIN flag set to 1 and payload containing zero bytes on_message_publish(Msg = #message{flags = #{retain := true}, topic = Topic, - payload = <<>>}, _Env) -> + payload = <<>>}, Env) -> mnesia:dirty_delete(?TAB, topic2tokens(Topic)), - {ok, Msg}; + case stop_publish_clear_msg(Env) of + true -> + {ok, emqx_message:set_header(allow_publish, false, Msg)}; + _ -> + {ok, Msg} + end; on_message_publish(Msg = #message{flags = #{retain := true}}, Env) -> Msg1 = emqx_message:set_header(retained, true, Msg), @@ -224,6 +229,9 @@ store_retained(Msg = #message{topic = Topic, payload = Payload}, Env) -> "for payload is too big!", [Topic, iolist_size(Payload)]) end. +stop_publish_clear_msg(Env) -> + proplists:get_bool(stop_publish_clear_msg, Env). + is_table_full(Env) -> Limit = proplists:get_value(max_retained_messages, Env, 0), Limit > 0 andalso (retained_count() > Limit). diff --git a/apps/emqx_retainer/test/emqx_retainer_SUITE.erl b/apps/emqx_retainer/test/emqx_retainer_SUITE.erl index 1df042dd97..28f667b535 100644 --- a/apps/emqx_retainer/test/emqx_retainer_SUITE.erl +++ b/apps/emqx_retainer/test/emqx_retainer_SUITE.erl @@ -42,6 +42,8 @@ init_per_testcase(TestCase, Config) -> case TestCase of t_message_expiry_2 -> application:set_env(emqx_retainer, expiry_interval, 2000); + t_stop_publish_clear_msg -> + application:set_env(emqx_retainer, stop_publish_clear_msg, true); _ -> application:set_env(emqx_retainer, expiry_interval, 0) end, @@ -173,6 +175,19 @@ t_clean(_) -> ok = emqtt:disconnect(C1). +t_stop_publish_clear_msg(_) -> + {ok, C1} = emqtt:start_link([{clean_start, true}, {proto_ver, v5}]), + {ok, _} = emqtt:connect(C1), + emqtt:publish(C1, <<"retained/0">>, <<"this is a retained message 0">>, [{qos, 0}, {retain, true}]), + + {ok, #{}, [0]} = emqtt:subscribe(C1, <<"retained/#">>, [{qos, 0}, {rh, 0}]), + ?assertEqual(1, length(receive_messages(1))), + + emqtt:publish(C1, <<"retained/0">>, <<"">>, [{qos, 0}, {retain, true}]), + ?assertEqual(0, length(receive_messages(1))), + + ok = emqtt:disconnect(C1). + %%-------------------------------------------------------------------- %% Helper functions %%-------------------------------------------------------------------- diff --git a/apps/emqx_rule_engine/include/rule_engine.hrl b/apps/emqx_rule_engine/include/rule_engine.hrl index 568724263d..6e84166886 100644 --- a/apps/emqx_rule_engine/include/rule_engine.hrl +++ b/apps/emqx_rule_engine/include/rule_engine.hrl @@ -158,7 +158,7 @@ -define(CLUSTER_CALL(Func, Args), ?CLUSTER_CALL(Func, Args, ok)). -define(CLUSTER_CALL(Func, Args, ResParttern), - fun() -> case rpc:multicall(ekka_mnesia:running_nodes(), ?MODULE, Func, Args, 5000) of + fun() -> case rpc:multicall(ekka_mnesia:running_nodes(), ?MODULE, Func, Args, 30000) of {ResL, []} -> case lists:filter(fun(ResParttern) -> false; (_) -> true end, ResL) of [] -> ResL; diff --git a/apps/emqx_rule_engine/src/emqx_rule_actions.erl b/apps/emqx_rule_engine/src/emqx_rule_actions.erl index 31492f2ddf..5d0dd463ad 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_actions.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_actions.erl @@ -157,7 +157,7 @@ on_action_republish(Selected, _Envs = #{ 'TargetQoS' := TargetQoS, 'TopicTks' := TopicTks, 'PayloadTks' := PayloadTks - } }) -> + }}) -> ?LOG(debug, "[republish] republish to: ~p, Payload: ~p", [TargetTopic, Selected]), increase_and_publish(ActId, diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src index ffc269e990..66af0da210 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.app.src +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.app.src @@ -1,6 +1,6 @@ {application, emqx_rule_engine, [{description, "EMQ X Rule Engine"}, - {vsn, "4.3.4"}, % strict semver, bump manually! + {vsn, "4.3.6"}, % strict semver, bump manually! {modules, []}, {registered, [emqx_rule_engine_sup, emqx_rule_registry]}, {applications, [kernel,stdlib,rulesql,getopt]}, @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-rule-engine"} + {"Github", "https://github.com/emqx/emqx-rule-engine"} ]} ]}. diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src b/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src index 2771f71f7a..c94dc994d8 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.appup.src @@ -1,54 +1,52 @@ -%% -*-: erlang -*- -{"4.3.4", - [ {"4.3.0", - [ {load_module, emqx_rule_funcs, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_registry, brutal_purge, soft_purge, []} - , {apply, {emqx_stats, cancel_update, [rule_registery_stats]}} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, +%% -*- mode: erlang -*- +{VSN, + [{"4.3.5",[{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, + {"4.3.0", + [{load_module,emqx_rule_funcs,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, + {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, {"4.3.1", - [ {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_registry, brutal_purge, soft_purge, []} - , {apply, {emqx_stats, cancel_update, [rule_registery_stats]}} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, + [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, + {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, {"4.3.2", - [ {load_module, emqx_rule_registry, brutal_purge, soft_purge, []} - , {apply, {emqx_stats, cancel_update, [rule_registery_stats]}} - , {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, + [{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, + {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, + {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, {"4.3.3", - [ {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ], - [ + [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, + {"4.3.4", + [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, + {<<".*">>,[]}], + [{"4.3.5",[{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, {"4.3.0", - [ {load_module, emqx_rule_funcs, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_registry, brutal_purge, soft_purge, []} - , {apply, {emqx_stats, cancel_update, [rule_registery_stats]}} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, + [{load_module,emqx_rule_funcs,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, + {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, {"4.3.1", - [ {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_registry, brutal_purge, soft_purge, []} - , {apply, {emqx_stats, cancel_update, [rule_registery_stats]}} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, + [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, + {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, {"4.3.2", - [ {load_module, emqx_rule_registry, brutal_purge, soft_purge, []} - , {apply, {emqx_stats, cancel_update, [rule_registery_stats]}} - , {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, + [{load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}, + {apply,{emqx_stats,cancel_update,[rule_registery_stats]}}, + {load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}]}, {"4.3.3", - [ {load_module, emqx_rule_engine, brutal_purge, soft_purge, []} - , {load_module, emqx_rule_actions, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ] -}. + [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_actions,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, + {"4.3.4", + [{load_module,emqx_rule_engine,brutal_purge,soft_purge,[]}, + {load_module,emqx_rule_registry,brutal_purge,soft_purge,[]}]}, + {<<".*">>,[]}]}. diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine.erl b/apps/emqx_rule_engine/src/emqx_rule_engine.erl index efb461527b..6c8b950647 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_engine.erl @@ -475,10 +475,10 @@ may_update_rule_params(Rule = #rule{enabled = OldEnb, actions = Actions, state = Params = #{enabled := NewEnb}) -> State = case {OldEnb, NewEnb} of {false, true} -> - refresh_rule(Rule), + _ = ?CLUSTER_CALL(refresh_rule, [Rule]), force_changed; {true, false} -> - clear_actions(Actions), + _ = ?CLUSTER_CALL(clear_actions, [Actions]), force_changed; _NoChange -> OldState end, @@ -637,7 +637,7 @@ refresh_actions(Actions, Pred) -> true -> {ok, #action{module = Mod, on_create = Create}} = emqx_rule_registry:find_action(ActName), - _ = ?CLUSTER_CALL(init_action, [Mod, Create, Id, with_resource_params(Args)]), + _ = init_action(Mod, Create, Id, with_resource_params(Args)), refresh_actions(Fallbacks, Pred); false -> ok end diff --git a/apps/emqx_sasl/src/emqx_sasl.app.src b/apps/emqx_sasl/src/emqx_sasl.app.src index 3be706cbe9..b079376d99 100644 --- a/apps/emqx_sasl/src/emqx_sasl.app.src +++ b/apps/emqx_sasl/src/emqx_sasl.app.src @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-sasl"} + {"Github", "https://github.com/emqx/emqx-sasl"} ]} ]}. diff --git a/apps/emqx_sn/intergration_test/add_emqx_sn_to_project.py b/apps/emqx_sn/intergration_test/add_emqx_sn_to_project.py index 941b53cb9f..657ddb54c3 100644 --- a/apps/emqx_sn/intergration_test/add_emqx_sn_to_project.py +++ b/apps/emqx_sn/intergration_test/add_emqx_sn_to_project.py @@ -6,7 +6,7 @@ if data.find("emqx-sn") < 0: f = open("emqx-rel/Makefile", "w") data = data.replace("emqx_auth_pgsql emqx_auth_mongo", "emqx_auth_pgsql emqx_auth_mongo emqx_sn") - data = data.replace("dep_emqx_auth_mongo", "dep_emqx_sn = git https://gitee.com/fastdgiot/emqx-sn.git develop\ndep_emqx_auth_mongo") + data = data.replace("dep_emqx_auth_mongo", "dep_emqx_sn = git https://github.com/emqx/emqx-sn.git develop\ndep_emqx_auth_mongo") f.write(data) f.close() diff --git a/apps/emqx_sn/src/emqx_sn.app.src b/apps/emqx_sn/src/emqx_sn.app.src index 11e6820278..a36166eae8 100644 --- a/apps/emqx_sn/src/emqx_sn.app.src +++ b/apps/emqx_sn/src/emqx_sn.app.src @@ -1,6 +1,6 @@ {application, emqx_sn, [{description, "EMQ X MQTT-SN Plugin"}, - {vsn, "4.3.0"}, % strict semver, bump manually! + {vsn, "4.3.4"}, % strict semver, bump manually! {modules, []}, {registered, []}, {applications, [kernel,stdlib,esockd]}, @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-sn"} + {"Github", "https://github.com/emqx/emqx-sn"} ]} ]}. diff --git a/apps/emqx_sn/src/emqx_sn.appup.src b/apps/emqx_sn/src/emqx_sn.appup.src new file mode 100644 index 0000000000..22d2cd6066 --- /dev/null +++ b/apps/emqx_sn/src/emqx_sn.appup.src @@ -0,0 +1,14 @@ +%% -*- mode: erlang -*- +{VSN, + [{"4.3.3",[{load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {"4.3.2", + [{load_module,emqx_sn_gateway,brutal_purge,soft_purge,[]}, + {load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {<<"4\\.3\\.[0-1]">>, + [{restart_application,emqx_sn}]}], + [{"4.3.3",[{load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {"4.3.2", + [{load_module,emqx_sn_gateway,brutal_purge,soft_purge,[]}, + {load_module,emqx_sn_registry,brutal_purge,soft_purge,[]}]}, + {<<"4\\.3\\.[0-1]">>, + [{restart_application,emqx_sn}]}]}. diff --git a/apps/emqx_sn/src/emqx_sn_app.erl b/apps/emqx_sn/src/emqx_sn_app.erl index d39e8b34f5..9575523f8f 100644 --- a/apps/emqx_sn/src/emqx_sn_app.erl +++ b/apps/emqx_sn/src/emqx_sn_app.erl @@ -43,7 +43,8 @@ start(_Type, _Args) -> Addr = application:get_env(emqx_sn, port, 1884), GwId = application:get_env(emqx_sn, gateway_id, 1), - {ok, Sup} = emqx_sn_sup:start_link(Addr, GwId), + PredefTopics = application:get_env(emqx_sn, predefined, []), + {ok, Sup} = emqx_sn_sup:start_link(Addr, GwId, PredefTopics), start_listeners(), {ok, Sup}. @@ -57,13 +58,7 @@ stop(_State) -> -spec start_listeners() -> ok. start_listeners() -> - PredefTopics = application:get_env(emqx_sn, predefined, []), - ListenCfs = [begin - TabName = tabname(Proto, ListenOn), - {ok, RegistryPid} = emqx_sn_sup:start_registry_proc(emqx_sn_sup, TabName, PredefTopics), - {Proto, ListenOn, [{registry, {TabName, RegistryPid}} | Options]} - end || {Proto, ListenOn, Options} <- listeners_confs()], - lists:foreach(fun start_listener/1, ListenCfs). + lists:foreach(fun start_listener/1, listeners_confs()). -spec start_listener(listener()) -> ok. start_listener({Proto, ListenOn, Options}) -> @@ -151,7 +146,3 @@ format({Addr, Port}) when is_list(Addr) -> io_lib:format("~s:~w", [Addr, Port]); format({Addr, Port}) when is_tuple(Addr) -> io_lib:format("~s:~w", [inet:ntoa(Addr), Port]). - -tabname(Proto, ListenOn) -> - list_to_atom(lists:flatten(["emqx_sn_registry__", atom_to_list(Proto), "_", format(ListenOn)])). - diff --git a/apps/emqx_sn/src/emqx_sn_asleep_timer.erl b/apps/emqx_sn/src/emqx_sn_asleep_timer.erl index 56a63ee2f0..37ea676895 100644 --- a/apps/emqx_sn/src/emqx_sn_asleep_timer.erl +++ b/apps/emqx_sn/src/emqx_sn_asleep_timer.erl @@ -18,6 +18,7 @@ -export([ init/0 , ensure/2 + , cancel/1 ]). -record(asleep_state, { @@ -42,8 +43,8 @@ init() -> -spec(ensure(undefined | integer(), asleep_state()) -> asleep_state()). ensure(undefined, State = #asleep_state{duration = Duration}) -> ensure(Duration, State); -ensure(Duration, State = #asleep_state{tref = TRef}) -> - _ = cancel(TRef), +ensure(Duration, State) -> + cancel(State), State#asleep_state{duration = Duration, tref = start(Duration)}. %%-------------------------------------------------------------------- @@ -55,6 +56,10 @@ ensure(Duration, State = #asleep_state{tref = TRef}) -> start(Duration) -> erlang:send_after(timer:seconds(Duration), self(), asleep_timeout). -cancel(undefined) -> ok; -cancel(TRef) when is_reference(TRef) -> - erlang:cancel_timer(TRef). +cancel(#asleep_state{tref = Timer}) when is_reference(Timer) -> + case erlang:cancel_timer(Timer) of + false -> + receive {timeout, Timer, _} -> ok after 0 -> ok end; + _ -> ok + end; +cancel(_) -> ok. \ No newline at end of file diff --git a/apps/emqx_sn/src/emqx_sn_gateway.erl b/apps/emqx_sn/src/emqx_sn_gateway.erl index bfb2f28df9..1bccf0c1a9 100644 --- a/apps/emqx_sn/src/emqx_sn_gateway.erl +++ b/apps/emqx_sn/src/emqx_sn_gateway.erl @@ -82,7 +82,6 @@ sockname :: {inet:ip_address(), inet:port()}, peername :: {inet:ip_address(), inet:port()}, channel :: maybe(emqx_channel:channel()), - registry :: emqx_sn_registry:registry(), clientid :: maybe(binary()), username :: maybe(binary()), password :: maybe(binary()), @@ -147,7 +146,6 @@ kick(GwPid) -> init([{_, SockPid, Sock}, Peername, Options]) -> GwId = proplists:get_value(gateway_id, Options), - Registry = proplists:get_value(registry, Options), Username = proplists:get_value(username, Options, undefined), Password = proplists:get_value(password, Options, undefined), EnableQos3 = proplists:get_value(enable_qos3, Options, false), @@ -165,7 +163,6 @@ init([{_, SockPid, Sock}, Peername, Options]) -> sockname = Sockname, peername = Peername, channel = Channel, - registry = Registry, asleep_timer = emqx_sn_asleep_timer:init(), enable_stats = EnableStats, enable_qos3 = EnableQos3, @@ -205,9 +202,9 @@ idle(cast, {incoming, ?SN_PUBLISH_MSG(_Flag, _TopicId, _MsgId, _Data)}, State = idle(cast, {incoming, ?SN_PUBLISH_MSG(#mqtt_sn_flags{qos = ?QOS_NEG1, topic_id_type = TopicIdType }, TopicId, _MsgId, Data)}, - State = #state{clientid = ClientId, registry = Registry}) -> + State = #state{clientid = ClientId}) -> TopicName = case (TopicIdType =:= ?SN_SHORT_TOPIC) of - false -> emqx_sn_registry:lookup_topic(Registry, self(), TopicId); + false -> emqx_sn_registry:lookup_topic(ClientId, TopicId); true -> <> end, _ = case TopicName =/= undefined of @@ -253,8 +250,9 @@ wait_for_will_topic(cast, {incoming, ?SN_ADVERTISE_MSG(_GwId, _Radius)}, _State) % ignore keep_state_and_data; -wait_for_will_topic(cast, {incoming, ?SN_CONNECT_MSG(Flags, _ProtoId, Duration, ClientId)}, State) -> - do_2nd_connect(Flags, Duration, ClientId, State); +wait_for_will_topic(cast, {incoming, ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId)}, _State) -> + ?LOG(warning, "Receive connect packet in wait_for_will_topic state", []), + keep_state_and_data; wait_for_will_topic(cast, {outgoing, Packet}, State) -> {keep_state, handle_outgoing(Packet, State)}; @@ -278,9 +276,9 @@ wait_for_will_msg(cast, {incoming, ?SN_ADVERTISE_MSG(_GwId, _Radius)}, _State) - % ignore keep_state_and_data; -%% XXX: ?? Why we will handling the 2nd CONNECT packet ?? -wait_for_will_msg(cast, {incoming, ?SN_CONNECT_MSG(Flags, _ProtoId, Duration, ClientId)}, State) -> - do_2nd_connect(Flags, Duration, ClientId, State); +wait_for_will_msg(cast, {incoming, ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId)}, _State) -> + ?LOG(warning, "Receive connect packet in wait_for_will_msg state", []), + keep_state_and_data; wait_for_will_msg(cast, {outgoing, Packet}, State) -> {keep_state, handle_outgoing(Packet, State)}; @@ -292,9 +290,9 @@ wait_for_will_msg(EventType, EventContent, State) -> handle_event(EventType, EventContent, wait_for_will_msg, State). connected(cast, {incoming, ?SN_REGISTER_MSG(_TopicId, MsgId, TopicName)}, - State = #state{clientid = ClientId, registry = Registry}) -> + State = #state{clientid = ClientId}) -> State0 = - case emqx_sn_registry:register_topic(Registry, self(), TopicName) of + case emqx_sn_registry:register_topic(ClientId, TopicName) of TopicId when is_integer(TopicId) -> ?LOG(debug, "register ClientId=~p, TopicName=~p, TopicId=~p", [ClientId, TopicName, TopicId]), send_message(?SN_REGACK_MSG(TopicId, MsgId, ?SN_RC_ACCEPTED), State); @@ -368,8 +366,9 @@ connected(cast, {incoming, ?SN_ADVERTISE_MSG(_GwId, _Radius)}, State) -> % ignore {keep_state, State}; -connected(cast, {incoming, ?SN_CONNECT_MSG(Flags, _ProtoId, Duration, ClientId)}, State) -> - do_2nd_connect(Flags, Duration, ClientId, State); +connected(cast, {incoming, ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId)}, _State) -> + ?LOG(warning, "Receive connect packet in wait_for_will_topic state", []), + keep_state_and_data; connected(cast, {outgoing, Packet}, State) -> {keep_state, handle_outgoing(Packet, State)}; @@ -439,12 +438,11 @@ asleep(cast, {incoming, ?SN_PUBREC_MSG(PubRec, MsgId)}, State) % 4) emq-sn regard this CONNECT as a signal to connected state, not a bootup CONNECT. For this reason, will procedure is lost % this should be a bug in mqtt-sn channel. asleep(cast, {incoming, ?SN_CONNECT_MSG(_Flags, _ProtoId, _Duration, _ClientId)}, - State = #state{keepalive_interval = _Interval}) -> - % device wakeup and goto connected state - % keepalive timer may timeout in asleep state and delete itself, need to restart keepalive - % TODO: Fixme later. - %% self() ! {keepalive, start, Interval}, - {next_state, connected, send_connack(State)}; + State = #state{channel = Channel, asleep_timer = Timer}) -> + NChannel = emqx_channel:ensure_keepalive(#{}, Channel), + emqx_sn_asleep_timer:cancel(Timer), + {next_state, connected, send_connack(State#state{channel = NChannel, + asleep_timer = emqx_sn_asleep_timer:init()})}; asleep(EventType, EventContent, State) -> handle_event(EventType, EventContent, asleep, State). @@ -581,13 +579,16 @@ handle_event(EventType, EventContent, StateName, State) -> [StateName, {EventType, EventContent}]), {keep_state, State}. -terminate(Reason, _StateName, #state{channel = Channel, - registry = Registry}) -> - emqx_sn_registry:unregister_topic(Registry, self()), - case Channel =:= undefined of - true -> ok; - false -> emqx_channel:terminate(Reason, Channel) - end. +terminate(Reason, _StateName, #state{channel = Channel}) -> + ClientId = emqx_channel:info(clientid, Channel), + case Reason of + {shutdown, takeovered} -> + ok; + _ -> + emqx_sn_registry:unregister_topic(ClientId) + end, + emqx_channel:terminate(Reason, Channel), + ok. code_change(_Vsn, StateName, State, _Extra) -> {ok, StateName, State}. @@ -720,11 +721,12 @@ mqtt2sn(?PUBCOMP_PACKET(MsgId), _State) -> mqtt2sn(?UNSUBACK_PACKET(MsgId), _State)-> ?SN_UNSUBACK_MSG(MsgId); -mqtt2sn(?PUBLISH_PACKET(QoS, Topic, PacketId, Payload), #state{registry = Registry}) -> +mqtt2sn(?PUBLISH_PACKET(QoS, Topic, PacketId, Payload), #state{channel = Channel}) -> NewPacketId = if QoS =:= ?QOS_0 -> 0; true -> PacketId end, - {TopicIdType, TopicContent} = case emqx_sn_registry:lookup_topic_id(Registry, self(), Topic) of + ClientId = emqx_channel:info(clientid, Channel), + {TopicIdType, TopicContent} = case emqx_sn_registry:lookup_topic_id(ClientId, Topic) of {predef, PredefTopicId} -> {?SN_PREDEFINED_TOPIC, PredefTopicId}; TopicId when is_integer(TopicId) -> @@ -771,10 +773,13 @@ send_message(Msg = #mqtt_sn_message{type = Type}, goto_asleep_state(State) -> goto_asleep_state(undefined, State). -goto_asleep_state(Duration, State=#state{asleep_timer = AsleepTimer}) -> +goto_asleep_state(Duration, State=#state{asleep_timer = AsleepTimer, + channel = Channel}) -> ?LOG(debug, "goto_asleep_state Duration=~p", [Duration]), NewTimer = emqx_sn_asleep_timer:ensure(Duration, AsleepTimer), - {next_state, asleep, State#state{asleep_timer = NewTimer}, hibernate}. + NChannel = emqx_channel:clear_keepalive(Channel), + {next_state, asleep, State#state{asleep_timer = NewTimer, + channel = NChannel}, hibernate}. %%-------------------------------------------------------------------- %% Helper funcs @@ -823,15 +828,17 @@ do_connect(ClientId, CleanStart, WillFlag, Duration, State) -> clean_start = CleanStart, username = State#state.username, password = State#state.password, + proto_name = <<"MQTT-SN">>, keepalive = Duration, - properties = OnlyOneInflight + properties = OnlyOneInflight, + proto_ver = 1 }, case WillFlag of true -> State0 = send_message(?SN_WILLTOPICREQ_MSG(), State), NState = State0#state{connpkt = ConnPkt, - clientid = ClientId, - keepalive_interval = Duration - }, + clientid = ClientId, + keepalive_interval = Duration + }, {next_state, wait_for_will_topic, NState}; false -> NState = State#state{clientid = ClientId, @@ -840,30 +847,10 @@ do_connect(ClientId, CleanStart, WillFlag, Duration, State) -> handle_incoming(?CONNECT_PACKET(ConnPkt), NState) end. -do_2nd_connect(Flags, Duration, ClientId, State = #state{sockname = Sockname, - peername = Peername, - registry = Registry, - channel = Channel}) -> - emqx_logger:set_metadata_clientid(ClientId), - #mqtt_sn_flags{will = Will, clean_start = CleanStart} = Flags, - NChannel = case CleanStart of - true -> - emqx_channel:terminate(normal, Channel), - emqx_sn_registry:unregister_topic(Registry, self()), - emqx_channel:init(#{socktype => udp, - sockname => Sockname, - peername => Peername, - peercert => ?NO_PEERCERT, - conn_mod => ?MODULE - }, ?DEFAULT_CHAN_OPTIONS); - false -> Channel - end, - NState = State#state{channel = NChannel}, - do_connect(ClientId, CleanStart, Will, Duration, NState). - handle_subscribe(?SN_NORMAL_TOPIC, TopicName, QoS, MsgId, - State=#state{registry = Registry}) -> - case emqx_sn_registry:register_topic(Registry, self(), TopicName) of + State=#state{channel = Channel}) -> + ClientId = emqx_channel:info(clientid, Channel), + case emqx_sn_registry:register_topic(ClientId, TopicName) of {error, too_large} -> State0 = send_message(?SN_SUBACK_MSG(#mqtt_sn_flags{qos = QoS}, ?SN_INVALID_TOPIC_ID, @@ -877,8 +864,9 @@ handle_subscribe(?SN_NORMAL_TOPIC, TopicName, QoS, MsgId, end; handle_subscribe(?SN_PREDEFINED_TOPIC, TopicId, QoS, MsgId, - State = #state{registry = Registry}) -> - case emqx_sn_registry:lookup_topic(Registry, self(), TopicId) of + State = #state{channel = Channel}) -> + ClientId = emqx_channel:info(clientid, Channel), + case emqx_sn_registry:lookup_topic(ClientId, TopicId) of undefined -> State0 = send_message(?SN_SUBACK_MSG(#mqtt_sn_flags{qos = QoS}, TopicId, @@ -907,8 +895,9 @@ handle_unsubscribe(?SN_NORMAL_TOPIC, TopicId, MsgId, State) -> proto_unsubscribe(TopicId, MsgId, State); handle_unsubscribe(?SN_PREDEFINED_TOPIC, TopicId, MsgId, - State = #state{registry = Registry}) -> - case emqx_sn_registry:lookup_topic(Registry, self(), TopicId) of + State = #state{channel = Channel}) -> + ClientId = emqx_channel:info(clientid, Channel), + case emqx_sn_registry:lookup_topic(ClientId, TopicId) of undefined -> {keep_state, send_message(?SN_UNSUBACK_MSG(MsgId), State)}; PredefinedTopic -> @@ -930,10 +919,11 @@ do_publish(?SN_NORMAL_TOPIC, TopicName, Data, Flags, MsgId, State) -> <> = TopicName, do_publish(?SN_PREDEFINED_TOPIC, TopicId, Data, Flags, MsgId, State); do_publish(?SN_PREDEFINED_TOPIC, TopicId, Data, Flags, MsgId, - State=#state{registry = Registry}) -> + State=#state{channel = Channel}) -> #mqtt_sn_flags{qos = QoS, dup = Dup, retain = Retain} = Flags, NewQoS = get_corrected_qos(QoS), - case emqx_sn_registry:lookup_topic(Registry, self(), TopicId) of + ClientId = emqx_channel:info(clientid, Channel), + case emqx_sn_registry:lookup_topic(ClientId, TopicId) of undefined -> {keep_state, maybe_send_puback(NewQoS, TopicId, MsgId, ?SN_RC_INVALID_TOPIC_ID, State)}; @@ -944,7 +934,7 @@ do_publish(?SN_PREDEFINED_TOPIC, TopicId, Data, Flags, MsgId, do_publish(?SN_SHORT_TOPIC, STopicName, Data, Flags, MsgId, State) -> #mqtt_sn_flags{qos = QoS, dup = Dup, retain = Retain} = Flags, NewQoS = get_corrected_qos(QoS), - <> = STopicName , + <> = STopicName, case emqx_topic:wildcard(STopicName) of true -> {keep_state, maybe_send_puback(NewQoS, TopicId, MsgId, ?SN_RC_NOT_SUPPORTED, @@ -972,12 +962,13 @@ do_publish_will(#state{will_msg = WillMsg, clientid = ClientId}) -> ok. do_puback(TopicId, MsgId, ReturnCode, StateName, - State=#state{registry = Registry}) -> + State=#state{channel = Channel}) -> case ReturnCode of ?SN_RC_ACCEPTED -> handle_incoming(?PUBACK_PACKET(MsgId), StateName, State); ?SN_RC_INVALID_TOPIC_ID -> - case emqx_sn_registry:lookup_topic(Registry, self(), TopicId) of + ClientId = emqx_channel:info(clientid, Channel), + case emqx_sn_registry:lookup_topic(ClientId, TopicId) of undefined -> {keep_state, State}; TopicName -> %%notice that this TopicName maybe normal or predefined, @@ -1066,9 +1057,10 @@ handle_outgoing(Packets, State) when is_list(Packets) -> end, State, Packets); handle_outgoing(PubPkt = ?PUBLISH_PACKET(_, TopicName, _, _), - State = #state{registry = Registry}) -> + State = #state{channel = Channel}) -> ?LOG(debug, "Handle outgoing publish: ~0p", [PubPkt]), - TopicId = emqx_sn_registry:lookup_topic_id(Registry, self(), TopicName), + ClientId = emqx_channel:info(clientid, Channel), + TopicId = emqx_sn_registry:lookup_topic_id(ClientId, TopicName), case (TopicId == undefined) andalso (byte_size(TopicName) =/= 2) of true -> register_and_notify_client(PubPkt, State); false -> send_message(mqtt2sn(PubPkt, State), State) @@ -1092,10 +1084,11 @@ replay_no_reg_pending_publishes(TopicId, #state{pending_topic_ids = Pendings} = State#state{pending_topic_ids = maps:remove(TopicId, Pendings)}. register_and_notify_client(?PUBLISH_PACKET(QoS, TopicName, PacketId, Payload) = PubPkt, - State = #state{registry = Registry, pending_topic_ids = Pendings}) -> + State = #state{pending_topic_ids = Pendings, channel = Channel}) -> MsgId = message_id(PacketId), #mqtt_packet{header = #mqtt_packet_header{dup = Dup, retain = Retain}} = PubPkt, - TopicId = emqx_sn_registry:register_topic(Registry, self(), TopicName), + ClientId = emqx_channel:info(clientid, Channel), + TopicId = emqx_sn_registry:register_topic(ClientId, TopicName), ?LOG(debug, "Register TopicId=~p, TopicName=~p, Payload=~p, Dup=~p, QoS=~p, " "Retain=~p, MsgId=~p", [TopicId, TopicName, Payload, Dup, QoS, Retain, MsgId]), NewPendings = cache_no_reg_publish_message(Pendings, TopicId, PubPkt, State), diff --git a/apps/emqx_sn/src/emqx_sn_registry.erl b/apps/emqx_sn/src/emqx_sn_registry.erl index fa17ebc277..c97a49f5ae 100644 --- a/apps/emqx_sn/src/emqx_sn_registry.erl +++ b/apps/emqx_sn/src/emqx_sn_registry.erl @@ -23,16 +23,16 @@ -define(LOG(Level, Format, Args), emqx_logger:Level("MQTT-SN(registry): " ++ Format, Args)). --export([ start_link/2 - , stop/1 +-export([ start_link/1 + , stop/0 ]). --export([ register_topic/3 - , unregister_topic/2 +-export([ register_topic/2 + , unregister_topic/1 ]). --export([ lookup_topic/3 - , lookup_topic_id/3 +-export([ lookup_topic/2 + , lookup_topic_id/2 ]). %% gen_server callbacks @@ -44,27 +44,31 @@ , code_change/3 ]). +-ifdef(TEST). +-export([create_table/0]). +-endif. + -define(TAB, ?MODULE). --record(state, {tab, max_predef_topic_id = 0}). +-record(state, {max_predef_topic_id = 0}). --type(registry() :: {ets:tab(), pid()}). +-record(emqx_sn_registry, {key, value}). %%----------------------------------------------------------------------------- --spec(start_link(atom(), list()) -> {ok, pid()} | ignore | {error, Reason :: term()}). -start_link(Tab, PredefTopics) -> - gen_server:start_link(?MODULE, [Tab, PredefTopics], []). +-spec(start_link(list()) -> {ok, pid()} | ignore | {error, Reason :: term()}). +start_link(PredefTopics) -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [PredefTopics], []). --spec(stop(registry()) -> ok). -stop({_Tab, Pid}) -> - gen_server:stop(Pid, normal, infinity). +-spec(stop() -> ok). +stop() -> + gen_server:stop(?MODULE, normal, infinity). --spec(register_topic(registry(), pid(), binary()) -> integer() | {error, term()}). -register_topic({_, Pid}, ClientPid, TopicName) when is_binary(TopicName) -> +-spec(register_topic(binary(), binary()) -> integer() | {error, term()}). +register_topic(ClientId, TopicName) when is_binary(TopicName) -> case emqx_topic:wildcard(TopicName) of false -> - gen_server:call(Pid, {register, ClientPid, TopicName}); + gen_server:call(?MODULE, {register, ClientId, TopicName}); %% TopicId: in case of “accepted” the value that will be used as topic %% id by the gateway when sending PUBLISH messages to the client (not %% relevant in case of subscriptions to a short topic name or to a topic @@ -72,22 +76,22 @@ register_topic({_, Pid}, ClientPid, TopicName) when is_binary(TopicName) -> true -> {error, wildcard_topic} end. --spec(lookup_topic(registry(), pid(), pos_integer()) -> undefined | binary()). -lookup_topic({Tab, _Pid}, ClientPid, TopicId) when is_integer(TopicId) -> - case lookup_element(Tab, {predef, TopicId}, 2) of +-spec(lookup_topic(binary(), pos_integer()) -> undefined | binary()). +lookup_topic(ClientId, TopicId) when is_integer(TopicId) -> + case lookup_element(?TAB, {predef, TopicId}, 3) of undefined -> - lookup_element(Tab, {ClientPid, TopicId}, 2); + lookup_element(?TAB, {ClientId, TopicId}, 3); Topic -> Topic end. --spec(lookup_topic_id(registry(), pid(), binary()) +-spec(lookup_topic_id(binary(), binary()) -> undefined | pos_integer() | {predef, integer()}). -lookup_topic_id({Tab, _Pid}, ClientPid, TopicName) when is_binary(TopicName) -> - case lookup_element(Tab, {predef, TopicName}, 2) of +lookup_topic_id(ClientId, TopicName) when is_binary(TopicName) -> + case lookup_element(?TAB, {predef, TopicName}, 3) of undefined -> - lookup_element(Tab, {ClientPid, TopicName}, 2); + lookup_element(?TAB, {ClientId, TopicName}, 3); TopicId -> {predef, TopicId} end. @@ -96,47 +100,69 @@ lookup_topic_id({Tab, _Pid}, ClientPid, TopicName) when is_binary(TopicName) -> lookup_element(Tab, Key, Pos) -> try ets:lookup_element(Tab, Key, Pos) catch error:badarg -> undefined end. --spec(unregister_topic(registry(), pid()) -> ok). -unregister_topic({_Tab, Pid}, ClientPid) -> - gen_server:call(Pid, {unregister, ClientPid}). +-spec(unregister_topic(binary()) -> ok). +unregister_topic(ClientId) -> + gen_server:call(?MODULE, {unregister, ClientId}). %%----------------------------------------------------------------------------- -init([Tab, PredefTopics]) -> +init([PredefTopics]) -> + create_table(), %% {predef, TopicId} -> TopicName %% {predef, TopicName} -> TopicId - %% {ClientPid, TopicId} -> TopicName - %% {ClientPid, TopicName} -> TopicId - _ = ets:new(Tab, [set, public, named_table, {read_concurrency, true}]), + %% {ClientId, TopicId} -> TopicName + %% {ClientId, TopicName} -> TopicId MaxPredefId = lists:foldl( fun({TopicId, TopicName}, AccId) -> - _ = ets:insert(Tab, {{predef, TopicId}, TopicName}), - _ = ets:insert(Tab, {{predef, TopicName}, TopicId}), + mnesia:dirty_write(#emqx_sn_registry{key = {predef, TopicId}, + value = TopicName}), + mnesia:dirty_write(#emqx_sn_registry{key = {predef, TopicName}, + value = TopicId}), if TopicId > AccId -> TopicId; true -> AccId end end, 0, PredefTopics), - {ok, #state{tab = Tab, max_predef_topic_id = MaxPredefId}}. - -handle_call({register, ClientPid, TopicName}, _From, - State = #state{tab = Tab, max_predef_topic_id = PredefId}) -> - case lookup_topic_id({Tab, self()}, ClientPid, TopicName) of + {ok, #state{max_predef_topic_id = MaxPredefId}}. + +create_table() -> + %% Optimize storage + StoreProps = [{ets, [{read_concurrency, true}]}], + ok = ekka_mnesia:create_table(?MODULE, [ + {attributes, record_info(fields, emqx_sn_registry)}, + {ram_copies, [node()]}, + {storage_properties, StoreProps}]), + ok = ekka_mnesia:copy_table(?MODULE, ram_copies). + +handle_call({register, ClientId, TopicName}, _From, + State = #state{max_predef_topic_id = PredefId}) -> + case lookup_topic_id(ClientId, TopicName) of {predef, PredefTopicId} when is_integer(PredefTopicId) -> {reply, PredefTopicId, State}; TopicId when is_integer(TopicId) -> {reply, TopicId, State}; undefined -> - case next_topic_id(Tab, PredefId, ClientPid) of + case next_topic_id(?TAB, PredefId, ClientId) of TopicId when TopicId >= 16#FFFF -> {reply, {error, too_large}, State}; TopicId -> - _ = ets:insert(Tab, {{ClientPid, next_topic_id}, TopicId + 1}), - _ = ets:insert(Tab, {{ClientPid, TopicName}, TopicId}), - _ = ets:insert(Tab, {{ClientPid, TopicId}, TopicName}), - {reply, TopicId, State} + Fun = fun() -> + mnesia:write(#emqx_sn_registry{key = {ClientId, next_topic_id}, + value = TopicId + 1}), + mnesia:write(#emqx_sn_registry{key = {ClientId, TopicName}, + value = TopicId}), + mnesia:write(#emqx_sn_registry{key = {ClientId, TopicId}, + value = TopicName}) + end, + case mnesia:transaction(Fun) of + {atomic, ok} -> + {reply, TopicId, State}; + {aborted, Error} -> + {reply, {error, Error}, State} + end end end; -handle_call({unregister, ClientPid}, _From, State = #state{tab = Tab}) -> - ets:match_delete(Tab, {{ClientPid, '_'}, '_'}), +handle_call({unregister, ClientId}, _From, State) -> + Registry = mnesia:dirty_match_object({?TAB, {ClientId, '_'}, '_'}), + lists:foreach(fun(R) -> mnesia:dirty_delete_object(R) end, Registry), {reply, ok, State}; handle_call(Req, _From, State) -> @@ -159,8 +185,8 @@ code_change(_OldVsn, State, _Extra) -> %%----------------------------------------------------------------------------- -next_topic_id(Tab, PredefId, ClientPid) -> - case ets:lookup(Tab, {ClientPid, next_topic_id}) of - [{_, Id}] -> Id; +next_topic_id(Tab, PredefId, ClientId) -> + case mnesia:dirty_read(Tab, {ClientId, next_topic_id}) of + [#emqx_sn_registry{value = Id}] -> Id; [] -> PredefId + 1 end. diff --git a/apps/emqx_sn/src/emqx_sn_sup.erl b/apps/emqx_sn/src/emqx_sn_sup.erl index 817aa4d06a..3d4fe602f7 100644 --- a/apps/emqx_sn/src/emqx_sn_sup.erl +++ b/apps/emqx_sn/src/emqx_sn_sup.erl @@ -18,32 +18,26 @@ -behaviour(supervisor). --export([ start_link/2 - , start_registry_proc/3 +-export([ start_link/3 , init/1 ]). -start_registry_proc(Sup, TabName, PredefTopics) -> - Registry = #{id => TabName, - start => {emqx_sn_registry, start_link, [TabName, PredefTopics]}, - restart => permanent, - shutdown => 5000, - type => worker, - modules => [emqx_sn_registry]}, - handle_ret(supervisor:start_child(Sup, Registry)). +start_link(Addr, GwId, PredefTopics) -> + supervisor:start_link({local, ?MODULE}, ?MODULE, [Addr, GwId, PredefTopics]). -start_link(Addr, GwId) -> - supervisor:start_link({local, ?MODULE}, ?MODULE, [Addr, GwId]). - -init([{_Ip, Port}, GwId]) -> +init([{_Ip, Port}, GwId, PredefTopics]) -> Broadcast = #{id => emqx_sn_broadcast, start => {emqx_sn_broadcast, start_link, [GwId, Port]}, restart => permanent, shutdown => brutal_kill, type => worker, modules => [emqx_sn_broadcast]}, - {ok, {{one_for_one, 10, 3600}, [Broadcast]}}. + Registry = #{id => emqx_sn_registry, + start => {emqx_sn_registry, start_link, [PredefTopics]}, + restart => permanent, + shutdown => brutal_kill, + type => worker, + modules => [emqx_sn_registry]}, + {ok, {{one_for_one, 10, 3600}, [Broadcast, Registry]}}. -handle_ret({ok, Pid, _Info}) -> {ok, Pid}; -handle_ret(Ret) -> Ret. diff --git a/apps/emqx_sn/test/emqx_sn_protocol_SUITE.erl b/apps/emqx_sn/test/emqx_sn_protocol_SUITE.erl index ad0c5f0329..0947bdaca3 100644 --- a/apps/emqx_sn/test/emqx_sn_protocol_SUITE.erl +++ b/apps/emqx_sn/test/emqx_sn_protocol_SUITE.erl @@ -47,6 +47,9 @@ % FLAG NOT USED -define(FNU, 0). +%% erlang:system_time should be unique and random enough +-define(CLIENTID, iolist_to_binary([atom_to_list(?FUNCTION_NAME), "-", + integer_to_list(erlang:system_time())])). %%-------------------------------------------------------------------- %% Setups %%-------------------------------------------------------------------- @@ -55,6 +58,7 @@ all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> + logger:set_module_level(emqx_sn_gateway, debug), emqx_ct_helpers:start_apps([emqx_sn], fun set_special_confs/1), Config. @@ -94,18 +98,6 @@ t_connect(_) -> ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), gen_udp:close(Socket). -t_do_2nd_connect(_) -> - {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"client_id_test">>), - ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), - timer:sleep(100), - send_connect_msg(Socket, <<"client_id_other">>), - ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), - - send_disconnect_msg(Socket, undefined), - ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), - gen_udp:close(Socket). - t_subscribe(_) -> Dup = 0, QoS = 0, @@ -116,7 +108,8 @@ t_subscribe(_) -> TopicId = ?MAX_PRED_TOPIC_ID + 1, ReturnCode = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"client_id_test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), TopicName1 = <<"abcD">>, send_register_msg(Socket, TopicName1, MsgId), @@ -125,12 +118,12 @@ t_subscribe(_) -> ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId:16, MsgId:16, ReturnCode>>, receive_response(Socket)), - ?assertEqual([TopicName1], emqx_broker:topics()), + ?assert(lists:member(TopicName1, emqx_broker:topics())), send_unsubscribe_msg_normal_topic(Socket, TopicName1, MsgId), ?assertEqual(<<4, ?SN_UNSUBACK, MsgId:16>>, receive_response(Socket)), timer:sleep(100), - ?assertEqual([], emqx_broker:topics()), + ?assertNot(lists:member(TopicName1, emqx_broker:topics())), send_disconnect_msg(Socket, undefined), ?assertEqual(<<2, ?SN_DISCONNECT>>, receive_response(Socket)), @@ -146,7 +139,8 @@ t_subscribe_case01(_) -> TopicId = ?MAX_PRED_TOPIC_ID + 1, ReturnCode = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"client_id_test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), TopicName1 = <<"abcD">>, @@ -176,7 +170,8 @@ t_subscribe_case02(_) -> ReturnCode = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"client_id_test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ?CLIENTID), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), Topic1 = ?PREDEF_TOPIC_NAME1, @@ -206,7 +201,7 @@ t_subscribe_case03(_) -> ReturnCode = 0, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"ClientA">>, + ClientId = ?CLIENTID, send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), @@ -234,7 +229,8 @@ t_subscribe_case04(_) -> TopicId = ?PREDEF_TOPIC_ID1, %this TopicId is the predefined topic id corresponding to ?PREDEF_TOPIC_NAME1 ReturnCode = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"client_id_test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), Topic1 = ?PREDEF_TOPIC_NAME1, send_register_msg(Socket, Topic1, MsgId), @@ -263,7 +259,7 @@ t_subscribe_case05(_) -> TopicId2 = ?MAX_PRED_TOPIC_ID + 2, ReturnCode = 0, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"testu">>, + ClientId = ?CLIENTID, send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), @@ -304,8 +300,9 @@ t_subscribe_case06(_) -> TopicId1 = ?MAX_PRED_TOPIC_ID + 1, TopicId2 = ?MAX_PRED_TOPIC_ID + 2, ReturnCode = 0, + ClientId = ?CLIENTID, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_register_msg(Socket, <<"abc">>, MsgId), @@ -340,7 +337,8 @@ t_subscribe_case07(_) -> TopicId1 = ?MAX_PRED_TOPIC_ID + 2, TopicId2 = ?MAX_PRED_TOPIC_ID + 3, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_predefined_topic(Socket, QoS, TopicId1, MsgId), @@ -362,7 +360,8 @@ t_subscribe_case08(_) -> MsgId = 1, TopicId2 = 2, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_reserved_topic(Socket, QoS, TopicId2, MsgId), @@ -383,7 +382,8 @@ t_publish_negqos_case09(_) -> MsgId = 1, TopicId1 = ?MAX_PRED_TOPIC_ID + 1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), Topic = <<"abc">>, @@ -416,7 +416,8 @@ t_publish_qos0_case01(_) -> MsgId = 1, TopicId1 = ?MAX_PRED_TOPIC_ID + 1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), Topic = <<"abc">>, @@ -447,7 +448,8 @@ t_publish_qos0_case02(_) -> MsgId = 1, PredefTopicId = ?PREDEF_TOPIC_ID1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId), @@ -476,7 +478,8 @@ t_publish_qos0_case3(_) -> MsgId = 1, TopicId = ?MAX_PRED_TOPIC_ID + 1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), Topic = <<"/a/b/c">>, @@ -506,7 +509,8 @@ t_publish_qos0_case04(_) -> MsgId = 1, TopicId0 = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_normal_topic(Socket, QoS, <<"#">>, MsgId), @@ -536,7 +540,8 @@ t_publish_qos0_case05(_) -> MsgId = 1, TopicId0 = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_short_topic(Socket, QoS, <<"/#">>, MsgId), ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, ?SN_NORMAL_TOPIC:2, TopicId0:16, MsgId:16, ?SN_RC_ACCEPTED>>, @@ -556,7 +561,8 @@ t_publish_qos0_case06(_) -> MsgId = 1, TopicId1 = ?MAX_PRED_TOPIC_ID + 1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), Topic = <<"abc">>, @@ -587,7 +593,8 @@ t_publish_qos1_case01(_) -> TopicId1 = ?MAX_PRED_TOPIC_ID + 1, Topic = <<"abc">>, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId), ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, @@ -613,7 +620,8 @@ t_publish_qos1_case02(_) -> MsgId = 1, PredefTopicId = ?PREDEF_TOPIC_ID1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId), @@ -633,7 +641,8 @@ t_publish_qos1_case03(_) -> MsgId = 1, TopicId5 = 5, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_publish_msg_predefined_topic(Socket, QoS, MsgId, tid(5), <<20, 21, 22, 23>>), ?assertEqual(<<7, ?SN_PUBACK, TopicId5:16, MsgId:16, ?SN_RC_INVALID_TOPIC_ID>>, receive_response(Socket)), @@ -651,7 +660,8 @@ t_publish_qos1_case04(_) -> MsgId = 7, TopicId0 = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_short_topic(Socket, QoS, <<"ab">>, MsgId), ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, Will:1, CleanSession:1, @@ -677,7 +687,8 @@ t_publish_qos1_case05(_) -> MsgId = 7, TopicId1 = ?MAX_PRED_TOPIC_ID + 1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_normal_topic(Socket, QoS, <<"ab">>, MsgId), @@ -702,7 +713,8 @@ t_publish_qos1_case06(_) -> MsgId = 7, TopicId1 = ?MAX_PRED_TOPIC_ID + 1, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_normal_topic(Socket, QoS, <<"ab">>, MsgId), @@ -728,7 +740,8 @@ t_publish_qos2_case01(_) -> TopicId1 = ?MAX_PRED_TOPIC_ID + 1, Topic = <<"/abc">>, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_normal_topic(Socket, QoS, Topic, MsgId), ?assertEqual(<<8, ?SN_SUBACK, ?FNU:1, QoS:2, ?FNU:5, TopicId1:16, MsgId:16, @@ -755,7 +768,8 @@ t_publish_qos2_case02(_) -> MsgId = 7, PredefTopicId = ?PREDEF_TOPIC_ID2, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_predefined_topic(Socket, QoS, PredefTopicId, MsgId), @@ -785,7 +799,8 @@ t_publish_qos2_case03(_) -> MsgId = 7, TopicId0 = 0, {ok, Socket} = gen_udp:open(0, [binary]), - send_connect_msg(Socket, <<"test">>), + ClientId = ?CLIENTID, + send_connect_msg(Socket, ClientId), ?assertEqual(<<3, ?SN_CONNACK, 0>>, receive_response(Socket)), send_subscribe_msg_normal_topic(Socket, QoS, <<"/#">>, MsgId), @@ -811,7 +826,7 @@ t_will_case01(_) -> WillMsg = <<10, 11, 12, 13, 14>>, WillTopic = <<"abc">>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, ok = emqx_broker:subscribe(WillTopic), @@ -846,7 +861,7 @@ t_will_test2(_) -> Duration = 1, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, <<"goodbye">>, QoS), @@ -856,7 +871,7 @@ t_will_test2(_) -> send_pingreq_msg(Socket, undefined), ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), - timer:sleep(10000), + timer:sleep(4000), receive_response(Socket), % ignore PUBACK receive_response(Socket), % ignore PUBCOMP @@ -870,7 +885,7 @@ t_will_test3(_) -> Duration = 1, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_empty_msg(Socket), @@ -878,7 +893,7 @@ t_will_test3(_) -> send_pingreq_msg(Socket, undefined), ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), - timer:sleep(10000), + timer:sleep(4000), ?assertEqual(udp_receive_timeout, receive_response(Socket)), @@ -892,7 +907,7 @@ t_will_test4(_) -> Duration = 1, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, <<"abc">>, QoS), @@ -906,7 +921,7 @@ t_will_test4(_) -> send_willmsgupd_msg(Socket, <<"1A2B3C">>), ?assertEqual(<<3, ?SN_WILLMSGRESP, ?SN_RC_ACCEPTED>>, receive_response(Socket)), - timer:sleep(10000), + timer:sleep(4000), receive_response(Socket), % ignore PUBACK @@ -920,7 +935,7 @@ t_will_test5(_) -> Duration = 5, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, <<"abc">>, QoS), @@ -947,7 +962,7 @@ t_will_case06(_) -> WillMsg = <<10, 11, 12, 13, 14>>, WillTopic = <<"abc">>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, ok = emqx_broker:subscribe(WillTopic), @@ -983,7 +998,7 @@ t_asleep_test01_timeout(_) -> WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1007,7 +1022,7 @@ t_asleep_test02_to_awake_and_back(_) -> WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Keepalive_Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1022,13 +1037,13 @@ t_asleep_test02_to_awake_and_back(_) -> timer:sleep(4500), % goto awake state and back - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), timer:sleep(4500), % goto awake state and back - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), %% during above procedure, mqtt keepalive timer should not terminate mqtt-sn process @@ -1045,7 +1060,7 @@ t_asleep_test03_to_awake_qos1_dl_msg(_) -> WillPayload = <<10, 11, 12, 13, 14>>, MsgId = 1000, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1084,7 +1099,7 @@ t_asleep_test03_to_awake_qos1_dl_msg(_) -> {ok, C} = emqtt:start_link(), {ok, _} = emqtt:connect(C), {ok, _} = emqtt:publish(C, TopicName1, Payload1, QoS), - timer:sleep(500), + timer:sleep(100), ok = emqtt:disconnect(C), timer:sleep(50), @@ -1108,7 +1123,7 @@ t_asleep_test04_to_awake_qos1_dl_msg(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1149,7 +1164,7 @@ t_asleep_test04_to_awake_qos1_dl_msg(_) -> timer:sleep(300), % goto awake state, receive downlink messages, and go back to asleep - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), %% 1. get REGISTER first, since this topic has never been registered UdpData1 = receive_response(Socket), @@ -1183,7 +1198,7 @@ t_asleep_test05_to_awake_qos1_dl_msg(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1228,7 +1243,7 @@ t_asleep_test05_to_awake_qos1_dl_msg(_) -> timer:sleep(50), % goto awake state, receive downlink messages, and go back to asleep - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), UdpData_reg = receive_response(Socket), {TopicIdNew, MsgId_reg} = check_register_msg_on_udp(TopicName_test5, UdpData_reg), @@ -1261,7 +1276,7 @@ t_asleep_test06_to_awake_qos2_dl_msg(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1278,6 +1293,7 @@ t_asleep_test06_to_awake_qos2_dl_msg(_) -> CleanSession = 0, ReturnCode = 0, send_register_msg(Socket, TopicName_tom, MsgId1), + timer:sleep(50), TopicId_tom = check_regack_msg_on_udp(MsgId1, receive_response(Socket)), send_subscribe_msg_predefined_topic(Socket, QoS, TopicId_tom, MsgId1), ?assertEqual(<<8, ?SN_SUBACK, Dup:1, QoS:2, Retain:1, WillBit:1, CleanSession:1, @@ -1303,7 +1319,7 @@ t_asleep_test06_to_awake_qos2_dl_msg(_) -> timer:sleep(300), % goto awake state, receive downlink messages, and go back to asleep - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), UdpData = wrap_receive_response(Socket), MsgId_udp = check_publish_msg_on_udp({Dup, QoS, Retain, WillBit, CleanSession, ?SN_NORMAL_TOPIC, TopicId_tom, Payload1}, UdpData), @@ -1322,7 +1338,7 @@ t_asleep_test07_to_connected(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Keepalive_Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1359,7 +1375,7 @@ t_asleep_test07_to_connected(_) -> timer:sleep(1500), % asleep timer should get timeout, without any effect - timer:sleep(9000), + timer:sleep(4000), % keepalive timer should get timeout gen_udp:close(Socket). @@ -1371,7 +1387,7 @@ t_asleep_test08_to_disconnected(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Keepalive_Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1402,7 +1418,7 @@ t_asleep_test09_to_awake_again_qos1_dl_msg(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1445,7 +1461,7 @@ t_asleep_test09_to_awake_again_qos1_dl_msg(_) -> ok = emqtt:disconnect(C), % goto awake state, receive downlink messages, and go back to asleep - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), UdpData_reg = receive_response(Socket), {TopicIdNew, MsgId_reg} = check_register_msg_on_udp(TopicName_test9, UdpData_reg), @@ -1481,7 +1497,7 @@ t_asleep_test09_to_awake_again_qos1_dl_msg(_) -> ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), %% send PINGREQ again to enter awake state - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), %% will not receive any buffered PUBLISH messages buffered before last awake, only receive PINGRESP here ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), @@ -1494,7 +1510,7 @@ t_awake_test01_to_connected(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Keepalive_Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1517,7 +1533,7 @@ t_awake_test01_to_connected(_) -> timer:sleep(1500), % asleep timer should get timeout - timer:sleep(9000), + timer:sleep(4000), % keepalive timer should get timeout gen_udp:close(Socket). @@ -1528,7 +1544,7 @@ t_awake_test02_to_disconnected(_) -> WillTopic = <<"dead">>, WillPayload = <<10, 11, 12, 13, 14>>, {ok, Socket} = gen_udp:open(0, [binary]), - ClientId = <<"test">>, + ClientId = ?CLIENTID, send_connect_msg_with_will(Socket, Keepalive_Duration, ClientId), ?assertEqual(<<2, ?SN_WILLTOPICREQ>>, receive_response(Socket)), send_willtopic_msg(Socket, WillTopic, QoS), @@ -1544,7 +1560,7 @@ t_awake_test02_to_disconnected(_) -> timer:sleep(100), % goto awake state - send_pingreq_msg(Socket, <<"test">>), + send_pingreq_msg(Socket, ClientId), ?assertEqual(<<2, ?SN_PINGRESP>>, receive_response(Socket)), %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1591,7 +1607,7 @@ send_connect_msg(Socket, ClientId) -> ok = gen_udp:send(Socket, ?HOST, ?PORT, Packet). send_connect_msg_with_will(Socket, Duration, ClientId) -> - Length = 10, + Length = 6 + byte_size(ClientId), Will = 1, CleanSession = 1, ProtocolId = 1, @@ -1600,7 +1616,7 @@ send_connect_msg_with_will(Socket, Duration, ClientId) -> ok = gen_udp:send(Socket, ?HOST, ?PORT, ConnectPacket). send_connect_msg_with_will1(Socket, Duration, ClientId) -> - Length = 10, + Length = 6 + byte_size(ClientId), Will = 1, CleanSession = 0, ProtocolId = 1, diff --git a/apps/emqx_sn/test/emqx_sn_registry_SUITE.erl b/apps/emqx_sn/test/emqx_sn_registry_SUITE.erl index 8cce4592a9..023d83ae56 100644 --- a/apps/emqx_sn/test/emqx_sn_registry_SUITE.erl +++ b/apps/emqx_sn/test/emqx_sn_registry_SUITE.erl @@ -16,12 +16,9 @@ -module(emqx_sn_registry_SUITE). --import(proplists, [get_value/2]). - -compile(export_all). -compile(nowarn_export_all). --include_lib("emqx_sn/include/emqx_sn.hrl"). -include_lib("eunit/include/eunit.hrl"). -define(REGISTRY, emqx_sn_registry). @@ -44,84 +41,80 @@ end_per_suite(_Config) -> ok. init_per_testcase(_TestCase, Config) -> + ekka_mnesia:start(), + emqx_sn_registry:create_table(), + mnesia:clear_table(emqx_sn_registry), PredefTopics = application:get_env(emqx_sn, predefined, []), - TabName = emqx_sn_registry, - {ok, Pid} = ?REGISTRY:start_link(TabName, PredefTopics), - [{registray, {TabName, Pid}} | Config]. + {ok, _Pid} = ?REGISTRY:start_link(PredefTopics), + Config. end_per_testcase(_TestCase, Config) -> - ?REGISTRY:stop(get_value(registray, Config)), + ?REGISTRY:stop(), Config. %%-------------------------------------------------------------------- %% Test cases %%-------------------------------------------------------------------- -t_register(Config) -> - Registry = get_value(registray, Config), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"Topic1">>)), - ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"Topic2">>)), - ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+1)), - ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+2)), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic1">>)), - ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic2">>)), - emqx_sn_registry:unregister_topic(Registry, <<"ClientId">>), - ?assertEqual(undefined, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+1)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+2)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic1">>)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic2">>)). - -t_register_case2(Config) -> - Registry = get_value(registray, Config), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"Topic1">>)), - ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"Topic2">>)), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"Topic1">>)), - ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+1)), - ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+2)), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic1">>)), - ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic2">>)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic3">>)), - ?REGISTRY:unregister_topic(Registry, <<"ClientId">>), - ?assertEqual(undefined, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+1)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+2)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic1">>)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, <<"Topic2">>)). - -t_reach_maximum(Config) -> - Registry = get_value(registray, Config), - register_a_lot(Registry, ?MAX_PREDEF_ID+1, 16#ffff), - ?assertEqual({error, too_large}, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"TopicABC">>)), +t_register(_Config) -> + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(<<"ClientId">>, <<"Topic1">>)), + ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(<<"ClientId">>, <<"Topic2">>)), + ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+1)), + ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+2)), + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic1">>)), + ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic2">>)), + emqx_sn_registry:unregister_topic(<<"ClientId">>), + ?assertEqual(undefined, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+1)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+2)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic1">>)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic2">>)). + +t_register_case2(_Config) -> + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(<<"ClientId">>, <<"Topic1">>)), + ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(<<"ClientId">>, <<"Topic2">>)), + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(<<"ClientId">>, <<"Topic1">>)), + ?assertEqual(<<"Topic1">>, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+1)), + ?assertEqual(<<"Topic2">>, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+2)), + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic1">>)), + ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic2">>)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic3">>)), + ?REGISTRY:unregister_topic(<<"ClientId">>), + ?assertEqual(undefined, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+1)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+2)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic1">>)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(<<"ClientId">>, <<"Topic2">>)). + +t_reach_maximum(_Config) -> + register_a_lot(?MAX_PREDEF_ID+1, 16#ffff), + ?assertEqual({error, too_large}, ?REGISTRY:register_topic(<<"ClientId">>, <<"TopicABC">>)), Topic1 = iolist_to_binary(io_lib:format("Topic~p", [?MAX_PREDEF_ID+1])), Topic2 = iolist_to_binary(io_lib:format("Topic~p", [?MAX_PREDEF_ID+2])), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, Topic1)), - ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, Topic2)), - ?REGISTRY:unregister_topic(Registry, <<"ClientId">>), - ?assertEqual(undefined, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+1)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic(Registry, <<"ClientId">>, ?MAX_PREDEF_ID+2)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, Topic1)), - ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(Registry, <<"ClientId">>, Topic2)). - -t_register_case4(Config) -> - Registry = get_value(registray, Config), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"TopicA">>)), - ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"TopicB">>)), - ?assertEqual(?MAX_PREDEF_ID+3, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"TopicC">>)), - ?REGISTRY:unregister_topic(Registry, <<"ClientId">>), - ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"TopicD">>)). - -t_deny_wildcard_topic(Config) -> - Registry = get_value(registray, Config), - ?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"/TopicA/#">>)), - ?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(Registry, <<"ClientId">>, <<"/+/TopicB">>)). + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:lookup_topic_id(<<"ClientId">>, Topic1)), + ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:lookup_topic_id(<<"ClientId">>, Topic2)), + ?REGISTRY:unregister_topic(<<"ClientId">>), + ?assertEqual(undefined, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+1)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic(<<"ClientId">>, ?MAX_PREDEF_ID+2)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(<<"ClientId">>, Topic1)), + ?assertEqual(undefined, ?REGISTRY:lookup_topic_id(<<"ClientId">>, Topic2)). + +t_register_case4(_Config) -> + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(<<"ClientId">>, <<"TopicA">>)), + ?assertEqual(?MAX_PREDEF_ID+2, ?REGISTRY:register_topic(<<"ClientId">>, <<"TopicB">>)), + ?assertEqual(?MAX_PREDEF_ID+3, ?REGISTRY:register_topic(<<"ClientId">>, <<"TopicC">>)), + ?REGISTRY:unregister_topic(<<"ClientId">>), + ?assertEqual(?MAX_PREDEF_ID+1, ?REGISTRY:register_topic(<<"ClientId">>, <<"TopicD">>)). + +t_deny_wildcard_topic(_Config) -> + ?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(<<"ClientId">>, <<"/TopicA/#">>)), + ?assertEqual({error, wildcard_topic}, ?REGISTRY:register_topic(<<"ClientId">>, <<"/+/TopicB">>)). %%-------------------------------------------------------------------- %% Helper funcs %%-------------------------------------------------------------------- -register_a_lot(_, Max, Max) -> +register_a_lot(Max, Max) -> ok; -register_a_lot(Registry, N, Max) when N < Max -> +register_a_lot(N, Max) when N < Max -> Topic = iolist_to_binary(["Topic", integer_to_list(N)]), - ?assertEqual(N, ?REGISTRY:register_topic(Registry, <<"ClientId">>, Topic)), - register_a_lot(Registry, N+1, Max). - + ?assertEqual(N, ?REGISTRY:register_topic(<<"ClientId">>, Topic)), + register_a_lot(N+1, Max). diff --git a/apps/emqx_stomp/src/emqx_stomp.app.src b/apps/emqx_stomp/src/emqx_stomp.app.src index 529ef14c72..c2f4b57d38 100644 --- a/apps/emqx_stomp/src/emqx_stomp.app.src +++ b/apps/emqx_stomp/src/emqx_stomp.app.src @@ -1,6 +1,6 @@ {application, emqx_stomp, [{description, "EMQ X Stomp Protocol Plugin"}, - {vsn, "4.3.0"}, % strict semver, bump manually! + {vsn, "4.3.3"}, % strict semver, bump manually! {modules, []}, {registered, [emqx_stomp_sup]}, {applications, [kernel,stdlib]}, @@ -9,6 +9,6 @@ {licenses, ["Apache-2.0"]}, {maintainers, ["EMQ X Team "]}, {links, [{"Homepage", "https://emqx.io/"}, - {"Github", "https://gitee.com/fastdgiot/emqx-stomp"} + {"Github", "https://github.com/emqx/emqx-stomp"} ]} ]}. diff --git a/apps/emqx_stomp/src/emqx_stomp.appup.src b/apps/emqx_stomp/src/emqx_stomp.appup.src new file mode 100644 index 0000000000..0b5372dc90 --- /dev/null +++ b/apps/emqx_stomp/src/emqx_stomp.appup.src @@ -0,0 +1,17 @@ +%% -*- mode: erlang -*- +{VSN, + [{"4.3.2",[{load_module,emqx_stomp_protocol,brutal_purge,soft_purge,[]}]}, + {"4.3.1", + [{load_module,emqx_stomp_protocol,brutal_purge,soft_purge,[]}, + {load_module,emqx_stomp_connection,brutal_purge,soft_purge,[]}]}, + {"4.3.0", + [{restart_application,emqx_stomp}, + {apply,{emqx_stomp,force_clear_after_app_stoped,[]}}]}, + {<<".*">>,[]}], + [{"4.3.2",[{load_module,emqx_stomp_protocol,brutal_purge,soft_purge,[]}]}, + {"4.3.1", + [{load_module,emqx_stomp_protocol,brutal_purge,soft_purge,[]}, + {load_module,emqx_stomp_connection,brutal_purge,soft_purge,[]}]}, + {"4.3.0", + [{restart_application,emqx_stomp}]}, + {<<".*">>,[]}]}. diff --git a/apps/emqx_stomp/src/emqx_stomp.erl b/apps/emqx_stomp/src/emqx_stomp.erl index 9eafe3cf72..b8a66009cc 100644 --- a/apps/emqx_stomp/src/emqx_stomp.erl +++ b/apps/emqx_stomp/src/emqx_stomp.erl @@ -33,6 +33,8 @@ , stop_listener/3 ]). +-export([force_clear_after_app_stoped/0]). + -export([init/1]). -define(APP, ?MODULE). @@ -52,6 +54,18 @@ start(_StartType, _StartArgs) -> stop(_State) -> stop_listeners(). +force_clear_after_app_stoped() -> + lists:foreach(fun({Name = {ProtoName, _}, _}) -> + case is_stomp_listener(ProtoName) of + true -> esockd:close(Name); + _ -> ok + end + end, esockd:listeners()). + +is_stomp_listener('stomp:tcp') -> true; +is_stomp_listener('stomp:ssl') -> true; +is_stomp_listener(_) -> false. + %%-------------------------------------------------------------------- %% Supervisor callbacks %%-------------------------------------------------------------------- diff --git a/apps/emqx_stomp/src/emqx_stomp_connection.erl b/apps/emqx_stomp/src/emqx_stomp_connection.erl index d4e7f64751..19ae6bc264 100644 --- a/apps/emqx_stomp/src/emqx_stomp_connection.erl +++ b/apps/emqx_stomp/src/emqx_stomp_connection.erl @@ -20,9 +20,15 @@ -include("emqx_stomp.hrl"). -include_lib("emqx/include/logger.hrl"). +-include_lib("emqx/include/types.hrl"). +-include_lib("snabbkaffe/include/snabbkaffe.hrl"). -logger_header("[Stomp-Conn]"). +-import(emqx_misc, + [ start_timer/2 + ]). + -export([ start_link/3 , info/1 ]). @@ -37,56 +43,177 @@ ]). %% for protocol --export([send/4, heartbeat/2]). - --record(state, {transport, socket, peername, conn_name, conn_state, - await_recv, rate_limit, parser, pstate, - proto_env, heartbeat}). - --define(INFO_KEYS, [peername, await_recv, conn_state]). --define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]). +-export([send/4, heartbeat/2, statfun/3]). + +%% for mgmt +-export([call/2, call/3]). + +-record(state, { + %% TCP/TLS Transport + transport :: esockd:transport(), + %% TCP/TLS Socket + socket :: esockd:socket(), + %% Peername of the connection + peername :: emqx_types:peername(), + %% Sockname of the connection + sockname :: emqx_types:peername(), + %% Sock State + sockstate :: emqx_types:sockstate(), + %% The {active, N} option + active_n :: pos_integer(), + %% Limiter + limiter :: maybe(emqx_limiter:limiter()), + %% Limit Timer + limit_timer :: maybe(reference()), + %% GC State + gc_state :: maybe(emqx_gc:gc_state()), + %% Stats Timer + stats_timer :: disabled | maybe(reference()), + %% Parser State + parser :: emqx_stomp_frame:parser(), + %% Protocol State + pstate :: emqx_stomp_protocol:pstate(), + %% XXX: some common confs + proto_env :: list() + }). + +-type(state() :: #state{}). + +-define(DEFAULT_GC_POLICY, #{bytes => 16777216, count => 16000}). +-define(DEFAULT_OOM_POLICY, #{ max_heap_size => 8388608, + message_queue_len => 10000}). + +-define(ACTIVE_N, 100). +-define(IDLE_TIMEOUT, 30000). +-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n]). +-define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). +-define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). + +-define(ENABLED(X), (X =/= undefined)). + +-elvis([{elvis_style, invalid_dynamic_call, #{ignore => [emqx_stomp_connection]}}]). + +-dialyzer({nowarn_function, [ ensure_stats_timer/2 + ]}). + +-dialyzer({no_return, [ init/1 + , init_state/3 + ]}). start_link(Transport, Sock, ProtoEnv) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Transport, Sock, ProtoEnv]])}. -info(CPid) -> - gen_server:call(CPid, info, infinity). - -init([Transport, Sock, ProtoEnv]) -> - process_flag(trap_exit, true), - case Transport:wait(Sock) of - {ok, NewSock} -> - {ok, Peername} = Transport:ensure_ok_or_exit(peername, [NewSock]), - ConnName = esockd:format(Peername), - SendFun = {fun ?MODULE:send/4, [Transport, Sock, self()]}, - HrtBtFun = {fun ?MODULE:heartbeat/2, [Transport, Sock]}, - Parser = emqx_stomp_frame:init_parer_state(ProtoEnv), - PState = emqx_stomp_protocol:init(#{peername => Peername, - sendfun => SendFun, - heartfun => HrtBtFun}, ProtoEnv), - RateLimit = init_rate_limit(proplists:get_value(rate_limit, ProtoEnv)), - State = run_socket(#state{transport = Transport, - socket = NewSock, - peername = Peername, - conn_name = ConnName, - conn_state = running, - await_recv = false, - rate_limit = RateLimit, - parser = Parser, - proto_env = ProtoEnv, - pstate = PState}), - emqx_logger:set_metadata_peername(esockd:format(Peername)), - gen_server:enter_loop(?MODULE, [{hibernate_after, 5000}], State, 20000); +-spec info(pid() | state()) -> emqx_types:infos(). +info(CPid) when is_pid(CPid) -> + call(CPid, info); +info(State = #state{pstate = PState}) -> + ChanInfo = emqx_stomp_protocol:info(PState), + SockInfo = maps:from_list( + info(?INFO_KEYS, State)), + ChanInfo#{sockinfo => SockInfo}. + +info(Keys, State) when is_list(Keys) -> + [{Key, info(Key, State)} || Key <- Keys]; +info(socktype, #state{transport = Transport, socket = Socket}) -> + Transport:type(Socket); +info(peername, #state{peername = Peername}) -> + Peername; +info(sockname, #state{sockname = Sockname}) -> + Sockname; +info(sockstate, #state{sockstate = SockSt}) -> + SockSt; +info(active_n, #state{active_n = ActiveN}) -> + ActiveN. + +-spec stats(pid() | state()) -> emqx_types:stats(). +stats(CPid) when is_pid(CPid) -> + call(CPid, stats); +stats(#state{transport = Transport, + socket = Socket, + pstate = PState}) -> + SockStats = case Transport:getstat(Socket, ?SOCK_STATS) of + {ok, Ss} -> Ss; + {error, _} -> [] + end, + ConnStats = emqx_pd:get_counters(?CONN_STATS), + ChanStats = emqx_stomp_protocol:stats(PState), + ProcStats = emqx_misc:proc_stats(), + lists:append([SockStats, ConnStats, ChanStats, ProcStats]). + +call(Pid, Req) -> + call(Pid, Req, infinity). +call(Pid, Req, Timeout) -> + gen_server:call(Pid, Req, Timeout). + +init([Transport, RawSocket, ProtoEnv]) -> + case Transport:wait(RawSocket) of + {ok, Socket} -> + init_state(Transport, Socket, ProtoEnv); {error, Reason} -> - {stop, Reason} + ok = Transport:fast_close(RawSocket), + exit_on_sock_error(Reason) end. -init_rate_limit(undefined) -> - undefined; -init_rate_limit({Rate, Burst}) -> - esockd_rate_limit:new(Rate, Burst). +init_state(Transport, Socket, ProtoEnv) -> + {ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]), + {ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]), + + SendFun = {fun ?MODULE:send/4, [Transport, Socket, self()]}, + StatFun = {fun ?MODULE:statfun/3, [Transport, Socket]}, + HrtBtFun = {fun ?MODULE:heartbeat/2, [Transport, Socket]}, + Parser = emqx_stomp_frame:init_parer_state(ProtoEnv), + + ActiveN = proplists:get_value(active_n, ProtoEnv, ?ACTIVE_N), + GcState = emqx_gc:init(?DEFAULT_GC_POLICY), + + Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]), + ConnInfo = #{socktype => Transport:type(Socket), + peername => Peername, + sockname => Sockname, + peercert => Peercert, + statfun => StatFun, + sendfun => SendFun, + heartfun => HrtBtFun, + conn_mod => ?MODULE + }, + PState = emqx_stomp_protocol:init(ConnInfo, ProtoEnv), + State = #state{transport = Transport, + socket = Socket, + peername = Peername, + sockname = Sockname, + sockstate = idle, + active_n = ActiveN, + limiter = undefined, + parser = Parser, + proto_env = ProtoEnv, + gc_state = GcState, + pstate = PState}, + case activate_socket(State) of + {ok, NState} -> + emqx_logger:set_metadata_peername( + esockd:format(Peername)), + gen_server:enter_loop( + ?MODULE, [{hibernate_after, 5000}], NState, 20000); + {error, Reason} -> + ok = Transport:fast_close(Socket), + exit_on_sock_error(Reason) + end. -send(Data, Transport, Sock, ConnPid) -> +-spec exit_on_sock_error(any()) -> no_return(). +exit_on_sock_error(Reason) when Reason =:= einval; + Reason =:= enotconn; + Reason =:= closed -> + erlang:exit(normal); +exit_on_sock_error(timeout) -> + erlang:exit({shutdown, ssl_upgrade_timeout}); +exit_on_sock_error(Reason) -> + erlang:exit({shutdown, Reason}). + +send(Frame, Transport, Sock, ConnPid) -> + ?LOG(info, "SEND Frame: ~s", [emqx_stomp_frame:format(Frame)]), + ok = inc_outgoing_stats(Frame), + Data = emqx_stomp_frame:serialize(Frame), + ?LOG(debug, "SEND ~p", [Data]), try Transport:async_send(Sock, Data) of ok -> ok; {error, Reason} -> ConnPid ! {shutdown, Reason} @@ -95,23 +222,27 @@ send(Data, Transport, Sock, ConnPid) -> end. heartbeat(Transport, Sock) -> + ?LOG(debug, "SEND heartbeat: \\n"), Transport:send(Sock, <<$\n>>). -handle_call(info, _From, State = #state{transport = Transport, - socket = Sock, - peername = Peername, - await_recv = AwaitRecv, - conn_state = ConnState, - pstate = PState}) -> - ClientInfo = [{peername, Peername}, {await_recv, AwaitRecv}, - {conn_state, ConnState}], - ProtoInfo = emqx_stomp_protocol:info(PState), - case Transport:getstat(Sock, ?SOCK_STATS) of - {ok, SockStats} -> - {reply, lists:append([ClientInfo, ProtoInfo, SockStats]), State}; - {error, Reason} -> - {stop, Reason, lists:append([ClientInfo, ProtoInfo]), State} - end; +statfun(Stat, Transport, Sock) -> + case Transport:getstat(Sock, [Stat]) of + {ok, [{Stat, Val}]} -> {ok, Val}; + {error, Error} -> {error, Error} + end. + +handle_call(info, _From, State) -> + {reply, info(State), State}; + +handle_call(stats, _From, State) -> + {reply, stats(State), State}; + +handle_call(discard, _From, State) -> + %% TODO: send the DISCONNECT packet? + shutdown_and_reply(discared, ok, State); + +handle_call(kick, _From, State) -> + shutdown_and_reply(kicked, ok, State); handle_call(Req, _From, State) -> ?LOG(error, "unexpected request: ~p", [Req]), @@ -121,6 +252,13 @@ handle_cast(Msg, State) -> ?LOG(error, "unexpected msg: ~p", [Msg]), noreply(State). +handle_info({event, Name}, State = #state{pstate = PState}) + when Name == connected; + Name == updated -> + ClientId = emqx_stomp_protocol:info(clientid, PState), + emqx_cm:insert_channel_info(ClientId, info(State), stats(State)), + noreply(State); + handle_info(timeout, State) -> shutdown(idle_timeout, State); @@ -141,26 +279,70 @@ handle_info({timeout, TRef, TMsg}, State) when TMsg =:= incoming; shutdown({sock_error, Reason}, State) end; -handle_info({timeout, TRef, TMsg}, State) -> - with_proto(timeout, [TRef, TMsg], State); +handle_info({timeout, _TRef, limit_timeout}, State) -> + NState = State#state{sockstate = idle, + limit_timer = undefined + }, + handle_info(activate_socket, NState); -handle_info({'EXIT', HbProc, Error}, State = #state{heartbeat = HbProc}) -> - stop(Error, State); +handle_info({timeout, _TRef, emit_stats}, + State = #state{pstate = PState}) -> + ClientId = emqx_stomp_protocol:info(clientid, PState), + emqx_cm:set_chan_stats(ClientId, stats(State)), + noreply(State#state{stats_timer = undefined}); -handle_info(activate_sock, State) -> - noreply(run_socket(State#state{conn_state = running})); - -handle_info({inet_async, _Sock, _Ref, {ok, Bytes}}, State) -> - ?LOG(debug, "RECV ~p", [Bytes]), - received(Bytes, rate_limit(size(Bytes), State#state{await_recv = false})); +handle_info({timeout, TRef, TMsg}, State) -> + with_proto(timeout, [TRef, TMsg], State); -handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) -> - shutdown(Reason, State); +handle_info(activate_socket, State) -> + case activate_socket(State) of + {ok, NState} -> + noreply(NState); + {error, Reason} -> + handle_info({sock_error, Reason}, State) + end; handle_info({inet_reply, _Ref, ok}, State) -> noreply(State); +handle_info({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl -> + ?LOG(debug, "RECV ~0p", [Data]), + Oct = iolist_size(Data), + inc_counter(incoming_bytes, Oct), + ok = emqx_metrics:inc('bytes.received', Oct), + received(Data, ensure_stats_timer(?IDLE_TIMEOUT, State)); + +handle_info({Passive, _Sock}, State) + when Passive == tcp_passive; Passive == ssl_passive -> + %% In Stats + Pubs = emqx_pd:reset_counter(incoming_pubs), + Bytes = emqx_pd:reset_counter(incoming_bytes), + InStats = #{cnt => Pubs, oct => Bytes}, + %% Ensure Rate Limit + NState = ensure_rate_limit(InStats, State), + %% Run GC and Check OOM + NState1 = check_oom(run_gc(InStats, NState)), + handle_info(activate_socket, NState1); + +handle_info({Error, _Sock, Reason}, State) + when Error == tcp_error; Error == ssl_error -> + handle_info({sock_error, Reason}, State); + +handle_info({Closed, _Sock}, State) + when Closed == tcp_closed; Closed == ssl_closed -> + handle_info({sock_closed, Closed}, close_socket(State)); + handle_info({inet_reply, _Sock, {error, Reason}}, State) -> + handle_info({sock_error, Reason}, State); + +handle_info({sock_error, Reason}, State) -> + case Reason =/= closed andalso Reason =/= einval of + true -> ?LOG(warning, "socket_error: ~p", [Reason]); + false -> ok + end, + handle_info({sock_closed, Reason}, close_socket(State)); + +handle_info({sock_closed, Reason}, State) -> shutdown(Reason, State); handle_info({deliver, _Topic, Msg}, State = #state{pstate = PState}) -> @@ -172,8 +354,7 @@ handle_info({deliver, _Topic, Msg}, State = #state{pstate = PState}) -> end}); handle_info(Info, State) -> - ?LOG(error, "Unexpected info: ~p", [Info]), - noreply(State). + with_proto(handle_info, [Info], State). terminate(Reason, #state{transport = Transport, socket = Sock, @@ -197,6 +378,8 @@ code_change(_OldVsn, State, _Extra) -> with_proto(Fun, Args, State = #state{pstate = PState}) -> case erlang:apply(emqx_stomp_protocol, Fun, Args ++ [PState]) of + ok -> + noreply(State); {ok, NPState} -> noreply(State#state{pstate = NPState}); {F, Reason, NPState} when F == stop; @@ -208,13 +391,14 @@ with_proto(Fun, Args, State = #state{pstate = PState}) -> received(<<>>, State) -> noreply(State); -received(Bytes, State = #state{parser = Parser, +received(Bytes, State = #state{parser = Parser, pstate = PState}) -> try emqx_stomp_frame:parse(Bytes, Parser) of {more, NewParser} -> noreply(State#state{parser = NewParser}); {ok, Frame, Rest} -> ?LOG(info, "RECV Frame: ~s", [emqx_stomp_frame:format(Frame)]), + ok = inc_incoming_stats(Frame), case emqx_stomp_protocol:received(Frame, PState) of {ok, PState1} -> received(Rest, reset_parser(State#state{pstate = PState1})); @@ -237,25 +421,97 @@ received(Bytes, State = #state{parser = Parser, reset_parser(State = #state{proto_env = ProtoEnv}) -> State#state{parser = emqx_stomp_frame:init_parer_state(ProtoEnv)}. -rate_limit(_Size, State = #state{rate_limit = undefined}) -> - run_socket(State); -rate_limit(Size, State = #state{rate_limit = Rl}) -> - case esockd_rate_limit:check(Size, Rl) of - {0, Rl1} -> - run_socket(State#state{conn_state = running, rate_limit = Rl1}); - {Pause, Rl1} -> - ?LOG(error, "Rate limiter pause for ~p", [Pause]), - erlang:send_after(Pause, self(), activate_sock), - State#state{conn_state = blocked, rate_limit = Rl1} +activate_socket(State = #state{sockstate = closed}) -> + {ok, State}; +activate_socket(State = #state{sockstate = blocked}) -> + {ok, State}; +activate_socket(State = #state{transport = Transport, + socket = Socket, + active_n = N}) -> + case Transport:setopts(Socket, [{active, N}]) of + ok -> {ok, State#state{sockstate = running}}; + Error -> Error + end. + +close_socket(State = #state{sockstate = closed}) -> State; +close_socket(State = #state{transport = Transport, socket = Socket}) -> + ok = Transport:fast_close(Socket), + State#state{sockstate = closed}. + +%%-------------------------------------------------------------------- +%% Inc incoming/outgoing stats + +inc_incoming_stats(#stomp_frame{command = Cmd}) -> + inc_counter(recv_pkt, 1), + case Cmd of + <<"SEND">> -> + inc_counter(recv_msg, 1), + inc_counter(incoming_pubs, 1), + emqx_metrics:inc('messages.received'), + emqx_metrics:inc('messages.qos1.received'); + _ -> + ok + end, + emqx_metrics:inc('packets.received'). + +inc_outgoing_stats(#stomp_frame{command = Cmd}) -> + inc_counter(send_pkt, 1), + case Cmd of + <<"MESSAGE">> -> + inc_counter(send_msg, 1), + inc_counter(outgoing_pubs, 1), + emqx_metrics:inc('messages.sent'), + emqx_metrics:inc('messages.qos1.sent'); + _ -> + ok + end, + emqx_metrics:inc('packets.sent'). + +%%-------------------------------------------------------------------- +%% Ensure rate limit + +ensure_rate_limit(Stats, State = #state{limiter = Limiter}) -> + case ?ENABLED(Limiter) andalso emqx_limiter:check(Stats, Limiter) of + false -> State; + {ok, Limiter1} -> + State#state{limiter = Limiter1}; + {pause, Time, Limiter1} -> + ?LOG(warning, "Pause ~pms due to rate limit", [Time]), + TRef = start_timer(Time, limit_timeout), + State#state{sockstate = blocked, + limiter = Limiter1, + limit_timer = TRef + } end. -run_socket(State = #state{conn_state = blocked}) -> - State; -run_socket(State = #state{await_recv = true}) -> - State; -run_socket(State = #state{transport = Transport, socket = Sock}) -> - Transport:async_recv(Sock, 0, infinity), - State#state{await_recv = true}. +%%-------------------------------------------------------------------- +%% Run GC and Check OOM + +run_gc(Stats, State = #state{gc_state = GcSt}) -> + case ?ENABLED(GcSt) andalso emqx_gc:run(Stats, GcSt) of + false -> State; + {_IsGC, GcSt1} -> + State#state{gc_state = GcSt1} + end. + +check_oom(State) -> + OomPolicy = ?DEFAULT_OOM_POLICY, + ?tp(debug, check_oom, #{policy => OomPolicy}), + case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of + {shutdown, Reason} -> + %% triggers terminate/2 callback immediately + erlang:exit({shutdown, Reason}); + _Other -> + ok + end, + State. + +%%-------------------------------------------------------------------- +%% Ensure/cancel stats timer + +ensure_stats_timer(Timeout, State = #state{stats_timer = undefined}) -> + State#state{stats_timer = start_timer(Timeout, emit_stats)}; +ensure_stats_timer(_Timeout, State) -> State. getstat(Stat, #state{transport = Transport, socket = Sock}) -> case Transport:getstat(Sock, [Stat]) of @@ -272,3 +528,9 @@ stop(Reason, State) -> shutdown(Reason, State) -> stop({shutdown, Reason}, State). +shutdown_and_reply(Reason, Reply, State) -> + {stop, {shutdown, Reason}, Reply, State}. + +inc_counter(Key, Inc) -> + _ = emqx_pd:inc_counter(Key, Inc), + ok. diff --git a/apps/emqx_stomp/src/emqx_stomp_frame.erl b/apps/emqx_stomp/src/emqx_stomp_frame.erl index fa9cb63a8f..c4d19ae3a5 100644 --- a/apps/emqx_stomp/src/emqx_stomp_frame.erl +++ b/apps/emqx_stomp/src/emqx_stomp_frame.erl @@ -126,6 +126,13 @@ parse(Bytes, #{phase := body, len := Len, state := State}) -> parse(Bytes, Parser = #{pre := Pre}) -> parse(<
>, maps:without([pre], Parser));
+parse(<>, Parser = #{phase := none}) ->
+    parse(Rest, Parser);
+parse(<>, Parser = #{phase := none}) ->
+    case byte_size(Rest) of
+        0 -> {more, Parser};
+        _ -> parse(Rest, Parser)
+    end;
 parse(<>, #{phase := Phase, state := State}) ->
     parse(Phase, <>, State);
 parse(<>, Parser) ->
diff --git a/apps/emqx_stomp/src/emqx_stomp_heartbeat.erl b/apps/emqx_stomp/src/emqx_stomp_heartbeat.erl
index 145359e530..4097756fe2 100644
--- a/apps/emqx_stomp/src/emqx_stomp_heartbeat.erl
+++ b/apps/emqx_stomp/src/emqx_stomp_heartbeat.erl
@@ -23,9 +23,10 @@
         , check/3
         , info/1
         , interval/2
+        , reset/3
         ]).
 
--record(heartbeater, {interval, statval, repeat}).
+-record(heartbeater, {interval, statval, repeat, repeat_max}).
 
 -type name() :: incoming | outgoing.
 
@@ -33,7 +34,6 @@
                        outgoing => #heartbeater{}
                       }.
 
-
 %%--------------------------------------------------------------------
 %% APIs
 %%--------------------------------------------------------------------
@@ -43,19 +43,23 @@ init({0, 0}) ->
     #{};
 init({Cx, Cy}) ->
     maps:filter(fun(_, V) -> V /= undefined end,
-      #{incoming => heartbeater(Cx),
-        outgoing => heartbeater(Cy)
+      #{incoming => heartbeater(incoming, Cx),
+        outgoing => heartbeater(outgoing, Cy)
        }).
 
-heartbeater(0) ->
+heartbeater(_, 0) ->
     undefined;
-heartbeater(I) ->
+heartbeater(N, I) ->
     #heartbeater{
        interval = I,
        statval = 0,
-       repeat = 0
+       repeat = 0,
+       repeat_max = repeat_max(N)
       }.
 
+repeat_max(incoming) -> 1;
+repeat_max(outgoing) -> 0.
+
 -spec check(name(), pos_integer(), heartbeat())
     -> {ok, heartbeat()}
      | {error, timeout}.
@@ -68,11 +72,12 @@ check(Name, NewVal, HrtBt) ->
     end.
 
 check(NewVal, HrtBter = #heartbeater{statval = OldVal,
-                                     repeat = Repeat}) ->
+                                     repeat = Repeat,
+                                     repeat_max = Max}) ->
     if
         NewVal =/= OldVal ->
             {ok, HrtBter#heartbeater{statval = NewVal, repeat = 0}};
-        Repeat < 1 ->
+        Repeat < Max ->
             {ok, HrtBter#heartbeater{repeat = Repeat + 1}};
         true -> {error, timeout}
     end.
@@ -90,3 +95,10 @@ interval(Type, HrtBt) ->
         undefined -> undefined;
         #heartbeater{interval = Intv} -> Intv
     end.
+
+reset(Type, StatVal, HrtBt) ->
+    case maps:get(Type, HrtBt, undefined) of
+        undefined -> HrtBt;
+        HrtBter ->
+            HrtBt#{Type => HrtBter#heartbeater{statval = StatVal, repeat = 0}}
+    end.
diff --git a/apps/emqx_stomp/src/emqx_stomp_protocol.erl b/apps/emqx_stomp/src/emqx_stomp_protocol.erl
index cc5c28ce9f..4e371d9b04 100644
--- a/apps/emqx_stomp/src/emqx_stomp_protocol.erl
+++ b/apps/emqx_stomp/src/emqx_stomp_protocol.erl
@@ -20,6 +20,7 @@
 -include("emqx_stomp.hrl").
 
 -include_lib("emqx/include/emqx.hrl").
+-include_lib("emqx/include/types.hrl").
 -include_lib("emqx/include/logger.hrl").
 -include_lib("emqx/include/emqx_mqtt.hrl").
 
@@ -30,6 +31,8 @@
 %% API
 -export([ init/2
         , info/1
+        , info/2
+        , stats/1
         ]).
 
 -export([ received/2
@@ -38,6 +41,9 @@
         , timeout/3
         ]).
 
+-export([ handle_info/2
+        ]).
+
 %% for trans callback
 -export([ handle_recv_send_frame/2
         , handle_recv_ack_frame/2
@@ -45,21 +51,37 @@
         ]).
 
 -record(pstate, {
-          peername,
-          heartfun,
-          sendfun,
+          %% Stomp ConnInfo
+          conninfo :: emqx_types:conninfo(),
+          %% Stomp ClientInfo
+          clientinfo :: emqx_types:clientinfo(),
+          %% Stomp Heartbeats
+          heart_beats :: maybe(emqx_stomp_hearbeat:heartbeat()),
+          %% Stomp Connection State
           connected = false,
-          proto_ver,
-          proto_name,
-          heart_beats,
-          login,
-          allow_anonymous,
-          default_user,
-          subscriptions = [],
+          %% Timers
           timers :: #{atom() => disable | undefined | reference()},
-          transaction :: #{binary() => list()}
+          %% Transaction
+          transaction :: #{binary() => list()},
+          %% Subscriptions
+          subscriptions = #{},
+          %% Send function
+          sendfun :: {function(), list()},
+          %% Heartbeat function
+          heartfun :: {function(), list()},
+          %% Get Socket stat function
+          statfun :: {function(), list()},
+          %% The confs for the connection
+          %% TODO: put these configs into a public mem?
+          allow_anonymous :: maybe(boolean()),
+          default_user :: maybe(list())
          }).
 
+-define(DEFAULT_SUB_ACK, <<"auto">>).
+
+-define(INCOMING_TIMER_BACKOFF, 1.25).
+-define(OUTCOMING_TIMER_BACKOFF, 0.75).
+
 -define(TIMER_TABLE, #{
           incoming_timer => incoming,
           outgoing_timer => outgoing,
@@ -68,34 +90,136 @@
 
 -define(TRANS_TIMEOUT, 60000).
 
+-define(INFO_KEYS, [conninfo, conn_state, clientinfo, session, will_msg]).
+
+-define(STATS_KEYS, [subscriptions_cnt,
+                     subscriptions_max,
+                     inflight_cnt,
+                     inflight_max,
+                     mqueue_len,
+                     mqueue_max,
+                     mqueue_dropped,
+                     next_pkt_id,
+                     awaiting_rel_cnt,
+                     awaiting_rel_max
+                    ]).
+
+-dialyzer({nowarn_function, [ check_acl/3
+                            , init/2
+                            ]}).
+
+-elvis([{elvis_style, dont_repeat_yourself, disable}]).
+
 -type(pstate() :: #pstate{}).
 
 %% @doc Init protocol
-init(#{peername := Peername,
-       sendfun := SendFun,
-       heartfun := HeartFun}, Env) ->
-    AllowAnonymous = get_value(allow_anonymous, Env, false),
-    DefaultUser = get_value(default_user, Env),
-	#pstate{peername = Peername,
-                 heartfun = HeartFun,
-                 sendfun = SendFun,
-                 timers = #{},
-                 transaction = #{},
-                 allow_anonymous = AllowAnonymous,
-                 default_user = DefaultUser}.
-
-info(#pstate{connected     = Connected,
-                  proto_ver     = ProtoVer,
-                  proto_name    = ProtoName,
-                  heart_beats   = Heartbeats,
-                  login         = Login,
-                  subscriptions = Subscriptions}) ->
-    [{connected, Connected},
-     {proto_ver, ProtoVer},
-     {proto_name, ProtoName},
-     {heart_beats, Heartbeats},
-     {login, Login},
-     {subscriptions, Subscriptions}].
+init(ConnInfo = #{peername := {PeerHost, _Port},
+                  sockname := {_Host, SockPort},
+                  statfun  := StatFun,
+                  sendfun  := SendFun,
+                  heartfun := HeartFun}, Opts) ->
+
+    NConnInfo = default_conninfo(ConnInfo),
+
+    ClientInfo = #{zone => undefined,
+                   protocol => stomp,
+                   peerhost => PeerHost,
+                   sockport => SockPort,
+                   clientid => undefined,
+                   username => undefined,
+                   mountpoint => undefined, %% XXX: not supported now
+                   is_bridge => false,
+                   is_superuser => false
+                  },
+
+    AllowAnonymous = get_value(allow_anonymous, Opts, false),
+    DefaultUser = get_value(default_user, Opts),
+    #pstate{
+       conninfo = NConnInfo,
+       clientinfo = ClientInfo,
+       heartfun = HeartFun,
+       sendfun = SendFun,
+       statfun = StatFun,
+       timers = #{},
+       transaction = #{},
+       allow_anonymous = AllowAnonymous,
+       default_user = DefaultUser
+      }.
+
+default_conninfo(ConnInfo) ->
+    NConnInfo = maps:without([sendfun, heartfun], ConnInfo),
+    NConnInfo#{
+      proto_name => <<"STOMP">>,
+      proto_ver => <<"1.2">>,
+      clean_start => true,
+      clientid => undefined,
+      username => undefined,
+      conn_props => [],
+      connected => false,
+      connected_at => undefined,
+      keepalive => undefined,
+      receive_maximum => 0,
+      expiry_interval => 0
+     }.
+
+-spec info(pstate()) -> emqx_types:infos().
+info(State) ->
+    maps:from_list(info(?INFO_KEYS, State)).
+
+-spec info(list(atom()) | atom(), pstate()) -> term().
+info(Keys, State) when is_list(Keys) ->
+    [{Key, info(Key, State)} || Key <- Keys];
+info(conninfo, #pstate{conninfo = ConnInfo}) ->
+    ConnInfo;
+info(socktype, #pstate{conninfo = ConnInfo}) ->
+    maps:get(socktype, ConnInfo, undefined);
+info(peername, #pstate{conninfo = ConnInfo}) ->
+    maps:get(peername, ConnInfo, undefined);
+info(sockname, #pstate{conninfo = ConnInfo}) ->
+    maps:get(sockname, ConnInfo, undefined);
+info(proto_name, #pstate{conninfo = ConnInfo}) ->
+    maps:get(proto_name, ConnInfo, undefined);
+info(proto_ver, #pstate{conninfo = ConnInfo}) ->
+    maps:get(proto_ver, ConnInfo, undefined);
+info(connected_at, #pstate{conninfo = ConnInfo}) ->
+    maps:get(connected_at, ConnInfo, undefined);
+info(clientinfo, #pstate{clientinfo = ClientInfo}) ->
+    ClientInfo;
+info(zone, _) ->
+    undefined;
+info(clientid, #pstate{clientinfo = ClientInfo}) ->
+    maps:get(clientid, ClientInfo, undefined);
+info(username, #pstate{clientinfo = ClientInfo}) ->
+    maps:get(username, ClientInfo, undefined);
+info(session, State) ->
+    session_info(State);
+info(conn_state, #pstate{connected = true}) ->
+    connected;
+info(conn_state, _) ->
+    disconnected;
+info(will_msg, _) ->
+    undefined.
+
+session_info(#pstate{conninfo = ConnInfo, subscriptions = Subs}) ->
+    #{subscriptions => Subs,
+      upgrade_qos => false,
+      retry_interval => 0,
+      await_rel_timeout => 0,
+      created_at => maps:get(connected_at, ConnInfo, 0)
+     }.
+
+-spec stats(pstate()) -> emqx_types:stats().
+stats(#pstate{subscriptions = Subs}) ->
+    [{subscriptions_cnt, maps:size(Subs)},
+     {subscriptions_max, 0},
+     {inflight_cnt, 0},
+     {inflight_max, 0},
+     {mqueue_len, 0},
+     {mqueue_max, 0},
+     {mqueue_dropped, 0},
+     {next_pkt_id, 0},
+     {awaiting_rel_cnt, 0},
+     {awaiting_rel_max, 0}].
 
 -spec(received(stomp_frame(), pstate())
     -> {ok, pstate()}
@@ -105,20 +229,49 @@ received(Frame = #stomp_frame{command = <<"STOMP">>}, State) ->
     received(Frame#stomp_frame{command = <<"CONNECT">>}, State);
 
 received(#stomp_frame{command = <<"CONNECT">>, headers = Headers},
-         State = #pstate{connected = false, allow_anonymous = AllowAnonymous, default_user = DefaultUser}) ->
+         State = #pstate{connected = false}) ->
     case negotiate_version(header(<<"accept-version">>, Headers)) of
         {ok, Version} ->
             Login = header(<<"login">>, Headers),
             Passc = header(<<"passcode">>, Headers),
-            case check_login(Login, Passc, AllowAnonymous, DefaultUser) of
+            case check_login(Login, Passc,
+                             allow_anonymous(State),
+                             default_user(State)
+                            ) of
                 true ->
-                    emqx_logger:set_metadata_clientid(Login),
-
-                    Heartbeats = parse_heartbeats(header(<<"heart-beat">>, Headers, <<"0,0">>)),
-                    NState = start_heartbeart_timer(Heartbeats, State#pstate{connected = true,
-                                                                                  proto_ver = Version, login = Login}),
-                    send(connected_frame([{<<"version">>, Version},
-                                          {<<"heart-beat">>, reverse_heartbeats(Heartbeats)}]), NState);
+                    Heartbeats = parse_heartbeats(
+                                   header(<<"heart-beat">>, Headers, <<"0,0">>)),
+                    ClientId = emqx_guid:to_base62(emqx_guid:gen()),
+                    emqx_logger:set_metadata_clientid(ClientId),
+                    ConnInfo = State#pstate.conninfo,
+                    ClitInfo = State#pstate.clientinfo,
+                    NConnInfo = ConnInfo#{
+                                  proto_ver => Version,
+                                  clientid => ClientId,
+                                  keepalive => element(1, Heartbeats) div 1000,
+                                  username => Login
+                                 },
+                    NClitInfo = ClitInfo#{
+                                  clientid => ClientId,
+                                  username => Login
+                                 },
+
+                    ConnPid = self(),
+                    _ = emqx_cm_locker:trans(ClientId, fun(_) ->
+                        emqx_cm:discard_session(ClientId),
+                        emqx_cm:register_channel(ClientId, ConnPid, NConnInfo)
+                    end),
+                    NState = start_heartbeart_timer(
+                               Heartbeats,
+                               State#pstate{
+                                 conninfo = NConnInfo,
+                                 clientinfo = NClitInfo}
+                              ),
+                    ConnectedFrame = connected_frame(
+                                       [{<<"version">>, Version},
+                                        {<<"heart-beat">>, reverse_heartbeats(Heartbeats)}
+                                       ]),
+                    send(ConnectedFrame, ensure_connected(NState));
                 false ->
                     _ = send(error_frame(undefined, <<"Login or passcode error!">>), State),
                     {error, login_or_passcode_error, State}
@@ -130,40 +283,66 @@ received(#stomp_frame{command = <<"CONNECT">>, headers = Headers},
     end;
 
 received(#stomp_frame{command = <<"CONNECT">>}, State = #pstate{connected = true}) ->
+    ?LOG(error, "Received CONNECT frame on connected=true state"),
     {error, unexpected_connect, State};
 
 received(Frame = #stomp_frame{command = <<"SEND">>, headers = Headers}, State) ->
     case header(<<"transaction">>, Headers) of
         undefined     -> {ok, handle_recv_send_frame(Frame, State)};
-        TransactionId -> add_action(TransactionId, {fun ?MODULE:handle_recv_send_frame/2, [Frame]}, receipt_id(Headers), State)
+        TransactionId ->
+            add_action(TransactionId,
+                       {fun ?MODULE:handle_recv_send_frame/2, [Frame]},
+                       receipt_id(Headers),
+                       State
+                      )
     end;
 
 received(#stomp_frame{command = <<"SUBSCRIBE">>, headers = Headers},
-            State = #pstate{subscriptions = Subscriptions}) ->
+            State = #pstate{subscriptions = Subs}) ->
     Id    = header(<<"id">>, Headers),
     Topic = header(<<"destination">>, Headers),
-    Ack   = header(<<"ack">>, Headers, <<"auto">>),
-    {ok, State1} = case lists:keyfind(Id, 1, Subscriptions) of
-                       {Id, Topic, Ack} ->
-                           {ok, State};
-                       false ->
-                           emqx_broker:subscribe(Topic),
-                           {ok, State#pstate{subscriptions = [{Id, Topic, Ack}|Subscriptions]}}
-                   end,
-    maybe_send_receipt(receipt_id(Headers), State1);
+    Ack   = header(<<"ack">>, Headers, ?DEFAULT_SUB_ACK),
+
+    case find_sub_by_id(Id, Subs) of
+        {Topic, #{sub_props := #{id := Id}}} ->
+            ?LOG(info, "Subscription has established: ~s", [Topic]),
+            maybe_send_receipt(receipt_id(Headers), State);
+        {InuseTopic, #{sub_props := #{id := InuseId}}} ->
+            ?LOG(info, "Subscription id ~p inused by topic: ~s, "
+                       "request topic: ~s", [InuseId, InuseTopic, Topic]),
+            send(error_frame(receipt_id(Headers),
+                             ["Request sub-id ", Id, " inused "]), State);
+        undefined ->
+            case check_acl(subscribe, Topic, State) of
+                allow ->
+                    ClientInfo = State#pstate.clientinfo,
+
+                    [{TopicFilter, SubOpts}] = parse_topic_filters(
+                                                 [{Topic, ?DEFAULT_SUBOPTS}
+                                               ]),
+                    NSubOpts = SubOpts#{sub_props => #{id => Id, ack => Ack}},
+                    _ = run_hooks('client.subscribe',
+                                  [ClientInfo, _SubProps = #{}],
+                                  [{TopicFilter, NSubOpts}]),
+                    NState = do_subscribe(TopicFilter, NSubOpts, State),
+                    maybe_send_receipt(receipt_id(Headers), NState)
+            end
+    end;
 
 received(#stomp_frame{command = <<"UNSUBSCRIBE">>, headers = Headers},
-            State = #pstate{subscriptions = Subscriptions}) ->
+            State = #pstate{subscriptions = Subs, clientinfo = ClientInfo}) ->
     Id = header(<<"id">>, Headers),
-
-    {ok, State1} = case lists:keyfind(Id, 1, Subscriptions) of
-                       {Id, Topic, _Ack} ->
-                           ok = emqx_broker:unsubscribe(Topic),
-                           {ok, State#pstate{subscriptions = lists:keydelete(Id, 1, Subscriptions)}};
-                       false ->
-                           {ok, State}
-                   end,
-    maybe_send_receipt(receipt_id(Headers), State1);
+    {ok, NState} = case find_sub_by_id(Id, Subs) of
+            {Topic, #{sub_props := #{id := Id}}} ->
+                _ = run_hooks('client.unsubscribe',
+                              [ClientInfo, #{}],
+                              [{Topic, #{}}]),
+                State1 = do_unsubscribe(Topic, ?DEFAULT_SUBOPTS, State),
+                {ok, State1};
+            undefined ->
+                {ok, State}
+        end,
+    maybe_send_receipt(receipt_id(Headers), NState);
 
 %% ACK
 %% id:12345
@@ -173,7 +352,11 @@ received(#stomp_frame{command = <<"UNSUBSCRIBE">>, headers = Headers},
 received(Frame = #stomp_frame{command = <<"ACK">>, headers = Headers}, State) ->
     case header(<<"transaction">>, Headers) of
         undefined     -> {ok, handle_recv_ack_frame(Frame, State)};
-        TransactionId -> add_action(TransactionId, {fun ?MODULE:handle_recv_ack_frame/2, [Frame]}, receipt_id(Headers), State)
+        TransactionId ->
+            add_action(TransactionId,
+                       {fun ?MODULE:handle_recv_ack_frame/2, [Frame]},
+                       receipt_id(Headers),
+                       State)
     end;
 
 %% NACK
@@ -184,7 +367,11 @@ received(Frame = #stomp_frame{command = <<"ACK">>, headers = Headers}, State) ->
 received(Frame = #stomp_frame{command = <<"NACK">>, headers = Headers}, State) ->
     case header(<<"transaction">>, Headers) of
         undefined     -> {ok, handle_recv_nack_frame(Frame, State)};
-        TransactionId -> add_action(TransactionId, {fun ?MODULE:handle_recv_nack_frame/2, [Frame]}, receipt_id(Headers), State)
+        TransactionId ->
+            add_action(TransactionId,
+                       {fun ?MODULE:handle_recv_nack_frame/2, [Frame]},
+                       receipt_id(Headers),
+                       State)
     end;
 
 %% BEGIN
@@ -239,10 +426,15 @@ received(#stomp_frame{command = <<"DISCONNECT">>, headers = Headers}, State) ->
     _ = maybe_send_receipt(receipt_id(Headers), State),
     {stop, normal, State}.
 
-send(Msg = #message{topic = Topic, headers = Headers, payload = Payload},
-     State = #pstate{subscriptions = Subscriptions}) ->
-    case lists:keyfind(Topic, 2, Subscriptions) of
-        {Id, Topic, Ack} ->
+send(Msg0 = #message{},
+     State = #pstate{clientinfo = ClientInfo, subscriptions = Subs}) ->
+    ok = emqx_metrics:inc('messages.delivered'),
+    Msg = emqx_hooks:run_fold('message.delivered', [ClientInfo], Msg0),
+    #message{topic = Topic,
+             headers = Headers,
+             payload = Payload} = Msg,
+    case find_sub_by_topic(Topic, Subs) of
+        {Topic, #{sub_props := #{id := Id, ack := Ack}}} ->
             Headers0 = [{<<"subscription">>, Id},
                         {<<"message-id">>, next_msgid()},
                         {<<"destination">>, Topic},
@@ -256,19 +448,21 @@ send(Msg = #message{topic = Topic, headers = Headers, payload = Payload},
             Frame = #stomp_frame{command = <<"MESSAGE">>,
                                  headers = Headers1 ++ maps:get(stomp_headers, Headers, []),
                                  body = Payload},
+
+
             send(Frame, State);
-        false ->
+        undefined ->
             ?LOG(error, "Stomp dropped: ~p", [Msg]),
             {error, dropped, State}
     end;
 
-send(Frame, State = #pstate{sendfun = {Fun, Args}}) ->
-    ?LOG(info, "SEND Frame: ~s", [emqx_stomp_frame:format(Frame)]),
-    Data = emqx_stomp_frame:serialize(Frame),
-    ?LOG(debug, "SEND ~p", [Data]),
-    erlang:apply(Fun, [Data] ++ Args),
+send(Frame, State = #pstate{sendfun = {Fun, Args}}) when is_record(Frame, stomp_frame) ->
+    erlang:apply(Fun, [Frame] ++ Args),
     {ok, State}.
 
+shutdown(Reason, State = #pstate{connected = true}) ->
+    _ = ensure_disconnected(Reason, State),
+    ok;
 shutdown(_Reason, _State) ->
     ok.
 
@@ -283,11 +477,18 @@ timeout(_TRef, {incoming, NewVal},
 
 timeout(_TRef, {outgoing, NewVal},
         State = #pstate{heart_beats = HrtBt,
-                             heartfun = {Fun, Args}}) ->
+                        statfun = {StatFun, StatArgs},
+                        heartfun = {Fun, Args}}) ->
     case emqx_stomp_heartbeat:check(outgoing, NewVal, HrtBt) of
         {error, timeout} ->
             _ = erlang:apply(Fun, Args),
-            {ok, State};
+            case erlang:apply(StatFun, [send_oct] ++ StatArgs) of
+                {ok, NewVal2} ->
+                    NHrtBt = emqx_stomp_heartbeat:reset(outgoing, NewVal2, HrtBt),
+                    {ok, reset_timer(outgoing_timer, State#pstate{heart_beats = NHrtBt})};
+                {error, Reason} ->
+                    {shutdown, {error, {get_stats_error, Reason}}, State}
+            end;
         {ok, NHrtBt} ->
             {ok, reset_timer(outgoing_timer, State#pstate{heart_beats = NHrtBt})}
     end;
@@ -297,6 +498,28 @@ timeout(_TRef, clean_trans, State = #pstate{transaction = Trans}) ->
     NTrans = maps:filter(fun(_, {Ts, _}) -> Ts + ?TRANS_TIMEOUT < Now end, Trans),
     {ok, ensure_clean_trans_timer(State#pstate{transaction = NTrans})}.
 
+
+-spec(handle_info(Info :: term(), pstate())
+      -> ok | {ok, pstate()} | {shutdown, Reason :: term(), pstate()}).
+
+handle_info({subscribe, TopicFilters}, State) ->
+    NState = lists:foldl(
+        fun({TopicFilter, SubOpts}, StateAcc = #pstate{subscriptions = Subs}) ->
+            NSubOpts = enrich_sub_opts(SubOpts, Subs),
+            do_subscribe(TopicFilter, NSubOpts, StateAcc)
+        end, State, parse_topic_filters(TopicFilters)),
+    {ok, NState};
+
+handle_info({unsubscribe, TopicFilters}, State) ->
+    NState = lists:foldl(fun({TopicFilter, SubOpts}, StateAcc) ->
+                do_unsubscribe(TopicFilter, SubOpts, StateAcc)
+             end, State, parse_topic_filters(TopicFilters)),
+    {ok, NState};
+
+handle_info(Info, State) ->
+    ?LOG(warning, "Unexpected info ~p", [Info]),
+    {ok, State}.
+
 negotiate_version(undefined) ->
     {ok, <<"1.0">>};
 negotiate_version(Accepts) ->
@@ -307,18 +530,20 @@ negotiate_version(Accepts) ->
 
 negotiate_version(Ver, []) ->
     {error, <<"Supported protocol versions < ", Ver/binary>>};
-negotiate_version(Ver, [AcceptVer|_]) when Ver >= AcceptVer ->
+negotiate_version(Ver, [AcceptVer | _]) when Ver >= AcceptVer ->
     {ok, AcceptVer};
-negotiate_version(Ver, [_|T]) ->
+negotiate_version(Ver, [_ | T]) ->
     negotiate_version(Ver, T).
 
-check_login(undefined, _, AllowAnonymous, _) ->
+check_login(Login, _, AllowAnonymous, _)
+  when Login == <<>>;
+       Login == undefined ->
     AllowAnonymous;
 check_login(_, _, _, undefined) ->
     false;
 check_login(Login, Passcode, _, DefaultUser) ->
-    case {list_to_binary(get_value(login, DefaultUser)),
-          list_to_binary(get_value(passcode, DefaultUser))} of
+    case {iolist_to_binary(get_value(login, DefaultUser)),
+          iolist_to_binary(get_value(passcode, DefaultUser))} of
         {Login, Passcode} -> true;
         {_,     _       } -> false
     end.
@@ -326,7 +551,7 @@ check_login(Login, Passcode, _, DefaultUser) ->
 add_action(Id, Action, ReceiptId, State = #pstate{transaction = Trans}) ->
     case maps:get(Id, Trans, undefined) of
         {Ts, Actions} ->
-            NTrans = Trans#{Id => {Ts, [Action|Actions]}},
+            NTrans = Trans#{Id => {Ts, [Action | Actions]}},
             {ok, State#pstate{transaction = NTrans}};
         _ ->
             send(error_frame(ReceiptId, ["Transaction ", Id, " not found"]), State)
@@ -377,15 +602,29 @@ next_ackid() ->
     put(ackid, AckId + 1),
     AckId.
 
-make_mqtt_message(Topic, Headers, Body) ->
-    Msg = emqx_message:make(stomp, Topic, Body),
-    Headers1 = lists:foldl(fun(Key, Headers0) ->
-                               proplists:delete(Key, Headers0)
-                           end, Headers, [<<"destination">>,
-                                          <<"content-length">>,
-                                          <<"content-type">>,
-                                          <<"transaction">>,
-                                          <<"receipt">>]),
+make_mqtt_message(Topic, Headers, Body,
+                  #pstate{
+                     conninfo = #{proto_ver := ProtoVer},
+                     clientinfo = #{
+                         protocol := Protocol,
+                         clientid := ClientId,
+                         username := Username,
+                         peerhost := PeerHost}}) ->
+    Msg = emqx_message:make(
+            ClientId, ?QOS_0,
+            Topic, Body, #{},
+            #{proto_ver => ProtoVer,
+              protocol => Protocol,
+              username => Username,
+              peerhost => PeerHost}),
+    Headers1 = lists:foldl(
+                 fun(Key, Headers0) ->
+                    proplists:delete(Key, Headers0)
+                 end, Headers, [<<"destination">>,
+                                <<"content-length">>,
+                                <<"content-type">>,
+                                <<"transaction">>,
+                                <<"receipt">>]),
     emqx_message:set_headers(#{stomp_headers => Headers1}, Msg).
 
 receipt_id(Headers) ->
@@ -396,11 +635,18 @@ receipt_id(Headers) ->
 
 handle_recv_send_frame(#stomp_frame{command = <<"SEND">>, headers = Headers, body = Body}, State) ->
     Topic = header(<<"destination">>, Headers),
-    _ = maybe_send_receipt(receipt_id(Headers), State),
-    _ = emqx_broker:publish(
-        make_mqtt_message(Topic, Headers, iolist_to_binary(Body))
-    ),
-    State.
+    case check_acl(publish, Topic, State) of
+        allow ->
+            _ = maybe_send_receipt(receipt_id(Headers), State),
+            _ = emqx_broker:publish(
+                make_mqtt_message(Topic, Headers, iolist_to_binary(Body), State)
+            ),
+            State;
+        deny ->
+            ErrFrame = error_frame(receipt_id(Headers), <<"Not Authorized">>),
+            {ok, NState} = send(ErrFrame, State),
+            NState
+    end.
 
 handle_recv_ack_frame(#stomp_frame{command = <<"ACK">>, headers = Headers}, State) ->
     Id = header(<<"id">>, Headers),
@@ -431,7 +677,111 @@ reverse_heartbeats({Cx, Cy}) ->
 start_heartbeart_timer(Heartbeats, State) ->
     ensure_timer(
       [incoming_timer, outgoing_timer],
-      State#pstate{heart_beats = emqx_stomp_heartbeat:init(Heartbeats)}).
+      State#pstate{heart_beats = emqx_stomp_heartbeat:init(backoff(Heartbeats))}).
+
+backoff({Cx, Cy}) ->
+    {erlang:ceil(Cx * ?INCOMING_TIMER_BACKOFF),
+     erlang:ceil(Cy * ?OUTCOMING_TIMER_BACKOFF)}.
+
+%%--------------------------------------------------------------------
+%% pub & sub helpers
+
+parse_topic_filters(TopicFilters) ->
+    lists:map(fun emqx_topic:parse/1, TopicFilters).
+
+check_acl(PubSub, Topic, State = #pstate{clientinfo = ClientInfo}) ->
+    case is_acl_enabled(State) andalso
+         emqx_access_control:check_acl(ClientInfo, PubSub, Topic) of
+        false -> allow;
+        Res   -> Res
+    end.
+
+do_subscribe(TopicFilter, SubOpts,
+             State = #pstate{clientinfo = ClientInfo, subscriptions = Subs}) ->
+    ClientId = maps:get(clientid, ClientInfo),
+    _ = emqx_broker:subscribe(TopicFilter, ClientId),
+    NSubOpts = SubOpts#{is_new => true},
+    _ = run_hooks('session.subscribed',
+                  [ClientInfo, TopicFilter, NSubOpts]),
+    send_event_to_self(updated),
+    State#pstate{subscriptions = maps:put(TopicFilter, SubOpts, Subs)}.
+
+do_unsubscribe(TopicFilter, SubOpts,
+               State = #pstate{clientinfo = ClientInfo, subscriptions = Subs}) ->
+    ok = emqx_broker:unsubscribe(TopicFilter),
+    _ = run_hooks('session.unsubscribe',
+                  [ClientInfo, TopicFilter, SubOpts]),
+    send_event_to_self(updated),
+    State#pstate{subscriptions = maps:remove(TopicFilter, Subs)}.
+
+find_sub_by_topic(Topic, Subs) ->
+    case maps:get(Topic, Subs, undefined) of
+        undefined -> undefined;
+        SubOpts -> {Topic, SubOpts}
+    end.
+
+find_sub_by_id(Id, Subs) ->
+    Found = maps:filter(fun(_, SubOpts) ->
+               %% FIXME: datatype??
+               maps:get(id, maps:get(sub_props, SubOpts, #{}), -1) == Id
+            end, Subs),
+    case maps:to_list(Found) of
+        [] -> undefined;
+        [Sub | _] -> Sub
+    end.
+
+is_acl_enabled(_) ->
+    %% TODO: configs from somewhere
+    true.
+
+%% automaticly fill the next sub-id and ack if sub-id is absent
+enrich_sub_opts(SubOpts0, Subs) ->
+    SubOpts = maps:merge(?DEFAULT_SUBOPTS, SubOpts0),
+    SubProps = maps:get(sub_props, SubOpts, #{}),
+    SubOpts#{sub_props =>
+             maps:merge(#{id => next_sub_id(Subs),
+                          ack => ?DEFAULT_SUB_ACK}, SubProps)}.
+
+next_sub_id(Subs) ->
+    Ids = maps:fold(fun(_, SubOpts, Acc) ->
+        [binary_to_integer(
+           maps:get(id, maps:get(sub_props, SubOpts, #{}), <<"0">>)) | Acc]
+    end, [], Subs),
+    integer_to_binary(lists:max(Ids) + 1).
+
+%%--------------------------------------------------------------------
+%% helpers
+
+default_user(#pstate{default_user = DefaultUser}) ->
+    DefaultUser.
+allow_anonymous(#pstate{allow_anonymous = AllowAnonymous}) ->
+    AllowAnonymous.
+
+ensure_connected(State = #pstate{conninfo = ConnInfo,
+                                 clientinfo = ClientInfo}) ->
+    NConnInfo = ConnInfo#{
+                  connected => true,
+                  connected_at => erlang:system_time(millisecond)
+                 },
+    send_event_to_self(connected),
+    ok = run_hooks('client.connected', [ClientInfo, NConnInfo]),
+    State#pstate{conninfo  = NConnInfo,
+                 connected = true
+                }.
+
+ensure_disconnected(Reason, State = #pstate{conninfo = ConnInfo, clientinfo = ClientInfo}) ->
+    NConnInfo = ConnInfo#{disconnected_at => erlang:system_time(millisecond)},
+    ok = run_hooks('client.disconnected', [ClientInfo, Reason, NConnInfo]),
+    State#pstate{conninfo = NConnInfo, connected = false}.
+
+send_event_to_self(Name) ->
+    self() ! {event, Name}, ok.
+
+run_hooks(Name, Args) ->
+    emqx_hooks:run(Name, Args).
+
+run_hooks(Name, Args, Acc) ->
+    emqx_hooks:run_fold(Name, Args, Acc).
 
 %%--------------------------------------------------------------------
 %% Timer
@@ -466,3 +816,4 @@ interval(outgoing_timer, #pstate{heart_beats = HrtBt}) ->
     emqx_stomp_heartbeat:interval(outgoing, HrtBt);
 interval(clean_trans_timer, _) ->
     ?TRANS_TIMEOUT.
+
diff --git a/apps/emqx_stomp/test/emqx_stomp_SUITE.erl b/apps/emqx_stomp/test/emqx_stomp_SUITE.erl
index 9a5d9698e8..c8ab883116 100644
--- a/apps/emqx_stomp/test/emqx_stomp_SUITE.erl
+++ b/apps/emqx_stomp/test/emqx_stomp_SUITE.erl
@@ -100,7 +100,7 @@ t_heartbeat(_) ->
                                                      {<<"host">>, <<"127.0.0.1:61613">>},
                                                      {<<"login">>, <<"guest">>},
                                                      {<<"passcode">>, <<"guest">>},
-                                                     {<<"heart-beat">>, <<"1000,800">>}])),
+                                                     {<<"heart-beat">>, <<"1000,2000">>}])),
                         {ok, Data} = gen_tcp:recv(Sock, 0),
                         {ok, #stomp_frame{command = <<"CONNECTED">>,
                                           headers = _,
diff --git a/apps/emqx_stomp/test/emqx_stomp_heartbeat_SUITE.erl b/apps/emqx_stomp/test/emqx_stomp_heartbeat_SUITE.erl
index cbced5f433..b0ce378fd9 100644
--- a/apps/emqx_stomp/test/emqx_stomp_heartbeat_SUITE.erl
+++ b/apps/emqx_stomp/test/emqx_stomp_heartbeat_SUITE.erl
@@ -35,8 +35,7 @@ t_check_1(_) ->
     {ok, HrtBt1} = emqx_stomp_heartbeat:check(incoming, 0, HrtBt),
     {error, timeout} = emqx_stomp_heartbeat:check(incoming, 0, HrtBt1),
 
-    {ok, HrtBt2} = emqx_stomp_heartbeat:check(outgoing, 0, HrtBt1),
-    {error, timeout} = emqx_stomp_heartbeat:check(outgoing, 0, HrtBt2),
+    {error, timeout} = emqx_stomp_heartbeat:check(outgoing, 0, HrtBt1),
     ok.
 
 t_check_2(_) ->
diff --git a/apps/emqx_web_hook/etc/emqx_web_hook.conf b/apps/emqx_web_hook/etc/emqx_web_hook.conf
index 7b9d32dfb5..6bffbc7ef9 100644
--- a/apps/emqx_web_hook/etc/emqx_web_hook.conf
+++ b/apps/emqx_web_hook/etc/emqx_web_hook.conf
@@ -57,6 +57,11 @@ web.hook.body.encoding_of_payload_field = plain
 ## Value: Number
 web.hook.pool_size = 32
 
+## Whether to enable HTTP Pipelining
+##
+## See: https://en.wikipedia.org/wiki/HTTP_pipelining
+web.hook.enable_pipelining = true
+
 ##--------------------------------------------------------------------
 ## Hook Rules
 ## These configuration items represent a list of events should be forwarded
diff --git a/apps/emqx_web_hook/priv/emqx_web_hook.schema b/apps/emqx_web_hook/priv/emqx_web_hook.schema
index 8ba1cc0fd4..a80f460c98 100644
--- a/apps/emqx_web_hook/priv/emqx_web_hook.schema
+++ b/apps/emqx_web_hook/priv/emqx_web_hook.schema
@@ -43,6 +43,11 @@
   {datatype, integer}
 ]}.
 
+{mapping, "web.hook.enable_pipelining", "emqx_web_hook.enable_pipelining", [
+  {default, true},
+  {datatype, {enum, [true, false]}}
+]}.
+
 {mapping, "web.hook.rule.client.connect.$name", "emqx_web_hook.rules", [
   {datatype, string}
 ]}.
diff --git a/apps/emqx_web_hook/src/emqx_web_hook.app.src b/apps/emqx_web_hook/src/emqx_web_hook.app.src
index 055b4bcf8b..e0632625f9 100644
--- a/apps/emqx_web_hook/src/emqx_web_hook.app.src
+++ b/apps/emqx_web_hook/src/emqx_web_hook.app.src
@@ -1,6 +1,6 @@
 {application, emqx_web_hook,
  [{description, "EMQ X WebHook Plugin"},
-  {vsn, "4.3.0"}, % strict semver, bump manually!
+  {vsn, "4.3.7"}, % strict semver, bump manually!
   {modules, []},
   {registered, [emqx_web_hook_sup]},
   {applications, [kernel,stdlib,ehttpc]},
@@ -9,6 +9,6 @@
   {licenses, ["Apache-2.0"]},
   {maintainers, ["EMQ X Team "]},
   {links, [{"Homepage", "https://emqx.io/"},
-           {"Github", "https://gitee.com/fastdgiot/emqx-web-hook"}
+           {"Github", "https://github.com/emqx/emqx-web-hook"}
           ]}
  ]}.
diff --git a/apps/emqx_web_hook/src/emqx_web_hook.appup.src b/apps/emqx_web_hook/src/emqx_web_hook.appup.src
index 0c7b8ebf3d..ec716f45c5 100644
--- a/apps/emqx_web_hook/src/emqx_web_hook.appup.src
+++ b/apps/emqx_web_hook/src/emqx_web_hook.appup.src
@@ -1,10 +1,20 @@
-%% -*-: erlang -*-
-
+%% -*- mode: erlang -*-
 {VSN,
-  [
-    {<<".*">>, []}
-  ],
-  [
-    {<<".*">>, []}
-  ]
-}.
+  [{"4.3.5",[{load_module,emqx_web_hook_actions,brutal_purge,soft_purge,[]}]},
+   {<<"4.3.[0-2]">>,
+    [{apply,{application,stop,[emqx_web_hook]}},
+     {load_module,emqx_web_hook_app,brutal_purge,soft_purge,[]},
+     {load_module,emqx_web_hook,brutal_purge,soft_purge,[]},
+     {load_module,emqx_web_hook_actions,brutal_purge,soft_purge,[]}]},
+   {<<"4.3.[3-4]">>,
+    [{load_module,emqx_web_hook_actions,brutal_purge,soft_purge,[]}]},
+   {<<".*">>,[]}],
+  [{"4.3.5",[{load_module,emqx_web_hook_actions,brutal_purge,soft_purge,[]}]},
+   {<<"4.3.[0-2]">>,
+    [{apply,{application,stop,[emqx_web_hook]}},
+     {load_module,emqx_web_hook_app,brutal_purge,soft_purge,[]},
+     {load_module,emqx_web_hook,brutal_purge,soft_purge,[]},
+     {load_module,emqx_web_hook_actions,brutal_purge,soft_purge,[]}]},
+   {<<"4.3.[3-4]">>,
+    [{load_module,emqx_web_hook_actions,brutal_purge,soft_purge,[]}]},
+   {<<".*">>,[]}]}.
diff --git a/apps/emqx_web_hook/src/emqx_web_hook.erl b/apps/emqx_web_hook/src/emqx_web_hook.erl
index 7af83d749a..b556c16f85 100644
--- a/apps/emqx_web_hook/src/emqx_web_hook.erl
+++ b/apps/emqx_web_hook/src/emqx_web_hook.erl
@@ -326,7 +326,7 @@ send_http_request(ClientID, Params) ->
     Headers = application:get_env(?APP, headers, []),
     Body = emqx_json:encode(Params),
     ?LOG(debug, "Send to: ~0p, params: ~s", [Path, Body]),
-    case ehttpc:request(ehttpc_pool:pick_worker(?APP, ClientID), post, {Path, Headers, Body}) of
+    case ehttpc:request({?APP, ClientID}, post, {Path, Headers, Body}) of
         {ok, StatusCode, _} when StatusCode >= 200 andalso StatusCode < 300 ->
             ok;
         {ok, StatusCode, _, _} when StatusCode >= 200 andalso StatusCode < 300 ->
diff --git a/apps/emqx_web_hook/src/emqx_web_hook_actions.erl b/apps/emqx_web_hook/src/emqx_web_hook_actions.erl
index c88f8f39b9..79aefdb851 100644
--- a/apps/emqx_web_hook/src/emqx_web_hook_actions.erl
+++ b/apps/emqx_web_hook/src/emqx_web_hook_actions.erl
@@ -57,7 +57,7 @@
                          type => string,
                          default => <<"5s">>,
                          title => #{en => <<"Request Timeout">>,
-                                    zh => <<"请求超时时间时间"/utf8>>},
+                                    zh => <<"请求超时时间"/utf8>>},
                          description => #{en => <<"Request Timeout In Seconds">>,
                                           zh => <<"请求超时时间"/utf8>>}},
     pool_size => #{order => 4,
@@ -67,35 +67,42 @@
                    description => #{en => <<"Connection Pool">>,
                                     zh => <<"连接池大小"/utf8>>}
                 },
-    cacertfile => #{order => 5,
+    enable_pipelining => #{order => 5,
+                           type => boolean,
+                           default => true,
+                           title => #{en => <<"Enable Pipelining">>, zh => <<"Enable Pipelining"/utf8>>},
+                           description => #{en => <<"Whether to enable HTTP Pipelining">>,
+                                            zh => <<"是否开启 HTTP Pipelining"/utf8>>}
+                },
+    cacertfile => #{order => 6,
                     type => file,
                     default => <<"">>,
                     title => #{en => <<"CA Certificate File">>,
                                zh => <<"CA 证书文件"/utf8>>},
                     description => #{en => <<"CA Certificate file">>,
                                      zh => <<"CA 证书文件"/utf8>>}},
-    keyfile => #{order => 6,
+    keyfile => #{order => 7,
                  type => file,
                  default => <<"">>,
                  title =>#{en => <<"SSL Key">>,
                            zh => <<"SSL Key"/utf8>>},
                  description => #{en => <<"Your ssl keyfile">>,
                                   zh => <<"SSL 私钥"/utf8>>}},
-    certfile => #{order => 7,
+    certfile => #{order => 8,
                   type => file,
                   default => <<"">>,
                   title => #{en => <<"SSL Cert">>,
                              zh => <<"SSL Cert"/utf8>>},
                   description => #{en => <<"Your ssl certfile">>,
                                    zh => <<"SSL 证书"/utf8>>}},
-    verify => #{order => 8,
+    verify => #{order => 9,
                 type => boolean,
                 default => false,
                 title => #{en => <<"Verify Server Certfile">>,
                            zh => <<"校验服务器证书"/utf8>>},
                 description => #{en => <<"Whether to verify the server certificate. By default, the client will not verify the server's certificate. If verification is required, please set it to true.">>,
                                  zh => <<"是否校验服务器证书。 默认客户端不会去校验服务器的证书,如果需要校验,请设置成true。"/utf8>>}},
-    server_name_indication => #{order => 9,
+    server_name_indication => #{order => 10,
                                 type => string,
                                 title => #{en => <<"Server Name Indication">>,
                                            zh => <<"服务器名称指示"/utf8>>},
@@ -254,19 +261,19 @@ on_action_data_to_webserver(Selected, _Envs =
     NBody = format_msg(BodyTokens, Selected),
     NPath = emqx_rule_utils:proc_tmpl(PathTokens, Selected),
     Req = create_req(Method, NPath, Headers, NBody),
-    case ehttpc:request(ehttpc_pool:pick_worker(Pool, ClientID), Method, Req, RequestTimeout) of
+    case ehttpc:request({Pool, ClientID}, Method, Req, RequestTimeout) of
         {ok, StatusCode, _} when StatusCode >= 200 andalso StatusCode < 300 ->
             emqx_rule_metrics:inc_actions_success(Id);
         {ok, StatusCode, _, _} when StatusCode >= 200 andalso StatusCode < 300 ->
             emqx_rule_metrics:inc_actions_success(Id);
         {ok, StatusCode, _} ->
-            ?LOG(warning, "[WebHook Action] HTTP request failed with status code: ~p", [StatusCode]),
+            ?LOG(warning, "HTTP request failed with path: ~p status code: ~p", [NPath, StatusCode]),
             emqx_rule_metrics:inc_actions_error(Id);
         {ok, StatusCode, _, _} ->
-            ?LOG(warning, "[WebHook Action] HTTP request failed with status code: ~p", [StatusCode]),
+            ?LOG(warning, "HTTP request failed with path: ~p status code: ~p", [NPath, StatusCode]),
             emqx_rule_metrics:inc_actions_error(Id);
         {error, Reason} ->
-            ?LOG(error, "[WebHook Action] HTTP request error: ~p", [Reason]),
+            ?LOG(error, "HTTP request failed path: ~p error: ~p", [NPath, Reason]),
             emqx_rule_metrics:inc_actions_error(Id)
     end.
 
@@ -286,28 +293,33 @@ create_req(_, Path, Headers, Body) ->
   {Path, Headers, Body}.
 
 parse_action_params(Params = #{<<"url">> := URL}) ->
-    try
-        {ok, #{path := CommonPath}} = emqx_http_lib:uri_parse(URL),
-        Method = method(maps:get(<<"method">>, Params, <<"POST">>)),
-        Headers = headers(maps:get(<<"headers">>, Params, undefined)),
-        NHeaders = ensure_content_type_header(Headers, Method),
-        #{method => Method,
-          path => path(filename:join(CommonPath, maps:get(<<"path">>, Params, <<>>))),
-          headers => NHeaders,
-          body => maps:get(<<"body">>, Params, <<>>),
-          request_timeout => cuttlefish_duration:parse(str(maps:get(<<"request_timeout">>, Params, <<"5s">>))),
-          pool => maps:get(<<"pool">>, Params)}
-    catch _:_ ->
-        throw({invalid_params, Params})
-    end.
+    {ok, #{path := CommonPath}} = emqx_http_lib:uri_parse(URL),
+    Method = method(maps:get(<<"method">>, Params, <<"POST">>)),
+    Headers = headers(maps:get(<<"headers">>, Params, undefined)),
+    NHeaders = ensure_content_type_header(Headers, Method),
+    #{method => Method,
+      path => merge_path(CommonPath, maps:get(<<"path">>, Params, <<>>)),
+      headers => NHeaders,
+      body => maps:get(<<"body">>, Params, <<>>),
+      request_timeout => cuttlefish_duration:parse(str(maps:get(<<"request_timeout">>, Params, <<"5s">>))),
+      pool => maps:get(<<"pool">>, Params)}.
 
 ensure_content_type_header(Headers, Method) when Method =:= post orelse Method =:= put ->
     Headers;
 ensure_content_type_header(Headers, _Method) ->
     lists:keydelete("content-type", 1, Headers).
 
-path(<<>>) -> <<"/">>;
-path(Path) -> Path.
+merge_path(CommonPath, <<>>) ->
+    l2b(CommonPath);
+merge_path(CommonPath, Path0) ->
+    case emqx_http_lib:uri_parse(Path0) of
+        {ok, #{path := Path1, 'query' := Query0}} ->
+            Path2 = l2b(filename:join(CommonPath, Path1)),
+            Query = l2b(Query0),
+            <>;
+        {ok, #{path := Path1}} ->
+            l2b(filename:join(CommonPath, Path1))
+    end.
 
 method(GET) when GET == <<"GET">>; GET == <<"get">> -> get;
 method(POST) when POST == <<"POST">>; POST == <<"post">> -> post;
@@ -333,16 +345,18 @@ pool_opts(Params = #{<<"url">> := URL}, ResId) ->
         cuttlefish_duration:parse(str(maps:get(<<"connect_timeout">>, Params, <<"5s">>))),
     TransportOpts0 =
         case Scheme =:= https of
-            true  -> [get_ssl_opts(Params, ResId)];
+            true  -> get_ssl_opts(Params, ResId);
             false -> []
         end,
     TransportOpts = emqx_misc:ipv6_probe(TransportOpts0),
+    EnablePipelining = maps:get(<<"enable_pipelining">>, Params, true),
     Opts = case Scheme =:= https  of
                true  -> [{transport_opts, TransportOpts}, {transport, ssl}];
                false -> [{transport_opts, TransportOpts}]
            end,
     [{host, Host},
      {port, Port},
+     {enable_pipelining, EnablePipelining},
      {pool_size, PoolSize},
      {pool_type, hash},
      {connect_timeout, ConnectTimeout},
@@ -353,7 +367,7 @@ pool_name(ResId) ->
     list_to_atom("webhook:" ++ str(ResId)).
 
 get_ssl_opts(Opts, ResId) ->
-    [{ssl, true}, {ssl_opts, emqx_plugin_libs_ssl:save_files_return_opts(Opts, "rules", ResId)}].
+    emqx_plugin_libs_ssl:save_files_return_opts(Opts, "rules", ResId).
 
 test_http_connect(Conf) ->
     Url = fun() -> maps:get(<<"url">>, Conf) end,
@@ -369,3 +383,5 @@ test_http_connect(Conf) ->
            ?LOG(error, "check http_connectivity failed: ~p, ~0p", [Conf, {Err, Reason, ST}]),
            false
     end.
+l2b(L) when is_list(L) -> iolist_to_binary(L);
+l2b(Any) -> Any.
diff --git a/apps/emqx_web_hook/src/emqx_web_hook_app.erl b/apps/emqx_web_hook/src/emqx_web_hook_app.erl
index 492af628d5..8265fa664d 100644
--- a/apps/emqx_web_hook/src/emqx_web_hook_app.erl
+++ b/apps/emqx_web_hook/src/emqx_web_hook_app.erl
@@ -42,10 +42,10 @@ stop(_State) ->
 translate_env() ->
     {ok, URL} = application:get_env(?APP, url),
     {ok, #{host := Host,
-           path := Path0,
            port := Port,
-           scheme := Scheme}} = emqx_http_lib:uri_parse(URL),
-    Path = path(Path0),
+           scheme := Scheme} = URIMap} = emqx_http_lib:uri_parse(URL),
+    Path = path(URIMap),
+    {ok, EnablePipelining} = application:get_env(?APP, enable_pipelining),
     PoolSize = application:get_env(?APP, pool_size, 32),
     MoreOpts = case Scheme of
                    http ->
@@ -64,7 +64,7 @@ translate_env() ->
                                  SNI0 -> SNI0
                              end,
                        TLSOpts = lists:filter(fun({_K, V}) ->
-                                                V /= <<>> andalso V /= undefined andalso V /= "" andalso true
+                                                V /= <<>> andalso V /= undefined andalso V /= ""
                                               end, [{keyfile, KeyFile},
                                                     {certfile, CertFile},
                                                     {cacertfile, CACertFile},
@@ -78,6 +78,7 @@ translate_env() ->
                 end,
     PoolOpts = [{host, Host},
                 {port, Port},
+                {enable_pipelining, EnablePipelining},
                 {pool_size, PoolSize},
                 {pool_type, hash},
                 {connect_timeout, 5000},
@@ -89,9 +90,13 @@ translate_env() ->
     NHeaders = set_content_type(Headers),
     application:set_env(?APP, headers, NHeaders).
 
-path("") ->
+path(#{path := "", 'query' := Query}) ->
+    "?" ++ Query;
+path(#{path := Path, 'query' := Query}) ->
+    Path ++ "?" ++ Query;
+path(#{path := ""}) ->
     "/";
-path(Path) ->
+path(#{path := Path}) ->
     Path.
 
 set_content_type(Headers) ->
diff --git a/bin/install_upgrade.escript b/bin/install_upgrade.escript
index 0658bdef27..97548cba8b 100755
--- a/bin/install_upgrade.escript
+++ b/bin/install_upgrade.escript
@@ -248,15 +248,20 @@ parse_version(V) when is_list(V) ->
     hd(string:tokens(V,"/")).
 
 check_and_install(TargetNode, Vsn) ->
-    {ok, [[CurrAppConf]]} = rpc:call(TargetNode, init, get_argument, [config], ?TIMEOUT),
+    %% Backup the vm.args. VM args should be unchanged during hot upgrade
+    %% but we still backup it here
     {ok, [[CurrVmArgs]]} = rpc:call(TargetNode, init, get_argument, [vm_args], ?TIMEOUT),
-    case filename:extension(CurrAppConf) of
-        ".config" ->
-            {ok, _} = file:copy(CurrAppConf, filename:join(["releases", Vsn, "sys.config"]));
-        _ ->
-            {ok, _} = file:copy(CurrAppConf++".config", filename:join(["releases", Vsn, "sys.config"]))
-    end,
     {ok, _} = file:copy(CurrVmArgs, filename:join(["releases", Vsn, "vm.args"])),
+    %% Backup the sys.config, this will be used when we check and install release
+    %% NOTE: We cannot backup the old sys.config directly, because the
+    %% configs for plugins are only in app-envs, not in the old sys.config
+    Configs0 =
+        [{AppName, rpc:call(TargetNode, application, get_all_env, [AppName], ?TIMEOUT)}
+         || {AppName, _, _} <- rpc:call(TargetNode, application, which_applications, [], ?TIMEOUT)],
+    Configs1 = [{AppName, Conf} || {AppName, Conf} <- Configs0, Conf =/= []],
+    ok = file:write_file(filename:join(["releases", Vsn, "sys.config"]), io_lib:format("~p.", [Configs1])),
+
+    %% check and install release
     case rpc:call(TargetNode, release_handler,
                   check_install_release, [Vsn], ?TIMEOUT) of
         {ok, _OtherVsn, _Desc} ->
diff --git a/dgiot_file/user/profile/Klht7ERlYn_companyinfo_backgroundimage.jpg b/dgiot_file/user/profile/Klht7ERlYn_companyinfo_backgroundimage.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..79b0433cf3dd99897199b7d1d0135d12340d5a5a
GIT binary patch
literal 385221
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�k>qmmBWJsN2#?JDzD
zh@P)s85%W6)|Py$8x`g39+f$-5!1OYLs^VZu6i2b*@8d0$u-ERe#x*JvMuhlxxJ$@;g
zyo_(v(OR+Jis;UkDtC-4HA-Ku`ne`kxyN|l6mm}#rs*HB?x`HOKO6UUG{ccmS}8Op
z+W5LwdE=lr>UDi2zF5z&US|I*2w5;(FaOSU4&L
zYGX!7Q|I-vPemIKEQ;E;?YfH~!`!T*x_U_PQbSEs&$fU1Y+~)}?N|bLoTrqZ>|(yW^Cst;Q#+ycI{+R<&6kKXXMa
z1UM+RRqlqlTYWMKPGcR&-f(IrHAXdv1J7IMZa*OWPi#MY7?DsM;j~^Xl~8CSEe$K(
z*ko%d530*C_E=eK>!QV^w&@kYqsM)XLp*RL7U78~-WJ0Swj;d2HV|ZaIpjXyv(TgC
zS>CNt(jsKF*GvxCN_
z#h_u(X$?Bj*4Swpkb{nUn4n2OXCn_K^oom(so2o}FsA>*7>;9o-ziJC?KzsTSkTI^
zXc@KFg6QsgR@S_ssk&37+qLKH{GOQ$5US#AxYMSbJibX3weHa&A%S|R=b12e-5F~b
zE3#_EuXU1eJXZW0kk7FE(;kA(Wj@ZkKM+xTZHQKFNW5#Dxwfs7_)?_TK(|TH4TcMc
zX3Zhp8n=#-m71%L@`C<7&M?9wM7@ZlnN^5^>N$G*lVag*ubUKW<-6hG5Hf9~-6}IAOXMVVBrbX@u+er{WmMU5^8p
z7Ve)0e*Has^_26XloiPg*(mhpM5Bwm$WjoBo7}Jm0Z?>JO=-*$YWajY3MeTFj12Yq
z=VRN>?h~k>Yc3&g2PPkD$oVYpc~AQQ8K;9;qqBcm))(QGNTp&k3BRK>7Ybl%wH*nQ
zYq!>IG2^REmOq(^ew74*fq_q&P}%YxeL#+%^K?v-nv&gYl2UQbjCwePJ*?jL@#t^2
zEd&2(94qd6;(^0DaB%0zP%A0XKgbr(O(a||+$;A&4Py}P6Vb-69UM)K_tT=@YNK2q
zr`(xNDX2u4wZEmW0+Ua230za~W;{ZUg_pbtKRqZop{vMer$EF|^-QBN2wcmJzYw!$
zX5=QW3i1x`fl$q~E$mb|=+8Mq3+6=opp>#-Bd{E;rIY8P(S{l2W~HN8INCSE0!DdLbxq6OplYg3p04+ZMuIbgNg2WXY0ta0zKg!k@X-yU8c!C4E7u#b=TxvXxr39G?IT
zTt2GoTbI76zdfKZ5c25Hv`0u>B5II&
z(=vSL8029k)=*$7!AsZfN?BL&MS+qbgp`s7LGQ;FmPt|FW4#kg(k|Vh8SDC_1s9(#
zQiCx)wkGaVoW{48dnoSUuhyxR=8^#?fOk-n&gWXnd_NV=L=VOw%*wG?+{4O4`(j;M
zn;iC3gNH99!yTqkB?@rp(DTga1~D2B6#2T`i58_@_`6)0lsSw{O(Tg$7qnmr_F)kzc?}7RiL#a7U=qy8IxVPQMcAV?pb_ZfX84p4PMXpi(S|L&s
zS+~txb_1n4w7a9HL(svi!IXNHy81-;f!<0*4rDa;ykvS>F*zL3Ic(n7>l_Jwyw;}i
zg}H+TTzfD={-qALDirJF2j|r+t)ZX)VZ?Cl3WuZ-GNgN049&cDMzQ|pKW)#NJW|C?
znYQ{sQ6YYkXgV829xs1ycI+>e)$Xajq0=!X5tL!+V8Lc_ULnn+5d>=IsMRjX3>Z^V
z?z~%sP`O8iZ^MdMDU?0pDLd%pu1&t-nQxh%*IQcBh#!l6@8FV^jy>W3h~Hy|$3yZQ
z1@r8YX{37p`l500xb~XPdTlEVD8(2Pb|tM9zhS1o(JJETTIVgvo&BJ>d1`=KDIMO|
zgs?xjcb)XpoOFgk#gJ-G9La~;nVW2SwZ%`~t67o050Xvqx$vfJWa>7vg%S8^F+tz{
z@fq5s@fx1A`bfmqE}-mx&k)oim?6MwgqgoJZXX~^tN2!?lBm!9>M_4gLM;*HtgRwG
zSl-=%UEy5xwx>_aYw2Dix+b${})wLClaXi&0tS36v_<$jZ20GnPLhV4||c+I@D%{GR>)d{Qkkr
zVO9h8--B~za7A(^CK};|Zm9k9{-c3A5k@wocl+8fPly3i$YAd`Cj0D1r7n30e_c3D
ze4KIeJC@o*WD^NBXW{edR@sz(N7=}JME*v8LI;`YcdK)|O8_b(_4?HqXOmKdXrp~=0oX|NX
zA?p71J14-A{m=Bfe^(m6lgz&By@@zTxWt??D~{0rfPc%X7b;{z!bHdM=F{0>W@(NXo8^4W>o30m9;aHTXa=o1_XjOm5?iikUj*h`m@1y>*uTiE(2|rh
za42{&_ASW>t!VN2qxs@g6Jz2zT80Yf;Mb@pacC;}q1rr;X`(pz0_sXkC7phXo>fW=
zyf$F_F5|FM`TiM5C4Shy_eD8+?79o%P5z}+2G2LeG-=Spm_PP8(#OP=9;U{~M4Uva
zXpml@#?by#Gj?Lq$cce7H1~E$zL#mC+kYUP@T9wKKqF~b+3aOnFz!A&5O{I%B_(r$
zXab8^X(?Ci701P!XFhlLOr9^EbjUF|=-#n%_gaTnzM}ihdiv+1WVsnGXK=>CkLX#Z
z6OBtw)(hird1!pu+TAk`4UAn~W_RKwUBP_J`L@R2Ey0I*I_N&?)+aHmiC1I#vr%LI
zCH~DA8)Bz(CO9FOqAF?1P?9D-(od!;vrI>2udZmtQj(^=q<)tssd|xCYFOz&oLGP<
zdE;B+^t{Mxu>d^-)*t@D(n9p+t(o<=iBz)WnLTlgY_^GQQ%a2N!$r1W^)+9%A0Aas
zv+r0sU!jYFCOWhqr)DhuqV#Y%F3=VmX1|D6{t;;Jk*)=%P?UxT=D&$4lg~`I*7=$k
zDWnIus4K*XycZ`%Zif!(9)lQ7d0B+m`C=sOnG0P{{ls}KlJ{&aY{bld_9RN(SMWYnw$@wfqb8YjP+8|e`jm8zG8QL
z-s)bd;;7@!t%U8j5lyN`>|kKsXe
zOu!i+NC(oAyknLw4vB-W3PzF{lb|`zHKpvYKC!wk=tLF=UbIU5e%fiHL^P3I#f^X@
z?mYOP3b@#@^XRf4?W3C!Ij}>TnktX87%y
zk=-DVa)Z4_5y#awg=VNp5zA5Gn@WVs=ft_DF3Mg@HVeM14?d;+x#Y>rV(3WShZt?N
z0pwCMN(~J%G0pQ1%#_YTjSoGrMK&41^eVo6(;j>YLzjfsotm4Y$%o>j?Ze4+SA(2d
z-Iwtt%B7lrZwJpz-4vKL&WH*R(ogWrfi*|2N1QCuDs(9{eq_7)`0B(s%!YcE8fpR6
zT1rkN6qiCR^VWg?VZ4X=AfOObv}7O9Oh05my-qbZ$UEOqv+VTpW|!h-uFUzy^I!wg
z0;6mml52$kx0c)2eRx>Erm?K|b~H$+*GtHv6c%eNHk?#Q>jrJPOsQ+A$x$;mLEA!|
z4n6gAB__2-7KiCyw;vjJdH_pZYyZwtQABnvPg4pMs>P{>wSt_@bLFu~4`V)k^$$^k
z+jkOsn;dZ(4%1qGeYb}Feb?Nz)Rf{aaZ~yeVEH}^D@kd3u24NZTrCHFb8zo`v{#ev
z?EbVyDWw@nsAuK|Ib>GGAt4QW@Tebq#S7%Dn`HgLx})Ye8D9wb;F%;$P=1*;91LC)?FWy45#=6mHZUQim5UGB|uqnS^Me6xvj
z%T2q-&B&CfQZNA8gbVXpR=4M_oh6K1s}O|inBvolu6LY5VeA_aHI9^B%^-<(?%{ZN
zHcggGKJ~(ndk$%J(ol1}8&79J`a>5qRp=Yc{>MxjQSRG>DbuCVPfN$o7Z+<^COkL^
zQqKNd-WKDr&z?VahYOU=%oW)U+-rei0#Ke3%XtiA7x4CeUD+ag4|}5zPsuBV^kDrh
zpZb3^qAJlwYpC|zPq#}Urz=N^D&iLF#DXVooYo}8ltYhQ!GHFFDIG-
z{9HF~*#p@04Mq*NYU)_DOUocldxGvm47sg&0|Gk|V`
z1y>0-PB;}$ewrOwX5ZijPhT57rLbDN9Q1XP?!xq!2uZSn0=snnTGGEtp4LH}>lg9B
z8q>5_L?jv?2=5=!t0Lm6Z4K1YLfqIB{eKu5pe)4uh2W5eHDk~9=rYvdSNR@!L%oWZ
zzQjEQEs^enej}d+_lsFN*#Zi$t@xJyHQCmXx|aR~>;H(cfey6x2^Qg+N2y;HE+kya
z)Q0tJp3}|NocAf|jyQZ1P3QQPoX^>&@cPfRgKJ8XeK%l!T50iLs)2rLf8)AK>}iYl
zhrU6S^zlkF%sxELXttgL_Tv=?lJ6$!Y(=H>4I{S%I@TmXx9EMn6dv#V46RlAtdhnz
z)(ldVQ!WxuC^siklcS^pK8UQ;r4+FZ#2Be3IV{+7(l?*^Z~@4Rskukb)X9F$SLQoZ
z$NRQZc|(+;@-V7;h^WMgwl1q?cXp8`;LDa&iV??rR$!OTvc=qpICHAJ3lN%3pofD7gRK*#2RKtU8voO|P`|HP?
zYY%wQeSO9e>w(stoRR9vq5Y_*B=^RT*Qw=6STx(P5j>}^4iKUn6=7`Ogsx0mI7l7-
zXrtv)&1QnPZ*m^Y%t`D+bKBL&9$XOfqeo$y^}Zd07Nsbb<)eOGg+|{xpI6CG4*-JU
zY)*xw)wm}*!2|Dvs1)UNW@UBX9xUamLa_4_6}>ac954qKxvx1QZP?T=swtT4mgpuH!oh2)Z%TjvA^|QQyZ>HKXPk-Bx)m+MoJ9
z&lhwVlMe#`)b=UH#2rLx-D5h%mBe}azQ1)g@XiR2XL#p?;Jnk(7H%jd9wp1eb?%o>
z#}gm?pbi_YBXg0ZNTlVP@QL7^mEw}^?v?V+)bt1~EKnX3*#sLSt4{T&1kz|D!>Gw*
zUA0mSPvrYK&ON7*$w$p4D&FP`Zc!fDR=lQ_k+`-dlJmca_ADgD!UUsgyPklYi+IoWkmN(
z_XfCu1PBlGe;|}N6YiU8cn>r~f+;Oon8vo`b4UZ%qx+YKUXm{-
zIfHcCjC%wg_bl!iW3T%4{FthNroID0p8tnI5$0}#GcRoG0K%((6^~cz^R#QnNV~ti
zIM^o5kIS@Ds#9MkE27d~w5kEzH_VZ8GMjUn*lKLT-u~I1$`RhAY?6ZdV|)ruY`Uu(
zYJ^5REXH^Al|FlVUHioX5~DV=WMk6#t$zJ9?be91vETZ36bo=d3D8J0BN~Fzx8ywG
z@paj}d03|XG8?a0c44C5&Iqj%U}!Eu3)FJW@B0@H%HZ
zpbGTBo5mJJ5HheHaMLbT-WkXgknlt=<9V}U@VHsVcvM$6Bz18h<0EhsixUMtE-0_z
z@3M6<4;60`V~9>Rm(xL&2KmG_#7C_s#|!6LE(kkP`pi%J#B@z)=;PHaYK@WTx4>6&
zuczRwHZCh!RX+4B!PdLW0Tu&j&7a$g2Beb+#=lexxL;7|mY6$QP8Gwe3=jCR6i^Z3
zTS+J5?=ajTV_7@JjN)dsF3^3m(enB3X$1tVrY-DhFZw%^YT>9zs($X1er}!;)i&!Z
zTULq{)YZMJ`IOV~+Z?n&7qD
zPzB1V!lX73STn`oN($f-zje#ooMk+8@=G&C5#@TSxU;O_6$zzXlo|#amRy+7B5Umt
zI!M&5JPrB}iGJdxpbYwMR~1lGDwkz(oh01)rhY*8j4l=6)JjB6{nj{c%>4)&l%k^y
z*;6!>abG{FJ@8KgKA0^6zeAYoDfYMmDywHZ*(1_~?nFB+|NTEx0p?Q5BJ9`d7;kLZ
z4lgQjXDRUZqags1+(X~e_@0sagb0rNO(e~At6k=G{avw@_Hm08yX-%%rZVZaS^fUi
zKY_Av_4;@_FgOk@vlM5pPk=OK1nZgUNeqRs==rq%Ag&!6=)a^uN|#9#8EGIpj99js^hF1+fFRD8nwv*~?xFO#!e
z!`quC@60S}eD&0$bL|m?zGAzH-nlrqnpPgTDwmEM%*KM={@hR?&L_8$>`l^o7`@!-e-z;G5wFBzOd
z^cT{aC`zyr*6~3k7uo2{oPYzWoU6O06%BCu8{zYHgK`G{!&^+-YRW((|5Hi7LF>Fa
zkwm*lT7l@eBLT@
z0<_9?!Ycapu(bhgX-UrExD8v8kd<0tSJpm(s{7%ttfnh2dMh8Na*t2(zHTFrW++~6
zYog<~$Z!-+udTe437Go&`Vlj{R&=d3O7v)f-kSlfN*t8uHj{@8{gxRvK+%*LFBZ}?(Q@~Pz5dL=$f#Xi?Cbh7EO`D=9H+$wX{SAa)
z(@2+N`Bc>mZFoSQ0PXj&?M72U*8J5NB|;3fFV@@YFc|4ruWhR^>ViS|S+-grxBj#$r-q91FO{sj^PVSyvnGWq!|
z)fLkb6)6Ni)WDE@vP^N6ZCO9X8ax;d+=KA=C*;MfCvZF_qW!Jcj~QBo88-8f@XDA^
zPY*KEmz3<~&g6I3mjrJ=R2o<}O~ozgQN`GA>hU*b1SFQBpV%OXn2!Rg-+6q*YXtN<
zISmTTwT|pL+BPBAXQp{-m?HqPzNs>l>o15x{;AALa-g1LeyOt5Xu59wtB0$e()!m3
zSM6xUkTvu$cgXD_nP{+$m>*Qc_ZzbeJ=$TN=F_cb#ybjtcp1eYkfh
zO0(*2bG3gHyjA8RldJD&&`^H6hBzqwEoDpY++)+yr9
zQaZI4yc4{uDlg6i*%#H4!e(rhhTPLPbku-K7CBo9N$FXLIa2+D&}6q|E3Z;Yxx|kq!D~{g|?IN@7AKXJscRMx~a6Z+CMur}B3pgw9^1$*}p#ASQ6CIJ#6CSoh
z0cEKj`Zn_@qOIGfem{u`jxjGtCANPv)7+1I#y3kZZOvnUDnw|6K?VkyWYt{nv`(q9
z6e1*MWYk`
z`x$4BiS*1!O(8gsA9KJgSJ}vogmt)WCyS^AKc(c?m$Kf{SJ)Ylj2GFE9xvhoO#X%9
zR;vVNNyR}SqmqSmTxecsxJHt3$D2?b)697>j{e{vvAFDMy003cnTR!4{nwZR>I|4t
zx_h5k7m6QcTjPnThRi-cbKPD`nRR;RTJ@8tgmCs3t}^{c(qd%ZE3_71c~(3ajW8*g
zeX-E0m=df$=Asc=J{s(kU~sI7agcy1lR~VNE6FKOfXL3iSPiC&=%
z6?RUi{SNQw<046Fee^X7s?{|3nLj^6`_+SViKIRzprvy=1s_>M`#dz!+KOF<6xwXG
z{e0;2bEC_>ge&~3a|zwSU^V{|^r@Zp7m2Z$ztG%4D=V!e1X139P<(*uw+S_tlc!-CUIi@nLeuX5|&)?
zVC0FPYhomrfUKE*0anrj$%cEMSHc%wR6Zl|E=4;sB+DGAeb6y#=Xae;-EA^_2HJmfj1w
zCCj2KuOp!U9B~<4dwyF)NyvH=m%S`4f&PoqQ7fY*5O5=1Ang35m+VhyHzgQP4=u_4
z%uc1Ss%|1^62+Q8WfR+!D{+6CIc*Zp!Y2q7>0IPdG1kucJX1gaqn&gh{CUdfQdS21
zTJ(uienaOJu?;PP(PW&+u3v*M5~rqwZ{rEn&Z2984ZNNB8{;QI9~+_6{9X2Y|6!1(
z0!s$5^Y~aFRewC|J?N)#IR=Hu^a47Xpl|}_$#vRcZ-4yFh$2UWuRQ!d2<5yW$h@XVujGBbG
z&a~q|IL_IcR&@67Y?HO4qnWx|&`hEFP%$w2*QAotA4QJyx%WDVPhsEzHQ^lQT1VK@
zQ9Nb6VFOqBi%xbev)L9|yfc95`wpHdqk3NhRc=*lFWJ1e6I~uIBe&F!YUGi2JC0p}
zlf(QKbV_Mj!>1osdNhBgMf-34vu5=b*_bG_*wgOOjD4j%8P#-l;cpmIYy6H795%Ox
z;+b4)v}G^Nw4z1*VF++gtFpnHi1s_~6B(X}r;LV#{65sV;e9(4;HuD!F^KW)#gBiE
z89N4}hkWH3Rz;oBP^q0e-WP3*WY%OuO!0Yo<=U_Hbju{h0smo`nY{jO-uk}DvY?qM
zfZHxY>Hkv=E_?P|s$o>`$#$JZTEb6I)I5l=pGU}(Uc=+nrPmu|5yim6Imhf|HmlwxK-M^H6QKLm)6XXR-a-_A;|JU?LG`8(10g4hipKx;bHH{@@=PchI>@pc)Rb())69jO+NgqBx^(}
z@U359Zq|@o-Fhx&(JtWzGAsecgtfobs0(yyl}1e1Ag_5zMa)ULw{7o~3>6L)SNf?(
zluZ8F!f~8e@~`8A!{jq9*}gU^Cq_Y1*VwsuuwM~~R_+kAM)50aoNoLz}8+WR!`A3
z82epO4iIl%bx#cRQx4$S$ipEI*tCXGbjeo8BsZPi-gKu3F{{ZiqsDm2H>urX<0asF
zx-Imt?Tykhr=lVoFxt8LnyBAgOHVmW!FViG(FFNQLeO4y;O2$;-$EslXHXs#fn-s@io6GgefP+qBs#h
zmct2^O|@+8R|YwS2kNa>KFUdWnsi^UVvKA&cjNA^TP#1R-9D1j_B%r~O}QB1C#_gR
zV!z;~oL8KPj(KEil&0$@Z|nxAp1fqe*#`ZFzBwwWa;#55)Lg|t*oMy?97JDz2+g+g
zb<1jlR^+V0!ha}C0c8kQvsyoqyw%%^1u)jdjT_ia*mH5M%^zzU2Eoz&(488^v~2Yt
zZgkF4&zzb0fpEDY>zl0c?e=qLA5i&Mfo-9~OJ1brQ?|@`15ubYc-8N3P*YFPxuoWw
zwu#Q_cKn!*ITTIjI(ffzv;o_L4!x22op1Jo2;i!ANLpf%=t(<5Jm>)HWVxmQ9l#5F
zTXNMDp1oj|P|p0+9e+*ab<|27NBB&aMU^qLVa2ax@%Lvw_i
zu9#*`R7ul#QLKF3lSfyG-CyJ8GC$qk+|4xoEH-WPIg+%)R_Xu5O%#ugPb`CEY(d_b
z@u`nw4b8YN#t6NlXuYkX{``r)@|pRlbpeEXQCyL3z-VjSII6Z5;%imofG4+0hYPX%*SO
zBbcA0+cQEG4;8#wZYu54PW=xfK-snNa-FSjCUj2hM0D6y1Mu|Y-VtSiiuHQXIm$(u
zftAOPR_n_l&WM!?kfO7Jt9K&L<>JD#6En^gyP&RyW6vGREUF8c8ZMcc#(NGr{2oys
z^Xg@*TLTceomP*YcXXnoK2Ev4DV$#qHpMBJ+bUwU@mK+3uLV0cA_U-|sjuL%wk3b3
zpu`J&DZli<1E0*~eGVTuE}DQ&fK(6A*2=w13g0T#gQCr1Hvzl>Usl=Jc#u4({6+5j
zj<;hIhcM%#R&m+|+Zt8l#^mQ|QzB10W_u;Kju@YMT77T09fesa!}5CDV&gq;YnNQ&
zbnO>PcD2~mIsgk7&ig)1Hel>&ZIo5rQAeYIg}udf{JWm7tKPv-oqxRjVa&UG*|xlZr|7D$mKpkU_xy2~Tc#(bX*
z*zlV7K@4qVb9A2Kzdu1)G$E8C|E?jWsU{1JJbLG=w34ejW!p}#YT`WMVGLZY#MyRD
z>B2RZ!X0YC8p-#WPPz%%P~A)(M$
zS7f+K@_o8J7VZ@Q?9dyLAOP|TkNMR?lahQ~S
z4{T}i@#KLVtKAbyz2qW*e|~DZm^Laa-d2ekF+41BC0g-8M2I-ASz6{>EH{U-iU_vI
z``A^D-92p&4+R-?^mMwQ{-E@oTyuV152eaB!gnT1PATV&DfPNG3xEDT7*FNMrg_@e
zOL=oJ1|96y4H~Xq6i;$5KmW8{=VVU(o!%BMoNVIhHQGqsZhOU>>32U}>+&2%jxusf
zIOAox7U%Dc`(8&YcLN9Rg~bH0i+iwmb0n1$**DH~wB0#w7gdcEC|o%e5t3IECL}T>*lZ@8y@1`s~S)3fEJpqaOEq*Ybxwm3Uv?WqEP3AJ2GfOOrB5TSsjU%G@MIC><}TRxKMXp0
zX~k{8T;O;X4x&*pVHd0)HOjxd`|Y}iZ`9Rbnaob3N`64Jilf@26i^}2(y9Yt`CX=<
zW3e(Wl9(2`6dG58Df-2ju
zkEO`tai~wRnW0{`+Cu%rIo^u(k1GU-83l8DFMPPF@{Bzk8i3%R4~-4+p-vi*_sS?%
zOxZb&0L4AjSReq+M1LwVcKw)A)BScL2i`8IPSQq;M7h9W
z&flkZtLl%npSxFh%Z>-vCD_|Fp&z@AOzQ)L4Sr4OTaSX#IqVPA)Ns}rtn#;5R=wgO
zzBfjJ_v`Rffo4`!LBpDYRl-fP^+e!_eHN|yakG<})Stok4P86hL4Hvxr<0>P)?yN;
z2E#tZC|jeW#=
z;k+WDxkW160B>AgoU$2;3ZnmtAP*?D#q&Ecb8IJjvqL3RPJU{TK*(s(anYo16czbz
z0b`NKQTOE$(qA^jPWK^aY;f_d)14+jrWZroKegN(%y-wJGFzHd$0$CxZf@~A4>cTaOlAp
zgV^_exLN)yZU1XB^rr_O>e3khmzm(IZ@BICjfysUoyPQp2wEo_eRv1=drY|I#Agnu
z>t?FM?{3p|Fc42vMn@V7y#SRguid;L`hl_J4{6Fpy4o!d*ge+)_`A3?o8lgEuY$&A
zqU$mr*`+ynFSP2?Hd^(Nd#o#Up!7$Cb5{EYv@6^6+~n_)#suc#$es_5db%x5$VcAo
zYwQ1^>Ad5q{@?#kDM=_ow#wc+TP4cQ-ek{X<{X>s%fuE+Jb-)|mT!pL1B@=2*+U&|*d_&2tGA1%l!O^Q2ysF(qk
z&V{qhDJmxC`BRF;B|og7%{C3x7E2@wIv6W|gJ%MM3*N~gbwekCa_HrSd)aJSe&biq
z?VnkQtN|f2DV~%P?$LBrXOD&W1s=t6bWp^gC!N0Y1&CTvs03&_g*&PF
zSbAx9;!9l2*`}HhhFbfaHCahf-~1%smtLV2aDz$fo^6KFlYDhBq<
zqe?Tu=>0-QLxs-A>1Ugu3uRR&lf~g2i!b)*4)~`Gb8`e!T(g}@#uHc_LzWVBi+s!Z
z7h=@JuAa{J7E+t69;sfiJrTJ4yD|6n(uj+!CU2g!AgXJ3CPZ!;dr=U@<2vRL@Knbr
zp>n^<{Ow_{se(R_Uanul3wfyY@F#NguNxDTNcokB&
zmEV`TiqOSVkjch11;Xege)9A1F$UumefTW|S+pzDzZVCc@W8V*rweW!J1^}{%D~>-
zMx@%nL!o#$pAu$2FE=N$ApD`c;Qu1)fuidR*FOM7+Oc&yp?gxK+a1mXE(i#eJ{Xb>hdwT<~+
zj`=@=;~Y;|=)UmfJ4SQky)q$u?JmstuKCHv_>JY5)tPH&c5&tbloq0OIl0QYQj1w_
z3sBbAg}Kdn>^_^%ec;s*i%9Z`h+a4lHe+7|LaNUm`_DVH2d~U0U3X0lVFAkl*4d2$
zWO0ukF#J%kLJFT2g0&!f@GFzhCZK%2kxh2yO>yg#QQ+sgl(~59=&rJcS0_$z`BFEC
zy6%$iO8M2P3vROsM+A;;&K*sFkOjbBDBl0xj5Vn||Ln`P=kD?GyQ%dlw+njgS3?MX
zu6q@Df1a8t1vg67D|kN2`2Na&dCW`MYTjR_RbupOKPZuwl%mb{qfHxBV*so$6V?=b
zjr9|ab_$;IyZb8AR0hSY4~*TLG^@8EVpPlik|qI7c^A@&sa^D5o$Z#*?`}q1hspXD
z+Jp>jm1=jyr`&6}Xw=m_MsphvB4ng{GN+~)qPF;(JUaBqx<$8xv
z^H%3&a5eW0_3@?<<$&^_&$psVfH0Dx|xfjtV8`KM#4B=BytQI~!m6
z*NK`+!BZEu5Qg19N#p4bX?SW>0U6fM+i
za2-c<-9E&+7`>Q~JYE370+)nQfo|k(FfFeB7l>k+GthaDc6<5Y_PO5n){;*br|4?)
zMZkyGuU}!Sm?vXk^!0Vrdjd8rKg3Bc)hNZUGk#^+Ue8SkQ!qDE&=J!;oFd;O$FG@c
z2x0p3;R^?juiJX3t8{pT)2yrW_PO>g%xE94)SfswrvN&_sf65tO}~u0`J68b8Rbuz
zQ`Opa+ZW6#2aS1kA&DEOxSYks2r;i+>);@$h2`yeR|8F~vkEKyT2X`9rUH{J<-iD4
zW!llp(0meBs{DThfobxQ4$P1B%m_66IA5QT)csf}Tr<%00mu@LvwcqqWvWWoX}^-x
z1+{*U9bB@!|B%W8T|yHo>0Nin^C6XfWlVK0d^7V71#f7sMgXAyk>UNN_Im9WWcZ*6
z*r(@G9V}ccG6@A7htu!LT>qAbY6b^8Q#SRhu%?=xr#mZsY0&ymR9MU?_9W!~^|V?B
zOx6n{_(bO2zR1JbBhNs(EM?K~h!$}h6iuLamF6w-anHE&c?V&E!z^3*58?|hHtJN*
z>m#QmTZX;NvbIlHx2Hd@_Y3sbFoudAy`SeOGh%yZnDs4-$p!nY_RTun6}~_7&XulQ
zvB~1|!*jKyo(_$Ner)emE@qxv%cVU9+5)pVJrP8kp~ok~L@~~-}lbCZt@M_wacbj!+&t-XGfbxKT7cAnFo0`d4MN0pMv`Z%$uCGfs&RNW&vWby#$Bv~%~-)l>D
zC|%Z{eIaj4Xby|vv>$oQn$IbFYETKp1eOt@B+IzMW5R9y<@I3W!i&RE{#djqwjFthwePw4Vjcb4yMa+aZfz>C?L3+@`gm*t3ym_
zz(p10$8f3E;-V~%6V?v<#WHO#<$I{*dVKSI&6IPpnpRo$8UDxw`)*Bdh(4f;nO8-8
z&8yOmOjagnHceBX=>1e6yM+q`o;6hTe(KGhj>(Q!`8UcO`UVtJ?x_O^{MlXD2DOTp
z@j?pIKy7}!;ZG(q*}LAF>zEDw9B4lr16I_sfIhgvJb)b*Jo6}tGVC>fDD_JU+v%hhbGDl6n!l++Ogayy2Vx(2m5n74k0pZxSiEk
zBI`3rq^{^YgFdv5=slx2XI2=Y@32r(@R}&O%rT$HI0$Ae!r#oDFWEMOO?h9o%z{2i
zJ%a^Hj3SKiq|Tn2--Vm&jpTOw^O-eslGyzlu;m!b*ij*O{d))6AS`M8R3ks44*X#m
zWv#B5b8A>4$Mq5G2qRbY$I~ZbgWaqBxeeGXTj)#Mi^tq&o&7X&xW6I7e#530Z~c~H
z)%3eF@DgaN%8-fHL>pOM|E~5~y(s`h!pTu!R6c4Z__*0Z1Fz6@CBU7jZTM=+z*Ri+
z!GahQ7Ifw5)}?OQ8Ru)JET5+KbMp{*xXTKXs>7zt=d7k+MqnAi!{G0ZZE2?YPZ7iE
z*e-Dw+muE_e@_s$YcenI^i$S&Y7fXZ;~DIkvrcb$xi~LZ36JNgzW9@VVQf>ZrtUr`
z_pd*gyA&t7C!R8ZKobAmOJZAe{y75TCsF
zu6E4V@u2g?8=8*2;FP+sek;~W>++crP1WsDkyxh#CunMGPxILQZSFe0!yaE3vuRFP
z0Q(ISi@sb3SnLc=K2mF*2Qp9ENS^gcvR1-;fw6CQkn@e<@G{Q$Zqo{LOT6E9+8@;VmcSqNMoI)DpfBH
zy^!Rz$98mj0HLQdr!heM>q=v*y|2HaPL2vJHO0(K_wV#xep|C$fYa&~9(8hwn?2~o
z%8%Cut+&!<;&?
zRtr607yqq9`eUuap}1;^6%>BOkZdn|xP|oT4mWo1YUOjINqQk&SiHQeJXT%T$Pr
z+pTVxIzs52Et1hR^AqzyUu*I-WzI`ycpgEZRT?S-{!2O3bebkr{rsHm6G!=|D|IAT
zyWTgiyn^{yC@uW~zeG;_q4@I`8k!4F@AddNRJJ>{^zEy@xI)?+OMt@djAz-C?rw}V
zR+n?R$d83ZJG5-jkUyFduv$Po$=`*$5{jrurKk_x_*x%+tsbB=WJ%1ewBgm%&0X);
zNkzE-YC5mTviFOEWEqZ%nl#mASyyS+n500`HYL`%{n^MigTc!zcNe)0}RG+%H#OEcy)1Ft-HVq
z+H|{>`;+Ysg`LuN6S+DEFtHOj6Ad(59{&sgF-80(p
z7p@Inu8@*aIvSUi8*DIc`MS9gL!KSQ_AY4>;V~segC^aRkwMvOeR3`p5E-zc-R9M`
zoSsqGZRPx(gs^2wcim8-{`|{w$;wbQGZKkx!0xXRbyR7gW52M0c5I`H*TLEI*3r|#_37aK
z^r?7=eyYp6?_Co2hcykSPBf<*s^^K_LexhX6;OUx7BWs4{^(*syqZ;Mz7-UxoC4rl
zj?{27=BnRgW2TDGtbYVcvrZ4}pV38fTTn%;lf1eq-8C(kY52)cn#~{mNTa)t_K~N+
z{%z2`FwB6~svuRly$wr;-&MZoV!*v&qg%v&)gqa{j>+^44q*POZUB_OJ
z`K_eZKpaQe^$YN}@XdOEOYOlw0#EyRh}Tfnb&z{rpyGAe^kH2HF92TfyRIm10XZfe
zn}$%ulQplR(<5f_adTX6F(ubrA?x;m0lXzQx}V^et_px~uDAG=pEbwMu}uzQn)o*Q
z&X-vxS`Kpg85U{F1@$ACe|n`ImYEb-wVHdY3&x#w+4hkD!X{I{F;iE80S*H-WNXQF
z_yXcZ)M|9}ksI;)%Ok(aLCEGDg>4Xzl;0zi`yo@b*Fezj{&K|$&o+kOGwg7^1U1Dc
zLh>a?5xduEi1K2_C@l7_C~sUvfORo^SfV6#yB8u&QG>@@GZ
z4D(zAC;(VYON`hPg}6G5kMxobGwwZ&vS_v5K_@=tXB}pbuERvx4Fl}sDEfnX59g*5
zeQ}ak6;9`dO!yF`@7J>%5UF*?5YD#@ktrD*c}AMCaW`I0n6?CH5RP%%QlFah#kpH6
z{zsd6)u}#OycbWoDQf$c6z;#wkGOwrJPSXz7%d24O%T>DSSM?>R%!`wF!Fpal=LR>
zv8jjW7%PJ>(k+YsOp8s(s9Z0;&_VN3L#79yKXjX%X^t)=)`^WYG9GjF*-3?0km!F~
zmZpr_d+H8w&8M;hj&)oRxsSKcK8tg@bPtHG<~62l_d8zmQ+MMJ)%
z+14S$H+%l-s3kG^G7C^Rc|XX!qnUwPuF|Q~jDQ^K6`#e+sVF}_TNhYi_`}IPWYN3T
zpT07~(@b4=vU%=v+JIVbI>|#Tp&A4`5Uxvry(=i((*pdNc{t7liMPH08-i;~(m_iu
zQY~9$FQTXs`f)VCb3AkZ*%BJ3D9(KOj2d3kovTsCFMu!E55VlEPp&P|{;dE|ry0E)
zCpW*0X=V+6^cZufHG9@slF{;bA+3PLf8Tf;&d1wV;&#zUOHbv3kd}(Ueo>1&d{V=3
z@`>n)K1vJN)pZ5yH&_Emv_E}!(0PNgZD*Y`4NI6888nB+nF8}|2b$*XK@dyhj5m)Z
zV?c!@f44C|J&ZrwKPuW<<_y4Mkh`YWrg0(3h!^-wsVj+~6j&x~bkK2qes8MDZEAPj
zIA4@DGSW}GXS!#)f4mycE~QgFnZUFSP)K_9QzNP*F(5p=-c+GUMtb>L09@>DK|a%&
z7vKfQtL2~+oa8D^_TAZ!a4hh9Tf|ydFWTzxPdE+uKqa31$M(HQ#j|Hm;%qxW!~8um
zK4+Fk{i?iSf$qT+6LsD#VD8hz7wM^OmqENZY?ZjL7OHZ_Ppz%wxB!T~hPF&0;qcB;K7{lVAhtSv+Vw(y?KK(>
z>jQ{Ax1m>2I}%HuTI-%Fq@%zc2=G2W&l*i(_U4U@^P}ux~s2=7?5IdLHU(vveu^
zlYK9=X802%4~a4_$HqPD`3%Qhb@=SRVH)&4!`x`twSlTy?a^O84HNF#)k?NZhfnZS
zc>SJ}!5(fFK@Kzr@Ri0ErYX8Z+jb}HhBiBfn!-9h&#DYktDxPM{utULtG3;l~-7f0PAi%A{lVbgs27(_-*2QjIm2}A?~WyQWWcg%Tq*em*X
zrx$T>x*+zv{HLM&2&s_ZGlqxjhgL8@j~OPFN@^3&6eo$=mBr6x{d3JTc$X~*s{dBT
z-L&Ie7-bYkTDt0&(ghonN2xw~+jZ7HjEoCxE2TSJoN}15X@%kDtrXEDYw<3V$Ug<+;uYGI$F`mOX7;OO5zA<(S#`j
zrwV?0|I%!KdB>XL(Dm&HDN|$whf^T?sj^Tpd~sPoo9Fy=Pxjo2rmELs!ao8j$4l|v
zYpoEP!bSYfAE4l{s8E&)4BVGdN7pW=i~$|-Yhx$g3OArQIb8CNNi#WBe84pSzU<$|_$Iz#
zicH;Ltp7o2`zAXJIb(4C^r5F%Q{ijeHoVr`)ci43gdY
zeQalD2Y(L5%1ngQ^KvUH_;+PIx2}O}L$;ADtdoI^op0!)!xQ$}T~8-f3z}6o!6AHr
z!dB81sRNXeeX)s;z(JN&*q|Vh_gdrnTUnPP4}5z?j;o-5sTs3>G?G;Moa#E3bNXgP
zP(J}8c>5rL0mZLHEV$n$d+`J=Y3f8H3M9@b;jV3?;`yg$7eIn2YNp6c}tKhn{2
z(hQ5wgzE?wG?9pNlCYB)zhGN_d`)hO=Q$72_D11jj_E{
zYpY_spUE$YIIdo+!~2qfvS-wsPsFP8)>1kf8dc9W&UZ4mM#q-Xv~F_Q4t^Xy59HNS
ztb_L-+);hvq*K;PpIGAmzN|A(m0T!NZk?^8wQ3sr4LXrma+--2QyiAwG;WZveIz%K
zfNHwhL%9gfV(O65LJ{$AF<1Of5*gdBH=}D0^^cvCD@;=gh_C6L_bjg0*Zhp#t%E*;
zr6Qs{(Gn7gGNNk!ypPV=<}Mi@Fm4)lpXL4WLvzvsXZ#^VFcKS%r=MxcMP*D0H#9ub
z1kv_o!;?g%2IiKm8wIyp
z=X9i_&BYBSu7>gcrdFs$&m^2TVSGL$V<>_C<7RZ^WEKC6K_tC*c_o*wZkiJ`*GxC>
z1&h7;lEV^)FecnbhOgcV{}nDUJTO*>q%mI#vq~9mqGe*nG(CDU?o=4}Kw@ft52pGD
zeIX$I9sm@n9{oQ+QR61!KSEInv*X{69I$b-kH@gP%*S6YcIP~)*a+tru>-RpDIPs8
zZpcUd)Jg9MC(m@T1pQ)2=U-8SGH2R`g*T*)3~v3A!LAJD%XPbUQ~kla!m|ihsDf4S
z5y);qNH^NQ+
z$0-7qBL3qPl{sz;9GYyt12=_K%V_TQ7}2Ws1otgjPkcPg0p+kG$L9FcG9Taq~aJFuS*BP
zXkck;!%5TBkdLkC!2NNfJ|RlNU2QsQfzS*pB%}Fl-=iN7ZYkXz!AHJxLiP4op}T__Ox)2S$i)
zWZ_(&WC8O;XkO=@kg}v-Am6cs&0>ppz*9PaG;6o0!wjjR99Do|Xl$oy3*-=HnDr5l
zWBZM)knSB*P3=98z3E$NxWDhd4UP+BZw=@RZj74)V4Y_mfnrAwJ>mK?o$O|P7H0+0
z=9QeL^|rM+qMY?i|MLN3;c;+Y7URv`@vy+XXOtq;yP5?pfw*6pi5T0kdvmWJ#P^f#
zNdB#i5{NjM|Em0vlct1@x3u8%vHF<9){5h3q%XI2wo@t8W%oBE_i-TEB})~e)(W?~
z@XBqx&10m2g4nH{l7C=flkW`BQKZpg4&(a8_%tI2svB2+GRX@LHFR>~eT~@=-yT=HgYb8}}8>)T36m%9+U@
zrt2Aq8Zl$1RIF2~go$0@C%aPh7}kyU?0K20n)lX6zJLgYBWuLWLb7NLiQ2cyf!Soa
z-Q9onrt9I#e7mrKg{bv>A_HsF<*@ZQ`SL{Vr2fX1)atl-j24lxsSgAN}zv->CS56bQ}VaSnXBn0WE5Soi2<
zM0u?t&gFgb$}LE+M0u!l^Sh;9qRpOG-a_iUjet&J{a&JRZK7ORm$0}(WAXI~30A5@
z#Du!)V4a+fS*U8{tzi8Lv*8$$g(5=vHZFhalp>;j9Bgkbg-{$;T1X=6hd(xa-C(>f
zy;ihx%elcIjATaT&4jVE)|(iT%Ej*%p`uiE)Sd!-#Qpj!MLYh_8v`s)GT4+d4l6nu
zi%7tnqL=S_FmoTA1L{zIa-orPE6dI~!{HXuud;QpwUxD0mg0Z;z*!bgXP`TPo+36UPWfPC==xJ`&lpx-ZsfFKu
zDSgna68B_HHIq1c_nk`CW!OL3(t|>P`6`8#v&6pWm24WTfe>qlLjA|+=3YT8j{+?uR=zERtJrZ#pfQ0|%i$eiVK7Dzp
zi~ct7eC23gAbKAvvwld&vR)R`t-~IJTLY+0J&3C`RQyb#^|p>I!Ri2N4@k
zVeurI8CrSZLf?1mmY;9n_fReAUx5A3&&3RILzcIN=3dO=reAWF_;OX>0y`M*X??4C
zso@87CD800vScH7R_u%jM
zan(1RrN1sqvdMyV9NC|Rr=1#d|4wF|u}&tG0~`FwZpb?lJ3CczD}?+bm|fHV=JscN
z^x}A!;vTtk~+|-^#k)A1PRY}5i
zP5Y3SlW}T-9(c+0J9syo$fhQjcgwmn7zFZ#4up=|&EcnLvNO{M8CpIS?|z1}m#g$R
zd#+@M+_f*^nl0ne?OZFGf-{JNC`y9jUmCp-Y(lW_#>wsP)-lTD3^=w2F*69}n+O;M
z5iMW!vlgx}c#{xJJ-AEfl(=gLD_e9bvWuvT$^Twx;(ZD&^&v9W4_Fu`86+JjFIXWeYYZz0ut
z!>?CH0?v|Y!XLnd_@cX0gwo37H_1{aGn&sTf%E4|VUO+G-v#%%tetaLB2VUDJdevK
zuAgv%d~3FceCW5ip&y%@d_VI;r+Q4-peoL`DJpEagX{rrRJUzYOqlT*ww2t*OolFt
zwZ?kQ~}sQ==P(dqOmGc}UBaR9!r
zA06FpSb1Pt?}115pkdXvF468;AA40kULR`+T?$F%6wJK|KV!*IunNQ|<+2f1|K1v=
zh%9^Ub{tO`Oz->AjQXTG$@Qm)QF|AA&+mrg1a%*2Vcj%S&m}nY8@8mC7KaQ#FK92$
zZhBsGfyr1-gir3E;5P3)DLz;U>cggn`MuU6;_zpncNeYz<@o(o|5{Ho?WU(
z^^37QR1z@nA!~AIhc5MYLRaHJ*X6!n=i)Q2;v3(dQZ&j%b)UXPZ7?`gO-FdTAI%Sa
zMvDuJstjMMW^0#dp!XieHDkKg@+TyYxe84mK@qRwio*uEuY}W`%9UQfz{VnGy=GM(
zwda1H)*Z*^4=F@P!dWhX?IF@1zrr5`B+<8J+Tmn<>c>Y~p`iZx5WAPqd#UP3;V1OA
zzfYnX_q>_l#iWx%%bouSH2vRVnFW^+Yhy^JZ$S-S?VNs@r{3YdZI^W&)_R3XK4q^Y
zXUf}|RJ5B+deo9SKOp#V$Bmj}`+DrXZL{W~NLmYwGl%$aX#slTAA$AAXJ2|KD5iAD
z#Iw;UE6PHSuRi3vh!8U(bG4zhHYogRT}7i^pDmN!KiNddv6Fah2;m2lE5zrUTe=9J
z9C~F~y;7zOfL845$NtTGQld&P^Wq5yFb9AIdm5hD-W+@_5`Oa^;%lp*GHqWrLZR68%>@TfUJ
z_$@zjI+ByoM>M{o9Z4y-drhD_5Ao%F2b?(n3IJIMANY;QDM%1p<
zJX3%nUtn%j!o3k!z*~AK<}o8YKctPwUG1<>zm5qeTG}Y74svn0kYw-Q_jgH4qIi1>U0!hUz!@J3jT$>f*F6iU9!OBL{O*1wK{7vO
zn^(-f`WC7#VV=`2;gqowyiV-IDX>><8TWnFeWxw?aB(eoaVYrLmp^Icm){hVmBeyhsFlWWUoxfi!4J;e3whFlGO>hGZti17gyOUAfbF(
z&pNx)0LRc~OUNZvL!(I<{nx0nNMt9OB%j@svubXO7jrj$aoW8P2X)st0l-d8k08%kajW#Wm-98f
zeI^FFbLz$!Z%@fBg%%j13(KRJUM$+>@4Az)G9y^aat-^(SiZWqiu$>$Lhm-_s2+*r+x*$Npe|4rnL8ogy=+0HJ)PvNFThC5c1|%}b$V
zmF>8-dzC5T>rbZbEFUf&r}$JDL}X+4EBFn~(4JILutVe@a6ckIt-_ns`FXo1cFkMx$b>wGgTDo5BC^%7DY`%W
zmgS9oY<=88g5kpgoR-%+f>h?9P{V?>D%0x(L0mqn{-KA*KLT7ZSs)j)OGJ1a^qE=r
zkDlKCzFr9?#hxCi04mDsHAin6X1(SUt1s+?z(g6E#sZ;d@*u8IdUwV0pqGiL70{leVn
z#AU|%UaFUkhcL^#*({B^&wpb(6)rg)G8GQ1ZeP!`?Z^n>O>HIW+=OL#o6^hT`gDd}
z4DyWnrzs;%ggoQd9i(1pi!IZVa^erPjs-o7p4W+qSD|*Vl%IFF8G}gck#no~xNaMX
znJPOYzV?{J2=LzBLoq-XOU{8Qtsx$-3r2ya*nH>f&uO?Bh?}3
zI;=@Q(EzHVoB;hHGWBSf^>$Clb5!sobwz0?OGd`{-96>GQ<^LPAau_A!7G9a)l&Le
zTuuI#PP$3%UOjk39jiLkm(
zQQ;r%G+wJy)y}xCp-F{D&fw+*q%D&VnK3S>Dmh?ID1!n9Y!%fF_KEpn&|@QP^ckZ4
zKJ6-7zd|q9B%ebZQ8BrfuEM7i7HKU3EDKS4+e%S~P_-Hh(_3Bhx%Ub11G+@UJKa39jCiMz!?}
zM|oHa5-H_bn+RH&ODxz(gn!+@LKf~tk5P!zcPuz)?2_~>eC(Lg!v2-j?7|(QOXp6f
zP$!o4np3}zTsd^Z=d1gxz0I_OG)oOF#CUCN7(@J|llO#Xc9)&zppIH3IuY>V7s?MU
zDc$?7aBUPjw9QEs0>djHu3ftIRd}7x5)OS}WKgoy{9&KP@lnIxa~$a*vAyyeWBh3d
z5$N53HtscAC&3DC;H};Up*|z#m+a5gHyS!ti>$HVYb$N7Z3kQot}>5Npthd@t@-<&o$En<&1msRP-xU)46-}x%#!;^nG&a&9xxU0?6B8
z3;ZqEzV=LiR73TZ4mJk2IN01(aOxNIT;OEUdCJ?q2lFCq!Y63YJeiKpUe=G{!70a~
zl2WTL=BrV;VdNON1l!7yx~O`(l9!1O4kn)StVhdo&>Q)#I8GPpc!t$+?HLqR>S4V+
zBFuRj<89N}((m=FsCr~Cb{TcHf0=9h)86dEI&K*W2k&$sN0VTHWkHNL*O5fNl-H(Efzf=vbsY6o0
z?mcsB!82D1H{=CKg(iy${LQSu{v@rPCuH`(LQVOkBgFqVUJVc7Gc}k<3~($sQe9re
zXrfvm(&V@J51q+=W!b-OxO+8qi7f$%
zfqYV$g50oUB|--W2VeG{TA4`!@LjJRW_o(2DRQgj<%N1cE5;}V9u~k#eZ>C)}x|-
z1X>-X)#@-s`_8M7&bN2d_6I^*ko%UkM-Iotc;hE?-Rqiy^NGAv?ws@?EthvteSe$w
z#OoNG2aGk)37t&QIFKF
ztSFH|lujAAWJXgDuZ4&q9C39Lcr}N56piuMx3@Jj`1LDG=v1k#i@Cl}+va$<`wIsT
z`y<3@5QOWyc=u)rj3?yVq7m?IBOAQ%M%10vDi|d<8=(RHbMGrG>#jz_1ETIe^w#rC
z*ZyBMe?O)NIEDMkLVeQPlIqAxt=nnF;SrA_o{%`nEnLPQoBz~vpuOf
z7ja02s>nfmVfuS%#}2?jhwu0b_KrN&Y4dr(qbv4NKSB
zF6w4Kn1k+K7C$G^xv{%O-Mcz(IMeFn@jn760B?c-0WPf!-*BbXQw4j0T=~PeG+)bi
zZFD%uT+NX*Lx;;T{cHnzm6{>?vv
zLQJU)3fdN%W%oett*JsU{BxjF_gT8>L0XPy_%Y~@yi3BoSpi4i7;s6KtZ#T{O%zaCe6MH?E9NhP`Xj
z*nmh1R9`kMM~Kjn{q!?}>~yeSOdcj4hbutTLVma2&Ty{7~x{;kEs9gnlA>
zRDCFwzyPZl_QQ4|xjp_roXZ@eKHOx++`SIKxfcI}bFKal&UHv$^H!(2gj=WAnrYeb
zEuw^5HRx>Z>{}l8qNezR8KTjAk$g&!*DzzoRt%E~kK7W9hYEw@m~qJK0lUwi&u+sf
z0X&z#|I2)DGq!>C`#(9Ae>&u(PmZdUeSMnPSggL{m5>>SK
zuM~CSm+AF#OYR4)YI;`~82Ipr!Ci|A83yQBV9N3xuzpZ?vJOW
zvC}%e)`r9vU5H)(a-7mA>;8;Xq*9qo^$gT2J}(x~N!Y7xr}uO|Oi42ZOrKuJEyGzX
z+U*(7u*c5;C*wL(K(7IzbNV*sFBk19bM5nm8T!O}{3B8T(HCVYw7fdt`r=eB4^K=O
zA1`uU?1|hkqgFFXaI4tGj-p%CGzCPQcjRHax=|cb_HM;$4BF_lbDdrpi|I8Cw#s&G
zOQjX1s)sdt?VVUO(ynp$D_^_Z?^k!Cx;*%PhOx&y1Dcmwq%mb(${#mXYK2o=R&w4Z
zB@F7N#wAQ$$PUd7_REZd
z9+TVcCVr0|4nL4$5&JdEV85@+-K+hv_4g*n>OTT+E^amQe+2sddLJ7jCDuh(&uf33
z=5SW-;wZfNI?po9Kc`hJx9l8zjp_I9?m98Y2J$w>b1g5F8J~3Q)eH9RLZ7l=@9hd)
z8jaU|4YHMMIj!4S&21+mF`-FcinadLNyD%S`W!Srf6qZ3q5{UK|AKY87Pl5_m?#Gh%&VB5^03CAbi`6+@H%
zQvOq9r}m>gtT;Ia_>v0xbeateyWdt0>CIYdis)!39wkg^A#_qo(f-&eZMVy%ggpG8
zg&Zbb(c!|x3c5ahZ_lOp!WUY>s%h(V>|Cu=+BxwlDT~k)h*~wtEKHym|I*GPR`5Ml
zq|?!3Gu6S$-^sk3s%L$?Yii$ADtjFNlXkq1l(#E1MsXH!rI`e1j;JeW5%`$3#S|KV+9zl^te-KJXKZa<={xQ(uSvI?`_Kjp9
zyNx2tRLR25B{c7(@A(#xwY?(9`(5&oMCR|zx6tL62KNQHs&jWyY8z&spl>byZtuocke)Oj!W$Hf%kpQ
zQo82z;yPr~@c0Qe=&wNiNqu(B&~G;ST9U_f+jFrAMBiUmkx(C2z7buZ-eLpQMS4#w
zO6M)WQ2Ph~xd;f&J~ggUeNWB=6x;t!7
zIV0NKwo(X1-bY!t9rwMt=j|bax(JqJr2N
z#cx31Swytc`2I39yW)GOdQ}FGfb2^RZNu`=?B7L%G9>xaU&t#TdKPAPs2pBiUY-Ma
zCTHTvrTpZdR&jjlfdQr|jgJ
zB=SB#stO4TjBoT<*^zzpil}tylapdeV@On3@xO~m`diCBnTh@mBY9S7
z#8n;gZ~BPwY_59l^nxXUk226DHj*`!E&JW)Ikto_h+&b&-GGC1ZxaT9n5MX0kfL$d
z##*_XbC4s2+~1Jw)AJvqYsn2nd0kHp9)D4L>+Bl!Q*IU3%F$mPs=m2vYEWaNV#IFZ
z{rGYAgY_`~+UIVzOgoh4z9Ah;pjFq_C?CQ0>%yhIGX!?PZT_pEWqa~o{k`1JY$7Dg
z`uCaen=e{lODv|>xg4l?r^1rYU`08As+i#ngx348v+8W=l@0Ew@l!P^*G-uHg9f+7
z>y>f!SR6Ig@s`cm|L|R|-Oa$sa3O3nUV(pn^5g{FRk0QOLCN~zp2b*L&udTJvY`Ij
zx_+$xIj{r47}d;QIH22WJeMfam}=M*v{pNF(zH@HnEPzLU37o2iyHUUj5by-hkvjJ
zMBd>?wptAVVI-l`-+%UI3G711!b_G`
zHt*|ESxlp25x;?%&V@i=5*YcrzJ*=^aGyP{-;FPww(h`})IE1}l{2FbCi=os1#7gRQ?g<*U>5Y
z^&~avxL*d1gh-2IPn`Lx`Yk$LFy$dD#VnGjVqCIm;U*u^)Cx
zt*0ofs2~QnqiPA$7-WEMO_pZvx=zhozd;Kg-Z#bfIO*Jaa;=CK^7oEYLljY<_#duo
zI~$_uW(ENVFaz5G9C;FhwqN{n
z&z=s#3V5qhKzHdA1-Yv;fQ2=G5v;Q|cx{!fgr<6B(y25_@J>RX-TBv*1ATV2N92@o
zO@31YNWd$smfGxiKYjM}UA
zPK2T;lGq7?_b;FC@BbV*jy!+ddG70Vz0R|{sB=Ts>Z0_byCXfcQDY5xde!w21q%C#
z8*JXc?cWB1w3zhuoBc=+x!t>1CFY>q>T<00RwS59wo0>blzUDzHym@^I6^D-F^u=u
zpGsxnqm-TZo!}X7gVbG9O%ym3jQ&)NrGX7LG7H8cgRA=I^Gv=$J6b!=wvhTeJ@h($
ze3}DPZ4DmSveJd6kU_}PR{1EjKcv<7k3|z5snUl<^*`BMJ&x^t)~ndYCw;ar-X|-V
z*4ZZ}7@Pt6n0coV1q@Vh$zDGWQmKs&nb_(NVW_>`9%My-pmBymD!L%FkP#5MZD+ow
zNX-fcfB5p+pEFgeZLaDro$8Bti+@E7_~ZaT%colTHC)ZJnC25Jz!1@)L)(^aqz%m9
z=yvFeA`~t_F3f}&|2|rP*;y=k)!q|)Zzj?)&)~bZ$lZRJaBeL*EXSZ(GvdPn8)^t7
zo;K!$xP(Jh!zDU&)%~O1%_d!rXwQv!rZ}_}uTNh+!iQqOt_W46E3?v9{km-Sl#M!)
zItKF=(e}iv9N^vC-Ql|KN)&;rGwlPIkG|%ZT^7}1l&66qTL^0UBrEpS=ALhY
zd@~1lPt4%y)&?6nI{8m+bcvFsC-PuRvG{uSC6Pox5XLgQ-lwLD<(q4?STYO}M?Eq0
zHRqq+89hr2do&>Lf)N8YFFlONk`f(dPkZ0v^LXY{M_b@OuC(g!
ze`HT#z_F4_NQFp)6));zX9PI9z>IZUPF|eg;;FC?IaxM-JH{Eu7{$(e2MpA
zUoS9M^yNm|G~dO4>TUlQExuFkI(!GBz{VWYW3Yd4%6-361ZXwU?+bpfT6bcnrXBa9
zti<7IlE$zg)dD=Sk)LkK*Vuz)R^_?Gvdy``+g5T%?fP*>d)^hU=u)K6ZlHK>WgQAB9YL35Co2@J==E?dYA=df%nFFF)UcYb!sF
zQSElZZyR!`}h@q|FO<(kMLM&SpAPoB65GA`wgk5q=b>B`YmxmTCnc0Aq2Ow
z9)JAfZ<7a9C67BWo^XZP6;;x0HO2X(J-s{r7Nh2|&R0${nArMi*r3g)<))Fmn@!*q
z6$(p{-tpO80~aV`o8lK$of1BEkv5&Uj*?*^f}=$}zJpaPUMez7l06l?*_hAAce=8qU)FKi2N$9d=I6X%_zd9G6d8mBfT=^F1?g*;J%b}Uhw
zjYFUZcU5RI{^dTrZ`PRA7TdSMlw8gUX2D|K8DG;sZ3(8rIQ`olsV6OMxb!WKz3mrU
z4!G8~Bq;68o8DOx7RbI$8V7NZ6LERs7;19L;fbn#J4q{cyw~2o|K^wpNgY*kMt`bb
z+?t;y+2z?eiEb{p@y?hybhF+4_iu6)D)T+4bT;ip)53bZdW!RyY(-Ea7>%0{cyK3;
zReYxsAo87?<{2ZY{Y7W;C^$;w+5?+#Eaue7D(4tF-=Qc<+8z+~5KRXwUAv2)bqfD&
zVWqU~4?<;!Ctvm0LP|xW^0*!p*4fVN$#488NYZD7vTCsliKNl6;KG|bXG7clXd6>~YA3G{ZhN7zq
z_}17tvq-?b*g!`4bjJja4mm3ulV!d-+u*&G?)~MLLBZ2K6Zs*2KlO<{y}t|-Fmf8F
zG6FMygmIM<+F5Vk%~LhS0g#8uDy`NnZFA#u)7WjfBD=7;xKBF#>9zkOJ5X8*lhpit
z4X~3{Zg+2Qs4|Kp4-+;E@W^TcS`BP+mh!
zeeZ$toHu0_AjhJ7HKkHL$BLJ=HpSh_Bj5RRuP(5ARWjcwy0NR?1eW
zVeO7pG`_dNb1m3gsAQ$X(8+~n+L9M@A(gDy(
z-5cXo6^+|4jDr`@x23NCM<%;!(sd@dP`Ihs4EbFE;1imL
zkdc*C^5t3(W=FrA>r~&)!EHm{)XA#V
zuK@{O?!Bg)*%q_1^L;8*)ZmvF8th2(S{Iv#h6Yi+>oyR+A1))JXqdsfvy@~F^&cyV
zUv?RKqfZ5l`}6v^`SsM-vI8k=jJ1yAJ9(ik9knZn*Z`X<
zBKG0Y1)7__jdi9IPJU%lM<1R6KfvecjsCJ>u^eJn!!C8`aEbD~W`6wa?R80Or!bjI
z*phCaL6JOiWlkPz#~JL`nHV;A=GM181_BF@z4EC}YYP9($}$iiNS!K~9D~E{sLNwH
z5f7BjrqGsSFjryocL$i)a1GPN)`hK^*J*o}`A1pX_#gJjggjHa%}n1(JQEazim~XX{dNZqzt036mC6B5O+B@ax&aecda05E;>LYr5)*Nw>h=Ido
zU+%2#Jv1*n8=u0(!^y9?>n19UI`WqiEs=PE;H=(lfcyBQ;=9MYFK8o$?-b*hCcU1)
z&Y_8HD&8Y>>Kevf$y=SR;-SxF)rsOjN=Kc7MX>V{s~C)}LGqseVG2ZPR}Xp7ASEQ-13G%np_?Lo~g}Yv{}mX@m$}=yYhOLmRS3X~4Dd
z%LsaqB+-0j2`AC>4iBH&GlFSe@%zs-x@w0yIgR%ef$b!NoGyvbLv{FJs^Cr1RCw
zV=qAA8J0wn=LGj7I3RFG^mn|?qR=gvaKox|N0ozAT%~NUO~oJ1gaN(LWAjAXhYDBs
zYa|YtYjmJS!hLHZ41utn)-`U{-Q=V%*zC;H$NRg`=CVa06j2hUkPYFu&p1I*-?1g{&XeP2zP;$dUMK%gdy~TB7*mqeEUfai0m?~Kv
zAldY{>-6~jhUQNUQ~naPj_DM__In4Ph@5aut!EZC5*a48TbhOS0;(A|UKzW>_5e-h
zmO{S7OoQ?}6y0MZQeb7az_TW#l?#5=!=e+k9Vl$DTwgUlUTe3v=J{^r!I*tW!fb1B
zc0cl?GTO}Y{&Kxpcx;CVu5j6;Youy>zX)@-XNRqSJX(0rWtS)Js%+LIZa9A_xG{cl
z(6m2vJ&xyG9H+0(8c23U*<9)$O+MDwLu!SNBG>I>}aAKzJu
z7QY`D8WcImg-gB^c@^Jp;sq$mjs}zeRfxJRIr(il1!fo&AOt?+E!7raT=M{@KqFMb0%@Ukx(9w?nVlCzS|He=Fm4
zRZiDBci*p=7|nsY6rDQU>{pNz4i^7NjBAwh&0L4Z*sh)|G(~@9tW(H4WPGBKLzJaqj=568^FunK{?`6S
z))0D$V(`L>gGD6x{ab`DC5VYB`L6X=b%?$|Xz1VfNVLp)9-*A?T1jYNf`d?s*oa&q
z9{UN2bMMx&R8!ubaTlKzr{Ug5>Lm4Oe-{eVS`l0M)%*rDtk_!m$%+LF!QNVWZxitT
z2<{@?@sL)ndnAELxMRRpu%yhUAh6MFWwdOA=@k&1`n<_>gSfc
z#>_;XY+-3j5pBb@y>vm>r2HylWg5!>_OQt_fd~F3$t!?)hs|bDLFa#Pa5mPNR)mNk
zf@R*=9yXe$cB3>_Q+(lkk7ylSD>ebcPubL+$;p3&H^$X`@|WZITVhzER=s3<=pooW
zpF)R~@%D*eeeCsZYg?D}qGt^XSi&>gzWS7FQgJ@!ix`QPU?{180xodSN{?}mvEn3+
zy6YH)>{_e2g*i_fMF3oSS?d@uBm1TvGpejW)tuW=CzP5sGa7hhnRJ+z0gMD2Zkg!d
zV=ZO~Oi$4BTub(BgDoF9gDl=NiIBMLC6dXmR4Z}d;)OM{={OGzyo9#D_-apNK=+sR
zc67xw%p&=6$LvEb-IdVU%M0vh#v5sG$jHcUzPa(4>>=3=Nc5rJM&jYai^$pZWPBUJZ*!+1ui<IMBr){ceGCqFJI
zG_ipuu!%mnd!l9MUt5R!ZTpJZ3}1-NwgDM2s?e{DI*GLA-J;AigsQvDU0`lbncDWN48goOg-vCE?H
z%ztDa7HO(h542#c!w{u5NjsF#7!H|+o@;(y)!G&v<+34YDyV!No|fTIr@{H>MS$=w
z-VT32q{CljZbF0?S6vZzU=TgsYUAEpU&jwNm4i@ZXk%{I
zFZb(PB7=WhOXLugrd1X5{;oBt92?9ssZXCh&o-4Fwj6Ab6-aD-0{?PpK@gC5^oZQ7
z@tI;+bi%h_Rx6ns>2;Gewhy-BTK9vSne7pR&qBQ%Lqeve#&)e!1qyeIOR!Cxj66#R
zz)Jo@msRwqb30Ux71B}Be#cst)bSK;=6p{hWdK#J;_=Who-LQ+c{h$a`j#@SzA~EI
zp)LIAv^`FAYtPsNSCU}zP{d;eOd8zeeBm-{z|Ev(0`va;0L^N+%eo|A$q45J5Ayz!
zzZ_`nTko2h4jCh{=qu05xMsFOXwpVN-t}{wQK+186WpZ5h_OI3xUlrpjV5DE9EXEG
zljD{eJwDHV1f}#6vLhHa;hR@h%!b~$(jtJ{#~+Rk8)U1D;UJcXf}a-Rc#EFi?+nh-
z?Tode;pW@oVZ9FGhHSSc5N1E%7bCSMvfk}YR#^}Qtpuzd?4!UD>sZ60J%SZhR_9G@
zdNkYQ-yW=5VVD$8n62nI$t_p7n|@uS04qRnggZ`L2_Hd16qrxx43L2~h5N;C`R7fE
zbm0X~`G_07?hDnA|9QN|^~uq=^rjc25xXofh`RPcPq*N$WZe1|QQ9gabwcW!mI$%>q3|%Sn*w<%BQ_(f6-^5^0)4Q2bImfEr
z)N3U+l&AVXvMN@jCoK%W_yul`CR9sK`Qlk0Kb~)5ut`I?^lF;XVI&IZw%A7n!H~(X
zR9{7{DQ*zOcri4M%SE^AGOEN6lQfK^N1k-a>Xz`R_;mpF+C!_(ulviFN=Md2{k{|K
zODh}Vcv@}ha67!*>fZ`hR1FaZnrM6YK{?x|khMesB~eM}{{P
zX)Fj*5fn9_T}*gL-Z`_++wef$NGsM5yumr^boB*qlVww)MVU6;V>^xK(@Fh+>ol9x
zTh8(9SF@;jByTj*sL+exN_&y_B7lnptgDOuwx<_YJ)#r-p89N_>c_~i^kZUezGPzQ&
zsapgn=2+v@c%(k=86aN5tA|gk%L>HGS0h6Y-ORg)O8&-sMP(!@5jfoi7Q>qdR_erv68EbgGs2c3TB)f0cGK05^yx1y5o+rG|jn
z!zam>BmA^5aMqz3QwFoD!bq&Qvq}9A$sk|;P#-4IN5moj^hX3!`*f{b@zDX<-_1%t
z`jeD?4cq$e%!NU9c$E${Efl@iH@u|e1|P^hwo%V@XL;wM?5!J@imRm~Ie#QddacOQ&)8>zoI$xyAuPSPxoeKuGQb2
zQni?6bA|UYz%|uFt>x%~P
zBuPKt{FAGJ)Fh%noB1ixCePdwY&g-3x?T1LASdVV7W3iu&o=aQ&K_HxcjxU_b@D&?nmaEVywUrGQ8cygFr7fMOrx}6e&sjZ
z4gtUq8Q47!OWB~E!rleVxDNDqxD!iK{vh$Sneo-
z@!sb7EiTsPU&Qg#{+US=*`6IJNyyvz@l*ze=4&WDSG;=EPkvK9o`b2N6p}WE0`;$b!Gr&fB9g-)SxTOqO$h(rke*=m#1oh)UliG6gLu?y}j2YfA99S
zo2~jtH&?`R%JVHzo!k3IqNm!aS%eRtuiYpdYhLbB>yvS{awz*qT?YrJyuPvae#-LO
ztx>Ik(|eSONjhXXk0o$FC`8k1^Dt9I&uRKO47E}pN$}O3ds7_L7<1BnmfkVt`RsS9
z{W+cxUtUpGLwZeMFYxp2xepv~{GESN$jWy~JqI4%P^5T5lX=>k{?3>6O%^Fvrr>x32#R+u
zC6j<7orSg89xDX?h4I{dblQJsi=ynN_YxnT6k47aw|9#vfBN&qnmcqv{THV)V*&$I
z=l0c)UDhvLrr|GcNS{rS0?%ose_#F>dT&H-WW`Q$a@?A6Y$*;Q#e-2C7cc%&yEdwk
zVFutH+SNA!21h?hUBCQH(x!(SrjUZ~`NYI=T0f}*+1XtAcusO6>~gWulmn
zSF%SlQL(DdP~wWIznXlceHsa)?{ZeCbzR4ps#T
zfs`6{&X?B7#wA|jVe($fwPH$DyJ}CUz1K)S;CSQJpQiPNe=Oc0MWzh(wyAU|(Qgr7
z92KaDR6=VR0(}jA`f~bW9uF0v#Mdv8bwK6~moCs+Mnr+-Qo41g{}6D4(l`uf+Kf)<
zRcSw5+b3)d;Tjm6ZRHc48lUMeEb~qm@2xKG^GR{FRu1I8Z_mHu;1{f?ejV9_qDr3q
z&6zKAifH>`@z+IL&(*sjF7L{eS6Rm}C3V?88J(2uJ&RmGZZ37~Z^BLa7DKouc0C=M
zuUaHGuY_|J3Et>$*Np!Z(GWH|%;=N1-Goa?t(Hr>1rm&XaMxyWg4ud(
zW-(i6$ouYHMrw>To1)Fe5xHP;3_b_$c)(yoY$Z%c|B!UTAlYg&NLD5
z9_9cM{ub}Qo+}}sz9wy{YqZg}34VHBw&W`#qc9)JaFyX~J+r-^{TN;Mi?cLK=3y~8
z^f^6jmRMsEp7)wrYNlrZn={~d*8fj*+zDEZ^`csmmCLo4QL!O-0N~!@!31CKZCsgliyr3YZN=&
zEEJd3J0c;HYIgy^#ZwZ1R%1cz&H3Ma0CBIA
zR8*y9$()o+HO$Awq@};4^8JD=4Q3>`5{qWZt&K{cWG_%H6?@g!oYZUWwWC30cr+y|
zH52ACAXd((>A`DO0SOEoTW<@lUG^BY1uoEw9-4p~Sa&Q&O_xI0ZL1kZyO`~kB;yN7
zt5@0|Rr|ybGvxz^{nut}BC_SDwOT-achj7txh8?WUPlvNb9G033s5yrRjO*HRCe;@
z|#Y
zNh-GSMP^h3mYY(^_q=mIv~9t!Zsz5MSva<+QO-d^H_rvYdz8y}5PGO<#h<@~O+f*(
zQ08HjFrsvn-;uk%{mxiVi6nuW&!?SFRm?Dllf|~yt+F(GZz#VkWT3!Au)XVv;Ae^y
ziG1KYn8Qy;y0IWfBSDWvqC^49&K4}|%G9s)nyPrh#($ab^7Qzsf8ixuHX2F0<+_Ik
zVWSq)LB0m1cT21So8t5cBH{IJ+@-nE^5ov?CAtQ)Qt(inQLD;&9#U-5Vrq14ix8f%M=
zBGAeI@G#!DWrEqP8G9_aO0o*>Wh{bq5|Kf>jH4~!?^w|psJntg>BRns6)Osd0(Dl+
z({SRBE@?j~y0W6(R>r_dh6!nn-(GTt_V=tj{p7CtLTFgU(j2&TrTcy&YG??d%z5b^
zr2o;^UZV%kB_v2(%*cqW4%Hi+SQSSt)H2P&)cB8UxM`vWjMDh&b^W}>NbE&!UbV9k
z&E{3Z2^Khafa?Tl_qcalw$&M5;zU;;e8=ylMPuX1K6aZjiupe>{k}8dO1zftVjNJ&
zhJIXSK-R8q_}((jOrJh3*Nf$Ox;0SP$O
zzO}{suCG@DH2=1`KC;Stq3#g=^@{C(rFdkA{_zP|*5?r5e%R!q+My0YBhcVv9ZgvN
z1N6V_LvyV-tdm24IVq||!rs8Smh(1R{3SoTH@+4^!%Vsh=iSy##%G45?mTeHOoqR}W!Y19GB*Jnk
zJmPZ<71iUM^tuT&!lC&uY5?Us!=A=K18kIp-3ed_5_(A^Kk_6c|6Sp65PkcB$a$wp
zvYv6yc$19ye#uJBGT643SK-IU!?E|)_z)E5S98INR^J-s*Gakj(4a?Epy
z6>`Q1QxY8BQPbY2VXZ;FpK;v*%P;%B_?biqp?5v#SP|P$BEiHgr~>j@c5SlHZugr0
zNpRt>g!rVG|2?;SrgPF&C%krwigeZfh)VG2qj#{#hxP6OWQbOHON+DD->s0U?~vMt
zb_-VHSSO&gV29)LHt_Aq>#_*)mm3g2d>CfJn&T)(&?yspXZGc{Zc`ZHKeC55x)s68
zoRD|@BL?YwK7r4O+>5W>t}16EfLW@g4>*>sHjN2<-ummQh%Cu{PL?@LOV3k_rgB#9
zV!_dcOBsc4`-0hE&tlrsiyW-?^20mjH1rf8%s)h|4FXp6PeXSAR$yISX*%5Y0}rgF
zpxZf)tlO`pS+J&A1phBh21IM6)^}R~`nx^4C4kZw
zG$eJISlq_fIG9<3k=riqLB69_mMBXeNq6If@$395V!*Osx1ddMjNOg~4BB)*(d
z5iCG%em~c$jIjN84>aGTVaY>3;D`J#G=5@L8lFL61UXgjM(^(RZSeK0KQ8)3(vqpW
z^TY0ki_z*8`UJj;=0f5>JG)!0nC+&VW9y26G#XdM6z2*Lt0CiyS+_X=Uf*R~sMsq7
z83}_sF=dlTuK__&`(Owr@$4%oG~iE;3TWTAdZGj|NA+Z_k2V(xGs~9QH^cP5ecSel
z(Qlu&`~i3a$S=tlN0gzy`3M^K%9d%r&h%2RIp1-tc*Jd_)+u9IVZr_|)+f)3
zUS<}oZx$aYzG?a@C#PCHg6N~gg-#`uk1nkk=CIu&g-hU`_40~YCfu&zUN@An++^R(SR;S~j_qi>$`s<>
zrr)b1Rc-X@JQlIcPO!vM$ONel7%C{M`vMIY>W4ZbL03Z^Iy&{=r_z2ozL1~m@UQ78
zpxZ%hYbUaZxQN=WGeyS=lL@d*Avp1IwO!97mu!eZuU^BtGRsMC;$=KBDvZ+nySpkT$Go*Y&Nz<>UBKP
zoj~}HCjd)vi^dD1=cwQ;HOhvd@_67t?%_71D?mlLb#aEp2
z>AF%)hsB_^!nDj#;roy-7NH=;Cinfu+-%G~O(rNgdvi&`9Hbz{oiJeF%l;6k)&z)i
zd)GQuyffpGwGwB@0&ep4<2v7rJ}o(=T?};!&O=&!S-s1WV}5VmQv%&CpWS1z_+S`>
z8+4&_Xr=YAyg@#8RWG=wOwM#i($Gvapw9hZG=(eaxd;-Hc>S*8)ATYpZftd^SH?zv3s`O3?JVG3HveWLx%r&KYeM|
zhJ&h}@;0?HecBLTFa_nGGNah$u2!SIVSNb9{Xs&$W|AEXuG9l{9)F+ur9SJeRCv)J
z--w&bv{M>X41+O&#&XVyRNn4=NX<5DgwN@*`a2
zE+^Xv_qz_JnQ(YJO3f;=7ftxR|LGQ=$x%!vt6>GU7QOADgMNq5g*4@+5?n_3P~)d~
zEe{0@5w<6SUPItJN*vz~n7*d_kIWpdJ)fNIv4}AGG5cH*Jjqm`Q&-`J==bm
z^^3ygmRNuG#gI{`_oJ?GcY-N<^GCRDxVgN>(jneq!{d?P4y4G$(e3L*!$19`m|RmT
zKb{E_C4j;#HzNmfZUDz8W?EV5X;FgCXhCgwAPwyWZs~ZBDWmAWVoj(7-VUHEyIqzJ
znp3wLLemnD(|%TEJ8}|ll!eYphx;=6o^A+d03!kp#d9jvwKW2Er@@V`M}`N0OQ9d@
zfBkoTkC6Hz)LA7DdU8!liwtw@H`F1Quc3G72ypYc9Q7N{Xv@mRmkAaKZ9P
z_ei|VPnZpKND7gmW2IO0{emb-`?=U#hvLE6AXWfX^w-P^M(L1t#RsQ@klFLWz;+cp
z;hejJ^^_Het%cvEr@>iN!pv1I2W!4Whb-EhaDLu7X4wXIf*gB^Y8#Kpb6cJ180;ff
zZaxrreYr_=zG|(@jZ}p?v1H3Ibk0>wlSrmKvjwR=#h#4%5%raEDW!>VmWfP)xE1*Ne$xhgj?=X)YS`oX!q_)FbZ3cMN2+%pW19deuob`PNb$kC7
zDfvy$;qO~>v#r&x!1-ig!v}{SVFJ;jqt8jX8s#6!87yELSObYd0;>mY8(Dt#PlVLG
zq|!Cu8#`(*M(M=B>}Z{PcvbmC)PxsFeXBdEYwTY}%k>JvN~;-^c>0Ao#|}MX1DltI
z5mFf3IUXKL;yPO7E{de*5m&Q869}+l&*z6Bg_BLd*O-<_kYxvOSx!K#=8!}i-&*v3
zycx050ZexnpFCkHN5+NOf<7@fAq--mUze5A$DLRi{W%gqnpf>8j-3%eN`GJS
z4!=7;*_o}@(JjuYP+Q_0%kR`$MD^EsYxeU^AI6XWk=+ZKLAT8lRYCn3_{5|U$mQmXAYxxo&@LiaC@|Fwi|3l*}+M@IQ#iIF&z
z&-v&UW8ls)w2D;y3dGjUP_ne!6f%)uf};Fu49`HX`3I(cAC1g;ou{!@k*K}uK2Y6M
zbss&bZte#O9ssqvc8+NZNmwhMg{zz_h$)bhgc-InW$|#9qqJ`Z@F=6Hi*&#HVwFu#*c25I_rFZ)MsMaInc05
zQ<40?qesDZ!;k58%S9$+QzYgw=@%wp--EgMeyP1=Ov56JguYKbr+t
zm0K1!8666dS2swU)^n(vf7_Y+7A0kaKNw)ZsV>Akwc^=QN+P6{;r4}l9}&PDq+enV
zVon35dm`Ag9Lzi;B8=)9%ER?pAIJ9crPvtE_Suv}04J`>PfAwaf6aYcBAH`%ushvO
zhu&?*@(%|Hen5GwG#9a%(p6VFcX@D0%80Vp5Eu4WXUE2
zIx*&*z+D{5(=Tkgx%P4E&UvC(1I}WhJso+l2v^@;l>fPnaF4tu^9Y%nIWh(ZQjZ$~7lI0f%$yt{ACpaNy41LW;A&bP5Noz5#
z%>2X7bR>P_^%O#X>_})Ff@~Q%Teb)^&eTb*f8|%g&Bw9{y}coLwKNSC>_g*nJA+Ze
zOX={Meh`Un*TbdMnCZw=>X50PYkK0^J}Lz@qTx$6;JmB0cHqkg+ql-0nJ!(HUFy+n
z9Ac@(huN6I4Ikss%y*6}+i)a)YOHrQJ3?lFSXZU+uxE*7`*E079$nTxoQu*siOgC2
z1Ce#??^(Nojev)piJV*5W!^;!23vI+D5PNl?3oXVUo>)*RZGH_1XrM{#&hr$#%k~W
ztneE3<7T}&J{mx|3YZ0lNLF+m4;^ov^sht@$O>lqkYZ~tam~Y|a)q1uT_dSO?jVnD
z?N&(bYN_z23%Tz0%CGgiA;Y&-b<=mVl_P#@-=eY~f^~Mgzh?}pUTd~vN|DI4d((#$
zseSX^rJnLWcJJ5D@)pXToPh!MCX-cCm@)j@MAQv&ebp{H$|K1eYOEp5rQq<8gB*}m2<(#;O)NNcZ#
z_CqEzf@`jX@uQ0f&vjvo`Cw*n4h#Zs!v&8zVw?9n&<=k;nn8uu-9ImNr`hH7N-hwT
z7;0<(LhQZ+Z)$fN>hw!o&S}|(Y(g|H5gzt|QlVh#Hy^q^AHr1mn`9vS`r2D3fHs*Uid*2a(zU&X)O@RbEUM#r)4MjFcF6y8
z*67E-c_o9&4L)fX2RdX^MM)pXn-1Q5o_}_%_MDnTYYc+_Y$MC}O}yDK*;~_fpP$t1
zGl=N7zaaYRo|Avh+q}mlQK-KBON(+~?*2J#QsonhF%@eXp*(frS2UaFGw-~f()YPlzYJqM3--G=DIAqGb=N-L3k75Ns|z
z8jaa+v(G88gG`u^IdRz<2Li?qEJFAsv(jTuWCW(uunsz2N$0g|cFm79`eo+&6!I=p
zoW(vwT3O`9P;+;-mbpKTX5>-9T2hF={U7UcW=@i|m-nLjsHfT*ayVSY4ifT##VeQX-WgWe!nEli%j7YwRhGs=az?
zeVK3PKXdi}mrdLGToyh*$3(GV;-h~4ouUWMAALKsGBV?HUW$@Yx$*7Fq3MTbg1$m;
zWK-C6$nv0Cn!|s1YW&RgmAJQ=l1Lw;Ep01yzZv*q;m@1eN5jIThfX0{8|0@G@Hy+>
zj~jffGRHS0;321vzi`XvWv6;Z2ht|PQ$5tr=?iXO^7Y*e0R>eKsSh8H*OR`T#EPYm
zts^C|sh_CZn26T;=3fD>)D4dQMil{1oHmoH{;!Ehvu5eaN+x$iDqCv$9lSvw{kiG?
z>4<{krenbk`KRV&TIv(xi+Mlg_1|z@e3}_Ed_G}o(a}qyBQIn{mQif9?-$fte7Kvx
z5yH3ohO1-toqbXP1@D#x8338LUU+gt;;8r=$93?{nBP@-w73F_a*i*Gf&GSbpSADU
zA_H3r3FF~+e{WTUlVzcIi=TbgN;&+QZ63uPFwp*Em(zaEOYL@^vDeSS
zS?(I>fQ_lobLxWI$LO6c5$(G_&$b0P3U+T9#C+peX#32uUzMcrSt;;ap2UdDiI7bm
z0@gbr&0jz^&$rH=>vqiPEBq$8kuuSx{E3q#Lf`TySKRam?lg2x%eO#!vI)~u?ZyA*
zk8W6@jt?6s({}s0I+TA<$UXkMIQR9(^IR|F4-FPqxUJ8Zy2SZBO
z-*Agm7<3H$xP56@&U|}*m-h*azZ%c<^Eq#|pOwEq{EbU|1aJJF7X3W}fSVGtz~%N$Nz(e{vL
zD|kSH*}a*{JJF>@=L>pWCuw=-T&u5#{T9u$;wk~2K3SipEO<&B_PgjVS))z3@EMgQ
zV3Ru}=@Hz8E|N5eMAmI&Kce#%vfbFHJQALw&lEkJm9usd9ZpH~CmIjnP!H02an|-dgwy{-p8`(4AxgUufc{P3e$<>d`6aZMu)1UH)U?_|F)17$)-BIE#cl3C7Jur
ze|W%ZJp0rA-3fuTSh2>npBf$$e%s5xFC+u@N**{HLr$s>uJyRDiz~Ps_Q8_nj=ZeKRJ^F%jx%^
zV6m(?DIxm##G0h|?JR|{&_|=nEZg=ATO$sop$2!5%tWPQF_tw-7j$J8<-@6kwwY1R
zf6wlpJFc$nzGsQ-mpJF0_O|p&T^;d0%OBHfj=%o{OE1u`xCKq>;U8zH(nufiRtSGx
zableRy%5wf+@igZ+^Dc992*OmjfB@c4$&?F@PXorXq`HXi>s$K
z|N7U4L?4#)E{BkylrhAvrpq@9KQnP@+_X!*!%yCh;)Y}u1I-2*KHPHdngwuRZ1wwX;5
z(93+|;qR__i?q4P)o#6-mY$N1X{(NxiTAPGY)^yMEx!f~B04`8buPFpbdGZ=i>`OY
z-n!I$V}^hEll)tR9dc9RWM$M9w{5T!l1AJ0ZH@G%EaQG?^@!153oo`OC29rIl{f8q
z#L92$sk1TxVIqU|~e*ARNM)bF%G>VdZW21+E<%A#@
z$U^4{V^nzm2s6-br#oCG?CLqfZ>C7^F!xMk^PpL<3{D4J`5fiSB|>~M>0vJAAu|&f
za`m$}{YhPoallH*X=#`MsiVupu@-|@4SSeA^*1Gp=sk{LD3H2{ky~!bTV#qq*qpEJYrh!xB)$PNV_P5xcOcP}+E?h+
zr9cJYab0HAGjZ*q?DvNcd}7y5I--bz_-we}Mi$mSPbY1*@l)T5e|ha(FIru}0IF8Q
zM;h2XC_hnJBFvC}WF6+@;$nvYttg77)?f(LUqMrJO_^2Z5
zvNw2>oSfxS#U=_om0%1kgIdLjC9F25H$5uq-9#5UW-J#qRy0*dm!#?fyo;PlF}pnm
z%Xz!`5JRu8KIQjmQVbZcyN3|BmMnia@GT|uIQV}=d6w&T+;7b_OsObsQ5UaJ4o^Fd
z^ivf}ZjzplNiJvm#%7pXaOGqP(pb~rmVYX-I8hZUJaBqop96=bD@_LTaFU#?^sRFBRHen@9
zry}1|HW)@jX8P9MW{RRlcMZK$WnypKj}}BGjq(pXfvlzQ=cAzH@R)j&l26O*=CL*z;>!@gR-<5M%|BYLOTq$EcD7t8|Bb(&b_@f(vd
zU<*w6Er=?iVfHXCT~54`O+U2AR)~caRml$en;T<-iRv4shf^Nxh?*5sAvFQYW}w2S
zh8FXYl;%_UOk~?6x+JkAD$d0KSI|D|J@P8t^r?jrLPfGy7b?A?(=*2Tay@`ftJ%1!
zuo%SZjU0>Xh3#5b*lg>Tvb6_V-Qn6>+to+o_Z$qi?cokF>j`^}>G&tp0iqW%>d
zKvB0@vyHTc{TF_;+8$icQg;jP?Xe<_0C+U5!ty{mZatv+D*8ev0N|r;V$i_OOFNSCF5*R
z%yRS>Oy{5w#JV+I_r%I<-_fMjBh>9G%>r=;Av4~Ptyt7#_fweti)a1#te~6X$v$H!
zjonf_vf2~~n=vY6B_#mKbDJmi%x3Yci#Z@Mitm5;#%z6-dpaDXXD#QX01+3z51wwf
z=defD)a`y%bD(w64}Z60cgW~Cv<4==`L*T>w3JEV4*%`*o0GBUl_#pa>-9q@vp8(E
z+XiiUgfmH%sY?L>?}GO4MBBbG#h8)=UV2pN)!8AFp7kq}q{2T2fqgH!zTkR<%FXLr
zDZ5@WPG}Y=0|D=4>+Z;X4FmoHX?9U#Gcs%@7sf=FLljEy>z89n9x@l*$^5g~5n0_D
z7~C&l?{zTxj91?y?$vpm>zv+7vxPb!)o7)?R{ocR*Px?VKd}0lg-oiURZLt#Q~`ZO
zpFx44pO`6RWR6=;Siek-XZ2M%%RiRYC9nsW&`_zl>`v(}V)e0f&<>+=C5KA8ZE@Dx
z92T*n2&?8-_}zAZxfh%4_8y6mjda+=d5ky3ap=J(JRhpOU?+AdAy+1v!S1gTB3^Pt
z%vIoy#$5~(XlTfjzH#e4IqBC6m)x2+uUsRJ_&^^vB>V1+qMNl~q*0nfA_v7eSRO)a0eCVpu0sHDmTt}
z|N378M9pL6b4SMy)89SaNY88mtL-O-5-GMoo=R5s{=BUT$;y_&aU`f0N914|Bs$^U
z1~#7}#-hd!&T*2G`>*)o4wmBOcl6ZHUh_4*wqg37>taYmKpAqC>vz-F=2hE6M98F5g#9u={Xy{SGMzJY^k
zRqq_frbfNk6mHu8N77kGHTC#!9Be=Y1*AiyJETiV0U6yT&4AI220zlRw4~(d-iQ&>
zN;hmH1Vn11*=T?Jp5Ol7Id|vm-uJ!t{XEa>0nY?a_q^WNcDMc!-mp+l`VCn>9lF!f
zohtLOqfU?>-f)6>t7O$Z##&U*JA7;uwbbcPd?G~VNq27z`)!clyRket)q<`J!^FUP%x{RAsq
zl01$W6}i0;&`sYu^OV%^%&q3}wb4in=a-)LOZL65$e
zf@fyTL?w6u&8Zhg8$sC#hJUEa@A?~8S_zpSJHG95d`8>9ZCuAb_AV}DPKJyw=Ce@K
zwIHj4{vl|H(qh*-5H5p2cxkTHo^5&1pP|BI>f;c36VY~DBT2GNEx*5)QAr(42s#*!)K>$2<|yU$~oH-Ljj9L83FB4O^ch}
zB?;tAid%j&#=|C9^Q+wnV+De*4_awl)A)SQ252i2FZs>DmrEljy-d-oOyJf4U%gw#
zr{(OyzKN26$y@@Vqz~PZ2cS{)@xteJE2wpkJ|#74&89j1?41B3KGfdj*MRKi6*1>7
z@MtXl<1Ee$!!pi7(9%J}Vm#-RwVW8!(#z_#LZ+|-iDk^(tu+D&d1!>L}FU!`<2qSfKy+L0K1laW%P6NNnQ+3?c(0hS_F
zVPUN2682_jP08nVlr@7g^$o@C@Myu`&EGvn{)rdcL7o#+hZ2{x-t~-s9iq)c9T4NK
zPx?Q;q)UkfEi)D{5ytu26*J;uE*!g+P}{)C#=Bwhn3f=~#N%&{*4#RY(1d~cbk7Sz
zu?e#0MwwS;ZL7$F`9>+hi+N}O!+)^K4c;>Gwkj`xAO9jev36nj=g!g+Tvdc;DmgZo
zik_bQibsI`RHe1?IT;GLzdcDOnYk+KoHiJqzPv3tE*>K>u#MOXYOC3%xANOxsK8Yj
zRvQ8QcmUdoWgMt5zPnT#&$f_fbOmHYPiFgOK1aT$a-bPB;}(37k;JpF&2nq?kKi{p
zl&q(D9UrWzU7hArUG8bIF!QHxaC#Z3;MeE`oAs19Y~jKe0Sk?}eh{;L+il;ZSq>t9
zXR`@QIkvmuVs;J+$Z7LvUKq8dtenY^weno&mzak{^lGdX`6bj+hXdEmTy%4+)|vxI
zI8&;iK}y)3rLOHgU^u-t=>I__O;)_b&rVE^Val#gGg&RguvVGx++VVP3D>6F`JySv
z0CS3V8&CU><@OXkh>b*PH!ZrGE1;tL0M;W20ddi4rNE&AiN^}RHig7$hcbD|xMx7+
zc>7`tKi;;;oDf=c{S2Yd5^fiGXqW3m?;tfn+DDac0Erstt;)8rWW!v35ND;_t61TQ
z7b%mO&6~Zb4t^~Y(B+@G+J3+{+-gKO0JW=e)v8cS`S?9zi7V#IWWs3Z=~ERmfgHZE
zCq_zwjBn_!?a7UAx!~-miC@>M=wTL2uwA38Fe0gV#1oPH48$p+Mx`NULp!ZiWoXm2
zG7r%`)T<_%P18%zyvf9n*)t}DX2K$9n^#IFq+*<<99ex`bSp}%imL%yR>?0E(jWk^
zeu*(T(#u7s)-mP3e+0(kNTD7b;|cWea5K~3#GSrBo=@Pg`OGvU<1tVz6lV2en$#>4
z4~AWR3xp|&-hXxLf+i!1f*>64i}cSfSEu;7PDY{p*a6h7c-nx`?|jU_EffI=j6@cU
zpI|C_r!wiHZHO}r*k)7XtED|xy{09esI*mAz-GLmYf3-x4w+9U)_Mbs1rB3p%r;#y
zVCXqf7~krI3~d|uAAvJ<-;5j%Y#Bp>W9(IYgz&}A!D=NiZA~>KbhNFBF^ymAi`Fgi
z3z3HRg`?N+F|TdlJctgIIr5oP)W`|3v~)(9lawhGkR%Dw>oR4-0d*tyG$y9O;B$1d
z12)3-)Z`CbBC;CKnDk7feptMkmtO)PzMAk(G!_p-^+c;3{p_$-UX+?Y>v8APM4i75
z{B34?Y?bZ!!7ta#t*u=7z~N_XoF^XNtg^&B26|)y#!Bt--FvlUlXmBGhhTOK!C|Ni
zN1b4fCW&PBCOoZ0(OHf9w0a8Ht0KZ6iSWN%QW|gUfC?icTHz`(#OT+2XgJ|beXeRI
zr@JsYRHGW1%tUrG_-Ll7^%VL7P%?YORw!`C*42u20K=uC(Z#1`nS5H60U#NkWoS(-
zEPqJip}oo1r$|qJ`Q~i0LWRWSmgP3!=yyz$r8Efk>EY+a`rEH?S)|3N%XVvknPE}p
zI@er@MlnEc%cUBggSX73@3vQt?0Vo|Rb4a`vU>Vxkp*7*b`@A!M8tq?u;RGz8&^9f
zI`baSAG4ulL6TrkaUKH?Rwo+lA$Y7@|aH
z)|@0FKYVmc8@=4h5!ow2B~=P}QO0+wU^ubFR5D
zzVDFv-?2FdkJ!(Y{@TdMHot4)4_425&fgCg1np}r?iSO=0jCwpKiQnAp*$UP
z2!s%dJ>;n051I*EvZl&WQ{cHa?5nm;+@sagfdVAU$(~w_@K%9#
zCP}?_plBwc+15l66bu23UIKlYC2g2W`_S?$8<_du*T?ss2g}sb)QVYXKL>B0SuT;{
zqLNy#8*bChC*?OMw_80G`%Hy(RWesK+RSRV4E5Y)S5T@yS*9K;0`Ct!Wt38SYY|YR&s&^TCs|a79Xlbi7=bM@#5}IG
z4hVb2k9Bj71v8+8gpNHu7ZTstQ}P#)l8>>uK2`32GaFm3V
z#A9s=v-FDjJio|1`;9|aD}Kn#8fWpOoEy09JiSn
z=tEx7X+@&*8@JW3gbO_)It0fhzw7SZ3u)5%^v8PyMtLg8LScG{k5i?^Clu;XRP38p
zd@}f1lXtam%2qq};CxM==Qj&SMMco3oQY;#77Dg6MC%RTjSAkfISm)q5zT!ymE4Vn
z$3LN%#!D?bT%WWRJ{I$Gbe*Y};SYYDh}-?1HR1f|o*w5%tewk00)Z}}13K4^zjNMK
z58De-xcApb!@(-2U+{wy6mXI4q2Kc+Kq^Y$^x4*gz(0b^}jr0|WL`1NP)UaWor7vMb;3
zaY}j+OSG@A6Pdqx-yC1-R(IHITx9atabDPhK+E1Kt62HL-_#SbOe?cPd?M{10Rz^#
zIv_uy8IiP`+hbP~cI#=bBu(@%Hq@a5yu{
zelv4zVmWQ9=0(7T!I!t}uLs!1;%~T88%-CwDOot{4CgJCAF{O&Nd~{3`vku98MO`A
zdCTb+z&ob#YkyH0aUB<#gXmQzI?RD85>1bi#x=8~t?TPjO%m@Gx9`aIXOB1a8KtQ
zC$an<3q|>O`?awe9>EsWt$T{ki*vvmCF=tJi&9vo?ghsu6Nm?mI;ddd{B>
zxH#9j6ieRieyL5ce1OGGO}Yt%=3u{4e?&EYc(+o0vG?LvVqD0QKN+EGTM(iF^du80
zx9|!Pbcq%nO8f?9?cP)t>9rD{a>HnqA~g0NGNNY%tJnH?O#yiuC1%1F$mPh?4V=H8
z`}><ql6<+5ZpU8@$f?_Hz6lIj{&|9eqJJfjQZh;4QjccfVs*G#I7?ERZ9_50W*N6V;h
z-s_NiL}VJ`4iMQ$FD^Y?x~%{Uoa~r@{chS9Zp3cU-AG`6HvQr~OssRY_)O4#=Sorl
zI`tf_lI9F|dqd#jmUrKBXwoZa=qF}bw5G3aVmlkSg))C9AuGFj=JepKTCptMl{J#EZt`q6putdW1hw5GR*
zR~Ttef@}Bo@qNs0<>UbzUHhA!eQDUqy%fB05VE)+^p9YyKD_ew;WfX;ZIr!h@1`-H
zJ0Yx%EtI4dzIt%QerD&tnYX;Z81~@T0&r{6oJyx!~&>bHU4motLj=J~LK8
zp7C#c(1Ga=oBD^$Sd}Rbu33vK7yQi+bZ7HZgHZieoQ82jHcS$ia)uDx&Ws_W+zu{2
zuXLk)J#_0a*9ebMvlmbX;*Q0X_u|EnV7_jwiQR$xO9qvar#~ow71dPb{IOt~KAYD1
zk4pDFf=spiFT2P(9iWwFhhETaW?&F~Db9s+?&6?Z53E+#9fxOvtD*8J;zlpa5S==B
zM$hue7ZBUJ8bwH|qUc8dG+sE16TXZXl+))xS|kso~AoLWle2i#u|g^Ka}(
zL0bL3@4ow)X?<&!F>i8Yt*Ksdu%d~TOxbDxU|t;WIAn&c$2GrxWzqCo7EhTtln?pI
z_JJ_Y(kL*pI(sXvcN%Dro5DmqqjhnjHX**|4t9OrYtU=!^}I409;Q*)`erT-TZ`_G
z*>3B3FlmSYC*d%UmOphY()B_yt}ctASLPxE@XQd)Mh~Q*=EJ4~Iwqq2rA(wlw5A$H
zh~(*KO}z&x+Vz>}gE5D5(WK&m>9YTd822(tng^n
zxf?nbq?UfePBt8tJ?p(Ic*WR=DC=+jSo?h&J!nmD5iZlM+gmP^l7I@6%K9qgZu|JW
zzcL&@N$2uFdjmi3K~8~6EbWyWY3RC$|!oz`qRH4_JRDn0iYUjL`~AWXGi4K9;+Kl4rw!W_ok=JXX`
z<(7O3Fa?(9S~p$4x^@ML_vsegcWXjQt?;z^3FjUZ*y*kU1fR#|R5fZ)FPB4TG{~p5
zN{hoZFy`o}R7~lvow(?m0sFKfwp|-a|C1{%E(o6-v3puSz-Tq^_jV7zg}%Gm$SO)k
z78A1@jf`mwwz~q#)mrY*YyDx&LJd3X*q*M+*#EEbNt)--YP1Er(DBkUoB-1g4qfXZ
zZ=KG5JaObbo0;R;V~u_}>!#)^Nn{6OcIo-*!;_v~3bT&6T-nz-UV;-^eq6CX1*Wa{33*?
z@e$8~?L~NsYvy5(u%R#Z{t;N-AwBx5?^Y`B6CXD4E;?K0ehbkQXHW0{`4Y6FIqGKa
zZ^b&)$ZR%YQ+GqyL<6mTYv%H3;=iX2DEY9!r_Dg1T;5m{Ul$r#A|<;N7Ue8~R6mAoI
zy{4exdLEiBi*Hfu{{B5bb&q|s+AG0qx~a!(D@*XzC-!KlwK$Smlk~r(#Mv8%pWFxi
z&v_`1tV47(Eti81yn%8yv^G{u(XTtwDv4X0E!m$|I~axl!lB>Xu+~5c_?WD4K%zxM
zTE+I@?~Stjp>g#ZWuC?FRjRYPR{h&+;~45^H28j%YL4IF#gA5xK(XdbYwcJ!=F7Kx
z$I7PD+HMINOI;F*lMM&s-+;|@%Q6e-gq@8Ad?wS>uFEDykR#ORz{D(rX$b3QF7nfd
zL9~n~-AgpfduPbY@RcT6T)x>k_d(^Lt@p3>or#i+=EsrZ6lT<(~ro*2V$EVc4+@CS=b
zGzHUiW;!6?9v-qG>M4O#PAl|{fhD!i#%fX0eF?3$`R_zOzD2@1*$nJj`-VO{Tm($U
z|2~USpBvjTsJGOK^rv)+t5+l+*2{OEf$-GR8k0p=0`k5e&#y>O
zZ`-$h*p?V^0ll=&*%Xz_J$$86pl?j7m{7~vy}~E|5?)|2FEg%c>MLwg1t`wx(_QmU
zaa=B#s?Qiq@}c%$8@Q!ym{VH9O}_eA?^ot4XbB;t2DUZ-BPd)kA6q%X2p}VxX+o|@
zdzvZ>YWJk~kaJogWI1}L}3>5NKEPq(Jbnj|5QU9wDa!%kl~?X!DY60ymM
zhPsq~|FyogI$ZebDg|^ynuL7#16HrK78P~-Mh%po+n1o%3$cs__nM!fUm}e^<^7
zjQSsnWEieYrxwwPcz`9VuB)KhbGgFU81F%y5m<`A;6}r&dG(tgdWH>tYGx9y6D7P~`tI;c;^qpvd^Dozf}GYx-x5x=%5Y`;&3
z9gUB>6%{`EF^+Qha{KF7`7?z)(GMv=m{BLUQvjdiJ*Z00lf@Ff%&UYO-K6)mm<5b)
z0lHN9SP_UMyvaG*rj8-_FY7nHOf*d3#_s5H
zH=G97`R*!t7>t3U#7iQFwGS^Hr2lT6VP7kXN1q{V-TWOR|h|*K|t5@{>rlVj7ox6Ku8Idp1>OAk6>1}B
zwLLOFsiPBiCKbr|Z0v!X)&+Vi#(GO`vO$?-VZ51cea^4|OFJFoIK}L9*m0}d*wy3)
zm~|gqyw0?p(Z~Q7mvQtwb%CsCTp^$7RxQ$bbr-Z}+f!~O6}%>iDtU_Rv?FfIc*3)n
z#4V+U^fGj6=e*O!PEAp9$c5ckIQ0}#YHtP)e`+SmDCR13dkbpz(~`ODDynNc4GtjZM+R5|_u*3sI@N
z^O*xO&%Ht~GRGds#C&08>?fmpl?AZu{4!Wx#)=0(AL@DqK_N#{cZb$QQOms=ddm>!
zA)x38Fz(AIAOIJ2&6tSaeDYxGvwZYZ68KZd#Mz4>yw)NbMxE-^I3o&ehi1
z>B1u_XVz+ZY9|IU$t&IU!Rj5w91sZdA==nc_<^se=EHiqm8PP4Dq0y$1Jr+vq4_=MWbjsEY6F}&1O`MG(3pGr?T^$j2GNfgW930THL4(*uj}#S15+>
z&KR7rR1eHgRlQ5Tv*gtn_D0R@yWv~S-3zewtxD*f3w1cZJt#tN>ZaN&Fc~R(gR$62@r86#tLCKuF@Z*3qBks%ON|!$c(Th>im!<(W
z#mho#zF+7LMr?hUx{!xXv}$gQIe%|+wlg@@1jjwQ)`B0D@fg06G%PYzX(`1&Z~@9U
zf!hB5fmZu_DBhN(+H@p?z|`*zh&8ExupgXz5JKCFCwQfogo1(*$l?Ds@6u&HL??j8
zD6YGl`*1(~Xa@qp4V
z&Hp1!Y_t#g*i)(41mN`oqe{Q{AK2nC{S9Ae6{he)0UW`F7UdYSZ;t^5u4?iPQ|XiO
zKzIJMX+R+{z2a5VWpo$l4;33#s`>A$Ok8}O=ktVh2Hz>6Z!Gek9L+`^3QiH$mROi%`Z27=aaeD
z*Z6zc6l<7dhwz;EUb8PU{$I4svMd`V2alx{61$6k^c=03?LU3YPy5)@ldt=_9wjN`-LD0FeTc6;^@GaQ
zVCqPk$773dUG-z|rH@CW#~Y25JBGE|L@K838(w-4TnImVt%a6`L7F+vQhaLr(tY&L
zc{6$=bipuY#m~hXLIe=XFzq(j<@V2TxNORR*q}Y}p<8;wtwM)}QHZXf?Qc8AEUoT<(enQ2BN1zi
z>kUD$HSD6NNmb?}Txax@juxPn4}d92FO)W#TG?3U$+&dm{e7@f-C1ueyS#x
zR!*$_jkpnF{EF3X3WYnYz#Kh?Huf=078SOhCSr8Hdycd&Q35!s#Nd(C>WIpy(n|e^
zn$?#+UAZeaQK6>2DvF^Wu+WDT%v5fw!hse-SMuyAn5~_4XOr7dPJl&!Lq8<+FDmV+
z5Zfq=2q$+T)^91w%unXk&%M6qWts=>)7tOL^dJ)Txgh}s)VA5n0uD=9rNkdL+#ehK
zggm^XIJp(`49((&^>V$_nmWrk1J&DQtVZ`^rkIb!FtdC7xzsHl6-sX4hf8xhG09QQ
zb{|cBkSn<6)kjuBZkk;{DbW06GwX^r+}Ug*Vdj@jtB~-sTr%1lZtVAe1Vo&UU1_qa
zzp%!*&X{$Y7#Pc@4bLe_7ZA
z+>BglC)n;d;cljW!H=4EZm@o2HLNxu-CXn6ssY^f
z_kMBWR`fK~JuUq`zB
z6dy(cPxpfsd<9lg@7Na)p7tVaS)Z5pG$(+{O#^x=yVCv+JkZyMv^#j@l}GVdmH{dWf+}!_s8o8)ACDl-yQd0WOlnczfN{7Rf{!XJj;Ts
zTK+m9SG3-0M34c9o8fWu>ZqUs(1>aoLjtAWbv4(;6(5=y-S`#J0|!xAIS7~OrghtY
z1m@OP8p41
zo&mRBd2za(D_goAEUx786xIXG$S|zUaQpkBToh=iu0|0z&da=*)AVX!+r~!GhZ@(_AP*wHUXO8p`dquZ7+&d
z0yXu1d-Zvg7*_bT-n+a{&NklJP5IoY!agp@r?=n)LEA@lI{z{wmI?6|YEaS>vtD#;ptL`eD*R
zg>Au75Kv?92r|t0Ivx_IUWM$WIA==e;3Sitnlfho2%
z04ZcS{P7VD5K>1&D{U3i(v^npo{};u(mn``=xJ49%hkZ;%e@lt2P$gadCeI1=~h>p
zHyv9!^}r>rs?hKjvc`wB%y+8hhru=U0>|>ce@Ra_r5Xv{pKTSAbFz#IevtW#6mdXU
z7m8;!jaMO7_NL2lo%a?yi*tcqox69EyVXDqqA;(8`5z0An$Whh&7<2znbqY^?8D{g
z=SK6@ME5YT;Dc`gYhkRfzBO@@6918nizR5`Fmd?)Q&fbeUipVZU^LLU?T2o@Swd1~
z!j)c-N?=O>sG@pQWpV#c;NBVbmi+WUaYaT}vEiN5Wq$c6nXT7-oYIemS5vi9o4|E|%>
z*TMqQ^0sSLSGt*>J-M_#CtnPP?l1V{z9frUZn}pZBmPIgkU7=V)bFaQou)h$YQm-{{}
z#iN(1m&Ems|Ef+#$kQC0pYV~tg|ABHOlw{VC(hY+sIX7rxPE);CU2B!`(N?mUN*is
zF5HtlzH5Oj=vylzJX)Am|4_Zw7OiHO6tu9h*2ugyJ2uDd>l#{D1<>RrJhZgRIoJ!R
zW4buHFqv;S&q^G#`SMz~x$3sCK_)yE9D^S^JP!0zwEzvViV4(N`TPucMmH56xsa;9
zORK7zQfb#9)*u$#(J{D}^LufT>g;GPna0iPEZ`G|-=EUAoQxsuTwjQ)dfETp(-S9@
ztO#7H$?118<@)5^AH%;FqJB;&Chx+#^!RktJuvDOFFRiM3A|zZ!a1*aKRI_!GnPmJ
zU%BR*kyeI>>Ji>7R0@15m>cK?GfM3X^LECA~`(kd2;e3!I$>_#lwy~_6jPu_pZmveTm
zEBV0&i6c1P%4&+_DBEZC)}>184@B%%DL?A$q}uskZQeQi20n!3{IwZJ4!D&(|D|m7
zVGmZm@xrf8p66U3%4gq_YDeyV;@J(rgj17Tj`FSKpCUpTb`x!&uDMn~WKLz(0A8d^
zk0r_vGkD9X)t#N5@|`n=Zk%Cs>H(%IRk`My+Yeij%8bC5MPg!P-#Lm2Wy9s;SeWF<
zSqjO7Q%F74@40NS6s$4?an5yq2F#Na;d6dIsP&jF)_Xc{+2!s2+_y;o`QUJ~dC~9*
zS$c*yA@^?rj-Y_~fWCVnA$Wz3^HksX)j6R&L$?7Jg|Qxy*kN9;deEmrL$bFN`o*6J
z>ywsB-)baOa3+Da8$Wy@s)YRa0N;1#_@=P^1b;Rap_ADt3_mN^1uo$A(+YxsB=|o9
zV&(gpwvF5I%4|84gUa_UWQp6T-=^{8t1I6#kp7g9IN$D}@?&9m@m8MJD3YsX<6VEa
zHn)wKvoWFk9=SFZHF*J*)S>Y`A{i`KTSR;;etY;>H#H`Hif!9c0?&8d-JMpx=TuI&
z;Qjuz!O3)in59tp3n1^!XfMMPG5ujfPZoxTMyM{wg!F{G{-Ay(9&-pla?pZ21#X;9aRES({GA6Wow{66`4>Wg2Ui{gVEA~AEt
z=w6O$l@cSC%b*G!{j!c&EmA#aw)_nko8B{@D&jX3CRgqnl#FrYsb7f}1v)#^ojF?h
zU%!`HH!zUBrw4p+GdlG*m*_ERK^VSD?|o(wbHioc+uPb^DgSJ#=3%0wb#S}5lQGwD
zKdrV&EnAObg*>?eon4+c-Jwl0{bn%l
zV)wy6qQD0)N4h-i&W%>Gp8j~YqG)zh3mY|RVGw0`WYx5|m)|g!AlT#Vl;C>J^#j8i
ztkgKqJzH;dl0l80&1-9=zH|5AeAG(6P3m#8gOO6WZW;owem7t?B{7n1QkwplgyYbwmaTs
zMBOJ5nz*>H0-f8cF%$lB@bop=DwJT^K-TqIsN6`9MUmqy?Hrq!BoVsT
zowT6uPrg|iG|chL9p>3K62FMg(G26UFmQ0KZpm-6{T5mpdI%dTnj}fg#L+xL%Wob*-!t~Fh)
zDd9;?H`uzIc!FZ7xqqnP&56pQ3rpbH9g|F({$XvIn%iMe(_G|Y?WBL`fwlL+;?bXQHE_7h4~&3H
z1d^fg*Ve_dnDiMgqpV$z7^u#HVaqSA!?4Yt$uRHg$y|netQ5#|eCSSuv6xYOalMQ}
z!C0~%dtmDkuLDSs;vDrpP#Se_@AU9h2>ae7uShbMxOk^qi`nx(X;;X1>r%=TZ6Le<
zk1cV_1t^GV9|XUpl=^zz$P%6N*w|etV1ZV<;}^1hQ^R}lvplFJzY^cpd#GEcf0(@$
zZkwRRd3COE@5n`FMmrHO+IWzk=9I}KzJK0$Pwi0Uk-0-=S#B%yPWCXXk``&TJ27wN
zAX^?o26=X9jLVZXu58$>>Z*CC5Zxe{wCKLq(ftMNdTqwBtstIGS~{ov;VA_Kn~S2Gv88B#VZ$gN>i}_&
z1*7CH!|bnhu)5HdYP8tX9=j^lz|6~ubNjpG
z;5mUcXN(#X$;soHs-*kUmFAU}5ZtU@yQ49iFr%xj^sO%TFj1GLSXkK&w(d3h4
zu-RJUdhhvu-|n0i)a9M2J5$$FTmnSfuCNV!yzQ&qK_p^jMEx*nSZlUdx7w`O*yVXT
z(u!Y2?iuk#p7=|ifSfN7>IpqvYw-x1RoB+QWx05`|L4I@@(Fr}Ssa$V^1h3d=XrVX
zSVzdo!)vTX7vJWJb97m8V*rEuJ(mLMBr`F(4*kUM$Yb+gmVs1Hjp(VUWyRhb#BXe}
zvvrdea^DpxL^9jtm&mpVQro>uH@5YMy=pxMnH)`6;r|V!t~PyKTu?83S{P6(Q#Yr-
zoxCD6pZjvW@WFc8a&v*$f45%|5R`QbV^)%EvwNkI!mr}=;9p{Dj8DpFAZw}u`Jr=p
zIrUl#)tngHVyFTiI0LJUUIT3#i~gi+lD#YO_Aa90d-M0)9bbJ+<$w3t*KfX3UV%MW
z_bU7<|H|~1jT0o7ZE9JLwX_l6pu2}?VMI|+HX9p-$ZXdr_L{aL4+mF9@fUj8*XJ%~
z81VtZM0n2_Yq-x(e{^EFqgx7)F%8_+WB&2f1p!#JrAXg>H`}+F_a`kK5>-ogA?v3C
z$N{waCmjHH=~_^;C(jUIYviHh^F;70Pd`R=V7(S@SOr}Nq+8Azu_jjhrQrc8^!7Xz
zI9(6})ti|ZFCbblTx}>Q(!$ya@G5T@-yW-;Irtxec?*-G>Ak_mZ}5q_L#KtpJ+w15
z8D^>4?k3lV@!8hq1GRzMR$ZpWYKeoWh1(EkZi7P?+yqRd0Ao5q
zqY>cIJ%U%ut;sZle6JD{oJE%Z5inKRo457E_pP)HRkMn!Mn;`^xngI%>q+N>=}8>>)Yie!R}kl
zwyLIH09MPe0`~bVzx9J-#}V*xg7jo;kVQim_aDU2FMGXSHx31o#3-o=oxXVpUF~qU
zZicK_kfEKPfMD2*0lV!)?dM#hRB@1pQcV5XTL>@$#Q0&@JW0`JSH*>J+ZZM{Wf7H6MZVJxYmSP6SQa3xp#JWlr(F4(tEsR
z%x=vvFJBCxpJ+onU+HAdUTy3xbXXJ#F@9NDX=qS9uaS`HDm^JwI3hLjQ#5SiWW)>D
zylS(A7N`oD!EX2oxWmhSTTAGsWJ$8cFrG#JYPGxT*hsSt^7X^pCly1hG7j6i+3P-h
zX?*>*;w8Lg`A)>XyTjD#BQ5Jz#pPwX`uS3}o>PY}Z|pK>2c*3qd}=<5!f7erzH&lM
zmJNc%#v5sk#%(tB%07@kxc~tSzGc*H1;AK5&>E-VCy=|f=zfgyH=FXK
zw_irtHI5Ga{2Y^ru^J((Y0F&4REx;1+wzP;S!-lc?0>8-MHj6q(!YYgX^R4z`dl3x
zlSzM=<^AD!;iN>LTvXtY5~Mr2XIAQ8Y490em6c>+slHy)LsuARh}+XRmp^nJZu;5m
zW~7^A6g{~miTO5J^kfL;WdFT{?ckWkO{wH_-%Q=IeVM8&JRVB3&{YTzmbzN$G6oYr
zn(TJ#!=Bzlcg8{M*PLFuUO$>^L@yN6d#-Pl_+f%s4^&i@2h`+kX{QsK-IrzaKV!~-
zZm`MiS_qnib8LTZ;13{JCKj9|%OUOpl}=lM>kQ~uS_#L0_Q~?MW>j#QiF!yyeMRpi
zKSf)ho;4diUjd?e$W~?7l;aQ4;6E7dfk^2W26KC7G82)aq%F_Su84eO^wHZbnKaSx
z*3{u-n57bl;#7yOu1>?^&|wzYH_tQkR@3WgkV&JEF6a@7wx!5qK+`Pgo6!$%7XOan
z?8OTmO85Q-n4KL71b-P&70IN7&M*4KYIao9s+p^5A@|h9*m^YWuhpC3+Unt)#t@^9
zNEeO7+e8)KeQKjfoU3leg-4%YMAEF*4f^WW9VxTYn(0?vf7m$|NQT&Ut!IwC2wMu~
zxuu%uo~%^^NeBEDKYeWE8;a;j62DynUZcQcEhPmq%%C~VPmXf+cJPlV#^iKv8?R?D
z%SS!0jZe~Nx-c}bGVNX-eaOSXJ`3iriNDrk%Xl=Ak;NNxxqxx@GUdEB%T_Txt)4-u
z{u9CTGW>os;ym~4lG(+|s@L*IC;;np)jDZYbddKNOy;t{aDRB3wB64c8_eu%X1&F{
zYqMnUUqhn0m=Hi1a6;8DdjDMYC-}UHUU@TSeC@MpOf0XKVdUi`c6Vj)WML@$ea
z#;AX>UvZvt>rI=f$FI`0W~zV@EtM{$cbeug*OB!T4~H)s#K&gFVM`I6)Zn9~l>8-d
zT6NfXTx7iaY*U_evu#wZO;1E54E+RFi1%i@uL@I;<1NX@pO%0nI1{6OTi_6ss8Tmw
zUtdc@#ArsnPCIG-z>g-t&hrc-IavM&_cAs5EO$k#3t@)687xm;P{Yt&y&delW09*@
zX?%8G;rhCXX&9f2`A6W@aPVlsZ|TsE+{5i>@zleC;EHMyc9gpr;U<57=YxCwZ~f`X
zjPjYA>c-YxpbRP&!}NO3Lmco*c6#f~Ha&n;GeSQlzUMF5(dWP4Lgfce^6LC5Ylga~
z^zx{gQ@SyeU^0EZHx?)wSNfFz-n9NC}=i4acpSaFRt8!7vkrS$~kQ`v)C@474
zPyeY}FIptGlSeBV;8pqTjQ6l%H?Ii!D-_}o9D=m?HcZk&Zkqhi)I+?;iuVfWKCxEMxa(7q
zGILFYtwQk^c>chEB>!HTHLZd42UWsbG`(xvkfr{AF6Pz7!e;L>;V$T6sb#tK_G4I7
zKE=zw#FL%Iz!=z81mp7;m$3nAiUN#N;RJX5*ETVL5=JF?P|3B4RQ=SYwkzsk&X(UP
z(2)`!{Ni9L3)-h(S2Y0YMN}G<;tKYoBvtkE*11)Mwc2@s*V-Rttd`tooV1CW16odV
zlGr1w;GKZeb(&dFRede3L2?ItC#*6o@0vXlEzu}20T+s~S=@7a?5-z3|}%^)->+E_I_BVSEB?NW)G~CI$751&XTuA`8FJ*
zom`oCRz?w>zhq859-FEJw!g_TXA-?*v^GAnK8o{{A;)}v`E0gfbGaKX!E~ldJ!#8w
zcz=7Ud=DYMFUXu7ko%?qgDHH9zu(*J?|fX&wkaRJX1vx3|8EUFnSk+ezFUIJWf%;A
zjCi23{S>Kl1_ind8i!0a-)8!FfO7*IY`Ys(GqqCd873RmL+RB*v=-m~JxC
zG^~D$;n6Xot*@Mp=GTbVpjxok$DNKk0OOol0b{Umt|6DeiEtU$+;zmc=2D2U$89pI
z_1;jc!Y|j6Ijww`m4pFWytfr^B6=~o>QKJqdwH5Ao{@^>cn(QM@ONCV|0aETpR*5b
z-J)TciJ72xK7PaWn!{jQ?YW{jb{8ORDnZi9T6@!f=q1SKy$bfGxe!?uJY@+K_Rs%3?Pe`<)^F$C(TV<6pT=2ML;8RS~
zpU_iSVJoMbTs6Av+4xDvJ_{NdMXh1-fuW2zyRDVu?mAWrP|9>_4Th|&2M<#zqR>%8gO0P
zA%a1h!tR
zIv@0iwVYG9;yk?|&O4E{mPSz__emMmF9tI*!2M}|=wzy+1ub^Jxw>Odu*qtoR$xld
zb?9I-H^Rp}sv%!l^rMLYOVny}&-=ZFklp>ZYAa5R$S>FK<4h7C!q03yIk~{34F2{B
zt}Pnh#uTx^)e_4sHG#XbWeTIuT^GElZTx62V!vz{D+_q4DihL~L}{hF
z{=k3PlyG$fxCBqAyZ7nqG^fwiO^_kCZs0GI79-*Q^8I{Eb+w!x4_o$r3B6Pzv&jy6
zDwd7{SW-6%5-E87`Ih_in}39IgwsilAA@gWdy@4@)?>8CAz%VIYDxWh(VE2ccCk@@
z0^G`ryh0hb(ng(Lt~)Syj6PqpwQ$_L{Gh0xnk2c~PBM!0^6(nu4#-_}uL<;)WmErgpb@ofDJv%!F>W-m
zxX`wHbpEEc<7@wofWmp*{q@f$5}Ohjm#)HeH^&7tzjHYo#Iok%B2S;7RcFAgK(VFB
z><|)EZelx?2;N2UzcG6iuE~1#&MX;fP^=unG&i)nA4eEBm@EJZs~3q9ny^wqd{_X!+;Ue(lK%)1cV7}G$RK1+xI!1
z9s6TDj{O0aB2Iu(H+k27(L`Tj_&d7T8M6l_
zjFB~e_Y2=|O0SHRc!#xy<@ka{996J`DJ(4d$`+??t4_7|Gqw%}bumM_Aewm)tH`nT
zMSCyBbwGKjnN;}jtDu-THxpSpz6*|xFlsHkyteJ5n>mCC%|znoOJL#D1Q
zt)hJ@ZhD*H^o368x|s2Sd$TQ=$VmF}^W_V|;=j8#C(!e@$Fq!+IC-y(BtWJaZ^SEo
z9|@U2hZ1J88t`pOF7QPS8p!aC1kUQPTl?&b3i|41erL*F9V~inpFcRY_AAIr3D#yX
zDWH^biUjtvbiEhC)u$OGNto=)ftYP5U72TaAm0FzqIh
z{=f{*^f>4K!6JS=Y_9NFFE=rqAfEmQvTNZ!WYJ@*qT+TUE(~|=)EPzc&T{M@j;46n
z8UvCWOiBj>|Cx^D1$D-`XJ9>h_DBih!nQq%M_Y`tJLbyUZ#BO+Vgf4Vu$%F@bq6%K
z1p>=vAp{DHvlA!v|8+bKco0yl|4;-(UPT)ZV99?-_%-lPoboj2S}`JAKZ<2YYs}n=
z#QWlJiLB7s_eVmrWtz%cTPG#5EQ7UI_9a-JUr4aeFp~|{qEqPy_6zPW5=|$6+V(gq
zS4vP1Ydlao?v#KgNczuJ#93mz^3Gp
zP#df350;yqw2r@KV&R!wf>=G?^E6p>yn6+cyE0*H=E+OV^cDcMrwTEtdF3At5Hab_7NY45{&*#8V0d9K_{
zPSOmpUHteZzxqSKmztGu^(WjQ?R1Fj?~h
z9_dFye*slavP|;DM(Z&JLhv@GVJ8yj$I{qf(+OPpRXPuR$n*@q_@D$|h#R8w=Z(Zc
zLSohj(rA5Ia~rCQL26io_9Fq!@U#-#e>g%h%5T!!J1z{q<1CS-Mf7J+2kI|6y+xpENM|L>E+^90b
zx&HDUzR<8jxbA1h{l3p&B5wlXLQ#XqX&rdkY5|v8PrVHuJwJ5A@`;%Szy6y$-Qu@c
zqi5MEqdn066zw4Ayv90@Jz#095q`_niv7hW>9Q=Q+R?bkq6Dons-?;Mg}27U>3V6&
zh8(6*0`%<5Uw?pp$*Jk92*&Y0PWIJ!g_l~bAU)7k{$BP|EaK$*3$|_N4}XgBMI8#C
zet+7N#=2}ItXAi4tO*c`wuK2VHaPTIQO`8jX0zrg&Zoo&JfzUScuEBQTM0ttX9FhG
z=n@lp0x6yNhpg-g{QWt`wVn7>hsRpe4#u^U-XVlD49G7mNcSmhiw24gOj>DrA2z#P
z%|gc?knc5Woyl(%6}!Q8c&KpbR|lgdTC&RZ%_x`8!d9QK+|{o!Z_=bc7N9qqZuHqK
zJwhIY=UWlVT)*mrn)6aHC+ld2{WEb|_4(Zkb~l}ks``44kAK!1j-XY4>fxbTLEg8)
zDXycA%LI$fwe3asaT3cozuLJ&N}!7B3S8SQ4;PQYGpobd{xSIi1PwY8YssndP>(THV2n(@?zZ-%aD!0U(~aK;@t!WS1mby}O;q`CNj~
zONe;RearY*%pHOa(g3!W}!hfYw4FeLuzd*m~Mu0zRxwp0k84+
z+5J&rGrS^(=s$|?L5DMv0^=Q)sn*dO{%g`EP1Klair`3
zxu|S;`pWG(5VNE)AmAJ-MfQ=SSK!m#(LWqAlSRJ<@DkYJs^ZW%#WnH8GUPt5Co0Jc
zb)J(Q^wsanW@q3w6FfWiWqx0bBXGqv#@HrFaWm1{t2R_M9%zyS^=WF|Y$J#6R6AB+
zhX$c)rB@=WMeLLO>c9B!8U)M!?o=)hSAA@~-mV@KggRpJ;uTP7U!K>PRm|xF^_55?
z<;$&>i+$@67)5Ez#sac_ytE-K1W9u^>~nnQdkW@xa`!bBFvRO5{Ij)=0)PR%-LY^Y
znoV~N88GJ9>!@BY-Gv+U$fc0-tv*-t=U`)}OroVuvS;2!E=8GNAi>qD5$pO&=jrVS
z_YtUyuvVc{|61rfloD5_Q_y@=Q#S2c4rWG;2qLfb^T3h^C|)D
z!n}lB5)o@?SqXN(6x^S;qH`m$#
zlxT=eOWtRrKJ|TV1-b&w*?Y0HgW>;&CLWft_
z^!3P5rD8j?@DPYfZ)x#sk&&Zo0$9Q|EWc~&UvR;_1GxW)9ERn48Y?8X3F=>!WB
z*LwxxzRrdpV}f>$x^1C_r&$5r6iD+40jYR6OZ-ne(dd||@^!yg`K9WmVHumcR}BPN
zXdbcvc+3%Ks}786cmq#@JW{gvB>Sskb~dzCy^-H=!+mtb?{mgDXQChqDz`BqMmP0~t-swy^paD4aKL5C&WOBA
ze*aeX!mBp_Bkd?~33BE!P1K-uWw$in6~RA1XHW!+}lYnO^7Y%7Z2oA$-{e>iS7kZ;9^H0QYkC9_=4
zkldC)2u$cp*>9M0Xntz}+iGjIb_R1p+4n6MlxyV*?VNqyqnzzCWMCVnD}~)rqEaoC
zp!Fzoe<|wsKO8Xuq!RQW≷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,
+ [ {<<".*">>,
     %% load all plugins
     %% NOTE: this depends on the fact that emqx_dashboard is always
     %% the last application gets upgraded
     [ {apply, {emqx_rule_engine, load_providers, []}}
     , {restart_application, emqx_dashboard}
     , {apply, {emqx_plugins, load, []}}
-    ]},
-   {<<".*">>, []}
+    ]}
  ],
- [ {<<"4.3.[0-2]">>,
+ [ {<<".*">>,
     [ {apply, {emqx_rule_engine, load_providers, []}}
     , {restart_application, emqx_dashboard}
     , {apply, {emqx_plugins, load, []}}
-    ]},
-   {<<".*">>, []}
+    ]}
  ]
 }.
diff --git a/lib-ce/emqx_dashboard/src/emqx_dashboard_admin.erl b/lib-ce/emqx_dashboard/src/emqx_dashboard_admin.erl
index c70308744a..94c5c3cdaf 100644
--- a/lib-ce/emqx_dashboard/src/emqx_dashboard_admin.erl
+++ b/lib-ce/emqx_dashboard/src/emqx_dashboard_admin.erl
@@ -168,10 +168,10 @@ check(Username, Password) ->
         [#mqtt_admin{password = <>}] ->
             case Hash =:= md5_hash(Salt, Password) of
                 true  -> ok;
-                false -> {error, <<"Password Error">>}
+                false -> {error, <<"Username/Password error">>}
             end;
         [] ->
-            {error, <<"Username Not Found">>}
+            {error, <<"Username/Password error">>}
     end.
 
 %%--------------------------------------------------------------------
diff --git a/lib-ce/emqx_dashboard/test/emqx_dashboard_SUITE.erl b/lib-ce/emqx_dashboard/test/emqx_dashboard_SUITE.erl
index 4a8ca73114..ef2e747fa7 100644
--- a/lib-ce/emqx_dashboard/test/emqx_dashboard_SUITE.erl
+++ b/lib-ce/emqx_dashboard/test/emqx_dashboard_SUITE.erl
@@ -49,7 +49,7 @@ all() ->
 groups() ->
     [{overview, [sequence], [t_overview]},
      {admins, [sequence], [t_admins_add_delete]},
-     {rest, [sequence], [t_rest_api]},
+     {rest, [sequence], [t_rest_api, t_auth_exhaustive_attack]},
      {cli, [sequence], [t_cli]}
     ].
 
@@ -98,6 +98,11 @@ t_rest_api(_Config) ->
              ]],
     ok.
 
+t_auth_exhaustive_attack(_Config) ->
+    {ok, Res0} = http_post("auth", #{<<"username">> => <<"invalid_login">>, <<"password">> => <<"newpwd">>}),
+    {ok, Res1} = http_post("auth", #{<<"username">> => <<"admin">>, <<"password">> => <<"invalid_password">>}),
+    ?assertEqual(Res0, Res1).
+
 t_cli(_Config) ->
     [mnesia:dirty_delete({mqtt_admin, Admin}) ||  Admin <- mnesia:dirty_all_keys(mqtt_admin)],
     emqx_dashboard_cli:admins(["add", "username", "password"]),
diff --git a/priv/emqx.schema b/priv/emqx.schema
index 904c99ec86..86a2a2892c 100644
--- a/priv/emqx.schema
+++ b/priv/emqx.schema
@@ -496,13 +496,13 @@ end}.
 %% @doc Maximum depth in Erlang term log formatting
 %% and message queue inspection.
 {mapping, "log.max_depth", "kernel.error_logger_format_depth", [
-  {default, 20},
+  {default, 100},
   {datatype, [{enum, [unlimited]}, integer]}
 ]}.
 
 %% @doc format logs as JSON objects
 {mapping, "log.formatter", "kernel.logger", [
-  {default, json},
+  {default, text},
   {datatype, {enum, [text, json]}}
 ]}.
 
diff --git a/rebar.config b/rebar.config
index d9d7abec24..3424c5e54c 100644
--- a/rebar.config
+++ b/rebar.config
@@ -37,42 +37,28 @@
 
 {deps,
     [
-        {getopt, {git, "https://gitee.com/fastdgiot/getopt.git", {tag, "v1.0.2"}}}
-        , {grpc, {git, "https://gitee.com/fastdgiot/grpc-erl", {tag, "0.6.4"}}}
-        , {grpc_plugin, {git, "https://gitee.com/fastdgiot/grpc_plugin.git", {tag, "v0.10.4"}}}
-        , {recon, {git, "https://gitee.com/fastdgiot/recon.git", {tag, "2.5.1"}}}
-        , {rebar3_proper, {git, "https://gitee.com/fastdgiot/rebar3_proper.git", {tag, "0.12.1"}}}
-        , {gpb, {git, "https://gitee.com/fastdgiot/gpb", {tag, "4.17.6"}}}
+        {gpb, "4.11.2"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps
+        , {ehttpc, {git, "https://gitee.com/fastdgiot/ehttpc", {tag, "0.1.14"}}}
+        , {eredis_cluster, {git, "https://gitee.com/fastdgiot/eredis_cluster", {tag, "0.6.5"}}}
+        , {gproc, {git, "https://gitee.com/fastdgiot/gproc", {tag, "0.8.0"}}}
+        , {jiffy, {git, "https://gitee.com/fastdgiot/jiffy", {tag, "1.0.5"}}}
+        , {cowboy, {git, "https://gitee.com/fastdgiot/cowboy", {tag, "2.8.2"}}}
+        , {esockd, {git, "https://gitee.com/fastdgiot/esockd", {tag, "5.8.0"}}}
+        , {ekka, {git, "https://gitee.com/fastdgiot/ekka", {tag, "0.8.1.7"}}}
+        , {gen_rpc, {git, "https://gitee.com/fastdgiot/gen_rpc", {tag, "2.5.1"}}}
         , {cuttlefish, {git, "https://gitee.com/fastdgiot/cuttlefish", {tag, "v4.3.4"}}}
-        , {cowboy, {git, "https://gitee.com/fastdgiot/cowboy", {tag, "2.8.3"}}}
-        , {minirest, {git, "https://gitee.com/fastdgiot/minirest", {tag, "0.3.5"}}}
-        , {ecpool, {git, "https://gitee.com/fastdgiot/ecpool", {tag, "0.5.1"}}}
+        , {minirest, {git, "https://gitee.com/fastdgiot/minirest", {tag, "0.3.7"}}}
+        , {ecpool, {git, "https://gitee.com/fastdgiot/ecpool", {tag, "0.5.2"}}}
         , {replayq, {git, "https://gitee.com/fastdgiot/replayq", {tag, "0.3.2"}}}
         , {pbkdf2, {git, "https://gitee.com/fastdgiot/erlang-pbkdf2.git", {branch, "2.0.4"}}}
-        , {emqtt, {git, "https://gitee.com/fastdgiot/emqtt", {tag, "1.2.3"}}}
+        , {emqtt, {git, "https://gitee.com/fastdgiot/emqtt", {tag, "1.2.3.1"}}}
         , {rulesql, {git, "https://gitee.com/fastdgiot/rulesql", {tag, "0.1.2"}}}
         , {recon, {git, "https://gitee.com/fastdgiot/recon", {tag, "2.5.1"}}}
-        , {ranch, {git, "https://gitee.com/fastdgiot/ranch", {tag, "1.7.1"}}}
-        , {gproc, {git, "https://gitee.com/fastdgiot/gproc", {tag, "0.8.0"}}}
-        , {gen_rpc, {git, "https://gitee.com/fastdgiot/gen_rpc", {tag, "2.5.1"}}}
-        , {gun, {git, "https://gitee.com/fastdgiot/gun", {tag, "1.3.5"}}}
-        , {ssl_verify_fun, {git, "https://gitee.com/fastdgiot/ssl_verify_fun.erl.git", {tag, "1.1.4"}}}
-        , {jiffy, {git, "https://gitee.com/fastdgiot/jiffy", {tag, "1.0.5"}}}
-        , {esockd, {git, "https://gitee.com/fastdgiot/esockd", {tag, "5.8.0"}}}
-        , {ekka, {git, "https://gitee.com/fastdgiot/ekka", {tag, "0.8.1"}}}
-        , {ehttpc, {git, "https://gitee.com/fastdgiot/ehttpc", {tag, "0.1.5"}}}
-        , {erlydtl, {git, "https://gitee.com/fastdgiot/erlydtl.git", {tag, "0.12.1"}}}
-        , {erlport, {git, "https://gitee.com/fastdgiot/erlport", {tag, "v1.2.2"}}}
-        , {poolboy, {git, "https://gitee.com/fastdgiot/poolboy.git", {tag, "1.5.3"}}}
-        , {websocket_client, {git, "https://gitee.com/fastdgiot/websocket_client", {tag, "v0.7"}}}
         , {observer_cli, "1.6.1"} % NOTE: depends on recon 2.5.1
-        , {ejdbc, {git, "https://gitee.com/fastdgiot/ejdbc", {tag, "1.0.1"}}}
+        , {getopt, "1.0.1"}
         , {snabbkaffe, {git, "https://gitee.com/fastdgiot/snabbkaffe.git", {tag, "0.12.2"}}}
-        , {cowlib, {git, "https://gitee.com/fastdgiot/cowlib",{tag,"2.8.0"}}}
-        , {ibrowse, {git, "https://gitee.com/fastdgiot/ibrowse.git", {tag, "v4.4.2"}}}
-        , {jesse, {git, "https://gitee.com/fastdgiot/jesse.git", {tag, "1.6.1"}}}
-        , {jwerl, {git, "https://gitee.com/fastdgiot/jwerl.git", {tag, "1.1.1"}}}
-        , {locus, {git, "https://gitee.com/fastdgiot/locus.git", {tag, "2.2.2"}}}
+        , {ejdbc, {git, "https://gitee.com/fastdgiot/ejdbc", {tag, "1.0.1"}}}
+        , {gun, {git, "https://gitee.com/fastdgiot/gun", {tag, "1.3.5"}}}
     ]}.
 
 {xref_ignores,
diff --git a/rebar.config.erl b/rebar.config.erl
index 178e73cef5..65e509b739 100644
--- a/rebar.config.erl
+++ b/rebar.config.erl
@@ -200,6 +200,7 @@ overlay_vars_rel(RelType) ->
         , {enable_plugin_emqx_retainer, true}
         , {enable_plugin_emqx_telemetry, true}
         , {enable_plugin_emqx_exhook, true}
+       , {enable_plugin_emqx_auth_mnesia, true}
         , {enable_plugin_dgiot, true}
         , {enable_plugin_dgiot_bridge, true}
         , {enable_plugin_dgiot_parse, true}
@@ -322,6 +323,7 @@ relx_plugin_apps(ReleaseType) ->
         , emqx_rule_engine
         , emqx_sasl
         , emqx_exhook
+       , emqx_auth_mnesia
     ]
     ++ [emqx_telemetry || not is_enterprise()]
         ++ relx_plugin_apps_per_rel(ReleaseType)
@@ -335,6 +337,7 @@ relx_plugin_apps_per_rel(cloud) ->
         , emqx_exproto
         , emqx_prometheus
         , emqx_psk_file
+        , emqx_auth_mnesia
         , dgiot
         , dgiot_parse
         , dgiot_api
diff --git a/scripts/get-distro.sh b/scripts/get-distro.sh
new file mode 100644
index 0000000000..ae52abba33
--- /dev/null
+++ b/scripts/get-distro.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+## This script prints Linux distro name and its version number
+## e.g. macos, centos8, ubuntu20.04
+
+set -euo pipefail
+
+if [ "$(uname -s)" = 'Darwin' ]; then
+	echo 'macos'
+elif [ "$(uname -s)" = 'Linux' ]; then
+    if grep -q -i 'centos' /etc/*-release; then
+        DIST='centos'
+        VERSION_ID="$(rpm --eval '%{centos_ver}')"
+    else
+        DIST="$(sed -n '/^ID=/p' /etc/os-release | sed -r 's/ID=(.*)/\1/g' | sed 's/"//g')"
+        VERSION_ID="$(sed -n '/^VERSION_ID=/p' /etc/os-release | sed -r 's/VERSION_ID=(.*)/\1/g' | sed 's/"//g')"
+    fi
+    echo "${DIST}${VERSION_ID}" | sed -r 's/([a-zA-Z]*)-.*/\1/g'
+fi
diff --git a/scripts/one-more-emqx-ee.sh b/scripts/one-more-emqx-ee.sh
new file mode 100644
index 0000000000..f946810564
--- /dev/null
+++ b/scripts/one-more-emqx-ee.sh
@@ -0,0 +1,106 @@
+#!/bin/bash
+# shellcheck disable=2090
+###############
+## args and env validation
+###############
+
+if ! [ -d "emqx" ]; then
+  echo "[error] this script must be run at the same dir as the emqx"
+  exit 1
+fi
+
+if [ $# -eq 0 ]
+  then
+    echo "[error] a new emqx name should be provided!"
+    echo "Usage: ./one_more_emqx "
+    echo "  e.g. ./one_more_emqx emqx2"
+    exit 1
+fi
+
+NEW_EMQX=$1
+if [ -d "$NEW_EMQX" ]; then
+  echo "[error] a dir named ${NEW_EMQX} already exists!"
+  exit 2
+fi
+echo creating "$NEW_EMQX" ...
+
+SED_REPLACE="sed -i "
+# shellcheck disable=2089
+case $(sed --help 2>&1) in
+    *GNU*) SED_REPLACE="sed -i ";;
+    *) SED_REPLACE="sed -i ''";;
+esac
+
+PORT_INC_=$(cksum <<< "$NEW_EMQX" | cut -f 1 -d ' ')
+PORT_INC=$((PORT_INC_ % 1000))
+echo using increment factor: $PORT_INC
+
+###############
+## helpers
+###############
+process_emqx_conf() {
+  echo "processing config file: $1"
+  $SED_REPLACE '/^#/d' "$1"
+  $SED_REPLACE '/^$/d' "$1"
+
+  for entry_ in "${entries_to_be_inc[@]}"
+  do
+    echo inc port for "$entry_"
+    ip_port_=$(grep -E "$entry_"'[ \t]*=' "$1" 2> /dev/null | tail -1 | cut -d = -f 2- | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+    echo -- from: "$ip_port_"
+    ip_=$(echo "$ip_port_" | cut -sd : -f 1)
+    port_=$(echo "$ip_port_" | cut -sd : -f 2)
+    if [ -z "$ip_" ]
+      then
+        new_ip_port=$(( ip_port_ + PORT_INC ))
+      else
+        new_ip_port="${ip_}:$(( port_ + PORT_INC ))"
+    fi
+    echo -- to: "$new_ip_port"
+    $SED_REPLACE 's|'"$entry_"'[ \t]*=.*|'"$entry_"' = '"$new_ip_port"'|g' "$1"
+  done
+}
+
+###############
+## main
+###############
+
+cp -r emqx "$NEW_EMQX"
+
+## change the rpc ports
+$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5369|g' emqx/etc/rpc.conf
+$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5370|g' emqx/etc/rpc.conf
+$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5369|g' "$NEW_EMQX/etc/rpc.conf"
+$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5370|g' "$NEW_EMQX/etc/rpc.conf"
+$SED_REPLACE 's|.*node\.name.*|node.name='"$NEW_EMQX"'@127.0.0.1|g' "$NEW_EMQX/etc/emqx.conf"
+
+conf_ext="*.conf"
+
+find "$NEW_EMQX" -name "${conf_ext}" | while read -r conf; do
+    if [ "${conf##*/}" = 'emqx.conf' ]
+      then
+        declare -a entries_to_be_inc=("node.dist_listen_min"
+                                      "node.dist_listen_max")
+        process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
+    elif [ "${conf##*/}" = 'listeners.conf' ]
+      then
+        declare -a entries_to_be_inc=("listener.tcp.external"
+                                      "listener.tcp.internal"
+                                      "listener.ssl.external"
+                                      "listener.ws.external"
+                                      "listener.wss.external")
+        process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
+    elif [ "${conf##*/}" = 'emqx_management.conf' ]
+      then
+        declare -a entries_to_be_inc=("management.listener.http"
+                                      "management.listener.https")
+        process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
+    elif [ "${conf##*/}" = 'emqx_dashboard.conf' ]
+      then
+        declare -a entries_to_be_inc=("dashboard.listener.http"
+                                      "dashboard.listener.https")
+        process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
+    else
+        echo "."
+    fi
+done
diff --git a/scripts/one-more-emqx.sh b/scripts/one-more-emqx.sh
new file mode 100644
index 0000000000..d905f64c46
--- /dev/null
+++ b/scripts/one-more-emqx.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+# shellcheck disable=2090
+###############
+## args and env validation
+###############
+
+if ! [ -d "emqx" ]; then
+  echo "[error] this script must be run at the same dir as the emqx"
+  exit 1
+fi
+
+if [ $# -eq 0 ]
+  then
+    echo "[error] a new emqx name should be provided!"
+    echo "Usage: ./one_more_emqx "
+    echo "  e.g. ./one_more_emqx emqx2"
+    exit 1
+fi
+
+NEW_EMQX=$1
+if [ -d "$NEW_EMQX" ]; then
+  echo "[error] a dir named ${NEW_EMQX} already exists!"
+  exit 2
+fi
+echo creating "$NEW_EMQX" ...
+
+SED_REPLACE="sed -i "
+# shellcheck disable=2089
+case $(sed --help 2>&1) in
+    *GNU*) SED_REPLACE="sed -i ";;
+    *) SED_REPLACE="sed -i ''";;
+esac
+
+PORT_INC_=$(cksum <<< "$NEW_EMQX" | cut -f 1 -d ' ')
+PORT_INC=$((PORT_INC_ % 1000))
+echo using increment factor: "$PORT_INC"
+
+###############
+## helpers
+###############
+process_emqx_conf() {
+  echo "processing config file: $1"
+  $SED_REPLACE '/^#/d' "$1"
+  $SED_REPLACE '/^$/d' "$1"
+  $SED_REPLACE 's|.*node\.name.*|node.name='"$NEW_EMQX"'@127.0.0.1|g' "$1"
+
+  for entry_ in "${entries_to_be_inc[@]}"
+  do
+    echo inc port for "$entry_"
+    ip_port_=$(grep -E "$entry_"'[ \t]*=' "$1" 2> /dev/null | tail -1 | cut -d = -f 2- | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
+    echo -- from: "$ip_port_"
+    ip_=$(echo "$ip_port_" | cut -sd : -f 1)
+    port_=$(echo "$ip_port_" | cut -sd : -f 2)
+    if [ -z "$ip_" ]
+      then
+        new_ip_port=$(( ip_port_ + PORT_INC ))
+      else
+        new_ip_port="${ip_}:$(( port_ + PORT_INC ))"
+    fi
+    echo -- to: "$new_ip_port"
+    $SED_REPLACE 's|'"$entry_"'[ \t]*=.*|'"$entry_"' = '"$new_ip_port"'|g' "$1"
+  done
+}
+
+###############
+## main
+###############
+
+cp -r emqx "$NEW_EMQX"
+
+## change the rpc ports
+$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5369|g' emqx/etc/emqx.conf
+$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5370|g' emqx/etc/emqx.conf
+$SED_REPLACE 's|tcp_client_port[ \t]*=.*|tcp_client_port = 5369|g' "$NEW_EMQX/etc/emqx.conf"
+$SED_REPLACE 's|tcp_server_port[ \t]*=.*|tcp_server_port = 5370|g' "$NEW_EMQX/etc/emqx.conf"
+
+conf_ext="*.conf"
+find "$NEW_EMQX" -name "${conf_ext}" | while read -r conf; do
+    if [ "${conf##*/}" = 'emqx.conf' ]
+      then
+        declare -a entries_to_be_inc=("node.dist_listen_min"
+                                      "dist_listen_max"
+                                      "listener.tcp.external"
+                                      "listener.tcp.internal"
+                                      "listener.ssl.external"
+                                      "listener.ws.external"
+                                      "listener.wss.external")
+        process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
+    elif [ "${conf##*/}" = 'emqx_management.conf' ]
+      then
+        declare -a entries_to_be_inc=("management.listener.http"
+                                      "management.listener.https")
+        process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
+    elif [ "${conf##*/}" = 'emqx_dashboard.conf' ]
+      then
+        declare -a entries_to_be_inc=("dashboard.listener.http"
+                                      "dashboard.listener.https")
+        process_emqx_conf "$conf" "${entries_to_be_inc[@]}"
+    else
+        echo "."
+    fi
+done
diff --git a/scripts/update_appup.escript b/scripts/update_appup.escript
index 6a78012d2a..7674a941c1 100644
--- a/scripts/update_appup.escript
+++ b/scripts/update_appup.escript
@@ -1,75 +1,531 @@
 #!/usr/bin/env -S escript -c
-%% A script that adds changed modules to the corresponding appup files
-
-main(_Args) ->
-    ChangedFiles = string:lexemes(os:cmd("git diff --name-only origin/master..HEAD"), "\n"),
-    AppModules0 = lists:filtermap(fun filter_erlang_modules/1, ChangedFiles),
-    %% emqx_app must always be included as we bump version number in emqx_release.hrl for each release
-    AppModules1 = [{emqx, emqx_app} | AppModules0],
-    AppModules = group_modules(AppModules1),
-    io:format("Changed modules: ~p~n", [AppModules]),
-    _ = maps:map(fun process_app/2, AppModules),
-    ok.
-
-process_app(App, Modules) ->
-    AppupFiles = filelib:wildcard(lists:concat(["{src,apps,lib-*}/**/", App, ".appup.src"])),
-    case AppupFiles of
-        [AppupFile] ->
-          update_appup(AppupFile, Modules);
-        []          ->
-          io:format("~nWARNING: Please create an stub appup src file for ~p~n", [App])
+%% -*- erlang-indent-level:4 -*-
+
+usage() ->
+"A script that fills in boilerplate for appup files.
+
+Algorithm: this script compares md5s of beam files of each
+application, and creates a `{load_module, Module, brutal_purge,
+soft_purge, []}` action for the changed and new modules. For deleted
+modules it creates `{delete_module, M}` action. These entries are
+added to each patch release preceding the current release. If an entry
+for a module already exists, this module is ignored. The existing
+actions are kept.
+
+Please note that it only compares the current release with its
+predecessor, assuming that the upgrade actions for the older releases
+are correct.
+
+Note: The defaults are set up for emqx, but they can be tuned to
+support other repos too.
+
+Usage:
+
+   update_appup.escript [--check] [--repo URL] [--remote NAME] [--skip-build] [--make-commad SCRIPT] [--release-dir DIR] 
+
+Options:
+
+  --check           Don't update the appfile, just check that they are complete
+  --repo            Upsteam git repo URL
+  --remote          Get upstream repo URL from the specified git remote
+  --skip-build      Don't rebuild the releases. May produce wrong results
+  --make-command    A command used to assemble the release
+  --release-dir     Release directory
+  --src-dirs        Directories where source code is found. Defaults to '{src,apps,lib-*}/**/'
+  --binary-rel-url  Binary release URL pattern. %TAG% variable is substituted with the release tag.
+                    E.g. \"https://github.com/emqx/emqx/releases/download/v%TAG%/emqx-centos7-%TAG%-amd64.zip\"
+".
+
+-record(app,
+        { modules       :: #{module() => binary()}
+        , version       :: string()
+        }).
+
+default_options() ->
+    #{ clone_url      => find_upstream_repo("origin")
+     , make_command   => "make emqx-rel"
+     , beams_dir      => "_build/emqx/rel/emqx/lib/"
+     , check          => false
+     , prev_tag       => undefined
+     , src_dirs       => "{src,apps,lib-*}/**/"
+     , binary_rel_url => undefined
+     }.
+
+%% App-specific actions that should be added unconditionally to any update/downgrade:
+app_specific_actions(_) ->
+    [].
+
+ignored_apps() ->
+    [emqx_dashboard, emqx_management] ++ otp_standard_apps().
+
+main(Args) ->
+    #{prev_tag := Baseline} = Options = parse_args(Args, default_options()),
+    init_globals(Options),
+    main(Options, Baseline).
+
+parse_args([PrevTag = [A|_]], State) when A =/= $- ->
+    State#{prev_tag => PrevTag};
+parse_args(["--check"|Rest], State) ->
+    parse_args(Rest, State#{check => true});
+parse_args(["--skip-build"|Rest], State) ->
+    parse_args(Rest, State#{make_command => "true"});
+parse_args(["--repo", Repo|Rest], State) ->
+    parse_args(Rest, State#{clone_url => Repo});
+parse_args(["--remote", Remote|Rest], State) ->
+    parse_args(Rest, State#{clone_url => find_upstream_repo(Remote)});
+parse_args(["--make-command", Command|Rest], State) ->
+    parse_args(Rest, State#{make_command => Command});
+parse_args(["--release-dir", Dir|Rest], State) ->
+    parse_args(Rest, State#{beams_dir => Dir});
+parse_args(["--src-dirs", Pattern|Rest], State) ->
+    parse_args(Rest, State#{src_dirs => Pattern});
+parse_args(["--binary-rel-url", URL|Rest], State) ->
+    parse_args(Rest, State#{binary_rel_url => {ok, URL}});
+parse_args(_, _) ->
+    fail(usage()).
+
+main(Options, Baseline) ->
+    {CurrRelDir, PrevRelDir} = prepare(Baseline, Options),
+    log("~n===================================~n"
+        "Processing changes..."
+        "~n===================================~n"),
+    CurrAppsIdx = index_apps(CurrRelDir),
+    PrevAppsIdx = index_apps(PrevRelDir),
+    %% log("Curr: ~p~nPrev: ~p~n", [CurrAppsIdx, PrevAppsIdx]),
+    AppupChanges = find_appup_actions(CurrAppsIdx, PrevAppsIdx),
+    case getopt(check) of
+        true ->
+            case AppupChanges of
+                [] ->
+                    ok;
+                _ ->
+                    Diffs =
+                        lists:filtermap(
+                          fun({App, {Upgrade, Downgrade, OldUpgrade, OldDowngrade}}) ->
+                                  case parse_appup_diffs(Upgrade, OldUpgrade,
+                                                         Downgrade, OldDowngrade) of
+                                      ok ->
+                                          false;
+                                      {diffs, Diffs} ->
+                                          {true, {App, Diffs}}
+                                  end
+                          end,
+                          AppupChanges),
+                    case Diffs =:= [] of
+                        true ->
+                            ok;
+                        false ->
+                            set_invalid(),
+                            log("ERROR: The appup files are incomplete. Missing changes:~n   ~p",
+                                [Diffs])
+                    end
+            end;
+        false ->
+            update_appups(AppupChanges)
+    end,
+    check_appup_files(),
+    warn_and_exit(is_valid()).
+
+warn_and_exit(true) ->
+    log("
+NOTE: Please review the changes manually. This script does not know about NIF
+changes, supervisor changes, process restarts and so on. Also the load order of
+the beam files might need updating.~n"),
+    halt(0);
+warn_and_exit(false) ->
+    log("~nERROR: Incomplete appups found. Please inspect the output for more details.~n"),
+    halt(1).
+
+prepare(Baseline, Options = #{make_command := MakeCommand, beams_dir := BeamDir, binary_rel_url := BinRel}) ->
+    log("~n===================================~n"
+        "Baseline: ~s"
+        "~n===================================~n", [Baseline]),
+    log("Building the current version...~n"),
+    bash(MakeCommand),
+    log("Downloading and building the previous release...~n"),
+    PrevRelDir =
+        case BinRel of
+            undefined ->
+                {ok, PrevRootDir} = build_prev_release(Baseline, Options),
+                filename:join(PrevRootDir, BeamDir);
+            {ok, _URL} ->
+                {ok, PrevRootDir} = download_prev_release(Baseline, Options),
+                PrevRootDir
+        end,
+    {BeamDir, PrevRelDir}.
+
+build_prev_release(Baseline, #{clone_url := Repo, make_command := MakeCommand}) ->
+    BaseDir = "/tmp/emqx-baseline/",
+    Dir = filename:basename(Repo, ".git") ++ [$-|Baseline],
+    %% TODO: shallow clone
+    Script = "mkdir -p ${BASEDIR} &&
+              cd ${BASEDIR} &&
+              { [ -d ${DIR} ] || git clone --branch ${TAG} ${REPO} ${DIR}; } &&
+              cd ${DIR} &&" ++ MakeCommand,
+    Env = [{"REPO", Repo}, {"TAG", Baseline}, {"BASEDIR", BaseDir}, {"DIR", Dir}],
+    bash(Script, Env),
+    {ok, filename:join(BaseDir, Dir)}.
+
+download_prev_release(Tag, #{binary_rel_url := {ok, URL0}, clone_url := Repo}) ->
+    URL = string:replace(URL0, "%TAG%", Tag, all),
+    BaseDir = "/tmp/emqx-baseline-bin/",
+    Dir = filename:basename(Repo, ".git") ++ [$-|Tag],
+    Filename = filename:join(BaseDir, Dir),
+    Script = "mkdir -p ${OUTFILE} &&
+              wget -c -O ${OUTFILE}.zip ${URL} &&
+              unzip -n -d ${OUTFILE} ${OUTFILE}.zip",
+    Env = [{"TAG", Tag}, {"OUTFILE", Filename}, {"URL", URL}],
+    bash(Script, Env),
+    {ok, Filename}.
+
+find_upstream_repo(Remote) ->
+    string:trim(os:cmd("git remote get-url " ++ Remote)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Appup action creation and updating
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+find_appup_actions(CurrApps, PrevApps) ->
+    maps:fold(
+      fun(App, CurrAppIdx, Acc) ->
+              case PrevApps of
+                  #{App := PrevAppIdx} -> find_appup_actions(App, CurrAppIdx, PrevAppIdx) ++ Acc;
+                  _                    -> Acc %% New app, nothing to upgrade here.
+              end
+      end,
+      [],
+      CurrApps).
+
+find_appup_actions(_App, AppIdx, AppIdx) ->
+    %% No changes to the app, ignore:
+    [];
+find_appup_actions(App, CurrAppIdx, PrevAppIdx = #app{version = PrevVersion}) ->
+    {OldUpgrade, OldDowngrade} = find_old_appup_actions(App, PrevVersion),
+    Upgrade = merge_update_actions(App, diff_app(App, CurrAppIdx, PrevAppIdx), OldUpgrade),
+    Downgrade = merge_update_actions(App, diff_app(App, PrevAppIdx, CurrAppIdx), OldDowngrade),
+    if OldUpgrade =:= Upgrade andalso OldDowngrade =:= Downgrade ->
+            %% The appup file has been already updated:
+            [];
+       true ->
+            [{App, {Upgrade, Downgrade, OldUpgrade, OldDowngrade}}]
     end.
 
-filter_erlang_modules(Filename) ->
-    case lists:reverse(filename:split(Filename)) of
-        [Module, "src"] ->
-            erl_basename("emqx", Module);
-        [Module, "src", App|_] ->
-            erl_basename(App, Module);
-        [Module, _, "src", App|_] ->
-            erl_basename(App, Module);
+%% For external dependencies, show only the changes that are missing
+%% in their current appup.
+diff_appup_instructions(ComputedChanges, PresentChanges) ->
+    lists:foldr(
+      fun({Vsn, ComputedActions}, Acc) ->
+              case find_matching_version(Vsn, PresentChanges) of
+                  undefined ->
+                      [{Vsn, ComputedActions} | Acc];
+                  PresentActions ->
+                      DiffActions = ComputedActions -- PresentActions,
+                      case DiffActions of
+                          [] ->
+                              %% no diff
+                              Acc;
+                          _ ->
+                              [{Vsn, DiffActions} | Acc]
+                      end
+              end
+      end,
+      [],
+      ComputedChanges).
+
+%% For external dependencies, checks if any missing diffs are present
+%% and groups them by `up' and `down' types.
+parse_appup_diffs(Upgrade, OldUpgrade, Downgrade, OldDowngrade) ->
+    DiffUp = diff_appup_instructions(Upgrade, OldUpgrade),
+    DiffDown = diff_appup_instructions(Downgrade, OldDowngrade),
+    case {DiffUp, DiffDown} of
+        {[], []} ->
+            %% no diff for external dependency; ignore
+            ok;
         _ ->
-            false
+            set_invalid(),
+            Diffs = #{ up => DiffUp
+                     , down => DiffDown
+                     },
+            {diffs, Diffs}
+    end.
+
+%% TODO: handle regexes
+find_matching_version(Vsn, PresentChanges) ->
+    proplists:get_value(Vsn, PresentChanges).
+
+find_old_appup_actions(App, PrevVersion) ->
+    {Upgrade0, Downgrade0} =
+        case locate(ebin_current, App, ".appup") of
+            {ok, AppupFile} ->
+                log("Found the previous appup file: ~s~n", [AppupFile]),
+                {_, U, D} = read_appup(AppupFile),
+                {U, D};
+            undefined ->
+                %% Fallback to the app.src file, in case the
+                %% application doesn't have a release (useful for the
+                %% apps that live outside the EMQX monorepo):
+                case locate(src, App, ".appup.src") of
+                    {ok, AppupSrcFile} ->
+                        log("Using ~s as a source of previous update actions~n", [AppupSrcFile]),
+                        {_, U, D} = read_appup(AppupSrcFile),
+                        {U, D};
+                    undefined ->
+                        {[], []}
+                end
+        end,
+    {ensure_version(PrevVersion, Upgrade0), ensure_version(PrevVersion, Downgrade0)}.
+
+merge_update_actions(App, Changes, Vsns) ->
+    lists:map(fun(Ret = {<<".*">>, _}) ->
+                      Ret;
+                 ({Vsn, Actions}) ->
+                      {Vsn, do_merge_update_actions(App, Changes, Actions)}
+              end,
+              Vsns).
+
+do_merge_update_actions(App, {New0, Changed0, Deleted0}, OldActions) ->
+    AppSpecific = app_specific_actions(App) -- OldActions,
+    AlreadyHandled = lists:flatten(lists:map(fun process_old_action/1, OldActions)),
+    New = New0 -- AlreadyHandled,
+    Changed = Changed0 -- AlreadyHandled,
+    Deleted = Deleted0 -- AlreadyHandled,
+    [{load_module, M, brutal_purge, soft_purge, []} || M <- Changed ++ New] ++
+        OldActions ++
+        [{delete_module, M} || M <- Deleted] ++
+        AppSpecific.
+
+
+%% @doc Process the existing actions to exclude modules that are
+%% already handled
+process_old_action({purge, Modules}) ->
+    Modules;
+process_old_action({delete_module, Module}) ->
+    [Module];
+process_old_action(LoadModule) when is_tuple(LoadModule) andalso
+                                    element(1, LoadModule) =:= load_module ->
+    element(2, LoadModule);
+process_old_action(_) ->
+    [].
+
+ensure_version(Version, OldInstructions) ->
+    OldVersions = [ensure_string(element(1, I)) || I <- OldInstructions],
+    case lists:member(Version, OldVersions) of
+        false ->
+            [{Version, []}|OldInstructions];
+        _ ->
+            OldInstructions
+    end.
+
+read_appup(File) ->
+    %% NOTE: appup file is a script, it may contain variables or functions.
+    case file:script(File, [{'VSN', "VSN"}]) of
+        {ok, Terms} ->
+            Terms;
+        Error ->
+            fail("Failed to parse appup file ~s: ~p", [File, Error])
     end.
 
-erl_basename(App, Name) ->
-    case filename:basename(Name, ".erl") of
-        Name   -> false;
-        Module -> {true, {list_to_atom(App), list_to_atom(Module)}}
+check_appup_files() ->
+    AppupFiles = filelib:wildcard(getopt(src_dirs) ++ "/*.appup.src"),
+    lists:foreach(fun read_appup/1, AppupFiles).
+
+update_appups(Changes) ->
+    lists:foreach(
+      fun({App, {Upgrade, Downgrade, OldUpgrade, OldDowngrade}}) ->
+              do_update_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade)
+      end,
+      Changes).
+
+do_update_appup(App, Upgrade, Downgrade, OldUpgrade, OldDowngrade) ->
+    case locate(src, App, ".appup.src") of
+        {ok, AppupFile} ->
+            render_appfile(AppupFile, Upgrade, Downgrade);
+        undefined ->
+            case create_stub(App) of
+                {ok, AppupFile} ->
+                    render_appfile(AppupFile, Upgrade, Downgrade);
+                false ->
+                    case parse_appup_diffs(Upgrade, OldUpgrade,
+                                           Downgrade, OldDowngrade) of
+                        ok ->
+                            %% no diff for external dependency; ignore
+                            ok;
+                        {diffs, Diffs} ->
+                            set_invalid(),
+                            log("ERROR: Appup file for the external dependency '~p' is not complete.~n       Missing changes: ~100p~n", [App, Diffs]),
+                            log("NOTE: Some changes above might be already covered by regexes.~n")
+                    end
+            end
     end.
 
-group_modules(L) ->
-    lists:foldl(fun({App, Mod}, Acc) ->
-                        maps:update_with(App, fun(Tl) -> [Mod|Tl] end, [Mod], Acc)
-                end, #{}, L).
-
-update_appup(File, Modules) ->
-    io:format("~nUpdating appup: ~p~n", [File]),
-    {_, Upgrade0, Downgrade0} = read_appup(File),
-    Upgrade = update_actions(Modules, Upgrade0),
-    Downgrade = update_actions(Modules, Downgrade0),
-    IOList = io_lib:format("%% -*- mode: erlang -*-
-{VSN,~n  ~p,~n  ~p}.~n", [Upgrade, Downgrade]),
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Appup file creation
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+render_appfile(File, Upgrade, Downgrade) ->
+    IOList = io_lib:format("%% -*- mode: erlang -*-\n{VSN,~n  ~p,~n  ~p}.~n", [Upgrade, Downgrade]),
     ok = file:write_file(File, IOList).
 
-update_actions(Modules, Versions) ->
-    lists:map(fun(L) -> do_update_actions(Modules, L) end, Versions).
+create_stub(App) ->
+    case locate(src, App, Ext = ".app.src") of
+        {ok, AppSrc} ->
+            DirName = filename:dirname(AppSrc),
+            AppupFile = filename:basename(AppSrc, Ext) ++ ".appup.src",
+            Default = {<<".*">>, []},
+            AppupFileFullpath = filename:join(DirName, AppupFile),
+            render_appfile(AppupFileFullpath, [Default], [Default]),
+            {ok, AppupFileFullpath};
+        undefined ->
+            false
+    end.
 
-do_update_actions(_, Ret = {<<".*">>, _}) ->
-    Ret;
-do_update_actions(Modules, {Vsn, Actions}) ->
-    {Vsn, add_modules(Modules, Actions)}.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% application and release indexing
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 
-add_modules(NewModules, OldActions) ->
-    OldModules = lists:map(fun(It) -> element(2, It) end, OldActions),
-    Modules = NewModules -- OldModules,
-    OldActions ++ [{load_module, M, brutal_purge, soft_purge, []} || M <- Modules].
+index_apps(ReleaseDir) ->
+    Apps0 = maps:from_list([index_app(filename:join(ReleaseDir, AppFile)) ||
+                               AppFile <- filelib:wildcard("**/ebin/*.app", ReleaseDir)]),
+    maps:without(ignored_apps(), Apps0).
 
-read_appup(File) ->
-    {ok, Bin0} = file:read_file(File),
-    %% Hack:
-    Bin1 = re:replace(Bin0, "VSN", "\"VSN\""),
-    TmpFile = filename:join("/tmp", filename:basename(File)),
-    ok = file:write_file(TmpFile, Bin1),
-    {ok, [Terms]} = file:consult(TmpFile),
-    Terms.
+index_app(AppFile) ->
+    {ok, [{application, App, Properties}]} = file:consult(AppFile),
+    Vsn = proplists:get_value(vsn, Properties),
+    %% Note: assuming that beams are always located in the same directory where app file is:
+    EbinDir = filename:dirname(AppFile),
+    Modules = hashsums(EbinDir),
+    {App, #app{ version       = Vsn
+              , modules       = Modules
+              }}.
+
+diff_app(App, #app{version = NewVersion, modules = NewModules}, #app{version = OldVersion, modules = OldModules}) ->
+    {New, Changed} =
+        maps:fold( fun(Mod, MD5, {New, Changed}) ->
+                           case OldModules of
+                               #{Mod := OldMD5} when MD5 =:= OldMD5 ->
+                                   {New, Changed};
+                               #{Mod := _} ->
+                                   {New, [Mod|Changed]};
+                               _ ->
+                                   {[Mod|New], Changed}
+                           end
+                   end
+                 , {[], []}
+                 , NewModules
+                 ),
+    Deleted = maps:keys(maps:without(maps:keys(NewModules), OldModules)),
+    NChanges = length(New) + length(Changed) + length(Deleted),
+    if NewVersion =:= OldVersion andalso NChanges > 0 ->
+            set_invalid(),
+            log("ERROR: Application '~p' contains changes, but its version is not updated~n", [App]);
+       NewVersion > OldVersion ->
+            log("INFO: Application '~p' has been updated: ~p -> ~p~n", [App, OldVersion, NewVersion]),
+            ok;
+       true ->
+            ok
+    end,
+    {New, Changed, Deleted}.
+
+-spec hashsums(file:filename()) -> #{module() => binary()}.
+hashsums(EbinDir) ->
+    maps:from_list(lists:map(
+                     fun(Beam) ->
+                             File = filename:join(EbinDir, Beam),
+                             {ok, Ret = {_Module, _MD5}} = beam_lib:md5(File),
+                             Ret
+                     end,
+                     filelib:wildcard("*.beam", EbinDir)
+                    )).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Global state
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+init_globals(Options) ->
+    ets:new(globals, [named_table, set, public]),
+    ets:insert(globals, {valid, true}),
+    ets:insert(globals, {options, Options}).
+
+getopt(Option) ->
+    maps:get(Option, ets:lookup_element(globals, options, 2)).
+
+%% Set a global flag that something about the appfiles is invalid
+set_invalid() ->
+    ets:insert(globals, {valid, false}).
+
+is_valid() ->
+    ets:lookup_element(globals, valid, 2).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Utility functions
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Locate a file in a specified application
+locate(ebin_current, App, Suffix) ->
+    ReleaseDir = getopt(beams_dir),
+    AppStr = atom_to_list(App),
+    case filelib:wildcard(ReleaseDir ++ "/**/ebin/" ++ AppStr ++ Suffix) of
+        [File] ->
+            {ok, File};
+        [] ->
+            undefined
+    end;
+locate(src, App, Suffix) ->
+    AppStr = atom_to_list(App),
+    SrcDirs = getopt(src_dirs),
+    case filelib:wildcard(SrcDirs ++ AppStr ++ Suffix) of
+        [File] ->
+            {ok, File};
+        [] ->
+            undefined
+    end.
+
+bash(Script) ->
+    bash(Script, []).
+
+bash(Script, Env) ->
+    log("+ ~s~n+ Env: ~p~n", [Script, Env]),
+    case cmd("bash", #{args => ["-c", Script], env => Env}) of
+        0 -> true;
+        _ -> fail("Failed to run command: ~s", [Script])
+    end.
+
+%% Spawn an executable and return the exit status
+cmd(Exec, Params) ->
+    case os:find_executable(Exec) of
+        false ->
+            fail("Executable not found in $PATH: ~s", [Exec]);
+        Path ->
+            Params1 = maps:to_list(maps:with([env, args, cd], Params)),
+            Port = erlang:open_port( {spawn_executable, Path}
+                                   , [ exit_status
+                                     , nouse_stdio
+                                     | Params1
+                                     ]
+                                   ),
+            receive
+                {Port, {exit_status, Status}} ->
+                    Status
+            end
+    end.
+
+fail(Str) ->
+    fail(Str, []).
+
+fail(Str, Args) ->
+    log(Str ++ "~n", Args),
+    halt(1).
+
+log(Msg) ->
+    log(Msg, []).
+
+log(Msg, Args) ->
+    io:format(standard_error, Msg, Args).
+
+ensure_string(Str) when is_binary(Str) ->
+    binary_to_list(Str);
+ensure_string(Str) when is_list(Str) ->
+    Str.
+
+otp_standard_apps() ->
+    [ssl, mnesia, kernel, asn1, stdlib].
diff --git a/src/emqx.app.src b/src/emqx.app.src
index a8eca424cb..a107c7ed40 100644
--- a/src/emqx.app.src
+++ b/src/emqx.app.src
@@ -1,7 +1,7 @@
 {application, emqx,
  [{id, "emqx"},
   {description, "EMQ X"},
-  {vsn, "4.3.7"}, % strict semver, bump manually!
+  {vsn, "4.3.11"}, % strict semver, bump manually!
   {modules, []},
   {registered, []},
   {applications, [kernel,stdlib,gproc,gen_rpc,esockd,cowboy,sasl,os_mon]},
diff --git a/src/emqx.appup.src b/src/emqx.appup.src
index 027f4273b3..d40db10933 100644
--- a/src/emqx.appup.src
+++ b/src/emqx.appup.src
@@ -1,45 +1,118 @@
 %% -*- mode: erlang -*-
 {VSN,
-  [
-   {"4.3.6", [
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.5", [
-     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
+  [{"4.3.10",
+    [{load_module,emqx_app,brutal_purge,soft_purge,[]},
+     {load_module,emqx_connection,brutal_purge,soft_purge,[]}]},
+   {"4.3.9",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.8",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.7",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.6",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.5",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.4", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.4",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.3", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.3",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.2", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.2",
+    [{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
      {load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
      {load_module,emqx_channel,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_connection,brutal_purge,soft_purge,[]},
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.1", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.1",
+    [{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
@@ -49,14 +122,20 @@
      {load_module,emqx_congestion,brutal_purge,soft_purge,[]},
      {load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
      {load_module,emqx_channel,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_plugins,brutal_purge,soft_purge,[]},
      {load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
      {load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-   ]},
-   {"4.3.0", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.0",
+    [{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_logger_jsonfmt,brutal_purge,soft_purge,[]},
@@ -68,56 +147,131 @@
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
      {load_module,emqx_channel,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_plugins,brutal_purge,soft_purge,[]},
      {load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
      {load_module,emqx_metrics,brutal_purge,soft_purge,[]},
      {apply,{emqx_metrics,upgrade_retained_delayed_counter_type,[]}},
      {load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-   ]},
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
    {<<".*">>,[]}],
-  [
-   {"4.3.6", [
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.5", [
-     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
+  [{"4.3.10",
+    [{load_module,emqx_app,brutal_purge,soft_purge,[]},
+     {load_module,emqx_connection,brutal_purge,soft_purge,[]}]},
+   {"4.3.9",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.8",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.7",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.6",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.5",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.4", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.4",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.3", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.3",
+    [{load_module,emqx_connection,brutal_purge,soft_purge,[]},
+     {load_module,emqx_channel,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.2", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.2",
+    [{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
      {load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
      {load_module,emqx_channel,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_connection,brutal_purge,soft_purge,[]},
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-    ]},
-   {"4.3.1", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_frame,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.1",
+    [{load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_ws_connection,brutal_purge,soft_purge,[]},
@@ -127,14 +281,16 @@
      {load_module,emqx_congestion,brutal_purge,soft_purge,[]},
      {load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
      {load_module,emqx_channel,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_plugins,brutal_purge,soft_purge,[]},
      {load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
      {load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-   ]},
-   {"4.3.0", [
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
+   {"4.3.0",
+    [{load_module,emqx_alarm_handler,brutal_purge,soft_purge,[]},
+     {load_module,emqx_misc,brutal_purge,soft_purge,[]},
      {load_module,emqx_packet,brutal_purge,soft_purge,[]},
      {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]},
      {load_module,emqx_logger_jsonfmt,brutal_purge,soft_purge,[]},
@@ -146,12 +302,14 @@
      {load_module,emqx_cm,brutal_purge,soft_purge,[]},
      {load_module,emqx_node_dump,brutal_purge,soft_purge,[]},
      {load_module,emqx_channel,brutal_purge,soft_purge,[]},
-     {load_module,emqx_app,brutal_purge,soft_purge,[]},
      {load_module,emqx_plugins,brutal_purge,soft_purge,[]},
      {load_module,emqx_logger_textfmt,brutal_purge,soft_purge,[]},
      {load_module,emqx_metrics,brutal_purge,soft_purge,[]},
      {load_module,emqx_http_lib,brutal_purge,soft_purge,[]},
      {load_module,emqx_access_rule,brutal_purge,soft_purge,[]},
-     {load_module,emqx_ctl,brutal_purge,soft_purge,[]}
-   ]},
+     {load_module,emqx_ctl,brutal_purge,soft_purge,[]},
+     {load_module,emqx_pqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_mqueue,brutal_purge,soft_purge,[]},
+     {load_module,emqx_rpc,brutal_purge,soft_purge,[]},
+     {load_module,emqx_app,brutal_purge,soft_purge,[]}]},
    {<<".*">>,[]}]}.
diff --git a/src/emqx_alarm_handler.erl b/src/emqx_alarm_handler.erl
index a69913afde..44186de60a 100644
--- a/src/emqx_alarm_handler.erl
+++ b/src/emqx_alarm_handler.erl
@@ -61,7 +61,7 @@ handle_event({set_alarm, {system_memory_high_watermark, []}}, State) ->
     {ok, State};
 
 handle_event({set_alarm, {process_memory_high_watermark, Pid}}, State) -> 
-    emqx_alarm:activate(high_process_memory_usage, #{pid => Pid,
+    emqx_alarm:activate(high_process_memory_usage, #{pid => list_to_binary(pid_to_list(Pid)),
                                                      high_watermark => emqx_os_mon:get_procmem_high_watermark()}),
     {ok, State};
 
diff --git a/src/emqx_app.erl b/src/emqx_app.erl
index 8d0ff11a82..be6f45e250 100644
--- a/src/emqx_app.erl
+++ b/src/emqx_app.erl
@@ -42,10 +42,13 @@ start(_Type, _Args) ->
     ekka:start(),
     {ok, Sup} = emqx_sup:start_link(),
     ok = start_autocluster(),
+    %% We need to make sure that emqx's listeners start before plugins
+    %% and modules. Since if the emqx-conf module/plugin is enabled, it will
+    %% try to start or update the listeners with the latest configuration
+    emqx_boot:is_enabled(listeners) andalso (ok = emqx_listeners:start()),
     ok = emqx_plugins:init(),
     _ = emqx_plugins:load(),
     _ = start_ce_modules(),
-    emqx_boot:is_enabled(listeners) andalso (ok = emqx_listeners:start()),
     register(emqx, self()),
     ok = emqx_alarm_handler:load(),
     print_vsn(),
diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl
index e3cbff6926..7bfef472df 100644
--- a/src/emqx_channel.erl
+++ b/src/emqx_channel.erl
@@ -977,8 +977,11 @@ handle_info({sock_closed, Reason}, Channel =
         Shutdown -> Shutdown
     end;
 
-handle_info({sock_closed, Reason}, Channel = #channel{conn_state = disconnected}) ->
-    ?LOG(error, "Unexpected sock_closed: ~p", [Reason]),
+handle_info({sock_closed, _Reason}, Channel = #channel{conn_state = disconnected}) ->
+    %% Since sock_closed messages can be generated multiple times,
+    %% we can simply ignore errors of this type in the disconnected state.
+    %% e.g. when the socket send function returns an error, there is already
+    %% a tcp_closed delivered to the process mailbox
     {ok, Channel};
 
 handle_info(clean_acl_cache, Channel) ->
diff --git a/src/emqx_cm.erl b/src/emqx_cm.erl
index 61982f5691..23f078568b 100644
--- a/src/emqx_cm.erl
+++ b/src/emqx_cm.erl
@@ -72,7 +72,7 @@
         ]).
 
 %% Internal export
--export([stats_fun/0]).
+-export([stats_fun/0, clean_down/1]).
 
 -type(chan_pid() :: pid()).
 
@@ -93,7 +93,9 @@
 %% Server name
 -define(CM, ?MODULE).
 
--define(T_TAKEOVER, 15000).
+-define(T_KICK, 5_000).
+-define(T_GET_INFO, 5_000).
+-define(T_TAKEOVER, 15_000).
 
 %% @doc Start the channel manager.
 -spec(start_link() -> startlink_ret()).
@@ -164,7 +166,7 @@ get_chan_info(ClientId, ChanPid) when node(ChanPid) == node() ->
         error:badarg -> undefined
     end;
 get_chan_info(ClientId, ChanPid) ->
-    rpc_call(node(ChanPid), get_chan_info, [ClientId, ChanPid]).
+    rpc_call(node(ChanPid), get_chan_info, [ClientId, ChanPid], ?T_GET_INFO).
 
 %% @doc Update infos of the channel.
 -spec(set_chan_info(emqx_types:clientid(), emqx_types:attrs()) -> boolean()).
@@ -189,7 +191,7 @@ get_chan_stats(ClientId, ChanPid) when node(ChanPid) == node() ->
         error:badarg -> undefined
     end;
 get_chan_stats(ClientId, ChanPid) ->
-    rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid]).
+    rpc_call(node(ChanPid), get_chan_stats, [ClientId, ChanPid], ?T_GET_INFO).
 
 %% @doc Set channel's stats.
 -spec(set_chan_stats(emqx_types:clientid(), emqx_types:stats()) -> boolean()).
@@ -257,7 +259,7 @@ takeover_session(ClientId) ->
             takeover_session(ClientId, ChanPid);
         ChanPids ->
             [ChanPid|StalePids] = lists:reverse(ChanPids),
-            ?LOG(error, "More than one channel found: ~p", [ChanPids]),
+            ?LOG(error, "more_than_one_channel_found: ~p", [ChanPids]),
             lists:foreach(fun(StalePid) ->
                                   catch discard_session(ClientId, StalePid)
                           end, StalePids),
@@ -269,77 +271,113 @@ takeover_session(ClientId, ChanPid) when node(ChanPid) == node() ->
         undefined ->
             {error, not_found};
         ConnMod when is_atom(ConnMod) ->
+            %% TODO: if takeover times out, maybe kill the old?
             Session = ConnMod:call(ChanPid, {takeover, 'begin'}, ?T_TAKEOVER),
             {ok, ConnMod, ChanPid, Session}
     end;
-
 takeover_session(ClientId, ChanPid) ->
-    rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid]).
+    rpc_call(node(ChanPid), takeover_session, [ClientId, ChanPid], ?T_TAKEOVER).
 
 %% @doc Discard all the sessions identified by the ClientId.
 -spec(discard_session(emqx_types:clientid()) -> ok).
 discard_session(ClientId) when is_binary(ClientId) ->
     case lookup_channels(ClientId) of
         [] -> ok;
-        ChanPids -> lists:foreach(fun(Pid) -> do_discard_session(ClientId, Pid) end, ChanPids)
+        ChanPids -> lists:foreach(fun(Pid) -> discard_session(ClientId, Pid) end, ChanPids)
     end.
 
-do_discard_session(ClientId, Pid) ->
+%% @private Kick a local stale session to force it step down.
+%% If failed to kick (e.g. timeout) force a kill.
+%% Keeping the stale pid around, or returning error or raise an exception
+%% benefits nobody.
+-spec kick_or_kill(kick | discard, module(), pid()) -> ok.
+kick_or_kill(Action, ConnMod, Pid) ->
     try
-        discard_session(ClientId, Pid)
+        %% this is essentailly a gen_server:call implemented in emqx_connection
+        %% and emqx_ws_connection.
+        %% the handle_call is implemented in emqx_channel
+        ok = apply(ConnMod, call, [Pid, Action, ?T_KICK])
     catch
         _ : noproc -> % emqx_ws_connection: call
-            ?tp(debug, "session_already_gone", #{pid => Pid}),
-            ok;
+            ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action});
         _ : {noproc, _} -> % emqx_connection: gen_server:call
-            ?tp(debug, "session_already_gone", #{pid => Pid}),
-            ok;
-        _ : {'EXIT', {noproc, _}} -> % rpc_call/3
-            ?tp(debug, "session_already_gone", #{pid => Pid}),
-            ok;
+            ok = ?tp(debug, "session_already_gone", #{pid => Pid, action => Action});
+        _ : {shutdown, _} ->
+            ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action});
         _ : {{shutdown, _}, _} ->
-            ?tp(debug, "session_already_shutdown", #{pid => Pid}),
-            ok;
+            ok = ?tp(debug, "session_already_shutdown", #{pid => Pid, action => Action});
+        _ : {timeout, {gen_server, call, _}} ->
+            ?tp(warning, "session_kick_timeout",
+                #{pid => Pid,
+                  action => Action,
+                  stale_channel => stale_channel_info(Pid)
+                 }),
+            ok = force_kill(Pid);
         _ : Error : St ->
-            ?tp(error, "failed_to_discard_session",
-                #{pid => Pid, reason => Error, stacktrace=>St})
+            ?tp(error, "session_kick_exception",
+                #{pid => Pid,
+                  action => Action,
+                  reason => Error,
+                  stacktrace => St,
+                  stale_channel => stale_channel_info(Pid)
+                 }),
+            ok = force_kill(Pid)
     end.
 
-discard_session(ClientId, ChanPid) when node(ChanPid) == node() ->
+force_kill(Pid) ->
+    exit(Pid, kill),
+    ok.
+
+stale_channel_info(Pid) ->
+    process_info(Pid, [status, message_queue_len, current_stacktrace]).
+
+discard_session(ClientId, ChanPid) ->
+    kick_session(discard, ClientId, ChanPid).
+
+kick_session(ClientId, ChanPid) ->
+    kick_session(kick, ClientId, ChanPid).
+
+%% @private This function is shared for session 'kick' and 'discard' (as the first arg Action).
+kick_session(Action, ClientId, ChanPid) when node(ChanPid) == node() ->
     case get_chann_conn_mod(ClientId, ChanPid) of
-        undefined -> ok;
+        undefined ->
+            %% already deregistered
+            ok;
         ConnMod when is_atom(ConnMod) ->
-            ConnMod:call(ChanPid, discard, ?T_TAKEOVER)
+            ok = kick_or_kill(Action, ConnMod, ChanPid)
     end;
-
-discard_session(ClientId, ChanPid) ->
-    rpc_call(node(ChanPid), discard_session, [ClientId, ChanPid]).
+kick_session(Action, ClientId, ChanPid) ->
+    %% call remote node on the old APIs because we do not know if they have upgraded
+    %% to have kick_session/3
+    Function = case Action of
+                   discard -> discard_session;
+                   kick -> kick_session
+               end,
+    try
+        rpc_call(node(ChanPid), Function, [ClientId, ChanPid], ?T_KICK)
+    catch
+        Error : Reason ->
+            %% This should mostly be RPC failures.
+            %% However, if the node is still running the old version
+            %% code (prior to emqx app 4.3.10) some of the RPC handler
+            %% exceptions may get propagated to a new version node
+            ?LOG(error, "failed_to_kick_session_on_remote_node ~p: ~p ~p ~p",
+                 [node(ChanPid), Action, Error, Reason])
+    end.
 
 kick_session(ClientId) ->
     case lookup_channels(ClientId) of
-        [] -> {error, not_found};
-        [ChanPid] ->
-            kick_session(ClientId, ChanPid);
+        [] ->
+            ?LOG(warning, "kiecked_an_unknown_session ~ts", [ClientId]),
+            ok;
         ChanPids ->
-            [ChanPid|StalePids] = lists:reverse(ChanPids),
-            ?LOG(error, "More than one channel found: ~p", [ChanPids]),
-            lists:foreach(fun(StalePid) ->
-                                  catch discard_session(ClientId, StalePid)
-                          end, StalePids),
-            kick_session(ClientId, ChanPid)
+            case length(ChanPids) > 1 of
+                true -> ?LOG(info, "more_than_one_channel_found: ~p", [ChanPids]);
+                false -> ok
+            end,
+            lists:foreach(fun(Pid) -> kick_session(ClientId, Pid) end, ChanPids)
     end.
 
-kick_session(ClientId, ChanPid) when node(ChanPid) == node() ->
-    case get_chan_info(ClientId, ChanPid) of
-        #{conninfo := #{conn_mod := ConnMod}} ->
-            ConnMod:call(ChanPid, kick, ?T_TAKEOVER);
-        undefined ->
-            {error, not_found}
-    end;
-
-kick_session(ClientId, ChanPid) ->
-    rpc_call(node(ChanPid), kick_session, [ClientId, ChanPid]).
-
 %% @doc Is clean start?
 % is_clean_start(#{clean_start := false}) -> false;
 % is_clean_start(_Attrs) -> true.
@@ -375,10 +413,16 @@ lookup_channels(local, ClientId) ->
     [ChanPid || {_, ChanPid} <- ets:lookup(?CHAN_TAB, ClientId)].
 
 %% @private
-rpc_call(Node, Fun, Args) ->
-    case rpc:call(Node, ?MODULE, Fun, Args, 2 * ?T_TAKEOVER) of
-        {badrpc, Reason} -> error(Reason);
-        Res -> Res
+rpc_call(Node, Fun, Args, Timeout) ->
+    case rpc:call(Node, ?MODULE, Fun, Args, 2 * Timeout) of
+        {badrpc, Reason} ->
+            %% since eqmx app 4.3.10, the 'kick' and 'discard' calls hanndler
+            %% should catch all exceptions and always return 'ok'.
+            %% This leaves 'badrpc' only possible when there is problem
+            %% calling the remote node.
+            error({badrpc, Reason});
+        Res ->
+            Res
     end.
 
 %% @private
@@ -411,7 +455,7 @@ handle_cast(Msg, State) ->
 handle_info({'DOWN', _MRef, process, Pid, _Reason}, State = #{chan_pmon := PMon}) ->
     ChanPids = [Pid | emqx_misc:drain_down(?BATCH_SIZE)],
     {Items, PMon1} = emqx_pmon:erase_all(ChanPids, PMon),
-    ok = emqx_pool:async_submit(fun lists:foreach/2, [fun clean_down/1, Items]),
+    ok = emqx_pool:async_submit(fun lists:foreach/2, [fun ?MODULE:clean_down/1, Items]),
     {noreply, State#{chan_pmon := PMon1}};
 
 handle_info(Info, State) ->
@@ -447,5 +491,5 @@ get_chann_conn_mod(ClientId, ChanPid) when node(ChanPid) == node() ->
         error:badarg -> undefined
     end;
 get_chann_conn_mod(ClientId, ChanPid) ->
-    rpc_call(node(ChanPid), get_chann_conn_mod, [ClientId, ChanPid]).
+    rpc_call(node(ChanPid), get_chann_conn_mod, [ClientId, ChanPid], ?T_GET_INFO).
 
diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl
index ab91c02b43..b8d1c485db 100644
--- a/src/emqx_connection.erl
+++ b/src/emqx_connection.erl
@@ -702,7 +702,7 @@ send(IoData, #state{transport = Transport, socket = Socket, channel = Channel})
     ok = emqx_metrics:inc('bytes.sent', Oct),
     inc_counter(outgoing_bytes, Oct),
     emqx_congestion:maybe_alarm_conn_congestion(Socket, Transport, Channel),
-    case Transport:async_send(Socket, IoData, [nosuspend]) of
+    case Transport:async_send(Socket, IoData, []) of
         ok -> ok;
         Error = {error, _Reason} ->
             %% Send an inet_reply to postpone handling the error
diff --git a/src/emqx_frame.erl b/src/emqx_frame.erl
index 37063c65f2..79ac9da356 100644
--- a/src/emqx_frame.erl
+++ b/src/emqx_frame.erl
@@ -69,6 +69,8 @@
           version     => ?MQTT_PROTO_V4
          }).
 
+-define(MULTIPLIER_MAX, 16#200000).
+
 -dialyzer({no_match, [serialize_utf8_string/2]}).
 
 %%--------------------------------------------------------------------
@@ -146,7 +148,7 @@ parse_remaining_len(<<0:8, Rest/binary>>, Header, 1, 0, Options) ->
 parse_remaining_len(<<0:1, 2:7, Rest/binary>>, Header, 1, 0, Options) ->
     parse_frame(Rest, Header, 2, Options);
 parse_remaining_len(<<1:1, _Len:7, _Rest/binary>>, _Header, Multiplier, _Value, _Options)
-        when Multiplier > 2097152 ->
+  when Multiplier > ?MULTIPLIER_MAX ->
     error(malformed_variable_byte_integer);
 parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Header, Multiplier, Value, Options) ->
     parse_remaining_len(Rest, Header, Multiplier * ?HIGHBIT, Value + Len * Multiplier, Options);
@@ -432,6 +434,9 @@ parse_property(<<16#2A, Val, Bin/binary>>, Props) ->
 
 parse_variable_byte_integer(Bin) ->
     parse_variable_byte_integer(Bin, 1, 0).
+parse_variable_byte_integer(<<1:1, _Len:7, _Rest/binary>>, Multiplier, _Value)
+  when Multiplier > ?MULTIPLIER_MAX ->
+    error(malformed_variable_byte_integer);
 parse_variable_byte_integer(<<1:1, Len:7, Rest/binary>>, Multiplier, Value) ->
     parse_variable_byte_integer(Rest, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
 parse_variable_byte_integer(<<0:1, Len:7, Rest/binary>>, Multiplier, Value) ->
diff --git a/src/emqx_logger_jsonfmt.erl b/src/emqx_logger_jsonfmt.erl
index ed27e79b2c..5bda0e9179 100644
--- a/src/emqx_logger_jsonfmt.erl
+++ b/src/emqx_logger_jsonfmt.erl
@@ -37,30 +37,30 @@
 
 -export_type([config/0]).
 
--elvis([{elvis_style, no_nested_try_catch, #{ignore => [emqx_logger_jsonfmt]}}]).
+-elvis([{elvis_style, no_nested_try_catch, #{ ignore => [emqx_logger_jsonfmt]}}]).
 
--type config() :: #{depth => pos_integer() | unlimited,
-report_cb => logger:report_cb(),
-single_line => boolean()}.
+-type config() :: #{depth       => pos_integer() | unlimited,
+                    report_cb   => logger:report_cb(),
+                    single_line => boolean()}.
 
 -define(IS_STRING(String), (is_list(String) orelse is_binary(String))).
 
 -spec format(logger:log_event(), config()) -> iodata().
 format(#{level := Level, msg := Msg, meta := Meta}, Config0) when is_map(Config0) ->
     Config = add_default_config(Config0),
-    [format(Msg, Meta#{level => Level}, Config), "\n"].
+    [format(Msg, Meta#{level => Level}, Config) , "\n"].
 
 format(Msg, Meta, Config) ->
     Data0 =
         try Meta#{msg => format_msg(Msg, Meta, Config)}
         catch
             C:R:S ->
-                Meta#{msg => "emqx_logger_jsonfmt_format_error"
-                    , fmt_raw_input => Msg
-                    , fmt_error => C
-                    , fmt_reason => R
-                    , fmt_stacktrace => S
-                }
+                Meta#{ msg => "emqx_logger_jsonfmt_format_error"
+                     , fmt_raw_input => Msg
+                     , fmt_error => C
+                     , fmt_reason => R
+                     , fmt_stacktrace => S
+                     }
         end,
     Data = maps:without([report_cb], Data0),
     Payload = jiffy:encode(json_obj(Data, Config)),
@@ -70,7 +70,7 @@ format(Msg, Meta, Config) ->
 format_msg({string, Chardata}, Meta, Config) ->
     format_msg({"~ts", [Chardata]}, Meta, Config);
 format_msg({report, _} = Msg, Meta, #{report_cb := Fun} = Config)
-    when is_function(Fun, 1); is_function(Fun, 2) ->
+  when is_function(Fun,1); is_function(Fun,2) ->
     format_msg(Msg, Meta#{report_cb => Fun}, maps:remove(report_cb, Config));
 format_msg({report, Report}, #{report_cb := Fun} = Meta, Config) when is_function(Fun, 1) ->
     case Fun(Report) of
@@ -79,10 +79,10 @@ format_msg({report, Report}, #{report_cb := Fun} = Meta, Config) when is_functio
         Map when is_map(Map) ->
             Map;
         Other ->
-            #{msg => "report_cb_bad_return"
-                , report_cb_fun => Fun
-                , report_cb_return => Other
-            }
+            #{ msg => "report_cb_bad_return"
+             , report_cb_fun => Fun
+             , report_cb_return => Other
+             }
     end;
 format_msg({report, Report}, #{report_cb := Fun}, Config) when is_function(Fun, 2) ->
     case Fun(Report, maps:with([depth, single_line], Config)) of
@@ -91,30 +91,30 @@ format_msg({report, Report}, #{report_cb := Fun}, Config) when is_function(Fun,
                 unicode:characters_to_binary(Chardata, utf8)
             catch
                 _:_ ->
-                    #{msg => "report_cb_bad_return"
-                        , report_cb_fun => Fun
-                        , report_cb_return => Chardata
-                    }
+                    #{ msg => "report_cb_bad_return"
+                     , report_cb_fun => Fun
+                     , report_cb_return => Chardata
+                     }
             end;
         Map when is_map(Map) ->
             Map;
         Other ->
-            #{msg => "report_cb_bad_return"
-                , report_cb_fun => Fun
-                , report_cb_return => Other
-            }
+            #{ msg => "report_cb_bad_return"
+             , report_cb_fun => Fun
+             , report_cb_return => Other
+             }
     end;
 format_msg({Fmt, Args}, _Meta, Config) ->
     do_format_msg(Fmt, Args, Config).
 
 do_format_msg(Format0, Args, #{depth := Depth,
-    single_line := SingleLine
-}) ->
+                               single_line := SingleLine
+                              }) ->
     Format1 = io_lib:scan_format(Format0, Args),
     Format = reformat(Format1, Depth, SingleLine),
     Text0 = io_lib:build_text(Format, []),
     Text = case SingleLine of
-               true -> re:replace(Text0, ",?\r?\n\s*", ", ", [{return, list}, global, unicode]);
+               true -> re:replace(Text0, ",?\r?\n\s*",", ", [{return, list}, global, unicode]);
                false -> Text0
            end,
     trim(unicode:characters_to_binary(Text, utf8)).
@@ -130,7 +130,7 @@ reformat([#{control_char := C} = M | T], Depth, true) when C =:= $p ->
     [limit_depth(M#{width => 0}, Depth) | reformat(T, Depth, true)];
 reformat([#{control_char := C} = M | T], Depth, true) when C =:= $P ->
     [M#{width => 0} | reformat(T, Depth, true)];
-reformat([#{control_char := C} = M | T], Depth, Single) when C =:= $p; C =:= $w ->
+reformat([#{control_char := C}=M | T], Depth, Single) when C =:= $p; C =:= $w ->
     [limit_depth(M, Depth) | reformat(T, Depth, Single)];
 reformat([H | T], Depth, Single) ->
     [H | reformat(T, Depth, Single)];
@@ -138,7 +138,7 @@ reformat([], _, _) ->
     [].
 
 limit_depth(M0, unlimited) -> M0;
-limit_depth(#{control_char := C0, args := Args} = M0, Depth) ->
+limit_depth(#{control_char:=C0, args:=Args}=M0, Depth) ->
     C = C0 - ($a - $A), %To uppercase.
     M0#{control_char := C, args := Args ++ [Depth]}.
 
@@ -184,7 +184,7 @@ json(P, C) when is_port(P) -> json(port_to_list(P), C);
 json(F, C) when is_function(F) -> json(erlang:fun_to_list(F), C);
 json(B, Config) when is_binary(B) ->
     best_effort_unicode(B, Config);
-json(L, Config) when is_list(L), is_integer(hd(L)) ->
+json(L, Config) when is_list(L), is_integer(hd(L))->
     best_effort_unicode(L, Config);
 json(M, Config) when is_list(M), is_tuple(hd(M)), tuple_size(hd(M)) =:= 2 ->
     best_effort_json_obj(M, Config);
@@ -196,8 +196,8 @@ json(Term, Config) ->
     do_format_msg("~p", [Term], Config).
 
 json_obj(Data, Config) ->
-    maps:fold(fun(K, V, D) ->
-        json_kv(K, V, D, Config)
+    maps:fold(fun (K, V, D) ->
+                      json_kv(K, V, D, Config)
               end, maps:new(), Data).
 
 json_kv(mfa, {M, F, A}, Data, _Config) -> %% emqx/snabbkaffe
@@ -232,43 +232,43 @@ json_key(Term) ->
 no_crash_test_() ->
     Opts = [{numtests, 1000}, {to_file, user}],
     {timeout, 30,
-        fun() -> ?assert(proper:quickcheck(t_no_crash(), Opts)) end}.
+     fun() -> ?assert(proper:quickcheck(t_no_crash(), Opts)) end}.
 
 t_no_crash() ->
     ?FORALL({Level, Report, Meta, Config},
-        {p_level(), p_report(), p_meta(), p_config()},
-        t_no_crash_run(Level, Report, Meta, Config)).
+            {p_level(), p_report(), p_meta(), p_config()},
+            t_no_crash_run(Level, Report, Meta, Config)).
 
 t_no_crash_run(Level, Report, {undefined, Meta}, Config) ->
     t_no_crash_run(Level, Report, maps:from_list(Meta), Config);
 t_no_crash_run(Level, Report, {ReportCb, Meta}, Config) ->
     t_no_crash_run(Level, Report, maps:from_list([{report_cb, ReportCb} | Meta]), Config);
 t_no_crash_run(Level, Report, Meta, Config) ->
-    Input = #{level => Level
-        , msg => {report, Report}
-        , meta => filter(Meta)
-    },
+    Input = #{ level => Level
+             , msg => {report, Report}
+             , meta => filter(Meta)
+             },
     _ = format(Input, maps:from_list(Config)),
     true.
 
 %% assume top level Report and Meta are sane
 filter(Map) ->
     Keys = lists:filter(
-        fun(K) ->
-            try json_key(K), true
-            catch throw : {badkey, _} -> false
-            end
-        end, maps:keys(Map)),
+             fun(K) ->
+                     try json_key(K), true
+                     catch throw : {badkey, _} -> false
+                     end
+             end, maps:keys(Map)),
     maps:with(Keys, Map).
 
 p_report_cb() ->
-    proper_types:oneof([fun ?MODULE:report_cb_1/1
-        , fun ?MODULE:report_cb_2/2
-        , fun ?MODULE:report_cb_crash/2
-        , fun logger:format_otp_report/1
-        , fun logger:format_report/1
-        , format_report_undefined
-    ]).
+    proper_types:oneof([ fun ?MODULE:report_cb_1/1
+                       , fun ?MODULE:report_cb_2/2
+                       , fun ?MODULE:report_cb_crash/2
+                       , fun logger:format_otp_report/1
+                       , fun logger:format_report/1
+                       , format_report_undefined
+                       ]).
 
 report_cb_1(Input) -> {"~p", [Input]}.
 
@@ -279,8 +279,8 @@ report_cb_crash(_Input, _Config) -> error(report_cb_crash).
 p_kvlist() ->
     proper_types:list({
         proper_types:oneof([proper_types:atom(),
-            proper_types:binary()
-        ]), proper_types:term()}).
+                            proper_types:binary()
+                           ]), proper_types:term()}).
 
 %% meta type is 2-tuple, report_cb type, and some random key value pairs
 p_meta() ->
@@ -294,8 +294,8 @@ p_level() -> proper_types:oneof([info, debug, error, warning, foobar]).
 
 p_config() ->
     proper_types:shrink_list(
-        [{depth, p_limit()}
-            , {single_line, proper_types:boolean()}
-        ]).
+      [ {depth, p_limit()}
+      , {single_line, proper_types:boolean()}
+      ]).
 
 -endif.
diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl
index 04af5f72cc..eb6a253774 100644
--- a/src/emqx_misc.erl
+++ b/src/emqx_misc.erl
@@ -52,6 +52,8 @@
         , hexstr2bin/1
         ]).
 
+-define(OOM_FACTOR, 1.25).
+
 %% @doc Parse v4 or v6 string format address to tuple.
 %% `Host' itself is returned if it's not an ip string.
 maybe_parse_ip(Host) ->
@@ -216,12 +218,29 @@ do_check_oom([{Val, Max, Reason}|Rest]) ->
 
 tune_heap_size(#{max_heap_size := MaxHeapSize}) ->
     %% If set to zero, the limit is disabled.
-    erlang:process_flag(max_heap_size, #{size => MaxHeapSize,
-                                         kill => false,
+    erlang:process_flag(max_heap_size, #{size => must_kill_heap_size(MaxHeapSize),
+                                         kill => true,
                                          error_logger => true
                                         });
 tune_heap_size(undefined) -> ok.
 
+must_kill_heap_size(Size) ->
+    %% We set the max allowed heap size by `erlang:process_flag(max_heap_size, #{size => Size})`,
+    %% where the `Size` cannot be set to an integer lager than `(1 bsl 59) - 1` on a 64-bit system,
+    %% or `(1 bsl 27) - 1` on a 32-bit system.
+    MaxAllowedSize = case erlang:system_info(wordsize) of
+        8 -> % arch_64
+            (1 bsl 59) - 1;
+        4 -> % arch_32
+            (1 bsl 27) - 1
+    end,
+    %% We multiply the size with factor ?OOM_FACTOR, to give the
+    %% process a chance to suicide by `check_oom/1`
+    case ceil(Size * ?OOM_FACTOR) of
+        Size0 when Size0 >= MaxAllowedSize -> MaxAllowedSize;
+        Size0 -> Size0
+    end.
+
 -spec(proc_name(atom(), pos_integer()) -> atom()).
 proc_name(Mod, Id) ->
     list_to_atom(lists:concat([Mod, "_", Id])).
diff --git a/src/emqx_mqueue.erl b/src/emqx_mqueue.erl
index d0c6365ffd..f5fc90f47c 100644
--- a/src/emqx_mqueue.erl
+++ b/src/emqx_mqueue.erl
@@ -67,6 +67,9 @@
         , dropped/1
         ]).
 
+-export([ live_upgrade/1
+        ]).
+
 -export_type([mqueue/0, options/0]).
 
 -type(topic() :: emqx_topic:topic()).
@@ -91,6 +94,11 @@
 -define(MAX_LEN_INFINITY, 0).
 -define(INFO_KEYS, [store_qos0, max_len, len, dropped]).
 
+-record(shift_opts, {
+          multiplier :: non_neg_integer(),
+          base       :: integer()
+         }).
+
 -record(mqueue, {
           store_qos0 = false              :: boolean(),
           max_len    = ?MAX_LEN_INFINITY  :: count(),
@@ -98,11 +106,16 @@
           dropped    = 0                  :: count(),
           p_table    = ?NO_PRIORITY_TABLE :: p_table(),
           default_p  = ?LOWEST_PRIORITY   :: priority(),
-          q          = ?PQUEUE:new()      :: pq()
+          q          = ?PQUEUE:new()      :: pq(),
+          shift_opts                      :: #shift_opts{},
+          last_p                          :: non_neg_integer() | undefined,
+          counter                         :: non_neg_integer() | undefined
          }).
 
 -type(mqueue() :: #mqueue{}).
 
+-define(OLD(Q), Q = {mqueue, _, _, _, _, _, _, _}).
+
 -spec(init(options()) -> mqueue()).
 init(Opts = #{max_len := MaxLen0, store_qos0 := QoS_0}) ->
     MaxLen = case (is_integer(MaxLen0) andalso MaxLen0 > ?MAX_LEN_INFINITY) of
@@ -112,7 +125,8 @@ init(Opts = #{max_len := MaxLen0, store_qos0 := QoS_0}) ->
     #mqueue{max_len = MaxLen,
             store_qos0 = QoS_0,
             p_table = get_opt(priorities, Opts, ?NO_PRIORITY_TABLE),
-            default_p = get_priority_opt(Opts)
+            default_p = get_priority_opt(Opts),
+            shift_opts = get_shift_opt(Opts)
            }.
 
 -spec(info(mqueue()) -> emqx_types:infos()).
@@ -127,22 +141,30 @@ info(max_len, #mqueue{max_len = MaxLen}) ->
 info(len, #mqueue{len = Len}) ->
     Len;
 info(dropped, #mqueue{dropped = Dropped}) ->
-    Dropped.
+    Dropped;
+info(Info, ?OLD(MQ)) ->
+    info(Info, live_upgrade(MQ)).
 
-is_empty(#mqueue{len = Len}) -> Len =:= 0.
+is_empty(#mqueue{len = Len}) -> Len =:= 0;
+is_empty(?OLD(MQ)) -> is_empty(live_upgrade(MQ)).
 
-len(#mqueue{len = Len}) -> Len.
+len(#mqueue{len = Len}) -> Len;
+len(?OLD(MQ)) -> len(live_upgrade(MQ)).
 
-max_len(#mqueue{max_len = MaxLen}) -> MaxLen.
+max_len(#mqueue{max_len = MaxLen}) -> MaxLen;
+max_len(?OLD(MQ)) -> max_len(live_upgrade(MQ)).
 
 %% @doc Return number of dropped messages.
 -spec(dropped(mqueue()) -> count()).
-dropped(#mqueue{dropped = Dropped}) -> Dropped.
+dropped(#mqueue{dropped = Dropped}) -> Dropped;
+dropped(?OLD(MQ)) -> dropped(live_upgrade(MQ)).
 
 %% @doc Stats of the mqueue
 -spec(stats(mqueue()) -> [stat()]).
 stats(#mqueue{max_len = MaxLen, dropped = Dropped} = MQ) ->
-    [{len, len(MQ)}, {max_len, MaxLen}, {dropped, Dropped}].
+    [{len, len(MQ)}, {max_len, MaxLen}, {dropped, Dropped}];
+stats(?OLD(MQ)) ->
+    stats(live_upgrade(MQ)).
 
 %% @doc Enqueue a message.
 -spec(in(message(), mqueue()) -> {maybe(message()), mqueue()}).
@@ -165,15 +187,34 @@ in(Msg = #message{topic = Topic}, MQ = #mqueue{default_p = Dp,
             {DroppedMsg, MQ#mqueue{q = Q2, dropped = Dropped + 1}};
         false ->
             {_DroppedMsg = undefined, MQ#mqueue{len = Len + 1, q = ?PQUEUE:in(Msg, Priority, Q)}}
-    end.
+    end;
+in(Msg, ?OLD(MQ)) ->
+    in(Msg, live_upgrade(MQ)).
 
 -spec(out(mqueue()) -> {empty | {value, message()}, mqueue()}).
 out(MQ = #mqueue{len = 0, q = Q}) ->
     0 = ?PQUEUE:len(Q), %% assert, in this case, ?PQUEUE:len should be very cheap
     {empty, MQ};
-out(MQ = #mqueue{q = Q, len = Len}) ->
+out(MQ = #mqueue{q = Q, len = Len, last_p = undefined, shift_opts = ShiftOpts}) ->
+    {{value, Val, Prio}, Q1} = ?PQUEUE:out_p(Q), %% Shouldn't fail, since we've checked the length
+    MQ1 = MQ#mqueue{
+            q = Q1,
+            len = Len - 1,
+            last_p = Prio,
+            counter = init_counter(Prio, ShiftOpts)
+           },
+    {{value, Val}, MQ1};
+out(MQ = #mqueue{q = Q, counter = 0}) ->
+    MQ1 = MQ#mqueue{
+            q      = ?PQUEUE:shift(Q),
+            last_p = undefined
+           },
+    out(MQ1);
+out(MQ = #mqueue{q = Q, len = Len, counter = Cnt}) ->
     {R, Q1} = ?PQUEUE:out(Q),
-    {R, MQ#mqueue{q = Q1, len = Len - 1}}.
+    {R, MQ#mqueue{q = Q1, len = Len - 1, counter = Cnt - 1}};
+out(?OLD(MQ)) ->
+    out(live_upgrade(MQ)).
 
 get_opt(Key, Opts, Default) ->
     case maps:get(Key, Opts, Default) of
@@ -194,3 +235,46 @@ get_priority_opt(Opts) ->
 %% while the highest 'infinity' is a [{infinity, queue:queue()}]
 get_priority(_Topic, ?NO_PRIORITY_TABLE, _) -> ?LOWEST_PRIORITY;
 get_priority(Topic, PTab, Dp) -> maps:get(Topic, PTab, Dp).
+
+init_counter(?HIGHEST_PRIORITY, Opts) ->
+    Infinity = 1000000,
+    init_counter(Infinity, Opts);
+init_counter(Prio, #shift_opts{multiplier = Mult, base = Base}) ->
+    (Prio + Base) * Mult.
+
+get_shift_opt(Opts) ->
+    Mult = maps:get(shift_multiplier, Opts, 10),
+    Min = case Opts of
+              #{p_table := PTab} ->
+                  case maps:size(PTab) of
+                      0 -> 0;
+                      _ -> lists:min(maps:values(PTab))
+                  end;
+              _ ->
+                  ?LOWEST_PRIORITY
+          end,
+    Base = case Min < 0 of
+               true  -> -Min;
+               false -> 0
+           end,
+    #shift_opts{
+       multiplier = Mult,
+       base       = Base
+      }.
+
+live_upgrade({mqueue, StoreQos0, MaxLen, Len, Dropped, PTable, DefaultP, Q}) ->
+    ShiftOpts = case is_map(PTable) of
+                    true  -> get_shift_opt(#{p_table => PTable});
+                    false -> get_shift_opt(#{})
+                end,
+    #mqueue{ store_qos0 = StoreQos0
+           , max_len    = MaxLen
+           , dropped    = Dropped
+           , p_table    = PTable
+           , default_p  = DefaultP
+           , len        = Len
+           , q          = Q
+           , shift_opts = ShiftOpts
+           , last_p     = undefined
+           , counter    = undefined
+           }.
diff --git a/src/emqx_pqueue.erl b/src/emqx_pqueue.erl
index 85c89866dc..5dd81af0b0 100644
--- a/src/emqx_pqueue.erl
+++ b/src/emqx_pqueue.erl
@@ -55,6 +55,7 @@
         , filter/2
         , fold/3
         , highest/1
+        , shift/1
         ]).
 
 -export_type([q/0]).
@@ -170,6 +171,14 @@ out({pqueue, [{P, Q} | Queues]}) ->
            end,
     {R, NewQ}.
 
+-spec(shift(pqueue()) -> pqueue()).
+shift(Q = {queue, _, _, _}) ->
+    Q;
+shift({pqueue, []}) ->
+    {pqueue, []}; %% Shouldn't happen?
+shift({pqueue, [Hd|Rest]}) ->
+    {pqueue, Rest ++ [Hd]}. %% Let's hope there are not many priorities.
+
 -spec(out_p(pqueue()) -> {empty | {value, any(), priority()}, pqueue()}).
 out_p({queue, _, _, _}       = Q) -> add_p(out(Q), 0);
 out_p({pqueue, [{P, _} | _]} = Q) -> add_p(out(Q), maybe_negate_priority(P)).
@@ -266,4 +275,3 @@ r2f([X,Y|R], L) -> {queue, [X,Y], lists:reverse(R, []), L}.
 
 maybe_negate_priority(infinity) -> infinity;
 maybe_negate_priority(P)        -> -P.
-
diff --git a/src/emqx_rpc.erl b/src/emqx_rpc.erl
index a37d67a0a0..fcd8ad4e78 100644
--- a/src/emqx_rpc.erl
+++ b/src/emqx_rpc.erl
@@ -41,10 +41,10 @@ call(Key, Node, Mod, Fun, Args) ->
     filter_result(?RPC:call(rpc_node({Key, Node}), Mod, Fun, Args)).
 
 multicall(Nodes, Mod, Fun, Args) ->
-    filter_result(?RPC:multicall(rpc_nodes(Nodes), Mod, Fun, Args)).
+    ?RPC:multicall(rpc_nodes(Nodes), Mod, Fun, Args).
 
 multicall(Key, Nodes, Mod, Fun, Args) ->
-    filter_result(?RPC:multicall(rpc_nodes([{Key, Node} || Node <- Nodes]), Mod, Fun, Args)).
+    ?RPC:multicall(rpc_nodes([{Key, Node} || Node <- Nodes]), Mod, Fun, Args).
 
 cast(Node, Mod, Fun, Args) ->
     filter_result(?RPC:cast(rpc_node(Node), Mod, Fun, Args)).
diff --git a/src/emqx_tracer.erl b/src/emqx_tracer.erl
index 43f2242429..e2b130a1c0 100644
--- a/src/emqx_tracer.erl
+++ b/src/emqx_tracer.erl
@@ -22,35 +22,35 @@
 -logger_header("[Tracer]").
 
 %% APIs
--export([trace/2
-    , start_trace/3
-    , lookup_traces/0
-    , stop_trace/1
-]).
+-export([ trace/2
+        , start_trace/3
+        , lookup_traces/0
+        , stop_trace/1
+        ]).
 
 -type(trace_who() :: {clientid | topic, binary()}).
 
 -define(TRACER, ?MODULE).
 -define(FORMAT, {logger_formatter,
-    #{template =>
-    [time, " [", level, "] ",
-        {clientid,
-            [{peername,
-                [clientid, "@", peername, " "],
-                [clientid, " "]}],
-            [{peername,
-                [peername, " "],
-                []}]},
-        msg, "\n"],
-        single_line => false
-    }}).
--define(TOPIC_TRACE_ID(T), "trace_topic_" ++ T).
--define(CLIENT_TRACE_ID(C), "trace_clientid_" ++ C).
+                  #{template =>
+                      [time, " [", level, "] ",
+                       {clientid,
+                          [{peername,
+                              [clientid, "@", peername, " "],
+                              [clientid, " "]}],
+                          [{peername,
+                              [peername, " "],
+                              []}]},
+                       msg, "\n"],
+                    single_line => false
+                   }}).
+-define(TOPIC_TRACE_ID(T), "trace_topic_"++T).
+-define(CLIENT_TRACE_ID(C), "trace_clientid_"++C).
 -define(TOPIC_TRACE(T), {topic, T}).
 -define(CLIENT_TRACE(C), {clientid, C}).
 
 -define(IS_LOG_LEVEL(L),
-    L =:= emergency orelse
+        L =:= emergency orelse
         L =:= alert orelse
         L =:= critical orelse
         L =:= error orelse
@@ -85,14 +85,14 @@ start_trace(Who, Level, LogFile) ->
             try logger:compare_levels(Level, PrimaryLevel) of
                 lt ->
                     {error,
-                        io_lib:format("Cannot trace at a log level (~s) "
-                        "lower than the primary log level (~s)",
-                            [Level, PrimaryLevel])};
+                     io_lib:format("Cannot trace at a log level (~s) "
+                                   "lower than the primary log level (~s)",
+                                   [Level, PrimaryLevel])};
                 _GtOrEq ->
                     install_trace_handler(Who, Level, LogFile)
             catch
                 _:Error ->
-                    {error, Error}
+                {error, Error}
             end;
         false -> {error, {invalid_log_level, Level}}
     end.
@@ -109,12 +109,12 @@ lookup_traces() ->
 
 install_trace_handler(Who, Level, LogFile) ->
     case logger:add_handler(handler_id(Who), logger_disk_log_h,
-        #{level => Level,
-            formatter => ?FORMAT,
-            config => #{type => halt, file => LogFile},
-            filter_default => stop,
-            filters => [{meta_key_filter,
-                {fun filter_by_meta_key/2, Who}}]})
+                            #{level => Level,
+                              formatter => ?FORMAT,
+                              config => #{type => halt, file => LogFile},
+                              filter_default => stop,
+                              filters => [{meta_key_filter,
+                                          {fun filter_by_meta_key/2, Who}}]})
     of
         ok ->
             ?LOG(info, "Start trace for ~p", [Who]);
@@ -134,7 +134,7 @@ uninstall_trance_handler(Who) ->
 
 filter_traces(#{id := Id, level := Level, dst := Dst}, Acc) ->
     case atom_to_list(Id) of
-        ?TOPIC_TRACE_ID(T) ->
+        ?TOPIC_TRACE_ID(T)->
             [{?TOPIC_TRACE(T), {Level, Dst}} | Acc];
         ?CLIENT_TRACE_ID(C) ->
             [{?CLIENT_TRACE(C), {Level, Dst}} | Acc];
diff --git a/src/emqx_types.erl b/src/emqx_types.erl
index fbe62e4b23..3e53eafd17 100644
--- a/src/emqx_types.erl
+++ b/src/emqx_types.erl
@@ -94,7 +94,10 @@
 -type(ver() :: ?MQTT_PROTO_V3
              | ?MQTT_PROTO_V4
              | ?MQTT_PROTO_V5
-             | non_neg_integer()).
+             | non_neg_integer()
+             %% Some non-MQTT versions of protocol may be a binary type
+             | binary()
+             ).
 
 -type(qos() :: ?QOS_0 | ?QOS_1 | ?QOS_2).
 -type(qos_name() :: qos0 | at_most_once |
diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl
index d686e1611a..07d437b4b9 100644
--- a/src/emqx_ws_connection.erl
+++ b/src/emqx_ws_connection.erl
@@ -242,7 +242,7 @@ parse_header_fun_origin(Req, Opts) ->
             Origins = proplists:get_value(check_origins, Opts, []),
             case lists:member(Value, Origins) of
                 true -> ok;
-                false -> {origin_not_allowed, Value}
+                false -> {error, {origin_not_allowed, Value}}
             end
     end.
 
diff --git a/test/emqx_broker_SUITE.erl b/test/emqx_broker_SUITE.erl
index 571a8483c6..4cef68660c 100644
--- a/test/emqx_broker_SUITE.erl
+++ b/test/emqx_broker_SUITE.erl
@@ -37,23 +37,35 @@ init_per_suite(Config) ->
 end_per_suite(_Config) ->
     emqx_ct_helpers:stop_apps([]).
 
+init_per_testcase(Case, Config) ->
+    ?MODULE:Case({init, Config}).
+
+end_per_testcase(Case, Config) ->
+    ?MODULE:Case({'end', Config}).
+
 %%--------------------------------------------------------------------
 %% PubSub Test
 %%--------------------------------------------------------------------
 
-t_subscribed(_) ->
+t_subscribed({init, Config}) ->
     emqx_broker:subscribe(<<"topic">>),
+    Config;
+t_subscribed(Config) when is_list(Config) ->
     ?assertEqual(false, emqx_broker:subscribed(undefined, <<"topic">>)),
-    ?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)),
+    ?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>));
+t_subscribed({'end', _Config}) ->
     emqx_broker:unsubscribe(<<"topic">>).
 
-t_subscribed_2(_) ->
+t_subscribed_2({init, Config}) ->
     emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
-    %?assertEqual(true, emqx_broker:subscribed(<<"clientid">>, <<"topic">>)),
-    ?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)),
+    Config;
+t_subscribed_2(Config) when is_list(Config) ->
+    ?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>));
+t_subscribed_2({'end', _Config}) ->
     emqx_broker:unsubscribe(<<"topic">>).
 
-t_subopts(_) ->
+t_subopts({init, Config}) -> Config;
+t_subopts(Config) when is_list(Config) ->
     ?assertEqual(false, emqx_broker:set_subopts(<<"topic">>, #{qos => 1})),
     ?assertEqual(undefined, emqx_broker:get_subopts(self(), <<"topic">>)),
     ?assertEqual(undefined, emqx_broker:get_subopts(<<"clientid">>, <<"topic">>)),
@@ -70,42 +82,54 @@ t_subopts(_) ->
 
     ?assertEqual(true, emqx_broker:set_subopts(<<"topic">>, #{qos => 0})),
     ?assertEqual(#{nl => 0, qos => 0, rap => 0, rh => 0, subid => <<"clientid">>},
-                 emqx_broker:get_subopts(self(), <<"topic">>)),
+                 emqx_broker:get_subopts(self(), <<"topic">>));
+t_subopts({'end', _Config}) ->
     emqx_broker:unsubscribe(<<"topic">>).
 
-t_topics(_) ->
+t_topics({init, Config}) ->
     Topics = [<<"topic">>, <<"topic/1">>, <<"topic/2">>],
-    ok = emqx_broker:subscribe(lists:nth(1, Topics), <<"clientId">>),
-    ok = emqx_broker:subscribe(lists:nth(2, Topics), <<"clientId">>),
-    ok = emqx_broker:subscribe(lists:nth(3, Topics), <<"clientId">>),
+    [{topics, Topics} | Config];
+t_topics(Config) when is_list(Config) ->
+    Topics = [T1, T2, T3] = proplists:get_value(topics, Config),
+    ok = emqx_broker:subscribe(T1, <<"clientId">>),
+    ok = emqx_broker:subscribe(T2, <<"clientId">>),
+    ok = emqx_broker:subscribe(T3, <<"clientId">>),
     Topics1 = emqx_broker:topics(),
     ?assertEqual(true, lists:foldl(fun(Topic, Acc) ->
                                        case lists:member(Topic, Topics1) of
                                            true -> Acc;
                                            false -> false
                                        end
-                                   end, true, Topics)),
-    emqx_broker:unsubscribe(lists:nth(1, Topics)),
-    emqx_broker:unsubscribe(lists:nth(2, Topics)),
-    emqx_broker:unsubscribe(lists:nth(3, Topics)).
+                                   end, true, Topics));
+t_topics({'end', Config}) ->
+    Topics = proplists:get_value(topics, Config),
+    lists:foreach(fun(T) -> emqx_broker:unsubscribe(T) end, Topics).
 
-t_subscribers(_) ->
+t_subscribers({init, Config}) ->
     emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
-    ?assertEqual([self()], emqx_broker:subscribers(<<"topic">>)),
+    Config;
+t_subscribers(Config) when is_list(Config) ->
+    ?assertEqual([self()], emqx_broker:subscribers(<<"topic">>));
+t_subscribers({'end', _Config}) ->
     emqx_broker:unsubscribe(<<"topic">>).
 
-t_subscriptions(_) ->
+t_subscriptions({init, Config}) ->
     emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{qos => 1}),
-    ok = timer:sleep(100),
+    Config;
+t_subscriptions(Config) when is_list(Config) ->
+    ct:sleep(100),
     ?assertEqual(#{nl => 0, qos => 1, rap => 0, rh => 0, subid => <<"clientid">>},
                  proplists:get_value(<<"topic">>, emqx_broker:subscriptions(self()))),
     ?assertEqual(#{nl => 0, qos => 1, rap => 0, rh => 0, subid => <<"clientid">>},
-                 proplists:get_value(<<"topic">>, emqx_broker:subscriptions(<<"clientid">>))),
+                 proplists:get_value(<<"topic">>, emqx_broker:subscriptions(<<"clientid">>)));
+t_subscriptions({'end', _Config}) ->
     emqx_broker:unsubscribe(<<"topic">>).
 
-t_sub_pub(_) ->
+t_sub_pub({init, Config}) ->
     ok = emqx_broker:subscribe(<<"topic">>),
-    ct:sleep(10),
+    Config;
+t_sub_pub(Config) when is_list(Config) ->
+    ct:sleep(100),
     emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
     ?assert(
         receive
@@ -115,16 +139,22 @@ t_sub_pub(_) ->
                 false
         after 100 ->
             false
-        end).
+        end);
+t_sub_pub({'end', _Config}) ->
+    ok = emqx_broker:unsubscribe(<<"topic">>).
 
-t_nosub_pub(_) ->
+t_nosub_pub({init, Config}) -> Config;
+t_nosub_pub({'end', _Config}) -> ok;
+t_nosub_pub(Config) when is_list(Config) ->
     ?assertEqual(0, emqx_metrics:val('messages.dropped')),
     emqx_broker:publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
     ?assertEqual(1, emqx_metrics:val('messages.dropped')).
 
-t_shared_subscribe(_) ->
+t_shared_subscribe({init, Config}) ->
     emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{share => <<"group">>}),
-    ct:sleep(10),
+    ct:sleep(100),
+    Config;
+t_shared_subscribe(Config) when is_list(Config) ->
     emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
     ?assert(receive
                 {deliver, <<"topic">>, #message{payload = <<"hello">>}} ->
@@ -134,9 +164,12 @@ t_shared_subscribe(_) ->
                     false
             after 100 ->
                 false
-            end),
+            end);
+t_shared_subscribe({'end', _Config}) ->
     emqx_broker:unsubscribe(<<"$share/group/topic">>).
 
+t_shared_subscribe_2({init, Config}) -> Config;
+t_shared_subscribe_2({'end', _Config}) -> ok;
 t_shared_subscribe_2(_) ->
     {ok, ConnPid} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid">>}]),
     {ok, _} = emqtt:connect(ConnPid),
@@ -158,6 +191,8 @@ t_shared_subscribe_2(_) ->
     emqtt:disconnect(ConnPid),
     emqtt:disconnect(ConnPid2).
 
+t_shared_subscribe_3({init, Config}) -> Config;
+t_shared_subscribe_3({'end', _Config}) -> ok;
 t_shared_subscribe_3(_) ->
     {ok, ConnPid} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid">>}]),
     {ok, _} = emqtt:connect(ConnPid),
@@ -174,11 +209,13 @@ t_shared_subscribe_3(_) ->
     emqtt:disconnect(ConnPid),
     emqtt:disconnect(ConnPid2).
 
-t_shard(_) ->
+t_shard({init, Config}) ->
     ok = meck:new(emqx_broker_helper, [passthrough, no_history]),
     ok = meck:expect(emqx_broker_helper, get_sub_shard, fun(_, _) -> 1 end),
     emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
-    ct:sleep(10),
+    Config;
+t_shard(Config) when is_list(Config) ->
+    ct:sleep(100),
     emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)),
     ?assert(
         receive
@@ -188,23 +225,57 @@ t_shard(_) ->
                 false
         after 100 ->
             false
-        end),
+        end);
+t_shard({'end', _Config}) ->
+    emqx_broker:unsubscribe(<<"topic">>),
     ok = meck:unload(emqx_broker_helper).
 
-t_stats_fun(_) ->
-    N = emqx_stats:getstat('subscribers.count'),
-    N = emqx_stats:getstat('subscriptions.count'),
-    N = emqx_stats:getstat('suboptions.count'),
+t_stats_fun({init, Config}) ->
+    Parent = self(),
+    F = fun Loop() ->
+                N1 = emqx_stats:getstat('subscribers.count'),
+                N2 = emqx_stats:getstat('subscriptions.count'),
+                N3 = emqx_stats:getstat('suboptions.count'),
+                case N1 + N2 + N3 =:= 0 of
+                    true ->
+                        Parent ! {ready, self()},
+                        exit(normal);
+                    false ->
+                        receive
+                            stop ->
+                                exit(normal)
+                        after
+                            100 ->
+                                Loop()
+                        end
+                end
+        end,
+    Pid = spawn_link(F),
+    receive
+        {ready, P} when P =:= Pid->
+            Config
+    after
+        5000 ->
+            Pid ! stop,
+            ct:fail("timedout_waiting_for_sub_stats_to_reach_zero")
+    end;
+t_stats_fun(Config) when is_list(Config) ->
     ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>),
     ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>),
+    %% ensure stats refreshed
     emqx_broker:stats_fun(),
-    ct:sleep(10),
-    ?assertEqual(N + 2, emqx_stats:getstat('subscribers.count')),
-    ?assertEqual(N + 2, emqx_stats:getstat('subscribers.max')),
-    ?assertEqual(N + 2, emqx_stats:getstat('subscriptions.count')),
-    ?assertEqual(N + 2, emqx_stats:getstat('subscriptions.max')),
-    ?assertEqual(N + 2, emqx_stats:getstat('suboptions.count')),
-    ?assertEqual(N + 2, emqx_stats:getstat('suboptions.max')).
+    %% emqx_stats:set_stat is a gen_server cast
+    %% make a synced call sync
+    ignored = gen_server:call(emqx_stats, call, infinity),
+    ?assertEqual(2, emqx_stats:getstat('subscribers.count')),
+    ?assertEqual(2, emqx_stats:getstat('subscribers.max')),
+    ?assertEqual(2, emqx_stats:getstat('subscriptions.count')),
+    ?assertEqual(2, emqx_stats:getstat('subscriptions.max')),
+    ?assertEqual(2, emqx_stats:getstat('suboptions.count')),
+    ?assertEqual(2, emqx_stats:getstat('suboptions.max'));
+t_stats_fun({'end', _Config}) ->
+    ok = emqx_broker:unsubscribe(<<"topic">>),
+    ok = emqx_broker:unsubscribe(<<"topic2">>).
 
 recv_msgs(Count) ->
     recv_msgs(Count, []).
diff --git a/test/emqx_cm_SUITE.erl b/test/emqx_cm_SUITE.erl
index 3f6950b3b0..acafeb36fa 100644
--- a/test/emqx_cm_SUITE.erl
+++ b/test/emqx_cm_SUITE.erl
@@ -32,6 +32,12 @@
                      conn_mod => emqx_connection,
                      receive_maximum => 100}}).
 
+-define(WAIT(PATTERN, TIMEOUT, RET),
+        fun() ->
+                receive PATTERN -> RET
+                after TIMEOUT -> error({timeout, ?LINE}) end
+        end()).
+
 %%--------------------------------------------------------------------
 %% CT callbacks
 %%--------------------------------------------------------------------
@@ -141,11 +147,8 @@ t_open_session_race_condition(_) ->
                      end
                    end,
     N = 1000,
-    [spawn(
-      fun() ->
-              spawn(OpenASession),
-              spawn(OpenASession)
-      end) || _ <- lists:seq(1, N)],
+    Pids = lists:flatten([[spawn_monitor(OpenASession), spawn_monitor(OpenASession)] ||
+                          _ <- lists:seq(1, N)]),
 
     WaitingRecv = fun _Wr(N1, N2, 0) ->
                           {N1, N2};
@@ -158,35 +161,120 @@ t_open_session_race_condition(_) ->
 
     {Succeeded, Failed} = WaitingRecv(0, 0, 2 * N),
     ct:pal("Race condition status: succeeded=~p failed=~p~n", [Succeeded, Failed]),
+    ?assertEqual(2 * N, length(Pids)),
+    WaitForDowns =
+        fun _Wd([{Pid, _Ref}]) -> Pid;
+            _Wd(Pids0) ->
+                receive
+                    {'DOWN', DownRef, process, DownPid, _} ->
+                        ?assert(lists:member({DownPid, DownRef}, Pids0)),
+                        _Wd(lists:delete({DownPid, DownRef}, Pids0))
+                after
+                    10000 ->
+                        exit(timeout)
+                end
+        end,
+    Winner = WaitForDowns(Pids),
 
     ?assertMatch([_], ets:lookup(emqx_channel, ClientId)),
-    [Pid] = emqx_cm:lookup_channels(ClientId),
-    ?assertMatch([_], ets:lookup(emqx_channel_conn, {ClientId, Pid})),
+    ?assertEqual([Winner], emqx_cm:lookup_channels(ClientId)),
+    ?assertMatch([_], ets:lookup(emqx_channel_conn, {ClientId, Winner})),
     ?assertMatch([_], ets:lookup(emqx_channel_registry, ClientId)),
 
-    exit(Pid, kill),
-    timer:sleep(100), %% TODO deterministic
+    exit(Winner, kill),
+    receive {'DOWN', _, process, Winner, _} -> ok end,
+    ignored = gen_server:call(emqx_cm, ignore, infinity), %% sync
     ?assertEqual([], emqx_cm:lookup_channels(ClientId)).
 
-t_discard_session(_) ->
+t_kick_session_discard_normal(_) ->
+    test_kick_session(discard, normal).
+
+t_kick_session_discard_shutdown(_) ->
+    test_kick_session(discard, shutdown).
+
+t_kick_session_discard_shutdown_with_reason(_) ->
+    test_kick_session(discard, {shutdown, discard}).
+
+t_kick_session_discard_timeout(_) ->
+    test_kick_session(discard, timeout).
+
+t_kick_session_discard_noproc(_) ->
+    test_kick_session(discard, noproc).
+
+t_kick_session_kick_normal(_) ->
+    test_kick_session(discard, normal).
+
+t_kick_session_kick_shutdown(_) ->
+    test_kick_session(discard, shutdown).
+
+t_kick_session_kick_shutdown_with_reason(_) ->
+    test_kick_session(discard, {shutdown, discard}).
+
+t_kick_session_kick_timeout(_) ->
+    test_kick_session(discard, timeout).
+
+t_kick_session_kick_noproc(_) ->
+    test_kick_session(discard, noproc).
+
+test_kick_session(Action, Reason) ->
     ClientId = rand_client_id(),
     #{conninfo := ConnInfo} = ?ChanInfo,
-    ok = emqx_cm:register_channel(ClientId, self(), ConnInfo),
+    FakeSessionFun =
+        fun Loop() ->
+                     receive
+                         {'$gen_call', From, A} when A =:= kick orelse
+                                                     A =:= discard ->
+                             case Reason of
+                                 normal ->
+                                     gen_server:reply(From, ok);
+                                 timeout ->
+                                     %% no response to the call
+                                     Loop();
+                                 _ ->
+                                     exit(Reason)
+                             end;
+                         Msg ->
+                             ct:pal("(~p) fake_session_discarded ~p", [Action, Msg]),
+                             Loop()
+                     end
+        end,
+    {Pid1, _} = spawn_monitor(FakeSessionFun),
+    {Pid2, _} = spawn_monitor(FakeSessionFun),
+    ok = emqx_cm:register_channel(ClientId, Pid1, ConnInfo),
+    ok = emqx_cm:register_channel(ClientId, Pid1, ConnInfo),
+    ok = emqx_cm:register_channel(ClientId, Pid2, ConnInfo),
+    ?assertEqual([Pid1, Pid2], lists:sort(emqx_cm:lookup_channels(ClientId))),
+    case Reason of
+        noproc -> exit(Pid1, kill), exit(Pid2, kill);
+        _ -> ok
+    end,
+    ok = case Action of
+             kick -> emqx_cm:kick_session(ClientId);
+             discard -> emqx_cm:discard_session(ClientId)
+         end,
+    case Reason =:= timeout orelse Reason =:= noproc of
+        true ->
+            ?assertEqual(killed, ?WAIT({'DOWN', _, process, Pid1, R}, 2_000, R)),
+            ?assertEqual(killed, ?WAIT({'DOWN', _, process, Pid2, R}, 2_000, R));
+        false ->
+            ?assertEqual(Reason, ?WAIT({'DOWN', _, process, Pid1, R}, 2_000, R)),
+            ?assertEqual(Reason, ?WAIT({'DOWN', _, process, Pid2, R}, 2_000, R))
+    end,
+    ok = flush_emqx_pool(),
+    ?assertEqual([], emqx_cm:lookup_channels(ClientId)).
 
-    ok = meck:new(emqx_connection, [passthrough, no_history]),
-    ok = meck:expect(emqx_connection, call, fun(_, _) -> ok end),
-    ok = meck:expect(emqx_connection, call, fun(_, _, _) -> ok end),
-    ok = emqx_cm:discard_session(ClientId),
-    ok = emqx_cm:register_channel(ClientId, self(), ConnInfo),
-    ok = emqx_cm:discard_session(ClientId),
-    ok = emqx_cm:unregister_channel(ClientId),
-    ok = emqx_cm:register_channel(ClientId, self(), ConnInfo),
-    ok = emqx_cm:discard_session(ClientId),
-    ok = meck:expect(emqx_connection, call, fun(_, _) -> error(testing) end),
-    ok = meck:expect(emqx_connection, call, fun(_, _, _) -> error(testing) end),
-    ok = emqx_cm:discard_session(ClientId),
-    ok = emqx_cm:unregister_channel(ClientId),
-    ok = meck:unload(emqx_connection).
+%% Channel deregistration is delegated to emqx_pool as a sync tasks.
+%% The emqx_pool is pool of workers, and there is no way to know
+%% which worker was picked for the last deregistration task.
+%% This help function creates a large enough number of async tasks
+%% to sync with the pool workers.
+%% The number of tasks should be large enough to ensure all workers have
+%% the chance to work on at least one of the tasks.
+flush_emqx_pool() ->
+    Self = self(),
+    L = lists:seq(1, 1000),
+    lists:foreach(fun(I) -> emqx_pool:async_submit(fun() -> Self ! {done, I} end, []) end, L),
+    lists:foreach(fun(I) -> receive {done, I} -> ok end end, L).
 
 t_discard_session_race(_) ->
     ClientId = rand_client_id(),
@@ -219,27 +307,6 @@ t_takeover_session(_) ->
     {ok, emqx_connection, _, test} = emqx_cm:takeover_session(<<"clientid">>),
     emqx_cm:unregister_channel(<<"clientid">>).
 
-t_kick_session(_) ->
-    Info = #{conninfo := ConnInfo} = ?ChanInfo,
-    ok = meck:new(emqx_connection, [passthrough, no_history]),
-    ok = meck:expect(emqx_connection, call, fun(_, _) -> test end),
-    ok = meck:expect(emqx_connection, call, fun(_, _, _) -> test end),
-    {error, not_found} = emqx_cm:kick_session(<<"clientid">>),
-    ok = emqx_cm:register_channel(<<"clientid">>, self(), ConnInfo),
-    ok = emqx_cm:insert_channel_info(<<"clientid">>, Info, []),
-    test = emqx_cm:kick_session(<<"clientid">>),
-    erlang:spawn_link(
-        fun() ->
-            ok = emqx_cm:register_channel(<<"clientid">>, self(), ConnInfo),
-            ok = emqx_cm:insert_channel_info(<<"clientid">>, Info, []),
-
-            timer:sleep(1000)
-        end),
-    ct:sleep(100),
-    test = emqx_cm:kick_session(<<"clientid">>),
-    ok = emqx_cm:unregister_channel(<<"clientid">>),
-    ok = meck:unload(emqx_connection).
-
 t_all_channels(_) ->
     ?assertEqual(true, is_list(emqx_cm:all_channels())).
 
diff --git a/test/emqx_frame_SUITE.erl b/test/emqx_frame_SUITE.erl
index 09206cee19..591c75bdcb 100644
--- a/test/emqx_frame_SUITE.erl
+++ b/test/emqx_frame_SUITE.erl
@@ -137,6 +137,11 @@ t_parse_frame_malformed_variable_byte_integer(_) ->
     ?catch_error(malformed_variable_byte_integer,
         emqx_frame:parse(MalformedPayload, ParseState)).
 
+t_parse_frame_variable_byte_integer(_) ->
+    Bin = <<2#10010011, 2#10000000, 2#10001000, 2#10011001, 2#10101101, 2#00110010>>,
+    ?catch_error(malformed_variable_byte_integer,
+        emqx_frame:parse_variable_byte_integer(Bin)).
+
 t_serialize_parse_v3_connect(_) ->
     Bin = <<16,37,0,6,77,81,73,115,100,112,3,2,0,60,0,23,109,111,115,
             113,112,117, 98,47,49,48,52,53,49,45,105,77,97,99,46,108,
diff --git a/test/emqx_mqtt_SUITE.erl b/test/emqx_mqtt_SUITE.erl
index c86d6334a2..3a77134b8f 100644
--- a/test/emqx_mqtt_SUITE.erl
+++ b/test/emqx_mqtt_SUITE.erl
@@ -62,79 +62,104 @@ t_conn_stats(_) ->
 t_tcp_sock_passive(_) ->
     with_client(fun(CPid) -> CPid ! {tcp_passive, sock} end, []).
 
-t_message_expiry_interval_1(_) ->
-    ClientA = message_expiry_interval_init(),
-    [message_expiry_interval_exipred(ClientA, QoS) || QoS <- [0,1,2]],
-    emqtt:stop(ClientA).
-
-t_message_expiry_interval_2(_) ->
-    ClientA = message_expiry_interval_init(),
-    [message_expiry_interval_not_exipred(ClientA, QoS) || QoS <- [0,1,2]],
-    emqtt:stop(ClientA).
+t_message_expiry_interval(_) ->
+    {CPublish, CControl} = message_expiry_interval_init(),
+    [message_expiry_interval_exipred(CPublish, CControl, QoS) || QoS <- [0,1,2]],
+    emqtt:stop(CPublish),
+    emqtt:stop(CControl).
+
+t_message_not_expiry_interval(_) ->
+    {CPublish, CControl} = message_expiry_interval_init(),
+    [message_expiry_interval_not_exipred(CPublish, CControl, QoS) || QoS <- [0,1,2]],
+    emqtt:stop(CPublish),
+    emqtt:stop(CControl).
 
 message_expiry_interval_init() ->
-    {ok, ClientA} = emqtt:start_link([{proto_ver,v5},
-                                      {clientid, <<"client-a">>},
+    {ok, CPublish} = emqtt:start_link([{proto_ver,v5},
+                                      {clientid, <<"Client-Publish">>},
+                                      {clean_start, false},
+                                      {properties, #{'Session-Expiry-Interval' => 360}}]),
+    {ok, CVerify} = emqtt:start_link([{proto_ver,v5},
+                                      {clientid, <<"Client-Verify">>},
                                       {clean_start, false},
                                       {properties, #{'Session-Expiry-Interval' => 360}}]),
-    {ok, ClientB} = emqtt:start_link([{proto_ver,v5},
-                                      {clientid, <<"client-b">>},
+    {ok, CControl} = emqtt:start_link([{proto_ver,v5},
+                                      {clientid, <<"Client-Control">>},
                                       {clean_start, false},
                                       {properties, #{'Session-Expiry-Interval' => 360}}]),
-    {ok, _} = emqtt:connect(ClientA),
-    {ok, _} = emqtt:connect(ClientB),
-        %% subscribe and disconnect client-b
-    emqtt:subscribe(ClientB, <<"t/a">>, 1),
-    emqtt:stop(ClientB),
-    ClientA.
-
-message_expiry_interval_exipred(ClientA, QoS) ->
+    {ok, _} = emqtt:connect(CPublish),
+    {ok, _} = emqtt:connect(CVerify),
+    {ok, _} = emqtt:connect(CControl),
+        %% subscribe and disconnect Client-verify
+    emqtt:subscribe(CControl, <<"t/a">>, 1),
+    emqtt:subscribe(CVerify, <<"t/a">>, 1),
+    emqtt:stop(CVerify),
+    {CPublish, CControl}.
+
+message_expiry_interval_exipred(CPublish, CControl, QoS) ->
     ct:pal("~p ~p", [?FUNCTION_NAME, QoS]),
     %% publish to t/a and waiting for the message expired
-    emqtt:publish(ClientA, <<"t/a">>, #{'Message-Expiry-Interval' => 1}, <<"this will be purged in 1s">>, [{qos, QoS}]),
-    ct:sleep(1500),
+    emqtt:publish(CPublish, <<"t/a">>, #{'Message-Expiry-Interval' => 1},
+        <<"this will be purged in 1s">>, [{qos, QoS}]),
+    %% CControl make sure publish already store in broker.
+    receive
+        {publish,#{client_pid := CControl, topic := <<"t/a">>}} ->
+            ok
+    after 1000 ->
+        ct:fail(should_receive_publish)
+    end,
+    ct:sleep(1100),
 
-    %% resume the session for client-b
-    {ok, ClientB1} = emqtt:start_link([{proto_ver,v5},
-                                       {clientid, <<"client-b">>},
+    %% resume the session for Client-Verify
+    {ok, CVerify} = emqtt:start_link([{proto_ver,v5},
+                                       {clientid, <<"Client-Verify">>},
                                        {clean_start, false},
                                        {properties, #{'Session-Expiry-Interval' => 360}}]),
-    {ok, _} = emqtt:connect(ClientB1),
+    {ok, _} = emqtt:connect(CVerify),
 
-    %% verify client-b could not receive the publish message
+    %% verify Client-Verify could not receive the publish message
     receive
-        {publish,#{client_pid := ClientB1, topic := <<"t/a">>}} ->
+        {publish,#{client_pid := CVerify, topic := <<"t/a">>}} ->
             ct:fail(should_have_expired)
     after 300 ->
         ok
     end,
-    emqtt:stop(ClientB1).
+    emqtt:stop(CVerify).
 
-message_expiry_interval_not_exipred(ClientA, QoS) ->
+message_expiry_interval_not_exipred(CPublish, CControl, QoS) ->
     ct:pal("~p ~p", [?FUNCTION_NAME, QoS]),
     %% publish to t/a
-    emqtt:publish(ClientA, <<"t/a">>, #{'Message-Expiry-Interval' => 20}, <<"this will be purged in 1s">>, [{qos, QoS}]),
+    emqtt:publish(CPublish, <<"t/a">>, #{'Message-Expiry-Interval' => 20},
+        <<"this will be purged in 20s">>, [{qos, QoS}]),
 
-    %% wait for 1s and then resume the session for client-b, the message should not expires
+    %% CControl make sure publish already store in broker.
+    receive
+        {publish,#{client_pid := CControl, topic := <<"t/a">>}} ->
+            ok
+    after 1000 ->
+        ct:fail(should_receive_publish)
+    end,
+
+    %% wait for 1.2s and then resume the session for Client-Verify, the message should not expires
     %% as Message-Expiry-Interval = 20s
-    ct:sleep(1000),
-    {ok, ClientB1} = emqtt:start_link([{proto_ver,v5},
-                                       {clientid, <<"client-b">>},
+    ct:sleep(1200),
+    {ok, CVerify} = emqtt:start_link([{proto_ver,v5},
+                                       {clientid, <<"Client-Verify">>},
                                        {clean_start, false},
                                        {properties, #{'Session-Expiry-Interval' => 360}}]),
-    {ok, _} = emqtt:connect(ClientB1),
+    {ok, _} = emqtt:connect(CVerify),
 
-	%% verify client-b could receive the publish message and the Message-Expiry-Interval is set
+	%% verify Client-Verify could receive the publish message and the Message-Expiry-Interval is set
 	receive
-		{publish,#{client_pid := ClientB1, topic := <<"t/a">>,
+		{publish,#{client_pid := CVerify, topic := <<"t/a">>,
 					properties := #{'Message-Expiry-Interval' := MsgExpItvl}}}
-			when MsgExpItvl < 20 -> ok;
+			when MsgExpItvl =< 20 -> ok;
 		{publish, _} = Msg ->
 			ct:fail({incorrect_publish, Msg})
 	after 300 ->
 		ct:fail(no_publish_received)
 	end,
-	emqtt:stop(ClientB1).
+	emqtt:stop(CVerify).
 
 with_client(TestFun, _Options) ->
     ClientId = <<"t_conn">>,
@@ -156,6 +181,15 @@ t_async_set_keepalive('end', _Config) ->
     ok.
 
 t_async_set_keepalive(_) ->
+    case os:type() of
+        {unix, darwin} ->
+            %% Mac OSX don't support the feature
+            ok;
+        _ ->
+            do_async_set_keepalive()
+    end.
+
+do_async_set_keepalive() ->
     ClientID = <<"client-tcp-keepalive">>,
     {ok, Client} = emqtt:start_link([{host, "localhost"},
                                      {proto_ver,v5},
diff --git a/test/emqx_mqueue_SUITE.erl b/test/emqx_mqueue_SUITE.erl
index 34e5091451..599e68da15 100644
--- a/test/emqx_mqueue_SUITE.erl
+++ b/test/emqx_mqueue_SUITE.erl
@@ -22,6 +22,7 @@
 -include_lib("emqx/include/emqx.hrl").
 -include_lib("emqx/include/emqx_mqtt.hrl").
 
+-include_lib("proper/include/proper.hrl").
 -include_lib("eunit/include/eunit.hrl").
 
 -define(Q, emqx_mqueue).
@@ -121,8 +122,55 @@ t_priority_mqueue(_) ->
     {_, Q6} = ?Q:in(#message{qos = 1, topic = <<"t2">>}, Q5),
     ?assertEqual(5, ?Q:len(Q6)),
     {{value, Msg}, Q7} = ?Q:out(Q6),
-    ?assertEqual(4, ?Q:len(Q7)),
-    ?assertEqual(<<"t3">>, Msg#message.topic).
+    ?assertEqual(4, ?Q:len(Q7)).
+
+t_priority_mqueue_conservation(_) ->
+    true = proper:quickcheck(conservation_prop()).
+
+t_priority_order(_) ->
+    Opts = #{max_len => 5,
+             shift_multiplier => 1,
+             priorities =>
+                 #{<<"t1">> => 0,
+                   <<"t2">> => 1,
+                   <<"t3">> => 2
+                  },
+            store_qos0 => false
+            },
+    Messages = [{Topic, Message} ||
+                   Topic <- [<<"t1">>, <<"t2">>, <<"t3">>],
+                   Message <- lists:seq(1, 10)],
+    Q = lists:foldl(fun({Topic, Message}, Q) ->
+                            element(2, ?Q:in(#message{topic = Topic, qos = 1, payload = Message}, Q))
+                    end,
+                    ?Q:init(Opts),
+                    Messages),
+    ?assertMatch([{<<"t3">>, 6},
+                  {<<"t3">>, 7},
+                  {<<"t3">>, 8},
+
+                  {<<"t2">>, 6},
+                  {<<"t2">>, 7},
+
+                  {<<"t1">>, 6},
+
+                  {<<"t3">>, 9},
+                  {<<"t3">>, 10},
+
+                  {<<"t2">>, 8},
+
+                  %% Note: for performance reasons we don't reset the
+                  %% counter when we run out of messages with the
+                  %% current prio, so next is t1:
+                  {<<"t1">>, 7},
+
+                  {<<"t2">>, 9},
+                  {<<"t2">>, 10},
+
+                  {<<"t1">>, 8},
+                  {<<"t1">>, 9},
+                  {<<"t1">>, 10}
+                 ], drain(Q)).
 
 t_infinity_priority_mqueue(_) ->
     Opts = #{max_len => 0,
@@ -163,3 +211,84 @@ t_dropped(_) ->
     {Msg, Q2} = ?Q:in(Msg, Q1),
     ?assertEqual(1, ?Q:dropped(Q2)).
 
+t_live_upgrade(_) ->
+    Q = {mqueue,true,1,0,0,none,0,
+                   {queue,[],[],0}},
+    ?assertMatch(#{}, ?Q:info(Q)),
+    ?assertMatch(true, ?Q:is_empty(Q)),
+    ?assertMatch(0, ?Q:len(Q)),
+    ?assertMatch(1, ?Q:max_len(Q)),
+    ?assertMatch({undefined, _}, ?Q:in(#message{qos = 0, topic = <<>>}, Q)),
+    ?assertMatch({empty, _}, ?Q:out(Q)),
+    ?assertMatch([_|_], ?Q:stats(Q)),
+    ?assertMatch(0, ?Q:dropped(Q)).
+
+
+t_live_upgrade2(_) ->
+    Q = {mqueue,false,10,0,0,
+                   #{<<"t">> => 1},
+                   0,
+                   {queue,[],[],0}},
+    ?assertMatch(#{}, ?Q:info(Q)),
+    ?assertMatch(true, ?Q:is_empty(Q)),
+    ?assertMatch(0, ?Q:len(Q)),
+    ?assertMatch(10, ?Q:max_len(Q)),
+    ?assertMatch({_, _}, ?Q:in(#message{qos = 0, topic = <<>>}, Q)),
+    ?assertMatch({empty, _}, ?Q:out(Q)),
+    ?assertMatch([_|_], ?Q:stats(Q)),
+    ?assertMatch(0, ?Q:dropped(Q)).
+
+conservation_prop() ->
+    ?FORALL({Priorities, Messages},
+            ?LET(Priorities, topic_priorities(),
+                 {Priorities, messages(Priorities)}),
+            try
+                Opts = #{max_len => 0,
+                         priorities => maps:from_list(Priorities),
+                         store_qos0 => false},
+                %% Put messages in
+                Q1 = lists:foldl(fun({Topic, Message}, Q) ->
+                                         element(2, ?Q:in(#message{topic = Topic, qos = 1, payload = Message}, Q))
+                                 end,
+                                 ?Q:init(Opts),
+                                 Messages),
+                %% Collect messages
+                Got = lists:sort(drain(Q1)),
+                Expected = lists:sort(Messages),
+                case Expected =:= Got of
+                    true ->
+                        true;
+                    false ->
+                        ct:pal("Mismatch: expected ~p~nGot ~p~n", [Expected, Got]),
+                        false
+                end
+            catch
+                EC:Err:Stack ->
+                    ct:pal("Error: ~p", [{EC, Err, Stack}]),
+                    false
+            end).
+
+%% Proper generators:
+
+topic(Priorities) ->
+    {Topics, _} = lists:unzip(Priorities),
+    oneof(Topics).
+
+topic_priorities() ->
+    non_empty(list({binary(), priority()})).
+
+priority() ->
+    oneof([integer(), infinity]).
+
+messages(Topics) ->
+    list({topic(Topics), binary()}).
+
+%% Internal functions:
+
+drain(Q) ->
+    case ?Q:out(Q) of
+        {empty, _} ->
+            [];
+        {{value, #message{topic = T, payload = P}}, Q1} ->
+            [{T, P}|drain(Q1)]
+    end.
diff --git a/test/emqx_ws_connection_SUITE.erl b/test/emqx_ws_connection_SUITE.erl
index 6db8319729..56d038c23d 100644
--- a/test/emqx_ws_connection_SUITE.erl
+++ b/test/emqx_ws_connection_SUITE.erl
@@ -247,7 +247,7 @@ t_ws_check_origin(_) ->
     ?assertMatch({gun_upgrade, _},
         start_ws_client(#{protocols => [<<"mqtt">>],
                           headers => [{<<"origin">>, <<"http://localhost:18083">>}]})),
-    ?assertMatch({gun_response, {_, 500, _}},
+    ?assertMatch({gun_response, {_, 403, _}},
         start_ws_client(#{protocols => [<<"mqtt">>],
                           headers => [{<<"origin">>, <<"http://localhost:18080">>}]})),
     emqx_ct_helpers:stop_apps([]).