This commit is contained in:
huangdan 2015-06-26 15:03:00 +08:00
commit 1dcf892a69
143 changed files with 2125 additions and 7232 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ plugins/*/ebin
log/ log/
*.swp *.swp
*.so *.so
examples

6
.gitmodules vendored
View File

@ -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

View File

@ -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)

View File

@ -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
![emqttd architecture](http://emqtt.io/static/img/Architecture.png) ![emqttd architecture](http://emqtt.io/static/img/Architecture.png)
@ -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
View File

@ -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

View File

@ -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{}.

View File

@ -1,12 +0,0 @@
{application, emqtt,
[
{description, "Erlang MQTT Common Library"},
{vsn, "0.8.0"},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib
]},
{env, []}
]}.

View File

@ -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{}.

View File

@ -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
]).

View File

@ -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{}.

View File

@ -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>
&nbsp; 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

View File

@ -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>
&nbsp; 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>

View File

@ -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).

View File

@ -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.

View File

@ -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}}.

View File

@ -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])).

View File

@ -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()}.

View File

@ -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}.

View File

@ -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.

234
doc/design/Seq.graphml Normal file
View File

@ -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>

14
doc/design/Seq.md Normal file
View File

@ -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

BIN
doc/design/Seq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
doc/design/qos0_seq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

5
go
View File

@ -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

View File

@ -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{}.

View File

@ -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!
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
@ -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}}).

View File

@ -1,12 +0,0 @@
{application, emqttd_amqp,
[
{description, ""},
{vsn, "1"},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, { emqttd_amqp_app, []}},
{env, []}
]}.

View File

@ -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.

View File

@ -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}, []} }.

View File

@ -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.

View File

@ -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"}]}
]}
].

View File

@ -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, []}
]}.

View File

@ -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}, []} }.

View File

@ -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.

View File

@ -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}
]}
]}

View File

@ -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, []}
]}.

View File

@ -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])).

View File

@ -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}, []} }.

View File

@ -1,12 +0,0 @@
{application, emqttd_dashboard,
[
{description, "emqttd management dashboard"},
{vsn, "0.1"},
{registered, []},
{applications, [
kernel,
stdlib
]},
{mod, {emqttd_dashboard_app, []}},
{env, []}
]}.

View File

@ -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).

View File

@ -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}, []} }.

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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}
]}
]}
].

View File

@ -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.

View File

@ -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,"."}]
}}
]}.

View File

@ -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".

View File

@ -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.

View File

@ -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, []}
]}.

View File

@ -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.

View File

@ -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

View File

@ -1,2 +0,0 @@
%% MySQL result record:
-record(mysql_result, {fieldinfo = [], rows = [], affectedrows = 0, insert_id =0, error = ""}).

View File

@ -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, []}}]}.

View File

@ -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]).

View File

@ -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.

View File

@ -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).

View File

@ -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.

View File

@ -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.

View File

@ -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)]]
}
}.

View File

@ -20,7 +20,6 @@
{sub_dirs, [ {sub_dirs, [
"rel", "rel",
"apps/*/",
"plugins/*/"]}. "plugins/*/"]}.
{deps, [ {deps, [

View File

@ -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}

View File

@ -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, [

View File

@ -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"}.

View File

@ -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,

View File

@ -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
{ok, [PluginApps]} ->
application:set_env(emqttd, plugins, [App || {App, _Env} <- PluginApps]), application:set_env(emqttd, plugins, [App || {App, _Env} <- PluginApps]),
[{App, load_plugin(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

View File

@ -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;
_ -> _ ->

View File

@ -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, []).

137
src/emqttd_alarm.erl Normal file
View File

@ -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">>).

View File

@ -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},

View File

@ -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}|_]) ->

View File

@ -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".

View File

@ -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};

View File

@ -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,

View File

@ -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]),
@ -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}).

View File

@ -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,7 +52,8 @@ 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,
qos = Qos,
retain = Retain, retain = Retain,
topic = Topic, topic = Topic,
payload = Message}), payload = Message}),
@ -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).

View File

@ -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

View File

@ -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,7 +222,8 @@ 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),
from = metrics,
payload = emqttd_util:integer_to_binary(Val)}). payload = emqttd_util:integer_to_binary(Val)}).
create_metric({gauge, Name}) -> create_metric({gauge, Name}) ->
@ -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