diff --git a/.gitignore b/.gitignore index 19797d37d..613802978 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,10 @@ log/ *.so examples docs/build/* +.erlang.mk/ +cover/ +emqttd.d +eunit.coverdata +test/ct.cover.spec +logs +ct.coverdata diff --git a/CHANGELOG.md b/CHANGELOG.md index 69c27bc46..73ccd767f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,42 @@ emqttd ChangeLog -================== +================= + +0.16.0-beta(2016-02-16) +------------------------ + +#### Highlights + +Licensed under the Apache License, Version 2.0 Now. + +Improve the design of cluster, support to join or leave the cluster (#449): + +``` +$ ./bin/emqttd_ctl cluster +cluster join #Join the cluster +cluster leave #Leave the cluster +cluster remove #Remove the node from cluster +cluster status #Cluster status +``` + +Improve the design of Trie and Route, only the wildcard topics stored in Trie. + +Common Test to replace EUnit. + +#### Enhancements + +mqtt_message record: add 'sender' field (#440) + +refactor the emqttd, emqttd_time, emqttd_opts, emqttd_node modules. + +#### BugFix + +noproc error when call to gen_server2:call(false, {add_route,Topic,<0.685.0>}, infinity) (#446) + +#### Plugins + +Changed the license of all plugins. + 0.15.0-beta (2016-01-31) ------------------------ diff --git a/LICENSE b/LICENSE index cd6ef5565..8dada3eda 100644 --- a/LICENSE +++ b/LICENSE @@ -1,25 +1,201 @@ -The MIT License (MIT) + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + 1. Definitions. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. -The source files 'gen_server2.erl' and 'priority_queue.erl' are from RabbitMQ -v3.5.4 and licensed under MPL license. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/Makefile b/Makefile index 5895a95e0..8c5f84fd9 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,8 @@ clean: @$(REBAR) clean test: - @$(REBAR) skip_deps=true eunit + ERL_FLAGS="-config rel/files/test.config" $(REBAR) -v skip_deps=true ct + #$(REBAR) skip_deps=true eunit edoc: @$(REBAR) doc diff --git a/README.md b/README.md index a76d15bb7..c4267105b 100644 --- a/README.md +++ b/README.md @@ -145,5 +145,5 @@ Feng Lee ## License -The MIT License (MIT) +Apache License Version 2.0 diff --git a/include/emqttd.hrl b/include/emqttd.hrl index 1163bc323..e0349d57f 100644 --- a/include/emqttd.hrl +++ b/include/emqttd.hrl @@ -1,36 +1,28 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% MQTT Broker Header. -%%% -%%% @end -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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. +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ +%% MQTT Broker Header + +%%-------------------------------------------------------------------- %% Banner -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- + -define(COPYRIGHT, "Copyright (C) 2012-2016, Feng Lee "). --define(LICENSE_MESSAGE, "Licensed under MIT"). +-define(LICENSE_MESSAGE, "Licensed under the Apache License, Version 2.0"). -define(PROTOCOL_VERSION, "MQTT/3.1.1"). @@ -42,16 +34,17 @@ %% Queue Topics. -define(QTop, <<"$Q">>). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% PubSub -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- + -type pubsub() :: publish | subscribe. -define(IS_PUBSUB(PS), (PS =:= publish orelse PS =:= subscribe)). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Topic -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_topic, { topic :: binary(), node :: node() @@ -59,9 +52,9 @@ -type mqtt_topic() :: #mqtt_topic{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Subscription -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_subscription, { subid :: binary() | atom(), topic :: binary(), @@ -70,12 +63,12 @@ -type mqtt_subscription() :: #mqtt_subscription{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Client -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- --type header_key() :: atom() | binary() | string(). --type header_val() :: atom() | binary() | string() | integer(). +-type ws_header_key() :: atom() | binary() | string(). +-type ws_header_val() :: atom() | binary() | string() | integer(). -record(mqtt_client, { client_id :: binary() | undefined, @@ -86,15 +79,15 @@ proto_ver :: 3 | 4, keepalive = 0, will_topic :: undefined | binary(), - ws_initial_headers :: list({header_key(), header_val()}), + ws_initial_headers :: list({ws_header_key(), ws_header_val()}), connected_at :: erlang:timestamp() }). -type mqtt_client() :: #mqtt_client{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Session -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_session, { client_id :: binary(), sess_pid :: pid(), @@ -103,30 +96,32 @@ -type mqtt_session() :: #mqtt_session{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Message -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -type mqtt_msgid() :: binary() | undefined. -type mqtt_pktid() :: 1..16#ffff | undefined. -record(mqtt_message, { - msgid :: mqtt_msgid(), %% Global unique message ID - pktid :: mqtt_pktid(), %% PacketId - topic :: binary(), %% Topic that the message is published to - from :: binary() | atom(), %% ClientId of publisher - qos = 0 :: 0 | 1 | 2, %% Message QoS - retain = false :: boolean(), %% Retain flag - dup = false :: boolean(), %% Dup flag - sys = false :: boolean(), %% $SYS flag - payload :: binary(), %% Payload - timestamp :: erlang:timestamp() %% os:timestamp + msgid :: mqtt_msgid(), %% Global unique message ID + pktid :: mqtt_pktid(), %% PacketId + topic :: binary(), %% Topic that the message is published to + from :: binary() | atom(), %% ClientId of the publisher + sender :: binary() | undefined, %% Username of the publisher + qos = 0 :: 0 | 1 | 2, %% Message QoS + flags = [] :: [retain | dup | sys], %% Message Flags + retain = false :: boolean(), %% Retain flag + dup = false :: boolean(), %% Dup flag + sys = false :: boolean(), %% $SYS flag + payload :: binary(), %% Payload + timestamp :: erlang:timestamp() %% os:timestamp }). -type mqtt_message() :: #mqtt_message{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Alarm -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_alarm, { id :: binary(), severity :: warning | error | critical, @@ -137,9 +132,9 @@ -type mqtt_alarm() :: #mqtt_alarm{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Plugin -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_plugin, { name, version, @@ -150,10 +145,10 @@ -type mqtt_plugin() :: #mqtt_plugin{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT CLI Command %% For example: 'broker metrics' -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_cli, { name, action, diff --git a/include/emqttd_cli.hrl b/include/emqttd_cli.hrl index 8a0be700a..620a771e2 100644 --- a/include/emqttd_cli.hrl +++ b/include/emqttd_cli.hrl @@ -1,34 +1,24 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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. +%%-------------------------------------------------------------------- --define(PRINT(Format, Args), - io:format(Format, Args)). +-define(PRINT_MSG(Msg), io:format(Msg)). --define(PRINT_MSG(Msg), - io:format(Msg)). +-define(PRINT(Format, Args), io:format(Format, Args)). --define(PRINT_CMD(Cmd, Descr), - io:format("~-40s#~s~n", [Cmd, Descr])). +-define(PRINT_CMD(Cmd, Descr), io:format("~-40s#~s~n", [Cmd, Descr])). --define(USAGE(CmdList), - [?PRINT_CMD(Cmd, Descr) || {Cmd, Descr} <- CmdList]). +-define(USAGE(CmdList), [?PRINT_CMD(Cmd, Descr) || {Cmd, Descr} <- CmdList]). diff --git a/include/emqttd_internal.hrl b/include/emqttd_internal.hrl index 4a95d1d6b..5b4f6189d 100644 --- a/include/emqttd_internal.hrl +++ b/include/emqttd_internal.hrl @@ -1,27 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Internal Header File -%%% -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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. +%%-------------------------------------------------------------------- + +%% Internal Header File -define(GPROC_POOL(JoinOrLeave, Pool, I), (begin @@ -31,6 +24,8 @@ end end)). +-define(PROC_NAME(M, I), (list_to_atom(lists:concat([M, "_", I])))). + -define(record_to_proplist(Def, Rec), lists:zip(record_info(fields, Def), tl(tuple_to_list(Rec)))). @@ -57,3 +52,9 @@ {noreply, State} end)). +-define(IF(Cond, TrueFun,FalseFun), + (case (Cond) of + true -> (TrueFun); + false-> (FalseFun) + end)). + diff --git a/include/emqttd_protocol.hrl b/include/emqttd_protocol.hrl index 00635e3c2..61b82f02d 100644 --- a/include/emqttd_protocol.hrl +++ b/include/emqttd_protocol.hrl @@ -1,33 +1,24 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% MQTT Protocol Header. -%%% -%%% @end -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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. +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ +%% MQTT Protocol Header + +%%-------------------------------------------------------------------- %% MQTT Protocol Version and Levels -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -define(MQTT_PROTO_V31, 3). -define(MQTT_PROTO_V311, 4). @@ -37,9 +28,9 @@ -type mqtt_vsn() :: ?MQTT_PROTO_V31 | ?MQTT_PROTO_V311. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT QoS -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -define(QOS_0, 0). %% At most once -define(QOS_1, 1). %% At least once -define(QOS_2, 2). %% Exactly once @@ -71,15 +62,14 @@ end) end). - -%%------------------------------------------------------------------------------ -%% Max ClientId Length. Why 1024? NiDongDe! -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- +%% Max ClientId Length. Why 1024? NiDongDe... +%%-------------------------------------------------------------------- -define(MAX_CLIENTID_LEN, 1024). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Control Packet Types -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -define(RESERVED, 0). %% Reserved -define(CONNECT, 1). %% Client request to connect to Server -define(CONNACK, 2). %% Server to Client: Connect acknowledgment @@ -114,9 +104,9 @@ -type mqtt_packet_type() :: ?RESERVED..?DISCONNECT. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Connect Return Codes -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -define(CONNACK_ACCEPT, 0). %% Connection accepted -define(CONNACK_PROTO_VER, 1). %% Unacceptable protocol version -define(CONNACK_INVALID_ID, 2). %% Client Identifier is correct UTF-8 but not allowed by the Server @@ -126,25 +116,25 @@ -type mqtt_connack() :: ?CONNACK_ACCEPT..?CONNACK_AUTH. -%%------------------------------------------------------------------------------ -%% MQTT Parser and Serialiser -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- +%% MQTT Parser and Serializer +%%-------------------------------------------------------------------- -define(MAX_LEN, 16#fffffff). -define(HIGHBIT, 2#10000000). -define(LOWBITS, 2#01111111). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Packet Fixed Header -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_packet_header, { type = ?RESERVED :: mqtt_packet_type(), dup = false :: boolean(), qos = ?QOS_0 :: mqtt_qos(), retain = false :: boolean()}). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Packets -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -type mqtt_client_id() :: binary(). -type mqtt_packet_id() :: 1..16#ffff | undefined. @@ -188,9 +178,9 @@ -record(mqtt_packet_unsuback, { packet_id :: mqtt_packet_id() }). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Control Packet -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -record(mqtt_packet, { header :: #mqtt_packet_header{}, variable :: #mqtt_packet_connect{} | #mqtt_packet_connack{} @@ -202,9 +192,9 @@ -type mqtt_packet() :: #mqtt_packet{}. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Packet Match -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- -define(CONNECT_PACKET(Var), #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, variable = Var}). diff --git a/include/emqttd_trie.hrl b/include/emqttd_trie.hrl new file mode 100644 index 000000000..d077da5fb --- /dev/null +++ b/include/emqttd_trie.hrl @@ -0,0 +1,35 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2016 Feng Lee . +%% +%% 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. +%%-------------------------------------------------------------------- + +-type trie_node_id() :: binary() | atom(). + +-record(trie_node, { + node_id :: trie_node_id(), + edge_count = 0 :: non_neg_integer(), + topic :: binary() | undefined, + flags :: [retained | static] +}). + +-record(trie_edge, { + node_id :: trie_node_id(), + word :: binary() | atom() +}). + +-record(trie, { + edge :: #trie_edge{}, + node_id :: trie_node_id() +}). + diff --git a/plugins/emqttd_dashboard b/plugins/emqttd_dashboard index 2a1de2c79..18d36e902 160000 --- a/plugins/emqttd_dashboard +++ b/plugins/emqttd_dashboard @@ -1 +1 @@ -Subproject commit 2a1de2c79fe97a91b6c1e4da19af94273b874b21 +Subproject commit 18d36e902828f04fd7e72af581d0329ddd854d62 diff --git a/plugins/emqttd_plugin_mysql b/plugins/emqttd_plugin_mysql index fc6ae569d..4319d0993 160000 --- a/plugins/emqttd_plugin_mysql +++ b/plugins/emqttd_plugin_mysql @@ -1 +1 @@ -Subproject commit fc6ae569d7f13cbf547484a82502c1523894e181 +Subproject commit 4319d09935eead1d72b24088ac96dff0dd50ed80 diff --git a/plugins/emqttd_plugin_pgsql b/plugins/emqttd_plugin_pgsql index b8934cf4d..5acb9f356 160000 --- a/plugins/emqttd_plugin_pgsql +++ b/plugins/emqttd_plugin_pgsql @@ -1 +1 @@ -Subproject commit b8934cf4d4eefba3e6e3ed35ab8ca55cf3ceaffd +Subproject commit 5acb9f356a8413d196ac6b38dfa1971e46f4fff3 diff --git a/plugins/emqttd_plugin_redis b/plugins/emqttd_plugin_redis index c6a532d49..831f71848 160000 --- a/plugins/emqttd_plugin_redis +++ b/plugins/emqttd_plugin_redis @@ -1 +1 @@ -Subproject commit c6a532d49d2b479551bfd3b8d278d40c99e96ae3 +Subproject commit 831f718486d34e8d15c1bc0e93528b7a4449b278 diff --git a/plugins/emqttd_plugin_template b/plugins/emqttd_plugin_template index b9a1296cb..821dccbd1 160000 --- a/plugins/emqttd_plugin_template +++ b/plugins/emqttd_plugin_template @@ -1 +1 @@ -Subproject commit b9a1296cbf3acb52e0f31db3ef14269bf920d076 +Subproject commit 821dccbd13e7fa34af7906da34454dacbd7115b3 diff --git a/plugins/emqttd_recon b/plugins/emqttd_recon index 287b0329b..32cd9fd54 160000 --- a/plugins/emqttd_recon +++ b/plugins/emqttd_recon @@ -1 +1 @@ -Subproject commit 287b0329bbc9fcbcfe64c5b67cd837ec61b36868 +Subproject commit 32cd9fd5413998aafeddd145f5569cf0d24bcd1d diff --git a/plugins/emqttd_stomp b/plugins/emqttd_stomp index 42802eefa..81136e0ed 160000 --- a/plugins/emqttd_stomp +++ b/plugins/emqttd_stomp @@ -1 +1 @@ -Subproject commit 42802eefadc235c5468cf4d1921fcb2e96f7d519 +Subproject commit 81136e0ed52a9f9bcf81d0ce1176cf0b0e456a1c diff --git a/rebar.config b/rebar.config index 28837b3f7..695b2dd0d 100644 --- a/rebar.config +++ b/rebar.config @@ -11,25 +11,32 @@ {i, "include"}, {src_dirs, ["src"]}]}. -{eunit_opts, [verbose]}. - -{xref_checks, [undefined_function_calls]}. -{cover_enabled, true}. - {validate_app_modules, true}. {erl_first_files, ["src/gen_server2.erl", "src/emqttd_auth_mod.erl", "src/emqttd_acl_mod.erl"]}. +{eunit_opts, []}. %%verbose + +{ct_dir, "test"}. + +{ct_log_dir, "logs"}. + +{ct_extra_params, "-name ct_emqttd@127.0.0.1 -config rel/files/test.config"}. + +{ct_use_short_names, false}. + +{xref_checks, [undefined_function_calls]}. + +{cover_enabled, true}. + %% plugins cannot find emqttd.hrl without ".." lib dirs:( %% but this setting will make deps apps collision %% comment in 0.13.0 release %% {lib_dirs, ["../"]}. -{sub_dirs, [ - "rel", - "plugins/*/"]}. +{sub_dirs, ["rel", "plugins/*/"]}. {deps, [ {gproc, ".*", {git, "git://github.com/uwiger/gproc.git", {branch, "master"}}}, diff --git a/rel/files/emqttd.config.production b/rel/files/emqttd.config.production index dc5a171d2..755a6b1dc 100644 --- a/rel/files/emqttd.config.production +++ b/rel/files/emqttd.config.production @@ -69,9 +69,9 @@ %% Packet {packet, [ %% Max ClientId Length Allowed - {max_clientid_len, 1024}, + {max_clientid_len, 512}, %% Max Packet Size Allowed, 64K default - {max_packet_size, 65536} + {max_packet_size, 65536} ]}, %% Client {client, [ @@ -206,7 +206,7 @@ {acceptors, 16}, %% Maximum number of concurrent clients - {max_clients, 8192}, + {max_clients, 512}, %% Socket Access Control {access, [{allow, all}]}, diff --git a/rel/files/test.config b/rel/files/test.config new file mode 100644 index 000000000..496648af6 --- /dev/null +++ b/rel/files/test.config @@ -0,0 +1,311 @@ +% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et +[{kernel, [ + {start_timer, true}, + {start_pg2, true} + ]}, + {sasl, [ + {sasl_error_logger, {file, "emqttd_sasl.log"}} + ]}, + {ssl, [ + %{versions, ['tlsv1.2', 'tlsv1.1']} + ]}, + {lager, [ + {colored, true}, + {async_threshold, 1000}, + {error_logger_redirect, false}, + {crash_log, "log/emqttd_crash.log"}, + {handlers, [ + {lager_console_backend, info}, + %%NOTICE: Level >= error + %%{lager_emqtt_backend, error}, + {lager_file_backend, [ + {formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]}, + {file, "log/emqttd_info.log"}, + {level, info}, + {size, 104857600}, + {date, "$D0"}, + {count, 30} + ]}, + {lager_file_backend, [ + {formatter_config, [time, " ", pid, " [",severity,"] ", message, "\n"]}, + {file, "log/emqttd_error.log"}, + {level, error}, + {size, 104857600}, + {date, "$D0"}, + {count, 30} + ]} + ]} + ]}, + {esockd, [ + {logger, {lager, info}} + ]}, + {emqttd, [ + %% Authentication and Authorization + {access, [ + %% Authetication. Anonymous Default + {auth, [ + %% Authentication with username, password + %{username, []}, + + %% Authentication with clientid + %{clientid, [{password, no}, {file, "etc/clients.config"}]}, + + %% Authentication with LDAP + % {ldap, [ + % {servers, ["localhost"]}, + % {port, 389}, + % {timeout, 30}, + % {user_dn, "uid=$u,ou=People,dc=example,dc=com"}, + % {ssl, fasle}, + % {sslopts, [ + % {"certfile", "ssl.crt"}, + % {"keyfile", "ssl.key"}]} + % ]}, + + %% Allow all + {anonymous, []} + ]}, + %% ACL config + {acl, [ + %% Internal ACL module + %% {internal, [{file, "testdata/test_acl.config"}, {nomatch, allow}]} + ]} + ]}, + %% MQTT Protocol Options + {mqtt, [ + %% Packet + {packet, [ + %% Max ClientId Length Allowed + {max_clientid_len, 1024}, + %% Max Packet Size Allowed, 64K default + {max_packet_size, 65536} + ]}, + %% Client + {client, [ + %% Socket is connected, but no 'CONNECT' packet received + {idle_timeout, 10} %% seconds + ]}, + %% Session + {session, [ + %% Max number of QoS 1 and 2 messages that can be “in flight” at one time. + %% 0 means no limit + {max_inflight, 100}, + + %% Retry interval for redelivering QoS1/2 messages. + {unack_retry_interval, 20}, + + %% Awaiting PUBREL Timeout + {await_rel_timeout, 20}, + + %% Max Packets that Awaiting PUBREL, 0 means no limit + {max_awaiting_rel, 0}, + + %% Statistics Collection Interval(seconds) + {collect_interval, 20}, + + %% Expired after 2 days + {expired_after, 48} + + ]}, + %% Queue + {queue, [ + %% simple | priority + {type, simple}, + + %% Topic Priority: 0~255, Default is 0 + %% {priority, [{"topic/1", 10}, {"topic/2", 8}]}, + + %% Max queue length. Enqueued messages when persistent client disconnected, + %% or inflight window is full. + {max_length, infinity}, + + %% Low-water mark of queued messages + {low_watermark, 0.2}, + + %% High-water mark of queued messages + {high_watermark, 0.6}, + + %% Queue Qos0 messages? + {queue_qos0, true} + ]} + ]}, + %% Broker Options + {broker, [ + %% System interval of publishing broker $SYS messages + {sys_interval, 60}, + + %% Retained messages + {retained, [ + %% Expired after seconds, never expired if 0 + {expired_after, 0}, + + %% Max number of retained messages + {max_message_num, 100000}, + + %% Max Payload Size of retained message + {max_playload_size, 65536} + ]}, + + %% PubSub and Router + {pubsub, [ + %% Default should be scheduler numbers + {pool_size, 8}, + + %% Subscription: disc | ram | false + {subscription, ram}, + + %% Route shard + {route_shard, false}, + + %% Route delay, false | integer + {route_delay, false}, + + %% Route aging time(seconds) + {route_aging, 5} + ]}, + + %% Bridge + {bridge, [ + %%TODO: bridge queue size + {max_queue_len, 10000}, + + %% Ping Interval of bridge node + {ping_down_interval, 1} %seconds + ]} + ]}, + %% Modules + {modules, [ + %% Client presence management module. + %% Publish messages when client connected or disconnected + {presence, [{qos, 0}]}, + + %% Subscribe topics automatically when client connected + {subscription, [ + %% Subscription from stored table + stored, + + %% $u will be replaced with username + {"$Q/username/$u", 1}, + + %% $c will be replaced with clientid + {"$Q/client/$c", 1} + ]} + + %% Rewrite rules + %% {rewrite, [{file, "etc/rewrite.config"}]} + ]}, + %% Plugins + {plugins, [ + %% Plugin App Library Dir + {plugins_dir, "./plugins"}, + + %% File to store loaded plugin names. + {loaded_file, "./data/loaded_plugins"} + ]}, + + %% Listeners + {listeners, [ + {mqtt, 1883, [ + %% Size of acceptor pool + {acceptors, 16}, + + %% Maximum number of concurrent clients + {max_clients, 512}, + + %% Socket Access Control + {access, [{allow, all}]}, + + %% Connection Options + {connopts, [ + %% Rate Limit. Format is 'burst, rate', Unit is KB/Sec + %% {rate_limit, "100,10"} %% 100K burst, 10K rate + ]}, + + %% Socket Options + {sockopts, [ + %Set buffer if hight thoughtput + %{recbuf, 4096}, + %{sndbuf, 4096}, + %{buffer, 4096}, + %{nodelay, true}, + {backlog, 512} + ]} + ]}, + + {mqtts, 8883, [ + %% Size of acceptor pool + {acceptors, 4}, + + %% Maximum number of concurrent clients + {max_clients, 512}, + + %% Socket Access Control + {access, [{allow, all}]}, + + %% SSL certificate and key files + {ssl, [{certfile, "etc/ssl/ssl.crt"}, + {keyfile, "etc/ssl/ssl.key"}]}, + + %% Socket Options + {sockopts, [ + {backlog, 1024} + %{buffer, 4096}, + ]} + ]}, + %% WebSocket over HTTPS Listener + %% {https, 8083, [ + %% %% Size of acceptor pool + %% {acceptors, 4}, + %% %% Maximum number of concurrent clients + %% {max_clients, 512}, + %% %% Socket Access Control + %% {access, [{allow, all}]}, + %% %% SSL certificate and key files + %% {ssl, [{certfile, "etc/ssl/ssl.crt"}, + %% {keyfile, "etc/ssl/ssl.key"}]}, + %% %% Socket Options + %% {sockopts, [ + %% %{buffer, 4096}, + %% {backlog, 1024} + %% ]} + %%]}, + + %% HTTP and WebSocket Listener + {http, 8083, [ + %% Size of acceptor pool + {acceptors, 4}, + %% Maximum number of concurrent clients + {max_clients, 64}, + %% Socket Access Control + {access, [{allow, all}]}, + %% Socket Options + {sockopts, [ + {backlog, 1024} + %{buffer, 4096}, + ]} + ]} + ]}, + + %% Erlang System Monitor + {sysmon, [ + %% Long GC + {long_gc, 100}, + + %% Long Schedule(ms) + {long_schedule, 100}, + + %% 8M words. 32MB on 32-bit VM, 64MB on 64-bit VM. + %% 8 * 1024 * 1024 + {large_heap, 8388608}, + + %% Busy Port + {busy_port, true}, + + %% Busy Dist Port + {busy_dist_port, true} + + ]} + ]} +]. + diff --git a/src/emqttd.app.src b/src/emqttd.app.src index 65c4f7ce4..b95538fb4 100644 --- a/src/emqttd.app.src +++ b/src/emqttd.app.src @@ -1,8 +1,8 @@ {application, emqttd, [ - {id, "emqttd"}, - {vsn, "0.15.0"}, {description, "Erlang MQTT Broker"}, + {vsn, "0.16.0"}, + {id, "emqttd"}, {modules, []}, {registered, []}, {applications, [kernel, diff --git a/src/emqttd.erl b/src/emqttd.erl index 4e9bd42d7..69cf92482 100644 --- a/src/emqttd.erl +++ b/src/emqttd.erl @@ -1,38 +1,23 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd main module. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd). --export([start/0, env/1, env/2, - start_listeners/0, stop_listeners/0, - load_all_mods/0, is_mod_enabled/1, - is_running/1, seed_now/0]). - -%% Utility functions. --export([reg_name/2]). +-export([start/0, env/1, env/2, start_listeners/0, stop_listeners/0, + load_all_mods/0, is_mod_enabled/1, is_running/1]). -define(MQTT_SOCKOPTS, [ binary, @@ -45,86 +30,66 @@ -type listener() :: {atom(), inet:port_number(), [esockd:option()]}. -%%------------------------------------------------------------------------------ %% @doc Start emqttd application. -%% @end -%%------------------------------------------------------------------------------ -spec start() -> ok | {error, any()}. -start() -> - application:start(?APP). +start() -> application:start(?APP). + +%% @doc Group environment +-spec env(Group :: atom()) -> list(). +env(Group) -> application:get_env(?APP, Group, []). -%%------------------------------------------------------------------------------ %% @doc Get environment -%% @end -%%------------------------------------------------------------------------------ --spec env(atom()) -> list(). -env(Group) -> - application:get_env(?APP, Group, []). +-spec env(Group :: atom(), Name :: atom()) -> undefined | any(). +env(Group, Name) -> proplists:get_value(Name, env(Group)). --spec env(atom(), atom()) -> undefined | any(). -env(Group, Name) -> - proplists:get_value(Name, env(Group)). - -%%------------------------------------------------------------------------------ -%% @doc Start Listeners -%% @end -%%------------------------------------------------------------------------------ +%% @doc Start Listeners of the broker. -spec start_listeners() -> any(). -start_listeners() -> - {ok, Listeners} = application:get_env(?APP, listeners), - lists:foreach(fun start_listener/1, Listeners). +start_listeners() -> lists:foreach(fun start_listener/1, env(listeners)). %% Start mqtt listener -spec start_listener(listener()) -> any(). -start_listener({mqtt, Port, Options}) -> - start_listener(mqtt, Port, Options); +start_listener({mqtt, Port, Opts}) -> start_listener(mqtt, Port, Opts); %% Start mqtt(SSL) listener -start_listener({mqtts, Port, Options}) -> - start_listener(mqtts, Port, Options); +start_listener({mqtts, Port, Opts}) -> start_listener(mqtts, Port, Opts); %% Start http listener -start_listener({http, Port, Options}) -> - MFArgs = {emqttd_http, handle_request, []}, - mochiweb:start_http(Port, Options, MFArgs); +start_listener({http, Port, Opts}) -> + mochiweb:start_http(Port, Opts, {emqttd_http, handle_request, []}); %% Start https listener -start_listener({https, Port, Options}) -> - MFArgs = {emqttd_http, handle_request, []}, - mochiweb:start_http(Port, Options, MFArgs). +start_listener({https, Port, Opts}) -> + mochiweb:start_http(Port, Opts, {emqttd_http, handle_request, []}). -start_listener(Protocol, Port, Options) -> +start_listener(Protocol, Port, Opts) -> MFArgs = {emqttd_client, start_link, [env(mqtt)]}, - esockd:open(Protocol, Port, merge_sockopts(Options) , MFArgs). + esockd:open(Protocol, Port, merge_sockopts(Opts), MFArgs). merge_sockopts(Options) -> SockOpts = emqttd_opts:merge(?MQTT_SOCKOPTS, proplists:get_value(sockopts, Options, [])), emqttd_opts:merge(Options, [{sockopts, SockOpts}]). -%%------------------------------------------------------------------------------ %% @doc Stop Listeners -%% @end -%%------------------------------------------------------------------------------ -stop_listeners() -> - {ok, Listeners} = application:get_env(?APP, listeners), - lists:foreach(fun stop_listener/1, Listeners). +stop_listeners() -> lists:foreach(fun stop_listener/1, env(listeners)). -stop_listener({Protocol, Port, _Options}) -> - esockd:close({Protocol, Port}). +stop_listener({Protocol, Port, _Opts}) -> esockd:close({Protocol, Port}). +%% @doc load all modules load_all_mods() -> lists:foreach(fun load_mod/1, env(modules)). load_mod({Name, Opts}) -> Mod = list_to_atom("emqttd_mod_" ++ atom_to_list(Name)), case catch Mod:load(Opts) of - {ok, _State} -> lager:info("load module ~s successfully", [Name]); - {'EXIT', Reason} -> lager:error("load module ~s error: ~p", [Name, Reason]) + ok -> lager:info("Load module ~s successfully", [Name]); + {error, Error} -> lager:error("Load module ~s error: ~p", [Name, Error]); + {'EXIT', Reason} -> lager:error("Load module ~s error: ~p", [Name, Reason]) end. -is_mod_enabled(Name) -> - env(modules, Name) =/= undefined. +%% @doc Is module enabled? +-spec is_mod_enabled(Name :: atom()) -> boolean(). +is_mod_enabled(Name) -> env(modules, Name) =/= undefined. %% @doc Is running? -spec is_running(node()) -> boolean(). @@ -135,16 +100,3 @@ is_running(Node) -> Pid when is_pid(Pid) -> true end. --spec reg_name(module(), pos_integer()) -> atom(). -reg_name(M, Id) when is_atom(M), is_integer(Id) -> - list_to_atom(lists:concat([M, "_", Id])). - -seed_now() -> - case erlang:function_exported(erlang, timestamp, 0) of - true -> %% R18 - random:seed(erlang:timestamp()); - false -> - %% compress 'now()' warning... - random:seed(os:timestamp()) - end. - diff --git a/src/emqttd_access_control.erl b/src/emqttd_access_control.erl index 0e6e7645b..412bb9e4c 100644 --- a/src/emqttd_access_control.erl +++ b/src/emqttd_access_control.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Authentication and ACL Control Server -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_access_control). -include("emqttd.hrl"). @@ -47,27 +38,24 @@ -define(ACCESS_CONTROL_TAB, mqtt_access_control). -%%%============================================================================= -%%% API -%%%============================================================================= +-type password() :: undefined | binary(). -%%------------------------------------------------------------------------------ -%% @doc Start access control server -%% @end -%%------------------------------------------------------------------------------ +-record(state, {}). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + +%% @doc Start access control server. -spec start_link() -> {ok, pid()} | ignore | {error, any()}. -start_link() -> - start_link(emqttd:env(access)). +start_link() -> start_link(emqttd:env(access)). -spec start_link(Opts :: list()) -> {ok, pid()} | ignore | {error, any()}. start_link(Opts) -> gen_server:start_link({local, ?SERVER}, ?MODULE, [Opts], []). -%%------------------------------------------------------------------------------ -%% @doc Authenticate MQTT Client -%% @end -%%------------------------------------------------------------------------------ --spec auth(mqtt_client(), undefined | binary()) -> ok | {error, string()}. +%% @doc Authenticate MQTT Client. +-spec auth(Client :: mqtt_client(), Password :: password()) -> ok | {error, any()}. auth(Client, Password) when is_record(Client, mqtt_client) -> auth(Client, Password, lookup_mods(auth)). auth(_Client, _Password, []) -> @@ -80,41 +68,32 @@ auth(Client, Password, [{Mod, State, _Seq} | Mods]) -> {'EXIT', Error} -> {error, Error} end. -%%------------------------------------------------------------------------------ %% @doc Check ACL -%% @end -%%------------------------------------------------------------------------------ -spec check_acl(Client, PubSub, Topic) -> allow | deny when Client :: mqtt_client(), PubSub :: pubsub(), Topic :: binary(). check_acl(Client, PubSub, Topic) when ?IS_PUBSUB(PubSub) -> case lookup_mods(acl) of - [] -> allow; + [] -> allow; AclMods -> check_acl(Client, PubSub, Topic, AclMods) end. check_acl(#mqtt_client{client_id = ClientId}, PubSub, Topic, []) -> - lager:error("ACL: nomatch when ~s ~s ~s", [ClientId, PubSub, Topic]), + lager:error("ACL: nomatch for ~s ~s ~s", [ClientId, PubSub, Topic]), allow; -check_acl(Client, PubSub, Topic, [{M, State, _Seq}|AclMods]) -> - case M:check_acl({Client, PubSub, Topic}, State) of +check_acl(Client, PubSub, Topic, [{Mod, State, _Seq}|AclMods]) -> + case Mod:check_acl({Client, PubSub, Topic}, State) of allow -> allow; deny -> deny; ignore -> check_acl(Client, PubSub, Topic, AclMods) end. -%%------------------------------------------------------------------------------ -%% @doc Reload ACL -%% @end -%%------------------------------------------------------------------------------ --spec reload_acl() -> list() | {error, any()}. +%% @doc Reload ACL Rules. +-spec reload_acl() -> list(ok | {error, any()}). reload_acl() -> - [M:reload_acl(State) || {M, State, _Seq} <- lookup_mods(acl)]. + [Mod:reload_acl(State) || {Mod, State, _Seq} <- lookup_mods(acl)]. -%%------------------------------------------------------------------------------ -%% @doc Register authentication or ACL module -%% @end -%%------------------------------------------------------------------------------ +%% @doc Register Authentication or ACL module. -spec register_mod(auth | acl, atom(), list()) -> ok | {error, any()}. register_mod(Type, Mod, Opts) when Type =:= auth; Type =:= acl-> register_mod(Type, Mod, Opts, 0). @@ -123,89 +102,71 @@ register_mod(Type, Mod, Opts) when Type =:= auth; Type =:= acl-> register_mod(Type, Mod, Opts, Seq) when Type =:= auth; Type =:= acl-> gen_server:call(?SERVER, {register_mod, Type, Mod, Opts, Seq}). -%%------------------------------------------------------------------------------ %% @doc Unregister authentication or ACL module -%% @end -%%------------------------------------------------------------------------------ -spec unregister_mod(Type :: auth | acl, Mod :: atom()) -> ok | {error, any()}. unregister_mod(Type, Mod) when Type =:= auth; Type =:= acl -> gen_server:call(?SERVER, {unregister_mod, Type, Mod}). -%%------------------------------------------------------------------------------ -%% @doc Lookup authentication or ACL modules -%% @end -%%------------------------------------------------------------------------------ +%% @doc Lookup authentication or ACL modules. -spec lookup_mods(auth | acl) -> list(). lookup_mods(Type) -> case ets:lookup(?ACCESS_CONTROL_TAB, tab_key(Type)) of - [] -> []; + [] -> []; [{_, Mods}] -> Mods end. tab_key(auth) -> auth_modules; tab_key(acl) -> acl_modules. -%%------------------------------------------------------------------------------ -%% @doc Stop access control server -%% @end -%%------------------------------------------------------------------------------ -stop() -> - gen_server:call(?MODULE, stop). +%% @doc Stop access control server. +stop() -> gen_server:call(?MODULE, stop). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([Opts]) -> ets:new(?ACCESS_CONTROL_TAB, [set, named_table, protected, {read_concurrency, true}]), - ets:insert(?ACCESS_CONTROL_TAB, {auth_modules, init_mods(auth, proplists:get_value(auth, Opts))}), ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(acl, proplists:get_value(acl, Opts))}), - {ok, state}. + {ok, #state{}}. init_mods(auth, AuthMods) -> - [init_mod(fun authmod/1, Name, Opts) || {Name, Opts} <- AuthMods]; + [init_mod(authmod(Name), Opts) || {Name, Opts} <- AuthMods]; init_mods(acl, AclMods) -> - [init_mod(fun aclmod/1, Name, Opts) || {Name, Opts} <- AclMods]. + [init_mod(aclmod(Name), Opts) || {Name, Opts} <- AclMods]. -init_mod(Fun, Name, Opts) -> - Module = Fun(Name), - {ok, State} = Module:init(Opts), - {Module, State, 0}. +init_mod(Mod, Opts) -> + {ok, State} = Mod:init(Opts), {Mod, State, 0}. handle_call({register_mod, Type, Mod, Opts, Seq}, _From, State) -> Mods = lookup_mods(Type), - Reply = - case lists:keyfind(Mod, 1, Mods) of - false -> - case catch Mod:init(Opts) of - {ok, ModState} -> - NewMods = - lists:sort(fun({_, _, Seq1}, {_, _, Seq2}) -> - Seq1 >= Seq2 - end, [{Mod, ModState, Seq} | Mods]), - ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), NewMods}), - ok; - {'EXIT', Error} -> - lager:error("Access Control: register ~s error - ~p", [Mod, Error]), - {error, Error} - end; - _ -> - {error, existed} - end, - {reply, Reply, State}; + Existed = lists:keyfind(Mod, 1, Mods), + {reply, if_existed(Existed, fun() -> + case catch Mod:init(Opts) of + {ok, ModState} -> + NewMods = lists:sort(fun({_, _, Seq1}, {_, _, Seq2}) -> + Seq1 >= Seq2 + end, [{Mod, ModState, Seq} | Mods]), + ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), NewMods}), + ok; + {error, Error} -> + {error, Error}; + {'EXIT', Reason} -> + {error, Reason} + end + end), State}; handle_call({unregister_mod, Type, Mod}, _From, State) -> Mods = lookup_mods(Type), - Reply = case lists:keyfind(Mod, 1, Mods) of - false -> - {error, not_found}; - _ -> - ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), lists:keydelete(Mod, 1, Mods)}), ok - end, - {reply, Reply, State}; + false -> + {reply, {error, not_found}, State}; + _ -> + ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), lists:keydelete(Mod, 1, Mods)}), + {reply, ok, State} + end; handle_call(stop, _From, State) -> {stop, normal, ok, State}; @@ -226,9 +187,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- authmod(Name) when is_atom(Name) -> mod(emqttd_auth_, Name). @@ -239,3 +200,6 @@ aclmod(Name) when is_atom(Name) -> mod(Prefix, Name) -> list_to_atom(lists:concat([Prefix, Name])). +if_existed(false, Fun) -> Fun(); +if_existed(true, _Fun) -> {error, existed}. + diff --git a/src/emqttd_access_rule.erl b/src/emqttd_access_rule.erl index 3d62fd65f..7f8d149fa 100644 --- a/src/emqttd_access_rule.erl +++ b/src/emqttd_access_rule.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd ACL Rule -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_access_rule). -include("emqttd.hrl"). @@ -47,10 +38,7 @@ -define(ALLOW_DENY(A), ((A =:= allow) orelse (A =:= deny))). -%%------------------------------------------------------------------------------ -%% @doc Compile access rule -%% @end -%%------------------------------------------------------------------------------ +%% @doc Compile Access Rule. compile({A, all}) when ?ALLOW_DENY(A) -> {A, all}; @@ -96,10 +84,7 @@ bin(L) when is_list(L) -> bin(B) when is_binary(B) -> B. -%%------------------------------------------------------------------------------ -%% @doc Match rule -%% @end -%%------------------------------------------------------------------------------ +%% @doc Match Access Rule -spec match(mqtt_client(), topic(), rule()) -> {matched, allow} | {matched, deny} | nomatch. match(_Client, _Topic, {AllowDeny, all}) when (AllowDeny =:= allow) orelse (AllowDeny =:= deny) -> {matched, AllowDeny}; diff --git a/src/emqttd_acl_internal.erl b/src/emqttd_acl_internal.erl index 1b5932e02..1f9f2f434 100644 --- a/src/emqttd_acl_internal.erl +++ b/src/emqttd_acl_internal.erl @@ -1,36 +1,27 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Internal ACL that load rules from etc/acl.config -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_acl_internal). +-behaviour(emqttd_acl_mod). + -include("emqttd.hrl"). -export([all_rules/0]). --behaviour(emqttd_acl_mod). - %% ACL callbacks -export([init/1, check_acl/2, reload_acl/1, description/0]). @@ -38,14 +29,11 @@ -record(state, {acl_file, nomatch = allow}). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Read all rules -%% @end -%%------------------------------------------------------------------------------ -spec all_rules() -> list(emqttd_access_rule:rule()). all_rules() -> case ets:lookup(?ACL_RULE_TAB, all_rules) of @@ -53,21 +41,18 @@ all_rules() -> [{_, Rules}] -> Rules end. -%%%============================================================================= -%%% ACL callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% ACL callbacks +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Init internal ACL -%% @end -%%------------------------------------------------------------------------------ -spec init(AclOpts :: list()) -> {ok, State :: any()}. init(AclOpts) -> ets:new(?ACL_RULE_TAB, [set, public, named_table, {read_concurrency, true}]), AclFile = proplists:get_value(file, AclOpts), Default = proplists:get_value(nomatch, AclOpts, allow), State = #state{acl_file = AclFile, nomatch = Default}, - load_rules_from_file(State), + true = load_rules_from_file(State), {ok, State}. load_rules_from_file(#state{acl_file = AclFile}) -> @@ -92,10 +77,7 @@ filter(subscribe, {_AllowDeny, _Who, subscribe, _Topics}) -> filter(_PubSub, {_AllowDeny, _Who, _, _Topics}) -> false. -%%------------------------------------------------------------------------------ %% @doc Check ACL -%% @end -%%------------------------------------------------------------------------------ -spec check_acl({Client, PubSub, Topic}, State) -> allow | deny | ignore when Client :: mqtt_client(), PubSub :: pubsub(), @@ -123,10 +105,7 @@ match(Client, Topic, [Rule|Rules]) -> {matched, AllowDeny} -> {matched, AllowDeny} end. -%%------------------------------------------------------------------------------ %% @doc Reload ACL -%% @end -%%------------------------------------------------------------------------------ -spec reload_acl(State :: #state{}) -> ok | {error, Reason :: any()}. reload_acl(State) -> case catch load_rules_from_file(State) of @@ -134,10 +113,7 @@ reload_acl(State) -> _ -> ok end. -%%------------------------------------------------------------------------------ %% @doc ACL Module Description -%% @end -%%------------------------------------------------------------------------------ -spec description() -> string(). description() -> "Internal ACL with etc/acl.config". diff --git a/src/emqttd_acl_mod.erl b/src/emqttd_acl_mod.erl index dcc07a830..dfec11157 100644 --- a/src/emqttd_acl_mod.erl +++ b/src/emqttd_acl_mod.erl @@ -1,35 +1,26 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc ACL module behaviour -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_acl_mod). -include("emqttd.hrl"). -%%%============================================================================= -%%% ACL behavihour -%%%============================================================================= +%%-------------------------------------------------------------------- +%% ACL behavihour +%%-------------------------------------------------------------------- -ifdef(use_specs). @@ -49,9 +40,9 @@ -export([behaviour_info/1]). behaviour_info(callbacks) -> - [{init, 1}, {check_acl, 2}, {reload_acl, 1}, {description, 0}]; + [{init, 1}, {check_acl, 2}, {reload_acl, 1}, {description, 0}]; behaviour_info(_Other) -> - undefined. + undefined. -endif. diff --git a/src/emqttd_alarm.erl b/src/emqttd_alarm.erl index f16e13d45..8ef08dec0 100644 --- a/src/emqttd_alarm.erl +++ b/src/emqttd_alarm.erl @@ -1,34 +1,25 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Copy alarm_handler -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_alarm). --include("emqttd.hrl"). - -behaviour(gen_event). +-include("emqttd.hrl"). + -define(ALARM_MGR, ?MODULE). %% API Function Exports @@ -41,9 +32,9 @@ -export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2, code_change/3]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- start_link() -> start_with(fun(Pid) -> gen_event:add_handler(Pid, ?MODULE, []) end). @@ -54,8 +45,7 @@ start_with(Fun) -> Error -> Error end. -alarm_fun() -> - alarm_fun(false). +alarm_fun() -> alarm_fun(false). alarm_fun(Bool) -> fun(alert, _Alarm) when Bool =:= true -> alarm_fun(true); @@ -85,12 +75,11 @@ add_alarm_handler(Module, Args) when is_atom(Module) -> delete_alarm_handler(Module) when is_atom(Module) -> gen_event:delete_handler(?ALARM_MGR, Module, []). -%%%============================================================================= -%%% Default Alarm handler -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Default Alarm handler +%%-------------------------------------------------------------------- -init(_) -> - {ok, []}. +init(_) -> {ok, []}. handle_event({set_alarm, Alarm = #mqtt_alarm{id = AlarmId, severity = Severity, @@ -101,12 +90,12 @@ handle_event({set_alarm, Alarm = #mqtt_alarm{id = AlarmId, {severity, Severity}, {title, iolist_to_binary(Title)}, {summary, iolist_to_binary(Summary)}, - {ts, emqttd_util:now_to_secs(Timestamp)}]), + {ts, emqttd_time:now_to_secs(Timestamp)}]), emqttd_pubsub:publish(alarm_msg(alert, AlarmId, Json)), {ok, [Alarm#mqtt_alarm{timestamp = Timestamp} | Alarms]}; handle_event({clear_alarm, AlarmId}, Alarms) -> - Json = mochijson2:encode([{id, AlarmId}, {ts, emqttd_util:now_to_secs()}]), + Json = mochijson2:encode([{id, AlarmId}, {ts, emqttd_time:now_to_secs()}]), emqttd_pubsub:publish(alarm_msg(clear, AlarmId, Json)), {ok, lists:keydelete(AlarmId, 2, Alarms), hibernate}; @@ -131,9 +120,9 @@ terminate(_, _) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- alarm_msg(Type, AlarmId, Json) -> Msg = emqttd_message:make(alarm, diff --git a/src/emqttd_app.erl b/src/emqttd_app.erl index d23046427..0f8158c58 100644 --- a/src/emqttd_app.erl +++ b/src/emqttd_app.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd application. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_app). -include("emqttd_cli.hrl"). @@ -32,9 +23,9 @@ %% Application callbacks -export([start/2, stop/1]). -%%%============================================================================= -%%% Application callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Application callbacks +%%-------------------------------------------------------------------- -spec start(StartType, StartArgs) -> {ok, pid()} | {ok, pid(), State} | {error, Reason} when StartType :: normal | {takeover, node()} | {failover, node()}, diff --git a/src/emqttd_auth_anonymous.erl b/src/emqttd_auth_anonymous.erl index 9f0d3167f..8acdb7bf0 100644 --- a/src/emqttd_auth_anonymous.erl +++ b/src/emqttd_auth_anonymous.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Anonymous Authentication Module -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Anonymous Authentication Module -module(emqttd_auth_anonymous). -behaviour(emqttd_auth_mod). diff --git a/src/emqttd_auth_clientid.erl b/src/emqttd_auth_clientid.erl index 84eb2af14..46d92d0e6 100644 --- a/src/emqttd_auth_clientid.erl +++ b/src/emqttd_auth_clientid.erl @@ -1,34 +1,24 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc ClientId Authentication Module -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_auth_clientid). -include("emqttd.hrl"). --export([add_clientid/1, add_clientid/2, - lookup_clientid/1, remove_clientid/1, +-export([add_clientid/1, add_clientid/2, lookup_clientid/1, remove_clientid/1, all_clientids/0]). -behaviour(emqttd_auth_mod). @@ -40,87 +30,71 @@ -record(?AUTH_CLIENTID_TAB, {client_id, ipaddr, password}). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Add clientid -%% @end -%%------------------------------------------------------------------------------ -spec add_clientid(binary()) -> {atomic, ok} | {aborted, any()}. add_clientid(ClientId) when is_binary(ClientId) -> R = #mqtt_auth_clientid{client_id = ClientId}, mnesia:transaction(fun mnesia:write/1, [R]). -%%------------------------------------------------------------------------------ %% @doc Add clientid with password -%% @end -%%------------------------------------------------------------------------------ -spec add_clientid(binary(), binary()) -> {atomic, ok} | {aborted, any()}. add_clientid(ClientId, Password) -> R = #mqtt_auth_clientid{client_id = ClientId, password = Password}, mnesia:transaction(fun mnesia:write/1, [R]). -%%------------------------------------------------------------------------------ %% @doc Lookup clientid -%% @end -%%------------------------------------------------------------------------------ --spec lookup_clientid(binary()) -> list(). +-spec lookup_clientid(binary()) -> list(#mqtt_auth_clientid{}). lookup_clientid(ClientId) -> mnesia:dirty_read(?AUTH_CLIENTID_TAB, ClientId). -%%------------------------------------------------------------------------------ %% @doc Lookup all clientids -%% @end -%%------------------------------------------------------------------------------ -spec all_clientids() -> list(binary()). -all_clientids() -> - mnesia:dirty_all_keys(?AUTH_CLIENTID_TAB). +all_clientids() -> mnesia:dirty_all_keys(?AUTH_CLIENTID_TAB). -%%------------------------------------------------------------------------------ %% @doc Remove clientid -%% @end -%%------------------------------------------------------------------------------ -spec remove_clientid(binary()) -> {atomic, ok} | {aborted, any()}. remove_clientid(ClientId) -> mnesia:transaction(fun mnesia:delete/1, [{?AUTH_CLIENTID_TAB, ClientId}]). -%%%============================================================================= -%%% emqttd_auth_mod callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% emqttd_auth_mod callbacks +%%-------------------------------------------------------------------- init(Opts) -> mnesia:create_table(?AUTH_CLIENTID_TAB, [ {ram_copies, [node()]}, {attributes, record_info(fields, ?AUTH_CLIENTID_TAB)}]), mnesia:add_table_copy(?AUTH_CLIENTID_TAB, node(), ram_copies), - case proplists:get_value(file, Opts) of - undefined -> ok; - File -> load(File) - end, + load(proplists:get_value(file, Opts)), {ok, Opts}. -check(#mqtt_client{client_id = undefined}, _Password, []) -> - {error, "ClientId undefined"}; +check(#mqtt_client{client_id = undefined}, _Password, _Opts) -> + {error, clientid_undefined}; check(#mqtt_client{client_id = ClientId, peername = {IpAddress, _}}, _Password, []) -> check_clientid_only(ClientId, IpAddress); check(#mqtt_client{client_id = ClientId, peername = {IpAddress, _}}, _Password, [{password, no}|_]) -> check_clientid_only(ClientId, IpAddress); check(_Client, undefined, [{password, yes}|_]) -> - {error, "Password undefined"}; + {error, password_undefined}; check(#mqtt_client{client_id = ClientId}, Password, [{password, yes}|_]) -> case mnesia:dirty_read(?AUTH_CLIENTID_TAB, ClientId) of - [] -> {error, "ClientId Not Found"}; + [] -> {error, clientid_not_found}; [#?AUTH_CLIENTID_TAB{password = Password}] -> ok; %% TODO: plaintext?? - _ -> {error, "Password Not Right"} + _ -> {error, password_error} end. description() -> "ClientId authentication module". -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +load(undefined) -> + ok; load(File) -> {ok, Fd} = file:open(File, [read]), @@ -149,13 +123,13 @@ load(Fd, eof, Clients) -> check_clientid_only(ClientId, IpAddr) -> case mnesia:dirty_read(?AUTH_CLIENTID_TAB, ClientId) of - [] -> {error, "ClientId Not Found"}; + [] -> {error, clientid_not_found}; [#?AUTH_CLIENTID_TAB{ipaddr = undefined}] -> ok; [#?AUTH_CLIENTID_TAB{ipaddr = {_, {Start, End}}}] -> I = esockd_access:atoi(IpAddr), case I >= Start andalso I =< End of - true -> ok; - false -> {error, "ClientId with wrong IP address"} + true -> ok; + false -> {error, wrong_ipaddr} end end. diff --git a/src/emqttd_auth_ldap.erl b/src/emqttd_auth_ldap.erl index 9ef39e89f..11e1f27f3 100644 --- a/src/emqttd_auth_ldap.erl +++ b/src/emqttd_auth_ldap.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc LDAP Authentication Module -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 LDAP Authentication Module -module(emqttd_auth_ldap). -include("emqttd.hrl"). @@ -51,11 +43,11 @@ init(Opts) -> {ok, #state{servers = Servers, user_dn = UserDn, options = LdapOpts}}. check(#mqtt_client{username = undefined}, _Password, _State) -> - {error, "Username undefined"}; + {error, username_undefined}; check(_Client, undefined, _State) -> - {error, "Password undefined"}; + {error, password_undefined}; check(_Client, <<>>, _State) -> - {error, "Password undefined"}; + {error, password_undefined}; check(#mqtt_client{username = Username}, Password, #state{servers = Servers, user_dn = UserDn, options = Options}) -> case eldap:open(Servers, Options) of @@ -71,7 +63,7 @@ ldap_bind(LDAP, UserDn, Password) -> ok -> ok; {error, invalidCredentials} -> - {error, "LDAP Invalid Credentials"}; + {error, invalid_credentials}; {error, Error} -> {error, Error}; {'EXIT', Reason} -> diff --git a/src/emqttd_auth_mod.erl b/src/emqttd_auth_mod.erl index a52edac28..fda937bea 100644 --- a/src/emqttd_auth_mod.erl +++ b/src/emqttd_auth_mod.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd Authentication Behaviour -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Authentication Behaviour. -module(emqttd_auth_mod). -include("emqttd.hrl"). @@ -31,9 +23,9 @@ -type hash_type() :: plain | md5 | sha | sha256. -%%%============================================================================= -%%% Auth behavihour -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Authentication behavihour +%%-------------------------------------------------------------------- -ifdef(use_specs). diff --git a/src/emqttd_auth_username.erl b/src/emqttd_auth_username.erl index a923aee6d..395233a19 100644 --- a/src/emqttd_auth_username.erl +++ b/src/emqttd_auth_username.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Authentication with username and password -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Authentication with username and password -module(emqttd_auth_username). -include("emqttd.hrl"). @@ -44,63 +36,54 @@ -record(?AUTH_USERNAME_TAB, {username, password}). -%%%============================================================================= -%%% CLI -%%%============================================================================= +%%-------------------------------------------------------------------- +%% CLI +%%-------------------------------------------------------------------- cli(["add", Username, Password]) -> - ?PRINT("~p~n", [add_user(list_to_binary(Username), list_to_binary(Password))]); + ?PRINT("~p~n", [add_user(iolist_to_binary(Username), iolist_to_binary(Password))]); cli(["del", Username]) -> - ?PRINT("~p~n", [remove_user(list_to_binary(Username))]); + ?PRINT("~p~n", [remove_user(iolist_to_binary(Username))]); cli(_) -> - ?USAGE([{"users add ", "add user"}, - {"users del ", "delete user"}]). + ?USAGE([{"users add ", "Add User"}, + {"users del ", "Delete User"}]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ -%% @doc Add user -%% @end -%%------------------------------------------------------------------------------ --spec add_user(binary(), binary()) -> {atomic, ok} | {aborted, any()}. +%% @doc Add User +-spec add_user(binary(), binary()) -> ok | {error, any()}. add_user(Username, Password) -> User = #?AUTH_USERNAME_TAB{username = Username, password = hash(Password)}, - mnesia:transaction(fun mnesia:write/1, [User]). + ret(mnesia:transaction(fun mnesia:write/1, [User])). add_default_user(Username, Password) -> - add_user(bin(Username), bin(Password)). + add_user(iolist_to_binary(Username), iolist_to_binary(Password)). -%%------------------------------------------------------------------------------ %% @doc Lookup user by username -%% @end -%%------------------------------------------------------------------------------ -spec lookup_user(binary()) -> list(). lookup_user(Username) -> mnesia:dirty_read(?AUTH_USERNAME_TAB, Username). -%%------------------------------------------------------------------------------ %% @doc Remove user -%% @end -%%------------------------------------------------------------------------------ --spec remove_user(binary()) -> {atomic, ok} | {aborted, any()}. +-spec remove_user(binary()) -> ok | {error, any()}. remove_user(Username) -> - mnesia:transaction(fun mnesia:delete/1, [{?AUTH_USERNAME_TAB, Username}]). + ret(mnesia:transaction(fun mnesia:delete/1, [{?AUTH_USERNAME_TAB, Username}])). + +ret({atomic, ok}) -> ok; +ret({aborted, Error}) -> {error, Error}. -%%------------------------------------------------------------------------------ %% @doc All usernames -%% @end -%%------------------------------------------------------------------------------ -spec all_users() -> list(). -all_users() -> - mnesia:dirty_all_keys(?AUTH_USERNAME_TAB). +all_users() -> mnesia:dirty_all_keys(?AUTH_USERNAME_TAB). + +%%-------------------------------------------------------------------- +%% emqttd_auth_mod callbacks +%%-------------------------------------------------------------------- -%%%============================================================================= -%%% emqttd_auth callbacks -%%%============================================================================= init(DefautUsers) -> mnesia:create_table(?AUTH_USERNAME_TAB, [ {disc_copies, [node()]}, @@ -113,40 +96,33 @@ init(DefautUsers) -> {ok, []}. check(#mqtt_client{username = undefined}, _Password, _Opts) -> - {error, "Username undefined"}; + {error, username_undefined}; check(_User, undefined, _Opts) -> - {error, "Password undefined"}; + {error, password_undefined}; check(#mqtt_client{username = Username}, Password, _Opts) -> case mnesia:dirty_read(?AUTH_USERNAME_TAB, Username) of [] -> - {error, "Username Not Found"}; + {error, username_not_found}; [#?AUTH_USERNAME_TAB{password = <>}] -> case Hash =:= md5_hash(Salt, Password) of true -> ok; - false -> {error, "Password Not Right"} + false -> {error, password_error} end end. description() -> "Username password authentication module". -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- hash(Password) -> - SaltBin = salt(), - <>. + SaltBin = salt(), <>. md5_hash(SaltBin, Password) -> erlang:md5(<>). salt() -> - emqttd:seed_now(), - Salt = random:uniform(16#ffffffff), - <>. - -bin(A) when is_atom(A) -> bin(atom_to_list(A)); -bin(L) when is_list(L) -> list_to_binary(L); -bin(B) when is_binary(B) -> B. + emqttd_time:seed(), Salt = random:uniform(16#ffffffff), <>. diff --git a/src/emqttd_boot.erl b/src/emqttd_boot.erl new file mode 100644 index 000000000..e2ca2b931 --- /dev/null +++ b/src/emqttd_boot.erl @@ -0,0 +1,63 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_boot). + +-export([apply_module_attributes/1, all_module_attributes/1]). + +%% only {F, Args}... +apply_module_attributes(Name) -> + [{Module, [apply(Module, F, Args) || {F, Args} <- Attrs]} || + {_App, Module, Attrs} <- all_module_attributes(Name)]. + +%% Copy from rabbit_misc.erl +all_module_attributes(Name) -> + Targets = + lists:usort( + lists:append( + [[{App, Module} || Module <- Modules] || + {App, _, _} <- ignore_lib_apps(application:loaded_applications()), + {ok, Modules} <- [application:get_key(App, modules)]])), + lists:foldl( + fun ({App, Module}, Acc) -> + case lists:append([Atts || {N, Atts} <- module_attributes(Module), + N =:= Name]) of + [] -> Acc; + Atts -> [{App, Module, Atts} | Acc] + end + end, [], Targets). + +%% Copy from rabbit_misc.erl +module_attributes(Module) -> + case catch Module:module_info(attributes) of + {'EXIT', {undef, [{Module, module_info, [attributes], []} | _]}} -> + []; + {'EXIT', Reason} -> + exit(Reason); + V -> + V + end. + +ignore_lib_apps(Apps) -> + LibApps = [kernel, stdlib, sasl, appmon, eldap, erts, + syntax_tools, ssl, crypto, mnesia, os_mon, + inets, goldrush, lager, gproc, runtime_tools, + snmp, otp_mibs, public_key, asn1, ssh, hipe, + common_test, observer, webtool, xmerl, tools, + test_server, compiler, debugger, eunit, et, + gen_logger, wx], + [App || App = {Name, _, _} <- Apps, not lists:member(Name, LibApps)]. + diff --git a/src/emqttd_bridge.erl b/src/emqttd_bridge.erl index 4d4f1173c..32e1c02ee 100644 --- a/src/emqttd_bridge.erl +++ b/src/emqttd_bridge.erl @@ -1,30 +1,23 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd bridge -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_bridge). +-behaviour(gen_server2). + -include("emqttd.hrl"). -include("emqttd_protocol.hrl"). @@ -34,8 +27,6 @@ %% API Function Exports -export([start_link/3]). --behaviour(gen_server). - %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -59,32 +50,29 @@ -export_type([option/0]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start a bridge -%% @end -%%------------------------------------------------------------------------------ -spec start_link(atom(), binary(), [option()]) -> {ok, pid()} | ignore | {error, term()}. -start_link(Node, SubTopic, Options) -> - gen_server:start_link(?MODULE, [Node, SubTopic, Options], []). +start_link(Node, Topic, Options) -> + gen_server2:start_link(?MODULE, [Node, Topic, Options], []). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- -init([Node, SubTopic, Options]) -> +init([Node, Topic, Options]) -> process_flag(trap_exit, true), case net_kernel:connect_node(Node) of true -> true = erlang:monitor_node(Node, true), - State = parse_opts(Options, #state{node = Node, subtopic = SubTopic}), - MQueue = emqttd_mqueue:new(qname(Node, SubTopic), + State = parse_opts(Options, #state{node = Node, subtopic = Topic}), + MQueue = emqttd_mqueue:new(qname(Node, Topic), [{max_len, State#state.max_queue_len}], emqttd_alarm:alarm_fun()), - emqttd_pubsub:subscribe({SubTopic, State#state.qos}), + emqttd_pubsub:subscribe({Topic, State#state.qos}), {ok, State#state{mqueue = MQueue}}; false -> {stop, {cannot_connect, Node}} @@ -105,10 +93,10 @@ parse_opts([{ping_down_interval, Interval} | Opts], State) -> parse_opts([_Opt | Opts], State) -> parse_opts(Opts, State). -qname(Node, SubTopic) when is_atom(Node) -> - qname(atom_to_list(Node), SubTopic); -qname(Node, SubTopic) -> - list_to_binary(["Bridge:", Node, ":", SubTopic]). +qname(Node, Topic) when is_atom(Node) -> + qname(atom_to_list(Node), Topic); +qname(Node, Topic) -> + iolist_to_binary(["Bridge:", Node, ":", Topic]). handle_call(Req, _From, State) -> ?UNEXPECTED_REQ(Req, State). @@ -163,9 +151,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- dequeue(State = #state{mqueue = MQ}) -> case emqttd_mqueue:out(MQ) of diff --git a/src/emqttd_bridge_sup.erl b/src/emqttd_bridge_sup.erl index a7d93997e..37183cc4b 100644 --- a/src/emqttd_bridge_sup.erl +++ b/src/emqttd_bridge_sup.erl @@ -1,103 +1,73 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Bridge Supervisor -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_bridge_sup). -behavior(supervisor). --export([start_link/0, - bridges/0, - start_bridge/2, start_bridge/3, - stop_bridge/2]). +-export([start_link/0, bridges/0, start_bridge/2, start_bridge/3, stop_bridge/2]). -export([init/1]). -%%%============================================================================= -%%% API -%%%============================================================================= +-define(BRIDGE_ID(Node, Topic), {bridge, Node, Topic}). + +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start bridge supervisor -%% @end -%%------------------------------------------------------------------------------ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -%%------------------------------------------------------------------------------ %% @doc List all bridges -%% @end -%%------------------------------------------------------------------------------ -spec bridges() -> [{tuple(), pid()}]. bridges() -> - [{{Node, SubTopic}, Pid} || {{bridge, Node, SubTopic}, Pid, worker, _} - <- supervisor:which_children(?MODULE)]. + [{{Node, Topic}, Pid} || {?BRIDGE_ID(Node, Topic), Pid, worker, _} + <- supervisor:which_children(?MODULE)]. -%%------------------------------------------------------------------------------ %% @doc Start a bridge -%% @end -%%------------------------------------------------------------------------------ -spec start_bridge(atom(), binary()) -> {ok, pid()} | {error, any()}. -start_bridge(Node, SubTopic) when is_atom(Node) and is_binary(SubTopic) -> - start_bridge(Node, SubTopic, []). +start_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) -> + start_bridge(Node, Topic, []). -spec start_bridge(atom(), binary(), [emqttd_bridge:option()]) -> {ok, pid()} | {error, any()}. -start_bridge(Node, SubTopic, Options) when is_atom(Node) and is_binary(SubTopic) -> - case Node =:= node() of - true -> - {error, bridge_to_self}; - false -> - Options1 = emqttd_opts:merge(emqttd_broker:env(bridge), Options), - supervisor:start_child(?MODULE, bridge_spec(Node, SubTopic, Options1)) - end. +start_bridge(Node, _Topic, _Options) when Node =:= node() -> + {error, bridge_to_self}; +start_bridge(Node, Topic, Options) when is_atom(Node) andalso is_binary(Topic) -> + Options1 = emqttd_opts:merge(emqttd_broker:env(bridge), Options), + supervisor:start_child(?MODULE, bridge_spec(Node, Topic, Options1)). -%%------------------------------------------------------------------------------ %% @doc Stop a bridge -%% @end -%%------------------------------------------------------------------------------ -spec stop_bridge(atom(), binary()) -> {ok, pid()} | ok. -stop_bridge(Node, SubTopic) -> - ChildId = bridge_id(Node, SubTopic), +stop_bridge(Node, Topic) when is_atom(Node) andalso is_binary(Topic) -> + ChildId = ?BRIDGE_ID(Node, Topic), case supervisor:terminate_child(?MODULE, ChildId) of - ok -> - supervisor:delete_child(?MODULE, ChildId); - {error, Reason} -> - {error, Reason} + ok -> supervisor:delete_child(?MODULE, ChildId); + Error -> Error end. -%%%============================================================================= -%%% Supervisor callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Supervisor callbacks +%%-------------------------------------------------------------------- init([]) -> {ok, {{one_for_one, 10, 100}, []}}. -bridge_id(Node, SubTopic) -> - {bridge, Node, SubTopic}. - -bridge_spec(Node, SubTopic, Options) -> - ChildId = bridge_id(Node, SubTopic), - {ChildId, {emqttd_bridge, start_link, [Node, SubTopic, Options]}, +bridge_spec(Node, Topic, Options) -> + ChildId = ?BRIDGE_ID(Node, Topic), + {ChildId, {emqttd_bridge, start_link, [Node, Topic, Options]}, transient, 10000, worker, [emqttd_bridge]}. diff --git a/src/emqttd_broker.erl b/src/emqttd_broker.erl index 8f90fa01f..5a26fd291 100644 --- a/src/emqttd_broker.erl +++ b/src/emqttd_broker.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd broker -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_broker). -behaviour(gen_server). @@ -34,9 +25,6 @@ %% API Function Exports -export([start_link/0]). -%% Running nodes --export([running_nodes/0]). - %% Event API -export([subscribe/1, notify/2]). @@ -67,78 +55,44 @@ sysdescr % Broker description ]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start emqttd broker -%% @end -%%------------------------------------------------------------------------------ -spec start_link() -> {ok, pid()} | ignore | {error, any()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -%%------------------------------------------------------------------------------ -%% @doc Get running nodes -%% @end -%%------------------------------------------------------------------------------ --spec running_nodes() -> list(node()). -running_nodes() -> - mnesia:system_info(running_db_nodes). - -%%------------------------------------------------------------------------------ %% @doc Subscribe broker event -%% @end -%%------------------------------------------------------------------------------ -spec subscribe(EventType :: any()) -> ok. subscribe(EventType) -> gproc:reg({p, l, {broker, EventType}}). -%%------------------------------------------------------------------------------ %% @doc Notify broker event -%% @end -%%------------------------------------------------------------------------------ -spec notify(EventType :: any(), Event :: any()) -> ok. notify(EventType, Event) -> - Key = {broker, EventType}, - gproc:send({p, l, Key}, {self(), Key, Event}). + gproc:send({p, l, {broker, EventType}}, {notify, EventType, self(), Event}). -%%------------------------------------------------------------------------------ %% @doc Get broker env -%% @end -%%------------------------------------------------------------------------------ env(Name) -> proplists:get_value(Name, emqttd:env(broker)). -%%------------------------------------------------------------------------------ %% @doc Get broker version -%% @end -%%------------------------------------------------------------------------------ -spec version() -> string(). version() -> {ok, Version} = application:get_key(emqttd, vsn), Version. -%%------------------------------------------------------------------------------ %% @doc Get broker description -%% @end -%%------------------------------------------------------------------------------ -spec sysdescr() -> string(). sysdescr() -> {ok, Descr} = application:get_key(emqttd, description), Descr. -%%------------------------------------------------------------------------------ %% @doc Get broker uptime -%% @end -%%------------------------------------------------------------------------------ -spec uptime() -> string(). -uptime() -> - gen_server:call(?SERVER, uptime). +uptime() -> gen_server:call(?SERVER, uptime). -%%------------------------------------------------------------------------------ %% @doc Get broker datetime -%% @end -%%------------------------------------------------------------------------------ -spec datetime() -> string(). datetime() -> {{Y, M, D}, {H, MM, S}} = calendar:local_time(), @@ -146,26 +100,17 @@ datetime() -> io_lib:format( "~4..0w-~2..0w-~2..0w ~2..0w:~2..0w:~2..0w", [Y, M, D, H, MM, S])). -%%------------------------------------------------------------------------------ %% @doc Hook -%% @end -%%------------------------------------------------------------------------------ -spec hook(Hook :: atom(), Name :: any(), MFA :: mfa()) -> ok | {error, any()}. hook(Hook, Name, MFA) -> gen_server:call(?SERVER, {hook, Hook, Name, MFA}). -%%------------------------------------------------------------------------------ %% @doc Unhook -%% @end -%%------------------------------------------------------------------------------ -spec unhook(Hook :: atom(), Name :: any()) -> ok | {error, any()}. unhook(Hook, Name) -> gen_server:call(?SERVER, {unhook, Hook, Name}). -%%------------------------------------------------------------------------------ %% @doc Foreach hooks -%% @end -%%------------------------------------------------------------------------------ -spec foreach_hooks(Hook :: atom(), Args :: list()) -> any(). foreach_hooks(Hook, Args) -> case ets:lookup(?BROKER_TAB, {hook, Hook}) of @@ -177,10 +122,7 @@ foreach_hooks(Hook, Args) -> ok end. -%%------------------------------------------------------------------------------ %% @doc Foldl hooks -%% @end -%%------------------------------------------------------------------------------ -spec foldl_hooks(Hook :: atom(), Args :: list(), Acc0 :: any()) -> any(). foldl_hooks(Hook, Args, Acc0) -> case ets:lookup(?BROKER_TAB, {hook, Hook}) of @@ -192,10 +134,7 @@ foldl_hooks(Hook, Args, Acc0) -> Acc0 end. -%%------------------------------------------------------------------------------ %% @doc Start a tick timer -%% @end -%%------------------------------------------------------------------------------ start_tick(Msg) -> start_tick(timer:seconds(env(sys_interval)), Msg). @@ -204,21 +143,18 @@ start_tick(0, _Msg) -> start_tick(Interval, Msg) when Interval > 0 -> {ok, TRef} = timer:send_interval(Interval, Msg), TRef. -%%------------------------------------------------------------------------------ %% @doc Start tick timer -%% @end -%%------------------------------------------------------------------------------ stop_tick(undefined) -> ok; stop_tick(TRef) -> timer:cancel(TRef). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([]) -> - emqttd:seed_now(), + emqttd_time:seed(), ets:new(?BROKER_TAB, [set, public, named_table]), % Create $SYS Topics emqttd_pubsub:create(topic, <<"$SYS/brokers">>), @@ -234,24 +170,24 @@ handle_call(uptime, _From, State) -> handle_call({hook, Hook, Name, MFArgs}, _From, State) -> Key = {hook, Hook}, Reply = case ets:lookup(?BROKER_TAB, Key) of - [{Key, Hooks}] -> + [{Key, Hooks}] -> case lists:keyfind(Name, 1, Hooks) of {Name, _MFArgs} -> {error, existed}; false -> - ets:insert(?BROKER_TAB, {Key, Hooks ++ [{Name, MFArgs}]}) + insert_hooks(Key, Hooks ++ [{Name, MFArgs}]) end; - [] -> - ets:insert(?BROKER_TAB, {Key, [{Name, MFArgs}]}) + [] -> + insert_hooks(Key, [{Name, MFArgs}]) end, {reply, Reply, State}; handle_call({unhook, Hook, Name}, _From, State) -> Key = {hook, Hook}, Reply = case ets:lookup(?BROKER_TAB, Key) of - [{Key, Hooks}] -> - ets:insert(?BROKER_TAB, {Key, lists:keydelete(Name, 1, Hooks)}); - [] -> + [{Key, Hooks}] -> + insert_hooks(Key, lists:keydelete(Name, 1, Hooks)); + [] -> {error, not_found} end, {reply, Reply, State}; @@ -284,15 +220,19 @@ terminate(_Reason, #state{heartbeat = Hb, tick_tref = TRef}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + +insert_hooks(Key, Hooks) -> + ets:insert(?BROKER_TAB, {Key, Hooks}), ok. create_topic(Topic) -> emqttd_pubsub:create(topic, emqttd_topic:systop(Topic)). retain(brokers) -> - Payload = list_to_binary(string:join([atom_to_list(N) || N <- running_nodes()], ",")), + Payload = list_to_binary(string:join([atom_to_list(N) || + N <- emqttd_mnesia:running_nodes()], ",")), Msg = emqttd_message:make(broker, <<"$SYS/brokers">>, Payload), emqttd_pubsub:publish(emqttd_message:set_flag(sys, Msg)). diff --git a/src/emqttd_cli.erl b/src/emqttd_cli.erl index 4af1b3088..645556044 100644 --- a/src/emqttd_cli.erl +++ b/src/emqttd_cli.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd cli -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_cli). -include("emqttd.hrl"). @@ -60,14 +51,12 @@ load() -> is_cmd(Fun) -> not lists:member(Fun, [init, load, module_info]). -%%%============================================================================= -%%% Commands -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Commands +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Node status -%% @end -%%------------------------------------------------------------------------------ status([]) -> {InternalStatus, _ProvidedStatus} = init:get_status(), ?PRINT("Node ~p is ~p~n", [node(), InternalStatus]), @@ -80,10 +69,8 @@ status([]) -> status(_) -> ?PRINT_CMD("status", "query broker status"). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Query broker -%% @end -%%------------------------------------------------------------------------------ broker([]) -> Funs = [sysdescr, version, uptime, datetime], foreach(fun(Fun) -> @@ -116,61 +103,48 @@ broker(_) -> {"broker stats", "query broker statistics of clients, topics, subscribers"}, {"broker metrics", "query broker metrics"}]). -%%------------------------------------------------------------------------------ -%% @doc Cluster with other node -%% @end -%%------------------------------------------------------------------------------ -cluster([]) -> - Nodes = emqttd_broker:running_nodes(), - ?PRINT("cluster nodes: ~p~n", [Nodes]); - -cluster(usage) -> - ?PRINT_CMD("cluster []", "cluster with node, query cluster info"); - -cluster([SNode]) -> - Node = emqttd_dist:parse_node(SNode), - case lists:member(Node, emqttd_broker:running_nodes()) of - true -> - ?PRINT("~s is already clustered~n", [Node]); - false -> - cluster(Node, fun() -> - emqttd_plugins:unload(), - stop_apps(), - emqttd_mnesia:cluster(Node), - start_apps() - end) +%%-------------------------------------------------------------------- +%% @doc Cluster with other nodes +cluster(["join", SNode]) -> + case emqttd_cluster:join(emqttd_node:parse_name(SNode)) of + ok -> + ?PRINT_MSG("Join the cluster successfully.~n"), + cluster(["status"]); + {error, Error} -> + ?PRINT("Failed to join the cluster: ~p~n", [Error]) end; +cluster(["leave"]) -> + case emqttd_cluster:leave() of + ok -> + ?PRINT_MSG("Leave the cluster successfully.~n"), + cluster(["status"]); + {error, Error} -> + ?PRINT("Failed to leave the cluster: ~p~n", [Error]) + end; + +cluster(["remove", SNode]) -> + case emqttd_cluster:remove(emqttd_node:parse_name(SNode)) of + ok -> + ?PRINT_MSG("Remove the node from cluster successfully.~n"), + cluster(["status"]); + {error, Error} -> + ?PRINT("Failed to remove the node from cluster: ~p~n", [Error]) + end; + +cluster(["status"]) -> + ?PRINT("Cluster status: ~p~n", [emqttd_cluster:status()]); + cluster(_) -> - cluster(usage). + ?USAGE([{"cluster join ", "Join the cluster"}, + {"cluster leave", "Leave the cluster"}, + {"cluster remove ","Remove the node from cluster"}, + {"cluster status", "Cluster status"}]). -cluster(Node, DoCluster) -> - cluster(net_adm:ping(Node), Node, DoCluster). - -cluster(pong, Node, DoCluster) -> - case emqttd:is_running(Node) of - true -> - DoCluster(), - ?PRINT("cluster with ~s successfully.~n", [Node]); - false -> - ?PRINT("emqttd is not running on ~s~n", [Node]) - end; - -cluster(pang, Node, _DoCluster) -> - ?PRINT("Cannot connect to ~s~n", [Node]). - -stop_apps() -> - [application:stop(App) || App <- [emqttd, esockd, gproc]]. - -start_apps() -> - [application:start(App) || App <- [gproc, esockd, emqttd]]. - -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Query clients -%% @end -%%------------------------------------------------------------------------------ clients(["list"]) -> - emqttd_mnesia:dump(ets, mqtt_client, fun print/1); + dump(ets, mqtt_client, fun print/1); clients(["show", ClientId]) -> if_client(ClientId, fun print/1); @@ -189,18 +163,16 @@ if_client(ClientId, Fun) -> Client -> Fun(Client) end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Sessions Command -%% @end -%%------------------------------------------------------------------------------ sessions(["list"]) -> [sessions(["list", Type]) || Type <- ["persistent", "transient"]]; sessions(["list", "persistent"]) -> - emqttd_mnesia:dump(ets, mqtt_persistent_session, fun print/1); + dump(ets, mqtt_persistent_session, fun print/1); sessions(["list", "transient"]) -> - emqttd_mnesia:dump(ets, mqtt_transient_session, fun print/1); + dump(ets, mqtt_transient_session, fun print/1); sessions(["show", ClientId]) -> MP = {{bin(ClientId), '_'}, '_'}, @@ -220,10 +192,8 @@ sessions(_) -> {"sessions list transient", "list all transient sessions"}, {"sessions show ", "show a session"}]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Topics Command -%% @end -%%------------------------------------------------------------------------------ topics(["list"]) -> Print = fun(Topic, Records) -> print(topic, Topic, Records) end, if_could_print(topic, Print); @@ -316,11 +286,8 @@ plugins(_) -> {"plugins load ", "load plugin"}, {"plugins unload ", "unload plugin"}]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Bridges command -%% @end -%%------------------------------------------------------------------------------ - bridges(["list"]) -> foreach(fun({{Node, Topic}, _Pid}) -> ?PRINT("bridge: ~s--~s-->~s~n", [node(), Topic, Node]) @@ -376,10 +343,8 @@ parse_opt(bridge, queue, Len) -> parse_opt(_Cmd, Opt, _Val) -> ?PRINT("Bad Option: ~s~n", [Opt]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc vm command -%% @end -%%------------------------------------------------------------------------------ vm([]) -> vm(["all"]); @@ -410,20 +375,16 @@ vm(_) -> {"vm process", "query process of erlang vm"}, {"vm io", "queue io of erlang vm"}]). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc mnesia Command -%% @end -%%------------------------------------------------------------------------------ mnesia([]) -> mnesia:system_info(); mnesia(_) -> ?PRINT_CMD("mnesia", "mnesia system info"). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Trace Command -%% @end -%%------------------------------------------------------------------------------ trace(["list"]) -> foreach(fun({{Who, Name}, LogFile}) -> ?PRINT("trace ~s ~s -> ~s~n", [Who, Name, LogFile]) @@ -464,10 +425,8 @@ trace_off(Who, Name) -> ?PRINT("stop to trace ~s ~s error: ~p.~n", [Who, Name, Error]) end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% @doc Listeners Command -%% @end -%%------------------------------------------------------------------------------ listeners([]) -> foreach(fun({{Protocol, Port}, Pid}) -> Info = [{acceptors, esockd:get_acceptors(Pid)}, @@ -493,7 +452,7 @@ print(#mqtt_client{client_id = ClientId, clean_sess = CleanSess, ?PRINT("Client(~s, clean_sess=~s, username=~s, peername=~s, connected_at=~p)~n", [ClientId, CleanSess, Username, emqttd_net:format(Peername), - emqttd_util:now_to_secs(ConnectedAt)]); + emqttd_time:now_to_secs(ConnectedAt)]); print(#mqtt_topic{topic = Topic, node = Node}) -> ?PRINT("~s on ~s~n", [Topic, Node]); @@ -523,7 +482,7 @@ print(subscription, ClientId, Subscriptions) -> ?PRINT("~s: ~p~n", [ClientId, TopicTable]). format(created_at, Val) -> - emqttd_util:now_to_secs(Val); + emqttd_time:now_to_secs(Val); format(subscriptions, List) -> string:join([io_lib:format("~s:~w", [Topic, Qos]) || {Topic, Qos} <- List], ","); @@ -531,6 +490,19 @@ format(subscriptions, List) -> format(_, Val) -> Val. -bin(S) when is_list(S) -> list_to_binary(S); -bin(B) when is_binary(B) -> B. +bin(S) -> iolist_to_binary(S). + +%%TODO: ... +dump(ets, Table, Fun) -> + dump(ets, Table, ets:first(Table), Fun). + +dump(ets, _Table, '$end_of_table', _Fun) -> + ok; + +dump(ets, Table, Key, Fun) -> + case ets:lookup(Table, Key) of + [Record] -> Fun(Record); + [] -> ignore + end, + dump(ets, Table, ets:next(Table, Key), Fun). diff --git a/src/emqttd_client.erl b/src/emqttd_client.erl index 6b47f0aad..3687eaa7f 100644 --- a/src/emqttd_client.erl +++ b/src/emqttd_client.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT Client Connection -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Client Connection -module(emqttd_client). -behaviour(gen_server). @@ -228,9 +220,9 @@ terminate(Reason, #client_state{connection = Connection, code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- with_proto_state(Fun, State = #client_state{proto_state = ProtoState}) -> {ok, ProtoState1} = Fun(ProtoState), diff --git a/src/emqttd_cluster.erl b/src/emqttd_cluster.erl new file mode 100644 index 000000000..3511792db --- /dev/null +++ b/src/emqttd_cluster.erl @@ -0,0 +1,87 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_cluster). + +-include("emqttd.hrl"). + +%% Cluster API +-export([join/1, leave/0, status/0, remove/1]). + +%% RPC Call +-export([prepare/0, reboot/0]). + +%% @doc Join cluster +-spec join(node()) -> ok | {error, any()}. +join(Node) when Node =:= node() -> + {error, {cannot_join_with_self, Node}}; + +join(Node) when is_atom(Node) -> + case {is_clustered(Node), emqttd:is_running(Node)} of + {false, true} -> + prepare(), ok = emqttd_mnesia:join_cluster(Node), reboot(); + {false, false} -> + {error, {node_not_running, Node}}; + {true, _} -> + {error, {already_clustered, Node}} + end. + +%% @doc Prepare to join or leave cluster. +-spec prepare() -> ok. +prepare() -> + emqttd_plugins:unload(), + lists:foreach(fun application:stop/1, [emqttd, mochiweb, esockd, gproc]). + +%% @doc Is node in cluster? +-spec is_clustered(node()) -> boolean(). +is_clustered(Node) -> + lists:member(Node, emqttd_mnesia:running_nodes()). + +%% @doc Reboot after join or leave cluster. +-spec reboot() -> ok. +reboot() -> + lists:foreach(fun application:start/1, [gproc, esockd, mochiweb, emqttd]). + +%% @doc Leave from Cluster. +-spec leave() -> ok | {error, any()}. +leave() -> + case emqttd_mnesia:running_nodes() -- [node()] of + [_|_] -> + prepare(), ok = emqttd_mnesia:leave_cluster(), reboot(); + [] -> + {error, node_not_in_cluster} + end. + +%% @doc Remove a node from cluster. +-spec remove(node()) -> ok | {error, any()}. +remove(Node) when Node =:= node() -> + {error, {cannot_remove_self, Node}}; + +remove(Node) -> + case rpc:call(Node, ?MODULE, prepare, []) of + ok -> + case emqttd_mnesia:remove_from_cluster(Node) of + ok -> rpc:call(Node, ?MODULE, reboot, []); + Error -> Error + end; + Error -> + {error, Error} + end. + +%% @doc Cluster status +status() -> + emqttd_mnesia:cluster_status(). + diff --git a/src/emqttd_cm.erl b/src/emqttd_cm.erl index dc9f6b792..28edac37e 100644 --- a/src/emqttd_cm.erl +++ b/src/emqttd_cm.erl @@ -1,30 +1,24 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT Client Manager -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Client Manager -module(emqttd_cm). +-behaviour(gen_server2). + -include("emqttd.hrl"). -include("emqttd_internal.hrl"). @@ -34,8 +28,6 @@ -export([lookup/1, lookup_proc/1, register/1, unregister/1]). --behaviour(gen_server2). - %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -47,14 +39,11 @@ -define(POOL, ?MODULE). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start Client Manager -%% @end -%%------------------------------------------------------------------------------ -spec start_link(Pool, Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when Pool :: atom(), Id :: pos_integer(), @@ -62,10 +51,7 @@ start_link(Pool, Id, StatsFun) -> gen_server2:start_link(?MODULE, [Pool, Id, StatsFun], []). -%%------------------------------------------------------------------------------ %% @doc Lookup Client by ClientId -%% @end -%%------------------------------------------------------------------------------ -spec lookup(ClientId :: binary()) -> mqtt_client() | undefined. lookup(ClientId) when is_binary(ClientId) -> case ets:lookup(mqtt_client, ClientId) of @@ -73,10 +59,7 @@ lookup(ClientId) when is_binary(ClientId) -> [] -> undefined end. -%%------------------------------------------------------------------------------ %% @doc Lookup client pid by clientId -%% @end -%%------------------------------------------------------------------------------ -spec lookup_proc(ClientId :: binary()) -> pid() | undefined. lookup_proc(ClientId) when is_binary(ClientId) -> try ets:lookup_element(mqtt_client, ClientId, #mqtt_client.client_pid) @@ -84,27 +67,21 @@ lookup_proc(ClientId) when is_binary(ClientId) -> error:badarg -> undefined end. -%%------------------------------------------------------------------------------ %% @doc Register ClientId with Pid. -%% @end -%%------------------------------------------------------------------------------ -spec register(Client :: mqtt_client()) -> ok. register(Client = #mqtt_client{client_id = ClientId}) -> CmPid = gproc_pool:pick_worker(?POOL, ClientId), gen_server2:cast(CmPid, {register, Client}). -%%------------------------------------------------------------------------------ %% @doc Unregister clientId with pid. -%% @end -%%------------------------------------------------------------------------------ -spec unregister(ClientId :: binary()) -> ok. unregister(ClientId) when is_binary(ClientId) -> CmPid = gproc_pool:pick_worker(?POOL, ClientId), gen_server2:cast(CmPid, {unregister, ClientId, self()}). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([Pool, Id, StatsFun]) -> ?GPROC_POOL(join, Pool, Id), @@ -174,9 +151,9 @@ terminate(_Reason, #state{pool = Pool, id = Id}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- monitor_client(ClientId, Pid, State = #state{monitors = Monitors}) -> MRef = erlang:monitor(process, Pid), diff --git a/src/emqttd_cm_sup.erl b/src/emqttd_cm_sup.erl index d3389ab92..0def00005 100644 --- a/src/emqttd_cm_sup.erl +++ b/src/emqttd_cm_sup.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Client Manager Supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Client Manager Supervisor. -module(emqttd_cm_sup). -behaviour(supervisor). diff --git a/src/emqttd_ctl.erl b/src/emqttd_ctl.erl index 11c4b7277..a880a674f 100644 --- a/src/emqttd_ctl.erl +++ b/src/emqttd_ctl.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd control -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_ctl). -behaviour(gen_server). @@ -34,10 +25,7 @@ -define(SERVER, ?MODULE). %% API Function Exports --export([start_link/0, - register_cmd/3, - unregister_cmd/1, - run/1]). +-export([start_link/0, register_cmd/3, unregister_cmd/1, run/1]). %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -47,36 +35,26 @@ -define(CMD_TAB, mqttd_ctl_cmd). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -%%------------------------------------------------------------------------------ %% @doc Register a command -%% @end -%%------------------------------------------------------------------------------ -spec register_cmd(atom(), {module(), atom()}, list()) -> ok. register_cmd(Cmd, MF, Opts) -> cast({register_cmd, Cmd, MF, Opts}). -%%------------------------------------------------------------------------------ %% @doc Unregister a command -%% @end -%%------------------------------------------------------------------------------ -spec unregister_cmd(atom()) -> ok. unregister_cmd(Cmd) -> cast({unregister_cmd, Cmd}). -cast(Msg) -> - gen_server:cast(?SERVER, Msg). +cast(Msg) -> gen_server:cast(?SERVER, Msg). -%%------------------------------------------------------------------------------ %% @doc Run a command -%% @end -%%------------------------------------------------------------------------------ run([]) -> usage(); run(["help"]) -> usage(); @@ -88,18 +66,15 @@ run([CmdS|Args]) -> [] -> usage() end. -%%------------------------------------------------------------------------------ %% @doc Usage -%% @end -%%------------------------------------------------------------------------------ usage() -> ?PRINT("Usage: ~s~n", [?MODULE]), [begin ?PRINT("~80..-s~n", [""]), Mod:Cmd(usage) end || {_, {Mod, Cmd}, _} <- ets:tab2list(?CMD_TAB)]. -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([]) -> ets:new(?CMD_TAB, [ordered_set, named_table, protected]), @@ -134,9 +109,9 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal Function Definitions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal Function Definitions +%%-------------------------------------------------------------------- noreply(State) -> {noreply, State, hibernate}. @@ -144,4 +119,3 @@ noreply(State) -> next_seq(State = #state{seq = Seq}) -> State#state{seq = Seq + 1}. - diff --git a/src/emqttd_dist.erl b/src/emqttd_dist.erl deleted file mode 100644 index fc8a6241f..000000000 --- a/src/emqttd_dist.erl +++ /dev/null @@ -1,50 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd distribution functions -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- --module(emqttd_dist). - --import(lists, [concat/1]). - --export([parse_node/1]). - -parse_node(Name) when is_list(Name) -> - case string:tokens(Name, "@") of - [_Node, _Server] -> - list_to_atom(Name); - _ -> - list_to_atom(with_domain(Name)) - end. - -with_domain(Name) -> - case net_kernel:longnames() of - true -> - concat([Name, "@", inet_db:gethostname(), - ".", inet_db:res_option(domain)]); - false -> - concat([Name, "@", inet_db:gethostname()]); - _ -> - Name - end. - diff --git a/src/emqttd_gen_mod.erl b/src/emqttd_gen_mod.erl index e36d7c283..043c6e177 100644 --- a/src/emqttd_gen_mod.erl +++ b/src/emqttd_gen_mod.erl @@ -1,35 +1,27 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd gen_mod behaviour -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd gen_mod behaviour -module(emqttd_gen_mod). -include("emqttd.hrl"). -ifdef(use_specs). --callback load(Opts :: any()) -> {ok, State :: any()}. +-callback load(Opts :: any()) -> ok | {error, any()}. -callback unload(State :: any()) -> any(). diff --git a/src/emqttd_guid.erl b/src/emqttd_guid.erl index 8844e2577..c50294c65 100644 --- a/src/emqttd_guid.erl +++ b/src/emqttd_guid.erl @@ -1,42 +1,32 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% -%%% Generate global unique id for mqtt message. -%%% -%%% -------------------------------------------------------- -%%% | Timestamp | NodeID + PID | Sequence | -%%% |<------- 64bits ------->|<--- 48bits --->|<- 16bits ->| -%%% -------------------------------------------------------- -%%% -%%% 1. Timestamp: erlang:system_time if Erlang >= R18, otherwise os:timestamp -%%% 2. NodeId: encode node() to 2 bytes integer -%%% 3. Pid: encode pid to 4 bytes integer -%%% 4. Sequence: 2 bytes sequence in one process -%%% -%%% @end -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Generate global unique id for mqtt message. +%% +%% -------------------------------------------------------- +%% | Timestamp | NodeID + PID | Sequence | +%% |<------- 64bits ------->|<--- 48bits --->|<- 16bits ->| +%% -------------------------------------------------------- +%% +%% 1. Timestamp: erlang:system_time if Erlang >= R18, otherwise os:timestamp +%% 2. NodeId: encode node() to 2 bytes integer +%% 3. Pid: encode pid to 4 bytes integer +%% 4. Sequence: 2 bytes sequence in one process +%% +%% @end -module(emqttd_guid). -export([gen/0, new/0, timestamp/1]). @@ -45,10 +35,7 @@ -type guid() :: <<_:128>>. -%%------------------------------------------------------------------------------ %% @doc Generate a global unique id. -%% @end -%%------------------------------------------------------------------------------ -spec gen() -> guid(). gen() -> Guid = case get(guid) of diff --git a/src/emqttd_http.erl b/src/emqttd_http.erl index 4d52846e3..d673b14d5 100644 --- a/src/emqttd_http.erl +++ b/src/emqttd_http.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd http publish API and websocket client. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd http publish API and websocket client. -module(emqttd_http). -include("emqttd.hrl"). @@ -47,9 +39,10 @@ handle_request('GET', "/status", Req) -> [node(), InternalStatus, AppStatus]), Req:ok({"text/plain", iolist_to_binary(Status)}); -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% HTTP Publish API -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- + handle_request('POST', "/mqtt/publish", Req) -> Params = mochiweb_request:parse_post(Req), lager:info("HTTP Publish: ~p", [Params]), @@ -74,9 +67,9 @@ handle_request('POST', "/mqtt/publish", Req) -> Req:respond({401, [], <<"Fobbiden">>}) end; -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% MQTT Over WebSocket -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- handle_request('GET', "/mqtt", Req) -> lager:info("WebSocket Connection from: ~s", [Req:get(peer)]), Upgrade = Req:get_header_value("Upgrade"), @@ -92,9 +85,10 @@ handle_request('GET', "/mqtt", Req) -> Req:respond({400, [], <<"Bad WebSocket Protocol">>}) end; -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Get static files -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- + handle_request('GET', "/" ++ File, Req) -> lager:info("HTTP GET File: ~s", [File]), mochiweb_request:serve_file(File, docroot(), Req); @@ -103,9 +97,9 @@ handle_request(Method, Path, Req) -> lager:error("Unexpected HTTP Request: ~s ~s", [Method, Path]), Req:not_found(). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% basic authorization -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- authorized(Req) -> case Req:get_header_value("Authorization") of undefined -> diff --git a/src/emqttd_keepalive.erl b/src/emqttd_keepalive.erl index 9a3151836..e6c6f7809 100644 --- a/src/emqttd_keepalive.erl +++ b/src/emqttd_keepalive.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc client keepalive -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Client Keepalive -module(emqttd_keepalive). -export([start/3, check/1, cancel/1]). @@ -33,10 +25,7 @@ -type keepalive() :: #keepalive{}. -%%------------------------------------------------------------------------------ %% @doc Start a keepalive -%% @end -%%------------------------------------------------------------------------------ -spec start(fun(), integer(), any()) -> undefined | keepalive(). start(_, 0, _) -> undefined; @@ -46,10 +35,7 @@ start(StatFun, TimeoutSec, TimeoutMsg) -> tsec = TimeoutSec, tmsg = TimeoutMsg, tref = timer(TimeoutSec, TimeoutMsg)}. -%%------------------------------------------------------------------------------ %% @doc Check keepalive, called when timeout. -%% @end -%%------------------------------------------------------------------------------ -spec check(keepalive()) -> {ok, keepalive()} | {error, any()}. check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repeat}) -> case StatFun() of @@ -68,10 +54,7 @@ check(KeepAlive = #keepalive{statfun = StatFun, statval = LastVal, repeat = Repe resume(KeepAlive = #keepalive{tsec = TimeoutSec, tmsg = TimeoutMsg}) -> KeepAlive#keepalive{tref = timer(TimeoutSec, TimeoutMsg)}. -%%------------------------------------------------------------------------------ %% @doc Cancel Keepalive -%% @end -%%------------------------------------------------------------------------------ -spec cancel(keepalive()) -> ok. cancel(#keepalive{tref = TRef}) -> cancel(TRef); diff --git a/src/emqttd_log.erl b/src/emqttd_log.erl deleted file mode 100644 index c2f82408d..000000000 --- a/src/emqttd_log.erl +++ /dev/null @@ -1,33 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd log trace. -%%% -%%% @end -%%%----------------------------------------------------------------------------- - -%% TODO: issue #103 -%% 0.12.0 ??? - --module(emqttd_log). - -%%TODO: Hooks to log??? diff --git a/src/emqttd_message.erl b/src/emqttd_message.erl index 79ed3edb8..172a86c79 100644 --- a/src/emqttd_message.erl +++ b/src/emqttd_message.erl @@ -1,44 +1,34 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT Message Functions -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Message Functions -module(emqttd_message). -include("emqttd.hrl"). -include("emqttd_protocol.hrl"). --export([make/3, make/4, from_packet/1, from_packet/2, to_packet/1]). +-export([make/3, make/4, from_packet/1, from_packet/2, from_packet/3, + to_packet/1]). -export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]). -export([format/1]). -%%------------------------------------------------------------------------------ %% @doc Make a message -%% @end -%%------------------------------------------------------------------------------ -spec make(From, Topic, Payload) -> mqtt_message() when From :: atom() | binary(), Topic :: binary(), @@ -62,10 +52,7 @@ make(From, Qos, Topic, Payload) -> payload = Payload, timestamp = os:timestamp()}. -%%------------------------------------------------------------------------------ %% @doc Message from Packet -%% @end -%%------------------------------------------------------------------------------ -spec from_packet(mqtt_packet()) -> mqtt_message(). from_packet(#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, retain = Retain, @@ -86,12 +73,16 @@ from_packet(#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, from_packet(#mqtt_packet_connect{will_flag = false}) -> undefined; -from_packet(#mqtt_packet_connect{will_retain = Retain, +from_packet(#mqtt_packet_connect{client_id = ClientId, + username = Username, + will_retain = Retain, will_qos = Qos, will_topic = Topic, will_msg = Msg}) -> #mqtt_message{msgid = msgid(Qos), topic = Topic, + from = ClientId, + sender = Username, retain = Retain, qos = Qos, dup = false, @@ -99,17 +90,19 @@ from_packet(#mqtt_packet_connect{will_retain = Retain, timestamp = os:timestamp()}. from_packet(ClientId, Packet) -> - Msg = from_packet(Packet), Msg#mqtt_message{from = ClientId}. + Msg = from_packet(Packet), + Msg#mqtt_message{from = ClientId}. + +from_packet(Username, ClientId, Packet) -> + Msg = from_packet(Packet), + Msg#mqtt_message{from = ClientId, sender = Username}. msgid(?QOS_0) -> undefined; msgid(Qos) when Qos =:= ?QOS_1 orelse Qos =:= ?QOS_2 -> emqttd_guid:gen(). -%%------------------------------------------------------------------------------ %% @doc Message to packet -%% @end -%%------------------------------------------------------------------------------ -spec to_packet(mqtt_message()) -> mqtt_packet(). to_packet(#mqtt_message{pktid = PkgId, qos = Qos, @@ -130,10 +123,7 @@ to_packet(#mqtt_message{pktid = PkgId, }, payload = Payload}. -%%------------------------------------------------------------------------------ %% @doc set dup, retain flag -%% @end -%%------------------------------------------------------------------------------ -spec set_flag(mqtt_message()) -> mqtt_message(). set_flag(Msg) -> Msg#mqtt_message{dup = true, retain = true}. @@ -147,10 +137,7 @@ set_flag(retain, Msg = #mqtt_message{retain = false}) -> Msg#mqtt_message{retain = true}; set_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg. -%%------------------------------------------------------------------------------ %% @doc Unset dup, retain flag -%% @end -%%------------------------------------------------------------------------------ -spec unset_flag(mqtt_message()) -> mqtt_message(). unset_flag(Msg) -> Msg#mqtt_message{dup = false, retain = false}. @@ -162,14 +149,11 @@ unset_flag(retain, Msg = #mqtt_message{retain = true}) -> Msg#mqtt_message{retain = false}; unset_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg. -%%------------------------------------------------------------------------------ %% @doc Format MQTT Message -%% @end -%%------------------------------------------------------------------------------ -format(#mqtt_message{msgid = MsgId, pktid = PktId, from = From, +format(#mqtt_message{msgid = MsgId, pktid = PktId, from = From, sender = Sender, qos = Qos, retain = Retain, dup = Dup, topic =Topic}) -> - io_lib:format("Message(Q~p, R~p, D~p, MsgId=~p, PktId=~p, From=~s, Topic=~s)", - [i(Qos), i(Retain), i(Dup), MsgId, PktId, From, Topic]). + io_lib:format("Message(Q~p, R~p, D~p, MsgId=~p, PktId=~p, From=~s, Sender=~s, Topic=~s)", + [i(Qos), i(Retain), i(Dup), MsgId, PktId, From, Sender, Topic]). i(true) -> 1; i(false) -> 0; diff --git a/src/emqttd_metrics.erl b/src/emqttd_metrics.erl index 0ef5bda1c..6b6c6b7a8 100644 --- a/src/emqttd_metrics.erl +++ b/src/emqttd_metrics.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd metrics. responsible for collecting broker metrics -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd metrics. responsible for collecting broker metrics. -module(emqttd_metrics). -behaviour(gen_server). @@ -39,10 +31,7 @@ %% Received/Sent Metrics -export([received/1, sent/1]). --export([all/0, value/1, - inc/1, inc/2, inc/3, - dec/2, dec/3, - set/2]). +-export([all/0, value/1, inc/1, inc/2, inc/3, dec/2, dec/3, set/2]). %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -97,22 +86,16 @@ {counter, 'messages/dropped'} % Messages dropped ]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start metrics server -%% @end -%%------------------------------------------------------------------------------ -spec start_link() -> {ok, pid()} | ignore | {error, any()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). -%%------------------------------------------------------------------------------ %% @doc Count packets received. -%% @end -%%------------------------------------------------------------------------------ -spec received(mqtt_packet()) -> ok. received(Packet) -> inc('packets/received'), @@ -150,10 +133,7 @@ qos_received(?QOS_1) -> qos_received(?QOS_2) -> inc('messages/qos2/received'). -%%------------------------------------------------------------------------------ %% @doc Count packets received. Will not count $SYS PUBLISH. -%% @end -%%------------------------------------------------------------------------------ -spec sent(mqtt_packet()) -> ok. sent(?PUBLISH_PACKET(_Qos, <<"$SYS/", _/binary>>, _, _)) -> ignore; @@ -191,10 +171,7 @@ qos_sent(?QOS_1) -> qos_sent(?QOS_2) -> inc('messages/qos2/sent'). -%%------------------------------------------------------------------------------ %% @doc Get all metrics -%% @end -%%------------------------------------------------------------------------------ -spec all() -> [{atom(), non_neg_integer()}]. all() -> maps:to_list( @@ -206,26 +183,17 @@ all() -> end end, #{}, ?METRIC_TAB)). -%%------------------------------------------------------------------------------ %% @doc Get metric value -%% @end -%%------------------------------------------------------------------------------ -spec value(atom()) -> non_neg_integer(). value(Metric) -> lists:sum(ets:select(?METRIC_TAB, [{{{Metric, '_'}, '$1'}, [], ['$1']}])). -%%------------------------------------------------------------------------------ %% @doc Increase counter -%% @end -%%------------------------------------------------------------------------------ -spec inc(atom()) -> non_neg_integer(). inc(Metric) -> inc(counter, Metric, 1). -%%------------------------------------------------------------------------------ %% @doc Increase metric value -%% @end -%%------------------------------------------------------------------------------ -spec inc({counter | gauge, atom()} | atom(), pos_integer()) -> non_neg_integer(). inc({gauge, Metric}, Val) -> inc(gauge, Metric, Val); @@ -234,56 +202,41 @@ inc({counter, Metric}, Val) -> inc(Metric, Val) when is_atom(Metric) -> inc(counter, Metric, Val). -%%------------------------------------------------------------------------------ %% @doc Increase metric value -%% @end -%%------------------------------------------------------------------------------ -spec inc(counter | gauge, atom(), pos_integer()) -> pos_integer(). inc(gauge, Metric, Val) -> ets:update_counter(?METRIC_TAB, key(gauge, Metric), {2, Val}); inc(counter, Metric, Val) -> ets:update_counter(?METRIC_TAB, key(counter, Metric), {2, Val}). -%%------------------------------------------------------------------------------ %% @doc Decrease metric value -%% @end -%%------------------------------------------------------------------------------ -spec dec(gauge, atom()) -> integer(). dec(gauge, Metric) -> dec(gauge, Metric, 1). -%%------------------------------------------------------------------------------ %% @doc Decrease metric value -%% @end -%%------------------------------------------------------------------------------ -spec dec(gauge, atom(), pos_integer()) -> integer(). dec(gauge, Metric, Val) -> ets:update_counter(?METRIC_TAB, key(gauge, Metric), {2, -Val}). -%%------------------------------------------------------------------------------ %% @doc Set metric value -%% @end -%%------------------------------------------------------------------------------ set(Metric, Val) when is_atom(Metric) -> set(gauge, Metric, Val). set(gauge, Metric, Val) -> ets:insert(?METRIC_TAB, {key(gauge, Metric), Val}). -%%------------------------------------------------------------------------------ %% @doc Metric Key -%% @end -%%------------------------------------------------------------------------------ key(gauge, Metric) -> {Metric, 0}; key(counter, Metric) -> {Metric, erlang:system_info(scheduler_id)}. -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([]) -> - emqttd:seed_now(), + emqttd_time:seed(), Metrics = ?SYSTOP_BYTES ++ ?SYSTOP_PACKETS ++ ?SYSTOP_MESSAGES, % Create metrics table ets:new(?METRIC_TAB, [set, public, named_table, {write_concurrency, true}]), @@ -314,13 +267,12 @@ terminate(_Reason, #state{tick_tref = TRef}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- publish(Metric, Val) -> - Payload = emqttd_util:integer_to_binary(Val), - Msg = emqttd_message:make(metrics, metric_topic(Metric), Payload), + Msg = emqttd_message:make(metrics, metric_topic(Metric), bin(Val)), emqttd_pubsub:publish(emqttd_message:set_flag(sys, Msg)). create_metric({gauge, Name}) -> @@ -333,3 +285,5 @@ create_metric({counter, Name}) -> metric_topic(Metric) -> emqttd_topic:systop(list_to_binary(lists:concat(['metrics/', Metric]))). +bin(I) when is_integer(I) -> list_to_binary(integer_to_list(I)). + diff --git a/src/emqttd_mnesia.erl b/src/emqttd_mnesia.erl index d299082d8..3ecf06277 100644 --- a/src/emqttd_mnesia.erl +++ b/src/emqttd_mnesia.erl @@ -1,182 +1,256 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd mnesia -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_mnesia). -include("emqttd.hrl"). --export([start/0, cluster/1]). +-include("emqttd_internal.hrl"). --export([create_table/2, copy_table/1]). +%% Start and stop mnesia +-export([start/0, ensure_started/0, ensure_stopped/0, connect/1]). --export([dump/3]). +%% Cluster mnesia +-export([join_cluster/1, cluster_status/0, leave_cluster/0, + remove_from_cluster/1, running_nodes/0]). +%% Schema and tables +-export([copy_schema/1, delete_schema/0, del_schema_copy/1, + create_table/2, copy_table/1, copy_table/2]). + +%%-------------------------------------------------------------------- +%% Start and init mnesia +%%-------------------------------------------------------------------- + +%% @doc Start mnesia database. +-spec start() -> ok. start() -> - case init_schema() of - ok -> - ok; - {error, {_Node, {already_exists, _Node}}} -> - ok; - {error, Reason} -> - lager:error("mnesia init_schema error: ~p", [Reason]) - end, + ensure_ok(ensure_data_dir()), + ensure_ok(init_schema()), ok = mnesia:start(), init_tables(), - wait_for_tables(). + wait_for(tables). -%%------------------------------------------------------------------------------ -%% @doc %% @private -%% init mnesia schema. -%% -%% @end -%%------------------------------------------------------------------------------ +ensure_data_dir() -> + Dir = mnesia:system_info(directory) ++ "/", + case filelib:ensure_dir(Dir) of + ok -> ok; + {error, Reason} -> {error, {mnesia_dir_error, Dir, Reason}} + end. + +%% @doc ensure mnesia started. +-spec ensure_started() -> ok | {error, any()}. +ensure_started() -> + ok = mnesia:start(), wait_for(start). + +%% @doc ensure mnesia stopped. +-spec ensure_stopped() -> ok | {error, any()}. +ensure_stopped() -> + stopped = mnesia:stop(), wait_for(stop). + +%% @private +%% @doc Init mnesia schema or tables. init_schema() -> case mnesia:system_info(extra_db_nodes) of - [] -> - %% create schema - mnesia:create_schema([node()]); - __ -> - ok + [] -> mnesia:create_schema([node()]); + [_|_] -> ok end. -%%------------------------------------------------------------------------------ -%% @doc %% @private -%% init mnesia tables. -%% -%% @end -%%------------------------------------------------------------------------------ +%% @doc Init mnesia tables. init_tables() -> case mnesia:system_info(extra_db_nodes) of - [] -> - create_tables(); - _ -> - copy_tables() + [] -> create_tables(); + [_|_] -> copy_tables() end. -%%------------------------------------------------------------------------------ -%% @doc -%% @private -%% create tables. -%% -%% @end -%%------------------------------------------------------------------------------ +%% @doc Create mnesia tables. create_tables() -> - emqttd_util:apply_module_attributes(boot_mnesia). + emqttd_boot:apply_module_attributes(boot_mnesia). -create_table(Table, Attrs) -> - case mnesia:create_table(Table, Attrs) of - {atomic, ok} -> ok; - {aborted, {already_exists, Table}} -> ok; - {aborted, {already_exists, Table, _}} -> ok; - Error -> Error - end. - -%%------------------------------------------------------------------------------ -%% @doc -%% @private -%% copy tables. -%% -%% @end -%%------------------------------------------------------------------------------ +%% @doc Copy mnesia tables. copy_tables() -> - emqttd_util:apply_module_attributes(copy_mnesia). + emqttd_boot:apply_module_attributes(copy_mnesia). -copy_table(Table) -> - case mnesia:add_table_copy(Table, node(), ram_copies) of - {atomic, ok} -> ok; - {aborted, {already_exists, Table}} -> ok; - {aborted, {already_exists, Table, _Node}} -> ok; - {aborted, Error} -> Error - end. +%% @doc Create mnesia table. +-spec create_table(Name:: atom(), TabDef :: list()) -> ok | {error, any()}. +create_table(Name, TabDef) -> + ensure_tab(mnesia:create_table(Name, TabDef)). -%%------------------------------------------------------------------------------ -%% @doc -%% @private -%% wait for tables. -%% -%% @end -%%------------------------------------------------------------------------------ -wait_for_tables() -> - %% io:format("mnesia wait_for_tables: ~p~n", [mnesia:system_info(local_tables)]), - mnesia:wait_for_tables(mnesia:system_info(local_tables), infinity). +%% @doc Copy mnesia table. +-spec copy_table(Name :: atom()) -> ok. +copy_table(Name) -> + copy_table(Name, ram_copies). -%%------------------------------------------------------------------------------ -%% @doc -%% @private -%% Simple cluster with another nodes. -%% -%% @end -%%------------------------------------------------------------------------------ -cluster(Node) -> - %% stop mnesia - mnesia:stop(), - ok = wait_for_mnesia(stop), - %% delete mnesia - ok = mnesia:delete_schema([node()]), - %% start mnesia - ok = mnesia:start(), - %% connect with extra_db_nodes - case mnesia:change_config(extra_db_nodes, [Node]) of - {ok, []} -> - throw({error, failed_to_connect_extra_db_nodes}); - {ok, Nodes} -> - case lists:member(Node, Nodes) of - true -> lager:info("mnesia connected to extra_db_node '~s' successfully!", [Node]); - false -> lager:error("mnesia failed to connect extra_db_node '~s'!", [Node]) - end, - mnesia:change_table_copy_type(schema, node(), disc_copies) - end, - copy_tables(), - wait_for_tables(). - -wait_for_mnesia(stop) -> - case mnesia:system_info(is_running) of - no -> +-spec copy_table(Name:: atom(), ram_copies | disc_copies) -> ok. +copy_table(Name, RamOrDisc) -> + ensure_tab(mnesia:add_table_copy(Name, node(), RamOrDisc)). + +%% @doc Copy schema. +copy_schema(Node) -> + case mnesia:change_table_copy_type(schema, Node, disc_copies) of + {atomic, ok} -> ok; - stopping -> - lager:info("Waiting for mnesia to stop..."), - timer:sleep(1000), - wait_for_mnesia(stop); - yes -> - {error, mnesia_unexpectedly_running}; - starting -> - {error, mnesia_unexpectedly_starting} + {aborted, {already_exists, schema, Node, disc_copies}} -> + ok; + {aborted, Error} -> + {error, Error} end. -dump(ets, Table, Fun) -> - dump(ets, Table, ets:first(Table), Fun). +%% @doc Force to delete schema. +delete_schema() -> + mnesia:delete_schema([node()]). -dump(ets, _Table, '$end_of_table', _Fun) -> - ok; +%% @doc Delete schema copy +del_schema_copy(Node) -> + case mnesia:del_table_copy(schema, Node) of + {atomic, ok} -> ok; + {aborted, Reason} -> {error, Reason} + end. -dump(ets, Table, Key, Fun) -> - case ets:lookup(Table, Key) of - [Record] -> Fun(Record); - [] -> ignore - end, - dump(ets, Table, ets:next(Table, Key), Fun). +%%-------------------------------------------------------------------- +%% Cluster mnesia +%%-------------------------------------------------------------------- + +%% @doc Join the mnesia cluster +-spec join_cluster(node()) -> ok. +join_cluster(Node) when Node =/= node() -> + %% Stop mnesia and delete schema first + ensure_ok(ensure_stopped()), + ensure_ok(delete_schema()), + %% Start mnesia and cluster to node + ensure_ok(ensure_started()), + ensure_ok(connect(Node)), + ensure_ok(copy_schema(node())), + %% Copy tables + copy_tables(), + ensure_ok(wait_for(tables)). + +%% @doc Cluster status +-spec cluster_status() -> list(). +cluster_status() -> + Running = mnesia:system_info(running_db_nodes), + Stopped = mnesia:system_info(db_nodes) -- Running, + ?IF(Stopped =:= [], [{running_nodes, Running}], + [{running_nodes, Running}, {stopped_nodes, Stopped}]). + +%% @doc This node try leave the cluster +-spec leave_cluster() -> ok | {error, any()}. +leave_cluster() -> + case running_nodes() -- [node()] of + [] -> + {error, node_not_in_cluster}; + Nodes -> + case lists:any(fun(Node) -> + case leave_cluster(Node) of + ok -> true; + {error, _Reason} -> false + end + end, Nodes) of + true -> ok; + false -> {error, {failed_to_leave, Nodes}} + end + end. + +-spec leave_cluster(node()) -> ok | {error, any()}. +leave_cluster(Node) when Node =/= node() -> + case is_running_db_node(Node) of + true -> + ensure_ok(ensure_stopped()), + ensure_ok(rpc:call(Node, ?MODULE, del_schema_copy, [node()])), + ensure_ok(delete_schema()); + %%ensure_ok(start()); %% restart? + false -> + {error, {node_not_running, Node}} + end. + +%% @doc Remove node from mnesia cluster. +-spec remove_from_cluster(node()) -> ok | {error, any()}. +remove_from_cluster(Node) when Node =/= node() -> + case {is_node_in_cluster(Node), is_running_db_node(Node)} of + {true, true} -> + ensure_ok(rpc:call(Node, ?MODULE, ensure_stopped, [])), + ensure_ok(del_schema_copy(Node)), + ensure_ok(rpc:call(Node, ?MODULE, delete_schema, [])); + {true, false} -> + ensure_ok(del_schema_copy(Node)), + ensure_ok(rpc:call(Node, ?MODULE, delete_schema, [])); + {false, _} -> + {error, node_not_in_cluster} + end. + +%% @doc Is node in mnesia cluster. +is_node_in_cluster(Node) -> + lists:member(Node, mnesia:system_info(db_nodes)). + +%% @private +%% @doc Is running db node. +is_running_db_node(Node) -> + lists:member(Node, running_nodes()). + +%% @doc Cluster with node. +-spec connect(node()) -> ok | {error, any()}. +connect(Node) -> + case mnesia:change_config(extra_db_nodes, [Node]) of + {ok, [Node]} -> ok; + {ok, []} -> {error, {failed_to_connect_node, Node}}; + Error -> Error + end. + +%% @doc Running nodes +-spec running_nodes() -> list(node()). +running_nodes() -> + mnesia:system_info(running_db_nodes). + +%% @private +ensure_ok(ok) -> ok; +ensure_ok({error, {_Node, {already_exists, _Node}}}) -> ok; +ensure_ok({badrpc, Reason}) -> throw({error, {badrpc, Reason}}); +ensure_ok({error, Reason}) -> throw({error, Reason}). + +%% @private +ensure_tab({atomic, ok}) -> ok; +ensure_tab({aborted, {already_exists, _Name}}) -> ok; +ensure_tab({aborted, {already_exists, _Name, _Node}})-> ok; +ensure_tab({aborted, Error}) -> Error. + +%% @doc Wait for mnesia to start, stop or tables ready. +-spec wait_for(start | stop | tables) -> ok | {error, Reason :: atom()}. +wait_for(start) -> + case mnesia:system_info(is_running) of + yes -> ok; + no -> {error, mnesia_unexpectedly_stopped}; + stopping -> {error, mnesia_unexpectedly_stopping}; + starting -> timer:sleep(1000), wait_for(start) + end; + +wait_for(stop) -> + case mnesia:system_info(is_running) of + no -> ok; + yes -> {error, mnesia_unexpectedly_running}; + starting -> {error, mnesia_unexpectedly_starting}; + stopping -> timer:sleep(1000), wait_for(stop) + end; + +wait_for(tables) -> + Tables = mnesia:system_info(local_tables), + case mnesia:wait_for_tables(Tables, 600000) of + ok -> ok; + {error, Reason} -> {error, Reason}; + {timeout, BadTables} -> {error, {timetout, BadTables}} + end. diff --git a/src/emqttd_mod_presence.erl b/src/emqttd_mod_presence.erl index 458616c7b..57a02c1bf 100644 --- a/src/emqttd_mod_presence.erl +++ b/src/emqttd_mod_presence.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd presence management module -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd presence management module -module(emqttd_mod_presence). -behaviour(emqttd_gen_mod). @@ -38,7 +30,7 @@ load(Opts) -> {?MODULE, client_connected, [Opts]}), emqttd_broker:hook('client.disconnected', {?MODULE, client_disconnected}, {?MODULE, client_disconnected, [Opts]}), - {ok, Opts}. + ok. client_connected(ConnAck, #mqtt_client{client_id = ClientId, username = Username, @@ -55,7 +47,7 @@ client_connected(ConnAck, #mqtt_client{client_id = ClientId, {session, Sess}, {protocol, ProtoVer}, {connack, ConnAck}, - {ts, emqttd_util:now_to_secs()}]), + {ts, emqttd_time:now_to_secs()}]), Msg = emqttd_message:make(presence, proplists:get_value(qos, Opts, 0), topic(connected, ClientId), @@ -65,7 +57,7 @@ client_connected(ConnAck, #mqtt_client{client_id = ClientId, client_disconnected(Reason, ClientId, Opts) -> Json = mochijson2:encode([{clientid, ClientId}, {reason, reason(Reason)}, - {ts, emqttd_util:now_to_secs()}]), + {ts, emqttd_time:now_to_secs()}]), Msg = emqttd_message:make(presence, proplists:get_value(qos, Opts, 0), topic(disconnected, ClientId), @@ -85,4 +77,3 @@ reason(Reason) when is_atom(Reason) -> Reason; reason({Error, _}) when is_atom(Error) -> Error; reason(_) -> internal_error. - diff --git a/src/emqttd_mod_rewrite.erl b/src/emqttd_mod_rewrite.erl index e18ba2c75..b1bad9766 100644 --- a/src/emqttd_mod_rewrite.erl +++ b/src/emqttd_mod_rewrite.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd rewrite module -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd rewrite module -module(emqttd_mod_rewrite). -behaviour(emqttd_gen_mod). @@ -33,9 +25,9 @@ -export([rewrite/3, rewrite/4]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- load(Opts) -> File = proplists:get_value(file, Opts), @@ -47,7 +39,7 @@ load(Opts) -> {?MODULE, rewrite, [unsubscribe, Sections]}), emqttd_broker:hook('message.publish', {?MODULE, rewrite_publish}, {?MODULE, rewrite, [publish, Sections]}), - {ok, Sections}. + ok. rewrite(_ClientId, TopicTable, subscribe, Sections) -> lager:info("rewrite subscribe: ~p", [TopicTable]), @@ -84,9 +76,9 @@ unload(_) -> emqttd_broker:unhook('client.unsubscribe',{?MODULE, rewrite_unsubscribe}), emqttd_broker:unhook('message.publish', {?MODULE, rewrite_publish}). -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- compile(Sections) -> C = fun({rewrite, Re, Dest}) -> diff --git a/src/emqttd_mod_subscription.erl b/src/emqttd_mod_subscription.erl index 701d68f94..098fc592d 100644 --- a/src/emqttd_mod_subscription.erl +++ b/src/emqttd_mod_subscription.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Subscription from Broker Side -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Subscription from Broker Side -module(emqttd_mod_subscription). -behaviour(emqttd_gen_mod). @@ -35,16 +27,12 @@ -record(state, {topics, stored = false}). --ifdef(TEST). --compile(export_all). --endif. - load(Opts) -> Topics = [{iolist_to_binary(Topic), QoS} || {Topic, QoS} <- Opts, ?IS_QOS(QoS)], State = #state{topics = Topics, stored = lists:member(stored, Opts)}, emqttd_broker:hook('client.connected', {?MODULE, client_connected}, {?MODULE, client_connected, [State]}), - {ok, State}. + ok. client_connected(?CONNACK_ACCEPT, #mqtt_client{client_id = ClientId, client_pid = ClientPid, diff --git a/src/emqttd_mod_sup.erl b/src/emqttd_mod_sup.erl index 6d7d809fe..759886fd3 100644 --- a/src/emqttd_mod_sup.erl +++ b/src/emqttd_mod_sup.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd module supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_mod_sup). -behaviour(supervisor). @@ -38,9 +29,9 @@ %% Helper macro for declaring children of supervisor -define(CHILD(Mod, Type), {Mod, {Mod, start_link, []}, permanent, 5000, Type, [Mod]}). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). @@ -55,10 +46,10 @@ start_child(ChildSpec) when is_tuple(ChildSpec) -> start_child(Mod, Type) when is_atom(Mod) and is_atom(Type) -> supervisor:start_child(?MODULE, ?CHILD(Mod, Type)). -%%%============================================================================= -%%% Supervisor callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Supervisor callbacks +%%-------------------------------------------------------------------- init([]) -> - {ok, {{one_for_one, 10, 3600}, []}}. + {ok, {{one_for_one, 10, 100}, []}}. diff --git a/src/emqttd_mqueue.erl b/src/emqttd_mqueue.erl index 51227dcf9..e17768efa 100644 --- a/src/emqttd_mqueue.erl +++ b/src/emqttd_mqueue.erl @@ -1,61 +1,54 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% -%%% A Simple in-memory message queue. -%%% -%%% Notice that MQTT is not an enterprise messaging queue. MQTT assume that client -%%% should be online in most of the time. -%%% -%%% This module implements a simple in-memory queue for MQTT persistent session. -%%% -%%% If the broker restarted or crashed, all the messages queued will be gone. -%%% -%%% Concept of Message Queue and Inflight Window: -%%% -%%% |<----------------- Max Len ----------------->| -%%% ----------------------------------------------- -%%% IN -> | Messages Queue | Inflight Window | -> Out -%%% ----------------------------------------------- -%%% |<--- Win Size --->| -%%% -%%% -%%% 1. Inflight Window to store the messages delivered and awaiting for puback. -%%% -%%% 2. Enqueue messages when the inflight window is full. -%%% -%%% 3. If the queue is full, dropped qos0 messages if store_qos0 is true, -%%% otherwise dropped the oldest one. -%%% -%%% @end -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 A Simple in-memory message queue. +%% +%% Notice that MQTT is not an enterprise messaging queue. MQTT assume that client +%% should be online in most of the time. +%% +%% This module implements a simple in-memory queue for MQTT persistent session. +%% +%% If the broker restarted or crashed, all the messages queued will be gone. +%% +%% Concept of Message Queue and Inflight Window: +%% +%% |<----------------- Max Len ----------------->| +%% ----------------------------------------------- +%% IN -> | Messages Queue | Inflight Window | -> Out +%% ----------------------------------------------- +%% |<--- Win Size --->| +%% +%% +%% 1. Inflight Window to store the messages delivered and awaiting for puback. +%% +%% 2. Enqueue messages when the inflight window is full. +%% +%% 3. If the queue is full, dropped qos0 messages if store_qos0 is true, +%% otherwise dropped the oldest one. +%% +%% @end + -module(emqttd_mqueue). -include("emqttd.hrl"). -include("emqttd_protocol.hrl"). +-import(proplists, [get_value/3]). + -export([new/3, type/1, name/1, is_empty/1, len/1, max_len/1, in/2, out/1, stats/1]). -define(LOW_WM, 0.2). @@ -97,19 +90,19 @@ %% @doc New Queue. -spec new(iolist(), list(mqueue_option()), fun()) -> mqueue(). new(Name, Opts, AlarmFun) -> - Type = emqttd_opts:g(type, Opts, simple), - MaxLen = emqttd_opts:g(max_length, Opts, infinity), + Type = get_value(type, Opts, simple), + MaxLen = get_value(max_length, Opts, infinity), init_q(#mqueue{type = Type, name = iolist_to_binary(Name), len = 0, max_len = MaxLen, low_wm = low_wm(MaxLen, Opts), high_wm = high_wm(MaxLen, Opts), - qos0 = emqttd_opts:g(queue_qos0, Opts, false), + qos0 = get_value(queue_qos0, Opts, false), alarm_fun = AlarmFun}, Opts). init_q(MQ = #mqueue{type = simple}, _Opts) -> MQ#mqueue{q = queue:new()}; init_q(MQ = #mqueue{type = priority}, Opts) -> - Priorities = emqttd_opts:g(priority, Opts, []), + Priorities = get_value(priority, Opts, []), init_p(Priorities, MQ#mqueue{q = priority_queue:new()}). init_p([], MQ) -> @@ -125,12 +118,12 @@ insert_p(Topic, P, MQ = #mqueue{priorities = Tab, pseq = Seq}) -> low_wm(infinity, _Opts) -> infinity; low_wm(MaxLen, Opts) -> - round(MaxLen * emqttd_opts:g(low_watermark, Opts, ?LOW_WM)). + round(MaxLen * get_value(low_watermark, Opts, ?LOW_WM)). high_wm(infinity, _Opts) -> infinity; high_wm(MaxLen, Opts) -> - round(MaxLen * emqttd_opts:g(high_watermark, Opts, ?HIGH_WM)). + round(MaxLen * get_value(high_watermark, Opts, ?HIGH_WM)). -spec name(mqueue()) -> iolist(). name(#mqueue{name = Name}) -> diff --git a/src/emqttd_net.erl b/src/emqttd_net.erl index 9257c92ac..a57e4a6ec 100644 --- a/src/emqttd_net.erl +++ b/src/emqttd_net.erl @@ -1,37 +1,29 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd net utility functions. some functions copied from rabbitmq. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd net utilities. -module(emqttd_net). -include_lib("kernel/include/inet.hrl"). --export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, - getaddr/2, port_to_listeners/1]). +-export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2, + port_to_listeners/1]). --export([peername/1, sockname/1, format/2, format/1, - connection_string/2, ntoa/1]). +-export([peername/1, sockname/1, format/2, format/1, ntoa/1, + connection_string/2]). -define(FIRST_TEST_BIND_PORT, 10000). diff --git a/src/emqttd_node.erl b/src/emqttd_node.erl new file mode 100644 index 000000000..255b432e6 --- /dev/null +++ b/src/emqttd_node.erl @@ -0,0 +1,42 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_node). + +-import(lists, [concat/1]). + +-export([is_aliving/1, parse_name/1]). + +%% @doc Is node aliving +-spec is_aliving(node()) -> boolean(). +is_aliving(Node) -> + case net_adm:ping(Node) of + pong -> true; + pang -> false + end. + +%% @doc Parse node name +-spec parse_name(string()) -> atom(). +parse_name(Name) when is_list(Name) -> + case string:tokens(Name, "@") of + [_Node, _Host] -> list_to_atom(Name); + _ -> with_host(Name) + end. + +with_host(Name) -> + [_, Host] = string:tokens(atom_to_list(node()), "@"), + list_to_atom(concat([Name, "@", Host])). + diff --git a/src/emqttd_opts.erl b/src/emqttd_opts.erl index 4e4a75e66..b2250bad6 100644 --- a/src/emqttd_opts.erl +++ b/src/emqttd_opts.erl @@ -1,36 +1,24 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd options handler. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_opts). --export([merge/2, g/2, g/3]). +-export([merge/2]). -%%------------------------------------------------------------------------------ %% @doc Merge Options -%% @end -%%------------------------------------------------------------------------------ merge(Defaults, Options) -> lists:foldl( fun({Opt, Val}, Acc) -> @@ -45,13 +33,3 @@ merge(Defaults, Options) -> end end, Defaults, Options). -%%------------------------------------------------------------------------------ -%% @doc Get option -%% @end -%%------------------------------------------------------------------------------ -g(Key, Options) -> - proplists:get_value(Key, Options). - -g(Key, Options, Default) -> - proplists:get_value(Key, Options, Default). - diff --git a/src/emqttd_packet.erl b/src/emqttd_packet.erl index 461696e69..f419a16ba 100644 --- a/src/emqttd_packet.erl +++ b/src/emqttd_packet.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT Packet Functions -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Packet Functions -module(emqttd_packet). -include("emqttd.hrl"). @@ -34,38 +26,26 @@ -export([format/1]). -%%------------------------------------------------------------------------------ %% @doc Protocol name of version -%% @end -%%------------------------------------------------------------------------------ --spec protocol_name(mqtt_vsn()) -> binary(). -protocol_name(Ver) when Ver =:= ?MQTT_PROTO_V31; Ver =:= ?MQTT_PROTO_V311-> +-spec protocol_name(mqtt_vsn()) -> binary(). +protocol_name(Ver) when Ver =:= ?MQTT_PROTO_V31; Ver =:= ?MQTT_PROTO_V311 -> proplists:get_value(Ver, ?PROTOCOL_NAMES). -%%------------------------------------------------------------------------------ %% @doc Name of MQTT packet type -%% @end -%%------------------------------------------------------------------------------ -spec type_name(mqtt_packet_type()) -> atom(). type_name(Type) when Type > ?RESERVED andalso Type =< ?DISCONNECT -> lists:nth(Type, ?TYPE_NAMES). -%%------------------------------------------------------------------------------ %% @doc Connack Name -%% @end -%%------------------------------------------------------------------------------ -spec connack_name(mqtt_connack()) -> atom(). -connack_name(?CONNACK_ACCEPT) -> 'CONNACK_ACCEPT'; -connack_name(?CONNACK_PROTO_VER) -> 'CONNACK_PROTO_VER'; -connack_name(?CONNACK_INVALID_ID ) -> 'CONNACK_INVALID_ID'; -connack_name(?CONNACK_SERVER) -> 'CONNACK_SERVER'; -connack_name(?CONNACK_CREDENTIALS) -> 'CONNACK_CREDENTIALS'; -connack_name(?CONNACK_AUTH) -> 'CONNACK_AUTH'. +connack_name(?CONNACK_ACCEPT) -> 'CONNACK_ACCEPT'; +connack_name(?CONNACK_PROTO_VER) -> 'CONNACK_PROTO_VER'; +connack_name(?CONNACK_INVALID_ID) -> 'CONNACK_INVALID_ID'; +connack_name(?CONNACK_SERVER) -> 'CONNACK_SERVER'; +connack_name(?CONNACK_CREDENTIALS) -> 'CONNACK_CREDENTIALS'; +connack_name(?CONNACK_AUTH) -> 'CONNACK_AUTH'. -%%------------------------------------------------------------------------------ %% @doc Format packet -%% @end -%%------------------------------------------------------------------------------ -spec format(mqtt_packet()) -> iolist(). format(#mqtt_packet{header = Header, variable = Variable, payload = Payload}) -> format_header(Header, format_variable(Variable, Payload)). @@ -115,7 +95,7 @@ format_variable(#mqtt_packet_connack{ack_flags = AckFlags, format_variable(#mqtt_packet_publish{topic_name = TopicName, packet_id = PacketId}) -> - io_lib:format("TopicName=~s, PacketId=~p", [TopicName, PacketId]); + io_lib:format("Topic=~s, PacketId=~p", [TopicName, PacketId]); format_variable(#mqtt_packet_puback{packet_id = PacketId}) -> io_lib:format("PacketId=~p", [PacketId]); diff --git a/src/emqttd_parser.erl b/src/emqttd_parser.erl index b9eddc8c6..8423308a3 100644 --- a/src/emqttd_parser.erl +++ b/src/emqttd_parser.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT Packet Parser -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Packet Parser -module(emqttd_parser). -include("emqttd.hrl"). @@ -38,10 +30,7 @@ -type parser() :: fun( (binary()) -> any() ). -%%------------------------------------------------------------------------------ %% @doc Initialize a parser -%% @end -%%------------------------------------------------------------------------------ -spec new(Opts :: [option()]) -> parser(). new(Opts) -> fun(Bin) -> parse(Bin, {none, limit(Opts)}) end. @@ -50,11 +39,9 @@ limit(Opts) -> #mqtt_packet_limit{max_packet_size = proplists:get_value(max_packet_size, Opts, ?MAX_LEN)}. -%%------------------------------------------------------------------------------ %% @doc Parse MQTT Packet -%% @end -%%------------------------------------------------------------------------------ --spec parse(binary(), {none, [option()]} | fun()) -> {ok, mqtt_packet()} | {error, any()} | {more, fun()}. +-spec parse(binary(), {none, [option()]} | fun()) -> + {ok, mqtt_packet()} | {error, any()} | {more, fun()}. parse(<<>>, {none, Limit}) -> {more, fun(Bin) -> parse(Bin, {none, Limit}) end}; parse(<>, {none, Limit}) -> diff --git a/src/emqttd_plugins.erl b/src/emqttd_plugins.erl index df008cc7f..accf2b03e 100644 --- a/src/emqttd_plugins.erl +++ b/src/emqttd_plugins.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd plugins. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_plugins). -include("emqttd.hrl"). @@ -33,10 +24,7 @@ -export([list/0]). -%%------------------------------------------------------------------------------ %% @doc Load all plugins when the broker started. -%% @end -%%------------------------------------------------------------------------------ -spec load() -> list() | {error, any()}. load() -> case env(loaded_file) of @@ -65,10 +53,7 @@ load_plugins(Names, Persistent) -> NeedToLoad = Names -- NotFound -- names(started_app), [load_plugin(find_plugin(Name, Plugins), Persistent) || Name <- NeedToLoad]. -%%------------------------------------------------------------------------------ %% @doc Unload all plugins before broker stopped. -%% @end -%%------------------------------------------------------------------------------ -spec unload() -> list() | {error, any()}. unload() -> case env(loaded_file) of @@ -82,10 +67,7 @@ unload() -> stop_plugins(Names) -> [stop_app(App) || App <- Names]. -%%------------------------------------------------------------------------------ %% @doc List all available plugins -%% @end -%%------------------------------------------------------------------------------ -spec list() -> [mqtt_plugin()]. list() -> case env(plugins_dir) of @@ -119,10 +101,7 @@ plugin(PluginsDir, AppFile0) -> Descr = proplists:get_value(description, Attrs, ""), #mqtt_plugin{name = Name, version = Ver, config = AppsEnv1, descr = Descr}. -%%------------------------------------------------------------------------------ %% @doc Load One Plugin -%% @end -%%------------------------------------------------------------------------------ -spec load(atom()) -> ok | {error, any()}. load(PluginName) when is_atom(PluginName) -> case lists:member(PluginName, names(started_app)) of @@ -182,10 +161,7 @@ find_plugin(Name) -> find_plugin(Name, Plugins) -> lists:keyfind(Name, 2, Plugins). -%%------------------------------------------------------------------------------ %% @doc UnLoad One Plugin -%% @end -%%------------------------------------------------------------------------------ -spec unload(atom()) -> ok | {error, any()}. unload(PluginName) when is_atom(PluginName) -> case {lists:member(PluginName, names(started_app)), lists:member(PluginName, names(plugin))} of @@ -217,9 +193,9 @@ stop_app(App) -> lager:error("stop plugin ~p error: ~p", [App]), {error, Reason} end. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- names(plugin) -> names(list()); diff --git a/src/emqttd_pool_sup.erl b/src/emqttd_pool_sup.erl index 8884e97c1..e1f883e3f 100644 --- a/src/emqttd_pool_sup.erl +++ b/src/emqttd_pool_sup.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Common Pool Supervisor -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Common Pool Supervisor -module(emqttd_pool_sup). -behaviour(supervisor). diff --git a/src/emqttd_pooler.erl b/src/emqttd_pooler.erl index 80fd400aa..c22e501dc 100644 --- a/src/emqttd_pooler.erl +++ b/src/emqttd_pooler.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd pooler. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_pooler). -behaviour(gen_server). @@ -41,40 +32,30 @@ -record(state, {pool, id}). -%%------------------------------------------------------------------------------ %% @doc Start Pooler Supervisor. -%% @end -%%------------------------------------------------------------------------------ start_link() -> emqttd_pool_sup:start_link(pooler, random, {?MODULE, start_link, []}). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -spec start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, any()}. start_link(Pool, Id) -> - gen_server:start_link({local, emqttd:reg_name(?MODULE, Id)}, ?MODULE, [Pool, Id], []). + gen_server:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id], []). -%%------------------------------------------------------------------------------ %% @doc Submit work to pooler -%% @end -%%------------------------------------------------------------------------------ -submit(Fun) -> - gen_server:call(worker(), {submit, Fun}, infinity). +submit(Fun) -> gen_server:call(worker(), {submit, Fun}, infinity). -%%------------------------------------------------------------------------------ %% @doc Submit work to pooler asynchronously -%% @end -%%------------------------------------------------------------------------------ async_submit(Fun) -> gen_server:cast(worker(), {async_submit, Fun}). worker() -> gproc_pool:pick_worker(pooler). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([Pool, Id]) -> ?GPROC_POOL(join, Pool, Id), @@ -105,9 +86,9 @@ terminate(_Reason, #state{pool = Pool, id = Id}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- run({M, F, A}) -> erlang:apply(M, F, A); diff --git a/src/emqttd_protocol.erl b/src/emqttd_protocol.erl index 96acfcba7..85c9fb0bc 100644 --- a/src/emqttd_protocol.erl +++ b/src/emqttd_protocol.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd protocol. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Protocol Processor. -module(emqttd_protocol). -include("emqttd.hrl"). @@ -31,6 +23,8 @@ -include("emqttd_internal.hrl"). +-import(proplists, [get_value/2, get_value/3]). + %% API -export([init/3, info/1, clientid/1, client/1, session/1]). @@ -55,13 +49,10 @@ lager:Level([{client, State#proto_state.client_id}], "Client(~s@~s): " ++ Format, [State#proto_state.client_id, esockd_net:format(State#proto_state.peername) | Args])). -%%------------------------------------------------------------------------------ %% @doc Init protocol -%% @end -%%------------------------------------------------------------------------------ init(Peername, SendFun, Opts) -> - MaxLen = emqttd_opts:g(max_clientid_len, Opts, ?MAX_CLIENTID_LEN), - WsInitialHeaders = emqttd_opts:g(ws_initial_headers, Opts), + MaxLen = get_value(max_clientid_len, Opts, ?MAX_CLIENTID_LEN), + WsInitialHeaders = get_value(ws_initial_headers, Opts), #proto_state{peername = Peername, sendfun = SendFun, max_clientid_len = MaxLen, @@ -233,8 +224,9 @@ process(?PACKET(?DISCONNECT), State) -> {stop, normal, State#proto_state{will_msg = undefined}}. publish(Packet = ?PUBLISH_PACKET(?QOS_0, _PacketId), - #proto_state{client_id = ClientId, session = Session}) -> - emqttd_session:publish(Session, emqttd_message:from_packet(ClientId, Packet)); + #proto_state{client_id = ClientId, username = Username, session = Session}) -> + Msg = emqttd_message:from_packet(Username, ClientId, Packet), + emqttd_session:publish(Session, Msg); publish(Packet = ?PUBLISH_PACKET(?QOS_1, _PacketId), State) -> with_puback(?PUBACK, Packet, State); @@ -243,8 +235,10 @@ publish(Packet = ?PUBLISH_PACKET(?QOS_2, _PacketId), State) -> with_puback(?PUBREC, Packet, State). with_puback(Type, Packet = ?PUBLISH_PACKET(_Qos, PacketId), - State = #proto_state{client_id = ClientId, session = Session}) -> - Msg = emqttd_message:from_packet(ClientId, Packet), + State = #proto_state{client_id = ClientId, + username = Username, + session = Session}) -> + Msg = emqttd_message:from_packet(Username, ClientId, Packet), case emqttd_session:publish(Session, Msg) of ok -> send(?PUBACK_PACKET(Type, PacketId), State); @@ -315,9 +309,10 @@ start_keepalive(0) -> ignore; start_keepalive(Sec) when Sec > 0 -> self() ! {keepalive, start, round(Sec * 1.2)}. -%%---------------------------------------------------------------------------- +%%-------------------------------------------------------------------- %% Validate Packets -%%---------------------------------------------------------------------------- +%%-------------------------------------------------------------------- + validate_connect(Connect = #mqtt_packet_connect{}, ProtoState) -> case validate_protocol(Connect) of true -> diff --git a/src/emqttd_pubsub.erl b/src/emqttd_pubsub.erl index 5ef0b915f..eb60fdb1a 100644 --- a/src/emqttd_pubsub.erl +++ b/src/emqttd_pubsub.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc PubSub -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_pubsub). -behaviour(gen_server2). @@ -52,17 +43,13 @@ -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --ifdef(TEST). --compile(export_all). --endif. - -record(state, {pool, id, statsfun}). -define(ROUTER, emqttd_router). -%%%============================================================================= -%%% Mnesia callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Mnesia callbacks +%%-------------------------------------------------------------------- mnesia(boot) -> ok = create_table(topic, ram_copies), if_subscription(fun(RamOrDisc) -> @@ -111,13 +98,13 @@ env(Key) -> end. cache_env(Key) -> - Val = emqttd_opts:g(Key, emqttd_broker:env(pubsub)), + Val = proplists:get_value(Key, emqttd_broker:env(pubsub)), put({pubsub, Key}, Val), Val. -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- %% @doc Start one pubsub server -spec start_link(Pool, Id, StatsFun, Opts) -> {ok, pid()} | ignore | {error, any()} when @@ -126,7 +113,7 @@ cache_env(Key) -> StatsFun :: fun((atom()) -> any()), Opts :: list(tuple()). start_link(Pool, Id, StatsFun, Opts) -> - gen_server2:start_link({local, emqttd:reg_name(?MODULE, Id)}, + gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id, StatsFun, Opts], []). %% @doc Create Topic or Subscription. @@ -237,13 +224,13 @@ publish(To, Msg) -> %% @doc Match Topic Name with Topic Filters -spec match(emqttd_topic:topic()) -> [mqtt_topic()]. match(To) -> - MatchedTopics = mnesia:async_dirty(fun emqttd_trie:match/1, [To]), + Matched = mnesia:async_dirty(fun emqttd_trie:match/1, [To]), %% ets:lookup for topic table will be replicated to all nodes. - lists:append([ets:lookup(topic, Topic) || Topic <- MatchedTopics]). + lists:append([ets:lookup(topic, Topic) || Topic <- [To | Matched]]). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([Pool, Id, StatsFun, _Opts]) -> ?GPROC_POOL(join, Pool, Id), @@ -332,9 +319,9 @@ terminate(_Reason, #state{pool = Pool, id = Id}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- add_topics(Records) -> lists:foreach(fun add_topic/1, Records). @@ -342,7 +329,10 @@ add_topics(Records) -> add_topic(TopicR = #mqtt_topic{topic = Topic}) -> case mnesia:wread({topic, Topic}) of [] -> - ok = emqttd_trie:insert(Topic), + case emqttd_topic:wildcard(Topic) of + true -> emqttd_trie:insert(Topic); + false -> ok + end, mnesia:write(topic, TopicR, write); Records -> case lists:member(TopicR, Records) of @@ -363,7 +353,7 @@ add_subscription(SubId, {Topic, Qos}) -> Pattern = #mqtt_subscription{subid = SubId, topic = Topic, qos = '_'}, Records = mnesia:match_object(subscription, Pattern, write), case lists:member(Subscription, Records) of - true -> + true -> ok; false -> [delete_subscription(Record) || Record <- Records], @@ -410,9 +400,9 @@ try_monitor(SubPid) -> false -> erlang:monitor(process, SubPid) end. -%%%============================================================================= -%%% Trace Functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Trace Functions +%%-------------------------------------------------------------------- trace(publish, From, _Msg) when is_atom(From) -> %% Dont' trace '$SYS' publish diff --git a/src/emqttd_pubsub_helper.erl b/src/emqttd_pubsub_helper.erl index 820280445..6e697b060 100644 --- a/src/emqttd_pubsub_helper.erl +++ b/src/emqttd_pubsub_helper.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 PubSub Helper. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- -module(emqttd_pubsub_helper). -behaviour(gen_server). @@ -47,9 +39,9 @@ start_link(StatsFun) -> gen_server:start_link({local, ?SERVER}, ?MODULE, [StatsFun], []). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([StatsFun]) -> mnesia:subscribe(system), diff --git a/src/emqttd_pubsub_sup.erl b/src/emqttd_pubsub_sup.erl index f44d56c1f..c04b486f6 100644 --- a/src/emqttd_pubsub_sup.erl +++ b/src/emqttd_pubsub_sup.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc PubSub Supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 PubSub Supervisor. -module(emqttd_pubsub_sup). -behaviour(supervisor). @@ -55,8 +47,9 @@ init([Env]) -> %% Router Pool Sup RouterMFA = {emqttd_router, start_link, [fun setstats/1, Env]}, + %% Pool_size / 2 - RouterSup = emqttd_pool_sup:spec(router_pool, [router, hash, 1 + (pool_size(Env) div 2), RouterMFA]), + RouterSup = emqttd_pool_sup:spec(router_pool, [router, hash, router_pool(Env), RouterMFA]), %% PubSub Pool Sup PubSubMFA = {emqttd_pubsub, start_link, [fun setstats/1, Env]}, @@ -79,6 +72,12 @@ ensure_tab(Tab, Opts) -> _ -> ok end. +router_pool(Env) -> + case pool_size(Env) div 2 of + 0 -> 1; + I -> I + end. + pool_size(Env) -> Schedulers = erlang:system_info(schedulers), proplists:get_value(pool_size, Env, Schedulers). diff --git a/src/emqttd_retainer.erl b/src/emqttd_retainer.erl index 5d21ffa29..f18050b28 100644 --- a/src/emqttd_retainer.erl +++ b/src/emqttd_retainer.erl @@ -1,32 +1,21 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT retained message storage. -%%% -%%% TODO: should match topic tree -%%% -%%% @end -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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. +%%-------------------------------------------------------------------- + +%% TODO: should match topic tree +%% @doc MQTT retained message storage. -module(emqttd_retainer). -behaviour(gen_server). @@ -57,9 +46,9 @@ -record(state, {stats_fun, expired_after, stats_timer, expire_timer}). -%%%============================================================================= -%%% Mnesia callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Mnesia callbacks +%%-------------------------------------------------------------------- mnesia(boot) -> ok = emqttd_mnesia:create_table(retained, [ @@ -70,22 +59,16 @@ mnesia(boot) -> mnesia(copy) -> ok = emqttd_mnesia:copy_table(retained). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start a retained server -%% @end -%%------------------------------------------------------------------------------ -spec start_link() -> {ok, pid()} | ignore | {error, any()}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). -%%%----------------------------------------------------------------------------- %% @doc Retain message -%% @end -%%%----------------------------------------------------------------------------- -spec retain(mqtt_message()) -> ok | ignore. retain(#mqtt_message{retain = false}) -> ignore; @@ -121,10 +104,7 @@ env(Key) -> Val end. -%%%----------------------------------------------------------------------------- %% @doc Deliver retained messages to subscribed client -%% @end -%%%----------------------------------------------------------------------------- -spec dispatch(Topic, CPid) -> any() when Topic :: binary(), CPid :: pid(). @@ -144,9 +124,9 @@ dispatch(Topic, CPid) when is_binary(Topic) -> end, lists:foreach(fun(Msg) -> CPid ! {dispatch, Topic, Msg} end, lists:reverse(Msgs)). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([]) -> StatsFun = emqttd_stats:statsfun('retained/count', 'retained/max'), @@ -174,7 +154,7 @@ handle_info(expire, State = #state{expired_after = Never}) {noreply, State, hibernate}; handle_info(expire, State = #state{expired_after = ExpiredAfter}) -> - expire(emqttd_util:now_to_secs(os:timestamp()) - ExpiredAfter), + expire(emqttd_time:now_to_secs() - ExpiredAfter), {noreply, State, hibernate}; handle_info(Info, State) -> @@ -187,9 +167,9 @@ terminate(_Reason, _State = #state{stats_timer = TRef1, expire_timer = TRef2}) - code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- expire(Time) -> mnesia:async_dirty( diff --git a/src/emqttd_router.erl b/src/emqttd_router.erl index 183a4b5a0..5fc025d8d 100644 --- a/src/emqttd_router.erl +++ b/src/emqttd_router.erl @@ -1,29 +1,22 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Message Router on local node. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%%-------------------------------------------------------------------- +%% @doc +%% The Message Router on Local Node. +%% @end -module(emqttd_router). -behaviour(gen_server2). @@ -54,20 +47,20 @@ -record(aging, {topics, time, tref}). --record(state, {pool, id, statsfun, aging :: #aging{}}). +-record(state, {pool, id, aging :: #aging{}, statsfun}). -%% @doc Start a local router. +%% @doc Start a router. -spec start_link(atom(), pos_integer(), fun((atom()) -> ok), list()) -> {ok, pid()} | {error, any()}. start_link(Pool, Id, StatsFun, Env) -> - gen_server2:start_link({local, emqttd:reg_name(?MODULE,Id)}, + gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id, StatsFun, Env], []). -%% @doc Route Message on the local node. +%% @doc Route Message on this node. -spec route(emqttd_topic:topic(), mqtt_message()) -> any(). route(Queue = <<"$Q/", _Q>>, Msg) -> case lookup_routes(Queue) of [] -> - emqttd_metrics:inc('messages/dropped'); + dropped(Queue); [SubPid] -> SubPid ! {dispatch, Queue, Msg}; Routes -> @@ -79,8 +72,8 @@ route(Queue = <<"$Q/", _Q>>, Msg) -> route(Topic, Msg) -> case lookup_routes(Topic) of [] -> - emqttd_metrics:inc('messages/dropped'); - [SubPid] -> %% optimize + dropped(Topic); + [SubPid] -> SubPid ! {dispatch, Topic, Msg}; Routes -> lists:foreach(fun(SubPid) -> @@ -88,9 +81,16 @@ route(Topic, Msg) -> end, Routes) end. +%% @private +%% @doc Ingore $SYS Messages. +dropped(<<"$SYS/", _/binary>>) -> + ok; +dropped(_Topic) -> + emqttd_metrics:inc('messages/dropped'). + %% @doc Has Route? -spec has_route(emqttd_topic:topic()) -> boolean(). -has_route(Topic) -> +has_route(Topic) when is_binary(Topic) -> ets:member(route, Topic). %% @doc Lookup Routes @@ -103,12 +103,12 @@ lookup_routes(Topic) when is_binary(Topic) -> [] end. -%% @doc Add Route. +%% @doc Add Route -spec add_route(emqttd_topic:topic(), pid()) -> ok. add_route(Topic, Pid) when is_pid(Pid) -> call(pick(Topic), {add_route, Topic, Pid}). -%% @doc Add Routes. +%% @doc Add Routes -spec add_routes(list(emqttd_topic:topic()), pid()) -> ok. add_routes([], _Pid) -> ok; @@ -120,12 +120,12 @@ add_routes(Topics, Pid) -> call(Router, {add_routes, Slice, Pid}) end, slice(Topics)). -%% @doc Delete Route. +%% @doc Delete Route -spec delete_route(emqttd_topic:topic(), pid()) -> ok. delete_route(Topic, Pid) -> cast(pick(Topic), {delete_route, Topic, Pid}). -%% @doc Delete Routes. +%% @doc Delete Routes -spec delete_routes(list(emqttd_topic:topic()), pid()) -> ok. delete_routes([Topic], Pid) -> delete_route(Topic, Pid); @@ -145,8 +145,11 @@ slice(Topics) -> pick(Topic) -> gproc_pool:pick_worker(router, Topic). +%% @doc For unit test. stop(Id) when is_integer(Id) -> - gen_server2:call(emqttd:reg_name(?MODULE, Id), stop). + gen_server2:call(?PROC_NAME(?MODULE, Id), stop); +stop(Pid) when is_pid(Pid) -> + gen_server2:call(Pid, stop). call(Router, Request) -> gen_server2:call(Router, Request, infinity). @@ -156,21 +159,19 @@ cast(Router, Msg) -> init([Pool, Id, StatsFun, Opts]) -> - %% Calls from pubsub should be scheduled first? - process_flag(priority, high), + emqttd_time:seed(), ?GPROC_POOL(join, Pool, Id), - emqttd:seed_now(), + Aging = init_aging(Opts), + {ok, #state{pool = Pool, id = Id, aging = Aging, statsfun = StatsFun}}. + +%% Init Aging +init_aging(Opts) -> AgingSecs = proplists:get_value(route_aging, Opts, 5), - - %% Aging Timer {ok, AgingTref} = start_tick(AgingSecs + random:uniform(AgingSecs)), - - Aging = #aging{topics = dict:new(), time = AgingSecs, tref = AgingTref}, - - {ok, #state{pool = Pool, id = Id, statsfun = StatsFun, aging = Aging}}. + #aging{topics = dict:new(), time = AgingSecs, tref = AgingTref}. start_tick(Secs) -> timer:send_interval(timer:seconds(Secs), {clean, aged}). @@ -190,24 +191,15 @@ handle_call(Req, _From, State) -> ?UNEXPECTED_REQ(Req, State). handle_cast({delete_route, Topic, Pid}, State = #state{aging = Aging}) -> - ets:delete_object(route, {Topic, Pid}), - NewState = - case has_route(Topic) of - false -> State#state{aging = store_aged(Topic, Aging)}; - true -> State - end, - {noreply, setstats(NewState)}; + Aging1 = delete_route_(Topic, Pid, Aging), + {noreply, setstats(State#state{aging = Aging1})}; handle_cast({delete_routes, Topics, Pid}, State) -> - NewAging = + Aging1 = lists:foldl(fun(Topic, Aging) -> - ets:delete_object(route, {Topic, Pid}), - case has_route(Topic) of - false -> store_aged(Topic, Aging); - true -> Aging - end + delete_route_(Topic, Pid, Aging) end, State#state.aging, Topics), - {noreply, setstats(State#state{aging = NewAging})}; + {noreply, setstats(State#state{aging = Aging1})}; handle_cast(Msg, State) -> ?UNEXPECTED_MSG(Msg, State). @@ -216,13 +208,13 @@ handle_info({clean, aged}, State = #state{aging = Aging}) -> #aging{topics = Dict, time = Time} = Aging, - ByTime = emqttd_util:now_to_secs() - Time, + ByTime = emqttd_time:now_to_secs() - Time, Dict1 = try_clean(ByTime, dict:to_list(Dict)), - NewAging = Aging#aging{topics = dict:from_list(Dict1)}, + Aging1 = Aging#aging{topics = dict:from_list(Dict1)}, - {noreply, State#state{aging = NewAging}, hibernate}; + {noreply, State#state{aging = Aging1}, hibernate}; handle_info(Info, State) -> ?UNEXPECTED_INFO(Info, State). @@ -234,6 +226,13 @@ terminate(_Reason, #state{pool = Pool, id = Id, aging = #aging{tref = TRef}}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. +delete_route_(Topic, Pid, Aging) -> + ets:delete_object(route, {Topic, Pid}), + case has_route(Topic) of + false -> store_aged(Topic, Aging); + true -> Aging + end. + try_clean(ByTime, List) -> try_clean(ByTime, List, []). @@ -262,22 +261,19 @@ try_clean2(ByTime, {Topic, _TS}, Left, Acc) -> try_remove_topic(TopicR = #mqtt_topic{topic = Topic}) -> %% Lock topic first case mnesia:wread({topic, Topic}) of - [] -> - ok; %% mnesia:abort(not_found); - [TopicR] -> - %% Remove topic and trie - delete_topic(TopicR), - emqttd_trie:delete(Topic); - _More -> - %% Remove topic only - delete_topic(TopicR) + [] -> ok; + [TopicR] -> %% Remove topic and trie + delete_topic(TopicR), + emqttd_trie:delete(Topic); + _More -> %% Remove topic only + delete_topic(TopicR) end. delete_topic(TopicR) -> mnesia:delete_object(topic, TopicR, write). store_aged(Topic, Aging = #aging{topics = Dict}) -> - Now = emqttd_util:now_to_secs(), + Now = emqttd_time:now_to_secs(), Aging#aging{topics = dict:store(Topic, Now, Dict)}. setstats(State = #state{statsfun = StatsFun}) -> diff --git a/src/emqttd_serializer.erl b/src/emqttd_serializer.erl index 6cded9b47..3580e8507 100644 --- a/src/emqttd_serializer.erl +++ b/src/emqttd_serializer.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT Packet Serializer -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Packet Serializer -module(emqttd_serializer). -include("emqttd.hrl"). @@ -32,10 +24,7 @@ %% API -export([serialize/1]). -%%------------------------------------------------------------------------------ %% @doc Serialise MQTT Packet -%% @end -%%------------------------------------------------------------------------------ -spec serialize(mqtt_packet()) -> binary(). serialize(#mqtt_packet{header = Header = #mqtt_packet_header{type = Type}, variable = Variable, diff --git a/src/emqttd_session.erl b/src/emqttd_session.erl index f5f3d7579..f9779509f 100644 --- a/src/emqttd_session.erl +++ b/src/emqttd_session.erl @@ -1,49 +1,42 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Session for persistent MQTT client. -%%% -%%% Session State in the broker consists of: -%%% -%%% 1. The Client’s subscriptions. -%%% -%%% 2. inflight qos1/2 messages sent to the client but unacked, QoS 1 and QoS 2 -%%% messages which have been sent to the Client, but have not been completely -%%% acknowledged. -%%% -%%% 3. inflight qos2 messages received from client and waiting for pubrel. QoS 2 -%%% messages which have been received from the Client, but have not been -%%% completely acknowledged. -%%% -%%% 4. all qos1, qos2 messages published to when client is disconnected. -%%% QoS 1 and QoS 2 messages pending transmission to the Client. -%%% -%%% 5. Optionally, QoS 0 messages pending transmission to the Client. -%%% -%%% State of Message: newcome, inflight, pending -%%% -%%% @end -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Session for persistent MQTT client. +%% +%% Session State in the broker consists of: +%% +%% 1. The Client’s subscriptions. +%% +%% 2. inflight qos1/2 messages sent to the client but unacked, QoS 1 and QoS 2 +%% messages which have been sent to the Client, but have not been completely +%% acknowledged. +%% +%% 3. inflight qos2 messages received from client and waiting for pubrel. QoS 2 +%% messages which have been received from the Client, but have not been +%% completely acknowledged. +%% +%% 4. all qos1, qos2 messages published to when client is disconnected. +%% QoS 1 and QoS 2 messages pending transmission to the Client. +%% +%% 5. Optionally, QoS 0 messages pending transmission to the Client. +%% +%% State of Message: newcome, inflight, pending +%% +%% @end + -module(emqttd_session). -include("emqttd.hrl"). @@ -54,6 +47,8 @@ -behaviour(gen_server2). +-import(proplists, [get_value/2, get_value/3]). + %% Session API -export([start_link/3, resume/3, info/1, destroy/2]). @@ -140,41 +135,26 @@ lager:Level([{client, State#session.client_id}], "Session(~s): " ++ Format, [State#session.client_id | Args])). -%%------------------------------------------------------------------------------ %% @doc Start a session. -%% @end -%%------------------------------------------------------------------------------ -spec start_link(boolean(), mqtt_client_id(), pid()) -> {ok, pid()} | {error, any()}. start_link(CleanSess, ClientId, ClientPid) -> gen_server2:start_link(?MODULE, [CleanSess, ClientId, ClientPid], []). -%%------------------------------------------------------------------------------ %% @doc Resume a session. -%% @end -%%------------------------------------------------------------------------------ -spec resume(pid(), mqtt_client_id(), pid()) -> ok. resume(SessPid, ClientId, ClientPid) -> gen_server2:cast(SessPid, {resume, ClientId, ClientPid}). -%%------------------------------------------------------------------------------ %% @doc Session Info. -%% @end -%%------------------------------------------------------------------------------ info(SessPid) -> gen_server2:call(SessPid, info). -%%------------------------------------------------------------------------------ %% @doc Destroy a session. -%% @end -%%------------------------------------------------------------------------------ -spec destroy(pid(), mqtt_client_id()) -> ok. destroy(SessPid, ClientId) -> gen_server2:cast(SessPid, {destroy, ClientId}). -%%------------------------------------------------------------------------------ %% @doc Subscribe Topics -%% @end -%%------------------------------------------------------------------------------ -spec subscribe(pid(), [{binary(), mqtt_qos()}]) -> ok. subscribe(SessPid, TopicTable) -> gen_server2:cast(SessPid, {subscribe, TopicTable, fun(_) -> ok end}). @@ -187,10 +167,7 @@ subscribe(SessPid, PacketId, TopicTable) -> end, gen_server2:cast(SessPid, {subscribe, TopicTable, AckFun}). -%%------------------------------------------------------------------------------ %% @doc Publish message -%% @end -%%------------------------------------------------------------------------------ -spec publish(pid(), mqtt_message()) -> ok | {error, any()}. publish(_SessPid, Msg = #mqtt_message{qos = ?QOS_0}) -> %% publish qos0 directly @@ -204,10 +181,7 @@ publish(SessPid, Msg = #mqtt_message{qos = ?QOS_2}) -> %% publish qos2 by session gen_server2:call(SessPid, {publish, Msg}, ?PUBSUB_TIMEOUT). -%%------------------------------------------------------------------------------ %% @doc PubAck message -%% @end -%%------------------------------------------------------------------------------ -spec puback(pid(), mqtt_packet_id()) -> ok. puback(SessPid, PktId) -> gen_server2:cast(SessPid, {puback, PktId}). @@ -224,17 +198,14 @@ pubrel(SessPid, PktId) -> pubcomp(SessPid, PktId) -> gen_server2:cast(SessPid, {pubcomp, PktId}). -%%------------------------------------------------------------------------------ %% @doc Unsubscribe Topics -%% @end -%%------------------------------------------------------------------------------ -spec unsubscribe(pid(), [binary()]) -> ok. unsubscribe(SessPid, Topics) -> gen_server2:cast(SessPid, {unsubscribe, Topics}). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([CleanSess, ClientId, ClientPid]) -> process_flag(trap_exit, true), @@ -247,16 +218,16 @@ init([CleanSess, ClientId, ClientPid]) -> client_pid = ClientPid, subscriptions = dict:new(), inflight_queue = [], - max_inflight = emqttd_opts:g(max_inflight, SessEnv, 0), + max_inflight = get_value(max_inflight, SessEnv, 0), message_queue = emqttd_mqueue:new(ClientId, QEnv, emqttd_alarm:alarm_fun()), awaiting_rel = #{}, awaiting_ack = #{}, awaiting_comp = #{}, - retry_interval = emqttd_opts:g(unack_retry_interval, SessEnv), - await_rel_timeout = emqttd_opts:g(await_rel_timeout, SessEnv), - max_awaiting_rel = emqttd_opts:g(max_awaiting_rel, SessEnv), - expired_after = emqttd_opts:g(expired_after, SessEnv) * 3600, - collect_interval = emqttd_opts:g(collect_interval, SessEnv, 0), + retry_interval = get_value(unack_retry_interval, SessEnv), + await_rel_timeout = get_value(await_rel_timeout, SessEnv), + max_awaiting_rel = get_value(max_awaiting_rel, SessEnv), + expired_after = get_value(expired_after, SessEnv) * 3600, + collect_interval = get_value(collect_interval, SessEnv, 0), timestamp = os:timestamp()}, emqttd_sm:register_session(CleanSess, ClientId, sess_info(Session)), %% start statistics @@ -564,13 +535,9 @@ terminate(_Reason, #session{clean_sess = CleanSess, client_id = ClientId}) -> code_change(_OldVsn, Session, _Extra) -> {ok, Session}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= - -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Kick old client out -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- kick(_ClientId, undefined, _Pid) -> ignore; kick(_ClientId, Pid, Pid) -> @@ -581,9 +548,9 @@ kick(ClientId, OldPid, Pid) -> %% Clean noproc receive {'EXIT', OldPid, _} -> ok after 0 -> ok end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Dispatch Messages -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Queue message if client disconnected dispatch(Msg, Session = #session{client_pid = undefined, message_queue = Q}) -> @@ -613,9 +580,9 @@ tune_qos(Topic, Msg = #mqtt_message{qos = PubQos}, Subscriptions) -> Msg end. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Check inflight and awaiting_rel -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- check_inflight(#session{max_inflight = 0}) -> true; @@ -628,9 +595,9 @@ check_awaiting_rel(#session{awaiting_rel = AwaitingRel, max_awaiting_rel = MaxLen}) -> maps:size(AwaitingRel) < MaxLen. -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Dequeue and Deliver -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- dequeue(Session = #session{client_pid = undefined}) -> %% do nothing if client is disconnected @@ -670,7 +637,7 @@ redeliver(Msg = #mqtt_message{qos = QoS}, Session = #session{client_pid = Client ClientPid ! {deliver, Msg#mqtt_message{dup = true}}, await(Msg, Session). -%%------------------------------------------------------------------------------ +%%-------------------------------------------------------------------- %% Awaiting ack for qos1, qos2 message %%------------------------------------------------------------------------------ await(#mqtt_message{pktid = PktId}, Session = #session{awaiting_ack = Awaiting, diff --git a/src/emqttd_session_sup.erl b/src/emqttd_session_sup.erl index 49a25546a..6bf703c6d 100644 --- a/src/emqttd_session_sup.erl +++ b/src/emqttd_session_sup.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd session supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd session supervisor. -module(emqttd_session_sup). -behavior(supervisor). @@ -31,29 +23,22 @@ -export([init/1]). -%%------------------------------------------------------------------------------ %% @doc Start session supervisor -%% @end -%%------------------------------------------------------------------------------ -spec start_link() -> {ok, pid()}. start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -%%------------------------------------------------------------------------------ %% @doc Start a session -%% @end -%%------------------------------------------------------------------------------ -spec start_session(boolean(), binary(), pid()) -> {ok, pid()}. start_session(CleanSess, ClientId, ClientPid) -> supervisor:start_child(?MODULE, [CleanSess, ClientId, ClientPid]). -%%%============================================================================= -%%% Supervisor callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Supervisor callbacks +%%-------------------------------------------------------------------- init([]) -> {ok, {{simple_one_for_one, 10, 10}, [{session, {emqttd_session, start_link, []}, temporary, 10000, worker, [emqttd_session]}]}}. - diff --git a/src/emqttd_sm.erl b/src/emqttd_sm.erl index 52cd5534b..a16c13f71 100644 --- a/src/emqttd_sm.erl +++ b/src/emqttd_sm.erl @@ -1,30 +1,24 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Session Manager -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Session Manager -module(emqttd_sm). +-behaviour(gen_server2). + -include("emqttd.hrl"). -include("emqttd_internal.hrl"). @@ -42,8 +36,6 @@ -export([register_session/3, unregister_session/2]). --behaviour(gen_server2). - %% gen_server Function Exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). @@ -60,9 +52,9 @@ -define(LOG(Level, Format, Args, Session), lager:Level("SM(~s): " ++ Format, [Session#mqtt_session.client_id | Args])). -%%%============================================================================= -%%% Mnesia callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Mnesia callbacks +%%-------------------------------------------------------------------- mnesia(boot) -> %% Global Session Table @@ -75,31 +67,22 @@ mnesia(boot) -> mnesia(copy) -> ok = emqttd_mnesia:copy_table(session). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start a session manager -%% @end -%%------------------------------------------------------------------------------ -spec start_link(atom(), pos_integer()) -> {ok, pid()} | ignore | {error, any()}. start_link(Pool, Id) -> - gen_server2:start_link({local, emqttd:reg_name(?MODULE, Id)}, ?MODULE, [Pool, Id], []). + gen_server2:start_link({local, ?PROC_NAME(?MODULE, Id)}, ?MODULE, [Pool, Id], []). -%%------------------------------------------------------------------------------ %% @doc Start a session -%% @end -%%------------------------------------------------------------------------------ -spec start_session(CleanSess :: boolean(), binary()) -> {ok, pid(), boolean()} | {error, any()}. start_session(CleanSess, ClientId) -> SM = gproc_pool:pick_worker(?POOL, ClientId), call(SM, {start_session, {CleanSess, ClientId, self()}}). -%%------------------------------------------------------------------------------ %% @doc Lookup a Session -%% @end -%%------------------------------------------------------------------------------ -spec lookup_session(binary()) -> mqtt_session() | undefined. lookup_session(ClientId) -> case mnesia:dirty_read(session, ClientId) of @@ -107,10 +90,7 @@ lookup_session(ClientId) -> [] -> undefined end. -%%------------------------------------------------------------------------------ %% @doc Register a session with info. -%% @end -%%------------------------------------------------------------------------------ -spec register_session(CleanSess, ClientId, Info) -> ok when CleanSess :: boolean(), ClientId :: binary(), @@ -118,10 +98,7 @@ lookup_session(ClientId) -> register_session(CleanSess, ClientId, Info) -> ets:insert(sesstab(CleanSess), {{ClientId, self()}, Info}). -%%------------------------------------------------------------------------------ %% @doc Unregister a session. -%% @end -%%------------------------------------------------------------------------------ -spec unregister_session(CleanSess, ClientId) -> ok when CleanSess :: boolean(), ClientId :: binary(). @@ -134,9 +111,9 @@ sesstab(false) -> mqtt_persistent_session. call(SM, Req) -> gen_server2:call(SM, Req, ?TIMEOUT). %%infinity). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([Pool, Id]) -> ?GPROC_POOL(join, Pool, Id), @@ -213,9 +190,9 @@ terminate(_Reason, #state{pool = Pool, id = Id}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- %% Create Session Locally create_session({CleanSess, ClientId, ClientPid}, State) -> diff --git a/src/emqttd_sm_helper.erl b/src/emqttd_sm_helper.erl index c4baa04ad..bce724eb2 100644 --- a/src/emqttd_sm_helper.erl +++ b/src/emqttd_sm_helper.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Session Helper. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Session Helper. -module(emqttd_sm_helper). -behaviour(gen_server). @@ -42,10 +34,7 @@ -record(state, {stats_fun, tick_tref}). -%%------------------------------------------------------------------------------ %% @doc Start a session helper -%% @end -%%------------------------------------------------------------------------------ -spec start_link(fun()) -> {ok, pid()} | ignore | {error, any()}. start_link(StatsFun) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [StatsFun], []). @@ -89,9 +78,9 @@ terminate(_Reason, _State = #state{tick_tref = TRef}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- setstats(State = #state{stats_fun = StatsFun}) -> StatsFun(ets:info(mqtt_persistent_session, size)), State. diff --git a/src/emqttd_sm_sup.erl b/src/emqttd_sm_sup.erl index 83e426b0d..556d9540f 100644 --- a/src/emqttd_sm_sup.erl +++ b/src/emqttd_sm_sup.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Session Manager Supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 Session Manager Supervisor. -module(emqttd_sm_sup). -behaviour(supervisor). @@ -33,8 +25,7 @@ -define(HELPER, emqttd_sm_helper). --define(TABS, [mqtt_transient_session, - mqtt_persistent_session]). +-define(TABS, [mqtt_transient_session, mqtt_persistent_session]). %% API -export([start_link/0]). diff --git a/src/emqttd_stats.erl b/src/emqttd_stats.erl index 7a3f0be6f..c6a156d81 100644 --- a/src/emqttd_stats.erl +++ b/src/emqttd_stats.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd statistics -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd statistics -module(emqttd_stats). -include("emqttd.hrl"). @@ -76,14 +68,11 @@ 'retained/max' ]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ %% @doc Start stats server -%% @end -%%------------------------------------------------------------------------------ -spec start_link() -> {ok, pid()} | ignore | {error, term()}. start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). @@ -91,10 +80,7 @@ start_link() -> stop() -> gen_server:call(?SERVER, stop). -%%------------------------------------------------------------------------------ %% @doc Generate stats fun -%% @end -%%------------------------------------------------------------------------------ -spec statsfun(Stat :: atom()) -> fun(). statsfun(Stat) -> fun(Val) -> setstat(Stat, Val) end. @@ -103,18 +89,12 @@ statsfun(Stat) -> statsfun(Stat, MaxStat) -> fun(Val) -> setstats(Stat, MaxStat, Val) end. -%%------------------------------------------------------------------------------ %% @doc Get broker statistics -%% @end -%%------------------------------------------------------------------------------ -spec getstats() -> [{atom(), non_neg_integer()}]. getstats() -> lists:sort(ets:tab2list(?STATS_TAB)). -%%------------------------------------------------------------------------------ %% @doc Get stats by name -%% @end -%%------------------------------------------------------------------------------ -spec getstat(atom()) -> non_neg_integer() | undefined. getstat(Name) -> case ets:lookup(?STATS_TAB, Name) of @@ -122,28 +102,22 @@ getstat(Name) -> [] -> undefined end. -%%------------------------------------------------------------------------------ %% @doc Set broker stats -%% @end -%%------------------------------------------------------------------------------ -spec setstat(Stat :: atom(), Val :: pos_integer()) -> boolean(). setstat(Stat, Val) -> ets:update_element(?STATS_TAB, Stat, {2, Val}). -%%------------------------------------------------------------------------------ %% @doc Set stats with max -%% @end -%%------------------------------------------------------------------------------ -spec setstats(Stat :: atom(), MaxStat :: atom(), Val :: pos_integer()) -> boolean(). setstats(Stat, MaxStat, Val) -> gen_server:cast(?MODULE, {setstats, Stat, MaxStat, Val}). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([]) -> - emqttd:seed_now(), + emqttd_time:seed(), ets:new(?STATS_TAB, [set, public, named_table, {write_concurrency, true}]), Topics = ?SYSTOP_CLIENTS ++ ?SYSTOP_SESSIONS ++ ?SYSTOP_PUBSUB ++ ?SYSTOP_RETAINED, ets:insert(?STATS_TAB, [{Topic, 0} || Topic <- Topics]), @@ -186,14 +160,16 @@ terminate(_Reason, #state{tick_tref = TRef}) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- + publish(Stat, Val) -> - Msg = emqttd_message:make(stats, stats_topic(Stat), - emqttd_util:integer_to_binary(Val)), + Msg = emqttd_message:make(stats, stats_topic(Stat), bin(Val)), emqttd_pubsub:publish(Msg). stats_topic(Stat) -> emqttd_topic:systop(list_to_binary(lists:concat(['stats/', Stat]))). +bin(I) when is_integer(I) -> list_to_binary(integer_to_list(I)). + diff --git a/src/emqttd_sup.erl b/src/emqttd_sup.erl index b157454e1..2f21b51ed 100644 --- a/src/emqttd_sup.erl +++ b/src/emqttd_sup.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd top supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 emqttd top supervisor. -module(emqttd_sup). -behaviour(supervisor). @@ -39,9 +31,9 @@ -define(CHILD(Mod, Type), {Mod, {Mod, start_link, []}, permanent, 5000, Type, [Mod]}). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). @@ -49,16 +41,13 @@ start_link() -> start_child(ChildSpec) when is_tuple(ChildSpec) -> supervisor:start_child(?MODULE, ChildSpec). -%% -%% start_child(Mod::atom(), Type::type()) -> {ok, pid()} -%% @type type() = worker | supervisor -%% +-spec start_child(Mod::atom(), Type :: worker | supervisor) -> {ok, pid()}. start_child(Mod, Type) when is_atom(Mod) and is_atom(Type) -> supervisor:start_child(?MODULE, ?CHILD(Mod, Type)). -%%%============================================================================= -%%% Supervisor callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Supervisor callbacks +%%-------------------------------------------------------------------- init([]) -> {ok, {{one_for_all, 10, 3600}, []}}. diff --git a/src/emqttd_sysmon.erl b/src/emqttd_sysmon.erl index 4c6fcd0a1..5fa68bcec 100644 --- a/src/emqttd_sysmon.erl +++ b/src/emqttd_sysmon.erl @@ -1,28 +1,20 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc VM System Monitor -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 VM System Monitor -module(emqttd_sysmon). -behavior(gen_server). @@ -44,18 +36,15 @@ -define(LOG(Msg, ProcInfo, PortInfo), lager:warning([{sysmon, true}], "~s~n~p~n~p", [WarnMsg, ProcInfo, PortInfo])). -%%------------------------------------------------------------------------------ %% @doc Start system monitor -%% @end -%%------------------------------------------------------------------------------ -spec start_link(Opts :: list(tuple())) -> {ok, pid()} | ignore | {error, term()}. start_link(Opts) -> gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []). -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([Opts]) -> erlang:system_monitor(self(), parse_opt(Opts)), diff --git a/src/emqttd_sysmon_sup.erl b/src/emqttd_sysmon_sup.erl index 6fcd3347e..02d60530a 100644 --- a/src/emqttd_sysmon_sup.erl +++ b/src/emqttd_sysmon_sup.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd sysmon supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_sysmon_sup). -behaviour(supervisor). @@ -37,8 +28,7 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - Env = emqttd:env(sysmon), - {ok, {{one_for_one, 10, 100}, - [{sysmon, {emqttd_sysmon, start_link, [Env]}, - permanent, 5000, worker, [emqttd_sysmon]}]}}. + Sysmon = {sysmon, {emqttd_sysmon, start_link, [emqttd:env(sysmon)]}, + permanent, 5000, worker, [emqttd_sysmon]} , + {ok, {{one_for_one, 10, 100}, [Sysmon]}}. diff --git a/src/emqttd_time.erl b/src/emqttd_time.erl new file mode 100644 index 000000000..0ade04f6f --- /dev/null +++ b/src/emqttd_time.erl @@ -0,0 +1,36 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_time). + +-export([seed/0, now_to_secs/0, now_to_secs/1, now_to_ms/0, now_to_ms/1]). + +seed() -> + case erlang:function_exported(erlang, timestamp, 0) of + true -> random:seed(erlang:timestamp()); %% R18 + false -> random:seed(os:timestamp()) %% Compress now() deprecated warning... + end. + +now_to_secs() -> now_to_secs(os:timestamp()). + +now_to_secs({MegaSecs, Secs, _MicroSecs}) -> + MegaSecs * 1000000 + Secs. + +now_to_ms() -> now_to_ms(os:timestamp()). + +now_to_ms({MegaSecs, Secs, MicroSecs}) -> + (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). + diff --git a/src/emqttd_topic.erl b/src/emqttd_topic.erl index 3dcae94b0..f3c1f1a33 100644 --- a/src/emqttd_topic.erl +++ b/src/emqttd_topic.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc MQTT Topic Functions -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_topic). -import(lists, [reverse/1]). @@ -33,8 +24,6 @@ -type topic() :: binary(). -%-type type() :: static | dynamic. - -type word() :: '' | '+' | '#' | binary(). -type words() :: list(word()). @@ -45,11 +34,8 @@ -define(MAX_TOPIC_LEN, 4096). -%%%----------------------------------------------------------------------------- %% @doc Is wildcard topic? -%% @end -%%%----------------------------------------------------------------------------- --spec wildcard(topic()) -> true | false. +-spec wildcard(topic() | words()) -> true | false. wildcard(Topic) when is_binary(Topic) -> wildcard(words(Topic)); wildcard([]) -> @@ -61,10 +47,7 @@ wildcard(['+'|_]) -> wildcard([_H|T]) -> wildcard(T). -%%------------------------------------------------------------------------------ %% @doc Match Topic name with filter -%% @end -%%------------------------------------------------------------------------------ -spec match(Name, Filter) -> boolean() when Name :: topic() | words(), Filter :: topic() | words(). @@ -89,10 +72,7 @@ match([_H1|_], []) -> match([], [_H|_T2]) -> false. -%%------------------------------------------------------------------------------ %% @doc Validate Topic -%% @end -%%------------------------------------------------------------------------------ -spec validate({name | filter, topic()}) -> boolean(). validate({_, <<>>}) -> false; @@ -127,10 +107,7 @@ validate3(<>) when C == $#; C == $+; C == 0 -> validate3(<<_/utf8, Rest/binary>>) -> validate3(Rest). -%%%----------------------------------------------------------------------------- %% @doc Topic to Triples -%% @end -%%%----------------------------------------------------------------------------- -spec triples(topic()) -> list(triple()). triples(Topic) when is_binary(Topic) -> triples(words(Topic), root, []). @@ -152,10 +129,7 @@ bin('+') -> <<"+">>; bin('#') -> <<"#">>; bin(B) when is_binary(B) -> B. -%%------------------------------------------------------------------------------ %% @doc Split Topic Path to Words -%% @end -%%------------------------------------------------------------------------------ -spec words(topic()) -> words(). words(Topic) when is_binary(Topic) -> [word(W) || W <- binary:split(Topic, <<"/">>, [global])]. @@ -165,21 +139,16 @@ word(<<"+">>) -> '+'; word(<<"#">>) -> '#'; word(Bin) -> Bin. -%%------------------------------------------------------------------------------ %% @doc Queue is a special topic name that starts with "$Q/" -%% @end -%%------------------------------------------------------------------------------ -spec is_queue(topic()) -> boolean(). is_queue(<<"$Q/", _Queue/binary>>) -> true; +is_queue(<<"$q/", _Queue/binary>>) -> + true; is_queue(_) -> false. -%%------------------------------------------------------------------------------ %% @doc '$SYS' Topic. -%% @end -%%------------------------------------------------------------------------------ - systop(Name) when is_atom(Name) -> list_to_binary(lists:concat(["$SYS/brokers/", node(), "/", Name])); diff --git a/src/emqttd_trace.erl b/src/emqttd_trace.erl index d3b8eec01..e6681e86a 100644 --- a/src/emqttd_trace.erl +++ b/src/emqttd_trace.erl @@ -1,28 +1,22 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc Trace MQTT packets/messages by ClientID or Topic. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 +%% Trace MQTT packets/messages by ClientID or Topic. +%% @end -module(emqttd_trace). -behaviour(gen_server). @@ -44,17 +38,15 @@ -define(TRACE_OPTIONS, [{formatter_config, [time, " [",severity,"] ", message, "\n"]}]). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% API +%%-------------------------------------------------------------------- + -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). -%%------------------------------------------------------------------------------ %% @doc Start to trace client or topic. -%% @end -%%------------------------------------------------------------------------------ -spec start_trace(trace_who(), string()) -> ok | {error, any()}. start_trace({client, ClientId}, LogFile) -> start_trace({start_trace, {client, ClientId}, LogFile}); @@ -62,26 +54,22 @@ start_trace({client, ClientId}, LogFile) -> start_trace({topic, Topic}, LogFile) -> start_trace({start_trace, {topic, Topic}, LogFile}). -start_trace(Req) -> - gen_server:call(?MODULE, Req, infinity). +start_trace(Req) -> gen_server:call(?MODULE, Req, infinity). -%%------------------------------------------------------------------------------ %% @doc Stop tracing client or topic. -%% @end -%%------------------------------------------------------------------------------ -spec stop_trace(trace_who()) -> ok | {error, any()}. stop_trace({client, ClientId}) -> gen_server:call(?MODULE, {stop_trace, {client, ClientId}}); stop_trace({topic, Topic}) -> gen_server:call(?MODULE, {stop_trace, {topic, Topic}}). -%%------------------------------------------------------------------------------ %% @doc Lookup all traces. -%% @end -%%------------------------------------------------------------------------------ -spec all_traces() -> [{Who :: trace_who(), LogFile :: string()}]. -all_traces() -> - gen_server:call(?MODULE, all_traces). +all_traces() -> gen_server:call(?MODULE, all_traces). + +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([]) -> {ok, #state{level = info, traces = #{}}}. diff --git a/src/emqttd_trace_sup.erl b/src/emqttd_trace_sup.erl index 6dc248201..f0402a257 100644 --- a/src/emqttd_trace_sup.erl +++ b/src/emqttd_trace_sup.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd trace supervisor. -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_trace_sup). -behaviour(supervisor). @@ -37,7 +28,7 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - {ok, {{one_for_one, 10, 100}, - [{trace, {emqttd_trace, start_link, []}, - permanent, 5000, worker, [emqttd_trace]}]}}. + Trace = {trace, {emqttd_trace, start_link, []}, + permanent, 5000, worker, [emqttd_trace]}, + {ok, {{one_for_one, 10, 100}, [Trace]}}. diff --git a/src/emqttd_trie.erl b/src/emqttd_trie.erl index 13db64b24..820a966a4 100644 --- a/src/emqttd_trie.erl +++ b/src/emqttd_trie.erl @@ -1,35 +1,26 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% MQTT Topic Trie. -%%% -%%% [Trie](http://en.wikipedia.org/wiki/Trie) -%%% -%%% @end -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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 MQTT Topic Trie: +%% [Trie](http://en.wikipedia.org/wiki/Trie) +%% @end -module(emqttd_trie). +-include("emqttd_trie.hrl"). + %% Mnesia Callbacks -export([mnesia/1]). @@ -37,34 +28,13 @@ -copy_mnesia({mnesia, [copy]}). %% Trie API --export([insert/1, match/1, delete/1]). +-export([insert/1, match/1, delete/1, lookup/1]). --type node_id() :: binary() | atom(). +%%-------------------------------------------------------------------- +%% Mnesia Callbacks +%%-------------------------------------------------------------------- --record(trie_node, { - node_id :: node_id(), - edge_count = 0 :: non_neg_integer(), - topic :: binary() | undefined -}). - --record(trie_edge, { - node_id :: node_id(), - word :: binary() | atom() -}). - --record(trie, { - edge :: #trie_edge{}, - node_id :: node_id() -}). - -%%%============================================================================= -%%% Mnesia Callbacks -%%%============================================================================= - -%%------------------------------------------------------------------------------ -%% @doc Create Trie Tables -%% @end -%%------------------------------------------------------------------------------ +%% @doc Create or Replicate trie tables. -spec mnesia(boot | copy) -> ok. mnesia(boot) -> %% Trie Table @@ -78,24 +48,17 @@ mnesia(boot) -> {record_name, trie_node}, {attributes, record_info(fields, trie_node)}]); -%%------------------------------------------------------------------------------ -%% @doc Replicate trie tables -%% @end -%%------------------------------------------------------------------------------ mnesia(copy) -> %% Copy Trie Table ok = emqttd_mnesia:copy_table(trie), %% Copy Trie Node Table ok = emqttd_mnesia:copy_table(trie_node). -%%%============================================================================= -%%% API -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Trie API +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ -%% @doc Insert topic to trie tree -%% @end -%%------------------------------------------------------------------------------ +%% @doc Insert topic to trie -spec insert(Topic :: binary()) -> ok. insert(Topic) when is_binary(Topic) -> case mnesia:read(trie_node, Topic) of @@ -105,24 +68,23 @@ insert(Topic) when is_binary(Topic) -> mnesia:write(TrieNode#trie_node{topic=Topic}); [] -> %add trie path - [add_path(Triple) || Triple <- emqttd_topic:triples(Topic)], + lists:foreach(fun add_path/1, emqttd_topic:triples(Topic)), %add last node mnesia:write(#trie_node{node_id=Topic, topic=Topic}) end. -%%------------------------------------------------------------------------------ %% @doc Find trie nodes that match topic -%% @end -%%------------------------------------------------------------------------------ -spec match(Topic :: binary()) -> list(MatchedTopic :: binary()). match(Topic) when is_binary(Topic) -> TrieNodes = match_node(root, emqttd_topic:words(Topic)), [Name || #trie_node{topic=Name} <- TrieNodes, Name =/= undefined]. -%%------------------------------------------------------------------------------ +%% @doc Lookup a Trie Node +-spec lookup(NodeId :: binary()) -> [#trie_node{}]. +lookup(NodeId) -> + mnesia:read(trie_node, NodeId). + %% @doc Delete topic from trie -%% @end -%%------------------------------------------------------------------------------ -spec delete(Topic :: binary()) -> ok. delete(Topic) when is_binary(Topic) -> case mnesia:read(trie_node, Topic) of @@ -130,22 +92,17 @@ delete(Topic) when is_binary(Topic) -> mnesia:delete({trie_node, Topic}), delete_path(lists:reverse(emqttd_topic:triples(Topic))); [TrieNode] -> - mnesia:write(TrieNode#trie_node{topic=Topic}); + mnesia:write(TrieNode#trie_node{topic = undefined}); [] -> ok end. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal Functions +%%-------------------------------------------------------------------- -%%------------------------------------------------------------------------------ -%% @doc %% @private -%% Add path to trie tree. -%% -%% @end -%%------------------------------------------------------------------------------ +%% @doc Add path to trie tree. add_path({Node, Word, Child}) -> Edge = #trie_edge{node_id=Node, word=Word}, case mnesia:read(trie_node, Node) of @@ -162,14 +119,8 @@ add_path({Node, Word, Child}) -> mnesia:write(#trie{edge=Edge, node_id=Child}) end. -%%------------------------------------------------------------------------------ -%% @doc %% @private -%% Match node with word or '+'. -%% -%% @end -%%------------------------------------------------------------------------------ - +%% @doc Match node with word or '+'. match_node(root, [<<"$SYS">>|Words]) -> match_node(<<"$SYS">>, Words, []); @@ -187,28 +138,18 @@ match_node(NodeId, [W|Words], ResAcc) -> end end, 'match_#'(NodeId, ResAcc), [W, '+']). -%%------------------------------------------------------------------------------ -%% @doc %% @private -%% Match node with '#'. -%% -%% @end -%%------------------------------------------------------------------------------ +%% @doc Match node with '#'. 'match_#'(NodeId, ResAcc) -> case mnesia:read(trie, #trie_edge{node_id=NodeId, word = '#'}) of [#trie{node_id=ChildId}] -> - mnesia:read(trie_node, ChildId) ++ ResAcc; + mnesia:read(trie_node, ChildId) ++ ResAcc; [] -> ResAcc end. -%%------------------------------------------------------------------------------ -%% @doc %% @private -%% Delete paths from trie tree. -%% -%% @end -%%------------------------------------------------------------------------------ +%% @doc Delete paths from trie tree. delete_path([]) -> ok; delete_path([{NodeId, Word, _} | RestPath]) -> diff --git a/src/emqttd_util.erl b/src/emqttd_util.erl deleted file mode 100644 index 29b87b661..000000000 --- a/src/emqttd_util.erl +++ /dev/null @@ -1,100 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd utility functions -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- --module(emqttd_util). - --export([apply_module_attributes/1, all_module_attributes/1, cancel_timer/1, - now_to_secs/0, now_to_secs/1, now_to_ms/0, now_to_ms/1]). - --export([integer_to_binary/1]). - -%% only {F, Args}... -apply_module_attributes(Name) -> - [{Module, [apply(Module, F, Args) || {F, Args} <- Attrs]} || - {_App, Module, Attrs} <- all_module_attributes(Name)]. - -%% copy from rabbit_misc.erl -all_module_attributes(Name) -> - Targets = - lists:usort( - lists:append( - [[{App, Module} || Module <- Modules] || - {App, _, _} <- ignore_lib_apps(application:loaded_applications()), - {ok, Modules} <- [application:get_key(App, modules)]])), - lists:foldl( - fun ({App, Module}, Acc) -> - case lists:append([Atts || {N, Atts} <- module_attributes(Module), - N =:= Name]) of - [] -> Acc; - Atts -> [{App, Module, Atts} | Acc] - end - end, [], Targets). - -%% copy from rabbit_misc.erl -module_attributes(Module) -> - case catch Module:module_info(attributes) of - {'EXIT', {undef, [{Module, module_info, _} | _]}} -> - []; - {'EXIT', Reason} -> - exit(Reason); - V -> - V - end. - -ignore_lib_apps(Apps) -> - LibApps = [kernel, stdlib, sasl, - syntax_tools, ssl, crypto, - mnesia, os_mon, inets, - goldrush, lager, gproc, - runtime_tools, snmp, otp_mibs, - public_key, asn1, ssh, - common_test, observer, webtool, - xmerl, tools, test_server, - compiler, debugger, eunit, - et, gen_logger, wx, - hipe, esockd, mochiweb], - [App || App = {Name, _, _} <- Apps, not lists:member(Name, LibApps)]. - - -cancel_timer(undefined) -> - undefined; -cancel_timer(Ref) -> - catch erlang:cancel_timer(Ref). - -integer_to_binary(I) when is_integer(I) -> - list_to_binary(integer_to_list(I)). - -now_to_secs() -> - now_to_secs(os:timestamp()). - -now_to_secs({MegaSecs, Secs, _MicroSecs}) -> - MegaSecs * 1000000 + Secs. - -now_to_ms() -> - now_to_ms(os:timestamp()). - -now_to_ms({MegaSecs, Secs, MicroSecs}) -> - (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). - diff --git a/src/emqttd_vm.erl b/src/emqttd_vm.erl index 182fcd804..bad4d7c96 100644 --- a/src/emqttd_vm.erl +++ b/src/emqttd_vm.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd erlang vm. -%%% -%%% @author huangdan -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_vm). -export([schedulers/0]). diff --git a/src/emqttd_ws_client.erl b/src/emqttd_ws_client.erl index 437fc2898..776dc4ce5 100644 --- a/src/emqttd_ws_client.erl +++ b/src/emqttd_ws_client.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqttd websocket client -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_ws_client). -include("emqttd.hrl"). @@ -50,10 +41,7 @@ -define(WSLOG(Level, Format, Args, Req), lager:Level("WsClient(~s): " ++ Format, [Req:get(peer) | Args])). -%%------------------------------------------------------------------------------ %% @doc Start WebSocket client. -%% @end -%%------------------------------------------------------------------------------ start_link(Req) -> PktOpts = emqttd:env(mqtt, packet), ParserFun = emqttd_parser:new(PktOpts), @@ -80,18 +68,12 @@ subscribe(CPid, TopicTable) -> unsubscribe(CPid, Topics) -> gen_server:cast(CPid, {unsubscribe, Topics}). -%%------------------------------------------------------------------------------ %% @private -%% @doc Start WebSocket client. -%% @end -%%------------------------------------------------------------------------------ +%% @doc Upgrade WebSocket. upgrade(Req) -> mochiweb_websocket:upgrade_connection(Req, fun ?MODULE:ws_loop/3). -%%------------------------------------------------------------------------------ %% @doc WebSocket frame receive loop. -%% @end -%%------------------------------------------------------------------------------ ws_loop(<<>>, State, _ReplyChannel) -> State; ws_loop([<<>>], State, _ReplyChannel) -> @@ -118,9 +100,9 @@ ws_loop(Data, State = #wsocket_state{request = Req, reset_parser(State = #wsocket_state{packet_opts = PktOpts}) -> State#wsocket_state{parser_fun = emqttd_parser:new(PktOpts)}. -%%%============================================================================= -%%% gen_server callbacks -%%%============================================================================= +%%-------------------------------------------------------------------- +%% gen_server callbacks +%%-------------------------------------------------------------------- init([WsPid, Req, ReplyChannel, PktOpts]) -> %%issue#413: trap_exit is unnecessary @@ -241,9 +223,9 @@ terminate(Reason, #wsclient_state{proto_state = ProtoState, keepalive = KeepAliv code_change(_OldVsn, State, _Extra) -> {ok, State}. -%%%============================================================================= -%%% Internal functions -%%%============================================================================= +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- with_proto_state(Fun, State = #wsclient_state{proto_state = ProtoState}) -> {ok, ProtoState1} = Fun(ProtoState), diff --git a/src/lager_emqtt_backend.erl b/src/lager_emqtt_backend.erl index f9d3424f7..9c28090bf 100644 --- a/src/lager_emqtt_backend.erl +++ b/src/lager_emqtt_backend.erl @@ -1,28 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% Copyright (c) 2012-2016 eMQTT.IO, All Rights Reserved. -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc emqtt lager backend -%%% -%%% @author Feng Lee -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(lager_emqtt_backend). -behaviour(gen_event). diff --git a/test/emqttd_SUITE.erl b/test/emqttd_SUITE.erl new file mode 100644 index 000000000..eca4b5294 --- /dev/null +++ b/test/emqttd_SUITE.erl @@ -0,0 +1,214 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_SUITE). + +-compile(export_all). + +-include("emqttd.hrl"). + +all() -> + [{group, pubsub}, + {group, router}, + {group, session}, + {group, retainer}, + {group, broker}, + {group, metrics}, + {group, stats}]. + +groups() -> + [{pubsub, [sequence], + [create_topic, + create_subscription, + subscribe_unsubscribe, + publish_message]}, + {router, [sequence], + [add_delete_routes, + add_delete_route, + route_message]}, + {session, [sequence], + [start_session]}, + {retainer, [sequence], + [retain_message]}, + {broker, [sequence], + [hook_unhook]}, + {metrics, [sequence], + [inc_dec_metric]}, + {stats, [sequence], + [set_get_stat]}]. + +init_per_suite(Config) -> + application:start(lager), + application:ensure_all_started(emqttd), + Config. + +end_per_suite(_Config) -> + application:stop(emqttd), + application:stop(esockd), + application:stop(gproc), + emqttd_mnesia:ensure_stopped(). + +%%-------------------------------------------------------------------- +%% PubSub Group +%%-------------------------------------------------------------------- + +create_topic(_) -> + Node = node(), + ok = emqttd_pubsub:create(topic, <<"topic/create">>), + ok = emqttd_pubsub:create(topic, <<"topic/create2">>), + [#mqtt_topic{topic = <<"topic/create">>, node = Node}] + = emqttd_pubsub:lookup(topic, <<"topic/create">>). + +create_subscription(_) -> + ok = emqttd_pubsub:create(subscription, {<<"clientId">>, <<"topic/sub">>, qos2}), + [#mqtt_subscription{subid = <<"clientId">>, topic = <<"topic/sub">>, qos = 2}] + = emqttd_pubsub:lookup(subscription, <<"clientId">>), + ok = emqttd_pubsub:delete(subscription, <<"clientId">>), + [] = emqttd_pubsub:lookup(subscription, <<"clientId">>). + +subscribe_unsubscribe(_) -> + {ok, [1]} = emqttd_pubsub:subscribe({<<"topic/subunsub">>, 1}), + {ok, [1, 2]} = emqttd_pubsub:subscribe([{<<"topic/subunsub1">>, 1}, {<<"topic/subunsub2">>, 2}]), + ok = emqttd_pubsub:unsubscribe(<<"topic/subunsub">>), + ok = emqttd_pubsub:unsubscribe([<<"topic/subunsub1">>, <<"topic/subunsub2">>]), + + {ok, [1]} = emqttd_pubsub:subscribe(<<"client_subunsub">>, {<<"topic/subunsub">>, 1}), + {ok, [1,2]} = emqttd_pubsub:subscribe(<<"client_subunsub">>, [{<<"topic/subunsub1">>, 1}, + {<<"topic/subunsub2">>, 2}]), + ok = emqttd_pubsub:unsubscribe(<<"client_subunsub">>, <<"topic/subunsub">>), + ok = emqttd_pubsub:unsubscribe(<<"client_subunsub">>, [<<"topic/subunsub1">>, + <<"topic/subunsub2">>]). + +publish_message(_) -> + Msg = emqttd_message:make(ct, <<"test/pubsub">>, <<"hello">>), + {ok, [1]} = emqttd_pubsub:subscribe({<<"test/+">>, qos1}), + emqttd_pubsub:publish(Msg), + true = receive {dispatch, <<"test/+">>, Msg} -> true after 5 -> false end. + +%%-------------------------------------------------------------------- +%% Route Group +%%-------------------------------------------------------------------- + +add_delete_route(_) -> + Self = self(), + emqttd_router:add_route(<<"topic1">>, Self), + true = emqttd_router:has_route(<<"topic1">>), + emqttd_router:add_route(<<"topic2">>, Self), + true = emqttd_router:has_route(<<"topic2">>), + [Self] = emqttd_router:lookup_routes(<<"topic1">>), + [Self] = emqttd_router:lookup_routes(<<"topic2">>), + %% Del topic1 + emqttd_router:delete_route(<<"topic1">>, Self), + erlang:yield(), + timer:sleep(10), + false = emqttd_router:has_route(<<"topic1">>), + %% Del topic2 + emqttd_router:delete_route(<<"topic2">>, Self), + erlang:yield(), + timer:sleep(10), + false = emqttd_router:has_route(<<"topic2">>). + +add_delete_routes(_) -> + Self = self(), + emqttd_router:add_routes([], Self), + emqttd_router:add_routes([<<"t0">>], Self), + emqttd_router:add_routes([<<"t1">>,<<"t2">>,<<"t3">>], Self), + true = emqttd_router:has_route(<<"t1">>), + [Self] = emqttd_router:lookup_routes(<<"t1">>), + [Self] = emqttd_router:lookup_routes(<<"t2">>), + [Self] = emqttd_router:lookup_routes(<<"t3">>), + + emqttd_router:delete_routes([<<"t3">>], Self), + emqttd_router:delete_routes([<<"t0">>, <<"t1">>], Self), + erlang:yield(), + timer:sleep(10), + false = emqttd_router:has_route(<<"t0">>), + false = emqttd_router:has_route(<<"t1">>), + true = emqttd_router:has_route(<<"t2">>), + false = emqttd_router:has_route(<<"t3">>). + +route_message(_) -> + Self = self(), + Pid = spawn_link(fun() -> timer:sleep(1000) end), + emqttd_router:add_routes([<<"$Q/1">>,<<"t/2">>,<<"t/3">>], Self), + emqttd_router:add_routes([<<"t/2">>], Pid), + Msg1 = #mqtt_message{topic = <<"$Q/1">>, payload = <<"q">>}, + Msg2 = #mqtt_message{topic = <<"t/2">>, payload = <<"t2">>}, + Msg3 = #mqtt_message{topic = <<"t/3">>, payload = <<"t3">>}, + emqttd_router:route(<<"$Q/1">>, Msg1), + emqttd_router:route(<<"t/2">>, Msg2), + emqttd_router:route(<<"t/3">>, Msg3), + [Msg1, Msg2, Msg3] = recv_loop([]), + emqttd_router:add_route(<<"$Q/1">>, Self), + emqttd_router:route(<<"$Q/1">>, Msg1). + +recv_loop(Msgs) -> + receive + {dispatch, _Topic, Msg} -> + recv_loop([Msg|Msgs]) + after + 100 -> lists:reverse(Msgs) + end. + +%%-------------------------------------------------------------------- +%% Session Group +%%-------------------------------------------------------------------- + +start_session(_) -> + {ok, ClientPid} = emqttd_mock_client:start_link(<<"clientId">>), + {ok, SessPid} = emqttd_mock_client:start_session(ClientPid), + Message = emqttd_message:make(<<"clientId">>, 2, <<"topic">>, <<"hello">>), + Message1 = Message#mqtt_message{pktid = 1}, + emqttd_session:publish(SessPid, Message1), + emqttd_session:pubrel(SessPid, 1), + emqttd_session:subscribe(SessPid, [{<<"topic/session">>, 2}]), + Message2 = emqttd_message:make(<<"clientId">>, 1, <<"topic/session">>, <<"test">>), + emqttd_session:publish(SessPid, Message2), + emqttd_session:unsubscribe(SessPid, [<<"topic/session">>]), + emqttd_mock_client:stop(ClientPid). + +%%-------------------------------------------------------------------- +%% Retainer Group +%%-------------------------------------------------------------------- + +retain_message(_) -> + Msg = #mqtt_message{retain = true, topic = <<"a/b/c">>, + payload = <<"payload">>}, + emqttd_retainer:retain(Msg), + emqttd_retainer:dispatch(<<"a/b/+">>, self()), + true = receive {dispatch, <<"a/b/+">>, Msg} -> true after 10 -> false end, + emqttd_retainer:retain(#mqtt_message{retain = true, topic = <<"a/b/c">>, payload = <<>>}), + [] = mnesia:dirty_read({retained, <<"a/b/c">>}). + +%%-------------------------------------------------------------------- +%% Broker Group +%%-------------------------------------------------------------------- +hook_unhook(_) -> + ok. + +%%-------------------------------------------------------------------- +%% Metric Group +%%-------------------------------------------------------------------- +inc_dec_metric(_) -> + emqttd_metrics:inc(gauge, 'messages/retained', 10), + emqttd_metrics:dec(gauge, 'messages/retained', 10). + +%%-------------------------------------------------------------------- +%% Stats Group +%%-------------------------------------------------------------------- +set_get_stat(_) -> + emqttd_stats:setstat('retained/max', 99), + 99 = emqttd_stats:getstat('retained/max'). diff --git a/test/emqttd_access_SUITE.erl b/test/emqttd_access_SUITE.erl new file mode 100644 index 000000000..3a58d031c --- /dev/null +++ b/test/emqttd_access_SUITE.erl @@ -0,0 +1,166 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_access_SUITE). + +-compile(export_all). + +-include("emqttd.hrl"). + +-define(AC, emqttd_access_control). + +-import(emqttd_access_rule, [compile/1, match/3]). + +all() -> + [{group, access_control}, + {group, access_rule}]. + +groups() -> + [{access_control, [sequence], + [reload_acl, + register_mod, + unregister_mod, + check_acl]}, + {access_rule, [], + [compile_rule, + match_rule]}]. + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, Config) -> + Config. + +init_per_testcase(TestCase, Config) when TestCase =:= reload_acl; + TestCase =:= register_mod; + TestCase =:= unregister_mod; + TestCase =:= check_acl -> + DataDir = proplists:get_value(data_dir, Config), + AclOpts = [ + {auth, [{anonymous, []}]}, + {acl, [{internal, [{file, filename:join([DataDir, "test_acl.config"])}, + {nomatch, allow}]}]} + ], + {ok, _Pid} = ?AC:start_link(AclOpts), + Config; + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(TestCase, _Config) when TestCase =:= reload_acl; + TestCase =:= register_mod; + TestCase =:= unregister_mod; + TestCase =:= check_acl -> + ?AC:stop(); + +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% emqttd_access_control +%%-------------------------------------------------------------------- + +reload_acl(_) -> + ct:print("~p~n", [whereis(?AC)]), + [ok] = ?AC:reload_acl(). + +register_mod(_) -> + ok = ?AC:register_mod(acl, emqttd_acl_test_mod, []), + [{emqttd_acl_test_mod, _, 0}, + {emqttd_acl_internal, _, 0}] = ?AC:lookup_mods(acl), + ok = ?AC:register_mod(auth, emqttd_auth_anonymous_test_mod,[]), + ok = ?AC:register_mod(auth, emqttd_auth_dashboard, [], 99), + [{emqttd_auth_dashboard, _, 99}, + {emqttd_auth_anonymous_test_mod, _, 0}, + {emqttd_auth_anonymous, _, 0}] = ?AC:lookup_mods(auth). + +unregister_mod(_) -> + ok = ?AC:register_mod(acl, emqttd_acl_test_mod, []), + [{emqttd_acl_test_mod, _, 0}, + {emqttd_acl_internal, _, 0}] = ?AC:lookup_mods(acl), + ok = ?AC:unregister_mod(acl, emqttd_acl_test_mod), + timer:sleep(5), + [{emqttd_acl_internal, _, 0}] = ?AC:lookup_mods(acl), + ok = ?AC:register_mod(auth, emqttd_auth_anonymous_test_mod,[]), + [{emqttd_auth_anonymous_test_mod, _, 0}, + {emqttd_auth_anonymous, _, 0}] = ?AC:lookup_mods(auth), + + ok = ?AC:unregister_mod(auth, emqttd_auth_anonymous_test_mod), + timer:sleep(5), + [{emqttd_auth_anonymous, _, 0}] = ?AC:lookup_mods(auth). + +check_acl(_) -> + User1 = #mqtt_client{client_id = <<"client1">>, username = <<"testuser">>}, + User2 = #mqtt_client{client_id = <<"client2">>, username = <<"xyz">>}, + allow = ?AC:check_acl(User1, subscribe, <<"users/testuser/1">>), + allow = ?AC:check_acl(User1, subscribe, <<"clients/client1">>), + deny = ?AC:check_acl(User1, subscribe, <<"clients/client1/x/y">>), + allow = ?AC:check_acl(User1, publish, <<"users/testuser/1">>), + allow = ?AC:check_acl(User1, subscribe, <<"a/b/c">>), + deny = ?AC:check_acl(User2, subscribe, <<"a/b/c">>). + +%%-------------------------------------------------------------------- +%% emqttd_access_rule +%%-------------------------------------------------------------------- + +compile_rule(_) -> + + {allow, {'and', [{ipaddr, {"127.0.0.1", _I, _I}}, + {user, <<"user">>}]}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]} = + compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"user">>}]}, subscribe, ["$SYS/#", "#"]}), + {allow, {'or', [{ipaddr, {"127.0.0.1", _I, _I}}, + {user, <<"user">>}]}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]} = + compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"user">>}]}, subscribe, ["$SYS/#", "#"]}), + + {allow, {ipaddr, {"127.0.0.1", _I, _I}}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]} = + compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}), + {allow, {user, <<"testuser">>}, subscribe, [ [<<"a">>, <<"b">>, <<"c">>], [<<"d">>, <<"e">>, <<"f">>, '#'] ]} = + compile({allow, {user, "testuser"}, subscribe, ["a/b/c", "d/e/f/#"]}), + {allow, {user, <<"admin">>}, pubsub, [ [<<"d">>, <<"e">>, <<"f">>, '#'] ]} = + compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]}), + {allow, {client, <<"testClient">>}, publish, [ [<<"testTopics">>, <<"testClient">>] ]} = + compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]}), + {allow, all, pubsub, [{pattern, [<<"clients">>, <<"$c">>]}]} = + compile({allow, all, pubsub, ["clients/$c"]}), + {allow, all, subscribe, [{pattern, [<<"users">>, <<"$u">>, '#']}]} = + compile({allow, all, subscribe, ["users/$u/#"]}), + {deny, all, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]} = + compile({deny, all, subscribe, ["$SYS/#", "#"]}), + {allow, all} = compile({allow, all}), + {deny, all} = compile({deny, all}). + +match_rule(_) -> + User = #mqtt_client{peername = {{127,0,0,1}, 2948}, client_id = <<"testClient">>, username = <<"TestUser">>}, + User2 = #mqtt_client{peername = {{192,168,0,10}, 3028}, client_id = <<"testClient">>, username = <<"TestUser">>}, + + {matched, allow} = match(User, <<"Test/Topic">>, {allow, all}), + {matched, deny} = match(User, <<"Test/Topic">>, {deny, all}), + {matched, allow} = match(User, <<"Test/Topic">>, compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})), + {matched, allow} = match(User2, <<"Test/Topic">>, compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]})), + {matched, allow} = match(User, <<"d/e/f/x">>, compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]})), + nomatch = match(User, <<"d/e/f/x">>, compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]})), + {matched, allow} = match(User, <<"testTopics/testClient">>, compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]})), + {matched, allow} = match(User, <<"clients/testClient">>, compile({allow, all, pubsub, ["clients/$c"]})), + {matched, allow} = match(#mqtt_client{username = <<"user2">>}, <<"users/user2/abc/def">>, + compile({allow, all, subscribe, ["users/$u/#"]})), + {matched, deny} = match(User, <<"d/e/f">>, compile({deny, all, subscribe, ["$SYS/#", "#"]})), + Rule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, <<"Topic">>}), + nomatch = match(User, <<"Topic">>, Rule), + AndRule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"TestUser">>}]}, publish, <<"Topic">>}), + {matched, allow} = match(User, <<"Topic">>, AndRule), + OrRule = compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, ["Topic"]}), + {matched, allow} = match(User, <<"Topic">>, OrRule). + diff --git a/test/test_acl.config b/test/emqttd_access_SUITE_data/test_acl.config similarity index 100% rename from test/test_acl.config rename to test/emqttd_access_SUITE_data/test_acl.config diff --git a/test/emqttd_access_control_tests.erl b/test/emqttd_access_control_tests.erl deleted file mode 100644 index e3030fa96..000000000 --- a/test/emqttd_access_control_tests.erl +++ /dev/null @@ -1,110 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd_access_control tests. -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_access_control_tests). - --include("emqttd.hrl"). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - -reload_acl_test() -> - with_acl( - fun() -> - ?assertEqual([ok], emqttd_access_control:reload_acl()) - end). - -register_mod_test() -> - with_acl( - fun() -> - emqttd_access_control:register_mod(acl, emqttd_acl_test_mod, []), - ?assertMatch([{emqttd_acl_test_mod, _, 0}, {emqttd_acl_internal, _, 0}], - emqttd_access_control:lookup_mods(acl)), - emqttd_access_control:register_mod(auth, emqttd_auth_anonymous_test_mod,[]), - emqttd_access_control:register_mod(auth, emqttd_auth_dashboard, [], 99), - ?assertMatch([{emqttd_auth_dashboard, _, 99}, - {emqttd_auth_anonymous_test_mod, _, 0}, - {emqttd_auth_anonymous, _, 0}], - emqttd_access_control:lookup_mods(auth)) - end). - -unregister_mod_test() -> - with_acl( - fun() -> - emqttd_access_control:register_mod(acl, emqttd_acl_test_mod, []), - ?assertMatch([{emqttd_acl_test_mod, _, 0}, {emqttd_acl_internal, _, 0}], - emqttd_access_control:lookup_mods(acl)), - emqttd_access_control:unregister_mod(acl, emqttd_acl_test_mod), - timer:sleep(5), - ?assertMatch([{emqttd_acl_internal, _, 0}], emqttd_access_control:lookup_mods(acl)), - - emqttd_access_control:register_mod(auth, emqttd_auth_anonymous_test_mod,[]), - ?assertMatch([{emqttd_auth_anonymous_test_mod, _, 0}, {emqttd_auth_anonymous, _, 0}], - emqttd_access_control:lookup_mods(auth)), - - emqttd_access_control:unregister_mod(auth, emqttd_auth_anonymous_test_mod), - timer:sleep(5), - ?assertMatch([{emqttd_auth_anonymous, _, 0}], emqttd_access_control:lookup_mods(auth)) - end). - -check_acl_test() -> - with_acl( - fun() -> - User1 = #mqtt_client{client_id = <<"client1">>, username = <<"testuser">>}, - User2 = #mqtt_client{client_id = <<"client2">>, username = <<"xyz">>}, - ?assertEqual(allow, emqttd_access_control:check_acl(User1, subscribe, <<"users/testuser/1">>)), - ?assertEqual(allow, emqttd_access_control:check_acl(User1, subscribe, <<"clients/client1">>)), - ?assertEqual(deny, emqttd_access_control:check_acl(User1, subscribe, <<"clients/client1/x/y">>)), - ?assertEqual(allow, emqttd_access_control:check_acl(User1, publish, <<"users/testuser/1">>)), - ?assertEqual(allow, emqttd_access_control:check_acl(User1, subscribe, <<"a/b/c">>)), - ?assertEqual(deny, emqttd_access_control:check_acl(User2, subscribe, <<"a/b/c">>)) - end). - -with_acl(Fun) -> - process_flag(trap_exit, true), - AclOpts = [ - {auth, [ - %% Authentication with username, password - %{username, []}, - %% Authentication with clientid - %{clientid, [{password, no}, {file, "etc/clients.config"}]}, - %% Allow all - {anonymous, []} - ]}, - %% ACL config - {acl, [ - %% Internal ACL module - {internal, [{file, "../test/test_acl.config"}, {nomatch, allow}]} - ]} - ], - %application:set_env(emqttd, access, AclOpts), - emqttd_access_control:start_link(AclOpts), - Fun(), - emqttd_access_control:stop(). - --endif. - diff --git a/test/emqttd_access_rule_tests.erl b/test/emqttd_access_rule_tests.erl deleted file mode 100644 index 7308f572a..000000000 --- a/test/emqttd_access_rule_tests.erl +++ /dev/null @@ -1,92 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd_access_rule tests. -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_access_rule_tests). - --import(emqttd_access_rule, [compile/1, match/3]). - --include("emqttd.hrl"). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - -compile_test() -> - - ?assertMatch({allow, {'and', [{ipaddr, {"127.0.0.1", _I, _I}}, - {user, <<"user">>}]}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, - compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"user">>}]}, subscribe, ["$SYS/#", "#"]})), - ?assertMatch({allow, {'or', [{ipaddr, {"127.0.0.1", _I, _I}}, - {user, <<"user">>}]}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, - compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"user">>}]}, subscribe, ["$SYS/#", "#"]})), - - ?assertMatch({allow, {ipaddr, {"127.0.0.1", _I, _I}}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, - compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})), - ?assertMatch({allow, {user, <<"testuser">>}, subscribe, [ [<<"a">>, <<"b">>, <<"c">>], [<<"d">>, <<"e">>, <<"f">>, '#'] ]}, - compile({allow, {user, "testuser"}, subscribe, ["a/b/c", "d/e/f/#"]})), - ?assertEqual({allow, {user, <<"admin">>}, pubsub, [ [<<"d">>, <<"e">>, <<"f">>, '#'] ]}, - compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]})), - ?assertEqual({allow, {client, <<"testClient">>}, publish, [ [<<"testTopics">>, <<"testClient">>] ]}, - compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]})), - ?assertEqual({allow, all, pubsub, [{pattern, [<<"clients">>, <<"$c">>]}]}, - compile({allow, all, pubsub, ["clients/$c"]})), - ?assertEqual({allow, all, subscribe, [{pattern, [<<"users">>, <<"$u">>, '#']}]}, - compile({allow, all, subscribe, ["users/$u/#"]})), - ?assertEqual({deny, all, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]}, - compile({deny, all, subscribe, ["$SYS/#", "#"]})), - ?assertEqual({allow, all}, compile({allow, all})), - ?assertEqual({deny, all}, compile({deny, all})). - -match_test() -> - User = #mqtt_client{peername = {{127,0,0,1}, 2948}, client_id = <<"testClient">>, username = <<"TestUser">>}, - User2 = #mqtt_client{peername = {{192,168,0,10}, 3028}, client_id = <<"testClient">>, username = <<"TestUser">>}, - - ?assertEqual({matched, allow}, match(User, <<"Test/Topic">>, {allow, all})), - ?assertEqual({matched, deny}, match(User, <<"Test/Topic">>, {deny, all})), - ?assertMatch({matched, allow}, match(User, <<"Test/Topic">>, - compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}))), - ?assertMatch({matched, allow}, match(User2, <<"Test/Topic">>, - compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]}))), - ?assertMatch({matched, allow}, match(User, <<"d/e/f/x">>, compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]}))), - ?assertEqual(nomatch, match(User, <<"d/e/f/x">>, compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]}))), - ?assertMatch({matched, allow}, match(User, <<"testTopics/testClient">>, - compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]}))), - ?assertMatch({matched, allow}, match(User, <<"clients/testClient">>, - compile({allow, all, pubsub, ["clients/$c"]}))), - ?assertMatch({matched, allow}, match(#mqtt_client{username = <<"user2">>}, <<"users/user2/abc/def">>, - compile({allow, all, subscribe, ["users/$u/#"]}))), - ?assertMatch({matched, deny}, match(User, <<"d/e/f">>, - compile({deny, all, subscribe, ["$SYS/#", "#"]}))), - Rule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, <<"Topic">>}), - ?assertMatch(nomatch, match(User, <<"Topic">>, Rule)), - AndRule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"TestUser">>}]}, publish, <<"Topic">>}), - ?assertMatch({matched, allow}, match(User, <<"Topic">>, AndRule)), - OrRule = compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, ["Topic"]}), - ?assertMatch({matched, allow}, match(User, <<"Topic">>, OrRule)). - --endif. - - diff --git a/test/emqttd_acl_test_mod.erl b/test/emqttd_acl_test_mod.erl index 3991e3dde..c9ddf057e 100644 --- a/test/emqttd_acl_test_mod.erl +++ b/test/emqttd_acl_test_mod.erl @@ -1,29 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% Test ACL Module. -%%% -%%% @end -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_acl_test_mod). %% ACL callbacks diff --git a/test/emqttd_auth_anonymous_test_mod.erl b/test/emqttd_auth_anonymous_test_mod.erl index 010a72dad..8424b467a 100644 --- a/test/emqttd_auth_anonymous_test_mod.erl +++ b/test/emqttd_auth_anonymous_test_mod.erl @@ -1,29 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% Test ACL Module. -%%% -%%% @end -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_auth_anonymous_test_mod). %% ACL callbacks diff --git a/test/emqttd_auth_dashboard.erl b/test/emqttd_auth_dashboard.erl index ea9aca7e1..d36390a09 100644 --- a/test/emqttd_auth_dashboard.erl +++ b/test/emqttd_auth_dashboard.erl @@ -1,3 +1,18 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_auth_dashboard). diff --git a/test/emqttd_backend_SUITE.erl b/test/emqttd_backend_SUITE.erl new file mode 100644 index 000000000..162510bc7 --- /dev/null +++ b/test/emqttd_backend_SUITE.erl @@ -0,0 +1,36 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_backend_SUITE). + +-compile(export_all). + +all() -> [{group, retainer}]. + +groups() -> [{retainer, [], [t_retain]}]. + +init_per_group(retainer, _Config) -> + ok = emqttd_mnesia:ensure_started(), + emqttd_retainer:mnesia(boot), + emqttd_retainer:mnesia(copy). + +end_per_group(retainer, _Config) -> + ok; +end_per_group(_Group, _Config) -> + ok. + +t_retain(_) -> ok. + diff --git a/test/emqttd_guid_tests.erl b/test/emqttd_guid_tests.erl deleted file mode 100644 index c53b5df79..000000000 --- a/test/emqttd_guid_tests.erl +++ /dev/null @@ -1,38 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- - --module(emqttd_guid_tests). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - -gen_test() -> - Guid1 = emqttd_guid:gen(), - Guid2 = emqttd_guid:gen(), - ?assertMatch(<<_:128>>, Guid1), - ?assertEqual(true, Guid2 >= Guid1). - --endif. - - - diff --git a/test/emqttd_keepalive_tests.erl b/test/emqttd_keepalive_tests.erl deleted file mode 100644 index e4b0d63df..000000000 --- a/test/emqttd_keepalive_tests.erl +++ /dev/null @@ -1,44 +0,0 @@ - -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- --module(emqttd_keepalive_tests). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - -keepalive_test() -> - KA = emqttd_keepalive:start(fun() -> {ok, 1} end, 1, {keepalive, timeout}), - ?assertEqual([resumed, timeout], lists:reverse(loop(KA, []))). - -loop(KA, Acc) -> - receive - {keepalive, timeout} -> - case emqttd_keepalive:check(KA) of - {ok, KA1} -> loop(KA1, [resumed | Acc]); - {error, timeout} -> [timeout | Acc] - end - after 4000 -> - Acc - end. - --endif. diff --git a/test/emqttd_lib_SUITE.erl b/test/emqttd_lib_SUITE.erl new file mode 100644 index 000000000..33a5e3547 --- /dev/null +++ b/test/emqttd_lib_SUITE.erl @@ -0,0 +1,146 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_lib_SUITE). + +-compile(export_all). + +-define(SOCKOPTS, [ + binary, + {packet, raw}, + {reuseaddr, true}, + {backlog, 512}, + {nodelay, true} +]). + +-define(PQ, priority_queue). + +all() -> [{group, guid}, {group, opts}, + {group, ?PQ}, {group, time}, + {group, node}]. + +groups() -> + [{guid, [], [guid_gen]}, + {opts, [], [opts_merge]}, + {?PQ, [], [priority_queue_plen, + priority_queue_out2]}, + {time, [], [time_now_to_]}, + {node, [], [node_is_aliving, node_parse_name]}]. + +%%-------------------------------------------------------------------- +%% emqttd_guid +%%-------------------------------------------------------------------- + +guid_gen(_) -> + Guid1 = emqttd_guid:gen(), + Guid2 = emqttd_guid:gen(), + <<_:128>> = Guid1, + true = (Guid2 >= Guid1), + {Ts1, _, 0} = emqttd_guid:new(), + Ts2 = emqttd_guid:timestamp(emqttd_guid:gen()), + true = Ts2 > Ts1. + +%%-------------------------------------------------------------------- +%% emqttd_opts +%%-------------------------------------------------------------------- + +opts_merge(_) -> + Opts = emqttd_opts:merge(?SOCKOPTS, [raw, + binary, + {backlog, 1024}, + {nodelay, false}, + {max_clients, 1024}, + {acceptors, 16}]), + 1024 = proplists:get_value(backlog, Opts), + 1024 = proplists:get_value(max_clients, Opts), + [binary, raw, + {acceptors, 16}, + {backlog, 1024}, + {max_clients, 1024}, + {nodelay, false}, + {packet, raw}, + {reuseaddr, true}] = lists:sort(Opts). + +%%-------------------------------------------------------------------- +%% priority_queue +%%-------------------------------------------------------------------- + +priority_queue_plen(_) -> + Q = ?PQ:new(), + 0 = ?PQ:plen(0, Q), + Q0 = ?PQ:in(z, Q), + 1 = ?PQ:plen(0, Q0), + Q1 = ?PQ:in(x, 1, Q0), + 1 = ?PQ:plen(1, Q1), + Q2 = ?PQ:in(y, 2, Q1), + 1 = ?PQ:plen(2, Q2), + Q3 = ?PQ:in(z, 2, Q2), + 2 = ?PQ:plen(2, Q3), + {_, Q4} = ?PQ:out(1, Q3), + 0 = ?PQ:plen(1, Q4), + {_, Q5} = ?PQ:out(Q4), + 1 = ?PQ:plen(2, Q5), + {_, Q6} = ?PQ:out(Q5), + 0 = ?PQ:plen(2, Q6), + 1 = ?PQ:len(Q6), + {_, Q7} = ?PQ:out(Q6), + 0 = ?PQ:len(Q7). + +priority_queue_out2(_) -> + Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}], + Q = ?PQ:new(), + Q0 = lists:foldl( + fun({El, P}, Acc) -> + ?PQ:in(El, P, Acc); + (El, Acc) -> + ?PQ:in(El, Acc) + end, Q, Els), + {Val, Q1} = ?PQ:out(Q0), + {value, d} = Val, + {Val1, Q2} = ?PQ:out(2, Q1), + {value, e} = Val1, + {Val2, Q3} = ?PQ:out(1, Q2), + {value, b} = Val2, + {Val3, Q4} = ?PQ:out(Q3), + {value, f} = Val3, + {Val4, Q5} = ?PQ:out(Q4), + {value, c} = Val4, + {Val5, Q6} = ?PQ:out(Q5), + {value, a} = Val5, + {empty, _Q7} = ?PQ:out(Q6). + +%%-------------------------------------------------------------------- +%% emqttd_time +%%-------------------------------------------------------------------- + +time_now_to_(_) -> + emqttd_time:seed(), + emqttd_time:now_to_secs(), + emqttd_time:now_to_ms(). + +%%-------------------------------------------------------------------- +%% emqttd_node +%%-------------------------------------------------------------------- + +node_is_aliving(_) -> + io:format("Node: ~p~n", [node()]), + true = emqttd_node:is_aliving(node()), + false = emqttd_node:is_aliving('x@127.0.0.1'). + +node_parse_name(_) -> + 'a@127.0.0.1' = emqttd_node:parse_name("a@127.0.0.1"), + 'b@127.0.0.1' = emqttd_node:parse_name("b"). + diff --git a/test/emqttd_mock_client.erl b/test/emqttd_mock_client.erl new file mode 100644 index 000000000..7a9f90010 --- /dev/null +++ b/test/emqttd_mock_client.erl @@ -0,0 +1,63 @@ + +-module(emqttd_mock_client). + +-behaviour(gen_server). + +%% ------------------------------------------------------------------ +%% API Function Exports +%% ------------------------------------------------------------------ + +-export([start_link/1, start_session/1, stop/1]). + +%% ------------------------------------------------------------------ +%% gen_server Function Exports +%% ------------------------------------------------------------------ + +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-record(state, {clientid, session}). + +%% ------------------------------------------------------------------ +%% API Function Definitions +%% ------------------------------------------------------------------ + +start_link(ClientId) -> + gen_server:start_link(?MODULE, [ClientId], []). + +start_session(CPid) -> + gen_server:call(CPid, start_session). + +stop(CPid) -> + gen_server:call(CPid, stop). + +%% ------------------------------------------------------------------ +%% gen_server Function Definitions +%% ------------------------------------------------------------------ + +init([ClientId]) -> + {ok, #state{clientid = ClientId}}. + +handle_call(start_session, _From, State = #state{clientid = ClientId}) -> + {ok, SessPid, _} = emqttd_sm:start_session(true, ClientId), + {reply, {ok, SessPid}, State#state{session = SessPid}}; + +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; + +handle_call(_Request, _From, State) -> + {reply, ok, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + diff --git a/test/emqttd_mod_SUITE.erl b/test/emqttd_mod_SUITE.erl new file mode 100644 index 000000000..9240b8a3f --- /dev/null +++ b/test/emqttd_mod_SUITE.erl @@ -0,0 +1,35 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2016 Feng Lee . +%% +%% 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(emqttd_mod_SUITE). + +-compile(export_all). + +-include("emqttd.hrl"). + +all() -> [mod_subscription_rep]. + +mod_subscription_rep(_) -> ok. +%% <<"topic/clientId">> = emqttd_mod_subscription:rep( +%% <<"$c">>, <<"clientId">>, <<"topic/$c">>), +%% <<"topic/username">> = emqttd_mod_subscription:rep( +%% <<"$u">>, <<"username">>, <<"topic/$u">>), +%% <<"topic/username/clientId">> = emqttd_mod_subscription:rep( +%% <<"$c">>, <<"clientId">>, emqttd_mod_subscription:rep( +%% <<"$u">>, <<"username">>, <<"topic/$u/$c">>)). + + + diff --git a/test/emqttd_mod_subscription_tests.erl b/test/emqttd_mod_subscription_tests.erl deleted file mode 100644 index 4f1cffbef..000000000 --- a/test/emqttd_mod_subscription_tests.erl +++ /dev/null @@ -1,18 +0,0 @@ --module(emqttd_mod_subscription_tests). - --include("emqttd.hrl"). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - --define(M, emqttd_mod_subscription). - -rep_test() -> - ?assertEqual(<<"topic/clientId">>, ?M:rep(<<"$c">>, <<"clientId">>, <<"topic/$c">>)), - ?assertEqual(<<"topic/username">>, ?M:rep(<<"$u">>, <<"username">>, <<"topic/$u">>)), - ?assertEqual(<<"topic/username/clientId">>, - ?M:rep(<<"$c">>, <<"clientId">>, - ?M:rep(<<"$u">>, <<"username">>, <<"topic/$u/$c">>))). - --endif. diff --git a/test/emqttd_mqueue_tests.erl b/test/emqttd_mqueue_SUITE.erl similarity index 51% rename from test/emqttd_mqueue_tests.erl rename to test/emqttd_mqueue_SUITE.erl index 1fa5d7993..76ab62044 100644 --- a/test/emqttd_mqueue_tests.erl +++ b/test/emqttd_mqueue_SUITE.erl @@ -1,106 +1,102 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_mqueue_tests). +-module(emqttd_mqueue_SUITE). + +-compile(export_all). -include("emqttd.hrl"). -define(Q, emqttd_mqueue). --ifdef(TEST). +all() -> [t_in, t_in_qos0, t_out, t_simple_mqueue, t_priority_mqueue, + t_priority_mqueue2, t_infinity_priority_mqueue, + t_infinity_simple_mqueue]. --include_lib("eunit/include/eunit.hrl"). - -in_test() -> +t_in(_) -> Opts = [{max_length, 5}, {queue_qos0, true}], Q = ?Q:new(<<"testQ">>, Opts, alarm_fun()), - ?assertEqual(true, ?Q:is_empty(Q)), + true = ?Q:is_empty(Q), Q1 = ?Q:in(#mqtt_message{}, Q), - ?assertEqual(1, ?Q:len(Q1)), + 1 = ?Q:len(Q1), Q2 = ?Q:in(#mqtt_message{qos = 1}, Q1), - ?assertEqual(2, ?Q:len(Q2)), + 2 = ?Q:len(Q2), Q3 = ?Q:in(#mqtt_message{qos = 2}, Q2), Q4 = ?Q:in(#mqtt_message{}, Q3), Q5 = ?Q:in(#mqtt_message{}, Q4), - ?assertEqual(5, ?Q:len(Q5)). - -in_qos0_test() -> + 5 = ?Q:len(Q5). + +t_in_qos0(_) -> Opts = [{max_length, 5}, {queue_qos0, false}], Q = ?Q:new(<<"testQ">>, Opts, alarm_fun()), Q1 = ?Q:in(#mqtt_message{}, Q), - ?assertEqual(true, ?Q:is_empty(Q1)), + true = ?Q:is_empty(Q1), Q2 = ?Q:in(#mqtt_message{qos = 0}, Q1), - ?assertEqual(true, ?Q:is_empty(Q2)). + true = ?Q:is_empty(Q2). -out_test() -> +t_out(_) -> Opts = [{max_length, 5}, {queue_qos0, true}], Q = ?Q:new(<<"testQ">>, Opts, alarm_fun()), - ?assertMatch({empty, Q}, ?Q:out(Q)), + {empty, Q} = ?Q:out(Q), Q1 = ?Q:in(#mqtt_message{}, Q), {Value, Q2} = ?Q:out(Q1), - ?assertEqual(0, ?Q:len(Q2)), - ?assertMatch({value, #mqtt_message{}}, Value). + 0 = ?Q:len(Q2), + {value, #mqtt_message{}} = Value. -simple_mqueue_test() -> +t_simple_mqueue(_) -> Opts = [{type, simple}, {max_length, 3}, {low_watermark, 0.2}, {high_watermark, 0.6}, {queue_qos0, false}], Q = ?Q:new("simple_queue", Opts, alarm_fun()), - ?assertEqual(simple, ?Q:type(Q)), - ?assertEqual(3, ?Q:max_len(Q)), - ?assertEqual(<<"simple_queue">>, ?Q:name(Q)), - ?assert(?Q:is_empty(Q)), + simple = ?Q:type(Q), + 3 = ?Q:max_len(Q), + <<"simple_queue">> = ?Q:name(Q), + true = ?Q:is_empty(Q), Q1 = ?Q:in(#mqtt_message{qos = 1, payload = <<"1">>}, Q), Q2 = ?Q:in(#mqtt_message{qos = 1, payload = <<"2">>}, Q1), Q3 = ?Q:in(#mqtt_message{qos = 1, payload = <<"3">>}, Q2), Q4 = ?Q:in(#mqtt_message{qos = 1, payload = <<"4">>}, Q3), - ?assertEqual(3, ?Q:len(Q4)), + 3 = ?Q:len(Q4), {{value, Msg}, Q5} = ?Q:out(Q4), - ?assertMatch(<<"2">>, Msg#mqtt_message.payload), - ?assertEqual([{len, 2}, {max_len, 3}, {dropped, 1}], ?Q:stats(Q5)). + <<"2">> = Msg#mqtt_message.payload, + [{len, 2}, {max_len, 3}, {dropped, 1}] = ?Q:stats(Q5). -infinity_simple_mqueue_test() -> +t_infinity_simple_mqueue(_) -> Opts = [{type, simple}, {max_length, infinity}, {low_watermark, 0.2}, {high_watermark, 0.6}, {queue_qos0, false}], Q = ?Q:new("infinity_simple_queue", Opts, alarm_fun()), - ?assert(?Q:is_empty(Q)), - ?assertEqual(infinity, ?Q:max_len(Q)), + true = ?Q:is_empty(Q), + infinity = ?Q:max_len(Q), Qx = lists:foldl(fun(I, AccQ) -> ?Q:in(#mqtt_message{qos = 1, payload = iolist_to_binary([I])}, AccQ) end, Q, lists:seq(1, 255)), - ?assertEqual(255, ?Q:len(Qx)), - ?assertEqual([{len, 255}, {max_len, infinity}, {dropped, 0}], ?Q:stats(Qx)), - {{value, V}, Qy} = ?Q:out(Qx), - ?assertEqual(<<1>>, V#mqtt_message.payload). + 255 = ?Q:len(Qx), + [{len, 255}, {max_len, infinity}, {dropped, 0}] = ?Q:stats(Qx), + {{value, V}, _Qy} = ?Q:out(Qx), + <<1>> = V#mqtt_message.payload. -priority_mqueue_test() -> +t_priority_mqueue(_) -> Opts = [{type, priority}, {priority, [{<<"t">>, 10}]}, {max_length, 3}, @@ -108,57 +104,54 @@ priority_mqueue_test() -> {high_watermark, 0.6}, {queue_qos0, false}], Q = ?Q:new("priority_queue", Opts, alarm_fun()), - ?assertEqual(priority, ?Q:type(Q)), - ?assertEqual(3, ?Q:max_len(Q)), - ?assertEqual(<<"priority_queue">>, ?Q:name(Q)), + priority = ?Q:type(Q), + 3 = ?Q:max_len(Q), + <<"priority_queue">> = ?Q:name(Q), - ?assert(?Q:is_empty(Q)), + true = ?Q:is_empty(Q), Q1 = ?Q:in(#mqtt_message{qos = 1, topic = <<"t1">>}, Q), Q2 = ?Q:in(#mqtt_message{qos = 1, topic = <<"t">>}, Q1), Q3 = ?Q:in(#mqtt_message{qos = 1, topic = <<"t2">>}, Q2), - ?assertEqual(3, ?Q:len(Q3)), + 3 = ?Q:len(Q3), Q4 = ?Q:in(#mqtt_message{qos = 1, topic = <<"t1">>}, Q3), - ?assertEqual(4, ?Q:len(Q4)), + 4 = ?Q:len(Q4), Q5 = ?Q:in(#mqtt_message{qos = 1, topic = <<"t1">>}, Q4), - ?assertEqual(5, ?Q:len(Q5)), + 5 = ?Q:len(Q5), Q6 = ?Q:in(#mqtt_message{qos = 1, topic = <<"t1">>}, Q5), - ?assertEqual(5, ?Q:len(Q6)), - {{value, Msg}, Q7} = ?Q:out(Q6), - ?assertMatch(<<"t">>, Msg#mqtt_message.topic). + 5 = ?Q:len(Q6), + {{value, Msg}, _Q7} = ?Q:out(Q6), + <<"t">> = Msg#mqtt_message.topic. -infinity_priority_mqueue_test() -> +t_infinity_priority_mqueue(_) -> Opts = [{type, priority}, {priority, [{<<"t1">>, 10}, {<<"t2">>, 8}]}, {max_length, infinity}, {queue_qos0, false}], Q = ?Q:new("infinity_priority_queue", Opts, alarm_fun()), - ?assertEqual(infinity, ?Q:max_len(Q)), + infinity = ?Q:max_len(Q), Qx = lists:foldl(fun(I, AccQ) -> AccQ1 = ?Q:in(#mqtt_message{topic = <<"t1">>, qos = 1, payload = iolist_to_binary([I])}, AccQ), ?Q:in(#mqtt_message{topic = <<"t">>, qos = 1, payload = iolist_to_binary([I])}, AccQ1) end, Q, lists:seq(1, 255)), - ?assertEqual(510, ?Q:len(Qx)), - ?assertEqual([{len, 510}, {max_len, infinity}, {dropped, 0}], ?Q:stats(Qx)). + 510 = ?Q:len(Qx), + [{len, 510}, {max_len, infinity}, {dropped, 0}] = ?Q:stats(Qx). -priority_mqueue2_test() -> +t_priority_mqueue2(_) -> Opts = [{type, priority}, {max_length, 2}, {low_watermark, 0.2}, {high_watermark, 0.6}, {queue_qos0, false}], Q = ?Q:new("priority_queue2_test", Opts, alarm_fun()), - ?assertEqual(2, ?Q:max_len(Q)), + 2 = ?Q:max_len(Q), Q1 = ?Q:in(#mqtt_message{topic = <<"x">>, qos = 1, payload = <<1>>}, Q), Q2 = ?Q:in(#mqtt_message{topic = <<"x">>, qos = 1, payload = <<2>>}, Q1), Q3 = ?Q:in(#mqtt_message{topic = <<"y">>, qos = 1, payload = <<3>>}, Q2), Q4 = ?Q:in(#mqtt_message{topic = <<"y">>, qos = 1, payload = <<4>>}, Q3), - ?assertEqual(4, ?Q:len(Q4)), - {{value, Val}, Q5} = ?Q:out(Q4), - ?debugFmt("Val: ~p~n", [Val]), - ?assertEqual(3, ?Q:len(Q5)). + 4 = ?Q:len(Q4), + {{value, _Val}, Q5} = ?Q:out(Q4), + 3 = ?Q:len(Q5). alarm_fun() -> fun(_, _) -> alarm_fun() end. --endif. - diff --git a/test/emqttd_net_SUITE.erl b/test/emqttd_net_SUITE.erl new file mode 100644 index 000000000..22641c2b2 --- /dev/null +++ b/test/emqttd_net_SUITE.erl @@ -0,0 +1,44 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2016 Feng Lee . +%% +%% 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(emqttd_net_SUITE). + +%% CT +-compile(export_all). + +all() -> [{group, keepalive}]. + +groups() -> [{keepalive, [], [t_keepalive]}]. + +%%-------------------------------------------------------------------- +%% Keepalive +%%-------------------------------------------------------------------- + +t_keepalive(_) -> + KA = emqttd_keepalive:start(fun() -> {ok, 1} end, 1, {keepalive, timeout}), + [resumed, timeout] = lists:reverse(keepalive_recv(KA, [])). + +keepalive_recv(KA, Acc) -> + receive + {keepalive, timeout} -> + case emqttd_keepalive:check(KA) of + {ok, KA1} -> keepalive_recv(KA1, [resumed | Acc]); + {error, timeout} -> [timeout | Acc] + end + after 4000 -> + Acc + end. + diff --git a/test/emqttd_opts_tests.erl b/test/emqttd_opts_tests.erl deleted file mode 100644 index 42d1bac8e..000000000 --- a/test/emqttd_opts_tests.erl +++ /dev/null @@ -1,58 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd_opts_tests. -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_opts_tests). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - --define(SOCKOPTS, [ - binary, - {packet, raw}, - {reuseaddr, true}, - {backlog, 512}, - {nodelay, true} -]). - -merge_test() -> - Opts = emqttd_opts:merge(?SOCKOPTS, [raw, - {backlog, 1024}, - {nodelay, false}, - {max_clients, 1024}, - {acceptors, 16}]), - ?assertEqual(1024, proplists:get_value(backlog, Opts)), - ?assertEqual(1024, proplists:get_value(max_clients, Opts)), - ?assertEqual(lists:sort(Opts), [binary, raw, - {acceptors, 16}, - {backlog, 1024}, - {max_clients, 1024}, - {nodelay, false}, - {packet, raw}, - {reuseaddr, true}]). - - --endif. diff --git a/test/emqttd_parser_tests.erl b/test/emqttd_parser_tests.erl deleted file mode 100644 index dc85c7abd..000000000 --- a/test/emqttd_parser_tests.erl +++ /dev/null @@ -1,157 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd_parser tests. -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_parser_tests). - --ifdef(TEST). - --include("emqttd_protocol.hrl"). - --include_lib("eunit/include/eunit.hrl"). - -parse_connect_test() -> - Parser = emqttd_parser:new([]), - %% CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined) - V31ConnBin = <<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,111,99,97>>, - ?assertMatch({ok, #mqtt_packet{ - header = #mqtt_packet_header{type = ?CONNECT, - dup = false, - qos = 0, - retain = false}, - variable = #mqtt_packet_connect{proto_ver = 3, - proto_name = <<"MQIsdp">>, - client_id = <<"mosqpub/10451-iMac.loca">>, - clean_sess = true, - keep_alive = 60}}, <<>>}, Parser(V31ConnBin)), - %% CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined) - V311ConnBin = <<16,35,0,4,77,81,84,84,4,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,111,99,97>>, - ?assertMatch({ok, #mqtt_packet{ - header = #mqtt_packet_header{type = ?CONNECT, - dup = false, - qos = 0, - retain = false}, - variable = #mqtt_packet_connect{proto_ver = 4, - proto_name = <<"MQTT">>, - client_id = <<"mosqpub/10451-iMac.loca">>, - clean_sess = true, - keep_alive = 60 } }, <<>>}, Parser(V311ConnBin)), - - %% CONNECT(Qos=0, Retain=false, Dup=false, ClientId="", ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60) - V311ConnWithoutClientId = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>, - ?assertMatch({ok, #mqtt_packet{ - header = #mqtt_packet_header{type = ?CONNECT, - dup = false, - qos = 0, - retain = false}, - variable = #mqtt_packet_connect{proto_ver = 4, - proto_name = <<"MQTT">>, - client_id = <<>>, - clean_sess = true, - keep_alive = 60 } }, <<>>}, Parser(V311ConnWithoutClientId)), - %%CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10452-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, Username=test, Password=******, Will(Qos=1, Retain=false, Topic=/will, Msg=willmsg)) - ConnBinWithWill = <<16,67,0,6,77,81,73,115,100,112,3,206,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,50,45,105,77,97,99,46,108,111,99,97,0,5,47,119,105,108,108,0,7,119,105,108,108,109,115,103,0,4,116,101,115,116,0,6,112,117,98,108,105,99>>, - ?assertMatch({ok, #mqtt_packet{ - header = #mqtt_packet_header{type = ?CONNECT, - dup = false, - qos = 0, - retain = false}, - variable = #mqtt_packet_connect{proto_ver = 3, - proto_name = <<"MQIsdp">>, - client_id = <<"mosqpub/10452-iMac.loca">>, - clean_sess = true, - keep_alive = 60, - will_retain = false, - will_qos = 1, - will_flag = true, - will_topic = <<"/will">>, - will_msg = <<"willmsg">> , - username = <<"test">>, - password = <<"public">>}}, - <<>> }, Parser(ConnBinWithWill)), - ok. - -parse_publish_test() -> - Parser = emqttd_parser:new([]), - %%PUBLISH(Qos=1, Retain=false, Dup=false, TopicName=a/b/c, PacketId=1, Payload=<<"hahah">>) - PubBin = <<50,14,0,5,97,47,98,47,99,0,1,104,97,104,97,104>>, - ?assertMatch({ok, #mqtt_packet{ - header = #mqtt_packet_header{type = ?PUBLISH, - dup = false, - qos = 1, - retain = false}, - variable = #mqtt_packet_publish{topic_name = <<"a/b/c">>, - packet_id = 1}, - payload = <<"hahah">> }, <<>>}, Parser(PubBin)), - - %PUBLISH(Qos=0, Retain=false, Dup=false, TopicName=xxx/yyy, PacketId=undefined, Payload=<<"hello">>) - %DISCONNECT(Qos=0, Retain=false, Dup=false) - PubBin1 = <<48,14,0,7,120,120,120,47,121,121,121,104,101,108,108,111,224,0>>, - ?assertMatch({ok, #mqtt_packet { - header = #mqtt_packet_header{type = ?PUBLISH, - dup = false, - qos = 0, - retain = false}, - variable = #mqtt_packet_publish{topic_name = <<"xxx/yyy">>, - packet_id = undefined}, - payload = <<"hello">> }, <<224,0>>}, Parser(PubBin1)), - ?assertMatch({ok, #mqtt_packet{ - header = #mqtt_packet_header{type = ?DISCONNECT, - dup = false, - qos = 0, - retain = false} - }, <<>>}, Parser(<<224, 0>>)). - -parse_puback_test() -> - Parser = emqttd_parser:new([]), - %%PUBACK(Qos=0, Retain=false, Dup=false, PacketId=1) - PubAckBin = <<64,2,0,1>>, - ?assertMatch({ok, #mqtt_packet { - header = #mqtt_packet_header{type = ?PUBACK, - dup = false, - qos = 0, - retain = false } - }, <<>>}, Parser(PubAckBin)), - ok. - -parse_subscribe_test() -> - ok. - -parse_pingreq_test() -> - ok. - -parse_disconnect_test() -> - Parser = emqttd_parser:new([]), - %DISCONNECT(Qos=0, Retain=false, Dup=false) - Bin = <<224, 0>>, - ?assertMatch({ok, #mqtt_packet{ - header = #mqtt_packet_header{type = ?DISCONNECT, - dup = false, - qos = 0, - retain = false} - }, <<>>}, Parser(Bin)). - --endif. - diff --git a/test/emqttd_protocol_SUITE.erl b/test/emqttd_protocol_SUITE.erl new file mode 100644 index 000000000..9b0311c2b --- /dev/null +++ b/test/emqttd_protocol_SUITE.erl @@ -0,0 +1,308 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_protocol_SUITE). + +-compile(export_all). + +-import(emqttd_serializer, [serialize/1]). + +-include("emqttd.hrl"). + +-include("emqttd_protocol.hrl"). + +all() -> + [{group, parser}, + {group, serializer}, + {group, packet}, + {group, message}]. + +groups() -> + [{parser, [], + [parse_connect, + parse_publish, + parse_puback, + parse_subscribe, + parse_pingreq, + parse_disconnect]}, + {serializer, [], + [serialize_connect, + serialize_connack, + serialize_publish, + serialize_puback, + serialize_pubrel, + serialize_subscribe, + serialize_suback, + serialize_unsubscribe, + serialize_unsuback, + serialize_pingreq, + serialize_pingresp, + serialize_disconnect]}, + {packet, [], + [packet_proto_name, + packet_type_name, + packet_connack_name, + packet_format]}, + {message, [], + [message_make, + message_from_packet, + message_flag]}]. + +%%-------------------------------------------------------------------- +%% Parse Cases +%%-------------------------------------------------------------------- + +parse_connect(_) -> + Parser = emqttd_parser:new([]), + %% CONNECT(Q0, R0, D0, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined) + V31ConnBin = <<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,111,99,97>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT, + dup = false, + qos = 0, + retain = false}, + variable = #mqtt_packet_connect{proto_ver = 3, + proto_name = <<"MQIsdp">>, + client_id = <<"mosqpub/10451-iMac.loca">>, + clean_sess = true, + keep_alive = 60}}, <<>>} = Parser(V31ConnBin), + %% CONNECT(Q0, R0, D0, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined) + V311ConnBin = <<16,35,0,4,77,81,84,84,4,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,111,99,97>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT, + dup = false, + qos = 0, + retain = false}, + variable = #mqtt_packet_connect{proto_ver = 4, + proto_name = <<"MQTT">>, + client_id = <<"mosqpub/10451-iMac.loca">>, + clean_sess = true, + keep_alive = 60 } }, <<>>} = Parser(V311ConnBin), + + %% CONNECT(Qos=0, Retain=false, Dup=false, ClientId="", ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60) + V311ConnWithoutClientId = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT, + dup = false, + qos = 0, + retain = false}, + variable = #mqtt_packet_connect{proto_ver = 4, + proto_name = <<"MQTT">>, + client_id = <<>>, + clean_sess = true, + keep_alive = 60 } }, <<>>} = Parser(V311ConnWithoutClientId), + %%CONNECT(Q0, R0, D0, ClientId=mosqpub/10452-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, + %% Username=test, Password=******, Will(Qos=1, Retain=false, Topic=/will, Msg=willmsg)) + ConnBinWithWill = <<16,67,0,6,77,81,73,115,100,112,3,206,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,50,45,105,77,97,99,46,108,111,99,97,0,5,47,119,105,108,108,0,7,119,105,108,108,109,115,103,0,4,116,101,115,116,0,6,112,117,98,108,105,99>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT, + dup = false, + qos = 0, + retain = false}, + variable = #mqtt_packet_connect{proto_ver = 3, + proto_name = <<"MQIsdp">>, + client_id = <<"mosqpub/10452-iMac.loca">>, + clean_sess = true, + keep_alive = 60, + will_retain = false, + will_qos = 1, + will_flag = true, + will_topic = <<"/will">>, + will_msg = <<"willmsg">>, + username = <<"test">>, + password = <<"public">>}}, <<>>} = Parser(ConnBinWithWill), + ok. + +parse_publish(_) -> + Parser = emqttd_parser:new([]), + %%PUBLISH(Qos=1, Retain=false, Dup=false, TopicName=a/b/c, PacketId=1, Payload=<<"hahah">>) + PubBin = <<50,14,0,5,97,47,98,47,99,0,1,104,97,104,97,104>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, + dup = false, + qos = 1, + retain = false}, + variable = #mqtt_packet_publish{topic_name = <<"a/b/c">>, + packet_id = 1}, + payload = <<"hahah">> }, <<>>} = Parser(PubBin), + + %PUBLISH(Qos=0, Retain=false, Dup=false, TopicName=xxx/yyy, PacketId=undefined, Payload=<<"hello">>) + %DISCONNECT(Qos=0, Retain=false, Dup=false) + PubBin1 = <<48,14,0,7,120,120,120,47,121,121,121,104,101,108,108,111,224,0>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, + dup = false, + qos = 0, + retain = false}, + variable = #mqtt_packet_publish{topic_name = <<"xxx/yyy">>, + packet_id = undefined}, + payload = <<"hello">> }, <<224,0>>} = Parser(PubBin1), + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?DISCONNECT, + dup = false, + qos = 0, + retain = false}}, <<>>} = Parser(<<224, 0>>). + +parse_puback(_) -> + Parser = emqttd_parser:new([]), + %%PUBACK(Qos=0, Retain=false, Dup=false, PacketId=1) + PubAckBin = <<64,2,0,1>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?PUBACK, + dup = false, + qos = 0, + retain = false}}, <<>>} = Parser(PubAckBin). + +parse_subscribe(_) -> + ok. + +parse_pingreq(_) -> + ok. + +parse_disconnect(_) -> + Parser = emqttd_parser:new([]), + %DISCONNECT(Qos=0, Retain=false, Dup=false) + Bin = <<224, 0>>, + {ok, #mqtt_packet{header = #mqtt_packet_header{type = ?DISCONNECT, + dup = false, + qos = 0, + retain = false}}, <<>>} = Parser(Bin). + +%%-------------------------------------------------------------------- +%% Serialize Cases +%%-------------------------------------------------------------------- + +serialize_connect(_) -> + serialize(?CONNECT_PACKET(#mqtt_packet_connect{})), + serialize(?CONNECT_PACKET(#mqtt_packet_connect{ + client_id = <<"clientId">>, + will_qos = ?QOS1, + will_flag = true, + will_retain = true, + will_topic = <<"will">>, + will_msg = <<"haha">>, + clean_sess = true})). + +serialize_connack(_) -> + ConnAck = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, + variable = #mqtt_packet_connack{ack_flags = 0, return_code = 0}}, + <<32,2,0,0>> = serialize(ConnAck). + +serialize_publish(_) -> + serialize(?PUBLISH_PACKET(?QOS_0, <<"Topic">>, undefined, <<"Payload">>)), + serialize(?PUBLISH_PACKET(?QOS_1, <<"Topic">>, 938, <<"Payload">>)), + serialize(?PUBLISH_PACKET(?QOS_2, <<"Topic">>, 99, long_payload())). + +serialize_puback(_) -> + serialize(?PUBACK_PACKET(?PUBACK, 10384)). + +serialize_pubrel(_) -> + serialize(?PUBREL_PACKET(10384)). + +serialize_subscribe(_) -> + TopicTable = [{<<"TopicQos0">>, ?QOS_0}, {<<"TopicQos1">>, ?QOS_1}, {<<"TopicQos2">>, ?QOS_2}], + serialize(?SUBSCRIBE_PACKET(10, TopicTable)). + +serialize_suback(_) -> + serialize(?SUBACK_PACKET(10, [?QOS_0, ?QOS_1, 128])). + +serialize_unsubscribe(_) -> + serialize(?UNSUBSCRIBE_PACKET(10, [<<"Topic1">>, <<"Topic2">>])). + +serialize_unsuback(_) -> + serialize(?UNSUBACK_PACKET(10)). + +serialize_pingreq(_) -> + serialize(?PACKET(?PINGREQ)). + +serialize_pingresp(_) -> + serialize(?PACKET(?PINGRESP)). + +serialize_disconnect(_) -> + serialize(?PACKET(?DISCONNECT)). + +long_payload() -> + iolist_to_binary(["payload." || _I <- lists:seq(1, 100)]). + +%%-------------------------------------------------------------------- +%% Packet Cases +%%-------------------------------------------------------------------- + +packet_proto_name(_) -> + <<"MQIsdp">> = emqttd_packet:protocol_name(3), + <<"MQTT">> = emqttd_packet:protocol_name(4). + +packet_type_name(_) -> + 'CONNECT' = emqttd_packet:type_name(?CONNECT), + 'UNSUBSCRIBE' = emqttd_packet:type_name(?UNSUBSCRIBE). + +packet_connack_name(_) -> + 'CONNACK_ACCEPT' = emqttd_packet:connack_name(?CONNACK_ACCEPT), + 'CONNACK_PROTO_VER' = emqttd_packet:connack_name(?CONNACK_PROTO_VER), + 'CONNACK_INVALID_ID' = emqttd_packet:connack_name(?CONNACK_INVALID_ID), + 'CONNACK_SERVER' = emqttd_packet:connack_name(?CONNACK_SERVER), + 'CONNACK_CREDENTIALS' = emqttd_packet:connack_name(?CONNACK_CREDENTIALS), + 'CONNACK_AUTH' = emqttd_packet:connack_name(?CONNACK_AUTH). + +packet_format(_) -> + io:format("~s", [emqttd_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{}))]), + io:format("~s", [emqttd_packet:format(?CONNACK_PACKET(?CONNACK_SERVER))]), + io:format("~s", [emqttd_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]), + io:format("~s", [emqttd_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]), + io:format("~s", [emqttd_packet:format(?PUBACK_PACKET(?PUBACK, 98))]), + io:format("~s", [emqttd_packet:format(?PUBREL_PACKET(99))]), + io:format("~s", [emqttd_packet:format(?SUBSCRIBE_PACKET(15, [{<<"topic">>, ?QOS0}, {<<"topic1">>, ?QOS1}]))]), + io:format("~s", [emqttd_packet:format(?SUBACK_PACKET(40, [?QOS0, ?QOS1]))]), + io:format("~s", [emqttd_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]), + io:format("~s", [emqttd_packet:format(?UNSUBACK_PACKET(90))]). + +%%-------------------------------------------------------------------- +%% Message Cases +%%-------------------------------------------------------------------- + +message_make(_) -> + Msg = emqttd_message:make(<<"clientid">>, <<"topic">>, <<"payload">>), + 0 = Msg#mqtt_message.qos, + undefined = Msg#mqtt_message.msgid, + Msg1 = emqttd_message:make(<<"clientid">>, qos2, <<"topic">>, <<"payload">>), + true = is_binary(Msg1#mqtt_message.msgid), + 2 = Msg1#mqtt_message.qos. + +message_from_packet(_) -> + Msg = emqttd_message:from_packet(?PUBLISH_PACKET(1, <<"topic">>, 10, <<"payload">>)), + 1 = Msg#mqtt_message.qos, + 10 = Msg#mqtt_message.pktid, + <<"topic">> = Msg#mqtt_message.topic, + + WillMsg = emqttd_message:from_packet(#mqtt_packet_connect{will_flag = true, + will_topic = <<"WillTopic">>, + will_msg = <<"WillMsg">>}), + <<"WillTopic">> = WillMsg#mqtt_message.topic, + <<"WillMsg">> = WillMsg#mqtt_message.payload, + + Msg2 = emqttd_message:from_packet(<<"username">>, <<"clientid">>, + ?PUBLISH_PACKET(1, <<"topic">>, 20, <<"payload">>)), + <<"clientid">> = Msg2#mqtt_message.from, + <<"username">> = Msg2#mqtt_message.sender, + io:format("~s", [emqttd_message:format(Msg2)]). + +message_flag(_) -> + Pkt = ?PUBLISH_PACKET(1, <<"t">>, 2, <<"payload">>), + Msg2 = emqttd_message:from_packet(<<"clientid">>, Pkt), + Msg3 = emqttd_message:set_flag(retain, Msg2), + Msg4 = emqttd_message:set_flag(dup, Msg3), + true = Msg4#mqtt_message.dup, + true = Msg4#mqtt_message.retain, + Msg5 = emqttd_message:set_flag(Msg4), + Msg6 = emqttd_message:unset_flag(dup, Msg5), + Msg7 = emqttd_message:unset_flag(retain, Msg6), + false = Msg7#mqtt_message.dup, + false = Msg7#mqtt_message.retain, + emqttd_message:unset_flag(Msg7), + emqttd_message:to_packet(Msg7). + diff --git a/test/emqttd_retainer_tests.erl b/test/emqttd_retainer_tests.erl deleted file mode 100644 index 6bd013185..000000000 --- a/test/emqttd_retainer_tests.erl +++ /dev/null @@ -1,14 +0,0 @@ --module(emqttd_retainer_tests). - --include("emqttd.hrl"). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - -retain_test() -> - mnesia:start(), - emqttd_retainer:mnesia(boot), - mnesia:stop(). - --endif. diff --git a/test/emqttd_router_tests.erl b/test/emqttd_router_tests.erl deleted file mode 100644 index 14e4aa51c..000000000 --- a/test/emqttd_router_tests.erl +++ /dev/null @@ -1,130 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- - --module(emqttd_router_tests). - --include("emqttd.hrl"). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - --define(R, emqttd_router). - -route_test_() -> - {foreach, - fun setup/0, fun teardown/1, - [?_test(t_add_route()), - ?_test(t_add_routes()), - ?_test(t_delete_route()), - ?_test(t_delete_routes()), - ?_test(t_route()) - ]}. - -setup() -> - application:start(gproc), - ensure_tab(route, [public, named_table, duplicate_bag]), - gproc_pool:new(router, hash, [{size, 2}]), - lists:foreach(fun(I) -> - gproc_pool:add_worker(router, {router, I}, I), - {ok, R} = ?R:start_link(router, I, fun(_) -> ok end, []) - end, [1, 2]). - -ensure_tab(Tab, Opts) -> - case ets:info(Tab, name) of - undefined -> ets:new(Tab, Opts); - _ -> ok - end. - -teardown(_) -> - lists:foreach(fun(I) -> - ?R:stop(I), gproc_pool:remove_worker(router, {router, I}) - end, [1, 2]), - gproc_pool:delete(router), - ets:delete(route). - -t_add_route() -> - Self = self(), - ?R:add_route(<<"topic1">>, Self), - ?assert(?R:has_route(<<"topic1">>)), - ?R:add_route(<<"topic2">>, Self), - ?assert(?R:has_route(<<"topic2">>)), - ?assertEqual([Self], ?R:lookup_routes(<<"topic1">>)), - ?assertEqual([Self], ?R:lookup_routes(<<"topic2">>)). - -t_add_routes() -> - Self = self(), - ?R:add_routes([], Self), - ?R:add_routes([<<"t0">>], Self), - ?R:add_routes([<<"t1">>,<<"t2">>,<<"t3">>], Self), - ?assert(?R:has_route(<<"t1">>)), - ?assertEqual([Self], ?R:lookup_routes(<<"t1">>)), - ?assertEqual([Self], ?R:lookup_routes(<<"t2">>)), - ?assertEqual([Self], ?R:lookup_routes(<<"t3">>)). - -t_delete_route() -> - Self = self(), - ?R:add_routes([<<"t1">>,<<"t2">>,<<"t3">>], Self), - ?assert(?R:has_route(<<"t1">>)), - ?R:delete_route(<<"t2">>, Self), - erlang:yield(), - ?assertNot(?R:has_route(<<"t2">>)), - ?assert(?R:has_route(<<"t1">>)), - ?R:delete_route(<<"t3">>, Self), - erlang:yield(), - ?assertNot(?R:has_route(<<"t3">>)). - -t_delete_routes() -> - Self = self(), - ?R:add_routes([<<"t1">>,<<"t2">>,<<"t3">>], Self), - ?R:delete_routes([<<"t3">>], Self), - erlang:yield(), %% for delete_routes is cast - ?assertNot(?R:has_route(<<"t3">>)), - ?R:delete_routes([<<"t1">>, <<"t2">>], Self), - erlang:yield(), - ?assertNot(?R:has_route(<<"t2">>)). - -t_route() -> - Self = self(), - Pid = spawn_link(fun() -> timer:sleep(1000) end), - ?R:add_routes([<<"$Q/1">>,<<"t/2">>,<<"t/3">>], Self), - ?R:add_routes([<<"t/2">>], Pid), - Msg1 = #mqtt_message{topic = <<"$Q/1">>, payload = <<"q">>}, - Msg2 = #mqtt_message{topic = <<"t/2">>, payload = <<"t2">>}, - Msg3 = #mqtt_message{topic = <<"t/3">>, payload = <<"t3">>}, - ?R:route(<<"$Q/1">>, Msg1), - ?R:route(<<"t/2">>, Msg2), - ?R:route(<<"t/3">>, Msg3), - ?assertEqual([Msg1, Msg2, Msg3], recv_loop([])), - ?R:add_route(<<"$Q/1">>, Pid), - ?R:route(<<"$Q/1">>, Msg1). - -recv_loop(Msgs) -> - receive - {dispatch, _Topic, Msg} -> - recv_loop([Msg|Msgs]) - after - 500 -> lists:reverse(Msgs) - end. - --endif. - diff --git a/test/emqttd_serializer_tests.erl b/test/emqttd_serializer_tests.erl deleted file mode 100644 index 3a3aee519..000000000 --- a/test/emqttd_serializer_tests.erl +++ /dev/null @@ -1,78 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd_serializer tests. -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_serializer_tests). - --ifdef(TEST). - --include("emqttd_protocol.hrl"). - --include_lib("eunit/include/eunit.hrl"). - --import(emqttd_serializer, [serialize/1]). - -serialize_connect_test() -> - serialize(?CONNECT_PACKET(#mqtt_packet_connect{})). - -serialize_connack_test() -> - ConnAck = #mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, - variable = #mqtt_packet_connack{ack_flags = 0, return_code = 0}}, - ?assertEqual(<<32,2,0,0>>, serialize(ConnAck)). - -serialize_publish_test() -> - serialize(?PUBLISH_PACKET(?QOS_0, <<"Topic">>, undefined, <<"Payload">>)), - serialize(?PUBLISH_PACKET(?QOS_1, <<"Topic">>, 938, <<"Payload">>)). - -serialize_puback_test() -> - serialize(?PUBACK_PACKET(?PUBACK, 10384)). - -serialize_pubrel_test() -> - serialize(?PUBREL_PACKET(10384)). - -serialize_subscribe_test() -> - TopicTable = [{<<"TopicQos0">>, ?QOS_0}, {<<"TopicQos1">>, ?QOS_1}, {<<"TopicQos2">>, ?QOS_2}], - serialize(?SUBSCRIBE_PACKET(10, TopicTable)). - -serialize_suback_test() -> - serialize(?SUBACK_PACKET(10, [?QOS_0, ?QOS_1, 128])). - -serialize_unsubscribe_test() -> - serialize(?UNSUBSCRIBE_PACKET(10, [<<"Topic1">>, <<"Topic2">>])). - -serialize_unsuback_test() -> - serialize(?UNSUBACK_PACKET(10)). - -serialize_pingreq_test() -> - serialize(?PACKET(?PINGREQ)). - -serialize_pingresp_test() -> - serialize(?PACKET(?PINGRESP)). - -serialize_disconnect_test() -> - serialize(?PACKET(?DISCONNECT)). - --endif. - diff --git a/test/emqttd_tests.erl b/test/emqttd_tests.erl deleted file mode 100644 index 5f0685824..000000000 --- a/test/emqttd_tests.erl +++ /dev/null @@ -1,33 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- - --module(emqttd_tests). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - -seed_now_test() -> - ?assertNotEqual(emqttd:seed_now(), emqttd:seed_now()). - --endif. - diff --git a/test/emqttd_topic_SUITE.erl b/test/emqttd_topic_SUITE.erl new file mode 100644 index 000000000..aa79fffa1 --- /dev/null +++ b/test/emqttd_topic_SUITE.erl @@ -0,0 +1,176 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2016 Feng Lee . +%% +%% 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(emqttd_topic_SUITE). + +%% CT +-compile(export_all). + +-import(emqttd_topic, [wildcard/1, match/2, validate/1, triples/1, join/1, + words/1, systop/1, is_queue/1, feed_var/3]). + +-define(N, 10000). + +all() -> [t_wildcard, t_match, t_match2, t_validate, t_triples, t_join, + t_words, t_systop, t_is_queue, t_feed_var, t_sys_match, 't_#_match', + t_sigle_level_validate, t_sigle_level_match, t_match_perf, + t_triples_perf]. + +t_wildcard(_) -> + true = wildcard(<<"a/b/#">>), + true = wildcard(<<"a/+/#">>), + false = wildcard(<<"">>), + false = wildcard(<<"a/b/c">>). + +t_match(_) -> + true = match(<<"a/b/c">>, <<"a/b/+">>), + true = match(<<"a/b/c">>, <<"a/#">>), + true = match(<<"abcd/ef/g">>, <<"#">>), + true = match(<<"abc/de/f">>, <<"abc/de/f">>), + true = match(<<"abc">>, <<"+">>), + true = match(<<"a/b/c">>, <<"a/b/c">>), + false = match(<<"a/b/c">>, <<"a/c/d">>), + false = match(<<"$shared/x/y">>, <<"+">>), + false = match(<<"$shared/x/y">>, <<"+/x/y">>), + false = match(<<"$shared/x/y">>, <<"#">>), + false = match(<<"$shared/x/y">>, <<"+/+/#">>), + false = match(<<"house/1/sensor/0">>, <<"house/+">>), + false = match(<<"house">>, <<"house/+">>). + +t_match2(_) -> + true = match(<<"sport/tennis/player1">>, <<"sport/tennis/player1/#">>), + true = match(<<"sport/tennis/player1/ranking">>, <<"sport/tennis/player1/#">>), + true = match(<<"sport/tennis/player1/score/wimbledon">>, <<"sport/tennis/player1/#">>), + true = match(<<"sport">>, <<"sport/#">>), + true = match(<<"sport">>, <<"#">>), + true = match(<<"/sport/football/score/1">>, <<"#">>), + true = match(<<"Topic/C">>, <<"+/+">>), + true = match(<<"TopicA/B">>, <<"+/+">>), + true = match(<<"TopicA/C">>, <<"+/+">>), + true = match(<<"abc">>, <<"+">>), + true = match(<<"a/b/c">>, <<"a/b/c">>), + false = match(<<"a/b/c">>, <<"a/c/d">>), + false = match(<<"$shared/x/y">>, <<"+">>), + false = match(<<"$shared/x/y">>, <<"+/x/y">>), + false = match(<<"$shared/x/y">>, <<"#">>), + false = match(<<"$shared/x/y">>, <<"+/+/#">>), + false = match(<<"house/1/sensor/0">>, <<"house/+">>). + +t_sigle_level_match(_) -> + true = match(<<"sport/tennis/player1">>, <<"sport/tennis/+">>), + false = match(<<"sport/tennis/player1/ranking">>, <<"sport/tennis/+">>), + false = match(<<"sport">>, <<"sport/+">>), + true = match(<<"sport/">>, <<"sport/+">>), + true = match(<<"/finance">>, <<"+/+">>), + true = match(<<"/finance">>, <<"/+">>), + false = match(<<"/finance">>, <<"+">>). + +t_sys_match(_) -> + true = match(<<"$SYS/broker/clients/testclient">>, <<"$SYS/#">>), + true = match(<<"$SYS/broker">>, <<"$SYS/+">>), + false = match(<<"$SYS/broker">>, <<"+/+">>), + false = match(<<"$SYS/broker">>, <<"#">>). + +'t_#_match'(_) -> + true = match(<<"a/b/c">>, <<"#">>), + true = match(<<"a/b/c">>, <<"+/#">>), + false = match(<<"$SYS/brokers">>, <<"#">>). + +t_match_perf(_) -> + true = match(<<"a/b/ccc">>, <<"a/#">>), + Name = <<"/abkc/19383/192939/akakdkkdkak/xxxyyuya/akakak">>, + Filter = <<"/abkc/19383/+/akakdkkdkak/#">>, + true = match(Name, Filter), + {Time, _} = timer:tc(fun() -> + [match(Name, Filter) || _I <- lists:seq(1, ?N)] + end), + io:format("Time for match: ~p(micro)", [Time/?N]). + +t_validate(_) -> + true = validate({name, <<"abc/de/f">>}), + true = validate({filter, <<"abc/+/f">>}), + true = validate({filter, <<"abc/#">>}), + true = validate({filter, <<"x">>}), + true = validate({name, <<"x//y">>}), + true = validate({filter, <<"sport/tennis/#">>}), + false = validate({name, <<>>}), + false = validate({name, long_topic()}), + false = validate({name, <<"abc/#">>}), + false = validate({filter, <<"abc/#/1">>}), + false = validate({filter, <<"abc/#xzy/+">>}), + false = validate({filter, <<"abc/xzy/+9827">>}), + false = validate({filter, <<"sport/tennis#">>}), + false = validate({filter, <<"sport/tennis/#/ranking">>}). + +t_sigle_level_validate(_) -> + true = validate({filter, <<"+">>}), + true = validate({filter, <<"+/tennis/#">>}), + true = validate({filter, <<"sport/+/player1">>}), + false = validate({filter, <<"sport+">>}). + +t_triples(_) -> + Triples = [{root,<<"a">>,<<"a">>}, + {<<"a">>,<<"b">>,<<"a/b">>}, + {<<"a/b">>,<<"c">>,<<"a/b/c">>}], + Triples = triples(<<"a/b/c">>). + +t_triples_perf(_) -> + Topic = <<"/abkc/19383/192939/akakdkkdkak/xxxyyuya/akakak">>, + {Time, _} = timer:tc(fun() -> + [triples(Topic) || _I <- lists:seq(1, ?N)] + end), + io:format("Time for triples: ~p(micro)", [Time/?N]). + +t_words(_) -> + ['', <<"a">>, '+', '#'] = words(<<"/a/+/#">>), + ['', <<"abkc">>, <<"19383">>, '+', <<"akakdkkdkak">>, '#'] = words(<<"/abkc/19383/+/akakdkkdkak/#">>), + {Time, _} = timer:tc(fun() -> + [words(<<"/abkc/19383/+/akakdkkdkak/#">>) || _I <- lists:seq(1, ?N)] + end), + io:format("Time for words: ~p(micro)", [Time/?N]), + {Time2, _} = timer:tc(fun() -> + [binary:split(<<"/abkc/19383/+/akakdkkdkak/#">>, <<"/">>, [global]) || _I <- lists:seq(1, ?N)] + end), + io:format("Time for binary:split: ~p(micro)", [Time2/?N]). + +t_join(_) -> + <<>> = join([]), + <<"x">> = join([<<"x">>]), + <<"#">> = join(['#']), + <<"+//#">> = join(['+', '', '#']), + <<"x/y/z/+">> = join([<<"x">>, <<"y">>, <<"z">>, '+']), + <<"/ab/cd/ef/">> = join(words(<<"/ab/cd/ef/">>)), + <<"ab/+/#">> = join(words(<<"ab/+/#">>)). + +t_is_queue(_) -> + true = is_queue(<<"$Q/queue">>), + true = is_queue(<<"$q/queue">>), + false = is_queue(<<"xyz/queue">>). + +t_systop(_) -> + SysTop1 = iolist_to_binary(["$SYS/brokers/", atom_to_list(node()), "/xyz"]), + SysTop1 = systop('xyz'), + SysTop2 = iolist_to_binary(["$SYS/brokers/", atom_to_list(node()), "/abc"]), + SysTop2 = systop(<<"abc">>). + +t_feed_var(_) -> + <<"$Q/client/clientId">> = feed_var(<<"$c">>, <<"clientId">>, <<"$Q/client/$c">>), + <<"username/test/client/x">> = feed_var(<<"%u">>, <<"test">>, <<"username/%u/client/x">>), + <<"username/test/client/clientId">> = feed_var(<<"%c">>, <<"clientId">>, <<"username/test/client/%c">>). + +long_topic() -> + iolist_to_binary([[integer_to_list(I), "/"] || I <- lists:seq(0, 10000)]). + diff --git a/test/emqttd_topic_tests.erl b/test/emqttd_topic_tests.erl deleted file mode 100644 index a48fba9c4..000000000 --- a/test/emqttd_topic_tests.erl +++ /dev/null @@ -1,129 +0,0 @@ -%%----------------------------------------------------------------------------- -%% Copyright (c) 2012-2016, Feng Lee -%% -%% Permission is hereby granted, free of charge, to any person obtaining a copy -%% of this software and associated documentation files (the "Software"), to deal -%% in the Software without restriction, including without limitation the rights -%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%% copies of the Software, and to permit persons to whom the Software is -%% furnished to do so, subject to the following conditions: -%% -%% The above copyright notice and this permission notice shall be included in all -%% copies or substantial portions of the Software. -%% -%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%% SOFTWARE. -%%------------------------------------------------------------------------------ --module(emqttd_topic_tests). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - --import(emqttd_topic, [validate/1, wildcard/1, match/2, triples/1, words/1]). - --define(N, 100000). - -validate_test() -> - ?assert( validate({filter, <<"sport/tennis/#">>}) ), - ?assert( validate({filter, <<"a/b/c">>}) ), - ?assert( validate({filter, <<"/a/b">>}) ), - ?assert( validate({filter, <<"/+/x">>}) ), - ?assert( validate({filter, <<"/a/b/c/#">>}) ), - ?assertNot( validate({filter, <<"a/#/c">>}) ), - ?assertNot( validate({filter, <<"sport/tennis#">>}) ), - ?assertNot( validate({filter, <<"sport/tennis/#/ranking">>}) ). - -sigle_level_validate_test() -> - ?assert( validate({filter, <<"+">>}) ), - ?assert( validate({filter, <<"+/tennis/#">>}) ), - ?assertNot( validate({filter, <<"sport+">>}) ), - ?assert( validate({filter, <<"sport/+/player1">>}) ). - -match_test() -> - ?assert( match(<<"sport/tennis/player1">>, <<"sport/tennis/player1/#">>) ), - ?assert( match(<<"sport/tennis/player1/ranking">>, <<"sport/tennis/player1/#">>) ), - ?assert( match(<<"sport/tennis/player1/score/wimbledon">>, <<"sport/tennis/player1/#">>) ), - - ?assert( match(<<"sport">>, <<"sport/#">>) ), - ?assert( match(<<"sport">>, <<"#">>) ), - ?assert( match(<<"/sport/football/score/1">>, <<"#">>) ), - %% paho test - ?assert( match(<<"Topic/C">>, <<"+/+">>) ), - ?assert( match(<<"TopicA/B">>, <<"+/+">>) ), - ?assert( match(<<"TopicA/C">>, <<"+/+">>) ). - -sigle_level_match_test() -> - ?assert( match(<<"sport/tennis/player1">>, <<"sport/tennis/+">>) ), - ?assertNot( match(<<"sport/tennis/player1/ranking">>, <<"sport/tennis/+">>) ), - ?assertNot( match(<<"sport">>, <<"sport/+">>) ), - ?assert( match(<<"sport/">>, <<"sport/+">>) ), - ?assert( match(<<"/finance">>, <<"+/+">>) ), - ?assert( match(<<"/finance">>, <<"/+">>) ), - ?assertNot( match(<<"/finance">>, <<"+">>) ). - -sys_match_test() -> - ?assert( match(<<"$SYS/broker/clients/testclient">>, <<"$SYS/#">>) ), - ?assert( match(<<"$SYS/broker">>, <<"$SYS/+">>) ), - ?assertNot( match(<<"$SYS/broker">>, <<"+/+">>) ), - ?assertNot( match(<<"$SYS/broker">>, <<"#">>) ). - -'#_match_test'() -> - ?assert( match(<<"a/b/c">>, <<"#">>) ), - ?assert( match(<<"a/b/c">>, <<"+/#">>) ), - ?assertNot( match(<<"$SYS/brokers">>, <<"#">>) ). - -match_perf_test() -> - ?assert( match(<<"a/b/ccc">>, <<"a/#">>) ), - Name = <<"/abkc/19383/192939/akakdkkdkak/xxxyyuya/akakak">>, - Filter = <<"/abkc/19383/+/akakdkkdkak/#">>, - ?assert( match(Name, Filter) ), - %?debugFmt("Match ~p with ~p", [Name, Filter]), - {Time, _} = timer:tc(fun() -> - [match(Name, Filter) || _I <- lists:seq(1, ?N)] - end), - ?debugFmt("Time for match: ~p(micro)", [Time/?N]), - ok. - -triples_test() -> - Triples = [{root, <<"a">>, <<"a">>}, {<<"a">>, <<"b">>, <<"a/b">>}], - ?assertMatch(Triples, triples(<<"a/b">>) ). - -triples_perf_test() -> - Topic = <<"/abkc/19383/192939/akakdkkdkak/xxxyyuya/akakak">>, - {Time, _} = timer:tc(fun() -> - [triples(Topic) || _I <- lists:seq(1, ?N)] - end), - ?debugFmt("Time for triples: ~p(micro)", [Time/?N]), - ok. - -type_test() -> - ?assertEqual(false, wildcard(<<"/a/b/cdkd">>)), - ?assertEqual(true, wildcard(<<"/a/+/d">>)), - ?assertEqual(true, wildcard(<<"/a/b/#">>)). - -words_test() -> - ?assertMatch(['', <<"abkc">>, <<"19383">>, '+', <<"akakdkkdkak">>, '#'], words(<<"/abkc/19383/+/akakdkkdkak/#">>)), - {Time, _} = timer:tc(fun() -> - [words(<<"/abkc/19383/+/akakdkkdkak/#">>) || _I <- lists:seq(1, ?N)] - end), - ?debugFmt("Time for words: ~p(micro)", [Time/?N]), - {Time2, _} = timer:tc(fun() -> - [binary:split(<<"/abkc/19383/+/akakdkkdkak/#">>, <<"/">>, [global]) || _I <- lists:seq(1, ?N)] - end), - ?debugFmt("Time for binary:split: ~p(micro)", [Time2/?N]), - ok. - -feed_var_test() -> - ?assertEqual(<<"$Q/client/clientId">>, emqttd_topic:feed_var(<<"$c">>, <<"clientId">>, <<"$Q/client/$c">>)). - -join_test() -> - ?assertEqual(<<"/ab/cd/ef/">>, emqttd_topic:join(words(<<"/ab/cd/ef/">>))), - ?assertEqual(<<"ab/+/#">>, emqttd_topic:join(words(<<"ab/+/#">>))). - --endif. diff --git a/test/emqttd_trie_SUITE.erl b/test/emqttd_trie_SUITE.erl new file mode 100644 index 000000000..7ee9d6616 --- /dev/null +++ b/test/emqttd_trie_SUITE.erl @@ -0,0 +1,133 @@ +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(emqttd_trie_SUITE). + +-compile(export_all). + +-include("emqttd_trie.hrl"). + +-define(TRIE, emqttd_trie). + +all() -> + [t_insert, t_match, t_match2, t_match3, t_delete, t_delete2, t_delete3]. + +init_per_suite(Config) -> + emqttd_mnesia:ensure_started(), + ?TRIE:mnesia(boot), + ?TRIE:mnesia(copy), + Config. + +end_per_suite(_Config) -> + emqttd_mnesia:ensure_stopped(), + emqttd_mnesia:delete_schema(). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + clear_tables(). + +t_insert(_) -> + TN = #trie_node{node_id = <<"sensor">>, + edge_count = 3, + topic = <<"sensor">>, + flags = undefined}, + {atomic, [TN]} = mnesia:transaction( + fun() -> + ?TRIE:insert(<<"sensor/1/metric/2">>), + ?TRIE:insert(<<"sensor/+/#">>), + ?TRIE:insert(<<"sensor/#">>), + ?TRIE:insert(<<"sensor">>), + ?TRIE:insert(<<"sensor">>), + ?TRIE:lookup(<<"sensor">>) + end). + +t_match(_) -> + Machted = [<<"sensor/+/#">>, <<"sensor/#">>], + {atomic, Machted} = mnesia:transaction( + fun() -> + ?TRIE:insert(<<"sensor/1/metric/2">>), + ?TRIE:insert(<<"sensor/+/#">>), + ?TRIE:insert(<<"sensor/#">>), + ?TRIE:match(<<"sensor/1">>) + end). + +t_match2(_) -> + Matched = {[<<"+/+/#">>, <<"+/#">>, <<"#">>], []}, + {atomic, Matched} = mnesia:transaction( + fun() -> + ?TRIE:insert(<<"#">>), + ?TRIE:insert(<<"+/#">>), + ?TRIE:insert(<<"+/+/#">>), + {?TRIE:match(<<"a/b/c">>), + ?TRIE:match(<<"$SYS/broker/zenmq">>)} + end). + +t_match3(_) -> + Topics = [<<"d/#">>, <<"a/b/c">>, <<"a/b/+">>, <<"a/#">>, <<"#">>, <<"$SYS/#">>], + mnesia:transaction(fun() -> [emqttd_trie:insert(Topic) || Topic <- Topics] end), + Matched = mnesia:async_dirty(fun emqttd_trie:match/1, [<<"a/b/c">>]), + 4 = length(Matched), + SysMatched = mnesia:async_dirty(fun emqttd_trie:match/1, [<<"$SYS/a/b/c">>]), + [<<"$SYS/#">>] = SysMatched. + +t_delete(_) -> + TN = #trie_node{node_id = <<"sensor/1">>, + edge_count = 2, + topic = undefined, + flags = undefined}, + {atomic, [TN]} = mnesia:transaction( + fun() -> + ?TRIE:insert(<<"sensor/1/#">>), + ?TRIE:insert(<<"sensor/1/metric/2">>), + ?TRIE:insert(<<"sensor/1/metric/3">>), + ?TRIE:delete(<<"sensor/1/metric/2">>), + ?TRIE:delete(<<"sensor/1/metric">>), + ?TRIE:delete(<<"sensor/1/metric">>), + ?TRIE:lookup(<<"sensor/1">>) + end). + +t_delete2(_) -> + {atomic, {[], []}} = mnesia:transaction( + fun() -> + ?TRIE:insert(<<"sensor">>), + ?TRIE:insert(<<"sensor/1/metric/2">>), + ?TRIE:insert(<<"sensor/1/metric/3">>), + ?TRIE:delete(<<"sensor">>), + ?TRIE:delete(<<"sensor/1/metric/2">>), + ?TRIE:delete(<<"sensor/1/metric/3">>), + {?TRIE:lookup(<<"sensor">>), + ?TRIE:lookup(<<"sensor/1">>)} + end). + +t_delete3(_) -> + {atomic, {[], []}} = mnesia:transaction( + fun() -> + ?TRIE:insert(<<"sensor/+">>), + ?TRIE:insert(<<"sensor/+/metric/2">>), + ?TRIE:insert(<<"sensor/+/metric/3">>), + ?TRIE:delete(<<"sensor/+/metric/2">>), + ?TRIE:delete(<<"sensor/+/metric/3">>), + ?TRIE:delete(<<"sensor">>), + ?TRIE:delete(<<"sensor/+">>), + ?TRIE:delete(<<"sensor/+/unknown">>), + {?TRIE:lookup(<<"sensor">>), ?TRIE:lookup(<<"sensor/+">>)} + end). + +clear_tables() -> + lists:foreach(fun mnesia:clear_table/1, [trie, trie_node]). + diff --git a/test/emqttd_trie_tests.erl b/test/emqttd_trie_tests.erl deleted file mode 100644 index f7de97d43..000000000 --- a/test/emqttd_trie_tests.erl +++ /dev/null @@ -1,41 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- - --module(emqttd_trie_tests). - --include("emqttd.hrl"). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - -match_test() -> - mnesia:start(), - emqttd_trie:mnesia(boot), - Topics = [<<"d/#">>, <<"a/b/c">>, <<"a/b/+">>, <<"a/#">>, <<"#">>, <<"$SYS/#">>], - mnesia:transaction(fun() -> [emqttd_trie:insert(Topic) || Topic <- Topics] end), - Matched = mnesia:async_dirty(fun emqttd_trie:match/1, [<<"a/b/c">>]), - ?assertEqual(4, length(Matched)), - SysMatched = mnesia:async_dirty(fun emqttd_trie:match/1, [<<"$SYS/a/b/c">>]), - ?assertEqual([<<"$SYS/#">>], SysMatched). - --endif. diff --git a/test/esockd_access.erl b/test/esockd_access.erl index 1c29d5357..8fcafed4b 100644 --- a/test/esockd_access.erl +++ b/test/esockd_access.erl @@ -1,31 +1,19 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2014-2015, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% esockd access control. -%%% -%%% CIDR: Classless Inter-Domain Routing -%%% -%%% @end -%%%----------------------------------------------------------------------------- +%%-------------------------------------------------------------------- +%% Copyright (c) 2012-2016 Feng Lee . +%% +%% 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(esockd_access). -type cidr() :: string(). diff --git a/test/priority_queue_tests.erl b/test/priority_queue_tests.erl deleted file mode 100644 index b2189a971..000000000 --- a/test/priority_queue_tests.erl +++ /dev/null @@ -1,78 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2016, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- - --module(priority_queue_tests). - --include("emqttd.hrl"). - --ifdef(TEST). - --include_lib("eunit/include/eunit.hrl"). - --define(PQ, priority_queue). - -plen_test() -> - Q = ?PQ:new(), - ?assertEqual(0, ?PQ:plen(0, Q)), - Q0 = ?PQ:in(z, Q), - ?assertEqual(1, ?PQ:plen(0, Q0)), - Q1 = ?PQ:in(x, 1, Q0), - ?assertEqual(1, ?PQ:plen(1, Q1)), - Q2 = ?PQ:in(y, 2, Q1), - ?assertEqual(1, ?PQ:plen(2, Q2)), - Q3 = ?PQ:in(z, 2, Q2), - ?assertEqual(2, ?PQ:plen(2, Q3)), - {_, Q4} = ?PQ:out(1, Q3), - ?assertEqual(0, ?PQ:plen(1, Q4)), - {_, Q5} = ?PQ:out(Q4), - ?assertEqual(1, ?PQ:plen(2, Q5)), - {_, Q6} = ?PQ:out(Q5), - ?assertEqual(0, ?PQ:plen(2, Q6)), - ?assertEqual(1, ?PQ:len(Q6)), - {_, Q7} = ?PQ:out(Q6), - ?assertEqual(0, ?PQ:len(Q7)). - -out2_test() -> - Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}], - Q = ?PQ:new(), - Q0 = lists:foldl( - fun({El, P}, Q) -> - ?PQ:in(El, P, Q); - (El, Q) -> - ?PQ:in(El, Q) - end, Q, Els), - {Val, Q1} = ?PQ:out(Q0), - ?assertEqual({value, d}, Val), - {Val1, Q2} = ?PQ:out(2, Q1), - ?assertEqual({value, e}, Val1), - {Val2, Q3} = ?PQ:out(1, Q2), - ?assertEqual({value, b}, Val2), - {Val3, Q4} = ?PQ:out(Q3), - ?assertEqual({value, f}, Val3), - {Val4, Q5} = ?PQ:out(Q4), - ?assertEqual({value, c}, Val4), - {Val5, Q6} = ?PQ:out(Q5), - ?assertEqual({value, a}, Val5), - {empty, _Q7} = ?PQ:out(Q6). - --endif. -