This commit is contained in:
commit
1dcf892a69
|
@ -16,3 +16,4 @@ plugins/*/ebin
|
||||||
log/
|
log/
|
||||||
*.swp
|
*.swp
|
||||||
*.so
|
*.so
|
||||||
|
examples
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[submodule "tests/org.eclipse.paho.mqtt.testing"]
|
[submodule "plugins/emqttd_dashboard"]
|
||||||
path = tests/org.eclipse.paho.mqtt.testing
|
path = plugins/emqttd_dashboard
|
||||||
url = git://git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.testing.git
|
url = https://github.com/emqtt/emqttd_dashboard.git
|
||||||
|
|
62
CHANGELOG.md
62
CHANGELOG.md
|
@ -2,12 +2,72 @@
|
||||||
emqttd ChangeLog
|
emqttd ChangeLog
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
0.9.0-alpha (2015-06-14)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Session and Queue
|
||||||
|
|
||||||
|
Merge emqtt, emqttd apps
|
||||||
|
|
||||||
|
Move apps/emqttd/src src
|
||||||
|
|
||||||
|
Session
|
||||||
|
|
||||||
|
Queue
|
||||||
|
|
||||||
|
Alarm
|
||||||
|
|
||||||
|
Protocol Compliant
|
||||||
|
|
||||||
|
|
||||||
|
0.8.6-beta (2015-06-17)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Bugfix: issue #175 - publish Will message when websocket is closed without 'DISCONNECT' packet
|
||||||
|
|
||||||
|
|
||||||
|
0.8.5-beta (2015-06-10)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Bugfix: issue #53 - client will receive duplicate messages when overlapping subscription
|
||||||
|
|
||||||
|
|
||||||
|
0.8.4-beta (2015-06-08)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Bugfix: issue #165 - duplicated message when publish 'retained' message to persistent client
|
||||||
|
|
||||||
|
|
||||||
|
0.8.3-beta (2015-06-05)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Bugfix: issue #158 - should queue:in new message after old one dropped
|
||||||
|
|
||||||
|
Bugfix: issue #155 - emqtt_parser.erl: parse_topics/3 should reverse topics
|
||||||
|
|
||||||
|
Bugfix: issue #149 - Forget to merge plugins/emqttd_auth_mysql from 'dev' branch to 'master' in 0.8.x release
|
||||||
|
|
||||||
|
|
||||||
|
0.8.2-alpha (2015-06-01)
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Bugfix: issue #147 - WebSocket client cannot subscribe queue '$Q/queue/${clientId}'
|
||||||
|
|
||||||
|
Bugfix: issue #146 - emqttd_auth_ldap: fill(Username, UserDn) is not right
|
||||||
|
|
||||||
|
|
||||||
0.8.1-alpha (2015-05-28)
|
0.8.1-alpha (2015-05-28)
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
Client [Presence](https://github.com/emqtt/emqttd/wiki/Presence) Support and [$SYS Topics](https://github.com/emqtt/emqttd/wiki/$SYS-Topics) Redesigned!
|
||||||
|
|
||||||
Bugfix: issue #138 - when client disconnected normally, broker will not publish disconnected $SYS message
|
Bugfix: issue #138 - when client disconnected normally, broker will not publish disconnected $SYS message
|
||||||
|
|
||||||
Improve: issue #136 - $SYS topics result should not include $SYS messages
|
Bugfix: fix websocket url in emqttd/priv/www/websocket.html
|
||||||
|
|
||||||
|
Improve: etc/emqttd.config to allow websocket connections from any hosts
|
||||||
|
|
||||||
|
Improve: rel/reltool.config to exclude unnecessary apps.
|
||||||
|
|
||||||
|
|
||||||
0.8.0-alpha (2015-05-25)
|
0.8.0-alpha (2015-05-25)
|
||||||
|
|
22
README.md
22
README.md
|
@ -37,6 +37,16 @@ emqttd is aimed to provide a solid, enterprise grade, extensible open-source MQT
|
||||||
* Passed eclipse paho interoperability tests
|
* Passed eclipse paho interoperability tests
|
||||||
|
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
* [emqttd_auth_clientid](https://github.com/emqtt/emqttd/wiki/Authentication) - Authentication with ClientIds
|
||||||
|
* emqttd_auth_mysql - Authentication with MySQL
|
||||||
|
* emqttd_auth_ldap - Authentication with LDAP
|
||||||
|
* emqttd_mod_autosub - Subscribe some topics automatically when client connected
|
||||||
|
* [emqttd_mod_presence](https://github.com/emqtt/emqttd/wiki/Presence) - Publish presence message to $SYS topics when client connected or disconnected
|
||||||
|
* [emqttd_mod_rewrite](https://github.com/emqtt/emqttd/wiki/Rewrite) - Topics rewrite like HTTP rewrite module
|
||||||
|
|
||||||
|
|
||||||
## Design
|
## Design
|
||||||
|
|
||||||

|

|
||||||
|
@ -92,12 +102,12 @@ The MIT License (MIT)
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
[@hejin1026](https://github.com/hejin1026)
|
* [@callbay](https://github.com/callbay)
|
||||||
[@desoulter](https://github.com/desoulter)
|
* [@hejin1026](https://github.com/hejin1026)
|
||||||
[@turtleDeng](https://github.com/turtleDeng)
|
* [@desoulter](https://github.com/desoulter)
|
||||||
[@Hades32](https://github.com/Hades32)
|
* [@turtleDeng](https://github.com/turtleDeng)
|
||||||
[@huangdan](https://github.com/huangdan)
|
* [@Hades32](https://github.com/Hades32)
|
||||||
|
* [@huangdan](https://github.com/huangdan)
|
||||||
|
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
63
TODO
63
TODO
|
@ -1,63 +0,0 @@
|
||||||
|
|
||||||
v0.9.0-alpha (2015-05-30)
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Presence Management....
|
|
||||||
|
|
||||||
Dashboard
|
|
||||||
|
|
||||||
Presence Management....
|
|
||||||
|
|
||||||
|
|
||||||
v0.8.0-alpha (2015-05-10)
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Force Subscriptions...
|
|
||||||
|
|
||||||
Documents...
|
|
||||||
|
|
||||||
MySQL Auth
|
|
||||||
|
|
||||||
AMQP
|
|
||||||
|
|
||||||
Bridge Test
|
|
||||||
|
|
||||||
|
|
||||||
0.8.0 (2015-05-10)
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Force Subscription...
|
|
||||||
|
|
||||||
Point2Point Queue...
|
|
||||||
|
|
||||||
MySQL Auth
|
|
||||||
|
|
||||||
LDAP Auth
|
|
||||||
|
|
||||||
PG Auth
|
|
||||||
|
|
||||||
MySQL ACL
|
|
||||||
|
|
||||||
Retained Message...
|
|
||||||
|
|
||||||
Tsung MQTT Test
|
|
||||||
|
|
||||||
|
|
||||||
0.7.1-alpha (2015-05-05)
|
|
||||||
-------------------------
|
|
||||||
|
|
||||||
Bugfix
|
|
||||||
|
|
||||||
Admin Dashboard
|
|
||||||
|
|
||||||
one million connections test...
|
|
||||||
|
|
||||||
topic match benchmark tests...
|
|
||||||
|
|
||||||
Dialyzer ...
|
|
||||||
|
|
||||||
full test cases...
|
|
||||||
|
|
||||||
README.md: add "Supports and Contact" include IRC, mailling-list, email.
|
|
||||||
|
|
||||||
Document
|
|
|
@ -1,69 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% @Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>
|
|
||||||
%%%
|
|
||||||
%%% 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 Common Header.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Protocol Version and Levels
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(MQTT_PROTO_V31, 3).
|
|
||||||
-define(MQTT_PROTO_V311, 4).
|
|
||||||
|
|
||||||
-define(PROTOCOL_NAMES, [
|
|
||||||
{?MQTT_PROTO_V31, <<"MQIsdp">>},
|
|
||||||
{?MQTT_PROTO_V311, <<"MQTT">>}]).
|
|
||||||
|
|
||||||
-type mqtt_vsn() :: ?MQTT_PROTO_V31 | ?MQTT_PROTO_V311.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% QoS Levels
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-define(QOS_0, 0).
|
|
||||||
-define(QOS_1, 1).
|
|
||||||
-define(QOS_2, 2).
|
|
||||||
|
|
||||||
-define(IS_QOS(I), (I >= ?QOS_0 andalso I =< ?QOS_2)).
|
|
||||||
|
|
||||||
-type mqtt_qos() :: ?QOS_0 | ?QOS_1 | ?QOS_2.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Message
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-type mqtt_msgid() :: undefined | 1..16#ffff.
|
|
||||||
|
|
||||||
-record(mqtt_message, {
|
|
||||||
%% topic is first for message may be retained
|
|
||||||
topic :: binary(),
|
|
||||||
qos = ?QOS_0 :: mqtt_qos(),
|
|
||||||
retain = false :: boolean(),
|
|
||||||
dup = false :: boolean(),
|
|
||||||
msgid :: mqtt_msgid(),
|
|
||||||
payload :: binary()
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_message() :: #mqtt_message{}.
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{application, emqtt,
|
|
||||||
[
|
|
||||||
{description, "Erlang MQTT Common Library"},
|
|
||||||
{vsn, "0.8.0"},
|
|
||||||
{modules, []},
|
|
||||||
{registered, []},
|
|
||||||
{applications, [
|
|
||||||
kernel,
|
|
||||||
stdlib
|
|
||||||
]},
|
|
||||||
{env, []}
|
|
||||||
]}.
|
|
|
@ -1,112 +0,0 @@
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
|
||||||
%%
|
|
||||||
%% 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
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Banner
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(COPYRIGHT, "Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-define(LICENSE_MESSAGE, "Licensed under MIT").
|
|
||||||
|
|
||||||
-define(PROTOCOL_VERSION, "MQTT/3.1.1").
|
|
||||||
|
|
||||||
-define(ERTS_MINIMUM, "6.0").
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% PubSub
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-type pubsub() :: publish | subscribe.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Topic
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(mqtt_topic, {
|
|
||||||
topic :: binary(),
|
|
||||||
node :: node()
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_topic() :: #mqtt_topic{}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Subscriber
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(mqtt_subscriber, {
|
|
||||||
topic :: binary(),
|
|
||||||
qos = 0 :: 0 | 1 | 2,
|
|
||||||
pid :: pid()
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_subscriber() :: #mqtt_subscriber{}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% P2P Queue Subscriber
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(mqtt_queue, {
|
|
||||||
name :: binary(),
|
|
||||||
subpid :: pid(),
|
|
||||||
qos = 0 :: 0 | 1 | 2
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_queue() :: #mqtt_queue{}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Client
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(mqtt_client, {
|
|
||||||
clientid :: binary(),
|
|
||||||
username :: binary() | undefined,
|
|
||||||
ipaddr :: inet:ip_address()
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_client() :: #mqtt_client{}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Session
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(mqtt_session, {
|
|
||||||
clientid,
|
|
||||||
session_pid,
|
|
||||||
subscriptions = [],
|
|
||||||
awaiting_ack,
|
|
||||||
awaiting_rel
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_session() :: #mqtt_session{}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Plugin
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(mqtt_plugin, {
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
attrs,
|
|
||||||
description
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type mqtt_plugin() :: #mqtt_plugin{}.
|
|
||||||
|
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% @Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>
|
|
||||||
%%%
|
|
||||||
%%% 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 System Topics.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
-define(SYSTOP, <<"$SYS">>).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% $SYS Topics of Broker
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(SYSTOP_BROKERS, [
|
|
||||||
version, % Broker version
|
|
||||||
uptime, % Broker uptime
|
|
||||||
datetime, % Broker local datetime
|
|
||||||
sysdescr % Broker description
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% $SYS Topics for Clients
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(SYSTOP_CLIENTS, [
|
|
||||||
'clients/count', % clients connected current
|
|
||||||
'clients/max' % max clients connected
|
|
||||||
%'clients/connected',
|
|
||||||
%'clients/disconnected',
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% $SYS Topics for Sessions
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(SYSTOP_SESSIONS, [
|
|
||||||
'sessions/count',
|
|
||||||
'sessions/max'
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% $SYS Topics for Subscribers
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(SYSTOP_PUBSUB, [
|
|
||||||
'queues/count', % ...
|
|
||||||
'queues/max', % ...
|
|
||||||
'topics/count', % ...
|
|
||||||
'topics/max', % ...
|
|
||||||
'subscribers/count', % ...
|
|
||||||
'subscribers/max' % ...
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Bytes sent and received of Broker
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(SYSTOP_BYTES, [
|
|
||||||
{counter, 'bytes/received'}, % Total bytes received
|
|
||||||
{counter, 'bytes/sent'} % Total bytes sent
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Packets sent and received of Broker
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(SYSTOP_PACKETS, [
|
|
||||||
{counter, 'packets/received'}, % All Packets received
|
|
||||||
{counter, 'packets/sent'}, % All Packets sent
|
|
||||||
{counter, 'packets/connect'}, % CONNECT Packets received
|
|
||||||
{counter, 'packets/connack'}, % CONNACK Packets sent
|
|
||||||
{counter, 'packets/publish/received'}, % PUBLISH packets received
|
|
||||||
{counter, 'packets/publish/sent'}, % PUBLISH packets sent
|
|
||||||
{counter, 'packets/subscribe'}, % SUBSCRIBE Packets received
|
|
||||||
{counter, 'packets/suback'}, % SUBACK packets sent
|
|
||||||
{counter, 'packets/unsubscribe'}, % UNSUBSCRIBE Packets received
|
|
||||||
{counter, 'packets/unsuback'}, % UNSUBACK Packets sent
|
|
||||||
{counter, 'packets/pingreq'}, % PINGREQ packets received
|
|
||||||
{counter, 'packets/pingresp'}, % PINGRESP Packets sent
|
|
||||||
{counter, 'packets/disconnect'} % DISCONNECT Packets received
|
|
||||||
]).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Messages sent and received of broker
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-define(SYSTOP_MESSAGES, [
|
|
||||||
{counter, 'messages/received'}, % Messages received
|
|
||||||
{counter, 'messages/sent'}, % Messages sent
|
|
||||||
{gauge, 'messages/retained/count'},% Messagea retained
|
|
||||||
{gauge, 'messages/stored/count'}, % Messages stored
|
|
||||||
{counter, 'messages/dropped'} % Messages dropped
|
|
||||||
]).
|
|
||||||
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2012-2015, Feng Lee <feng@emqtt.io>
|
|
||||||
%%
|
|
||||||
%% 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 topic header.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Topic
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(topic, {
|
|
||||||
name :: binary(),
|
|
||||||
node :: node()
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type topic() :: #topic{}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% MQTT Topic Subscriber
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-record(topic_subscriber, {
|
|
||||||
topic :: binary(),
|
|
||||||
qos = 0 :: 0 | 1 | 2,
|
|
||||||
subpid :: pid()
|
|
||||||
}).
|
|
||||||
|
|
||||||
-type topic_subscriber() :: #topic_subscriber{}.
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>MQTT over WebSocket</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>MQTT Over WebSocket</h1>
|
|
||||||
<div id="connect">
|
|
||||||
<button id="btnConn">Connect</button>
|
|
||||||
State: <span id="connstate" style="font-weight:bold;"></span>
|
|
||||||
</div>
|
|
||||||
<script src="./mqttws31.js"></script>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
var ws;
|
|
||||||
if (!window.WebSocket) {
|
|
||||||
alert("WebSocket not supported by this browser");
|
|
||||||
}
|
|
||||||
function $(id) {
|
|
||||||
return document.getElementById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a client instance
|
|
||||||
client = new Paho.MQTT.Client(location.hostname, Number(location.port), "/mqtt", "clientId");
|
|
||||||
|
|
||||||
// set callback handlers
|
|
||||||
client.onConnectionLost = onConnectionLost;
|
|
||||||
client.onMessageArrived = onMessageArrived;
|
|
||||||
|
|
||||||
function go() {
|
|
||||||
// connect the client
|
|
||||||
client.connect({onSuccess:onConnect});
|
|
||||||
}
|
|
||||||
|
|
||||||
// called when the client connects
|
|
||||||
function onConnect() {
|
|
||||||
alert("connected"),
|
|
||||||
$('connstate').innerHTML = 'CONNECTED';
|
|
||||||
// Once a connection has been made, make a subscription and send a message.
|
|
||||||
console.log("onConnect");
|
|
||||||
client.subscribe("/World");
|
|
||||||
message = new Paho.MQTT.Message("Hello");
|
|
||||||
message.destinationName = "/World";
|
|
||||||
client.send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// called when the client loses its connection
|
|
||||||
function onConnectionLost(responseObject) {
|
|
||||||
if (responseObject.errorCode !== 0) {
|
|
||||||
console.log("onConnectionLost:"+responseObject.errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// called when a message arrives
|
|
||||||
function onMessageArrived(message) {
|
|
||||||
console.log("onMessageArrived:"+message.payloadString);
|
|
||||||
}
|
|
||||||
|
|
||||||
$('btnConn').onclick = function(event) {
|
|
||||||
go(); return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,59 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>MQTT Over Mochiweb websocket</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>MQTT Over Mochiweb websocket</h1>
|
|
||||||
|
|
||||||
<div id="connect">
|
|
||||||
<button id="btnConn">Connect</button>
|
|
||||||
State: <span id="connstate" style="font-weight:bold;"></span>
|
|
||||||
</div>
|
|
||||||
<br/><i>Protip: open your javascript error console, just in case..</i><br/>
|
|
||||||
<hr/>
|
|
||||||
<div id="connected">
|
|
||||||
<form id="sendForm">
|
|
||||||
<input id="phrase" type="text"/>
|
|
||||||
<input id="btnSend" class="button" type="submit" name="connect"
|
|
||||||
value="Send"/>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
<div id="msgs"></div>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var ws;
|
|
||||||
if (!window.WebSocket) {
|
|
||||||
alert("WebSocket not supported by this browser");
|
|
||||||
}
|
|
||||||
function $(id) {
|
|
||||||
return document.getElementById(id);
|
|
||||||
}
|
|
||||||
function go() {
|
|
||||||
ws = new WebSocket("ws://" + location.host + "/mqtt/wsocket");
|
|
||||||
ws.onopen = function () {
|
|
||||||
$('connstate').innerHTML = 'CONNECTED';
|
|
||||||
}
|
|
||||||
ws.onclose = function () {
|
|
||||||
$('connstate').innerHTML = 'CLOSED';
|
|
||||||
}
|
|
||||||
ws.onmessage = function (e) {
|
|
||||||
var p = document.createElement('pre');
|
|
||||||
p.appendChild(document.createTextNode(e.data));
|
|
||||||
$('msgs').appendChild(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('sendForm').onsubmit = function (event) {
|
|
||||||
var p = $('phrase');
|
|
||||||
ws.send(p.value);
|
|
||||||
p.value='';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
$('btnConn').onclick = function(event) {
|
|
||||||
go(); return false;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 cluster to monitor clusted nodes.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_cluster).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-export([running_nodes/0]).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Get running nodes
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
running_nodes() ->
|
|
||||||
mnesia:system_info(running_db_nodes).
|
|
||||||
|
|
|
@ -1,158 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_cm).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-behaviour(gen_server).
|
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
|
||||||
|
|
||||||
%% API Exports
|
|
||||||
-export([start_link/2, pool/0, table/0]).
|
|
||||||
|
|
||||||
-export([lookup/1, register/1, unregister/1]).
|
|
||||||
|
|
||||||
%% gen_server Function Exports
|
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
||||||
terminate/2, code_change/3]).
|
|
||||||
|
|
||||||
-record(state, {id, tab, statsfun}).
|
|
||||||
|
|
||||||
-define(CM_POOL, cm_pool).
|
|
||||||
|
|
||||||
-define(CLIENT_TAB, mqtt_client).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% API
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Start client manager
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec start_link(Id, StatsFun) -> {ok, pid()} | ignore | {error, any()} when
|
|
||||||
Id :: pos_integer(),
|
|
||||||
StatsFun :: fun().
|
|
||||||
start_link(Id, StatsFun) ->
|
|
||||||
gen_server:start_link(?MODULE, [Id, StatsFun], []).
|
|
||||||
|
|
||||||
pool() -> ?CM_POOL.
|
|
||||||
|
|
||||||
table() -> ?CLIENT_TAB.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Lookup client pid with clientId
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec lookup(ClientId :: binary()) -> pid() | undefined.
|
|
||||||
lookup(ClientId) when is_binary(ClientId) ->
|
|
||||||
case ets:lookup(?CLIENT_TAB, ClientId) of
|
|
||||||
[{_, Pid, _}] -> Pid;
|
|
||||||
[] -> undefined
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Register clientId with pid.
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec register(ClientId :: binary()) -> ok.
|
|
||||||
register(ClientId) when is_binary(ClientId) ->
|
|
||||||
CmPid = gproc_pool:pick_worker(?CM_POOL, ClientId),
|
|
||||||
gen_server:call(CmPid, {register, ClientId, self()}, infinity).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Unregister clientId with pid.
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec unregister(ClientId :: binary()) -> ok.
|
|
||||||
unregister(ClientId) when is_binary(ClientId) ->
|
|
||||||
CmPid = gproc_pool:pick_worker(?CM_POOL, ClientId),
|
|
||||||
gen_server:cast(CmPid, {unregister, ClientId, self()}).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% gen_server callbacks
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
init([Id, StatsFun]) ->
|
|
||||||
gproc_pool:connect_worker(?CM_POOL, {?MODULE, Id}),
|
|
||||||
{ok, #state{id = Id, statsfun = StatsFun}}.
|
|
||||||
|
|
||||||
handle_call({register, ClientId, Pid}, _From, State) ->
|
|
||||||
case ets:lookup(?CLIENT_TAB, ClientId) of
|
|
||||||
[{_, Pid, _}] ->
|
|
||||||
lager:error("clientId '~s' has been registered with ~p", [ClientId, Pid]),
|
|
||||||
ignore;
|
|
||||||
[{_, OldPid, MRef}] ->
|
|
||||||
lager:error("clientId '~s' is duplicated: pid=~p, oldpid=~p", [ClientId, Pid, OldPid]),
|
|
||||||
OldPid ! {stop, duplicate_id, Pid},
|
|
||||||
erlang:demonitor(MRef),
|
|
||||||
ets:insert(?CLIENT_TAB, {ClientId, Pid, erlang:monitor(process, Pid)});
|
|
||||||
[] ->
|
|
||||||
ets:insert(?CLIENT_TAB, {ClientId, Pid, erlang:monitor(process, Pid)})
|
|
||||||
end,
|
|
||||||
{reply, ok, setstats(State)};
|
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
|
||||||
lager:error("unexpected request: ~p", [Req]),
|
|
||||||
{reply, {error, badreq}, State}.
|
|
||||||
|
|
||||||
handle_cast({unregister, ClientId, Pid}, State) ->
|
|
||||||
case ets:lookup(?CLIENT_TAB, ClientId) of
|
|
||||||
[{_, Pid, MRef}] ->
|
|
||||||
erlang:demonitor(MRef, [flush]),
|
|
||||||
ets:delete(?CLIENT_TAB, ClientId);
|
|
||||||
[_] ->
|
|
||||||
ignore;
|
|
||||||
[] ->
|
|
||||||
lager:error("cannot find clientId '~s' with ~p", [ClientId, Pid])
|
|
||||||
end,
|
|
||||||
{noreply, setstats(State)};
|
|
||||||
|
|
||||||
handle_cast(_Msg, State) ->
|
|
||||||
{noreply, State}.
|
|
||||||
|
|
||||||
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
|
|
||||||
ets:match_delete(?CLIENT_TAB, {'_', DownPid, MRef}),
|
|
||||||
{noreply, setstats(State)};
|
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
|
||||||
{noreply, State}.
|
|
||||||
|
|
||||||
terminate(_Reason, #state{id = Id}) ->
|
|
||||||
gproc_pool:disconnect_worker(?CM_POOL, {?MODULE, Id}), ok.
|
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Internal functions
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
setstats(State = #state{statsfun = StatsFun}) ->
|
|
||||||
StatsFun(ets:info(?CLIENT_TAB, size)), State.
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 client manager supervisor.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_cm_sup).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-behaviour(supervisor).
|
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([start_link/0]).
|
|
||||||
|
|
||||||
%% Supervisor callbacks
|
|
||||||
-export([init/1]).
|
|
||||||
|
|
||||||
start_link() ->
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
ets:new(emqttd_cm:table(), [set, named_table, public,
|
|
||||||
{write_concurrency, true}]),
|
|
||||||
Schedulers = erlang:system_info(schedulers),
|
|
||||||
gproc_pool:new(emqttd_cm:pool(), hash, [{size, Schedulers}]),
|
|
||||||
StatsFun = emqttd_stats:statsfun('clients/count', 'clients/max'),
|
|
||||||
Children = lists:map(
|
|
||||||
fun(I) ->
|
|
||||||
Name = {emqttd_cm, I},
|
|
||||||
gproc_pool:add_worker(emqttd_cm:pool(), Name, I),
|
|
||||||
{Name, {emqttd_cm, start_link, [I, StatsFun]},
|
|
||||||
permanent, 10000, worker, [emqttd_cm]}
|
|
||||||
end, lists:seq(1, Schedulers)),
|
|
||||||
{ok, {{one_for_all, 10, 100}, Children}}.
|
|
||||||
|
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 event manager.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_event).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
|
||||||
|
|
||||||
%% API Function Exports
|
|
||||||
-export([start_link/0, add_handler/2, notify/1]).
|
|
||||||
|
|
||||||
%% gen_event Function Exports
|
|
||||||
-export([init/1, handle_event/2, handle_call/2, handle_info/2,
|
|
||||||
terminate/2, code_change/3]).
|
|
||||||
|
|
||||||
-record(state, {systop}).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Start event manager
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec start_link() -> {ok, pid()} | {error, any()}.
|
|
||||||
start_link() ->
|
|
||||||
case gen_event:start_link({local, ?MODULE}) of
|
|
||||||
{ok, Pid} ->
|
|
||||||
add_handler(?MODULE, []),
|
|
||||||
{ok, Pid};
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
add_handler(Handler, Args) ->
|
|
||||||
gen_event:add_handler(?MODULE, Handler, Args).
|
|
||||||
|
|
||||||
notify(Event) ->
|
|
||||||
gen_event:notify(?MODULE, Event).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% gen_event callbacks
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
SysTop = list_to_binary(lists:concat(["$SYS/brokers/", node(), "/"])),
|
|
||||||
{ok, #state{systop = SysTop}}.
|
|
||||||
|
|
||||||
handle_event({connected, ClientId, Params}, State = #state{systop = SysTop}) ->
|
|
||||||
Topic = <<SysTop/binary, "clients/", ClientId/binary, "/connected">>,
|
|
||||||
Msg = #mqtt_message{topic = Topic, payload = payload(connected, Params)},
|
|
||||||
emqttd_pubsub:publish(event, Msg),
|
|
||||||
{ok, State};
|
|
||||||
|
|
||||||
%%TODO: Protect from undefined clientId...
|
|
||||||
handle_event({disconnected, undefined, Reason}, State = #state{systop = SysTop}) ->
|
|
||||||
{ok, State};
|
|
||||||
|
|
||||||
handle_event({disconnected, ClientId, Reason}, State = #state{systop = SysTop}) ->
|
|
||||||
Topic = <<SysTop/binary, "clients/", ClientId/binary, "/disconnected">>,
|
|
||||||
Msg = #mqtt_message{topic = Topic, payload = payload(disconnected, Reason)},
|
|
||||||
emqttd_pubsub:publish(event, Msg),
|
|
||||||
{ok, State};
|
|
||||||
|
|
||||||
handle_event({subscribed, ClientId, TopicTable}, State) ->
|
|
||||||
lager:error("TODO: subscribed ~s, ~p", [ClientId, TopicTable]),
|
|
||||||
{ok, State};
|
|
||||||
|
|
||||||
handle_event({unsubscribed, ClientId, Topics}, State) ->
|
|
||||||
lager:error("TODO: unsubscribed ~s, ~p", [ClientId, Topics]),
|
|
||||||
{ok, State};
|
|
||||||
|
|
||||||
handle_event(_Event, State) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
handle_call(_Request, State) ->
|
|
||||||
Reply = ok,
|
|
||||||
{ok, Reply, State}.
|
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Internal functions
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
payload(connected, Params) ->
|
|
||||||
From = proplists:get_value(from, Params),
|
|
||||||
Proto = proplists:get_value(protocol, Params),
|
|
||||||
Sess = proplists:get_value(session, Params),
|
|
||||||
iolist_to_binary(io_lib:format("from: ~s~nprotocol: ~p~nsession: ~s", [From, Proto, Sess]));
|
|
||||||
|
|
||||||
payload(disconnected, Reason) ->
|
|
||||||
list_to_binary(io_lib:format("reason: ~p", [Reason])).
|
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 simple queue.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
%% TODO: this module should be removed...
|
|
||||||
|
|
||||||
-module(emqttd_queue).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
|
||||||
|
|
||||||
-export([new/1, new/2, in/3, all/1, clear/1]).
|
|
||||||
|
|
||||||
-define(DEFAULT_MAX_LEN, 1000).
|
|
||||||
|
|
||||||
-record(mqtt_queue_wrapper, {queue = queue:new(),
|
|
||||||
max_len = ?DEFAULT_MAX_LEN,
|
|
||||||
store_qos0 = false}).
|
|
||||||
|
|
||||||
-type mqtt_queue() :: #mqtt_queue_wrapper{}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc
|
|
||||||
%% New Queue.
|
|
||||||
%%
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec new(non_neg_integer()) -> mqtt_queue().
|
|
||||||
new(MaxLen) -> #mqtt_queue_wrapper{max_len = MaxLen}.
|
|
||||||
|
|
||||||
new(MaxLen, StoreQos0) -> #mqtt_queue_wrapper{max_len = MaxLen, store_qos0 = StoreQos0}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc
|
|
||||||
%% Queue one message.
|
|
||||||
%%
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec in(binary(), mqtt_message(), mqtt_queue()) -> mqtt_queue().
|
|
||||||
in(ClientId, Message = #mqtt_message{qos = Qos},
|
|
||||||
Wrapper = #mqtt_queue_wrapper{queue = Queue, max_len = MaxLen}) ->
|
|
||||||
case queue:len(Queue) < MaxLen of
|
|
||||||
true ->
|
|
||||||
Wrapper#mqtt_queue_wrapper{queue = queue:in(Message, Queue)};
|
|
||||||
false -> % full
|
|
||||||
if
|
|
||||||
Qos =:= ?QOS_0 ->
|
|
||||||
lager:warning("Queue ~s drop qos0 message: ~p", [ClientId, Message]),
|
|
||||||
Wrapper;
|
|
||||||
true ->
|
|
||||||
{{value, Msg}, Queue1} = queue:drop(Queue),
|
|
||||||
lager:warning("Queue ~s drop message: ~p", [ClientId, Msg]),
|
|
||||||
Wrapper#mqtt_queue_wrapper{queue = Queue1}
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc
|
|
||||||
%% Get all messages in queue.
|
|
||||||
%%
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec all(mqtt_queue()) -> list().
|
|
||||||
all(#mqtt_queue_wrapper { queue = Queue }) -> queue:to_list(Queue).
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc
|
|
||||||
%% Clear queue.
|
|
||||||
%%
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec clear(mqtt_queue()) -> mqtt_queue().
|
|
||||||
clear(Queue) -> Queue#mqtt_queue_wrapper{queue = queue:new()}.
|
|
||||||
|
|
|
@ -1,416 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_session).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt_packet.hrl").
|
|
||||||
|
|
||||||
%% API Function Exports
|
|
||||||
-export([start/1,
|
|
||||||
resume/3,
|
|
||||||
publish/3,
|
|
||||||
puback/2,
|
|
||||||
subscribe/2,
|
|
||||||
unsubscribe/2,
|
|
||||||
destroy/2]).
|
|
||||||
|
|
||||||
-export([store/2]).
|
|
||||||
|
|
||||||
%% Start gen_server
|
|
||||||
-export([start_link/2]).
|
|
||||||
|
|
||||||
%% gen_server Function Exports
|
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
||||||
terminate/2, code_change/3]).
|
|
||||||
|
|
||||||
-record(session_state, {
|
|
||||||
clientid :: binary(),
|
|
||||||
client_pid :: pid(),
|
|
||||||
message_id = 1,
|
|
||||||
submap :: map(),
|
|
||||||
msg_queue, %% do not receive rel
|
|
||||||
awaiting_ack :: map(),
|
|
||||||
awaiting_rel :: map(),
|
|
||||||
awaiting_comp :: map(),
|
|
||||||
expires,
|
|
||||||
expire_timer}).
|
|
||||||
|
|
||||||
-type session() :: #session_state{} | pid().
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Session API
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Start Session
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec start({boolean(), binary(), pid()}) -> {ok, session()}.
|
|
||||||
start({true = _CleanSess, ClientId, _ClientPid}) ->
|
|
||||||
%%Destroy old session if CleanSess is true before.
|
|
||||||
ok = emqttd_sm:destroy_session(ClientId),
|
|
||||||
{ok, initial_state(ClientId)};
|
|
||||||
|
|
||||||
start({false = _CleanSess, ClientId, ClientPid}) ->
|
|
||||||
{ok, SessPid} = emqttd_sm:start_session(ClientId, ClientPid),
|
|
||||||
{ok, SessPid}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Resume Session
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec resume(session(), binary(), pid()) -> session().
|
|
||||||
resume(SessState = #session_state{}, _ClientId, _ClientPid) ->
|
|
||||||
SessState;
|
|
||||||
resume(SessPid, ClientId, ClientPid) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {resume, ClientId, ClientPid}),
|
|
||||||
SessPid.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Publish message
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec publish(session(), mqtt_clientid(), {mqtt_qos(), mqtt_message()}) -> session().
|
|
||||||
publish(Session, ClientId, {?QOS_0, Message}) ->
|
|
||||||
emqttd_pubsub:publish(ClientId, Message), Session;
|
|
||||||
|
|
||||||
publish(Session, ClientId, {?QOS_1, Message}) ->
|
|
||||||
emqttd_pubsub:publish(ClientId, Message), Session;
|
|
||||||
|
|
||||||
publish(SessState = #session_state{awaiting_rel = AwaitingRel}, _ClientId,
|
|
||||||
{?QOS_2, Message = #mqtt_message{msgid = MsgId}}) ->
|
|
||||||
%% store in awaiting_rel
|
|
||||||
SessState#session_state{awaiting_rel = maps:put(MsgId, Message, AwaitingRel)};
|
|
||||||
|
|
||||||
publish(SessPid, ClientId, {?QOS_2, Message}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {publish, ClientId, {?QOS_2, Message}}),
|
|
||||||
SessPid.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc PubAck message
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec puback(session(), {mqtt_packet_type(), mqtt_packet_id()}) -> session().
|
|
||||||
puback(SessState = #session_state{clientid = ClientId, awaiting_ack = Awaiting}, {?PUBACK, PacketId}) ->
|
|
||||||
case maps:is_key(PacketId, Awaiting) of
|
|
||||||
true -> ok;
|
|
||||||
false -> lager:warning("Session ~s: PUBACK PacketId '~p' not found!", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
SessState#session_state{awaiting_ack = maps:remove(PacketId, Awaiting)};
|
|
||||||
puback(SessPid, {?PUBACK, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {puback, PacketId}), SessPid;
|
|
||||||
|
|
||||||
%% PUBREC
|
|
||||||
puback(SessState = #session_state{clientid = ClientId,
|
|
||||||
awaiting_ack = AwaitingAck,
|
|
||||||
awaiting_comp = AwaitingComp}, {?PUBREC, PacketId}) ->
|
|
||||||
case maps:is_key(PacketId, AwaitingAck) of
|
|
||||||
true -> ok;
|
|
||||||
false -> lager:warning("Session ~s: PUBREC PacketId '~p' not found!", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
SessState#session_state{awaiting_ack = maps:remove(PacketId, AwaitingAck),
|
|
||||||
awaiting_comp = maps:put(PacketId, true, AwaitingComp)};
|
|
||||||
|
|
||||||
puback(SessPid, {?PUBREC, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {pubrec, PacketId}), SessPid;
|
|
||||||
|
|
||||||
%% PUBREL
|
|
||||||
puback(SessState = #session_state{clientid = ClientId,
|
|
||||||
awaiting_rel = Awaiting}, {?PUBREL, PacketId}) ->
|
|
||||||
case maps:find(PacketId, Awaiting) of
|
|
||||||
{ok, Msg} -> emqttd_pubsub:publish(ClientId, Msg);
|
|
||||||
error -> lager:warning("Session ~s: PUBREL PacketId '~p' not found!", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
SessState#session_state{awaiting_rel = maps:remove(PacketId, Awaiting)};
|
|
||||||
|
|
||||||
puback(SessPid, {?PUBREL, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {pubrel, PacketId}), SessPid;
|
|
||||||
|
|
||||||
%% PUBCOMP
|
|
||||||
puback(SessState = #session_state{clientid = ClientId,
|
|
||||||
awaiting_comp = AwaitingComp}, {?PUBCOMP, PacketId}) ->
|
|
||||||
case maps:is_key(PacketId, AwaitingComp) of
|
|
||||||
true -> ok;
|
|
||||||
false -> lager:warning("Session ~s: PUBREC PacketId '~p' not exist", [ClientId, PacketId])
|
|
||||||
end,
|
|
||||||
SessState#session_state{awaiting_comp = maps:remove(PacketId, AwaitingComp)};
|
|
||||||
|
|
||||||
puback(SessPid, {?PUBCOMP, PacketId}) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {pubcomp, PacketId}), SessPid.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Subscribe Topics
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec subscribe(session(), [{binary(), mqtt_qos()}]) -> {ok, session(), [mqtt_qos()]}.
|
|
||||||
subscribe(SessState = #session_state{clientid = ClientId, submap = SubMap}, Topics) ->
|
|
||||||
Resubs = [Topic || {Name, _Qos} = Topic <- Topics, maps:is_key(Name, SubMap)],
|
|
||||||
case Resubs of
|
|
||||||
[] -> ok;
|
|
||||||
_ -> lager:warning("~s resubscribe ~p", [ClientId, Resubs])
|
|
||||||
end,
|
|
||||||
SubMap1 = lists:foldl(fun({Name, Qos}, Acc) -> maps:put(Name, Qos, Acc) end, SubMap, Topics),
|
|
||||||
{ok, GrantedQos} = emqttd_pubsub:subscribe(Topics),
|
|
||||||
lager:info([{client, ClientId}], "Client ~s subscribe ~p. Granted QoS: ~p",
|
|
||||||
[ClientId, Topics, GrantedQos]),
|
|
||||||
%%TODO: should be gen_event and notification...
|
|
||||||
[emqttd_msg_store:redeliver(Name, self()) || {Name, _} <- Topics],
|
|
||||||
{ok, SessState#session_state{submap = SubMap1}, GrantedQos};
|
|
||||||
|
|
||||||
subscribe(SessPid, Topics) when is_pid(SessPid) ->
|
|
||||||
{ok, GrantedQos} = gen_server:call(SessPid, {subscribe, Topics}),
|
|
||||||
{ok, SessPid, GrantedQos}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Unsubscribe Topics
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec unsubscribe(session(), [binary()]) -> {ok, session()}.
|
|
||||||
unsubscribe(SessState = #session_state{clientid = ClientId, submap = SubMap}, Topics) ->
|
|
||||||
%%TODO: refactor later.
|
|
||||||
case Topics -- maps:keys(SubMap) of
|
|
||||||
[] -> ok;
|
|
||||||
BadUnsubs -> lager:warning("~s should not unsubscribe ~p", [ClientId, BadUnsubs])
|
|
||||||
end,
|
|
||||||
%%unsubscribe from topic tree
|
|
||||||
ok = emqttd_pubsub:unsubscribe(Topics),
|
|
||||||
lager:info([{client, ClientId}], "Client ~s unsubscribe ~p.", [ClientId, Topics]),
|
|
||||||
SubMap1 = lists:foldl(fun(Topic, Acc) -> maps:remove(Topic, Acc) end, SubMap, Topics),
|
|
||||||
{ok, SessState#session_state{submap = SubMap1}};
|
|
||||||
|
|
||||||
unsubscribe(SessPid, Topics) when is_pid(SessPid) ->
|
|
||||||
gen_server:call(SessPid, {unsubscribe, Topics}),
|
|
||||||
{ok, SessPid}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Destroy Session
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
-spec destroy(SessPid :: pid(), ClientId :: binary()) -> ok.
|
|
||||||
destroy(SessPid, ClientId) when is_pid(SessPid) ->
|
|
||||||
gen_server:cast(SessPid, {destroy, ClientId}).
|
|
||||||
|
|
||||||
%store message(qos1) that sent to client
|
|
||||||
store(SessState = #session_state{message_id = MsgId, awaiting_ack = Awaiting},
|
|
||||||
Message = #mqtt_message{qos = Qos}) when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
|
||||||
%%assign msgid before send
|
|
||||||
Message1 = Message#mqtt_message{msgid = MsgId},
|
|
||||||
Message2 =
|
|
||||||
if
|
|
||||||
Qos =:= ?QOS_2 -> Message1#mqtt_message{dup = false};
|
|
||||||
true -> Message1
|
|
||||||
end,
|
|
||||||
Awaiting1 = maps:put(MsgId, Message2, Awaiting),
|
|
||||||
{Message1, next_msg_id(SessState#session_state{awaiting_ack = Awaiting1})}.
|
|
||||||
|
|
||||||
initial_state(ClientId) ->
|
|
||||||
#session_state{clientid = ClientId,
|
|
||||||
submap = #{},
|
|
||||||
awaiting_ack = #{},
|
|
||||||
awaiting_rel = #{},
|
|
||||||
awaiting_comp = #{}}.
|
|
||||||
|
|
||||||
initial_state(ClientId, ClientPid) ->
|
|
||||||
State = initial_state(ClientId),
|
|
||||||
State#session_state{client_pid = ClientPid}.
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
%% @doc Start a session process.
|
|
||||||
%% @end
|
|
||||||
%%------------------------------------------------------------------------------
|
|
||||||
start_link(ClientId, ClientPid) ->
|
|
||||||
gen_server:start_link(?MODULE, [ClientId, ClientPid], []).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% gen_server callbacks
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
init([ClientId, ClientPid]) ->
|
|
||||||
process_flag(trap_exit, true),
|
|
||||||
%%TODO: Is this OK? or should monitor...
|
|
||||||
true = link(ClientPid),
|
|
||||||
SessOpts = emqttd:env(mqtt, session),
|
|
||||||
State = initial_state(ClientId, ClientPid),
|
|
||||||
Expires = proplists:get_value(expires, SessOpts, 1) * 3600,
|
|
||||||
MsgQueue = emqttd_queue:new(proplists:get_value(max_queue, SessOpts, 1000),
|
|
||||||
proplists:get_value(store_qos0, SessOpts, false)),
|
|
||||||
{ok, State#session_state{expires = Expires,
|
|
||||||
msg_queue = MsgQueue}, hibernate}.
|
|
||||||
|
|
||||||
handle_call({subscribe, Topics}, _From, State) ->
|
|
||||||
{ok, NewState, GrantedQos} = subscribe(State, Topics),
|
|
||||||
{reply, {ok, GrantedQos}, NewState};
|
|
||||||
|
|
||||||
handle_call({unsubscribe, Topics}, _From, State) ->
|
|
||||||
{ok, NewState} = unsubscribe(State, Topics),
|
|
||||||
{reply, ok, NewState};
|
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
|
||||||
lager:error("Unexpected request: ~p", [Req]),
|
|
||||||
{reply, error, State}.
|
|
||||||
|
|
||||||
handle_cast({resume, ClientId, ClientPid}, State = #session_state{
|
|
||||||
clientid = ClientId,
|
|
||||||
client_pid = OldClientPid,
|
|
||||||
msg_queue = Queue,
|
|
||||||
awaiting_ack = AwaitingAck,
|
|
||||||
awaiting_comp = AwaitingComp,
|
|
||||||
expire_timer = ETimer}) ->
|
|
||||||
|
|
||||||
lager:info([{client, ClientId}], "Session ~s resumed by ~p",[ClientId, ClientPid]),
|
|
||||||
|
|
||||||
%% kick old client...
|
|
||||||
if
|
|
||||||
OldClientPid =:= undefined ->
|
|
||||||
ok;
|
|
||||||
OldClientPid =:= ClientPid ->
|
|
||||||
ok;
|
|
||||||
true ->
|
|
||||||
lager:error("Session '~s' is duplicated: pid=~p, oldpid=~p", [ClientId, ClientPid, OldClientPid]),
|
|
||||||
unlink(OldClientPid),
|
|
||||||
OldClientPid ! {stop, duplicate_id, ClientPid}
|
|
||||||
end,
|
|
||||||
|
|
||||||
%% cancel timeout timer
|
|
||||||
emqttd_util:cancel_timer(ETimer),
|
|
||||||
|
|
||||||
%% redelivery PUBREL
|
|
||||||
lists:foreach(fun(PacketId) ->
|
|
||||||
ClientPid ! {redeliver, {?PUBREL, PacketId}}
|
|
||||||
end, maps:keys(AwaitingComp)),
|
|
||||||
|
|
||||||
%% redelivery messages that awaiting PUBACK or PUBREC
|
|
||||||
Dup = fun(Msg) -> Msg#mqtt_message{dup = true} end,
|
|
||||||
lists:foreach(fun(Msg) ->
|
|
||||||
ClientPid ! {dispatch, {self(), Dup(Msg)}}
|
|
||||||
end, maps:values(AwaitingAck)),
|
|
||||||
|
|
||||||
%% send offline messages
|
|
||||||
lists:foreach(fun(Msg) ->
|
|
||||||
ClientPid ! {dispatch, {self(), Msg}}
|
|
||||||
end, emqttd_queue:all(Queue)),
|
|
||||||
|
|
||||||
{noreply, State#session_state{client_pid = ClientPid,
|
|
||||||
msg_queue = emqttd_queue:clear(Queue),
|
|
||||||
expire_timer = undefined}, hibernate};
|
|
||||||
|
|
||||||
handle_cast({publish, ClientId, {?QOS_2, Message}}, State) ->
|
|
||||||
NewState = publish(State, ClientId, {?QOS_2, Message}),
|
|
||||||
{noreply, NewState};
|
|
||||||
|
|
||||||
handle_cast({puback, PacketId}, State) ->
|
|
||||||
NewState = puback(State, {?PUBACK, PacketId}),
|
|
||||||
{noreply, NewState};
|
|
||||||
|
|
||||||
handle_cast({pubrec, PacketId}, State) ->
|
|
||||||
NewState = puback(State, {?PUBREC, PacketId}),
|
|
||||||
{noreply, NewState};
|
|
||||||
|
|
||||||
handle_cast({pubrel, PacketId}, State) ->
|
|
||||||
NewState = puback(State, {?PUBREL, PacketId}),
|
|
||||||
{noreply, NewState};
|
|
||||||
|
|
||||||
handle_cast({pubcomp, PacketId}, State) ->
|
|
||||||
NewState = puback(State, {?PUBCOMP, PacketId}),
|
|
||||||
{noreply, NewState};
|
|
||||||
|
|
||||||
handle_cast({destroy, ClientId}, State = #session_state{clientid = ClientId}) ->
|
|
||||||
lager:warning("Session ~s destroyed", [ClientId]),
|
|
||||||
{stop, normal, State};
|
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
|
||||||
lager:critical("Unexpected Msg: ~p, State: ~p", [Msg, State]),
|
|
||||||
{noreply, State}.
|
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Messages}}, State) when is_list(Messages) ->
|
|
||||||
F = fun(Message, S) -> dispatch(Message, S) end,
|
|
||||||
{noreply, lists:foldl(F, State, Messages)};
|
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Message}}, State) ->
|
|
||||||
{noreply, dispatch(Message, State)};
|
|
||||||
|
|
||||||
handle_info({'EXIT', ClientPid, Reason}, State = #session_state{clientid = ClientId,
|
|
||||||
client_pid = ClientPid}) ->
|
|
||||||
lager:error("Session: client ~s@~p exited, caused by ~p", [ClientId, ClientPid, Reason]),
|
|
||||||
{noreply, start_expire_timer(State#session_state{client_pid = undefined})};
|
|
||||||
|
|
||||||
handle_info({'EXIT', ClientPid0, _Reason}, State = #session_state{client_pid = ClientPid}) ->
|
|
||||||
lager:error("Unexpected Client EXIT: pid=~p, pid(state): ~p", [ClientPid0, ClientPid]),
|
|
||||||
{noreply, State};
|
|
||||||
|
|
||||||
handle_info(session_expired, State = #session_state{clientid = ClientId}) ->
|
|
||||||
lager:warning("Session ~s expired!", [ClientId]),
|
|
||||||
{stop, {shutdown, expired}, State};
|
|
||||||
|
|
||||||
handle_info(Info, State) ->
|
|
||||||
lager:critical("Unexpected Info: ~p, State: ~p", [Info, State]),
|
|
||||||
{noreply, State}.
|
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Internal functions
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
dispatch(Message, State = #session_state{clientid = ClientId,
|
|
||||||
client_pid = undefined}) ->
|
|
||||||
queue(ClientId, Message, State);
|
|
||||||
|
|
||||||
dispatch(Message = #mqtt_message{qos = ?QOS_0}, State = #session_state{client_pid = ClientPid}) ->
|
|
||||||
ClientPid ! {dispatch, {self(), Message}},
|
|
||||||
State;
|
|
||||||
|
|
||||||
dispatch(Message = #mqtt_message{qos = Qos}, State = #session_state{client_pid = ClientPid})
|
|
||||||
when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
|
|
||||||
{Message1, NewState} = store(State, Message),
|
|
||||||
ClientPid ! {dispatch, {self(), Message1}},
|
|
||||||
NewState.
|
|
||||||
|
|
||||||
queue(ClientId, Message, State = #session_state{msg_queue = Queue}) ->
|
|
||||||
State#session_state{msg_queue = emqttd_queue:in(ClientId, Message, Queue)}.
|
|
||||||
|
|
||||||
next_msg_id(State = #session_state{message_id = 16#ffff}) ->
|
|
||||||
State#session_state{message_id = 1};
|
|
||||||
|
|
||||||
next_msg_id(State = #session_state{message_id = MsgId}) ->
|
|
||||||
State#session_state{message_id = MsgId + 1}.
|
|
||||||
|
|
||||||
start_expire_timer(State = #session_state{expires = Expires,
|
|
||||||
expire_timer = OldTimer}) ->
|
|
||||||
emqttd_util:cancel_timer(OldTimer),
|
|
||||||
Timer = erlang:send_after(Expires * 1000, self(), session_expired),
|
|
||||||
State#session_state{expire_timer = Timer}.
|
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% @Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>
|
|
||||||
%%%
|
|
||||||
%%% 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 tests.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_acl_tests).
|
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-ifdef(TEST).
|
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
|
|
||||||
all_modules_test() ->
|
|
||||||
with_acl(
|
|
||||||
fun() ->
|
|
||||||
?assertMatch([{emqttd_acl_internal, _State}], emqttd_acl:all_modules())
|
|
||||||
end).
|
|
||||||
|
|
||||||
reload_test() ->
|
|
||||||
with_acl(
|
|
||||||
fun() ->
|
|
||||||
?assertEqual([ok], emqttd_acl:reload())
|
|
||||||
end).
|
|
||||||
|
|
||||||
register_mod_test() ->
|
|
||||||
with_acl(
|
|
||||||
fun() ->
|
|
||||||
emqttd_acl:register_mod(emqttd_acl_test_mod, []),
|
|
||||||
?assertMatch([{emqttd_acl_test_mod, _}, {emqttd_acl_internal, _}],
|
|
||||||
emqttd_acl:all_modules())
|
|
||||||
end).
|
|
||||||
|
|
||||||
unregister_mod_test() ->
|
|
||||||
with_acl(
|
|
||||||
fun() ->
|
|
||||||
emqttd_acl:register_mod(emqttd_acl_test_mod, []),
|
|
||||||
?assertMatch([{emqttd_acl_test_mod, _}, {emqttd_acl_internal, _}],
|
|
||||||
emqttd_acl:all_modules()),
|
|
||||||
emqttd_acl:unregister_mod(emqttd_acl_test_mod),
|
|
||||||
timer:sleep(5),
|
|
||||||
?assertMatch([{emqttd_acl_internal, _}], emqttd_acl:all_modules())
|
|
||||||
end).
|
|
||||||
|
|
||||||
check_test() ->
|
|
||||||
with_acl(
|
|
||||||
fun() ->
|
|
||||||
User1 = #mqtt_user{clientid = <<"client1">>, username = <<"testuser">>},
|
|
||||||
User2 = #mqtt_user{clientid = <<"client2">>, username = <<"xyz">>},
|
|
||||||
?assertEqual(allow, emqttd_acl:check({User1, subscribe, <<"users/testuser/1">>})),
|
|
||||||
?assertEqual(allow, emqttd_acl:check({User1, subscribe, <<"clients/client1">>})),
|
|
||||||
?assertEqual(deny, emqttd_acl:check({User1, subscribe, <<"clients/client1/x/y">>})),
|
|
||||||
?assertEqual(allow, emqttd_acl:check({User1, publish, <<"users/testuser/1">>})),
|
|
||||||
?assertEqual(allow, emqttd_acl:check({User1, subscribe, <<"a/b/c">>})),
|
|
||||||
?assertEqual(deny, emqttd_acl:check({User2, subscribe, <<"a/b/c">>}))
|
|
||||||
end).
|
|
||||||
|
|
||||||
with_acl(Fun) ->
|
|
||||||
process_flag(trap_exit, true),
|
|
||||||
AclOpts = [{internal, [{file, "../test/test_acl.config"},
|
|
||||||
{nomatch, allow}]}],
|
|
||||||
{ok, _AclSrv} = emqttd_acl:start_link(AclOpts),
|
|
||||||
Fun(),
|
|
||||||
emqttd_acl:stop().
|
|
||||||
|
|
||||||
-endif.
|
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,234 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
|
||||||
|
<!--Created by yEd 3.14.2-->
|
||||||
|
<key attr.name="Description" attr.type="string" for="graph" id="d0"/>
|
||||||
|
<key for="port" id="d1" yfiles.type="portgraphics"/>
|
||||||
|
<key for="port" id="d2" yfiles.type="portgeometry"/>
|
||||||
|
<key for="port" id="d3" yfiles.type="portuserdata"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="node" id="d4"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="node" id="d5"/>
|
||||||
|
<key for="node" id="d6" yfiles.type="nodegraphics"/>
|
||||||
|
<key for="graphml" id="d7" yfiles.type="resources"/>
|
||||||
|
<key attr.name="url" attr.type="string" for="edge" id="d8"/>
|
||||||
|
<key attr.name="description" attr.type="string" for="edge" id="d9"/>
|
||||||
|
<key for="edge" id="d10" yfiles.type="edgegraphics"/>
|
||||||
|
<graph edgedefault="directed" id="G">
|
||||||
|
<data key="d0"/>
|
||||||
|
<node id="n0">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="60.72000000000003" width="64.55999999999995" x="-371.08000000000004" y="-960.3600000000001"/>
|
||||||
|
<y:Fill color="#FFCC00" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="11.587890625" x="26.486054687499973" y="21.293593750000014">T<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="diamond"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n1">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="30.0" x="-585.0" y="-945.0"/>
|
||||||
|
<y:Fill color="#CCFFCC" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="19.890625" x="5.0546875" y="5.93359375">C1<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="ellipse"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n2">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="30.0" x="-478.04" y="-945.0"/>
|
||||||
|
<y:Fill color="#99CCFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="18.05078125" x="5.974609375" y="5.93359375">S1<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="ellipse"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n3">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="30.0" x="-122.60000000000014" y="-945.0"/>
|
||||||
|
<y:Fill color="#CCFFCC" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="19.890625" x="5.0546875" y="5.93359375">C2<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="ellipse"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n4">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="30.0" width="30.0" x="-229.56000000000012" y="-945.0"/>
|
||||||
|
<y:Fill color="#99CCFF" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="18.05078125" x="5.974609375" y="5.93359375">S2<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="ellipse"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n5">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="18.715277777777715" width="82.08333333333326" x="-255.60166666666674" y="-879.3576388888889"/>
|
||||||
|
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="41.587890625" x="20.24772135416663" y="0.29123263888891415">Queue<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<node id="n6">
|
||||||
|
<data key="d5"/>
|
||||||
|
<data key="d6">
|
||||||
|
<y:ShapeNode>
|
||||||
|
<y:Geometry height="18.715277777777715" width="82.08333333333326" x="-504.08166666666665" y="-879.3576388888889"/>
|
||||||
|
<y:Fill color="#C0C0C0" transparent="false"/>
|
||||||
|
<y:BorderStyle color="#000000" type="line" width="1.0"/>
|
||||||
|
<y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="41.587890625" x="20.24772135416663" y="0.29123263888891415">Queue<y:LabelModel>
|
||||||
|
<y:SmartNodeLabelModel distance="4.0"/>
|
||||||
|
</y:LabelModel>
|
||||||
|
<y:ModelParameter>
|
||||||
|
<y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
|
||||||
|
</y:ModelParameter>
|
||||||
|
</y:NodeLabel>
|
||||||
|
<y:Shape type="rectangle"/>
|
||||||
|
</y:ShapeNode>
|
||||||
|
</data>
|
||||||
|
</node>
|
||||||
|
<edge id="e0" source="n0" target="n4">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="54.8359375" x="6.2584301757812" y="-23.026406250000264">Dispatch<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||||
|
</y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e1" source="n4" target="n3">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="44.41796875" x="13.971013183593755" y="-23.66640625000025">Deliver<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||||
|
</y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e2" source="n1" target="n2">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="94.556640625" x="43.54167968749999" y="-36.46640625000032">Publish QoS1/2<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||||
|
</y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e3" source="n2" target="n0">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e4" source="n1" target="n0">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="-32.24675781249998" ty="0.0">
|
||||||
|
<y:Point x="-464.4087997380702" y="-990.0"/>
|
||||||
|
</y:Path>
|
||||||
|
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="80.330078125" x="53.093395996093705" y="-79.45582275390632">Publish Qos0<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
|
||||||
|
</y:EdgeLabel>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e5" source="n4" target="n5">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
<edge id="e6" source="n2" target="n6">
|
||||||
|
<data key="d9"/>
|
||||||
|
<data key="d10">
|
||||||
|
<y:PolyLineEdge>
|
||||||
|
<y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
|
||||||
|
<y:LineStyle color="#000000" type="dashed" width="1.0"/>
|
||||||
|
<y:Arrows source="none" target="standard"/>
|
||||||
|
<y:BendStyle smoothed="false"/>
|
||||||
|
</y:PolyLineEdge>
|
||||||
|
</data>
|
||||||
|
</edge>
|
||||||
|
</graph>
|
||||||
|
<data key="d7">
|
||||||
|
<y:Resources/>
|
||||||
|
</data>
|
||||||
|
</graphml>
|
|
@ -0,0 +1,14 @@
|
||||||
|
## QoS0 Publish Sequence
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
title QoS0 Publish Sequence
|
||||||
|
|
||||||
|
C1->PubSub: Publish QoS0
|
||||||
|
PubSub-->S2: Dispatch QoS0
|
||||||
|
S2-->C2: Deliver QoS0
|
||||||
|
```
|
||||||
|
|
||||||
|
## QoS1 Publish Sequence
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
5
go
5
go
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# -*- tab-width:4;indent-tabs-mode:nil -*-
|
|
||||||
# ex: ts=4 sw=4 et
|
|
||||||
|
|
||||||
make && make dist && cd rel/emqttd && ./bin/emqttd console
|
|
|
@ -36,6 +36,12 @@
|
||||||
|
|
||||||
-define(ERTS_MINIMUM, "6.0").
|
-define(ERTS_MINIMUM, "6.0").
|
||||||
|
|
||||||
|
%% System Topics.
|
||||||
|
-define(SYSTOP, <<"$SYS">>).
|
||||||
|
|
||||||
|
%% Queue Topics.
|
||||||
|
-define(QTop, <<"$Q">>).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% PubSub
|
%% PubSub
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -56,8 +62,8 @@
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-record(mqtt_subscriber, {
|
-record(mqtt_subscriber, {
|
||||||
topic :: binary(),
|
topic :: binary(),
|
||||||
qos = 0 :: 0 | 1 | 2,
|
subpid :: pid(),
|
||||||
pid :: pid()
|
qos = 0 :: 0 | 1 | 2
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_subscriber() :: #mqtt_subscriber{}.
|
-type mqtt_subscriber() :: #mqtt_subscriber{}.
|
||||||
|
@ -77,9 +83,13 @@
|
||||||
%% MQTT Client
|
%% MQTT Client
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-record(mqtt_client, {
|
-record(mqtt_client, {
|
||||||
clientid :: binary(),
|
clientid :: binary() | undefined,
|
||||||
username :: binary() | undefined,
|
username :: binary() | undefined,
|
||||||
ipaddr :: inet:ip_address()
|
ipaddress :: inet:ip_address(),
|
||||||
|
client_pid :: pid(),
|
||||||
|
client_mon :: reference(),
|
||||||
|
clean_sess :: boolean(),
|
||||||
|
proto_ver :: 3 | 4
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_client() :: #mqtt_client{}.
|
-type mqtt_client() :: #mqtt_client{}.
|
||||||
|
@ -90,13 +100,30 @@
|
||||||
-record(mqtt_session, {
|
-record(mqtt_session, {
|
||||||
clientid,
|
clientid,
|
||||||
session_pid,
|
session_pid,
|
||||||
subscriptions = [],
|
subscriptions = []
|
||||||
awaiting_ack,
|
|
||||||
awaiting_rel
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type mqtt_session() :: #mqtt_session{}.
|
-type mqtt_session() :: #mqtt_session{}.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% MQTT Message
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-type mqtt_msgid() :: undefined | 1..16#ffff.
|
||||||
|
|
||||||
|
-record(mqtt_message, {
|
||||||
|
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
|
||||||
|
msgid :: mqtt_msgid(), %% Message ID
|
||||||
|
payload :: binary(), %% Payload
|
||||||
|
timestamp :: erlang:timestamp() %% Timestamp
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type mqtt_message() :: #mqtt_message{}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% MQTT Plugin
|
%% MQTT Plugin
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -109,4 +136,16 @@
|
||||||
|
|
||||||
-type mqtt_plugin() :: #mqtt_plugin{}.
|
-type mqtt_plugin() :: #mqtt_plugin{}.
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% MQTT Alarm
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-record(mqtt_alarm, {
|
||||||
|
id :: binary(),
|
||||||
|
severity :: warning | error | critical,
|
||||||
|
title :: binary(),
|
||||||
|
summary :: binary(),
|
||||||
|
timestamp :: erlang:timestamp() %% Timestamp
|
||||||
|
}).
|
||||||
|
|
||||||
|
-type mqtt_alarm() :: #mqtt_alarm{}.
|
||||||
|
|
|
@ -20,11 +20,35 @@
|
||||||
%%% SOFTWARE.
|
%%% SOFTWARE.
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
%%% @doc
|
%%% @doc
|
||||||
%%% MQTT Packet Header.
|
%%% MQTT Protocol Header.
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% MQTT Protocol Version and Levels
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
-define(MQTT_PROTO_V31, 3).
|
||||||
|
-define(MQTT_PROTO_V311, 4).
|
||||||
|
|
||||||
|
-define(PROTOCOL_NAMES, [
|
||||||
|
{?MQTT_PROTO_V31, <<"MQIsdp">>},
|
||||||
|
{?MQTT_PROTO_V311, <<"MQTT">>}]).
|
||||||
|
|
||||||
|
-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
|
||||||
|
|
||||||
|
-define(IS_QOS(I), (I >= ?QOS_0 andalso I =< ?QOS_2)).
|
||||||
|
|
||||||
|
-type mqtt_qos() :: ?QOS_0 | ?QOS_1 | ?QOS_2.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Max ClientId Length. Why 1024? NiDongDe!
|
%% Max ClientId Length. Why 1024? NiDongDe!
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
@ -162,7 +186,7 @@
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, variable = Var}).
|
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNECT}, variable = Var}).
|
||||||
|
|
||||||
-define(CONNACK_PACKET(ReturnCode),
|
-define(CONNACK_PACKET(ReturnCode),
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
|
#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK},
|
||||||
variable = #mqtt_packet_connack{return_code = ReturnCode}}).
|
variable = #mqtt_packet_connack{return_code = ReturnCode}}).
|
||||||
|
|
||||||
-define(PUBLISH_PACKET(Qos, Topic, PacketId, Payload),
|
-define(PUBLISH_PACKET(Qos, Topic, PacketId, Payload),
|
||||||
|
@ -172,6 +196,11 @@
|
||||||
packet_id = PacketId},
|
packet_id = PacketId},
|
||||||
payload = Payload}).
|
payload = Payload}).
|
||||||
|
|
||||||
|
-define(PUBLISH(Qos, PacketId),
|
||||||
|
#mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
|
||||||
|
qos = Qos},
|
||||||
|
variable = #mqtt_packet_publish{packet_id = PacketId}}).
|
||||||
|
|
||||||
-define(PUBACK_PACKET(Type, PacketId),
|
-define(PUBACK_PACKET(Type, PacketId),
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = Type},
|
#mqtt_packet{header = #mqtt_packet_header{type = Type},
|
||||||
variable = #mqtt_packet_puback{packet_id = PacketId}}).
|
variable = #mqtt_packet_puback{packet_id = PacketId}}).
|
||||||
|
@ -199,4 +228,3 @@
|
||||||
-define(PACKET(Type),
|
-define(PACKET(Type),
|
||||||
#mqtt_packet{header = #mqtt_packet_header{type = Type}}).
|
#mqtt_packet{header = #mqtt_packet_header{type = Type}}).
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{application, emqttd_amqp,
|
|
||||||
[
|
|
||||||
{description, ""},
|
|
||||||
{vsn, "1"},
|
|
||||||
{registered, []},
|
|
||||||
{applications, [
|
|
||||||
kernel,
|
|
||||||
stdlib
|
|
||||||
]},
|
|
||||||
{mod, { emqttd_amqp_app, []}},
|
|
||||||
{env, []}
|
|
||||||
]}.
|
|
|
@ -1,16 +0,0 @@
|
||||||
-module(emqttd_amqp_app).
|
|
||||||
|
|
||||||
-behaviour(application).
|
|
||||||
|
|
||||||
%% Application callbacks
|
|
||||||
-export([start/2, stop/1]).
|
|
||||||
|
|
||||||
%% ===================================================================
|
|
||||||
%% Application callbacks
|
|
||||||
%% ===================================================================
|
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
|
||||||
emqttd_amqp_sup:start_link().
|
|
||||||
|
|
||||||
stop(_State) ->
|
|
||||||
ok.
|
|
|
@ -1,27 +0,0 @@
|
||||||
-module(emqttd_amqp_sup).
|
|
||||||
|
|
||||||
-behaviour(supervisor).
|
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([start_link/0]).
|
|
||||||
|
|
||||||
%% Supervisor callbacks
|
|
||||||
-export([init/1]).
|
|
||||||
|
|
||||||
%% Helper macro for declaring children of supervisor
|
|
||||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
|
|
||||||
|
|
||||||
%% ===================================================================
|
|
||||||
%% API functions
|
|
||||||
%% ===================================================================
|
|
||||||
|
|
||||||
start_link() ->
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
||||||
|
|
||||||
%% ===================================================================
|
|
||||||
%% Supervisor callbacks
|
|
||||||
%% ===================================================================
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
{ok, { {one_for_one, 5, 10}, []} }.
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
## Overview
|
|
||||||
|
|
||||||
Authentication with LDAP.
|
|
||||||
|
|
||||||
## Plugin Config
|
|
||||||
|
|
||||||
```
|
|
||||||
{emqttd_auth_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"}]}
|
|
||||||
]}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Load Plugin
|
|
||||||
|
|
||||||
Merge the'etc/plugin.config' to emqttd/etc/plugins.config, and the plugin will be loaded automatically.
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
[
|
|
||||||
{emqttd_auth_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"}]}
|
|
||||||
]}
|
|
||||||
].
|
|
|
@ -1,12 +0,0 @@
|
||||||
{application, emqttd_auth_ldap,
|
|
||||||
[
|
|
||||||
{description, "emqttd LDAP Authentication Plugin"},
|
|
||||||
{vsn, "1.0"},
|
|
||||||
{registered, []},
|
|
||||||
{applications, [
|
|
||||||
kernel,
|
|
||||||
stdlib
|
|
||||||
]},
|
|
||||||
{mod, { emqttd_auth_ldap_app, []}},
|
|
||||||
{env, []}
|
|
||||||
]}.
|
|
|
@ -1,58 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 Plugin.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_auth_ldap_app).
|
|
||||||
|
|
||||||
-behaviour(application).
|
|
||||||
%% Application callbacks
|
|
||||||
-export([start/2, prep_stop/1, stop/1]).
|
|
||||||
|
|
||||||
-behaviour(supervisor).
|
|
||||||
%% Supervisor callbacks
|
|
||||||
-export([init/1]).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Application callbacks
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
|
||||||
Env = application:get_all_env(emqttd_auth_ldap),
|
|
||||||
emqttd_access_control:register_mod(auth, emqttd_auth_ldap, Env),
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
||||||
|
|
||||||
prep_stop(State) ->
|
|
||||||
emqttd_access_control:unregister_mod(auth, emqttd_auth_ldap), State.
|
|
||||||
|
|
||||||
stop(_State) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Supervisor callbacks(Dummy)
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
{ok, { {one_for_one, 5, 10}, []} }.
|
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Authentication with user table of MySQL database.
|
|
||||||
|
|
||||||
## etc/plugin.config
|
|
||||||
|
|
||||||
```
|
|
||||||
{emysql, [
|
|
||||||
{pool, 4},
|
|
||||||
{host, "localhost"},
|
|
||||||
{port, 3306},
|
|
||||||
{username, ""},
|
|
||||||
{password, ""},
|
|
||||||
{database, "mqtt"},
|
|
||||||
{encoding, utf8}
|
|
||||||
]},
|
|
||||||
{emqttd_auth_mysql, [
|
|
||||||
{user_table, mqtt_users},
|
|
||||||
%% plain password only
|
|
||||||
{password_hash, plain},
|
|
||||||
{field_mapper, [
|
|
||||||
{username, username},
|
|
||||||
{password, password}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Users Table(Demo)
|
|
||||||
|
|
||||||
Notice: This is a demo table. You could authenticate with any user tables.
|
|
||||||
|
|
||||||
```
|
|
||||||
CREATE TABLE `mqtt_users` (
|
|
||||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`username` varchar(60) DEFAULT NULL,
|
|
||||||
`password` varchar(60) DEFAULT NULL,
|
|
||||||
`salt` varchar(20) DEFAULT NULL,
|
|
||||||
`created` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `mqtt_users_username` (`username`)
|
|
||||||
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Load Plugin
|
|
||||||
|
|
||||||
Merge the'etc/plugin.config' to emqttd/etc/plugins.config, and the plugin will be loaded by the broker.
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
{emysql, [
|
|
||||||
{pool, 4},
|
|
||||||
{host, "localhost"},
|
|
||||||
{port, 3306},
|
|
||||||
{username, "root"},
|
|
||||||
{password, "public"},
|
|
||||||
{database, "mqtt"},
|
|
||||||
{encoding, utf8}
|
|
||||||
]},
|
|
||||||
{emqttd_auth_mysql, [
|
|
||||||
{users_table, mqtt_users},
|
|
||||||
{field_mapper, [
|
|
||||||
{username, username},
|
|
||||||
{password, password, plain}
|
|
||||||
]}
|
|
||||||
]}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{application, emqttd_auth_mysql,
|
|
||||||
[
|
|
||||||
{description, "emqttd MySQL Authentication Plugin"},
|
|
||||||
{vsn, "1.0"},
|
|
||||||
{registered, []},
|
|
||||||
{applications, [
|
|
||||||
kernel,
|
|
||||||
stdlib
|
|
||||||
]},
|
|
||||||
{mod, {emqttd_auth_mysql_app, []}},
|
|
||||||
{env, []}
|
|
||||||
]}.
|
|
|
@ -1,75 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 by mysql 'user' table.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_auth_mysql).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-include_lib("emqttd/include/emqttd.hrl").
|
|
||||||
|
|
||||||
-behaviour(emqttd_auth_mod).
|
|
||||||
|
|
||||||
-export([init/1, check/3, description/0]).
|
|
||||||
|
|
||||||
-record(state, {user_table, name_field, pass_field, pass_hash}).
|
|
||||||
|
|
||||||
init(Opts) ->
|
|
||||||
Mapper = proplists:get_value(field_mapper, Opts),
|
|
||||||
{ok, #state{user_table = proplists:get_value(user_table, Opts, mqtt_users),
|
|
||||||
name_field = proplists:get_value(username, Mapper),
|
|
||||||
pass_field = proplists:get_value(password, Mapper),
|
|
||||||
pass_hash = proplists:get_value(Opts, password_hash)}}.
|
|
||||||
|
|
||||||
check(#mqtt_client{username = undefined}, _Password, _State) ->
|
|
||||||
{error, "Username undefined"};
|
|
||||||
check(_Client, undefined, _State) ->
|
|
||||||
{error, "Password undefined"};
|
|
||||||
check(#mqtt_client{username = Username}, Password,
|
|
||||||
#state{user_table = UserTab, pass_hash = Type,
|
|
||||||
name_field = NameField, pass_field = PassField}) ->
|
|
||||||
Where = {'and', {NameField, Username}, {PassField, hash(Type, Password)}},
|
|
||||||
case emysql:select(UserTab, Where) of
|
|
||||||
{ok, []} -> {error, "Username or Password "};
|
|
||||||
{ok, _Record} -> ok
|
|
||||||
end.
|
|
||||||
|
|
||||||
description() -> "Authentication by MySQL".
|
|
||||||
|
|
||||||
hash(plain, Password) ->
|
|
||||||
Password;
|
|
||||||
|
|
||||||
hash(md5, Password) ->
|
|
||||||
hexstring(crypto:hash(md5, Password));
|
|
||||||
|
|
||||||
hash(sha, Password) ->
|
|
||||||
hexstring(crypto:hash(sha, Password)).
|
|
||||||
|
|
||||||
hexstring(<<X:128/big-unsigned-integer>>) ->
|
|
||||||
lists:flatten(io_lib:format("~32.16.0b", [X]));
|
|
||||||
|
|
||||||
hexstring(<<X:160/big-unsigned-integer>>) ->
|
|
||||||
lists:flatten(io_lib:format("~40.16.0b", [X])).
|
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 mysql authentication app.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_auth_mysql_app).
|
|
||||||
|
|
||||||
-behaviour(application).
|
|
||||||
%% Application callbacks
|
|
||||||
-export([start/2, prep_stop/1, stop/1]).
|
|
||||||
|
|
||||||
-behaviour(supervisor).
|
|
||||||
%% Supervisor callbacks
|
|
||||||
-export([init/1]).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Application callbacks
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
|
||||||
Env = application:get_all_env(),
|
|
||||||
emqttd_access_control:register_mod(auth, emqttd_auth_mysql, Env),
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
||||||
|
|
||||||
prep_stop(State) ->
|
|
||||||
emqttd_access_control:unregister_mod(auth, emqttd_auth_mysql), State.
|
|
||||||
|
|
||||||
stop(_State) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Supervisor callbacks(Dummy)
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
{ok, { {one_for_one, 5, 10}, []} }.
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{application, emqttd_dashboard,
|
|
||||||
[
|
|
||||||
{description, "emqttd management dashboard"},
|
|
||||||
{vsn, "0.1"},
|
|
||||||
{registered, []},
|
|
||||||
{applications, [
|
|
||||||
kernel,
|
|
||||||
stdlib
|
|
||||||
]},
|
|
||||||
{mod, {emqttd_dashboard_app, []}},
|
|
||||||
{env, []}
|
|
||||||
]}.
|
|
|
@ -1,27 +0,0 @@
|
||||||
-module(emqttd_dashboard_app).
|
|
||||||
|
|
||||||
-behaviour(application).
|
|
||||||
|
|
||||||
%% Application callbacks
|
|
||||||
-export([start/2, stop/1]).
|
|
||||||
|
|
||||||
%% ===================================================================
|
|
||||||
%% Application callbacks
|
|
||||||
%% ===================================================================
|
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
|
||||||
{ok, Sup} = emqttd_dashboard_sup:start_link(),
|
|
||||||
open_listener(application:get_env(listener)),
|
|
||||||
{ok, Sup}.
|
|
||||||
|
|
||||||
stop(_State) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
%% open http port
|
|
||||||
open_listener({_Http, Port, Options}) ->
|
|
||||||
MFArgs = {emqttd_dashboard, handle_request, []},
|
|
||||||
mochiweb:start_http(Port, Options, MFArgs).
|
|
||||||
|
|
||||||
close_listener(Port) ->
|
|
||||||
mochiweb:stop_http(Port).
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
-module(emqttd_dashboard_sup).
|
|
||||||
|
|
||||||
-behaviour(supervisor).
|
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([start_link/0]).
|
|
||||||
|
|
||||||
%% Supervisor callbacks
|
|
||||||
-export([init/1]).
|
|
||||||
|
|
||||||
%% Helper macro for declaring children of supervisor
|
|
||||||
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
|
|
||||||
|
|
||||||
%% ===================================================================
|
|
||||||
%% API functions
|
|
||||||
%% ===================================================================
|
|
||||||
|
|
||||||
start_link() ->
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
||||||
|
|
||||||
%% ===================================================================
|
|
||||||
%% Supervisor callbacks
|
|
||||||
%% ===================================================================
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
{ok, { {one_for_one, 5, 10}, []} }.
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
REBAR?=./rebar
|
|
||||||
|
|
||||||
|
|
||||||
all: build
|
|
||||||
|
|
||||||
|
|
||||||
clean:
|
|
||||||
$(REBAR) clean
|
|
||||||
rm -rf logs
|
|
||||||
rm -rf .eunit
|
|
||||||
rm -f test/*.beam
|
|
||||||
|
|
||||||
|
|
||||||
distclean: clean
|
|
||||||
git clean -fxd
|
|
||||||
|
|
||||||
build: depends
|
|
||||||
$(REBAR) compile
|
|
||||||
|
|
||||||
|
|
||||||
eunit:
|
|
||||||
$(REBAR) eunit skip_deps=true
|
|
||||||
|
|
||||||
|
|
||||||
check: build eunit
|
|
||||||
|
|
||||||
|
|
||||||
%.beam: %.erl
|
|
||||||
erlc -o test/ $<
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: all clean distclean depends build eunit check
|
|
|
@ -1,49 +0,0 @@
|
||||||
## Overview
|
|
||||||
|
|
||||||
Authentication with user table of MySQL database.
|
|
||||||
|
|
||||||
## etc/plugin.config
|
|
||||||
|
|
||||||
```erlang
|
|
||||||
[
|
|
||||||
{emysql, [
|
|
||||||
{pool, 4},
|
|
||||||
{host, "localhost"},
|
|
||||||
{port, 3306},
|
|
||||||
{username, ""},
|
|
||||||
{password, ""},
|
|
||||||
{database, "mqtt"},
|
|
||||||
{encoding, utf8}
|
|
||||||
]},
|
|
||||||
{emqttd_auth_mysql, [
|
|
||||||
{user_table, mqtt_users},
|
|
||||||
%% plain password only
|
|
||||||
{password_hash, plain},
|
|
||||||
{field_mapper, [
|
|
||||||
{username, username},
|
|
||||||
{password, password}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
].
|
|
||||||
```
|
|
||||||
|
|
||||||
## Users Table(Demo)
|
|
||||||
|
|
||||||
Notice: This is a demo table. You could authenticate with any user tables.
|
|
||||||
|
|
||||||
```
|
|
||||||
CREATE TABLE `mqtt_users` (
|
|
||||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
|
||||||
`username` varchar(60) DEFAULT NULL,
|
|
||||||
`password` varchar(60) DEFAULT NULL,
|
|
||||||
`salt` varchar(20) DEFAULT NULL,
|
|
||||||
`created` datetime DEFAULT NULL,
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `mqtt_users_username` (`username`)
|
|
||||||
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Load Plugin
|
|
||||||
|
|
||||||
Merge the'etc/plugin.config' to emqttd/etc/plugins.config, and the plugin will be loaded by the broker.
|
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
|
|
||||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions
|
|
||||||
* are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. All advertising materials mentioning features or use of this software
|
|
||||||
* must display the following acknowledgement:
|
|
||||||
* This product includes software developed by the Kungliga Tekniska
|
|
||||||
* Hgskolan and its contributors.
|
|
||||||
*
|
|
||||||
* 4. Neither the name of the Institute nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software
|
|
||||||
* without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
||||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
||||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
||||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
||||||
* SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include <config.h>
|
|
||||||
/*RCSID("$Id: base64.c,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $");*/
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "base64.h"
|
|
||||||
|
|
||||||
static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
||||||
|
|
||||||
static int pos(char c)
|
|
||||||
{
|
|
||||||
char *p;
|
|
||||||
for(p = base64; *p; p++)
|
|
||||||
if(*p == c)
|
|
||||||
return p - base64;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int base64_encode(const void *data, int size, char **str)
|
|
||||||
{
|
|
||||||
char *s, *p;
|
|
||||||
int i;
|
|
||||||
int c;
|
|
||||||
unsigned char *q;
|
|
||||||
|
|
||||||
p = s = (char*)malloc(size*4/3+4);
|
|
||||||
if (p == NULL)
|
|
||||||
return -1;
|
|
||||||
q = (unsigned char*)data;
|
|
||||||
i=0;
|
|
||||||
for(i = 0; i < size;){
|
|
||||||
c=q[i++];
|
|
||||||
c*=256;
|
|
||||||
if(i < size)
|
|
||||||
c+=q[i];
|
|
||||||
i++;
|
|
||||||
c*=256;
|
|
||||||
if(i < size)
|
|
||||||
c+=q[i];
|
|
||||||
i++;
|
|
||||||
p[0]=base64[(c&0x00fc0000) >> 18];
|
|
||||||
p[1]=base64[(c&0x0003f000) >> 12];
|
|
||||||
p[2]=base64[(c&0x00000fc0) >> 6];
|
|
||||||
p[3]=base64[(c&0x0000003f) >> 0];
|
|
||||||
if(i > size)
|
|
||||||
p[3]='=';
|
|
||||||
if(i > size+1)
|
|
||||||
p[2]='=';
|
|
||||||
p+=4;
|
|
||||||
}
|
|
||||||
*p=0;
|
|
||||||
*str = s;
|
|
||||||
return strlen(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
int base64_decode(const char *str, void *data)
|
|
||||||
{
|
|
||||||
const char *p;
|
|
||||||
unsigned char *q;
|
|
||||||
int c;
|
|
||||||
int x;
|
|
||||||
int done = 0;
|
|
||||||
q=(unsigned char*)data;
|
|
||||||
for(p=str; *p && !done; p+=4){
|
|
||||||
x = pos(p[0]);
|
|
||||||
if(x >= 0)
|
|
||||||
c = x;
|
|
||||||
else{
|
|
||||||
done = 3;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
c*=64;
|
|
||||||
|
|
||||||
x = pos(p[1]);
|
|
||||||
if(x >= 0)
|
|
||||||
c += x;
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
c*=64;
|
|
||||||
|
|
||||||
if(p[2] == '=')
|
|
||||||
done++;
|
|
||||||
else{
|
|
||||||
x = pos(p[2]);
|
|
||||||
if(x >= 0)
|
|
||||||
c += x;
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
c*=64;
|
|
||||||
|
|
||||||
if(p[3] == '=')
|
|
||||||
done++;
|
|
||||||
else{
|
|
||||||
if(done)
|
|
||||||
return -1;
|
|
||||||
x = pos(p[3]);
|
|
||||||
if(x >= 0)
|
|
||||||
c += x;
|
|
||||||
else
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if(done < 3)
|
|
||||||
*q++=(c&0x00ff0000)>>16;
|
|
||||||
|
|
||||||
if(done < 2)
|
|
||||||
*q++=(c&0x0000ff00)>>8;
|
|
||||||
if(done < 1)
|
|
||||||
*q++=(c&0x000000ff)>>0;
|
|
||||||
}
|
|
||||||
return q - (unsigned char*)data;
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 1995, 1996, 1997 Kungliga Tekniska Hgskolan
|
|
||||||
* (Royal Institute of Technology, Stockholm, Sweden).
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions
|
|
||||||
* are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. All advertising materials mentioning features or use of this software
|
|
||||||
* must display the following acknowledgement:
|
|
||||||
* This product includes software developed by the Kungliga Tekniska
|
|
||||||
* Hgskolan and its contributors.
|
|
||||||
*
|
|
||||||
* 4. Neither the name of the Institute nor the names of its contributors
|
|
||||||
* may be used to endorse or promote products derived from this software
|
|
||||||
* without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
||||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
||||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
||||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
||||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
||||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
||||||
* SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* $Id: base64.h,v 1.1 2005/02/11 07:34:35 jpm Exp jpm $ */
|
|
||||||
|
|
||||||
#ifndef _BASE64_H_
|
|
||||||
#define _BASE64_H_
|
|
||||||
|
|
||||||
int base64_encode(const void *data, int size, char **str);
|
|
||||||
int base64_decode(const char *str, void *data);
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -1,60 +0,0 @@
|
||||||
// This file is part of Jiffy released under the MIT license.
|
|
||||||
// See the LICENSE file for more information.
|
|
||||||
|
|
||||||
#include "emqttd_plugin_mysql_app.h"
|
|
||||||
|
|
||||||
static int
|
|
||||||
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
|
|
||||||
{
|
|
||||||
emqttd_plugin_mysql_app_st* st = enif_alloc(sizeof(emqttd_plugin_mysql_app_st));
|
|
||||||
if(st == NULL) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
st->atom_ok = make_atom(env, "ok");
|
|
||||||
st->atom_error = make_atom(env, "error");
|
|
||||||
st->atom_null = make_atom(env, "null");
|
|
||||||
st->atom_true = make_atom(env, "true");
|
|
||||||
st->atom_false = make_atom(env, "false");
|
|
||||||
st->atom_bignum = make_atom(env, "bignum");
|
|
||||||
st->atom_bignum_e = make_atom(env, "bignum_e");
|
|
||||||
st->atom_bigdbl = make_atom(env, "bigdbl");
|
|
||||||
st->atom_partial = make_atom(env, "partial");
|
|
||||||
st->atom_uescape = make_atom(env, "uescape");
|
|
||||||
st->atom_pretty = make_atom(env, "pretty");
|
|
||||||
st->atom_force_utf8 = make_atom(env, "force_utf8");
|
|
||||||
|
|
||||||
// Markers used in encoding
|
|
||||||
st->ref_object = make_atom(env, "$object_ref$");
|
|
||||||
st->ref_array = make_atom(env, "$array_ref$");
|
|
||||||
|
|
||||||
*priv = (void*) st;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info)
|
|
||||||
{
|
|
||||||
return load(env, priv, info);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
unload(ErlNifEnv* env, void* priv)
|
|
||||||
{
|
|
||||||
enif_free(priv);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ErlNifFunc funcs[] =
|
|
||||||
{
|
|
||||||
{"nif_pbkdf2_check", 2, pbkdf2_check}
|
|
||||||
};
|
|
||||||
|
|
||||||
ERL_NIF_INIT(emqttd_plugin_mysql_app, funcs, &load, &reload, &upgrade, &unload);
|
|
|
@ -1,44 +0,0 @@
|
||||||
// This file is part of Jiffy released under the MIT license.
|
|
||||||
// See the LICENSE file for more information.
|
|
||||||
|
|
||||||
#ifndef EMQTTD_PLUGIN_MYSQL_APP_H
|
|
||||||
#define EMQTTD_PLUGIN_MYSQL_APP_H
|
|
||||||
|
|
||||||
#include "erl_nif.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
ERL_NIF_TERM atom_ok;
|
|
||||||
ERL_NIF_TERM atom_error;
|
|
||||||
ERL_NIF_TERM atom_null;
|
|
||||||
ERL_NIF_TERM atom_true;
|
|
||||||
ERL_NIF_TERM atom_false;
|
|
||||||
ERL_NIF_TERM atom_bignum;
|
|
||||||
ERL_NIF_TERM atom_bignum_e;
|
|
||||||
ERL_NIF_TERM atom_bigdbl;
|
|
||||||
ERL_NIF_TERM atom_partial;
|
|
||||||
ERL_NIF_TERM atom_uescape;
|
|
||||||
ERL_NIF_TERM atom_pretty;
|
|
||||||
ERL_NIF_TERM atom_force_utf8;
|
|
||||||
|
|
||||||
ERL_NIF_TERM ref_object;
|
|
||||||
ERL_NIF_TERM ref_array;
|
|
||||||
} emqttd_plugin_mysql_app_st;
|
|
||||||
|
|
||||||
ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name);
|
|
||||||
ERL_NIF_TERM make_ok(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, ERL_NIF_TERM data);
|
|
||||||
ERL_NIF_TERM make_error(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, const char* error);
|
|
||||||
|
|
||||||
ERL_NIF_TERM pbkdf2_check(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
|
|
||||||
|
|
||||||
int int_from_hex(const unsigned char* p);
|
|
||||||
int int_to_hex(int val, char* p);
|
|
||||||
int utf8_len(int c);
|
|
||||||
int utf8_esc_len(int c);
|
|
||||||
int utf8_validate(unsigned char* data, size_t size);
|
|
||||||
int utf8_to_unicode(unsigned char* buf, size_t size);
|
|
||||||
int unicode_to_utf8(int c, unsigned char* buf);
|
|
||||||
int unicode_from_pair(int hi, int lo);
|
|
||||||
int unicode_uescape(int c, char* buf);
|
|
||||||
int double_to_shortest(char *buf, size_t size, size_t* len, double val);
|
|
||||||
|
|
||||||
#endif // Included EMQTTD_PLUGIN_MYSQL_APP_H
|
|
|
@ -1,278 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2013 Jan-Piet Mens <jpmens()gmail.com>
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer.
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* 3. Neither the name of mosquitto nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
||||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
||||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
||||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
||||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
||||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
||||||
* POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
#include "base64.h"
|
|
||||||
#include "erl_nif.h"
|
|
||||||
#include "emqttd_plugin_mysql_app.h"
|
|
||||||
|
|
||||||
#define KEY_LENGTH 24
|
|
||||||
#define SEPARATOR "$"
|
|
||||||
#define SEPARATOR1 "_"
|
|
||||||
#define TRUE (1)
|
|
||||||
#define FALSE (0)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Split PBKDF2$... string into their components. The caller must free()
|
|
||||||
* the strings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int detoken(char *pbkstr, char **sha, int *iter, char **salt, char **key)
|
|
||||||
{
|
|
||||||
char *p, *s, *save;
|
|
||||||
int rc = 1;
|
|
||||||
|
|
||||||
save = s = strdup(pbkstr);
|
|
||||||
|
|
||||||
if ((p = strsep(&s, SEPARATOR1)) == NULL)
|
|
||||||
goto out;
|
|
||||||
if (strcmp(p, "pbkdf2") != 0)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
if ((p = strsep(&s, SEPARATOR)) == NULL)
|
|
||||||
goto out;
|
|
||||||
*sha = strdup(p);
|
|
||||||
|
|
||||||
if ((p = strsep(&s, SEPARATOR)) == NULL)
|
|
||||||
goto out;
|
|
||||||
*iter = atoi(p);
|
|
||||||
|
|
||||||
if ((p = strsep(&s, SEPARATOR)) == NULL)
|
|
||||||
goto out;
|
|
||||||
*salt = strdup(p);
|
|
||||||
|
|
||||||
if ((p = strsep(&s, SEPARATOR)) == NULL)
|
|
||||||
goto out;
|
|
||||||
*key = strdup(p);
|
|
||||||
|
|
||||||
rc = 0;
|
|
||||||
|
|
||||||
out:
|
|
||||||
free(save);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
ERL_NIF_TERM
|
|
||||||
pbkdf2_check(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
|
|
||||||
{
|
|
||||||
ERL_NIF_TERM ret;
|
|
||||||
ErlNifBinary binps, binhash;
|
|
||||||
emqttd_plugin_mysql_app_st* st = enif_alloc(sizeof(emqttd_plugin_mysql_app_st));
|
|
||||||
if(st == NULL) {
|
|
||||||
return make_atom(env, "alloc_error");
|
|
||||||
}
|
|
||||||
|
|
||||||
st->atom_ok = make_atom(env, "ok");
|
|
||||||
st->atom_error = make_atom(env, "error");
|
|
||||||
st->atom_null = make_atom(env, "null");
|
|
||||||
st->atom_true = make_atom(env, "true");
|
|
||||||
st->atom_false = make_atom(env, "false");
|
|
||||||
st->atom_bignum = make_atom(env, "bignum");
|
|
||||||
st->atom_bignum_e = make_atom(env, "bignum_e");
|
|
||||||
st->atom_bigdbl = make_atom(env, "bigdbl");
|
|
||||||
st->atom_partial = make_atom(env, "partial");
|
|
||||||
st->atom_uescape = make_atom(env, "uescape");
|
|
||||||
st->atom_pretty = make_atom(env, "pretty");
|
|
||||||
st->atom_force_utf8 = make_atom(env, "force_utf8");
|
|
||||||
|
|
||||||
// Markers used in encoding
|
|
||||||
st->ref_object = make_atom(env, "$object_ref$");
|
|
||||||
st->ref_array = make_atom(env, "$array_ref$");
|
|
||||||
|
|
||||||
if(argc != 2) {
|
|
||||||
return make_error(st, env, "Bad args");
|
|
||||||
} else if(!enif_inspect_binary(env, argv[0], &binps)|!enif_inspect_binary(env, argv[1], &binhash)) {
|
|
||||||
return make_error(st, env, "Bad args password or username inspect error");
|
|
||||||
}
|
|
||||||
|
|
||||||
char* password = (char*)binps.data;
|
|
||||||
char* hash = (char*)binhash.data;
|
|
||||||
static char *sha, *salt, *h_pw;
|
|
||||||
int iterations, saltlen, blen;
|
|
||||||
char *b64, *keybuf;
|
|
||||||
unsigned char *out;
|
|
||||||
int match = FALSE;
|
|
||||||
const EVP_MD *evpmd;
|
|
||||||
int keylen, rc;
|
|
||||||
|
|
||||||
if (detoken(hash, &sha, &iterations, &salt, &h_pw) != 0)
|
|
||||||
return match;
|
|
||||||
|
|
||||||
/* Determine key length by decoding base64 */
|
|
||||||
if ((keybuf = malloc(strlen(h_pw) + 1)) == NULL) {
|
|
||||||
return make_error(st, env, "internal_error: Out Of memory");
|
|
||||||
}
|
|
||||||
keylen = base64_decode(h_pw, keybuf);
|
|
||||||
if (keylen < 1) {
|
|
||||||
free(keybuf);
|
|
||||||
return make_atom(env, "false");
|
|
||||||
}
|
|
||||||
free(keybuf);
|
|
||||||
|
|
||||||
if ((out = malloc(keylen)) == NULL) {
|
|
||||||
return make_error(st, env, "Cannot allocate out; out of memory\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PWDEBUG
|
|
||||||
fprintf(stderr, "sha =[%s]\n", sha);
|
|
||||||
fprintf(stderr, "iterations =%d\n", iterations);
|
|
||||||
fprintf(stderr, "salt =[%s]\n", salt);
|
|
||||||
fprintf(stderr, "h_pw =[%s]\n", h_pw);
|
|
||||||
fprintf(stderr, "kenlen =[%d]\n", keylen);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
saltlen = strlen((char *)salt);
|
|
||||||
|
|
||||||
evpmd = EVP_sha256();
|
|
||||||
if (strcmp(sha, "sha1") == 0) {
|
|
||||||
evpmd = EVP_sha1();
|
|
||||||
} else if (strcmp(sha, "sha512") == 0) {
|
|
||||||
evpmd = EVP_sha512();
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = PKCS5_PBKDF2_HMAC(password, strlen(password),
|
|
||||||
(unsigned char *)salt, saltlen,
|
|
||||||
iterations,
|
|
||||||
evpmd, keylen, out);
|
|
||||||
if (rc != 1) {
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
blen = base64_encode(out, keylen, &b64);
|
|
||||||
if (blen > 0) {
|
|
||||||
int i, diff = 0, hlen = strlen(h_pw);
|
|
||||||
#ifdef PWDEBUG
|
|
||||||
fprintf(stderr, "HMAC b64 =[%s]\n", b64);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* "manual" strcmp() to ensure constant time */
|
|
||||||
for (i = 0; (i < blen) && (i < hlen); i++) {
|
|
||||||
diff |= h_pw[i] ^ b64[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
match = diff == 0;
|
|
||||||
if (hlen != blen)
|
|
||||||
match = 0;
|
|
||||||
|
|
||||||
free(b64);
|
|
||||||
}
|
|
||||||
|
|
||||||
out:
|
|
||||||
free(sha);
|
|
||||||
free(salt);
|
|
||||||
free(h_pw);
|
|
||||||
free(out);
|
|
||||||
|
|
||||||
if(match == 0){
|
|
||||||
ret = make_atom(env, "false");
|
|
||||||
}else{
|
|
||||||
ret = make_atom(env, "true");
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pbkdf2_check_native(char *password, char *hash)
|
|
||||||
{
|
|
||||||
static char *sha, *salt, *h_pw;
|
|
||||||
int iterations, saltlen, blen;
|
|
||||||
char *b64;
|
|
||||||
unsigned char key[128];
|
|
||||||
int match = FALSE;
|
|
||||||
const EVP_MD *evpmd;
|
|
||||||
|
|
||||||
if (detoken(hash, &sha, &iterations, &salt, &h_pw) != 0)
|
|
||||||
return match;
|
|
||||||
|
|
||||||
#ifdef PWDEBUG
|
|
||||||
fprintf(stderr, "sha =[%s]\n", sha);
|
|
||||||
fprintf(stderr, "iterations =%d\n", iterations);
|
|
||||||
fprintf(stderr, "salt =[%s]\n", salt);
|
|
||||||
fprintf(stderr, "h_pw =[%s]\n", h_pw);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
saltlen = strlen((char *)salt);
|
|
||||||
|
|
||||||
evpmd = EVP_sha256();
|
|
||||||
if (strcmp(sha, "sha1") == 0) {
|
|
||||||
evpmd = EVP_sha1();
|
|
||||||
} else if (strcmp(sha, "sha512") == 0) {
|
|
||||||
evpmd = EVP_sha512();
|
|
||||||
}
|
|
||||||
|
|
||||||
PKCS5_PBKDF2_HMAC(password, strlen(password),
|
|
||||||
(unsigned char *)salt, saltlen,
|
|
||||||
iterations,
|
|
||||||
evpmd, KEY_LENGTH, key);
|
|
||||||
|
|
||||||
blen = base64_encode(key, KEY_LENGTH, &b64);
|
|
||||||
if (blen > 0) {
|
|
||||||
int i, diff = 0, hlen = strlen(h_pw);
|
|
||||||
#ifdef PWDEBUG
|
|
||||||
fprintf(stderr, "HMAC b64 =[%s]\n", b64);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* "manual" strcmp() to ensure constant time */
|
|
||||||
for (i = 0; (i < blen) && (i < hlen); i++) {
|
|
||||||
diff |= h_pw[i] ^ b64[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
match = diff == 0;
|
|
||||||
if (hlen != blen)
|
|
||||||
match = 0;
|
|
||||||
|
|
||||||
free(b64);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(sha);
|
|
||||||
free(salt);
|
|
||||||
free(h_pw);
|
|
||||||
|
|
||||||
return match;
|
|
||||||
}
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
// char password[] = "hello";
|
|
||||||
// char PB1[] = "PBKDF2$sha256$10000$eytf9sEo8EprP9P3$2eO6tROHiqI3bm+gg+vpmWooWMpz1zji";
|
|
||||||
char password[] = "supersecret";
|
|
||||||
//char PB1[] = "PBKDF2$sha256$10000$YEbSTt8FaMRDq/ib$Kt97+sMCYg00mqMOBAYinqZlnxX8HqHk";
|
|
||||||
char PB1[] = "pbkdf2_sha256$10000$YEbSTt8FaMRDq/ib$Kt97+sMCYg00mqMOBAYinqZlnxX8HqHk";
|
|
||||||
// char PB1[] = "PBKDF2$sha1$10000$XWfyPLeC9gsD6SbI$HOnjU4Ux7RpeBHdqYxpIGH1R5qCCtNA1";
|
|
||||||
// char PB1[] = "PBKDF2$sha512$10000$v/aaCgBZ+VZN5L8n$BpgjSTyb4weVxr9cA2mvQ+jaCyaAPeYe";
|
|
||||||
int match;
|
|
||||||
|
|
||||||
printf("Checking password [%s] for %s\n", password, PB1);
|
|
||||||
|
|
||||||
match = pbkdf2_check_native(password, PB1);
|
|
||||||
printf("match == %d\n", match);
|
|
||||||
return match;
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
// This file is part of Jiffy released under the MIT license.
|
|
||||||
// See the LICENSE file for more information.
|
|
||||||
|
|
||||||
#include "emqttd_plugin_mysql_app.h"
|
|
||||||
|
|
||||||
ERL_NIF_TERM
|
|
||||||
make_atom(ErlNifEnv* env, const char* name)
|
|
||||||
{
|
|
||||||
ERL_NIF_TERM ret;
|
|
||||||
if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return enif_make_atom(env, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
ERL_NIF_TERM
|
|
||||||
make_ok(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, ERL_NIF_TERM value)
|
|
||||||
{
|
|
||||||
return enif_make_tuple2(env, st->atom_ok, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
ERL_NIF_TERM
|
|
||||||
make_error(emqttd_plugin_mysql_app_st* st, ErlNifEnv* env, const char* error)
|
|
||||||
{
|
|
||||||
return enif_make_tuple2(env, st->atom_error, make_atom(env, error));
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
[
|
|
||||||
{emysql, [
|
|
||||||
{pool, 4},
|
|
||||||
{host, "localhost"},
|
|
||||||
{port, 3306},
|
|
||||||
{username, "root"},
|
|
||||||
{password, "root"},
|
|
||||||
{database, "emqtt"},
|
|
||||||
{encoding, utf8}
|
|
||||||
]},
|
|
||||||
{emqttd_plugin_mysql, [
|
|
||||||
{users_table, auth_user},
|
|
||||||
{acls_table, auth_acl},
|
|
||||||
{field_mapper, [
|
|
||||||
{username, username},
|
|
||||||
{password, password, pbkdf2},
|
|
||||||
{user_super, is_super_user},
|
|
||||||
{acl_username, username},
|
|
||||||
{acl_rw, rw},
|
|
||||||
{acl_topic, topic}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
].
|
|
|
@ -1,23 +0,0 @@
|
||||||
[
|
|
||||||
{emysql, [
|
|
||||||
{pool, 4},
|
|
||||||
{host, "59.188.253.198"},
|
|
||||||
{port, 3306},
|
|
||||||
{username, "root"},
|
|
||||||
{password, "lhroot."},
|
|
||||||
{database, "musicfield"},
|
|
||||||
{encoding, utf8}
|
|
||||||
]},
|
|
||||||
{emqttd_plugin_mysql, [
|
|
||||||
{users_table, auth_user},
|
|
||||||
{acls_table, auth_acl},
|
|
||||||
{field_mapper, [
|
|
||||||
{username, username},
|
|
||||||
{password, password, pbkdf2},
|
|
||||||
{user_super, is_super_user},
|
|
||||||
{acl_username, username},
|
|
||||||
{acl_rw, rw},
|
|
||||||
{acl_topic, topic}
|
|
||||||
]}
|
|
||||||
]}
|
|
||||||
].
|
|
Binary file not shown.
|
@ -1,32 +0,0 @@
|
||||||
{port_specs, [
|
|
||||||
{"priv/emqttd_plugin_mysql_app.so", [
|
|
||||||
"c_src/*.c"
|
|
||||||
]}
|
|
||||||
]}.
|
|
||||||
|
|
||||||
{port_env, [
|
|
||||||
{".*", "CXXFLAGS", "$CXXFLAGS -g -Wall -Werror -O3"},
|
|
||||||
|
|
||||||
{"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin)",
|
|
||||||
"LDFLAGS", "$LDFLAGS -lstdc++ -lcrypto"},
|
|
||||||
|
|
||||||
%% OS X Leopard flags for 64-bit
|
|
||||||
{"darwin9.*-64$", "CXXFLAGS", "-m64"},
|
|
||||||
{"darwin9.*-64$", "LDFLAGS", "-arch x86_64"},
|
|
||||||
|
|
||||||
%% OS X Snow Leopard flags for 32-bit
|
|
||||||
{"darwin10.*-32$", "CXXFLAGS", "-m32"},
|
|
||||||
{"darwin10.*-32$", "LDFLAGS", "-arch i386"},
|
|
||||||
|
|
||||||
%% This will merge into basho/rebar/rebar.config eventually
|
|
||||||
{"win32", "CFLAGS", "/Wall /DWIN32 /D_WINDOWS /D_WIN32 /DWINDOWS"},
|
|
||||||
{"win32", "CXXFLAGS", "-g -Wall -O3"}
|
|
||||||
]}.
|
|
||||||
|
|
||||||
|
|
||||||
{eunit_opts, [
|
|
||||||
verbose,
|
|
||||||
{report, {
|
|
||||||
eunit_surefire, [{dir,"."}]
|
|
||||||
}}
|
|
||||||
]}.
|
|
|
@ -1,70 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% @Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>
|
|
||||||
%%%
|
|
||||||
%%% 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 demo acl module.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_acl_mysql).
|
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-behaviour(emqttd_acl_mod).
|
|
||||||
|
|
||||||
%% ACL callbacks
|
|
||||||
-export([init/1, check_acl/2, reload_acl/1, description/0]).
|
|
||||||
-record(state, {user_table, acl_table, acl_username_field, acl_topic_field, acl_rw_field, user_name_field, user_super_field}).
|
|
||||||
|
|
||||||
init(Opts) ->
|
|
||||||
Mapper = proplists:get_value(field_mapper, Opts),
|
|
||||||
State =
|
|
||||||
#state{
|
|
||||||
user_table = proplists:get_value(users_table, Opts, auth_user),
|
|
||||||
user_super_field = proplists:get_value(is_super, Mapper, is_superuser),
|
|
||||||
user_name_field = proplists:get_value(username, Mapper, username),
|
|
||||||
acl_table = proplists:get_value(acls_table, Opts, auth_acl),
|
|
||||||
acl_username_field = proplists:get_value(acl_username, Mapper, username),
|
|
||||||
acl_rw_field = proplists:get_value(acl_rw, Mapper, rw),
|
|
||||||
acl_topic_field = proplists:get_value(acl_topic, Mapper, topic)
|
|
||||||
},
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
check_acl({#mqtt_client{username = Username}, PubSub, Topic}, #state{user_table = UserTab, acl_table = AclTab, user_name_field = UsernameField, user_super_field = SuperField, acl_topic_field = TopicField, acl_username_field = AclUserField, acl_rw_field = AclRwField}) ->
|
|
||||||
Flag = case PubSub of publish -> 2; subscribe -> 1; pubsub -> 2 end,
|
|
||||||
Where = {'and', {'>=', AclRwField, Flag}, {TopicField, Topic}},
|
|
||||||
Where1 = {'or', {AclUserField, Username}, {AclUserField, "*"}},
|
|
||||||
Where2 = {'and', Where, Where1},
|
|
||||||
case emysql:select(UserTab, {'and', {UsernameField, Username}, {SuperField, 1}}) of
|
|
||||||
{ok, []} ->
|
|
||||||
case emysql:select(UserTab, {UsernameField, Username}) of
|
|
||||||
{ok, []} -> ignore;
|
|
||||||
{ok, _} -> case emysql:select(AclTab, Where2) of
|
|
||||||
{ok, []} -> deny;
|
|
||||||
{ok, _Record} -> allow
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
{ok, _} -> allow
|
|
||||||
end.
|
|
||||||
|
|
||||||
reload_acl(_State) -> ok.
|
|
||||||
|
|
||||||
description() -> "ACL Module by Mysql".
|
|
|
@ -1,110 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 by mysql 'user' table.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_auth_mysql).
|
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
|
||||||
|
|
||||||
-behaviour(emqttd_auth_mod).
|
|
||||||
|
|
||||||
-export([init/1, check/3, description/0]).
|
|
||||||
|
|
||||||
-define(NOT_LOADED, not_loaded(?LINE)).
|
|
||||||
|
|
||||||
-record(state, {user_table, name_field, pass_field, pass_hash}).
|
|
||||||
|
|
||||||
init(Opts) ->
|
|
||||||
Mapper = proplists:get_value(field_mapper, Opts),
|
|
||||||
{ok, #state{user_table = proplists:get_value(user_table, Opts, auth_user),
|
|
||||||
name_field = proplists:get_value(username, Mapper),
|
|
||||||
pass_field = proplists:get_value(password, Mapper),
|
|
||||||
pass_hash = proplists:get_value(Opts, password_hash)}}.
|
|
||||||
|
|
||||||
check(#mqtt_client{username = undefined}, _Password, _State) ->
|
|
||||||
{error, "Username undefined"};
|
|
||||||
check(_Client, undefined, _State) ->
|
|
||||||
{error, "Password undefined"};
|
|
||||||
check(#mqtt_client{username = Username}, Password,
|
|
||||||
#state{user_table = UserTab, pass_hash = Type,
|
|
||||||
name_field = NameField, pass_field = PassField}) ->
|
|
||||||
Where = {'and', {NameField, Username}, {PassField, hash(Type, Password)}},
|
|
||||||
if Type =:= pbkdf2 ->
|
|
||||||
case emysql:select(UserTab, [PassField], {NameField, Username}) of
|
|
||||||
{ok, []} -> {error, "User not exist"};
|
|
||||||
{ok, Records} ->
|
|
||||||
if length(Records) =:= 1 ->
|
|
||||||
case pbkdf2_check(Password, lists:nth(Records, 1)) of
|
|
||||||
true ->
|
|
||||||
{ok, []};
|
|
||||||
false ->
|
|
||||||
{error, "UserName or Password is invalid"};
|
|
||||||
ErrorInfo ->
|
|
||||||
{error, ErrorInfo}
|
|
||||||
end;
|
|
||||||
true ->
|
|
||||||
{error, "UserName is ambiguous"}
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
true ->
|
|
||||||
case emysql:select(UserTab, Where) of
|
|
||||||
{ok, []} -> {error, "Username or Password "};
|
|
||||||
{ok, _Record} -> ok
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
description() -> "Authentication by MySQL".
|
|
||||||
|
|
||||||
hash(plain, Password) ->
|
|
||||||
Password;
|
|
||||||
|
|
||||||
hash(md5, Password) ->
|
|
||||||
hexstring(crypto:hash(md5, Password));
|
|
||||||
|
|
||||||
hash(sha, Password) ->
|
|
||||||
hexstring(crypto:hash(sha, Password)).
|
|
||||||
|
|
||||||
hexstring(<<X:128/big-unsigned-integer>>) ->
|
|
||||||
lists:flatten(io_lib:format("~32.16.0b", [X]));
|
|
||||||
|
|
||||||
hexstring(<<X:160/big-unsigned-integer>>) ->
|
|
||||||
lists:flatten(io_lib:format("~40.16.0b", [X])).
|
|
||||||
|
|
||||||
not_loaded(Line) ->
|
|
||||||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).
|
|
||||||
|
|
||||||
pbkdf2_check(Password, Pbkstr) ->
|
|
||||||
case nif_pbkdf2_check(Password, Pbkstr) of
|
|
||||||
{error, _} = Error ->
|
|
||||||
throw(Error);
|
|
||||||
IOData ->
|
|
||||||
IOData
|
|
||||||
end.
|
|
||||||
|
|
||||||
nif_pbkdf2_check(Password, Pbkstr) ->
|
|
||||||
?NOT_LOADED.
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{application, emqttd_plugin_mysql,
|
|
||||||
[
|
|
||||||
{description, "emqttd MySQL Authentication Plugin"},
|
|
||||||
{vsn, "1.0"},
|
|
||||||
{registered, []},
|
|
||||||
{applications, [
|
|
||||||
kernel,
|
|
||||||
stdlib
|
|
||||||
]},
|
|
||||||
{mod, {emqttd_plugin_mysql_app, []}},
|
|
||||||
{env, []}
|
|
||||||
]}.
|
|
|
@ -1,80 +0,0 @@
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
%%% Copyright (c) 2012-2015 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 mysql authentication app.
|
|
||||||
%%%
|
|
||||||
%%% @end
|
|
||||||
%%%-----------------------------------------------------------------------------
|
|
||||||
-module(emqttd_plugin_mysql_app).
|
|
||||||
-on_load(init/0).
|
|
||||||
-behaviour(application).
|
|
||||||
%% Application callbacks
|
|
||||||
-export([start/2, prep_stop/1, stop/1, nif_pbkdf2_check/2]).
|
|
||||||
|
|
||||||
-behaviour(supervisor).
|
|
||||||
%% Supervisor callbacks
|
|
||||||
-export([init/1]).
|
|
||||||
-define(NOT_LOADED, not_loaded(?LINE)).
|
|
||||||
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Application callbacks
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
start(_StartType, _StartArgs) ->
|
|
||||||
Env = application:get_all_env(),
|
|
||||||
emqttd_access_control:register_mod(auth, emqttd_auth_mysql, Env),
|
|
||||||
emqttd_access_control:register_mod(acl, emqttd_acl_mysql, Env),
|
|
||||||
crypto:start(),
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
||||||
|
|
||||||
prep_stop(State) ->
|
|
||||||
emqttd_access_control:unregister_mod(auth, emqttd_auth_mysql), State,
|
|
||||||
emqttd_access_control:unregister_mod(acl, emqttd_acl_mysql), State,
|
|
||||||
crypto:stop().
|
|
||||||
|
|
||||||
stop(_State) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
init() ->
|
|
||||||
PrivDir = case code:priv_dir(?MODULE) of
|
|
||||||
{error, _} ->
|
|
||||||
EbinDir = filename:dirname(code:which(?MODULE)),
|
|
||||||
AppPath = filename:dirname(EbinDir),
|
|
||||||
filename:join(AppPath, "priv");
|
|
||||||
Path ->
|
|
||||||
Path
|
|
||||||
end,
|
|
||||||
erlang:load_nif(filename:join(PrivDir, "emqttd_plugin_mysql_app"), 0).
|
|
||||||
|
|
||||||
%%%=============================================================================
|
|
||||||
%%% Supervisor callbacks(Dummy)
|
|
||||||
%%%=============================================================================
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
{ok, {{one_for_one, 5, 10}, []}}.
|
|
||||||
|
|
||||||
not_loaded(Line) ->
|
|
||||||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).
|
|
||||||
|
|
||||||
nif_pbkdf2_check(Password, Hash) ->
|
|
||||||
?NOT_LOADED.
|
|
|
@ -1,42 +0,0 @@
|
||||||
# emysql
|
|
||||||
|
|
||||||
Erlang MySQL client
|
|
||||||
|
|
||||||
## config
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## Select API
|
|
||||||
|
|
||||||
* emyssql:select(tab).
|
|
||||||
* emysql:select({tab, [col1,col2]}).
|
|
||||||
* emysql:select({tab, [col1, col2], {id,1}}).
|
|
||||||
* emysql:select(Query, Load).
|
|
||||||
|
|
||||||
## Update API
|
|
||||||
|
|
||||||
* emysql:update(tab, [{Field1, Val}, {Field2, Val2}], {id, 1}).
|
|
||||||
|
|
||||||
## Insert API
|
|
||||||
|
|
||||||
* emysql:insert(tab, [{Field1, Val}, {Field2, Val2}]).
|
|
||||||
|
|
||||||
## Delete API
|
|
||||||
|
|
||||||
* emysql:delete(tab, {name, Name}]).
|
|
||||||
|
|
||||||
## Query API
|
|
||||||
|
|
||||||
* emysql:sqlquery("select * from tab;").
|
|
||||||
|
|
||||||
## Prepare API
|
|
||||||
|
|
||||||
* emysql:prepare(find_with_id, "select * from tab where id = ?;").
|
|
||||||
* emysql:execute(find_with_id, [Id]).
|
|
||||||
* emysql:unprepare(find_with_id).
|
|
||||||
|
|
||||||
## MySQL Client Protocal
|
|
||||||
|
|
||||||
* http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol
|
|
|
@ -1,2 +0,0 @@
|
||||||
%% MySQL result record:
|
|
||||||
-record(mysql_result, {fieldinfo = [], rows = [], affectedrows = 0, insert_id =0, error = ""}).
|
|
|
@ -1,14 +0,0 @@
|
||||||
{application, emysql,
|
|
||||||
[{description, "Erlang MySQL Driver"},
|
|
||||||
{vsn, "1.0"},
|
|
||||||
{modules, [
|
|
||||||
emysql,
|
|
||||||
emysql_app,
|
|
||||||
emysql_sup,
|
|
||||||
emysql_auth,
|
|
||||||
emysql_conn,
|
|
||||||
emysql_recv]},
|
|
||||||
{registered, []},
|
|
||||||
{applications, [kernel, stdlib, sasl, crypto]},
|
|
||||||
{env, []},
|
|
||||||
{mod, {emysql_app, []}}]}.
|
|
|
@ -1,514 +0,0 @@
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% File : emysql.erl
|
|
||||||
%%% Author : Ery Lee <ery.lee@gmail.com>
|
|
||||||
%%% Purpose : Mysql access api.
|
|
||||||
%%% Created : 19 May 2009
|
|
||||||
%%% License : http://www.opengoss.com
|
|
||||||
%%%
|
|
||||||
%%% Copyright (C) 2012, www.opengoss.com
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
-module(emysql).
|
|
||||||
|
|
||||||
-author('ery.lee@gmail.com').
|
|
||||||
|
|
||||||
-include("emysql.hrl").
|
|
||||||
|
|
||||||
-export([start_link/1]).
|
|
||||||
|
|
||||||
-ifdef(use_specs).
|
|
||||||
|
|
||||||
-spec(conns/0 :: () -> list()).
|
|
||||||
|
|
||||||
-endif.
|
|
||||||
|
|
||||||
%command functions
|
|
||||||
-export([info/0,
|
|
||||||
pool/1,
|
|
||||||
conns/0]).
|
|
||||||
|
|
||||||
%sql functions
|
|
||||||
-export([insert/2,
|
|
||||||
insert/3,
|
|
||||||
select/1,
|
|
||||||
select/2,
|
|
||||||
select/3,
|
|
||||||
update/2,
|
|
||||||
update/3,
|
|
||||||
delete/1,
|
|
||||||
delete/2,
|
|
||||||
truncate/1,
|
|
||||||
prepare/2,
|
|
||||||
execute/1,
|
|
||||||
execute/2,
|
|
||||||
unprepare/1,
|
|
||||||
sqlquery/1,
|
|
||||||
sqlquery/2]).
|
|
||||||
|
|
||||||
-behavior(gen_server).
|
|
||||||
|
|
||||||
-export([init/1,
|
|
||||||
handle_call/3,
|
|
||||||
handle_cast/2,
|
|
||||||
handle_info/2,
|
|
||||||
terminate/2,
|
|
||||||
code_change/3]).
|
|
||||||
|
|
||||||
-record(state, {ids}).
|
|
||||||
|
|
||||||
%% External exports
|
|
||||||
-export([encode/1,
|
|
||||||
encode/2,
|
|
||||||
escape/1,
|
|
||||||
escape_like/1]).
|
|
||||||
|
|
||||||
start_link(PoolSize) ->
|
|
||||||
gen_server:start_link({local, ?MODULE}, ?MODULE, [PoolSize], []).
|
|
||||||
|
|
||||||
info() ->
|
|
||||||
[emysql_conn:info(Pid) || Pid <-
|
|
||||||
pg2:get_local_members(emysql_conn)].
|
|
||||||
|
|
||||||
%pool pool
|
|
||||||
pool(Id) ->
|
|
||||||
gen_server:cast(?MODULE, {pool, Id}).
|
|
||||||
|
|
||||||
conns() ->
|
|
||||||
gen_server:call(?MODULE, conns).
|
|
||||||
|
|
||||||
insert(Tab, Record) when is_atom(Tab) ->
|
|
||||||
sqlquery(encode_insert(Tab, Record)).
|
|
||||||
|
|
||||||
insert(_Tab, _Fields, Values) when length(Values) == 0 ->
|
|
||||||
{updated, {0, 0}};
|
|
||||||
|
|
||||||
insert(Tab, Fields, Values) when length(Values) > 0 ->
|
|
||||||
sqlquery(encode_insert(Tab, Fields, Values)).
|
|
||||||
|
|
||||||
encode_insert(Tab, Record) ->
|
|
||||||
{Fields, Values} = lists:unzip([{atom_to_list(F), encode(V)}
|
|
||||||
|| {F, V} <- Record]),
|
|
||||||
["insert into ", atom_to_list(Tab), "(",
|
|
||||||
string:join(Fields, ","), ") values(",
|
|
||||||
string:join(Values, ","), ");"].
|
|
||||||
|
|
||||||
encode_insert(Tab, Fields, Rows) ->
|
|
||||||
Encode = fun(Row) -> string:join([encode(V) || V <- Row], ",") end,
|
|
||||||
Rows1 = [lists:concat(["(", Encode(Row), ")"]) || Row <- Rows],
|
|
||||||
["insert into ", atom_to_list(Tab), "(",
|
|
||||||
string:join([atom_to_list(F) || F <- Fields], ","),
|
|
||||||
") values", string:join(Rows1, ","), ";"].
|
|
||||||
|
|
||||||
select(Tab) when is_atom(Tab) ->
|
|
||||||
sqlquery(encode_select(Tab));
|
|
||||||
|
|
||||||
select(Select) when is_tuple(Select) ->
|
|
||||||
sqlquery(encode_select(Select)).
|
|
||||||
|
|
||||||
select(Tab, Where) when is_atom(Tab) and is_tuple(Where) ->
|
|
||||||
sqlquery(encode_select({Tab, Where}));
|
|
||||||
|
|
||||||
select(Tab, Fields) when is_atom(Tab) and is_list(Fields) ->
|
|
||||||
sqlquery(encode_select({Tab, Fields}));
|
|
||||||
|
|
||||||
select(Select, Load) when is_tuple(Select) and is_integer(Load) ->
|
|
||||||
sqlquery(encode_select(Select), Load).
|
|
||||||
|
|
||||||
select(Tab, Fields, Where) when is_atom(Tab)
|
|
||||||
and is_list(Fields) and is_tuple(Where) ->
|
|
||||||
sqlquery(encode_select({Tab, Fields, Where})).
|
|
||||||
|
|
||||||
encode_select(Tab) when is_atom(Tab) ->
|
|
||||||
encode_select({Tab, ['*'], undefined});
|
|
||||||
|
|
||||||
encode_select({Tab, Fields}) when is_atom(Tab)
|
|
||||||
and is_list(Fields) ->
|
|
||||||
encode_select({Tab, Fields, undefined});
|
|
||||||
|
|
||||||
encode_select({Tab, Where}) when is_atom(Tab)
|
|
||||||
and is_tuple(Where) ->
|
|
||||||
encode_select({Tab, ['*'], Where});
|
|
||||||
|
|
||||||
encode_select({Tab, Fields, undefined}) when is_atom(Tab)
|
|
||||||
and is_list(Fields) ->
|
|
||||||
["select ", encode_fields(Fields), " from ", atom_to_list(Tab), ";"];
|
|
||||||
|
|
||||||
encode_select({Tab, Fields, Where}) when is_atom(Tab)
|
|
||||||
and is_list(Fields) and is_tuple(Where) ->
|
|
||||||
["select ", encode_fields(Fields), " from ",
|
|
||||||
atom_to_list(Tab), " where ", encode_where(Where), ";"].
|
|
||||||
|
|
||||||
encode_fields(Fields) ->
|
|
||||||
string:join([atom_to_list(F) || F <- Fields], " ,").
|
|
||||||
|
|
||||||
update(Tab, Record) when is_atom(Tab)
|
|
||||||
and is_list(Record) ->
|
|
||||||
case proplists:get_value(id, Record) of
|
|
||||||
undefined ->
|
|
||||||
Updates = string:join([encode_column(Col) || Col <- Record], ","),
|
|
||||||
Query = ["update ", atom_to_list(Tab), " set ", Updates, ";"],
|
|
||||||
sqlquery(Query);
|
|
||||||
Id ->
|
|
||||||
update(Tab, lists:keydelete(id, 1, Record), {id, Id})
|
|
||||||
end.
|
|
||||||
|
|
||||||
update(Tab, Record, Where) ->
|
|
||||||
Update = string:join([encode_column(Col) || Col <- Record], ","),
|
|
||||||
Query = ["update ", atom_to_list(Tab), " set ", Update,
|
|
||||||
" where ", encode_where(Where), ";"],
|
|
||||||
sqlquery(Query).
|
|
||||||
|
|
||||||
encode_column({F, V}) when is_atom(F) ->
|
|
||||||
lists:concat([atom_to_list(F), "=", encode(V)]).
|
|
||||||
|
|
||||||
delete(Tab) when is_atom(Tab) ->
|
|
||||||
sqlquery(["delete from ", atom_to_list(Tab), ";"]).
|
|
||||||
|
|
||||||
delete(Tab, Id) when is_atom(Tab)
|
|
||||||
and is_integer(Id) ->
|
|
||||||
Query = ["delete from ", atom_to_list(Tab),
|
|
||||||
" where ", encode_where({id, Id})],
|
|
||||||
sqlquery(Query);
|
|
||||||
|
|
||||||
delete(Tab, Where) when is_atom(Tab)
|
|
||||||
and is_tuple(Where) ->
|
|
||||||
Query = ["delete from ", atom_to_list(Tab),
|
|
||||||
" where ", encode_where(Where)],
|
|
||||||
sqlquery(Query).
|
|
||||||
|
|
||||||
truncate(Tab) when is_atom(Tab) ->
|
|
||||||
sqlquery(["truncate table ", atom_to_list(Tab), ";"]).
|
|
||||||
|
|
||||||
sqlquery(Query) ->
|
|
||||||
sqlquery(Query, 1).
|
|
||||||
|
|
||||||
sqlquery(Query, Load) ->
|
|
||||||
with_next_conn(fun(Conn) ->
|
|
||||||
case catch mysql_to_odbc(emysql_conn:sqlquery(Conn, iolist_to_binary(Query))) of
|
|
||||||
{selected, NewFields, Records} ->
|
|
||||||
{ok, to_tuple_records(NewFields, Records)};
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason};
|
|
||||||
Res ->
|
|
||||||
Res
|
|
||||||
end
|
|
||||||
end, Load).
|
|
||||||
|
|
||||||
prepare(Name, Stmt) when is_list(Stmt) ->
|
|
||||||
prepare(Name, list_to_binary(Stmt));
|
|
||||||
|
|
||||||
prepare(Name, Stmt) when is_binary(Stmt) ->
|
|
||||||
with_all_conns(fun(Conn) ->
|
|
||||||
emysql_conn:prepare(Conn, Name, Stmt)
|
|
||||||
end).
|
|
||||||
|
|
||||||
execute(Name) ->
|
|
||||||
execute(Name, []).
|
|
||||||
|
|
||||||
execute(Name, Params) ->
|
|
||||||
with_next_conn(fun(Conn) ->
|
|
||||||
case catch mysql_to_odbc(emysql_conn:execute(Conn, Name, Params)) of
|
|
||||||
{selected, NewFields, Records} ->
|
|
||||||
{ok, to_tuple_records(NewFields, Records)};
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason};
|
|
||||||
Res ->
|
|
||||||
Res
|
|
||||||
end
|
|
||||||
end, 1).
|
|
||||||
|
|
||||||
unprepare(Name) ->
|
|
||||||
with_all_conns(fun(Conn) ->
|
|
||||||
emysql_conn:unprepare(Conn, Name)
|
|
||||||
end).
|
|
||||||
|
|
||||||
with_next_conn(Fun, _Load) ->
|
|
||||||
Fun(pg2:get_closest_pid(emysql_conn)).
|
|
||||||
|
|
||||||
with_all_conns(Fun) ->
|
|
||||||
[Fun(Pid) || Pid <- pg2:get_local_members(emysql_conn)].
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: init(Args) -> {ok, State} |
|
|
||||||
%% {ok, State, Timeout} |
|
|
||||||
%% ignore |
|
|
||||||
%% {stop, Reason}
|
|
||||||
%% Description: Initiates the server
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
init([PoolSize]) ->
|
|
||||||
Ids = lists:seq(1, PoolSize),
|
|
||||||
[put(Id, 0) || Id <- Ids],
|
|
||||||
[put({count, Id}, 0) || Id <- Ids],
|
|
||||||
{ok, #state{ids = Ids}}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
|
|
||||||
%% {reply, Reply, State, Timeout} |
|
|
||||||
%% {noreply, State} |
|
|
||||||
%% {noreply, State, Timeout} |
|
|
||||||
%% {stop, Reason, Reply, State} |
|
|
||||||
%% {stop, Reason, State}
|
|
||||||
%% Description: Handling call messages
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
handle_call(info, _From, State) ->
|
|
||||||
Reply = [{conn, Id, Pid, get(Id), get({total, Id})}
|
|
||||||
|| {Id, Pid} <- get_all_conns()],
|
|
||||||
{reply, Reply, State};
|
|
||||||
|
|
||||||
handle_call({next_conn, Load}, _From, #state{ids = Ids} = State) ->
|
|
||||||
{ConnId, ConnLoad} =
|
|
||||||
lists:foldl(fun(Id, {MinId, MinLoad}) ->
|
|
||||||
ThisLoad = get(Id),
|
|
||||||
if
|
|
||||||
ThisLoad =< MinLoad -> {Id, ThisLoad};
|
|
||||||
true -> {MinId, MinLoad}
|
|
||||||
end
|
|
||||||
end, {undefined, 16#ffffffff}, Ids),
|
|
||||||
Reply =
|
|
||||||
case ConnId of
|
|
||||||
undefined ->
|
|
||||||
undefined;
|
|
||||||
_ ->
|
|
||||||
ConnPid = get_conn_pid(ConnId),
|
|
||||||
put(ConnId, ConnLoad+Load),
|
|
||||||
Count = get({total, ConnId}),
|
|
||||||
put({total, ConnId}, Count+1),
|
|
||||||
{ConnId, ConnPid}
|
|
||||||
end,
|
|
||||||
{reply, Reply, State};
|
|
||||||
|
|
||||||
handle_call(conns, _From, State) ->
|
|
||||||
Conns = get_all_conns(),
|
|
||||||
{reply, Conns, State};
|
|
||||||
|
|
||||||
handle_call(Req, From, State) ->
|
|
||||||
gen_server:reply(From, {badcall, Req}),
|
|
||||||
{stop, {badcall, Req}, State}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: handle_cast(Msg, State) -> {noreply, State} |
|
|
||||||
%% {noreply, State, Timeout} |
|
|
||||||
%% {stop, Reason, State}
|
|
||||||
%% Description: Handling cast messages
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
handle_cast({pool, Id}, State) ->
|
|
||||||
put(Id, 0),
|
|
||||||
put({total, Id}, 0),
|
|
||||||
{noreply, State};
|
|
||||||
|
|
||||||
handle_cast({done, ConnId, Load}, State) ->
|
|
||||||
put(ConnId, get(ConnId) - Load),
|
|
||||||
{noreply, State};
|
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
|
||||||
{stop, {badcast, Msg}, State}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: handle_info(Info, State) -> {noreply, State} |
|
|
||||||
%% {noreply, State, Timeout} |
|
|
||||||
%% {stop, Reason, State}
|
|
||||||
%% Description: Handling all non call/cast messages
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
handle_info(Info, State) ->
|
|
||||||
{stop, {badinfo, Info}, State}.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: terminate(Reason, State) -> void()
|
|
||||||
%% Description: This function is called by a gen_server when it is about to
|
|
||||||
%% terminate. It should be the opposite of Module:init/1 and do any necessary
|
|
||||||
%% cleaning up. When it returns, the gen_server terminates with Reason.
|
|
||||||
%% The return value is ignored.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
terminate(_Reason, _State) ->
|
|
||||||
ok.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
|
|
||||||
%% Description: Convert process state when code is changed
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
get_conn_pid(CId) ->
|
|
||||||
[{CId, Pid, _Type, _Modules} | _] =
|
|
||||||
lists:dropwhile(fun ({Id, _Pid, _Type, _Modules})
|
|
||||||
when Id =:= CId -> false;
|
|
||||||
(_) -> true
|
|
||||||
end,
|
|
||||||
supervisor:which_children(emysql_sup)),
|
|
||||||
Pid.
|
|
||||||
|
|
||||||
get_all_conns() ->
|
|
||||||
[{Id, Pid} || {Id, Pid, _Type, _Modules} <-
|
|
||||||
supervisor:which_children(emysql_sup), is_integer(Id)].
|
|
||||||
|
|
||||||
%% Convert MySQL query result to Erlang ODBC result formalism
|
|
||||||
mysql_to_odbc({updated, #mysql_result{affectedrows=AffectedRows, insert_id = InsertId} = _MySQLRes}) ->
|
|
||||||
{updated, {AffectedRows, InsertId}};
|
|
||||||
|
|
||||||
mysql_to_odbc({data, #mysql_result{fieldinfo = FieldInfo, rows=AllRows} = _MySQLRes}) ->
|
|
||||||
mysql_item_to_odbc(FieldInfo, AllRows);
|
|
||||||
|
|
||||||
mysql_to_odbc({error, MySQLRes}) when is_list(MySQLRes) ->
|
|
||||||
{error, MySQLRes};
|
|
||||||
|
|
||||||
mysql_to_odbc({error, #mysql_result{error=Reason} = _MySQLRes}) ->
|
|
||||||
{error, Reason};
|
|
||||||
|
|
||||||
mysql_to_odbc({error, Reason}) ->
|
|
||||||
{error, Reason}.
|
|
||||||
|
|
||||||
%% When tabular data is returned, convert it to the ODBC formalism
|
|
||||||
mysql_item_to_odbc(Columns, Recs) ->
|
|
||||||
%% For now, there is a bug and we do not get the correct value from MySQL
|
|
||||||
%% module:
|
|
||||||
{selected,
|
|
||||||
[element(2, Column) || Column <- Columns],
|
|
||||||
[list_to_tuple(Rec) || Rec <- Recs]}.
|
|
||||||
|
|
||||||
%%internal functions
|
|
||||||
encode_where({'and', L, R}) ->
|
|
||||||
encode_where(L) ++ " and " ++ encode_where(R);
|
|
||||||
|
|
||||||
encode_where({'and', List}) when is_list(List) ->
|
|
||||||
string:join([encode_where(E) || E <- List], " and ");
|
|
||||||
|
|
||||||
encode_where({'or', L, R}) ->
|
|
||||||
encode_where(L) ++ " or " ++ encode_where(R);
|
|
||||||
|
|
||||||
encode_where({'or', List}) when is_list(List) ->
|
|
||||||
string:join([encode_where(E) || E <- List], " or ");
|
|
||||||
|
|
||||||
encode_where({like, Field, Value}) ->
|
|
||||||
atom_to_list(Field) ++ " like " ++ encode(Value);
|
|
||||||
|
|
||||||
encode_where({'<', Field, Value}) ->
|
|
||||||
atom_to_list(Field) ++ " < " ++ encode(Value);
|
|
||||||
|
|
||||||
encode_where({'<=', Field, Value}) ->
|
|
||||||
atom_to_list(Field) ++ " <= " ++ encode(Value);
|
|
||||||
|
|
||||||
encode_where({'>', Field, Value}) ->
|
|
||||||
atom_to_list(Field) ++ " > " ++ encode(Value);
|
|
||||||
|
|
||||||
encode_where({'>=', Field, Value}) ->
|
|
||||||
atom_to_list(Field) ++ " >= " ++ encode(Value);
|
|
||||||
|
|
||||||
encode_where({'in', Field, Values}) ->
|
|
||||||
InStr = string:join([encode(Value) || Value <- Values], ","),
|
|
||||||
atom_to_list(Field) ++ " in (" ++ InStr ++ ")";
|
|
||||||
|
|
||||||
encode_where({Field, Value}) ->
|
|
||||||
atom_to_list(Field) ++ " = " ++ encode(Value).
|
|
||||||
|
|
||||||
to_tuple_records(_Fields, []) ->
|
|
||||||
[];
|
|
||||||
|
|
||||||
to_tuple_records(Fields, Records) ->
|
|
||||||
[to_tuple_record(Fields, tuple_to_list(Record)) || Record <- Records].
|
|
||||||
|
|
||||||
to_tuple_record(Fields, Record) when length(Fields) == length(Record) ->
|
|
||||||
to_tuple_record(Fields, Record, []).
|
|
||||||
|
|
||||||
to_tuple_record([], [], Acc) ->
|
|
||||||
Acc;
|
|
||||||
|
|
||||||
to_tuple_record([_F|FT], [undefined|VT], Acc) ->
|
|
||||||
to_tuple_record(FT, VT, Acc);
|
|
||||||
|
|
||||||
to_tuple_record([F|FT], [V|VT], Acc) ->
|
|
||||||
to_tuple_record(FT, VT, [{list_to_atom(binary_to_list(F)), V} | Acc]).
|
|
||||||
|
|
||||||
%% Escape character that will confuse an SQL engine
|
|
||||||
%% Percent and underscore only need to be escaped for pattern matching like
|
|
||||||
%% statement
|
|
||||||
escape_like(S) when is_list(S) ->
|
|
||||||
[escape_like(C) || C <- S];
|
|
||||||
escape_like($%) -> "\\%";
|
|
||||||
escape_like($_) -> "\\_";
|
|
||||||
escape_like(C) -> escape(C).
|
|
||||||
|
|
||||||
%% Escape character that will confuse an SQL engine
|
|
||||||
escape(S) when is_list(S) ->
|
|
||||||
[escape(C) || C <- S];
|
|
||||||
%% Characters to escape
|
|
||||||
escape($\0) -> "\\0";
|
|
||||||
escape($\n) -> "\\n";
|
|
||||||
escape($\t) -> "\\t";
|
|
||||||
escape($\b) -> "\\b";
|
|
||||||
escape($\r) -> "\\r";
|
|
||||||
escape($') -> "\\'";
|
|
||||||
escape($") -> "\\\"";
|
|
||||||
escape($\\) -> "\\\\";
|
|
||||||
escape(C) -> C.
|
|
||||||
|
|
||||||
encode(Val) ->
|
|
||||||
encode(Val, false).
|
|
||||||
encode(Val, false) when Val == undefined; Val == null ->
|
|
||||||
"NULL";
|
|
||||||
encode(Val, true) when Val == undefined; Val == null ->
|
|
||||||
<<"NULL">>;
|
|
||||||
encode(Val, false) when is_binary(Val) ->
|
|
||||||
binary_to_list(quote(Val));
|
|
||||||
encode(Val, true) when is_binary(Val) ->
|
|
||||||
quote(Val);
|
|
||||||
encode(Val, true) ->
|
|
||||||
list_to_binary(encode(Val,false));
|
|
||||||
encode(Val, false) when is_atom(Val) ->
|
|
||||||
quote(atom_to_list(Val));
|
|
||||||
encode(Val, false) when is_list(Val) ->
|
|
||||||
quote(Val);
|
|
||||||
encode(Val, false) when is_integer(Val) ->
|
|
||||||
integer_to_list(Val);
|
|
||||||
encode(Val, false) when is_float(Val) ->
|
|
||||||
[Res] = io_lib:format("~w", [Val]),
|
|
||||||
Res;
|
|
||||||
encode({datetime, Val}, AsBinary) ->
|
|
||||||
encode(Val, AsBinary);
|
|
||||||
encode({{Year, Month, Day}, {Hour, Minute, Second}}, false) ->
|
|
||||||
Res = two_digits([Year, Month, Day, Hour, Minute, Second]),
|
|
||||||
lists:flatten(Res);
|
|
||||||
encode({TimeType, Val}, AsBinary)
|
|
||||||
when TimeType == 'date';
|
|
||||||
TimeType == 'time' ->
|
|
||||||
encode(Val, AsBinary);
|
|
||||||
encode({Time1, Time2, Time3}, false) ->
|
|
||||||
Res = two_digits([Time1, Time2, Time3]),
|
|
||||||
lists:flatten(Res);
|
|
||||||
encode(Val, _AsBinary) ->
|
|
||||||
{error, {unrecognized_value, Val}}.
|
|
||||||
|
|
||||||
two_digits(Nums) when is_list(Nums) ->
|
|
||||||
[two_digits(Num) || Num <- Nums];
|
|
||||||
two_digits(Num) ->
|
|
||||||
[Str] = io_lib:format("~b", [Num]),
|
|
||||||
case length(Str) of
|
|
||||||
1 -> [$0 | Str];
|
|
||||||
_ -> Str
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% Quote a string or binary value so that it can be included safely in a
|
|
||||||
%% MySQL query.
|
|
||||||
quote(String) when is_list(String) ->
|
|
||||||
[39 | lists:reverse([39 | quote(String, [])])]; %% 39 is $'
|
|
||||||
quote(Bin) when is_binary(Bin) ->
|
|
||||||
list_to_binary(quote(binary_to_list(Bin))).
|
|
||||||
|
|
||||||
quote([], Acc) ->
|
|
||||||
Acc;
|
|
||||||
quote([0 | Rest], Acc) ->
|
|
||||||
quote(Rest, [$0, $\\ | Acc]);
|
|
||||||
quote([10 | Rest], Acc) ->
|
|
||||||
quote(Rest, [$n, $\\ | Acc]);
|
|
||||||
quote([13 | Rest], Acc) ->
|
|
||||||
quote(Rest, [$r, $\\ | Acc]);
|
|
||||||
quote([$\\ | Rest], Acc) ->
|
|
||||||
quote(Rest, [$\\ , $\\ | Acc]);
|
|
||||||
quote([39 | Rest], Acc) -> %% 39 is $'
|
|
||||||
quote(Rest, [39, $\\ | Acc]); %% 39 is $'
|
|
||||||
quote([34 | Rest], Acc) -> %% 34 is $"
|
|
||||||
quote(Rest, [34, $\\ | Acc]); %% 34 is $"
|
|
||||||
quote([26 | Rest], Acc) ->
|
|
||||||
quote(Rest, [$Z, $\\ | Acc]);
|
|
||||||
quote([C | Rest], Acc) ->
|
|
||||||
quote(Rest, [C | Acc]).
|
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% File : emysql_app.erl
|
|
||||||
%%% Author : Ery Lee <ery.lee@gmail.com>
|
|
||||||
%%% Purpose : mysql driver application
|
|
||||||
%%% Created : 21 May 2009
|
|
||||||
%%% Updated : 11 Jan 2010
|
|
||||||
%%% License : http://www.opengoss.com
|
|
||||||
%%%
|
|
||||||
%%% Copyright (C) 2007-2010, www.opengoss.com
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
-module(emysql_app).
|
|
||||||
|
|
||||||
-author('ery.lee@gmail.com').
|
|
||||||
|
|
||||||
-behavior(application).
|
|
||||||
|
|
||||||
-export([start/0, start/2, stop/1]).
|
|
||||||
|
|
||||||
start() ->
|
|
||||||
application:start(emysql).
|
|
||||||
|
|
||||||
start(normal, _Args) ->
|
|
||||||
emysql_sup:start_link(application:get_all_env()).
|
|
||||||
|
|
||||||
stop(_) ->
|
|
||||||
ok.
|
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
-module(emysql_auth).
|
|
||||||
|
|
||||||
-export([make_auth/2, make_new_auth/3, password_old/2, password_new/2]).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Macros
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-define(LONG_PASSWORD, 1).
|
|
||||||
-define(LONG_FLAG, 4).
|
|
||||||
-define(PROTOCOL_41, 512).
|
|
||||||
-define(TRANSACTIONS, 8192).
|
|
||||||
-define(SECURE_CONNECTION, 32768).
|
|
||||||
-define(CONNECT_WITH_DB, 8).
|
|
||||||
-define(MAX_PACKET_SIZE, 1000000).
|
|
||||||
|
|
||||||
password_old(Password, Salt) ->
|
|
||||||
{P1, P2} = hash(Password),
|
|
||||||
{S1, S2} = hash(Salt),
|
|
||||||
Seed1 = P1 bxor S1,
|
|
||||||
Seed2 = P2 bxor S2,
|
|
||||||
List = rnd(9, Seed1, Seed2),
|
|
||||||
{L, [Extra]} = lists:split(8, List),
|
|
||||||
list_to_binary(lists:map(fun (E) -> E bxor (Extra - 64) end, L)).
|
|
||||||
|
|
||||||
%% part of do_old_auth/4, which is part of mysql_init/4
|
|
||||||
make_auth(User, Password) ->
|
|
||||||
Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS,
|
|
||||||
Maxsize = 0,
|
|
||||||
UserB = list_to_binary(User),
|
|
||||||
PasswordB = Password,
|
|
||||||
<<Caps:16/little, Maxsize:24/little, UserB/binary, 0:8,
|
|
||||||
PasswordB/binary>>.
|
|
||||||
|
|
||||||
%% part of do_new_auth/4, which is part of mysql_init/4
|
|
||||||
make_new_auth(User, Password, Database) ->
|
|
||||||
DBCaps = case Database of
|
|
||||||
none ->
|
|
||||||
0;
|
|
||||||
_ ->
|
|
||||||
?CONNECT_WITH_DB
|
|
||||||
end,
|
|
||||||
Caps = ?LONG_PASSWORD bor ?LONG_FLAG bor ?TRANSACTIONS bor
|
|
||||||
?PROTOCOL_41 bor ?SECURE_CONNECTION bor DBCaps,
|
|
||||||
Maxsize = ?MAX_PACKET_SIZE,
|
|
||||||
UserB = list_to_binary(User),
|
|
||||||
PasswordL = size(Password),
|
|
||||||
DatabaseB = case Database of
|
|
||||||
none ->
|
|
||||||
<<>>;
|
|
||||||
_ ->
|
|
||||||
list_to_binary(Database)
|
|
||||||
end,
|
|
||||||
<<Caps:32/little, Maxsize:32/little, 8:8, 0:23/integer-unit:8,
|
|
||||||
UserB/binary, 0:8, PasswordL:8, Password/binary, DatabaseB/binary>>.
|
|
||||||
|
|
||||||
hash(S) ->
|
|
||||||
hash(S, 1345345333, 305419889, 7).
|
|
||||||
|
|
||||||
hash([C | S], N1, N2, Add) ->
|
|
||||||
N1_1 = N1 bxor (((N1 band 63) + Add) * C + N1 * 256),
|
|
||||||
N2_1 = N2 + ((N2 * 256) bxor N1_1),
|
|
||||||
Add_1 = Add + C,
|
|
||||||
hash(S, N1_1, N2_1, Add_1);
|
|
||||||
hash([], N1, N2, _Add) ->
|
|
||||||
Mask = (1 bsl 31) - 1,
|
|
||||||
{N1 band Mask , N2 band Mask}.
|
|
||||||
|
|
||||||
rnd(N, Seed1, Seed2) ->
|
|
||||||
Mod = (1 bsl 30) - 1,
|
|
||||||
rnd(N, [], Seed1 rem Mod, Seed2 rem Mod).
|
|
||||||
|
|
||||||
rnd(0, List, _, _) ->
|
|
||||||
lists:reverse(List);
|
|
||||||
rnd(N, List, Seed1, Seed2) ->
|
|
||||||
Mod = (1 bsl 30) - 1,
|
|
||||||
NSeed1 = (Seed1 * 3 + Seed2) rem Mod,
|
|
||||||
NSeed2 = (NSeed1 + Seed2 + 33) rem Mod,
|
|
||||||
Float = (float(NSeed1) / float(Mod))*31,
|
|
||||||
Val = trunc(Float)+64,
|
|
||||||
rnd(N - 1, [Val | List], NSeed1, NSeed2).
|
|
||||||
|
|
||||||
|
|
||||||
dualmap(_F, [], []) ->
|
|
||||||
[];
|
|
||||||
dualmap(F, [E1 | R1], [E2 | R2]) ->
|
|
||||||
[F(E1, E2) | dualmap(F, R1, R2)].
|
|
||||||
|
|
||||||
bxor_binary(B1, B2) ->
|
|
||||||
list_to_binary(dualmap(fun (E1, E2) ->
|
|
||||||
E1 bxor E2
|
|
||||||
end, binary_to_list(B1), binary_to_list(B2))).
|
|
||||||
|
|
||||||
password_new(Password, Salt) ->
|
|
||||||
Stage1 = crypto:sha(Password),
|
|
||||||
Stage2 = crypto:sha(Stage1),
|
|
||||||
Res = crypto:sha_final(
|
|
||||||
crypto:sha_update(
|
|
||||||
crypto:sha_update(crypto:sha_init(), Salt),
|
|
||||||
Stage2)
|
|
||||||
),
|
|
||||||
bxor_binary(Res, Stage1).
|
|
||||||
|
|
|
@ -1,739 +0,0 @@
|
||||||
%%% File : emysql_conn.erl
|
|
||||||
%%% Author : Ery Lee
|
|
||||||
%%% Purpose : connection of mysql driver
|
|
||||||
%%% Created : 11 Jan 2010
|
|
||||||
%%% License : http://www.opengoss.com
|
|
||||||
%%%
|
|
||||||
%%% Copyright (C) 2012, www.opengoss.com
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
-module(emysql_conn).
|
|
||||||
|
|
||||||
-include("emysql.hrl").
|
|
||||||
|
|
||||||
-import(proplists, [get_value/2, get_value/3]).
|
|
||||||
|
|
||||||
-behaviour(gen_server).
|
|
||||||
|
|
||||||
%% External exports
|
|
||||||
-export([start_link/2,
|
|
||||||
info/1,
|
|
||||||
sqlquery/2,
|
|
||||||
sqlquery/3,
|
|
||||||
prepare/3,
|
|
||||||
execute/3,
|
|
||||||
execute/4,
|
|
||||||
unprepare/2]).
|
|
||||||
|
|
||||||
%% Callback
|
|
||||||
-export([init/1,
|
|
||||||
handle_call/3,
|
|
||||||
handle_cast/2,
|
|
||||||
handle_info/2,
|
|
||||||
terminate/2,
|
|
||||||
code_change/3]).
|
|
||||||
|
|
||||||
-record(state, {
|
|
||||||
id,
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
user,
|
|
||||||
password,
|
|
||||||
database,
|
|
||||||
encoding,
|
|
||||||
mysql_version,
|
|
||||||
recv_pid,
|
|
||||||
socket,
|
|
||||||
data}).
|
|
||||||
|
|
||||||
%%-define(KEEPALIVE_QUERY, <<"SELECT 1;">>).
|
|
||||||
|
|
||||||
-define(SECURE_CONNECTION, 32768).
|
|
||||||
|
|
||||||
-define(MYSQL_QUERY_OP, 3).
|
|
||||||
|
|
||||||
%CALL > CONNECT
|
|
||||||
-define(CALL_TIMEOUT, 301000).
|
|
||||||
|
|
||||||
-define(CONNECT_TIMEOUT, 300000).
|
|
||||||
|
|
||||||
-define(MYSQL_4_0, 40). %% Support for MySQL 4.0.x
|
|
||||||
|
|
||||||
-define(MYSQL_4_1, 41). %% Support for MySQL 4.1.x et 5.0.x
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: start(Opts)
|
|
||||||
%% Descrip.: Starts a mysql_conn process that connects to a MySQL
|
|
||||||
%% server, logs in and chooses a database.
|
|
||||||
%% Returns : {ok, Pid} | {error, Reason}
|
|
||||||
%% Pid = pid()
|
|
||||||
%% Reason = string()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
start_link(Id, Opts) ->
|
|
||||||
gen_server:start_link(?MODULE, [Id, Opts], []).
|
|
||||||
|
|
||||||
info(Conn) ->
|
|
||||||
gen_server:call(Conn, info).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: sqlquery(Query)
|
|
||||||
%% Queries = A single binary() query or a list of binary() queries.
|
|
||||||
%% If a list is provided, the return value is the return
|
|
||||||
%% of the last query, or the first query that has
|
|
||||||
%% returned an error. If an error occurs, execution of
|
|
||||||
%% the following queries is aborted.
|
|
||||||
%% From = pid() or term(), use a From of self() when
|
|
||||||
%% using this module for a single connection,
|
|
||||||
%% or pass the gen_server:call/3 From argument if
|
|
||||||
%% using a gen_server to do the querys (e.g. the
|
|
||||||
%% mysql_dispatcher)
|
|
||||||
%% Timeout = integer() | infinity, gen_server timeout value
|
|
||||||
%% Descrip.: Send a query or a list of queries and wait for the result
|
|
||||||
%% if running stand-alone (From = self()), but don't block
|
|
||||||
%% the caller if we are not running stand-alone
|
|
||||||
%% (From = gen_server From).
|
|
||||||
%% Returns : ok | (non-stand-alone mode)
|
|
||||||
%% {data, #mysql_result} | (stand-alone mode)
|
|
||||||
%% {updated, #mysql_result} | (stand-alone mode)
|
|
||||||
%% {error, #mysql_result} (stand-alone mode)
|
|
||||||
%% FieldInfo = term()
|
|
||||||
%% Rows = list() of [string()]
|
|
||||||
%% Reason = term()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
sqlquery(Conn, Query) ->
|
|
||||||
sqlquery(Conn, Query, ?CALL_TIMEOUT).
|
|
||||||
|
|
||||||
sqlquery(Conn, Query, Timeout) ->
|
|
||||||
call(Conn, {sqlquery, Query}, Timeout).
|
|
||||||
|
|
||||||
prepare(Conn, Name, Stmt) ->
|
|
||||||
call(Conn, {prepare, Name, Stmt}).
|
|
||||||
|
|
||||||
execute(Conn, Name, Params) ->
|
|
||||||
execute(Conn, Name, Params, ?CALL_TIMEOUT).
|
|
||||||
|
|
||||||
execute(Conn, Name, Params, Timeout) ->
|
|
||||||
call(Conn, {execute, Name, Params}, Timeout).
|
|
||||||
|
|
||||||
unprepare(Conn, Name) ->
|
|
||||||
call(Conn, {unprepare, Name}).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: init(Host, Port, User, Password, Database, Parent)
|
|
||||||
%% Host = string()
|
|
||||||
%% Port = integer()
|
|
||||||
%% User = string()
|
|
||||||
%% Password = string()
|
|
||||||
%% Database = string()
|
|
||||||
%% Parent = pid() of process starting this mysql_conn
|
|
||||||
%% Descrip.: Connect to a MySQL server, log in and chooses a database.
|
|
||||||
%% Report result of this to Parent, and then enter loop() if
|
|
||||||
%% we were successfull.
|
|
||||||
%% Returns : void() | does not return
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
init([Id, Opts]) ->
|
|
||||||
put(queries, 0),
|
|
||||||
Host = get_value(host, Opts, "localhost"),
|
|
||||||
Port = get_value(port, Opts, 3306),
|
|
||||||
UserName = get_value(username, Opts, "root"),
|
|
||||||
Password = get_value(password, Opts, "public"),
|
|
||||||
Database = get_value(database, Opts),
|
|
||||||
Encoding = get_value(encoding, Opts, utf8),
|
|
||||||
case emysql_recv:start_link(Host, Port) of
|
|
||||||
{ok, RecvPid, Sock} ->
|
|
||||||
case mysql_init(Sock, RecvPid, UserName, Password) of
|
|
||||||
{ok, Version} ->
|
|
||||||
Db = iolist_to_binary(Database),
|
|
||||||
case do_query(Sock, RecvPid, <<"use ", Db/binary>>, Version) of
|
|
||||||
{error, #mysql_result{error = Error} = _MySQLRes} ->
|
|
||||||
error_logger:error_msg("emysql_conn: use '~p' error: ~p", [Database, Error]),
|
|
||||||
{stop, using_db_error};
|
|
||||||
{_ResultType, _MySQLRes} ->
|
|
||||||
emysql:pool(Id), %pool it
|
|
||||||
pg2:create(emysql_conn),
|
|
||||||
pg2:join(emysql_conn, self()),
|
|
||||||
EncodingBinary = list_to_binary(atom_to_list(Encoding)),
|
|
||||||
do_query(Sock, RecvPid, <<"set names '", EncodingBinary/binary, "'">>, Version),
|
|
||||||
State = #state{
|
|
||||||
id = Id,
|
|
||||||
host = Host,
|
|
||||||
port = Port,
|
|
||||||
user = UserName,
|
|
||||||
password = Password,
|
|
||||||
database = Database,
|
|
||||||
encoding = Encoding,
|
|
||||||
mysql_version = Version,
|
|
||||||
recv_pid = RecvPid,
|
|
||||||
socket = Sock,
|
|
||||||
data = <<>>},
|
|
||||||
{ok, State}
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{stop, {login_failed, Reason}}
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{stop, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
handle_call(info, _From, #state{id = Id} = State) ->
|
|
||||||
Reply = {Id, self(), get(queries)},
|
|
||||||
{reply, Reply, State};
|
|
||||||
|
|
||||||
handle_call({sqlquery, Query}, _From, #state{socket = Socket,
|
|
||||||
recv_pid = RecvPid, mysql_version = Ver} = State) ->
|
|
||||||
put(queries, get(queries) + 1),
|
|
||||||
case do_query(Socket, RecvPid, Query, Ver) of
|
|
||||||
{error, mysql_timeout} = Err ->
|
|
||||||
{stop, mysql_timeout, Err, State};
|
|
||||||
Res ->
|
|
||||||
{reply, Res, State}
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_call({prepare, Name, Stmt}, _From, #state{socket = Socket,
|
|
||||||
recv_pid = RecvPid, mysql_version = Ver} = State) ->
|
|
||||||
|
|
||||||
case do_prepare(Socket, RecvPid, Name, Stmt, Ver) of
|
|
||||||
{error, mysql_timeout} ->
|
|
||||||
{stop, mysql_timeout, State};
|
|
||||||
_ ->
|
|
||||||
{reply, ok, State}
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_call({unprepare, Name}, _From, #state{socket = Socket,
|
|
||||||
recv_pid = RecvPid, mysql_version = Ver} = State) ->
|
|
||||||
case do_unprepare(Socket, RecvPid, Name, Ver) of
|
|
||||||
{error, mysql_timeout} ->
|
|
||||||
{stop, mysql_timeout, State};
|
|
||||||
_ ->
|
|
||||||
{reply, ok, State}
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_call({execute, Name, Params}, _From, #state{socket = Socket,
|
|
||||||
recv_pid = RecvPid, mysql_version = Ver} = State) ->
|
|
||||||
case do_execute(Socket, RecvPid, Name, Params, Ver) of
|
|
||||||
{error, mysql_timeout} = Err ->
|
|
||||||
{stop, mysql_timeout, Err, State};
|
|
||||||
Res ->
|
|
||||||
{reply, Res, State}
|
|
||||||
end;
|
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
|
||||||
error_logger:error_msg("badreq to emysql_conn: ~p", [Req]),
|
|
||||||
{reply, {error, badreq}, State}.
|
|
||||||
|
|
||||||
handle_cast(_Msg, State) ->
|
|
||||||
{noreply, State}.
|
|
||||||
|
|
||||||
handle_info({mysql_recv, _RecvPid, data, _Packet, SeqNum}, State) ->
|
|
||||||
error_logger:error_msg("unexpected mysql_recv: seq_num = ~p", [SeqNum]),
|
|
||||||
{noreply, State};
|
|
||||||
|
|
||||||
handle_info({mysql_recv, _RecvPid, closed, E}, State) ->
|
|
||||||
error_logger:error_msg("mysql socket closed: ~p", [E]),
|
|
||||||
{stop, socket_closed, State};
|
|
||||||
|
|
||||||
handle_info(_Info, State) ->
|
|
||||||
{noreply, State}.
|
|
||||||
|
|
||||||
terminate(_Reason, _State) ->
|
|
||||||
ok.
|
|
||||||
|
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
do_queries(Sock, RecvPid, Queries, Version) ->
|
|
||||||
catch
|
|
||||||
lists:foldl(
|
|
||||||
fun(Query, _LastResponse) ->
|
|
||||||
case do_query(Sock, RecvPid, Query, Version) of
|
|
||||||
{error, _} = Err -> throw(Err);
|
|
||||||
Res -> Res
|
|
||||||
end
|
|
||||||
end, ok, Queries).
|
|
||||||
|
|
||||||
do_query(Sock, RecvPid, Query, Version) ->
|
|
||||||
Query1 = iolist_to_binary(Query),
|
|
||||||
%?DEBUG("sqlquery ~p (id ~p)", [Query1, RecvPid]),
|
|
||||||
Packet = <<?MYSQL_QUERY_OP, Query1/binary>>,
|
|
||||||
case do_send(Sock, Packet, 0) of
|
|
||||||
ok ->
|
|
||||||
get_query_response(RecvPid, Version);
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
do_prepare(Socket, RecvPid, Name, Stmt, Ver) ->
|
|
||||||
NameBin = atom_to_binary(Name),
|
|
||||||
StmtBin = <<"PREPARE ", NameBin/binary, " FROM '", Stmt/binary, "'">>,
|
|
||||||
do_query(Socket, RecvPid, StmtBin, Ver).
|
|
||||||
|
|
||||||
do_execute(Socket, RecvPid, Name, Params, Ver) ->
|
|
||||||
Stmts = make_statements(Name, Params),
|
|
||||||
do_queries(Socket, RecvPid, Stmts, Ver).
|
|
||||||
|
|
||||||
do_unprepare(Socket, RecvPid, Name, Ver) ->
|
|
||||||
NameBin = atom_to_binary(Name),
|
|
||||||
StmtBin = <<"UNPREPARE ", NameBin/binary>>,
|
|
||||||
do_query(Socket, RecvPid, StmtBin, Ver).
|
|
||||||
|
|
||||||
make_statements(Name, []) ->
|
|
||||||
NameBin = atom_to_binary(Name),
|
|
||||||
[<<"EXECUTE ", NameBin/binary>>];
|
|
||||||
|
|
||||||
make_statements(Name, Params) ->
|
|
||||||
NumParams = length(Params),
|
|
||||||
ParamNums = lists:seq(1, NumParams),
|
|
||||||
NameBin = atom_to_binary(Name),
|
|
||||||
ParamNames =
|
|
||||||
lists:foldl(
|
|
||||||
fun(Num, Acc) ->
|
|
||||||
ParamName = [$@ | integer_to_list(Num)],
|
|
||||||
if Num == 1 ->
|
|
||||||
ParamName ++ Acc;
|
|
||||||
true ->
|
|
||||||
[$, | ParamName] ++ Acc
|
|
||||||
end
|
|
||||||
end, [], lists:reverse(ParamNums)),
|
|
||||||
ParamNamesBin = list_to_binary(ParamNames),
|
|
||||||
ExecStmt = <<"EXECUTE ", NameBin/binary, " USING ",
|
|
||||||
ParamNamesBin/binary>>,
|
|
||||||
|
|
||||||
ParamVals = lists:zip(ParamNums, Params),
|
|
||||||
Stmts = lists:foldl(
|
|
||||||
fun({Num, Val}, Acc) ->
|
|
||||||
NumBin = emysql:encode(Num, true),
|
|
||||||
ValBin = emysql:encode(Val, true),
|
|
||||||
[<<"SET @", NumBin/binary, "=", ValBin/binary>> | Acc]
|
|
||||||
end, [ExecStmt], lists:reverse(ParamVals)),
|
|
||||||
Stmts.
|
|
||||||
|
|
||||||
atom_to_binary(Val) ->
|
|
||||||
<<_:4/binary, Bin/binary>> = term_to_binary(Val),
|
|
||||||
Bin.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% authentication
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
do_old_auth(Sock, RecvPid, SeqNum, User, Password, Salt1) ->
|
|
||||||
Auth = emysql_auth:password_old(Password, Salt1),
|
|
||||||
Packet = emysql_auth:make_auth(User, Auth),
|
|
||||||
do_send(Sock, Packet, SeqNum),
|
|
||||||
do_recv(RecvPid, SeqNum).
|
|
||||||
|
|
||||||
do_new_auth(Sock, RecvPid, SeqNum, User, Password, Salt1, Salt2) ->
|
|
||||||
Auth = emysql_auth:password_new(Password, Salt1 ++ Salt2),
|
|
||||||
Packet2 = emysql_auth:make_new_auth(User, Auth, none),
|
|
||||||
do_send(Sock, Packet2, SeqNum),
|
|
||||||
case do_recv(RecvPid, SeqNum) of
|
|
||||||
{ok, Packet3, SeqNum2} ->
|
|
||||||
case Packet3 of
|
|
||||||
<<254:8>> ->
|
|
||||||
AuthOld = emysql_auth:password_old(Password, Salt1),
|
|
||||||
do_send(Sock, <<AuthOld/binary, 0:8>>, SeqNum2 + 1),
|
|
||||||
do_recv(RecvPid, SeqNum2 + 1);
|
|
||||||
_ ->
|
|
||||||
{ok, Packet3, SeqNum2}
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: mysql_init(Sock, RecvPid, User, Password)
|
|
||||||
%% Sock = term(), gen_tcp socket
|
|
||||||
%% RecvPid = pid(), mysql_recv process
|
|
||||||
%% User = string()
|
|
||||||
%% Password = string()
|
|
||||||
%% LogFun = undefined | function() with arity 3
|
|
||||||
%% Descrip.: Try to authenticate on our new socket.
|
|
||||||
%% Returns : ok | {error, Reason}
|
|
||||||
%% Reason = string()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
mysql_init(Sock, RecvPid, User, Password) ->
|
|
||||||
case do_recv(RecvPid, undefined) of
|
|
||||||
{ok, Packet, InitSeqNum} ->
|
|
||||||
{Version, Salt1, Salt2, Caps} = greeting(Packet),
|
|
||||||
%?DEBUG("version: ~p, ~p, ~p, ~p", [Version, Salt1, Salt2, Caps]),
|
|
||||||
AuthRes =
|
|
||||||
case Caps band ?SECURE_CONNECTION of
|
|
||||||
?SECURE_CONNECTION ->
|
|
||||||
do_new_auth(Sock, RecvPid, InitSeqNum + 1, User, Password, Salt1, Salt2);
|
|
||||||
_ ->
|
|
||||||
do_old_auth(Sock, RecvPid, InitSeqNum + 1, User, Password, Salt1)
|
|
||||||
end,
|
|
||||||
case AuthRes of
|
|
||||||
{ok, <<0:8, _Rest/binary>>, _RecvNum} ->
|
|
||||||
{ok,Version};
|
|
||||||
{ok, <<255:8, _Code:16/little, Message/binary>>, _RecvNum} ->
|
|
||||||
{error, binary_to_list(Message)};
|
|
||||||
{ok, RecvPacket, _RecvNum} ->
|
|
||||||
{error, binary_to_list(RecvPacket)};
|
|
||||||
{error, Reason} ->
|
|
||||||
%?ERROR("init failed receiving data : ~p", [Reason]),
|
|
||||||
{error, Reason}
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
greeting(Packet) ->
|
|
||||||
<<_Protocol:8, Rest/binary>> = Packet,
|
|
||||||
{Version, Rest2} = asciz(Rest),
|
|
||||||
<<_TreadID:32/little, Rest3/binary>> = Rest2,
|
|
||||||
{Salt, Rest4} = asciz(Rest3),
|
|
||||||
<<Caps:16/little, Rest5/binary>> = Rest4,
|
|
||||||
<<_ServerChar:16/binary-unit:8, Rest6/binary>> = Rest5,
|
|
||||||
{Salt2, _Rest7} = asciz(Rest6),
|
|
||||||
%?DEBUG("greeting version ~p (protocol ~p) salt ~p caps ~p serverchar ~p"
|
|
||||||
%"salt2 ~p",
|
|
||||||
%[Version, Protocol, Salt, Caps, ServerChar, Salt2]),
|
|
||||||
{normalize_version(Version), Salt, Salt2, Caps}.
|
|
||||||
|
|
||||||
%% part of greeting/2
|
|
||||||
asciz(Data) when is_binary(Data) ->
|
|
||||||
asciz_binary(Data, []);
|
|
||||||
asciz(Data) when is_list(Data) ->
|
|
||||||
{String, [0 | Rest]} = lists:splitwith(fun (C) ->
|
|
||||||
C /= 0
|
|
||||||
end, Data),
|
|
||||||
{String, Rest}.
|
|
||||||
|
|
||||||
%% @doc Find the first zero-byte in Data and add everything before it
|
|
||||||
%% to Acc, as a string.
|
|
||||||
%%
|
|
||||||
%% @spec asciz_binary(Data::binary(), Acc::list()) ->
|
|
||||||
%% {NewList::list(), Rest::binary()}
|
|
||||||
asciz_binary(<<>>, Acc) ->
|
|
||||||
{lists:reverse(Acc), <<>>};
|
|
||||||
asciz_binary(<<0:8, Rest/binary>>, Acc) ->
|
|
||||||
{lists:reverse(Acc), Rest};
|
|
||||||
asciz_binary(<<C:8, Rest/binary>>, Acc) ->
|
|
||||||
asciz_binary(Rest, [C | Acc]).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: get_query_response(RecvPid)
|
|
||||||
%% RecvPid = pid(), mysql_recv process
|
|
||||||
%% Version = integer(), Representing MySQL version used
|
|
||||||
%% Descrip.: Wait for frames until we have a complete query response.
|
|
||||||
%% Returns : {data, #mysql_result}
|
|
||||||
%% {updated, #mysql_result}
|
|
||||||
%% {error, #mysql_result}
|
|
||||||
%% FieldInfo = list() of term()
|
|
||||||
%% Rows = list() of [string()]
|
|
||||||
%% AffectedRows = int()
|
|
||||||
%% Reason = term()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
get_query_response(RecvPid, Version) ->
|
|
||||||
case do_recv(RecvPid, undefined) of
|
|
||||||
{ok, <<Fieldcount:8, Rest/binary>>, _} ->
|
|
||||||
case Fieldcount of
|
|
||||||
0 ->
|
|
||||||
%% No Tabular data
|
|
||||||
{AffectedRows, Rest1} = decode_length_binary(Rest),
|
|
||||||
{InsertId, _} = decode_length_binary(Rest1),
|
|
||||||
{updated, #mysql_result{insert_id = InsertId, affectedrows=AffectedRows}};
|
|
||||||
255 ->
|
|
||||||
<<_Code:16/little, Message/binary>> = Rest,
|
|
||||||
{error, #mysql_result{error=Message}};
|
|
||||||
_ ->
|
|
||||||
%% Tabular data received
|
|
||||||
case get_fields(RecvPid, [], Version) of
|
|
||||||
{ok, Fields} ->
|
|
||||||
case get_rows(Fields, RecvPid, []) of
|
|
||||||
{ok, Rows} ->
|
|
||||||
{data, #mysql_result{fieldinfo=Fields,
|
|
||||||
rows=Rows}};
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
decode_length_binary(<<Len:8, Rest/binary>>) ->
|
|
||||||
if
|
|
||||||
Len =< 251 ->
|
|
||||||
{Len, Rest};
|
|
||||||
Len == 252 -> %two bytes
|
|
||||||
<<Val:16/little, Rest1/binary>> = Rest,
|
|
||||||
{Val, Rest1};
|
|
||||||
Len == 253 -> %three
|
|
||||||
<<Val:24/little, Rest1/binary>> = Rest,
|
|
||||||
{Val, Rest1};
|
|
||||||
Len == 254 -> %eight
|
|
||||||
<<Val:64/little, Rest1/binary>> = Rest,
|
|
||||||
{Val, Rest1};
|
|
||||||
true ->
|
|
||||||
%?ERROR("affectedrows: ~p", [Len]),
|
|
||||||
{0, Rest}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: do_recv(RecvPid, SeqNum)
|
|
||||||
%% RecvPid = pid(), mysql_recv process
|
|
||||||
%% SeqNum = undefined | integer()
|
|
||||||
%% Descrip.: Wait for a frame decoded and sent to us by RecvPid.
|
|
||||||
%% Either wait for a specific frame if SeqNum is an integer,
|
|
||||||
%% or just any frame if SeqNum is undefined.
|
|
||||||
%% Returns : {ok, Packet, Num} |
|
|
||||||
%% {error, Reason}
|
|
||||||
%% Reason = term()
|
|
||||||
%%
|
|
||||||
%% Note : Only to be used externally by the 'mysql_auth' module.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
do_recv(RecvPid, SeqNum) when SeqNum == undefined ->
|
|
||||||
receive
|
|
||||||
{mysql_recv, RecvPid, data, Packet, Num} ->
|
|
||||||
{ok, Packet, Num};
|
|
||||||
{mysql_recv, RecvPid, closed, _E} ->
|
|
||||||
{error, socket_closed}
|
|
||||||
after ?CONNECT_TIMEOUT ->
|
|
||||||
{error, mysql_timeout}
|
|
||||||
end;
|
|
||||||
|
|
||||||
do_recv(RecvPid, SeqNum) when is_integer(SeqNum) ->
|
|
||||||
ResponseNum = SeqNum + 1,
|
|
||||||
receive
|
|
||||||
{mysql_recv, RecvPid, data, Packet, ResponseNum} ->
|
|
||||||
{ok, Packet, ResponseNum};
|
|
||||||
{mysql_recv, RecvPid, closed, _E} ->
|
|
||||||
{error, socket_closed}
|
|
||||||
after ?CONNECT_TIMEOUT ->
|
|
||||||
{error, mysql_timeout}
|
|
||||||
end.
|
|
||||||
|
|
||||||
call(Conn, Req) ->
|
|
||||||
gen_server:call(Conn, Req).
|
|
||||||
|
|
||||||
call(Conn, Req, Timeout) ->
|
|
||||||
gen_server:call(Conn, Req, Timeout).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: get_fields(RecvPid, [], Version)
|
|
||||||
%% RecvPid = pid(), mysql_recv process
|
|
||||||
%% Version = integer(), Representing MySQL version used
|
|
||||||
%% Descrip.: Received and decode field information.
|
|
||||||
%% Returns : {ok, FieldInfo} |
|
|
||||||
%% {error, Reason}
|
|
||||||
%% FieldInfo = list() of term()
|
|
||||||
%% Reason = term()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Support for MySQL 4.0.x:
|
|
||||||
get_fields(RecvPid, Res, ?MYSQL_4_0) ->
|
|
||||||
case do_recv(RecvPid, undefined) of
|
|
||||||
{ok, Packet, _Num} ->
|
|
||||||
case Packet of
|
|
||||||
<<254:8>> ->
|
|
||||||
{ok, lists:reverse(Res)};
|
|
||||||
<<254:8, Rest/binary>> when size(Rest) < 8 ->
|
|
||||||
{ok, lists:reverse(Res)};
|
|
||||||
_ ->
|
|
||||||
{Table, Rest} = get_with_length(Packet),
|
|
||||||
{Field, Rest2} = get_with_length(Rest),
|
|
||||||
{LengthB, Rest3} = get_with_length(Rest2),
|
|
||||||
LengthL = size(LengthB) * 8,
|
|
||||||
<<Length:LengthL/little>> = LengthB,
|
|
||||||
{Type, Rest4} = get_with_length(Rest3),
|
|
||||||
{_Flags, _Rest5} = get_with_length(Rest4),
|
|
||||||
This = {Table,
|
|
||||||
Field,
|
|
||||||
Length,
|
|
||||||
%% TODO: Check on MySQL 4.0 if types are specified
|
|
||||||
%% using the same 4.1 formalism and could
|
|
||||||
%% be expanded to atoms:
|
|
||||||
Type},
|
|
||||||
get_fields(RecvPid, [This | Res], ?MYSQL_4_0)
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end;
|
|
||||||
%% Support for MySQL 4.1.x and 5.x:
|
|
||||||
get_fields(RecvPid, Res, ?MYSQL_4_1) ->
|
|
||||||
case do_recv(RecvPid, undefined) of
|
|
||||||
{ok, Packet, _Num} ->
|
|
||||||
case Packet of
|
|
||||||
<<254:8>> ->
|
|
||||||
{ok, lists:reverse(Res)};
|
|
||||||
<<254:8, Rest/binary>> when size(Rest) < 8 ->
|
|
||||||
{ok, lists:reverse(Res)};
|
|
||||||
_ ->
|
|
||||||
{_Catalog, Rest} = get_with_length(Packet),
|
|
||||||
{_Database, Rest2} = get_with_length(Rest),
|
|
||||||
{Table, Rest3} = get_with_length(Rest2),
|
|
||||||
%% OrgTable is the real table name if Table is an alias
|
|
||||||
{_OrgTable, Rest4} = get_with_length(Rest3),
|
|
||||||
{Field, Rest5} = get_with_length(Rest4),
|
|
||||||
%% OrgField is the real field name if Field is an alias
|
|
||||||
{_OrgField, Rest6} = get_with_length(Rest5),
|
|
||||||
|
|
||||||
<<_Metadata:8/little, _Charset:16/little,
|
|
||||||
Length:32/little, Type:8/little,
|
|
||||||
_Flags:16/little, _Decimals:8/little,
|
|
||||||
_Rest7/binary>> = Rest6,
|
|
||||||
|
|
||||||
This = {Table,
|
|
||||||
Field,
|
|
||||||
Length,
|
|
||||||
get_field_datatype(Type)},
|
|
||||||
get_fields(RecvPid, [This | Res], ?MYSQL_4_1)
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: get_rows(N, RecvPid, [])
|
|
||||||
%% N = integer(), number of rows to get
|
|
||||||
%% RecvPid = pid(), mysql_recv process
|
|
||||||
%% Descrip.: Receive and decode a number of rows.
|
|
||||||
%% Returns : {ok, Rows} |
|
|
||||||
%% {error, Reason}
|
|
||||||
%% Rows = list() of [string()]
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
get_rows(Fields, RecvPid, Res) ->
|
|
||||||
case do_recv(RecvPid, undefined) of
|
|
||||||
{ok, Packet, _Num} ->
|
|
||||||
case Packet of
|
|
||||||
<<254:8, Rest/binary>> when size(Rest) < 8 ->
|
|
||||||
{ok, lists:reverse(Res)};
|
|
||||||
_ ->
|
|
||||||
{ok, This} = get_row(Fields, Packet, []),
|
|
||||||
get_rows(Fields, RecvPid, [This | Res])
|
|
||||||
end;
|
|
||||||
{error, Reason} ->
|
|
||||||
{error, Reason}
|
|
||||||
end.
|
|
||||||
|
|
||||||
%% part of get_rows/4
|
|
||||||
get_row([], _Data, Res) ->
|
|
||||||
{ok, lists:reverse(Res)};
|
|
||||||
get_row([Field | OtherFields], Data, Res) ->
|
|
||||||
{Col, Rest} = get_with_length(Data),
|
|
||||||
This = case Col of
|
|
||||||
null ->
|
|
||||||
undefined;
|
|
||||||
_ ->
|
|
||||||
convert_type(Col, element(4, Field))
|
|
||||||
end,
|
|
||||||
get_row(OtherFields, Rest, [This | Res]).
|
|
||||||
|
|
||||||
get_with_length(<<251:8, Rest/binary>>) ->
|
|
||||||
{null, Rest};
|
|
||||||
get_with_length(<<252:8, Length:16/little, Rest/binary>>) ->
|
|
||||||
split_binary(Rest, Length);
|
|
||||||
get_with_length(<<253:8, Length:24/little, Rest/binary>>) ->
|
|
||||||
split_binary(Rest, Length);
|
|
||||||
get_with_length(<<254:8, Length:64/little, Rest/binary>>) ->
|
|
||||||
split_binary(Rest, Length);
|
|
||||||
get_with_length(<<Length:8, Rest/binary>>) when Length < 251 ->
|
|
||||||
split_binary(Rest, Length).
|
|
||||||
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: do_send(Sock, Packet, SeqNum)
|
|
||||||
%% Sock = term(), gen_tcp socket
|
|
||||||
%% Packet = binary()
|
|
||||||
%% SeqNum = integer(), packet sequence number
|
|
||||||
%% Descrip.: Send a packet to the MySQL server.
|
|
||||||
%% Returns : result of gen_tcp:send/2
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
do_send(Sock, Packet, SeqNum) when is_binary(Packet), is_integer(SeqNum) ->
|
|
||||||
Data = <<(size(Packet)):24/little, SeqNum:8, Packet/binary>>,
|
|
||||||
gen_tcp:send(Sock, Data).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: normalize_version(Version)
|
|
||||||
%% Version = string()
|
|
||||||
%% Descrip.: Return a flag corresponding to the MySQL version used.
|
|
||||||
%% The protocol used depends on this flag.
|
|
||||||
%% Returns : Version = string()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
normalize_version([$4,$.,$0|_T]) ->
|
|
||||||
%?DEBUG("switching to MySQL 4.0.x protocol.", []),
|
|
||||||
?MYSQL_4_0;
|
|
||||||
normalize_version([$4,$.,$1|_T]) ->
|
|
||||||
?MYSQL_4_1;
|
|
||||||
normalize_version([$5|_T]) ->
|
|
||||||
%% MySQL version 5.x protocol is compliant with MySQL 4.1.x:
|
|
||||||
?MYSQL_4_1;
|
|
||||||
normalize_version([$6|_T]) ->
|
|
||||||
%% MySQL version 6.x protocol is compliant with MySQL 4.1.x:
|
|
||||||
?MYSQL_4_1;
|
|
||||||
normalize_version(_Other) ->
|
|
||||||
%?ERROR("MySQL version '~p' not supported: MySQL Erlang module "
|
|
||||||
% "might not work correctly.", [Other]),
|
|
||||||
%% Error, but trying the oldest protocol anyway:
|
|
||||||
?MYSQL_4_0.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: get_field_datatype(DataType)
|
|
||||||
%% DataType = integer(), MySQL datatype
|
|
||||||
%% Descrip.: Return MySQL field datatype as description string
|
|
||||||
%% Returns : String, MySQL datatype
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
get_field_datatype(0) -> 'DECIMAL';
|
|
||||||
get_field_datatype(1) -> 'TINY';
|
|
||||||
get_field_datatype(2) -> 'SHORT';
|
|
||||||
get_field_datatype(3) -> 'LONG';
|
|
||||||
get_field_datatype(4) -> 'FLOAT';
|
|
||||||
get_field_datatype(5) -> 'DOUBLE';
|
|
||||||
get_field_datatype(6) -> 'NULL';
|
|
||||||
get_field_datatype(7) -> 'TIMESTAMP';
|
|
||||||
get_field_datatype(8) -> 'LONGLONG';
|
|
||||||
get_field_datatype(9) -> 'INT24';
|
|
||||||
get_field_datatype(10) -> 'DATE';
|
|
||||||
get_field_datatype(11) -> 'TIME';
|
|
||||||
get_field_datatype(12) -> 'DATETIME';
|
|
||||||
get_field_datatype(13) -> 'YEAR';
|
|
||||||
get_field_datatype(14) -> 'NEWDATE';
|
|
||||||
get_field_datatype(246) -> 'NEWDECIMAL';
|
|
||||||
get_field_datatype(247) -> 'ENUM';
|
|
||||||
get_field_datatype(248) -> 'SET';
|
|
||||||
get_field_datatype(249) -> 'TINYBLOB';
|
|
||||||
get_field_datatype(250) -> 'MEDIUM_BLOG';
|
|
||||||
get_field_datatype(251) -> 'LONG_BLOG';
|
|
||||||
get_field_datatype(252) -> 'BLOB';
|
|
||||||
get_field_datatype(253) -> 'VAR_STRING';
|
|
||||||
get_field_datatype(254) -> 'STRING';
|
|
||||||
get_field_datatype(255) -> 'GEOMETRY'.
|
|
||||||
|
|
||||||
convert_type(Val, ColType) ->
|
|
||||||
case ColType of
|
|
||||||
T when T == 'TINY';
|
|
||||||
T == 'SHORT';
|
|
||||||
T == 'LONG';
|
|
||||||
T == 'LONGLONG';
|
|
||||||
T == 'INT24';
|
|
||||||
T == 'YEAR' ->
|
|
||||||
list_to_integer(binary_to_list(Val));
|
|
||||||
T when T == 'TIMESTAMP';
|
|
||||||
T == 'DATETIME' ->
|
|
||||||
{ok, [Year, Month, Day, Hour, Minute, Second], _Leftovers} =
|
|
||||||
io_lib:fread("~d-~d-~d ~d:~d:~d", binary_to_list(Val)),
|
|
||||||
{datetime, {{Year, Month, Day}, {Hour, Minute, Second}}};
|
|
||||||
'TIME' ->
|
|
||||||
{ok, [Hour, Minute, Second], _Leftovers} =
|
|
||||||
io_lib:fread("~d:~d:~d", binary_to_list(Val)),
|
|
||||||
{time, {Hour, Minute, Second}};
|
|
||||||
'DATE' ->
|
|
||||||
{ok, [Year, Month, Day], _Leftovers} =
|
|
||||||
io_lib:fread("~d-~d-~d", binary_to_list(Val)),
|
|
||||||
{date, {Year, Month, Day}};
|
|
||||||
T when T == 'DECIMAL';
|
|
||||||
T == 'NEWDECIMAL';
|
|
||||||
T == 'FLOAT';
|
|
||||||
T == 'DOUBLE' ->
|
|
||||||
{ok, [Num], _Leftovers} =
|
|
||||||
case io_lib:fread("~f", binary_to_list(Val)) of
|
|
||||||
{error, _} ->
|
|
||||||
io_lib:fread("~d", binary_to_list(Val));
|
|
||||||
Res ->
|
|
||||||
Res
|
|
||||||
end,
|
|
||||||
Num;
|
|
||||||
_Other ->
|
|
||||||
Val
|
|
||||||
end.
|
|
|
@ -1,130 +0,0 @@
|
||||||
%%%-------------------------------------------------------------------
|
|
||||||
%%% File : emysql_recv.erl
|
|
||||||
%%% Author : Fredrik Thulin <ft@it.su.se>
|
|
||||||
%%% Descrip.: Handles data being received on a MySQL socket. Decodes
|
|
||||||
%%% per-row framing and sends each row to parent.
|
|
||||||
%%%
|
|
||||||
%%% Created : 4 Aug 2005 by Fredrik Thulin <ft@it.su.se>
|
|
||||||
%%%
|
|
||||||
%%% Note : All MySQL code was written by Magnus Ahltorp, originally
|
|
||||||
%%% in the file mysql.erl - I just moved it here.
|
|
||||||
%%%
|
|
||||||
%%% Copyright (c) 2001-2004 Kungliga Tekniska
|
|
||||||
%%% See the file COPYING
|
|
||||||
%%%
|
|
||||||
%%% Signals this receiver process can send to it's parent
|
|
||||||
%%% (the parent is a mysql_conn connection handler) :
|
|
||||||
%%%
|
|
||||||
%%% {mysql_recv, self(), data, Packet, Num}
|
|
||||||
%%% {mysql_recv, self(), closed, {error, Reason}}
|
|
||||||
%%% {mysql_recv, self(), closed, normal}
|
|
||||||
%%%
|
|
||||||
%%% Internally (from inside init/4 to start_link/4) the
|
|
||||||
%%% following signals may be sent to the parent process :
|
|
||||||
%%%
|
|
||||||
%%% {mysql_recv, self(), init, {ok, Sock}}
|
|
||||||
%%% {mysql_recv, self(), init, {error, E}}
|
|
||||||
%%%
|
|
||||||
%%%-------------------------------------------------------------------
|
|
||||||
-module(emysql_recv).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% External exports (should only be used by the 'mysql_conn' module)
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-export([start_link/2]).
|
|
||||||
|
|
||||||
%callback
|
|
||||||
-export([init/3]).
|
|
||||||
|
|
||||||
-record(state, {
|
|
||||||
socket,
|
|
||||||
parent,
|
|
||||||
log_fun,
|
|
||||||
data}).
|
|
||||||
|
|
||||||
-define(SECURE_CONNECTION, 32768).
|
|
||||||
|
|
||||||
-define(CONNECT_TIMEOUT, 10000).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: start_link(Host, Port, Parent)
|
|
||||||
%% Host = string()
|
|
||||||
%% Port = integer()
|
|
||||||
%% Parent = pid(), process that should get received frames
|
|
||||||
%% Descrip.: Start a process that connects to Host:Port and waits for
|
|
||||||
%% data. When it has received a MySQL frame, it sends it to
|
|
||||||
%% Parent and waits for the next frame.
|
|
||||||
%% Returns : {ok, RecvPid, Socket} |
|
|
||||||
%% {error, Reason}
|
|
||||||
%% RecvPid = pid(), receiver process pid
|
|
||||||
%% Socket = term(), gen_tcp socket
|
|
||||||
%% Reason = atom() | string()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
start_link(Host, Port) ->
|
|
||||||
proc_lib:start_link(?MODULE, init, [self(), Host, Port]).
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: init((Host, Port, Parent)
|
|
||||||
%% Host = string()
|
|
||||||
%% Port = integer()
|
|
||||||
%% Parent = pid(), process that should get received frames
|
|
||||||
%% Descrip.: Connect to Host:Port and then enter receive-loop.
|
|
||||||
%% Returns : error | never returns
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
init(Parent, Host, Port) ->
|
|
||||||
case gen_tcp:connect(Host, Port, [binary, {packet, 0}]) of
|
|
||||||
{ok, Sock} ->
|
|
||||||
proc_lib:init_ack(Parent, {ok, self(), Sock}),
|
|
||||||
loop(#state{socket = Sock, parent = Parent, data = <<>>});
|
|
||||||
{error, Reason} ->
|
|
||||||
proc_lib:init_ack(Parent, {error, Reason})
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: loop(State)
|
|
||||||
%% State = state record()
|
|
||||||
%% Descrip.: The main loop. Wait for data from our TCP socket and act
|
|
||||||
%% on received data or signals that our socket was closed.
|
|
||||||
%% Returns : error | never returns
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
loop(State) ->
|
|
||||||
Sock = State#state.socket,
|
|
||||||
receive
|
|
||||||
{tcp, Sock, InData} ->
|
|
||||||
NewData = list_to_binary([State#state.data, InData]),
|
|
||||||
%% send data to parent if we have enough data
|
|
||||||
Rest = sendpacket(State#state.parent, NewData),
|
|
||||||
loop(State#state{data = Rest});
|
|
||||||
{tcp_error, Sock, Reason} ->
|
|
||||||
State#state.parent ! {mysql_recv, self(), closed, {error, Reason}},
|
|
||||||
error;
|
|
||||||
{tcp_closed, Sock} ->
|
|
||||||
State#state.parent ! {mysql_recv, self(), closed, normal},
|
|
||||||
error;
|
|
||||||
_Other -> %maybe system message
|
|
||||||
loop(State)
|
|
||||||
end.
|
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Function: sendpacket(Parent, Data)
|
|
||||||
%% Parent = pid()
|
|
||||||
%% Data = binary()
|
|
||||||
%% Descrip.: Check if we have received one or more complete frames by
|
|
||||||
%% now, and if so - send them to Parent.
|
|
||||||
%% Returns : Rest = binary()
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% send data to parent if we have enough data
|
|
||||||
sendpacket(Parent, Data) ->
|
|
||||||
case Data of
|
|
||||||
<<Length:24/little, Num:8, D/binary>> ->
|
|
||||||
if
|
|
||||||
Length =< size(D) ->
|
|
||||||
{Packet, Rest} = split_binary(D, Length),
|
|
||||||
Parent ! {mysql_recv, self(), data, Packet, Num},
|
|
||||||
sendpacket(Parent, Rest);
|
|
||||||
true ->
|
|
||||||
Data
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
Data
|
|
||||||
end.
|
|
|
@ -1,34 +0,0 @@
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
%%% File : emysql_sup.erl
|
|
||||||
%%% Author : Ery Lee
|
|
||||||
%%% Purpose : Mysql driver supervisor
|
|
||||||
%%% Created : 21 May 2009
|
|
||||||
%%% Updated : 11 Jan 2010
|
|
||||||
%%% License : http://www.opengoss.com
|
|
||||||
%%%
|
|
||||||
%%% Copyright (C) 2012, www.opengoss.com
|
|
||||||
%%%----------------------------------------------------------------------
|
|
||||||
-module(emysql_sup).
|
|
||||||
|
|
||||||
-author('ery.lee@gmail.com').
|
|
||||||
|
|
||||||
-behavior(supervisor).
|
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([start_link/1, init/1]).
|
|
||||||
|
|
||||||
start_link(Opts) ->
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, Opts).
|
|
||||||
|
|
||||||
init(Opts) ->
|
|
||||||
PoolSize = proplists:get_value(pool, Opts,
|
|
||||||
erlang:system_info(schedulers)),
|
|
||||||
{ok, {{one_for_one, 10, 10},
|
|
||||||
[{emysql, {emysql, start_link, [PoolSize]}, transient,
|
|
||||||
16#ffffffff, worker, [emysql]} |
|
|
||||||
[{I, {emysql_conn, start_link, [I, Opts]}, transient, 16#ffffffff,
|
|
||||||
worker, [emysql_conn, emysql_recv]} || I <- lists:seq(1, PoolSize)]]
|
|
||||||
}
|
|
||||||
}.
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
|
|
||||||
{sub_dirs, [
|
{sub_dirs, [
|
||||||
"rel",
|
"rel",
|
||||||
"apps/*/",
|
|
||||||
"plugins/*/"]}.
|
"plugins/*/"]}.
|
||||||
|
|
||||||
{deps, [
|
{deps, [
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
%% ex: ft=erlang ts=4 sw=4 et
|
||||||
[{kernel,
|
[{kernel,
|
||||||
[{start_timer, true},
|
[{start_timer, true},
|
||||||
|
@ -45,8 +45,22 @@
|
||||||
{auth, [
|
{auth, [
|
||||||
%% Authentication with username, password
|
%% Authentication with username, password
|
||||||
%{username, []},
|
%{username, []},
|
||||||
|
|
||||||
%% Authentication with clientid
|
%% Authentication with clientid
|
||||||
%{clientid, [{password, no}, {file, "etc/clients.config"}]},
|
%{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
|
%% Allow all
|
||||||
{anonymous, []}
|
{anonymous, []}
|
||||||
]},
|
]},
|
||||||
|
@ -73,12 +87,40 @@
|
||||||
]},
|
]},
|
||||||
%% Session
|
%% Session
|
||||||
{session, [
|
{session, [
|
||||||
%% Expired after 24 hours
|
%% Expired after 2 days
|
||||||
{expires, 24},
|
{expired_after, 48},
|
||||||
%% Max offline message queue
|
|
||||||
{max_queue, 100},
|
%% Max number of QoS 1 and 2 messages that can be “in flight” at one time.
|
||||||
%% Store Qos0?
|
%% 0 means no limit
|
||||||
{store_qos0, false}
|
{max_inflight, 100},
|
||||||
|
|
||||||
|
%% Max retries for unack Qos1/2 messages
|
||||||
|
{unack_retries, 3},
|
||||||
|
|
||||||
|
%% Retry after 4, 8, 16 seconds
|
||||||
|
{unack_timeout, 4},
|
||||||
|
|
||||||
|
%% Awaiting PUBREL Timeout
|
||||||
|
{await_rel_timeout, 8},
|
||||||
|
|
||||||
|
%% Max Packets that Awaiting PUBREL, 0 means no limit
|
||||||
|
{max_awaiting_rel, 0}
|
||||||
|
|
||||||
|
]},
|
||||||
|
%% Session
|
||||||
|
{queue, [
|
||||||
|
%% Max queue length. enqueued messages when persistent client disconnected,
|
||||||
|
%% or inflight window is full.
|
||||||
|
{max_length, 1000},
|
||||||
|
|
||||||
|
%% Low-water mark of queued messsages
|
||||||
|
{low_watermark, 0.2},
|
||||||
|
|
||||||
|
%% High-water mark of queued messsages
|
||||||
|
{high_watermark, 0.6},
|
||||||
|
|
||||||
|
%% Queue Qos0 messages?
|
||||||
|
{queue_qos0, true}
|
||||||
]}
|
]}
|
||||||
]},
|
]},
|
||||||
%% Broker Options
|
%% Broker Options
|
||||||
|
@ -101,15 +143,21 @@
|
||||||
%% Bridge
|
%% Bridge
|
||||||
{bridge, [
|
{bridge, [
|
||||||
%%TODO: bridge queue size
|
%%TODO: bridge queue size
|
||||||
{max_queue_len, 1000},
|
{max_queue_len, 10000},
|
||||||
|
|
||||||
%% Ping Interval of bridge node
|
%% Ping Interval of bridge node
|
||||||
{ping_down_interval, 1} %seconds
|
{ping_down_interval, 1} %seconds
|
||||||
]}
|
]}
|
||||||
]},
|
]},
|
||||||
%% Modules
|
%% Modules
|
||||||
{modules, [
|
{modules, [
|
||||||
|
%% Client presence management module.
|
||||||
|
%% Publish messages when client connected or disconnected
|
||||||
|
{presence, [{qos, 0}]},
|
||||||
|
|
||||||
%% Subscribe topics automatically when client connected
|
%% Subscribe topics automatically when client connected
|
||||||
{autosub, [{"$Q/client/$c", 0}]}
|
{autosub, [{"$Q/client/$c", 0}]}
|
||||||
|
|
||||||
%% Rewrite rules
|
%% Rewrite rules
|
||||||
%% {rewrite, [{file, "etc/rewrite.config"}]}
|
%% {rewrite, [{file, "etc/rewrite.config"}]}
|
||||||
|
|
||||||
|
@ -155,10 +203,7 @@
|
||||||
%% Maximum number of concurrent clients
|
%% Maximum number of concurrent clients
|
||||||
{max_clients, 512},
|
{max_clients, 512},
|
||||||
%% Socket Access Control
|
%% Socket Access Control
|
||||||
{access, [
|
{access, [{allow, all}]},
|
||||||
{allow, "127.0.0.1"},
|
|
||||||
{deny, all}
|
|
||||||
]},
|
|
||||||
%% Socket Options
|
%% Socket Options
|
||||||
{sockopts, [
|
{sockopts, [
|
||||||
{backlog, 1024}
|
{backlog, 1024}
|
||||||
|
|
|
@ -9,9 +9,13 @@
|
||||||
% {encoding, utf8}
|
% {encoding, utf8}
|
||||||
% ]},
|
% ]},
|
||||||
% {emqttd_auth_mysql, [
|
% {emqttd_auth_mysql, [
|
||||||
% {user_table, mqtt_users}
|
% {user_table, mqtt_users},
|
||||||
% ]}
|
% {password_hash, plain},
|
||||||
%
|
% {field_mapper, [
|
||||||
|
% {username, username},
|
||||||
|
% {password, password}
|
||||||
|
% ]}
|
||||||
|
% ]},
|
||||||
% {emqttd_dashboard, [
|
% {emqttd_dashboard, [
|
||||||
% {listener,
|
% {listener,
|
||||||
% {http, 18083, [
|
% {http, 18083, [
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
|
||||||
%% ex: ft=erlang ts=4 sw=4 et
|
%% ex: ft=erlang ts=4 sw=4 et
|
||||||
{sys, [
|
{sys, [
|
||||||
{lib_dirs, ["../apps", "../deps", "../plugins"]},
|
{lib_dirs, ["../deps"]},
|
||||||
{erts, [{mod_cond, derived}, {app_file, strip}]},
|
{erts, [{mod_cond, derived}, {app_file, strip}]},
|
||||||
{app_file, strip},
|
{app_file, strip},
|
||||||
{rel, "emqttd", "0.7.0",
|
{rel, "emqttd", git,
|
||||||
[
|
[
|
||||||
kernel,
|
kernel,
|
||||||
stdlib,
|
stdlib,
|
||||||
sasl,
|
sasl,
|
||||||
|
asn1,
|
||||||
syntax_tools,
|
syntax_tools,
|
||||||
ssl,
|
ssl,
|
||||||
crypto,
|
crypto,
|
||||||
%mnesia,
|
%mnesia,
|
||||||
|
eldap,
|
||||||
|
xmerl,
|
||||||
os_mon,
|
os_mon,
|
||||||
inets,
|
inets,
|
||||||
goldrush,
|
goldrush,
|
||||||
|
compiler,
|
||||||
lager,
|
lager,
|
||||||
|
{gen_logger, load},
|
||||||
gproc,
|
gproc,
|
||||||
esockd,
|
esockd,
|
||||||
mochiweb,
|
mochiweb,
|
||||||
{emqtt, load},
|
|
||||||
emqttd
|
emqttd
|
||||||
]},
|
]},
|
||||||
{rel, "start_clean", "",
|
{rel, "start_clean", "",
|
||||||
|
@ -30,7 +34,7 @@
|
||||||
]},
|
]},
|
||||||
{boot_rel, "emqttd"},
|
{boot_rel, "emqttd"},
|
||||||
{profile, embedded},
|
{profile, embedded},
|
||||||
{incl_cond, derived},
|
{incl_cond, exclude},
|
||||||
%{mod_cond, derived},
|
%{mod_cond, derived},
|
||||||
{excl_archive_filters, [".*"]}, %% Do not archive built libs
|
{excl_archive_filters, [".*"]}, %% Do not archive built libs
|
||||||
{excl_sys_filters, ["^bin/(?!start_clean.boot)",
|
{excl_sys_filters, ["^bin/(?!start_clean.boot)",
|
||||||
|
@ -40,20 +44,24 @@
|
||||||
{app, kernel, [{incl_cond, include}]},
|
{app, kernel, [{incl_cond, include}]},
|
||||||
{app, stdlib, [{incl_cond, include}]},
|
{app, stdlib, [{incl_cond, include}]},
|
||||||
{app, sasl, [{incl_cond, include}]},
|
{app, sasl, [{incl_cond, include}]},
|
||||||
{app, crypto, [{mod_cond, app}, {incl_cond, include}]},
|
{app, asn1, [{incl_cond, include}]},
|
||||||
{app, ssl, [{mod_cond, app}, {incl_cond, include}]},
|
{app, crypto, [{incl_cond, include}]},
|
||||||
{app, os_mon, [{mod_cond, app}, {incl_cond, include}]},
|
{app, ssl, [{incl_cond, include}]},
|
||||||
{app, syntax_tools, [{mod_cond, app}, {incl_cond, include}]},
|
{app, xmerl, [{incl_cond, include}]},
|
||||||
{app, public_key, [{mod_cond, app}, {incl_cond, include}]},
|
{app, os_mon, [{incl_cond, include}]},
|
||||||
{app, mnesia, [{mod_cond, app}, {incl_cond, include}]},
|
{app, syntax_tools, [{incl_cond, include}]},
|
||||||
{app, inets, [{mod_cond, app},{incl_cond, include}]},
|
{app, public_key, [{incl_cond, include}]},
|
||||||
{app, goldrush, [{mod_cond, app}, {incl_cond, include}]},
|
{app, mnesia, [{incl_cond, include}]},
|
||||||
{app, lager, [{mod_cond, app}, {incl_cond, include}]},
|
{app, eldap, [{incl_cond, include}]},
|
||||||
{app, gproc, [{mod_cond, app}, {incl_cond, include}]},
|
{app, inets, [{incl_cond, include}]},
|
||||||
|
{app, compiler, [{incl_cond, include}]},
|
||||||
|
{app, goldrush, [{incl_cond, include}]},
|
||||||
|
{app, gen_logger, [{incl_cond, include}]},
|
||||||
|
{app, lager, [{incl_cond, include}]},
|
||||||
|
{app, gproc, [{incl_cond, include}]},
|
||||||
{app, esockd, [{mod_cond, app}, {incl_cond, include}]},
|
{app, esockd, [{mod_cond, app}, {incl_cond, include}]},
|
||||||
{app, mochiweb, [{mod_cond, app}, {incl_cond, include}]},
|
{app, mochiweb, [{mod_cond, app}, {incl_cond, include}]},
|
||||||
{app, emqtt, [{mod_cond, app}, {incl_cond, include}]},
|
{app, emqttd, [{mod_cond, app}, {incl_cond, include}, {lib_dir, ".."}]}
|
||||||
{app, emqttd, [{mod_cond, app}, {incl_cond, include}]}
|
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{target_dir, "emqttd"}.
|
{target_dir, "emqttd"}.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{application, emqttd,
|
{application, emqttd,
|
||||||
[
|
[
|
||||||
{description, "Erlang MQTT Broker"},
|
{description, "Erlang MQTT Broker"},
|
||||||
{vsn, "0.8.0"},
|
{vsn, git},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications, [kernel,
|
{applications, [kernel,
|
|
@ -114,9 +114,15 @@ close_listener({Protocol, Port, _Options}) ->
|
||||||
-spec load_all_plugins() -> [{App :: atom(), ok | {error, any()}}].
|
-spec load_all_plugins() -> [{App :: atom(), ok | {error, any()}}].
|
||||||
load_all_plugins() ->
|
load_all_plugins() ->
|
||||||
%% save first
|
%% save first
|
||||||
{ok, [PluginApps]} = file:consult("etc/plugins.config"),
|
case file:consult("etc/plugins.config") of
|
||||||
application:set_env(emqttd, plugins, [App || {App, _Env} <- PluginApps]),
|
{ok, [PluginApps]} ->
|
||||||
[{App, load_plugin(App)} || {App, _Env} <- PluginApps].
|
application:set_env(emqttd, plugins, [App || {App, _Env} <- PluginApps]),
|
||||||
|
[{App, load_plugin(App)} || {App, _Env} <- PluginApps];
|
||||||
|
{error, enoent} ->
|
||||||
|
lager:error("etc/plugins.config not found!");
|
||||||
|
{error, Error} ->
|
||||||
|
lager:error("Load etc/plugins.config error: ~p", [Error])
|
||||||
|
end.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Load plugin
|
%% @doc Load plugin
|
||||||
|
@ -169,7 +175,6 @@ unload_all_plugins() ->
|
||||||
PluginApps = application:get_env(emqttd, plugins, []),
|
PluginApps = application:get_env(emqttd, plugins, []),
|
||||||
[{App, unload_plugin(App)} || App <- PluginApps].
|
[{App, unload_plugin(App)} || App <- PluginApps].
|
||||||
|
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Unload plugin
|
%% @doc Unload plugin
|
||||||
%% @end
|
%% @end
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/0,
|
-export([start_link/0,
|
||||||
|
start_link/1,
|
||||||
auth/2, % authentication
|
auth/2, % authentication
|
||||||
check_acl/3, % acl check
|
check_acl/3, % acl check
|
||||||
reload_acl/0, % reload acl
|
reload_acl/0, % reload acl
|
||||||
|
@ -60,7 +61,12 @@
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
-spec start_link() -> {ok, pid()} | ignore | {error, any()}.
|
-spec start_link() -> {ok, pid()} | ignore | {error, any()}.
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
{ok, AcOpts} = application:get_env(emqttd, access),
|
||||||
|
start_link(AcOpts).
|
||||||
|
|
||||||
|
-spec start_link(AcOpts :: list()) -> {ok, pid()} | ignore | {error, any()}.
|
||||||
|
start_link(AcOpts) ->
|
||||||
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [AcOpts], []).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Authenticate MQTT Client
|
%% @doc Authenticate MQTT Client
|
||||||
|
@ -151,8 +157,7 @@ stop() ->
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
init([]) ->
|
init([AcOpts]) ->
|
||||||
{ok, AcOpts} = application:get_env(emqttd, access),
|
|
||||||
ets:new(?ACCESS_CONTROL_TAB, [set, named_table, protected, {read_concurrency, true}]),
|
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, AcOpts))}),
|
ets:insert(?ACCESS_CONTROL_TAB, {auth_modules, init_mods(auth, proplists:get_value(auth, AcOpts))}),
|
||||||
ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(acl, proplists:get_value(acl, AcOpts))}),
|
ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(acl, proplists:get_value(acl, AcOpts))}),
|
||||||
|
@ -179,6 +184,7 @@ handle_call({register_mod, Type, Mod, Opts}, _From, State) ->
|
||||||
ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), [{Mod, ModState}|Mods]}),
|
ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), [{Mod, ModState}|Mods]}),
|
||||||
ok;
|
ok;
|
||||||
{'EXIT', Error} ->
|
{'EXIT', Error} ->
|
||||||
|
lager:error("Access Control: register ~s error - ~p", [Mod, Error]),
|
||||||
{error, Error}
|
{error, Error}
|
||||||
end;
|
end;
|
||||||
_ ->
|
_ ->
|
|
@ -73,9 +73,9 @@ compile(who, {user, Username}) ->
|
||||||
{user, bin(Username)};
|
{user, bin(Username)};
|
||||||
|
|
||||||
compile(topic, {eq, Topic}) ->
|
compile(topic, {eq, Topic}) ->
|
||||||
{eq, emqtt_topic:words(bin(Topic))};
|
{eq, emqttd_topic:words(bin(Topic))};
|
||||||
compile(topic, Topic) ->
|
compile(topic, Topic) ->
|
||||||
Words = emqtt_topic:words(bin(Topic)),
|
Words = emqttd_topic:words(bin(Topic)),
|
||||||
case 'pattern?'(Words) of
|
case 'pattern?'(Words) of
|
||||||
true -> {pattern, Words};
|
true -> {pattern, Words};
|
||||||
false -> Words
|
false -> Words
|
||||||
|
@ -114,9 +114,9 @@ match_who(#mqtt_client{clientid = ClientId}, {client, ClientId}) ->
|
||||||
true;
|
true;
|
||||||
match_who(#mqtt_client{username = Username}, {user, Username}) ->
|
match_who(#mqtt_client{username = Username}, {user, Username}) ->
|
||||||
true;
|
true;
|
||||||
match_who(#mqtt_client{ipaddr = undefined}, {ipaddr, _Tup}) ->
|
match_who(#mqtt_client{ipaddress = undefined}, {ipaddr, _Tup}) ->
|
||||||
false;
|
false;
|
||||||
match_who(#mqtt_client{ipaddr = IP}, {ipaddr, {_CDIR, Start, End}}) ->
|
match_who(#mqtt_client{ipaddress = IP}, {ipaddr, {_CDIR, Start, End}}) ->
|
||||||
I = esockd_access:atoi(IP),
|
I = esockd_access:atoi(IP),
|
||||||
I >= Start andalso I =< End;
|
I >= Start andalso I =< End;
|
||||||
match_who(_Client, _Who) ->
|
match_who(_Client, _Who) ->
|
||||||
|
@ -126,12 +126,12 @@ match_topics(_Client, _Topic, []) ->
|
||||||
false;
|
false;
|
||||||
match_topics(Client, Topic, [{pattern, PatternFilter}|Filters]) ->
|
match_topics(Client, Topic, [{pattern, PatternFilter}|Filters]) ->
|
||||||
TopicFilter = feed_var(Client, PatternFilter),
|
TopicFilter = feed_var(Client, PatternFilter),
|
||||||
case match_topic(emqtt_topic:words(Topic), TopicFilter) of
|
case match_topic(emqttd_topic:words(Topic), TopicFilter) of
|
||||||
true -> true;
|
true -> true;
|
||||||
false -> match_topics(Client, Topic, Filters)
|
false -> match_topics(Client, Topic, Filters)
|
||||||
end;
|
end;
|
||||||
match_topics(Client, Topic, [TopicFilter|Filters]) ->
|
match_topics(Client, Topic, [TopicFilter|Filters]) ->
|
||||||
case match_topic(emqtt_topic:words(Topic), TopicFilter) of
|
case match_topic(emqttd_topic:words(Topic), TopicFilter) of
|
||||||
true -> true;
|
true -> true;
|
||||||
false -> match_topics(Client, Topic, Filters)
|
false -> match_topics(Client, Topic, Filters)
|
||||||
end.
|
end.
|
||||||
|
@ -139,7 +139,7 @@ match_topics(Client, Topic, [TopicFilter|Filters]) ->
|
||||||
match_topic(Topic, {eq, TopicFilter}) ->
|
match_topic(Topic, {eq, TopicFilter}) ->
|
||||||
Topic =:= TopicFilter;
|
Topic =:= TopicFilter;
|
||||||
match_topic(Topic, TopicFilter) ->
|
match_topic(Topic, TopicFilter) ->
|
||||||
emqtt_topic:match(Topic, TopicFilter).
|
emqttd_topic:match(Topic, TopicFilter).
|
||||||
|
|
||||||
feed_var(Client, Pattern) ->
|
feed_var(Client, Pattern) ->
|
||||||
feed_var(Client, Pattern, []).
|
feed_var(Client, Pattern, []).
|
|
@ -0,0 +1,137 @@
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
%%% Copyright (c) 2012-2015 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.
|
||||||
|
%%%
|
||||||
|
%%% @end
|
||||||
|
%%%-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqttd_alarm).
|
||||||
|
|
||||||
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
|
-export([start_link/0, alarm_fun/0, get_alarms/0,
|
||||||
|
set_alarm/1, clear_alarm/1,
|
||||||
|
add_alarm_handler/1, add_alarm_handler/2,
|
||||||
|
delete_alarm_handler/1]).
|
||||||
|
|
||||||
|
-export([init/1, handle_event/2, handle_call/2, handle_info/2,
|
||||||
|
terminate/2]).
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
|
start_link() ->
|
||||||
|
case gen_event:start_link({local, ?SERVER}) of
|
||||||
|
{ok, Pid} ->
|
||||||
|
gen_event:add_handler(?SERVER, ?MODULE, []),
|
||||||
|
{ok, Pid};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
alarm_fun() ->
|
||||||
|
alarm_fun(false).
|
||||||
|
|
||||||
|
alarm_fun(Bool) ->
|
||||||
|
fun(alert, _Alarm) when Bool =:= true -> alarm_fun(true);
|
||||||
|
(alert, Alarm) when Bool =:= false -> set_alarm(Alarm), alarm_fun(true);
|
||||||
|
(clear, AlarmId) when Bool =:= true -> clear_alarm(AlarmId), alarm_fun(false);
|
||||||
|
(clear, _AlarmId) when Bool =:= false -> alarm_fun(false)
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec set_alarm(mqtt_alarm()) -> ok.
|
||||||
|
set_alarm(Alarm) when is_record(Alarm, mqtt_alarm) ->
|
||||||
|
gen_event:notify(?SERVER, {set_alarm, Alarm}).
|
||||||
|
|
||||||
|
-spec clear_alarm(any()) -> ok.
|
||||||
|
clear_alarm(AlarmId) when is_binary(AlarmId) ->
|
||||||
|
gen_event:notify(?SERVER, {clear_alarm, AlarmId}).
|
||||||
|
|
||||||
|
get_alarms() ->
|
||||||
|
gen_event:call(?SERVER, ?MODULE, get_alarms).
|
||||||
|
|
||||||
|
add_alarm_handler(Module) when is_atom(Module) ->
|
||||||
|
gen_event:add_handler(?SERVER, Module, []).
|
||||||
|
|
||||||
|
add_alarm_handler(Module, Args) when is_atom(Module) ->
|
||||||
|
gen_event:add_handler(?SERVER, Module, Args).
|
||||||
|
|
||||||
|
delete_alarm_handler(Module) when is_atom(Module) ->
|
||||||
|
gen_event:delete_handler(?SERVER, Module, []).
|
||||||
|
|
||||||
|
%%-----------------------------------------------------------------
|
||||||
|
%% Default Alarm handler
|
||||||
|
%%-----------------------------------------------------------------
|
||||||
|
init(_) ->
|
||||||
|
{ok, []}.
|
||||||
|
|
||||||
|
handle_event({set_alarm, Alarm = #mqtt_alarm{id = AlarmId,
|
||||||
|
severity = Severity,
|
||||||
|
title = Title,
|
||||||
|
summary = Summary}}, Alarms)->
|
||||||
|
Timestamp = os:timestamp(),
|
||||||
|
Json = mochijson2:encode([{id, AlarmId},
|
||||||
|
{severity, Severity},
|
||||||
|
{title, iolist_to_binary(Title)},
|
||||||
|
{summary, iolist_to_binary(Summary)},
|
||||||
|
{ts, emqttd_util: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()}]),
|
||||||
|
emqttd_pubsub:publish(alarm_msg(clear, AlarmId, Json)),
|
||||||
|
{ok, lists:keydelete(AlarmId, 2, Alarms)};
|
||||||
|
|
||||||
|
handle_event(_, Alarms)->
|
||||||
|
{ok, Alarms}.
|
||||||
|
|
||||||
|
handle_info(_, Alarms) ->
|
||||||
|
{ok, Alarms}.
|
||||||
|
|
||||||
|
handle_call(get_alarms, Alarms) ->
|
||||||
|
{ok, Alarms, Alarms};
|
||||||
|
|
||||||
|
handle_call(_Query, Alarms) ->
|
||||||
|
{ok, {error, bad_query}, Alarms}.
|
||||||
|
|
||||||
|
terminate(swap, Alarms) ->
|
||||||
|
{?MODULE, Alarms};
|
||||||
|
|
||||||
|
terminate(_, _) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
alarm_msg(Type, AlarmId, Json) ->
|
||||||
|
#mqtt_message{from = alarm,
|
||||||
|
qos = 1,
|
||||||
|
sys = true,
|
||||||
|
topic = topic(Type, AlarmId),
|
||||||
|
payload = iolist_to_binary(Json),
|
||||||
|
timestamp = os:timestamp()}.
|
||||||
|
|
||||||
|
topic(alert, AlarmId) ->
|
||||||
|
emqttd_topic:systop(<<"alarms/", AlarmId/binary, "/alert">>);
|
||||||
|
|
||||||
|
topic(clear, AlarmId) ->
|
||||||
|
emqttd_topic:systop(<<"alarms/", AlarmId/binary, "/clear">>).
|
||||||
|
|
||||||
|
|
|
@ -68,17 +68,15 @@ print_vsn() ->
|
||||||
?PRINT("~s ~s is running now~n", [Desc, Vsn]).
|
?PRINT("~s ~s is running now~n", [Desc, Vsn]).
|
||||||
|
|
||||||
start_servers(Sup) ->
|
start_servers(Sup) ->
|
||||||
Servers = [{"emqttd event", emqttd_event},
|
Servers = [{"emqttd trace", emqttd_trace},
|
||||||
{"emqttd trace", emqttd_trace},
|
|
||||||
{"emqttd pooler", {supervisor, emqttd_pooler_sup}},
|
{"emqttd pooler", {supervisor, emqttd_pooler_sup}},
|
||||||
{"emqttd client manager", {supervisor, emqttd_cm_sup}},
|
|
||||||
{"emqttd session manager", {supervisor, emqttd_sm_sup}},
|
{"emqttd session manager", {supervisor, emqttd_sm_sup}},
|
||||||
{"emqttd session supervisor", {supervisor, emqttd_session_sup}},
|
{"emqttd session supervisor", {supervisor, emqttd_session_sup}},
|
||||||
{"emqttd pubsub", {supervisor, emqttd_pubsub_sup}},
|
{"emqttd pubsub", {supervisor, emqttd_pubsub_sup}},
|
||||||
{"emqttd stats", emqttd_stats},
|
{"emqttd stats", emqttd_stats},
|
||||||
{"emqttd metrics", emqttd_metrics},
|
{"emqttd metrics", emqttd_metrics},
|
||||||
%{"emqttd router", emqttd_router},
|
|
||||||
{"emqttd broker", emqttd_broker},
|
{"emqttd broker", emqttd_broker},
|
||||||
|
{"emqttd alarm", emqttd_alarm},
|
||||||
{"emqttd mode supervisor", emqttd_mod_sup},
|
{"emqttd mode supervisor", emqttd_mod_sup},
|
||||||
{"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}},
|
{"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}},
|
||||||
{"emqttd access control", emqttd_access_control},
|
{"emqttd access control", emqttd_access_control},
|
|
@ -101,10 +101,10 @@ init(Opts) ->
|
||||||
|
|
||||||
check(#mqtt_client{clientid = undefined}, _Password, []) ->
|
check(#mqtt_client{clientid = undefined}, _Password, []) ->
|
||||||
{error, "ClientId undefined"};
|
{error, "ClientId undefined"};
|
||||||
check(#mqtt_client{clientid = ClientId, ipaddr = IpAddr}, _Password, []) ->
|
check(#mqtt_client{clientid = ClientId, ipaddress = IpAddress}, _Password, []) ->
|
||||||
check_clientid_only(ClientId, IpAddr);
|
check_clientid_only(ClientId, IpAddress);
|
||||||
check(#mqtt_client{clientid = ClientId, ipaddr = IpAddr}, _Password, [{password, no}|_]) ->
|
check(#mqtt_client{clientid = ClientId, ipaddress = IpAddress}, _Password, [{password, no}|_]) ->
|
||||||
check_clientid_only(ClientId, IpAddr);
|
check_clientid_only(ClientId, IpAddress);
|
||||||
check(_Client, undefined, [{password, yes}|_]) ->
|
check(_Client, undefined, [{password, yes}|_]) ->
|
||||||
{error, "Password undefined"};
|
{error, "Password undefined"};
|
||||||
check(#mqtt_client{clientid = ClientId}, Password, [{password, yes}|_]) ->
|
check(#mqtt_client{clientid = ClientId}, Password, [{password, yes}|_]) ->
|
|
@ -82,10 +82,7 @@ ldap_bind(LDAP, UserDn, Password) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
fill(Username, UserDn) ->
|
fill(Username, UserDn) ->
|
||||||
lists:append(lists:map(
|
re:replace(UserDn, "\\$u", Username, [global, {return, list}]).
|
||||||
fun("$u") -> Username;
|
|
||||||
(S) -> S
|
|
||||||
end, string:tokens(UserDn, ",="))).
|
|
||||||
|
|
||||||
description() ->
|
description() ->
|
||||||
"LDAP Authentication Module".
|
"LDAP Authentication Module".
|
|
@ -30,13 +30,13 @@
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
-include("emqttd_protocol.hrl").
|
||||||
|
|
||||||
-behaviour(gen_server).
|
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/3]).
|
-export([start_link/3]).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
@ -106,11 +106,11 @@ handle_call(_Request, _From, State) ->
|
||||||
handle_cast(_Msg, State) ->
|
handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Msg}}, State = #state{node = Node, status = down}) ->
|
handle_info({dispatch, Msg}, State = #state{node = Node, status = down}) ->
|
||||||
lager:warning("Bridge Dropped Msg for ~p Down:~n~p", [Node, Msg]),
|
lager:warning("Bridge Dropped Msg for ~p Down:~n~p", [Node, Msg]),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
||||||
handle_info({dispatch, {_From, Msg}}, State = #state{node = Node, status = up}) ->
|
handle_info({dispatch, Msg}, State = #state{node = Node, status = up}) ->
|
||||||
rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]),
|
rpc:cast(Node, emqttd_pubsub, publish, [transform(Msg, State)]),
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
|
|
|
@ -28,17 +28,14 @@
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-include("emqttd_systop.hrl").
|
-include_lib("emqttd.hrl").
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
|
||||||
|
|
||||||
-behaviour(gen_server).
|
|
||||||
|
|
||||||
-define(SERVER, ?MODULE).
|
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/0]).
|
-export([start_link/0]).
|
||||||
|
|
||||||
|
%% Running nodes
|
||||||
|
-export([running_nodes/0]).
|
||||||
|
|
||||||
%% Event API
|
%% Event API
|
||||||
-export([subscribe/1, notify/2]).
|
-export([subscribe/1, notify/2]).
|
||||||
|
|
||||||
|
@ -51,6 +48,10 @@
|
||||||
%% Tick API
|
%% Tick API
|
||||||
-export([start_tick/1, stop_tick/1]).
|
-export([start_tick/1, stop_tick/1]).
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
-define(SERVER, ?MODULE).
|
||||||
|
|
||||||
%% gen_server Function Exports
|
%% gen_server Function Exports
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
@ -59,6 +60,14 @@
|
||||||
|
|
||||||
-record(state, {started_at, sys_interval, tick_tref}).
|
-record(state, {started_at, sys_interval, tick_tref}).
|
||||||
|
|
||||||
|
%% $SYS Topics of Broker
|
||||||
|
-define(SYSTOP_BROKERS, [
|
||||||
|
version, % Broker version
|
||||||
|
uptime, % Broker uptime
|
||||||
|
datetime, % Broker local datetime
|
||||||
|
sysdescr % Broker description
|
||||||
|
]).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% API
|
%%% API
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
@ -71,6 +80,13 @@
|
||||||
start_link() ->
|
start_link() ->
|
||||||
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
||||||
|
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
%% @doc Get running nodes
|
||||||
|
%% @end
|
||||||
|
%%------------------------------------------------------------------------------
|
||||||
|
running_nodes() ->
|
||||||
|
mnesia:system_info(running_db_nodes).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Subscribe broker event
|
%% @doc Subscribe broker event
|
||||||
%% @end
|
%% @end
|
||||||
|
@ -205,6 +221,7 @@ init([]) ->
|
||||||
random:seed(now()),
|
random:seed(now()),
|
||||||
ets:new(?BROKER_TAB, [set, public, named_table]),
|
ets:new(?BROKER_TAB, [set, public, named_table]),
|
||||||
% Create $SYS Topics
|
% Create $SYS Topics
|
||||||
|
emqttd_pubsub:create(<<"$SYS/brokers">>),
|
||||||
[ok = create_topic(Topic) || Topic <- ?SYSTOP_BROKERS],
|
[ok = create_topic(Topic) || Topic <- ?SYSTOP_BROKERS],
|
||||||
% Tick
|
% Tick
|
||||||
{ok, #state{started_at = os:timestamp(), tick_tref = start_tick(tick)}, hibernate}.
|
{ok, #state{started_at = os:timestamp(), tick_tref = start_tick(tick)}, hibernate}.
|
||||||
|
@ -244,6 +261,7 @@ handle_cast(_Msg, State) ->
|
||||||
{noreply, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info(tick, State) ->
|
handle_info(tick, State) ->
|
||||||
|
retain(brokers),
|
||||||
retain(version, list_to_binary(version())),
|
retain(version, list_to_binary(version())),
|
||||||
retain(sysdescr, list_to_binary(sysdescr())),
|
retain(sysdescr, list_to_binary(sysdescr())),
|
||||||
publish(uptime, list_to_binary(uptime(State))),
|
publish(uptime, list_to_binary(uptime(State))),
|
||||||
|
@ -264,20 +282,28 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
create_topic(Topic) ->
|
create_topic(Topic) ->
|
||||||
emqttd_pubsub:create(emqtt_topic:systop(Topic)).
|
emqttd_pubsub:create(emqttd_topic:systop(Topic)).
|
||||||
|
|
||||||
|
retain(brokers) ->
|
||||||
|
Payload = list_to_binary(string:join([atom_to_list(N) || N <- running_nodes()], ",")),
|
||||||
|
publish(#mqtt_message{from = broker,
|
||||||
|
retain = true,
|
||||||
|
topic = <<"$SYS/brokers">>,
|
||||||
|
payload = Payload}).
|
||||||
|
|
||||||
retain(Topic, Payload) when is_binary(Payload) ->
|
retain(Topic, Payload) when is_binary(Payload) ->
|
||||||
publish(#mqtt_message{retain = true,
|
publish(#mqtt_message{from = broker,
|
||||||
topic = emqtt_topic:systop(Topic),
|
retain = true,
|
||||||
|
topic = emqttd_topic:systop(Topic),
|
||||||
payload = Payload}).
|
payload = Payload}).
|
||||||
|
|
||||||
publish(Topic, Payload) when is_binary(Payload) ->
|
publish(Topic, Payload) when is_binary(Payload) ->
|
||||||
publish( #mqtt_message{topic = emqtt_topic:systop(Topic),
|
publish( #mqtt_message{from = broker,
|
||||||
|
topic = emqttd_topic:systop(Topic),
|
||||||
payload = Payload}).
|
payload = Payload}).
|
||||||
|
|
||||||
publish(Msg) ->
|
publish(Msg) ->
|
||||||
emqttd_pubsub:publish(broker, Msg).
|
emqttd_pubsub:publish(Msg).
|
||||||
|
|
||||||
|
|
||||||
uptime(#state{started_at = Ts}) ->
|
uptime(#state{started_at = Ts}) ->
|
||||||
Secs = timer:now_diff(os:timestamp(), Ts) div 1000000,
|
Secs = timer:now_diff(os:timestamp(), Ts) div 1000000,
|
|
@ -28,9 +28,9 @@
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt_packet.hrl").
|
-include("emqttd_protocol.hrl").
|
||||||
|
|
||||||
%% API Function Exports
|
%% API Function Exports
|
||||||
-export([start_link/2, info/1]).
|
-export([start_link/2, info/1]).
|
||||||
|
@ -68,7 +68,7 @@ init([SockArgs = {Transport, Sock, _SockFun}, PacketOpts]) ->
|
||||||
{ok, ConnStr} = emqttd_net:connection_string(Sock, inbound),
|
{ok, ConnStr} = emqttd_net:connection_string(Sock, inbound),
|
||||||
lager:info("Connect from ~s", [ConnStr]),
|
lager:info("Connect from ~s", [ConnStr]),
|
||||||
SendFun = fun(Data) -> Transport:send(NewSock, Data) end,
|
SendFun = fun(Data) -> Transport:send(NewSock, Data) end,
|
||||||
ParserState = emqtt_parser:init(PacketOpts),
|
ParserState = emqttd_parser:init(PacketOpts),
|
||||||
ProtoState = emqttd_protocol:init(Peername, SendFun, PacketOpts),
|
ProtoState = emqttd_protocol:init(Peername, SendFun, PacketOpts),
|
||||||
State = control_throttle(#state{transport = Transport,
|
State = control_throttle(#state{transport = Transport,
|
||||||
socket = NewSock,
|
socket = NewSock,
|
||||||
|
@ -87,42 +87,35 @@ handle_call(info, _From, State = #state{conn_name=ConnName,
|
||||||
proto_state = ProtoState}) ->
|
proto_state = ProtoState}) ->
|
||||||
{reply, [{conn_name, ConnName} | emqttd_protocol:info(ProtoState)], State};
|
{reply, [{conn_name, ConnName} | emqttd_protocol:info(ProtoState)], State};
|
||||||
|
|
||||||
handle_call(Req, _From, State) ->
|
handle_call(Req, _From, State = #state{peername = Peername}) ->
|
||||||
{stop, {badreq, Req}, State}.
|
lager:critical("Client ~s: unexpected request - ~p",[emqttd_net:format(Peername), Req]),
|
||||||
|
{reply, {error, unsupported_request}, State}.
|
||||||
|
|
||||||
handle_cast(Msg, State) ->
|
handle_cast(Msg, State = #state{peername = Peername}) ->
|
||||||
{stop, {badmsg, Msg}, State}.
|
lager:critical("Client ~s: unexpected msg - ~p",[emqttd_net:format(Peername), Msg]),
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
handle_info(timeout, State) ->
|
handle_info(timeout, State) ->
|
||||||
stop({shutdown, timeout}, State);
|
stop({shutdown, timeout}, State);
|
||||||
|
|
||||||
handle_info({stop, duplicate_id, _NewPid}, State=#state{proto_state = ProtoState,
|
handle_info({stop, duplicate_id, _NewPid}, State=#state{proto_state = ProtoState,
|
||||||
conn_name=ConnName}) ->
|
conn_name=ConnName}) ->
|
||||||
%% TODO: to...
|
|
||||||
%% need transfer data???
|
%% need transfer data???
|
||||||
%% emqttd_client:transfer(NewPid, Data),
|
%% emqttd_client:transfer(NewPid, Data),
|
||||||
lager:error("Shutdown for duplicate clientid: ~s, conn:~s",
|
lager:error("Shutdown for duplicate clientid: ~s, conn:~s",
|
||||||
[emqttd_protocol:clientid(ProtoState), ConnName]),
|
[emqttd_protocol:clientid(ProtoState), ConnName]),
|
||||||
stop({shutdown, duplicate_id}, State);
|
stop({shutdown, duplicate_id}, State);
|
||||||
|
|
||||||
%%TODO: ok??
|
handle_info({deliver, Message}, #state{proto_state = ProtoState} = State) ->
|
||||||
handle_info({dispatch, {From, Messages}}, #state{proto_state = ProtoState} = State) when is_list(Messages) ->
|
{ok, ProtoState1} = emqttd_protocol:send(Message, ProtoState),
|
||||||
ProtoState1 =
|
|
||||||
lists:foldl(fun(Message, PState) ->
|
|
||||||
{ok, PState1} = emqttd_protocol:send({From, Message}, PState), PState1
|
|
||||||
end, ProtoState, Messages),
|
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
|
||||||
|
|
||||||
handle_info({dispatch, {From, Message}}, #state{proto_state = ProtoState} = State) ->
|
|
||||||
{ok, ProtoState1} = emqttd_protocol:send({From, Message}, ProtoState),
|
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
{noreply, State#state{proto_state = ProtoState1}};
|
||||||
|
|
||||||
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
|
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
|
||||||
{ok, ProtoState1} = emqttd_protocol:redeliver({?PUBREL, PacketId}, ProtoState),
|
{ok, ProtoState1} = emqttd_protocol:redeliver({?PUBREL, PacketId}, ProtoState),
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
{noreply, State#state{proto_state = ProtoState1}};
|
||||||
|
|
||||||
handle_info({subscribe, Topic, Qos}, #state{proto_state = ProtoState} = State) ->
|
handle_info({subscribe, TopicTable}, #state{proto_state = ProtoState} = State) ->
|
||||||
{ok, ProtoState1} = emqttd_protocol:handle({subscribe, Topic, Qos}, ProtoState),
|
{ok, ProtoState1} = emqttd_protocol:handle({subscribe, TopicTable}, ProtoState),
|
||||||
{noreply, State#state{proto_state = ProtoState1}};
|
{noreply, State#state{proto_state = ProtoState1}};
|
||||||
|
|
||||||
handle_info({inet_reply, _Ref, ok}, State) ->
|
handle_info({inet_reply, _Ref, ok}, State) ->
|
||||||
|
@ -158,11 +151,10 @@ handle_info({keepalive, timeout}, State = #state{peername = Peername, keepalive
|
||||||
|
|
||||||
handle_info(Info, State = #state{peername = Peername}) ->
|
handle_info(Info, State = #state{peername = Peername}) ->
|
||||||
lager:critical("Client ~s: unexpected info ~p",[emqttd_net:format(Peername), Info]),
|
lager:critical("Client ~s: unexpected info ~p",[emqttd_net:format(Peername), Info]),
|
||||||
{stop, {badinfo, Info}, State}.
|
{noreply, State}.
|
||||||
|
|
||||||
terminate(Reason, #state{peername = Peername, keepalive = KeepAlive, proto_state = ProtoState}) ->
|
terminate(Reason, #state{peername = Peername, keepalive = KeepAlive, proto_state = ProtoState}) ->
|
||||||
lager:info("Client ~s: ~p terminated, reason: ~p~n", [emqttd_net:format(Peername), self(), Reason]),
|
lager:info("Client ~s terminated, reason: ~p", [emqttd_net:format(Peername), Reason]),
|
||||||
notify(disconnected, Reason, ProtoState),
|
|
||||||
emqttd_keepalive:cancel(KeepAlive),
|
emqttd_keepalive:cancel(KeepAlive),
|
||||||
case {ProtoState, Reason} of
|
case {ProtoState, Reason} of
|
||||||
{undefined, _} -> ok;
|
{undefined, _} -> ok;
|
||||||
|
@ -185,7 +177,7 @@ process_received_bytes(Bytes, State = #state{packet_opts = PacketOpts,
|
||||||
parse_state = ParseState,
|
parse_state = ParseState,
|
||||||
proto_state = ProtoState,
|
proto_state = ProtoState,
|
||||||
conn_name = ConnStr}) ->
|
conn_name = ConnStr}) ->
|
||||||
case emqtt_parser:parse(Bytes, ParseState) of
|
case emqttd_parser:parse(Bytes, ParseState) of
|
||||||
{more, ParseState1} ->
|
{more, ParseState1} ->
|
||||||
{noreply,
|
{noreply,
|
||||||
control_throttle(State #state{parse_state = ParseState1}),
|
control_throttle(State #state{parse_state = ParseState1}),
|
||||||
|
@ -194,7 +186,7 @@ process_received_bytes(Bytes, State = #state{packet_opts = PacketOpts,
|
||||||
received_stats(Packet),
|
received_stats(Packet),
|
||||||
case emqttd_protocol:received(Packet, ProtoState) of
|
case emqttd_protocol:received(Packet, ProtoState) of
|
||||||
{ok, ProtoState1} ->
|
{ok, ProtoState1} ->
|
||||||
process_received_bytes(Rest, State#state{parse_state = emqtt_parser:init(PacketOpts),
|
process_received_bytes(Rest, State#state{parse_state = emqttd_parser:init(PacketOpts),
|
||||||
proto_state = ProtoState1});
|
proto_state = ProtoState1});
|
||||||
{error, Error} ->
|
{error, Error} ->
|
||||||
lager:error("MQTT protocol error ~p for connection ~p~n", [Error, ConnStr]),
|
lager:error("MQTT protocol error ~p for connection ~p~n", [Error, ConnStr]),
|
||||||
|
@ -231,7 +223,7 @@ control_throttle(State = #state{conn_state = Flow,
|
||||||
{_, _} -> run_socket(State)
|
{_, _} -> run_socket(State)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
stop(Reason, State ) ->
|
stop(Reason, State) ->
|
||||||
{stop, Reason, State}.
|
{stop, Reason, State}.
|
||||||
|
|
||||||
received_stats(?PACKET(Type)) ->
|
received_stats(?PACKET(Type)) ->
|
||||||
|
@ -253,12 +245,3 @@ inc(?DISCONNECT) ->
|
||||||
inc(_) ->
|
inc(_) ->
|
||||||
ignore.
|
ignore.
|
||||||
|
|
||||||
%%TODO: should be moved to emqttd_protocol... for event emitted when protocol shutdown...
|
|
||||||
notify(disconnected, _Reason, undefined) -> ingore;
|
|
||||||
|
|
||||||
notify(disconnected, {shutdown, Reason}, ProtoState) ->
|
|
||||||
emqttd_event:notify({disconnected, emqttd_protocol:clientid(ProtoState), Reason});
|
|
||||||
|
|
||||||
notify(disconnected, Reason, ProtoState) ->
|
|
||||||
emqttd_event:notify({disconnected, emqttd_protocol:clientid(ProtoState), Reason}).
|
|
||||||
|
|
|
@ -29,8 +29,7 @@
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-include("emqttd.hrl").
|
-include("emqttd.hrl").
|
||||||
|
-include("emqttd_protocol.hrl").
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
|
||||||
|
|
||||||
-import(proplists, [get_value/2, get_value/3]).
|
-import(proplists, [get_value/2, get_value/3]).
|
||||||
|
|
||||||
|
@ -53,10 +52,11 @@ handle_request('POST', "/mqtt/publish", Req) ->
|
||||||
Message = list_to_binary(get_value("message", Params)),
|
Message = list_to_binary(get_value("message", Params)),
|
||||||
case {validate(qos, Qos), validate(topic, Topic)} of
|
case {validate(qos, Qos), validate(topic, Topic)} of
|
||||||
{true, true} ->
|
{true, true} ->
|
||||||
emqttd_pubsub:publish(http, #mqtt_message{qos = Qos,
|
emqttd_pubsub:publish(#mqtt_message{from = http,
|
||||||
retain = Retain,
|
qos = Qos,
|
||||||
topic = Topic,
|
retain = Retain,
|
||||||
payload = Message}),
|
topic = Topic,
|
||||||
|
payload = Message}),
|
||||||
Req:ok({"text/plan", <<"ok\n">>});
|
Req:ok({"text/plan", <<"ok\n">>});
|
||||||
{false, _} ->
|
{false, _} ->
|
||||||
Req:respond({400, [], <<"Bad QoS">>});
|
Req:respond({400, [], <<"Bad QoS">>});
|
||||||
|
@ -121,7 +121,7 @@ validate(qos, Qos) ->
|
||||||
(Qos >= ?QOS_0) and (Qos =< ?QOS_2);
|
(Qos >= ?QOS_0) and (Qos =< ?QOS_2);
|
||||||
|
|
||||||
validate(topic, Topic) ->
|
validate(topic, Topic) ->
|
||||||
emqtt_topic:validate({name, Topic}).
|
emqttd_topic:validate({name, Topic}).
|
||||||
|
|
||||||
int(S) -> list_to_integer(S).
|
int(S) -> list_to_integer(S).
|
||||||
|
|
|
@ -24,15 +24,15 @@
|
||||||
%%%
|
%%%
|
||||||
%%% @end
|
%%% @end
|
||||||
%%%-----------------------------------------------------------------------------
|
%%%-----------------------------------------------------------------------------
|
||||||
-module(emqtt_message).
|
-module(emqttd_message).
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-include("emqtt.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
-include("emqtt_packet.hrl").
|
-include("emqttd_protocol.hrl").
|
||||||
|
|
||||||
-export([from_packet/1, to_packet/1]).
|
-export([from_packet/1, from_packet/2, to_packet/1]).
|
||||||
|
|
||||||
-export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]).
|
-export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]).
|
||||||
|
|
||||||
|
@ -70,6 +70,9 @@ from_packet(#mqtt_packet_connect{will_retain = Retain,
|
||||||
dup = false,
|
dup = false,
|
||||||
payload = Msg}.
|
payload = Msg}.
|
||||||
|
|
||||||
|
from_packet(ClientId, Packet) ->
|
||||||
|
Msg = from_packet(Packet), Msg#mqtt_message{from = ClientId}.
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% @doc Message to packet
|
%% @doc Message to packet
|
||||||
%% @end
|
%% @end
|
|
@ -28,9 +28,7 @@
|
||||||
|
|
||||||
-author("Feng Lee <feng@emqtt.io>").
|
-author("Feng Lee <feng@emqtt.io>").
|
||||||
|
|
||||||
-include("emqttd_systop.hrl").
|
-include("emqttd.hrl").
|
||||||
|
|
||||||
-include_lib("emqtt/include/emqtt.hrl").
|
|
||||||
|
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
@ -48,9 +46,41 @@
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||||
terminate/2, code_change/3]).
|
terminate/2, code_change/3]).
|
||||||
|
|
||||||
|
-record(state, {tick_tref}).
|
||||||
|
|
||||||
-define(METRIC_TAB, mqtt_metric).
|
-define(METRIC_TAB, mqtt_metric).
|
||||||
|
|
||||||
-record(state, {tick_tref}).
|
%% Bytes sent and received of Broker
|
||||||
|
-define(SYSTOP_BYTES, [
|
||||||
|
{counter, 'bytes/received'}, % Total bytes received
|
||||||
|
{counter, 'bytes/sent'} % Total bytes sent
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% Packets sent and received of Broker
|
||||||
|
-define(SYSTOP_PACKETS, [
|
||||||
|
{counter, 'packets/received'}, % All Packets received
|
||||||
|
{counter, 'packets/sent'}, % All Packets sent
|
||||||
|
{counter, 'packets/connect'}, % CONNECT Packets received
|
||||||
|
{counter, 'packets/connack'}, % CONNACK Packets sent
|
||||||
|
{counter, 'packets/publish/received'}, % PUBLISH packets received
|
||||||
|
{counter, 'packets/publish/sent'}, % PUBLISH packets sent
|
||||||
|
{counter, 'packets/subscribe'}, % SUBSCRIBE Packets received
|
||||||
|
{counter, 'packets/suback'}, % SUBACK packets sent
|
||||||
|
{counter, 'packets/unsubscribe'}, % UNSUBSCRIBE Packets received
|
||||||
|
{counter, 'packets/unsuback'}, % UNSUBACK Packets sent
|
||||||
|
{counter, 'packets/pingreq'}, % PINGREQ packets received
|
||||||
|
{counter, 'packets/pingresp'}, % PINGRESP Packets sent
|
||||||
|
{counter, 'packets/disconnect'} % DISCONNECT Packets received
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% Messages sent and received of broker
|
||||||
|
-define(SYSTOP_MESSAGES, [
|
||||||
|
{counter, 'messages/received'}, % Messages received
|
||||||
|
{counter, 'messages/sent'}, % Messages sent
|
||||||
|
{gauge, 'messages/retained/count'},% Messagea retained
|
||||||
|
{gauge, 'messages/stored/count'}, % Messages stored
|
||||||
|
{counter, 'messages/dropped'} % Messages dropped
|
||||||
|
]).
|
||||||
|
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
%%% API
|
%%% API
|
||||||
|
@ -163,7 +193,7 @@ init([]) ->
|
||||||
% Init metrics
|
% Init metrics
|
||||||
[create_metric(Metric) || Metric <- Metrics],
|
[create_metric(Metric) || Metric <- Metrics],
|
||||||
% $SYS Topics for metrics
|
% $SYS Topics for metrics
|
||||||
[ok = create_topic(Topic) || {_, Topic} <- Metrics],
|
[ok = emqttd_pubsub:create(metric_topic(Topic)) || {_, Topic} <- Metrics],
|
||||||
% Tick to publish metrics
|
% Tick to publish metrics
|
||||||
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.
|
{ok, #state{tick_tref = emqttd_broker:start_tick(tick)}, hibernate}.
|
||||||
|
|
||||||
|
@ -192,8 +222,9 @@ code_change(_OldVsn, State, _Extra) ->
|
||||||
%%%=============================================================================
|
%%%=============================================================================
|
||||||
|
|
||||||
publish(Metric, Val) ->
|
publish(Metric, Val) ->
|
||||||
emqttd_pubsub:publish(metrics, #mqtt_message{topic = emqtt_topic:systop(Metric),
|
emqttd_pubsub:publish(#mqtt_message{topic = metric_topic(Metric),
|
||||||
payload = emqttd_util:integer_to_binary(Val)}).
|
from = metrics,
|
||||||
|
payload = emqttd_util:integer_to_binary(Val)}).
|
||||||
|
|
||||||
create_metric({gauge, Name}) ->
|
create_metric({gauge, Name}) ->
|
||||||
ets:insert(?METRIC_TAB, {{Name, 0}, 0});
|
ets:insert(?METRIC_TAB, {{Name, 0}, 0});
|
||||||
|
@ -202,7 +233,7 @@ create_metric({counter, Name}) ->
|
||||||
Schedulers = lists:seq(1, erlang:system_info(schedulers)),
|
Schedulers = lists:seq(1, erlang:system_info(schedulers)),
|
||||||
[ets:insert(?METRIC_TAB, {{Name, I}, 0}) || I <- Schedulers].
|
[ets:insert(?METRIC_TAB, {{Name, I}, 0}) || I <- Schedulers].
|
||||||
|
|
||||||
create_topic(Topic) ->
|
metric_topic(Metric) ->
|
||||||
emqttd_pubsub:create(emqtt_topic:systop(Topic)).
|
emqttd_topic:systop(list_to_binary(lists:concat(['metrics/', Metric]))).
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue