Merge branch 'dev'

This commit is contained in:
Feng Lee 2015-01-18 13:36:10 +08:00
commit 94748be765
61 changed files with 3063 additions and 1186 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "tests/org.eclipse.paho.mqtt.testing"]
path = tests/org.eclipse.paho.mqtt.testing
url = git://git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.testing.git

View File

@ -1,6 +1,34 @@
eMQTT ChangeLog
==================
v0.3.0-alpha (2015-01-18)
------------------------
NOTICE: Full MQTT 3.1.1 support now!
Feature: Passed org.eclipse.paho.mqtt.testing/interoperability tests
Feature: Qos0, Qos1 and Qos2 publish and suscribe
Feature: session(clean_sess=false) management and offline messages
Feature: redeliver awaiting puback/pubrec messages(doc: Chapter 4.4)
Feature: retain messages, add emqtt_server module
Feature: MQTT 3.1.1 null client_id support
Bugfix: keepalive timeout to send will message
Improve: overlapping subscription support
Improve: add emqtt_packet:dump to dump packets
Test: passed org.eclipse.paho.mqtt.testing/interoperability
Test: simple cluster test
v0.2.1-beta (2015-01-08)
------------------------

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014, Feng Lee <feng@slimchat.io>
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

View File

@ -45,19 +45,19 @@ cd $INSTALL_DIR/emqtt
### etc/app.config
```
{emqtt, [
{auth, {anonymous, []}}, %internal, anonymous
{listen, [
{mqtt, 1883, [
{max_conns, 1024},
{acceptor_pool, 4}
]},
{http, 8883, [
{max_conns, 512},
{acceptor_pool, 1}
]}
]}
]}
{emqtt, [
{auth, {anonymous, []}}, %internal, anonymous
{listen, [
{mqtt, 1883, [
{max_conns, 1024},
{acceptor_pool, 4}
]},
{http, 8883, [
{max_conns, 512},
{acceptor_pool, 1}
]}
]}
]}
```
@ -97,6 +97,26 @@ on 'host2':
Run './bin/emqtt_ctl cluster' on 'host1' or 'host2' to check cluster nodes.
## Cluster
Suppose we cluster two nodes on 'host1', 'host2', steps:
on 'host1':
```
./bin/emqtt start
```
on 'host2':
```
./bin/emqtt start
./bin/emqtt cluster emqtt@host1
```
Run './bin/emqtt cluster' on 'host1' or 'host2' to check cluster nodes.
## HTTP API
eMQTT support http to publish message.
@ -134,5 +154,6 @@ feng at emqtt.io
## Thanks
@hejin1026 (260495915 at qq.com)
@desoulter (assoulter123 at gmail.com)

29
TODO
View File

@ -23,3 +23,32 @@ merge pull request#26
temporary, 5000, worker, [emqtt_client]}]}}.
fucking stupid..... esockd locked
0.2.1
=====
full MQTT 3.1.1 support...
node cluster....
one million connections test...
topic match benchmark tests...
Dialyzer ...
full test cases...
spawn_link to replace 'spawn' and 'link'
keepalive
retained
QOS
dural sub
packet dump...

View File

@ -1,5 +1,5 @@
%%------------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -20,49 +20,76 @@
%% SOFTWARE.
%%------------------------------------------------------------------------------
%% ---------------------------------
%% banner
%% ---------------------------------
-define(COPYRIGHT, "Copyright (C) 2014, Feng Lee<feng@slimchat.io>").
%%------------------------------------------------------------------------------
%% Banner
%%------------------------------------------------------------------------------
-define(COPYRIGHT, "Copyright (C) 2012-2015, Feng Lee <feng@emqtt.io>").
-define(LICENSE_MESSAGE, "Licensed under MIT").
-define(PROTOCOL_VERSION, "MQTT/3.1").
-define(PROTOCOL_VERSION, "MQTT/3.1.1").
-define(ERTS_MINIMUM, "6.0").
%%------------------------------------------------------------------------------
%% MQTT Qos
%% MQTT QoS
%%------------------------------------------------------------------------------
-define(QOS_0, 0).
-define(QOS_1, 1).
-define(QOS_2, 2).
-type qos() :: ?QOS_2 | ?QOS_1 | ?QOS_0.
-type mqtt_qos() :: ?QOS_2 | ?QOS_1 | ?QOS_0.
%%------------------------------------------------------------------------------
%% MQTT Client
%%------------------------------------------------------------------------------
-record(mqtt_client, {
client_id,
username
}).
-type mqtt_client() :: #mqtt_client{}.
%%------------------------------------------------------------------------------
%% MQTT Session
%%------------------------------------------------------------------------------
-record(mqtt_session, {
client_id,
session_pid,
subscriptions = [],
awaiting_ack,
awaiting_rel
}).
-type mqtt_session() :: #mqtt_session{}.
%%------------------------------------------------------------------------------
%% MQTT Message
%%------------------------------------------------------------------------------
-record(mqtt_msg, {
retain,
qos,
topic,
dup,
msgid,
payload,
encoder
-record(mqtt_message, {
msgid :: integer() | undefined,
qos = ?QOS_0 :: mqtt_qos(),
retain = false :: boolean(),
dup = false :: boolean(),
topic :: binary(),
payload :: binary()
}).
-type mqtt_msg() :: #mqtt_msg{}.
-type mqtt_message() :: #mqtt_message{}.
%%------------------------------------------------------------------------------
%% MQTT User Management
%%------------------------------------------------------------------------------
-record(emqtt_user, {
username :: binary(),
passwdhash :: binary()
-record(mqtt_user, {
username :: binary(),
passwdhash :: binary()
}).
%%------------------------------------------------------------------------------
%% MQTT Authorization
%%------------------------------------------------------------------------------
%%TODO: ClientId | Username --> Pub | Sub --> Topics

View File

@ -1,92 +0,0 @@
%
% NOTICE: copy from rabbitmq mqtt-adaper
%
%% The contents of this file are subject to the Mozilla Public License
%% Version 1.1 (the "License"); you may not use this file except in
%% compliance with the License. You may obtain a copy of the License
%% at http://www.mozilla.org/MPL/
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and
%% limitations under the License.
%%
%% The Original Code is RabbitMQ.
%%
%% The Initial Developer of the Original Code is VMware, Inc.
%% Copyright (c) 2007-2012 VMware, Inc. All rights reserved.
%%
-define(CLIENT_ID_MAXLEN, 1024).
-define(PROTOCOL_NAMES, [{3, <<"MQIsdp">>}, {4, <<"MQTT">>}]).
-define(MQTT_PROTO_MAJOR, 3).
-define(MQTT_PROTO_MINOR, 1).
%% frame types
-define(CONNECT, 1).
-define(CONNACK, 2).
-define(PUBLISH, 3).
-define(PUBACK, 4).
-define(PUBREC, 5).
-define(PUBREL, 6).
-define(PUBCOMP, 7).
-define(SUBSCRIBE, 8).
-define(SUBACK, 9).
-define(UNSUBSCRIBE, 10).
-define(UNSUBACK, 11).
-define(PINGREQ, 12).
-define(PINGRESP, 13).
-define(DISCONNECT, 14).
%% connect return codes
-define(CONNACK_ACCEPT, 0).
-define(CONNACK_PROTO_VER, 1). %% unacceptable protocol version
-define(CONNACK_INVALID_ID, 2). %% identifier rejected
-define(CONNACK_SERVER, 3). %% server unavailable
-define(CONNACK_CREDENTIALS, 4). %% bad user name or password
-define(CONNACK_AUTH, 5). %% not authorized
-record(mqtt_frame, {fixed,
variable,
payload}).
-record(mqtt_frame_fixed, {type = 0,
dup = 0,
qos = 0,
retain = 0}).
-record(mqtt_frame_connect, {proto_ver,
will_retain,
will_qos,
will_flag,
clean_sess,
keep_alive,
client_id,
will_topic,
will_msg,
username,
password}).
-record(mqtt_frame_connack, {return_code}).
-record(mqtt_frame_publish, {topic_name,
message_id}).
-record(mqtt_frame_subscribe,{message_id,
topic_table}).
-record(mqtt_frame_suback, {message_id,
qos_table = []}).
-record(mqtt_topic, {name,
qos}).
-record(mqtt_frame_other, {other}).

View File

@ -1,85 +0,0 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.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.
%%------------------------------------------------------------------------------
%%------------------------------------------------------------------------------
%% Logging mechanism
%%------------------------------------------------------------------------------
-define(PRINT(Format, Args),
io:format(Format, Args)).
-define(PRINT_MSG(Msg),
io:format(Msg)).
-define(DEBUG(Format, Args),
lager:debug(Format, Args)).
-define(DEBUG_TRACE(Dest, Format, Args),
lager:debug(Dest, Format, Args)).
-define(DEBUG_MSG(Msg),
lager:debug(Msg)).
-define(INFO(Format, Args),
lager:info(Format, Args)).
-define(INFO_TRACE(Dest, Format, Args),
lager:info(Dest, Format, Args)).
-define(INFO_MSG(Msg),
lager:info(Msg)).
-define(WARN(Format, Args),
lager:warning(Format, Args)).
-define(WARN_TRACE(Dest, Format, Args),
lager:warning(Dest, Format, Args)).
-define(WARN_MSG(Msg),
lager:warning(Msg)).
-define(WARNING(Format, Args),
lager:warning(Format, Args)).
-define(WARNING_TRACE(Dest, Format, Args),
lager:warning(Dest, Format, Args)).
-define(WARNING_MSG(Msg),
lager:warning(Msg)).
-define(ERROR(Format, Args),
lager:error(Format, Args)).
-define(ERROR_TRACE(Dest, Format, Args),
lager:error(Dest, Format, Args)).
-define(ERROR_MSG(Msg),
lager:error(Msg)).
-define(CRITICAL(Format, Args),
lager:critical(Format, Args)).
-define(CRITICAL_TRACE(Dest, Format, Args),
lager:critical(Dest, Format, Args)).
-define(CRITICAL_MSG(Msg),
lager:critical(Msg)).

View File

@ -0,0 +1,135 @@
%%------------------------------------------------------------------------------
%% Copyright (c) 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.
%%------------------------------------------------------------------------------
%%
%% The Original Code is from RabbitMQ.
%%
%%------------------------------------------------------------------------------
%% 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">>}]).
-define(MAX_CLIENTID_LEN, 1024).
%%------------------------------------------------------------------------------
%% MQTT Control Packet Types
%%------------------------------------------------------------------------------
-define(RESERVED, 0). %% Reserved
-define(CONNECT, 1). %% Client request to connect to Server
-define(CONNACK, 2). %% Server to Client: Connect acknowledgment
-define(PUBLISH, 3). %% Publish message
-define(PUBACK, 4). %% Publish acknowledgment
-define(PUBREC, 5). %% Publish received (assured delivery part 1)
-define(PUBREL, 6). %% Publish release (assured delivery part 2)
-define(PUBCOMP, 7). %% Publish complete (assured delivery part 3)
-define(SUBSCRIBE, 8). %% Client subscribe request
-define(SUBACK, 9). %% Server Subscribe acknowledgment
-define(UNSUBSCRIBE, 10). %% Unsubscribe request
-define(UNSUBACK, 11). %% Unsubscribe acknowledgment
-define(PINGREQ, 12). %% PING request
-define(PINGRESP, 13). %% PING response
-define(DISCONNECT, 14). %% Client is disconnecting
%%------------------------------------------------------------------------------
%% MQTT Connect Return Codes
%%------------------------------------------------------------------------------
-define(CONNACK_ACCEPT, 0). %% Connection accepted
-define(CONNACK_PROTO_VER, 1). %% Unacceptable protocol version
-define(CONNACK_INVALID_ID, 2). %% Client Identifier is correct UTF-8 but not allowed by the Server
-define(CONNACK_SERVER, 3). %% Server unavailable
-define(CONNACK_CREDENTIALS, 4). %% Username or password is malformed
-define(CONNACK_AUTH, 5). %% Client is not authorized to connect
%%------------------------------------------------------------------------------
%% MQTT Erlang Types
%%------------------------------------------------------------------------------
-type mqtt_packet_type() :: ?RESERVED..?DISCONNECT.
-type mqtt_packet_id() :: 1..16#ffff | undefined.
%%------------------------------------------------------------------------------
%% MQTT Packet Fixed Header
%%------------------------------------------------------------------------------
-record(mqtt_packet_header, {
type = ?RESERVED :: mqtt_packet_type(),
dup = false :: boolean(),
qos = 0 :: 0 | 1 | 2,
retain = false :: boolean() }).
%%------------------------------------------------------------------------------
%% MQTT Packets
%%------------------------------------------------------------------------------
-record(mqtt_packet_connect, {
proto_ver,
proto_name,
will_retain,
will_qos,
will_flag,
clean_sess,
keep_alive,
client_id,
will_topic,
will_msg,
username,
password }).
-record(mqtt_packet_connack, {
ack_flags = ?RESERVED,
return_code }).
-record(mqtt_packet_publish, {
topic_name :: binary(),
packet_id :: mqtt_packet_id() }).
-record(mqtt_packet_puback, {
packet_id :: mqtt_packet_id() }).
-record(mqtt_topic, {
name :: binary(),
qos :: 0 | 1 | 2 }).
-record(mqtt_packet_subscribe, {
packet_id :: mqtt_packet_id(),
topic_table :: list(#mqtt_topic{}) }).
-record(mqtt_packet_suback, {
packet_id :: mqtt_packet_id(),
qos_table = [] }).
%%------------------------------------------------------------------------------
%% MQTT Control Packet
%%------------------------------------------------------------------------------
-record(mqtt_packet, {
header :: #mqtt_packet_header{},
variable :: #mqtt_packet_connect{} | #mqtt_packet_connack{}
| #mqtt_packet_publish{} | #mqtt_packet_puback{}
| #mqtt_packet_subscribe{} | #mqtt_packet_suback{}
| mqtt_packet_id(),
payload :: binary() }).
-type mqtt_packet() :: #mqtt_packet{}.

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% Copyright (c) 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
@ -24,15 +24,15 @@
%% Core PubSub Topic
%%------------------------------------------------------------------------------
-record(topic, {
name :: binary(),
node :: node()
name :: binary(),
node :: node()
}).
-type topic() :: #topic{}.
-record(topic_subscriber, {
topic :: binary(),
qos = 0 :: integer(),
qos = 0 :: non_neg_integer(),
subpid :: pid()
}).
@ -44,7 +44,7 @@
-record(topic_trie_edge, {
node_id :: binary(),
word :: binary()
word :: binary() | char()
}).
-record(topic_trie, {
@ -52,3 +52,8 @@
node_id :: binary()
}).
%%------------------------------------------------------------------------------
%% System Topic
%%------------------------------------------------------------------------------
-define(SYSTOP, <<"$SYS">>).

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -29,7 +29,7 @@
{packet, raw},
{reuseaddr, true},
{backlog, 512},
{nodelay, false}
{nodelay, true}
]).
listen(Listeners) when is_list(Listeners) ->

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,15 +22,17 @@
-module(emqtt_app).
-author('feng@slimchat.io').
-include("emqtt_log.hrl").
-author('feng@emqtt.io').
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).
-define(PRINT_MSG(Msg), io:format(Msg)).
-define(PRINT(Format, Args), io:format(Format, Args)).
%% ===================================================================
%% Application callbacks
%% ===================================================================
@ -57,38 +59,62 @@ print_vsn() ->
?PRINT("~s ~s is running now~n", [Desc, Vsn]).
start_servers(Sup) ->
{ok, SessOpts} = application:get_env(session),
{ok, RetainOpts} = application:get_env(retain),
lists:foreach(
fun({Name, F}) when is_function(F) ->
?PRINT("~s is starting...", [Name]),
F(),
?PRINT_MSG("[done]~n");
({Name, Server}) when is_atom(Server) ->
({Name, Server}) ->
?PRINT("~s is starting...", [Name]),
start_child(Sup, Server),
?PRINT_MSG("[done]~n");
({Name, Server, Opts}) when is_atom(Server) ->
({Name, Server, Opts}) ->
?PRINT("~s is starting...", [ Name]),
start_child(Sup, Server, Opts),
?PRINT_MSG("[done]~n")
end,
[{"emqtt cm", emqtt_cm},
[{"emqtt config", emqtt_config},
{"emqtt server", emqtt_server, RetainOpts},
{"emqtt client manager", emqtt_cm},
{"emqtt session manager", emqtt_sm},
{"emqtt session supervisor", {supervisor, emqtt_session_sup}, SessOpts},
{"emqtt auth", emqtt_auth},
{"emqtt retained", emqtt_retained},
{"emqtt pubsub", emqtt_pubsub},
{"emqtt router", emqtt_router},
{"emqtt monitor", emqtt_monitor}
]).
start_child(Sup, Name) ->
start_child(Sup, {supervisor, Name}) ->
supervisor:start_child(Sup, supervisor_spec(Name));
start_child(Sup, Name) when is_atom(Name) ->
{ok, _ChiId} = supervisor:start_child(Sup, worker_spec(Name)).
start_child(Sup, Name, Opts) ->
start_child(Sup, {supervisor, Name}, Opts) ->
supervisor:start_child(Sup, supervisor_spec(Name, Opts));
start_child(Sup, Name, Opts) when is_atom(Name) ->
{ok, _ChiId} = supervisor:start_child(Sup, worker_spec(Name, Opts)).
%%TODO: refactor...
supervisor_spec(Name) ->
{Name,
{Name, start_link, []},
permanent, infinity, supervisor, [Name]}.
supervisor_spec(Name, Opts) ->
{Name,
{Name, start_link, [Opts]},
permanent, infinity, supervisor, [Name]}.
worker_spec(Name) ->
{Name, {Name, start_link, []},
permanent, 5000, worker, [Name]}.
worker_spec(Name, Opts) ->
{Name, {Name, start_link, [Opts]},
permanent, 5000, worker, [Name]}.
{Name,
{Name, start_link, []},
permanent, 5000, worker, [Name]}.
worker_spec(Name, Opts) ->
{Name,
{Name, start_link, [Opts]},
permanent, 5000, worker, [Name]}.
%%
%% @spec stop(atom) -> 'ok'

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,12 +22,10 @@
-module(emqtt_auth).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-include("emqtt.hrl").
-include("emqtt_log.hrl").
-export([start_link/0,
add/2,
check/1, check/2,
@ -73,7 +71,6 @@ init([]) ->
ok = AuthMod:init(Opts),
ets:new(?TAB, [named_table, protected]),
ets:insert(?TAB, {mod, AuthMod}),
?PRINT("emqtt authmod is ~p", [AuthMod]),
{ok, undefined}.
authmod(Name) when is_atom(Name) ->

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,7 +22,7 @@
-module(emqtt_auth_anonymous).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-export([init/1,
add/2,

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,7 +22,7 @@
-module(emqtt_auth_internal).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-include("emqtt.hrl").
@ -32,10 +32,10 @@
delete/1]).
init(_Opts) ->
mnesia:create_table(emqtt_user, [
mnesia:create_table(mqtt_user, [
{ram_copies, [node()]},
{attributes, record_info(fields, emqtt_user)}]),
mnesia:add_table_copy(emqtt_user, node(), ram_copies),
{attributes, record_info(fields, mqtt_user)}]),
mnesia:add_table_copy(mqtt_user, node(), ram_copies),
ok.
check(undefined, _) -> false;
@ -44,19 +44,19 @@ check(_, undefined) -> false;
check(Username, Password) when is_binary(Username), is_binary(Password) ->
PasswdHash = crypto:hash(md5, Password),
case mnesia:dirty_read(emqtt_user, Username) of
[#emqtt_user{passwdhash=PasswdHash}] -> true;
case mnesia:dirty_read(mqtt_user, Username) of
[#mqtt_user{passwdhash=PasswdHash}] -> true;
_ -> false
end.
add(Username, Password) when is_binary(Username) and is_binary(Password) ->
mnesia:dirty_write(
#emqtt_user{
#mqtt_user{
username=Username,
passwdhash=crypto:hash(md5, Password)
}
).
delete(Username) when is_binary(Username) ->
mnesia:dirty_delete(emqtt_user, Username).
mnesia:dirty_delete(mqtt_user, Username).

View File

@ -0,0 +1,26 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_bridge).

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,165 +22,141 @@
-module(emqtt_client).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-behaviour(gen_server).
-export([start_link/1,
info/1,
go/2]).
-export([start_link/1, info/1, go/2]).
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2]).
terminate/2]).
-include("emqtt.hrl").
-include("emqtt_log.hrl").
-include("emqtt_packet.hrl").
-include("emqtt_frame.hrl").
-record(state, {socket,
conn_name,
await_recv,
connection_state,
conserve,
parse_state,
message_id,
client_id,
clean_sess,
will_msg,
keep_alive,
awaiting_ack,
subtopics,
awaiting_rel}).
-define(FRAME_TYPE(Frame, Type),
Frame = #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = Type }}).
%%Client State...
-record(state, {
socket,
peer_name,
conn_name,
await_recv,
conn_state,
conserve,
parse_state,
proto_state,
keepalive
}).
start_link(Sock) ->
gen_server:start_link(?MODULE, [Sock], []).
info(Pid) ->
gen_server:call(Pid, info).
gen_server:call(Pid, info).
go(Pid, Sock) ->
gen_server:call(Pid, {go, Sock}).
gen_server:call(Pid, {go, Sock}).
init([Sock]) ->
{ok, #state{socket = Sock}, 1000}.
handle_call({go, Sock}, _From, State=#state{socket = Sock}) ->
handle_call({go, Sock}, _From, #state{socket = Sock}) ->
{ok, Peername} = emqtt_net:peer_string(Sock),
{ok, ConnStr} = emqtt_net:connection_string(Sock, inbound),
lager:info("Connect from ~s", [ConnStr]),
{reply, ok,
control_throttle(
#state{ socket = Sock,
conn_name = ConnStr,
await_recv = false,
connection_state = running,
conserve = false,
parse_state = emqtt_frame:initial_state(),
message_id = 1,
subtopics = [],
awaiting_ack = gb_trees:empty(),
awaiting_rel = gb_trees:empty()})};
control_throttle(
#state{ socket = Sock,
peer_name = Peername,
conn_name = ConnStr,
await_recv = false,
conn_state = running,
conserve = false,
parse_state = emqtt_packet:initial_state(),
proto_state = emqtt_protocol:initial_state(Sock, Peername)}), 10000};
handle_call(info, _From, State = #state{
conn_name=ConnName, proto_state = ProtoState}) ->
{reply, [{conn_name, ConnName} | emqtt_protocol:info(ProtoState)], State};
handle_call(info, _From, #state{conn_name=ConnName,
message_id=MsgId, client_id=ClientId} = State) ->
Info = [{conn_name, ConnName},
{message_id, MsgId},
{client_id, ClientId}],
{reply, Info, State};
handle_call(_Req, _From, State) ->
{reply, ok, State}.
handle_call(Req, _From, State) ->
{stop, {badreq, Req}, State}.
handle_cast(Msg, State) ->
{stop, {badmsg, Msg}, State}.
{stop, {badmsg, Msg}, State}.
handle_info(timeout, State) ->
stop({shutdown, timeout}, State);
stop({shutdown, timeout}, State);
handle_info({stop, duplicate_id}, State=#state{conn_name=ConnName, client_id=ClientId}) ->
?ERROR("Shutdown for duplicate clientid:~s, conn:~s", [ClientId, ConnName]),
stop({shutdown, duplicate_id}, State);
handle_info({stop, duplicate_id, _NewPid}, State=#state{ proto_state = ProtoState, conn_name=ConnName}) ->
%% TODO: to...
%% need transfer data???
%% emqtt_client:transfer(NewPid, Data),
lager:error("Shutdown for duplicate clientid: ~s, conn:~s",
[emqtt_protocol:client_id(ProtoState), ConnName]),
stop({shutdown, duplicate_id}, State);
handle_info({dispatch, Msg}, #state{socket = Sock, message_id=MsgId} = State) ->
%%TODO: ok??
handle_info({dispatch, {From, Message}}, #state{proto_state = ProtoState} = State) ->
{ok, ProtoState1} = emqtt_protocol:send_message({From, Message}, ProtoState),
{noreply, State#state{proto_state = ProtoState1}};
#mqtt_msg{retain = Retain,
qos = Qos,
topic = Topic,
dup = Dup,
payload = Payload,
encoder = Encoder} = Msg,
Payload1 =
if
Encoder == undefined -> Payload;
true -> Encoder(Payload)
end,
Frame = #mqtt_frame{
fixed = #mqtt_frame_fixed{type = ?PUBLISH,
qos = Qos,
retain = Retain,
dup = Dup},
variable = #mqtt_frame_publish{topic_name = Topic,
message_id = if
Qos == ?QOS_0 -> undefined;
true -> MsgId
end},
payload = Payload1},
send_frame(Sock, Frame),
if
Qos == ?QOS_0 ->
{noreply, State};
true ->
{noreply, next_msg_id(State)}
end;
handle_info({redeliver, {?PUBREL, PacketId}}, #state{proto_state = ProtoState} = State) ->
{ok, ProtoState1} = emqtt_protocol:redeliver({?PUBREL, PacketId}, ProtoState),
{noreply, State#state{proto_state = ProtoState1}};
handle_info({inet_reply, _Ref, ok}, State) ->
{noreply, State, hibernate};
handle_info({inet_async, Sock, _Ref, {ok, Data}}, #state{ socket = Sock}=State) ->
handle_info({inet_async, Sock, _Ref, {ok, Data}}, State = #state{ peer_name = PeerName, socket = Sock }) ->
lager:debug("RECV from ~s: ~p", [PeerName, Data]),
process_received_bytes(
Data, control_throttle(State #state{ await_recv = false }));
Data, control_throttle(State #state{ await_recv = false }));
handle_info({inet_async, _Sock, _Ref, {error, Reason}}, State) ->
network_error(Reason, State);
handle_info({inet_reply, _Sock, {error, Reason}}, State) ->
{noreply, State};
handle_info({inet_reply, _Sock, {error, Reason}}, State = #state{peer_name = PeerName}) ->
lager:critical("Client ~s: unexpected inet_reply '~p'", [PeerName, Reason]),
{noreply, State};
handle_info(keep_alive_timeout, #state{keep_alive=KeepAlive}=State) ->
case emqtt_keep_alive:state(KeepAlive) of
idle ->
?INFO("keep_alive timeout: ~p", [State#state.conn_name]),
{stop, normal, State};
active ->
KeepAlive1 = emqtt_keep_alive:reset(KeepAlive),
{noreply, State#state{keep_alive=KeepAlive1}}
end;
handle_info({keepalive, start, TimeoutSec}, State = #state{socket = Socket}) ->
lager:info("Client ~s: Start KeepAlive with ~p seconds", [State#state.peer_name, TimeoutSec]),
KeepAlive = emqtt_keepalive:new(Socket, TimeoutSec, {keepalive, timeout}),
{noreply, State#state{ keepalive = KeepAlive }};
handle_info(Info, State) ->
?ERROR("badinfo :~p",[Info]),
{stop, {badinfo, Info}, State}.
handle_info({keepalive, timeout}, State = #state { keepalive = KeepAlive }) ->
case emqtt_keepalive:resume(KeepAlive) of
timeout ->
lager:info("Client ~s: Keepalive Timeout!", [State#state.peer_name]),
stop({shutdown, keepalive_timeout}, State#state{keepalive = undefined});
{resumed, KeepAlive1} ->
lager:info("Client ~s: Keepalive Resumed", [State#state.peer_name]),
{noreply, State#state{ keepalive = KeepAlive1 }}
end;
terminate(_Reason, #state{keep_alive=KeepAlive}) ->
emqtt_keep_alive:cancel(KeepAlive),
emqtt_cm:destroy(self()),
ok.
handle_info(Info, State = #state{peer_name = PeerName}) ->
lager:critical("Client ~s: unexpected info ~p",[PeerName, Info]),
{stop, {badinfo, Info}, State}.
terminate(Reason, #state{ peer_name = PeerName, keepalive = KeepAlive, proto_state = ProtoState }) ->
lager:info("Client ~s: ~p terminated, reason: ~p~n", [PeerName, self(), Reason]),
emqtt_keepalive:cancel(KeepAlive),
case {ProtoState, Reason} of
{undefined, _} -> ok;
{_, {shutdown, Error}} ->
emqtt_protocol:shutdown(Error, ProtoState);
{_, _} -> ok %TODO:
end,
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
async_recv(Sock, Length, infinity) when is_port(Sock) ->
prim_inet:async_recv(Sock, Length, -1);
@ -191,221 +167,43 @@ async_recv(Sock, Length, Timeout) when is_port(Sock) ->
% receive and parse tcp data
%-------------------------------------------------------
process_received_bytes(<<>>, State) ->
{noreply, State};
{noreply, State, hibernate};
process_received_bytes(Bytes,
State = #state{ parse_state = ParseState,
proto_state = ProtoState,
conn_name = ConnStr }) ->
case emqtt_frame:parse(Bytes, ParseState) of
{more, ParseState1} ->
{noreply,
control_throttle( State #state{ parse_state = ParseState1 }),
hibernate};
{ok, Frame, Rest} ->
case process_frame(Frame, State) of
{ok, State1} ->
PS = emqtt_frame:initial_state(),
process_received_bytes(
Rest,
State1 #state{ parse_state = PS});
{err, Reason, State1} ->
?ERROR("MQTT protocol error ~p for connection ~p~n", [Reason, ConnStr]),
stop({shutdown, Reason}, State1);
{stop, State1} ->
stop(normal, State1)
end;
{error, Error} ->
?ERROR("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]),
stop({shutdown, Error}, State)
case emqtt_packet:parse(Bytes, ParseState) of
{more, ParseState1} ->
{noreply,
control_throttle( State #state{ parse_state = ParseState1 }),
hibernate};
{ok, Packet, Rest} ->
case emqtt_protocol:handle_packet(Packet, ProtoState) of
{ok, ProtoState1} ->
process_received_bytes(
Rest,
State#state{ parse_state = emqtt_packet:initial_state(),
proto_state = ProtoState1 });
{error, Error} ->
lager:error("MQTT protocol error ~p for connection ~p~n", [Error, ConnStr]),
stop({shutdown, Error}, State);
{error, Error, ProtoState1} ->
stop({shutdown, Error}, State#state{proto_state = ProtoState1});
{stop, Reason, ProtoState1} ->
stop(Reason, State#state{proto_state = ProtoState1})
end;
{error, Error} ->
lager:error("MQTT detected framing error ~p for connection ~p~n", [ConnStr, Error]),
stop({shutdown, Error}, State)
end.
process_frame(Frame = #mqtt_frame{fixed = #mqtt_frame_fixed{type = Type}},
State=#state{client_id=ClientId, keep_alive=KeepAlive}) ->
KeepAlive1 = emqtt_keep_alive:activate(KeepAlive),
case validate_frame(Type, Frame) of
ok ->
?INFO("frame from ~s: ~p", [ClientId, Frame]),
handle_retained(Type, Frame),
process_request(Type, Frame, State#state{keep_alive=KeepAlive1});
{error, Reason} ->
{err, Reason, State}
end.
process_request(?CONNECT,
#mqtt_frame{ variable = #mqtt_frame_connect{
username = Username,
password = Password,
proto_ver = ProtoVersion,
clean_sess = CleanSess,
keep_alive = AlivePeriod,
client_id = ClientId } = Var}, #state{socket = Sock} = State) ->
{ReturnCode, State1} =
case {lists:member(ProtoVersion, proplists:get_keys(?PROTOCOL_NAMES)),
valid_client_id(ClientId)} of
{false, _} ->
{?CONNACK_PROTO_VER, State};
{_, false} ->
{?CONNACK_INVALID_ID, State};
_ ->
case emqtt_auth:check(Username, Password) of
false ->
?ERROR_MSG("MQTT login failed - no credentials"),
{?CONNACK_CREDENTIALS, State};
true ->
?INFO("connect from clientid: ~p, ~p", [ClientId, AlivePeriod]),
emqtt_cm:create(ClientId, self()),
KeepAlive = emqtt_keep_alive:new(AlivePeriod*1500, keep_alive_timeout),
{?CONNACK_ACCEPT,
State #state{ will_msg = make_will_msg(Var),
client_id = ClientId,
keep_alive = KeepAlive}}
end
end,
?INFO("recv conn...:~p", [ReturnCode]),
send_frame(Sock, #mqtt_frame{ fixed = #mqtt_frame_fixed{ type = ?CONNACK},
variable = #mqtt_frame_connack{
return_code = ReturnCode }}),
{ok, State1};
process_request(?PUBLISH, Frame=#mqtt_frame{
fixed = #mqtt_frame_fixed{qos = ?QOS_0}}, State) ->
emqtt_pubsub:publish(make_msg(Frame)),
{ok, State};
process_request(?PUBLISH,
Frame=#mqtt_frame{
fixed = #mqtt_frame_fixed{qos = ?QOS_1},
variable = #mqtt_frame_publish{message_id = MsgId}},
State=#state{socket=Sock}) ->
emqtt_pubsub:publish(make_msg(Frame)),
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PUBACK },
variable = #mqtt_frame_publish{ message_id = MsgId}}),
{ok, State};
process_request(?PUBLISH,
Frame=#mqtt_frame{
fixed = #mqtt_frame_fixed{qos = ?QOS_2},
variable = #mqtt_frame_publish{message_id = MsgId}},
State=#state{socket=Sock}) ->
emqtt_pubsub:publish(make_msg(Frame)),
put({msg, MsgId}, pubrec),
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{type = ?PUBREC},
variable = #mqtt_frame_publish{ message_id = MsgId}}),
{ok, State};
process_request(?PUBACK, #mqtt_frame{}, State) ->
%TODO: fixme later
{ok, State};
process_request(?PUBREC, #mqtt_frame{
variable = #mqtt_frame_publish{message_id = MsgId}},
State=#state{socket=Sock}) ->
%TODO: fixme later
send_frame(Sock,
#mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PUBREL},
variable = #mqtt_frame_publish{ message_id = MsgId}}),
{ok, State};
process_request(?PUBREL,
#mqtt_frame{
variable = #mqtt_frame_publish{message_id = MsgId}},
State=#state{socket=Sock}) ->
erase({msg, MsgId}),
send_frame(Sock,
#mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PUBCOMP},
variable = #mqtt_frame_publish{ message_id = MsgId}}),
{ok, State};
process_request(?PUBCOMP, #mqtt_frame{
variable = #mqtt_frame_publish{message_id = _MsgId}}, State) ->
%TODO: fixme later
{ok, State};
process_request(?SUBSCRIBE,
#mqtt_frame{
variable = #mqtt_frame_subscribe{message_id = MessageId,
topic_table = Topics},
payload = undefined},
#state{socket=Sock} = State) ->
[emqtt_pubsub:subscribe({Name, Qos}, self()) ||
#mqtt_topic{name=Name, qos=Qos} <- Topics],
GrantedQos = [Qos || #mqtt_topic{qos=Qos} <- Topics],
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{type = ?SUBACK},
variable = #mqtt_frame_suback{
message_id = MessageId,
qos_table = GrantedQos}}),
{ok, State};
process_request(?UNSUBSCRIBE,
#mqtt_frame{
variable = #mqtt_frame_subscribe{message_id = MessageId,
topic_table = Topics },
payload = undefined}, #state{socket = Sock, client_id = ClientId} = State) ->
[emqtt_pubsub:unsubscribe(Name, self()) || #mqtt_topic{name=Name} <- Topics],
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{type = ?UNSUBACK },
variable = #mqtt_frame_suback{message_id = MessageId }}),
{ok, State};
process_request(?PINGREQ, #mqtt_frame{}, #state{socket=Sock, keep_alive=KeepAlive}=State) ->
%Keep alive timer
KeepAlive1 = emqtt_keep_alive:reset(KeepAlive),
send_frame(Sock, #mqtt_frame{fixed = #mqtt_frame_fixed{ type = ?PINGRESP }}),
{ok, State#state{keep_alive=KeepAlive1}};
process_request(?DISCONNECT, #mqtt_frame{}, State=#state{client_id=ClientId}) ->
?INFO("~s disconnected", [ClientId]),
{stop, State}.
next_msg_id(State = #state{ message_id = 16#ffff }) ->
State #state{ message_id = 1 };
next_msg_id(State = #state{ message_id = MsgId }) ->
State #state{ message_id = MsgId + 1 }.
maybe_clean_sess(false, _Conn, _ClientId) ->
% todo: establish subscription to deliver old unacknowledged messages
ok.
%%----------------------------------------------------------------------------
make_will_msg(#mqtt_frame_connect{ will_flag = false }) ->
undefined;
make_will_msg(#mqtt_frame_connect{ will_retain = Retain,
will_qos = Qos,
will_topic = Topic,
will_msg = Msg }) ->
#mqtt_msg{retain = Retain,
qos = Qos,
topic = Topic,
dup = false,
payload = Msg }.
send_will_msg(#state{will_msg = undefined}) ->
ignore;
send_will_msg(#state{will_msg = WillMsg }) ->
emqtt_pubsub:publish(WillMsg).
send_frame(Sock, Frame) ->
?INFO("send frame:~p", [Frame]),
erlang:port_command(Sock, emqtt_frame:serialise(Frame)).
%%----------------------------------------------------------------------------
network_error(Reason,
State = #state{ conn_name = ConnStr}) ->
?ERROR("MQTT detected network error '~p' for ~p", [Reason, ConnStr]),
send_will_msg(State),
% todo: flush channel after publish
network_error(Reason, State = #state{ peer_name = PeerName }) ->
lager:error("Client ~s: MQTT detected network error '~p'", [PeerName, Reason]),
stop({shutdown, conn_closed}, State).
run_socket(State = #state{ connection_state = blocked }) ->
run_socket(State = #state{ conn_state = blocked }) ->
State;
run_socket(State = #state{ await_recv = true }) ->
State;
@ -413,75 +211,16 @@ run_socket(State = #state{ socket = Sock }) ->
async_recv(Sock, 0, infinity),
State#state{ await_recv = true }.
control_throttle(State = #state{ connection_state = Flow,
control_throttle(State = #state{ conn_state = Flow,
conserve = Conserve }) ->
case {Flow, Conserve} of
{running, true} -> State #state{ connection_state = blocked };
{running, true} -> State #state{ conn_state = blocked };
{blocked, false} -> run_socket(State #state{
connection_state = running });
conn_state = running });
{_, _} -> run_socket(State)
end.
stop(Reason, State ) ->
{stop, Reason, State}.
valid_client_id(ClientId) ->
ClientIdLen = size(ClientId),
1 =< ClientIdLen andalso ClientIdLen =< ?CLIENT_ID_MAXLEN.
handle_retained(?PUBLISH, #mqtt_frame{fixed = #mqtt_frame_fixed{retain = false}}) ->
ignore;
handle_retained(?PUBLISH, #mqtt_frame{
fixed = #mqtt_frame_fixed{retain = true},
variable = #mqtt_frame_publish{topic_name = Topic},
payload= <<>> }) ->
emqtt_retained:delete(Topic);
handle_retained(?PUBLISH, Frame=#mqtt_frame{
fixed = #mqtt_frame_fixed{retain = true},
variable = #mqtt_frame_publish{topic_name = Topic}}) ->
emqtt_retained:insert(Topic, make_msg(Frame));
handle_retained(_, _) ->
ignore.
validate_frame(?PUBLISH, #mqtt_frame{variable = #mqtt_frame_publish{topic_name = Topic}}) ->
case emqtt_topic:validate({publish, Topic}) of
true -> ok;
false -> {error, badtopic}
end;
validate_frame(?UNSUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_table = Topics}}) ->
ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics,
not emqtt_topic:validate({subscribe, Topic})],
case ErrTopics of
[] -> ok;
_ -> ?ERROR("error topics: ~p", [ErrTopics]), {error, badtopic}
end;
validate_frame(?SUBSCRIBE, #mqtt_frame{variable = #mqtt_frame_subscribe{topic_table = Topics}}) ->
ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics,
not (emqtt_topic:validate({subscribe, Topic}) and (Qos < 3))],
case ErrTopics of
[] -> ok;
_ -> ?ERROR("error topics: ~p", [ErrTopics]), {error, badtopic}
end;
validate_frame(_Type, _Frame) ->
ok.
make_msg(#mqtt_frame{
fixed = #mqtt_frame_fixed{qos = Qos,
retain = Retain,
dup = Dup},
variable = #mqtt_frame_publish{topic_name = Topic,
message_id = MessageId},
payload = Payload}) ->
#mqtt_msg{retain = Retain,
qos = Qos,
topic = Topic,
dup = Dup,
msgid = MessageId,
payload = Payload}.

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -23,21 +23,21 @@
%client manager
-module(emqtt_cm).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(TAB, emqtt_client).
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([start_link/0]).
-export([create/2,
destroy/1,
lookup/1]).
-export([lookup/1, register/2, unregister/2]).
%% ------------------------------------------------------------------
%% gen_server Function Exports
@ -56,47 +56,75 @@
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%
%% @doc lookup client pid with clientId.
%%
-spec lookup(ClientId :: binary()) -> pid() | undefined.
lookup(ClientId) ->
lookup(ClientId) when is_binary(ClientId) ->
case ets:lookup(emqtt_client, ClientId) of
[{_, Pid}] -> Pid;
[{_, Pid, _}] -> Pid;
[] -> undefined
end.
-spec create(ClientId :: binary(), Pid :: pid()) -> ok.
create(ClientId, Pid) ->
case lookup(ClientId) of
Pid ->
ignore;
OldPid when is_pid(OldPid) ->
OldPid ! {stop, duplicate_id},
ets:insert(emqtt_client, {ClientId, Pid});
undefined ->
ets:insert(emqtt_client, {ClientId, Pid})
end.
%%
%% @doc register clientId with pid.
%%
-spec register(ClientId :: binary(), Pid :: pid()) -> ok.
register(ClientId, Pid) when is_binary(ClientId), is_pid(Pid) ->
gen_server:call(?SERVER, {register, ClientId, Pid}).
-spec destroy(binary() | pid()) -> ok.
destroy(ClientId) when is_binary(ClientId) ->
ets:delete(emqtt_client, ClientId);
destroy(Pid) when is_pid(Pid) ->
ets:match_delete(emqtt_client, {{'_', Pid}}).
%%
%% @doc unregister clientId with pid.
%%
-spec unregister(ClientId :: binary(), Pid :: pid()) -> ok.
unregister(ClientId, Pid) when is_binary(ClientId), is_pid(Pid) ->
gen_server:cast(?SERVER, {unregister, ClientId, Pid}).
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
init(Args) ->
init([]) ->
%on one node
ets:new(emqtt_client, [named_table, public]),
{ok, Args}.
ets:new(?TAB, [set, named_table, protected]),
{ok, none}.
handle_call({register, ClientId, Pid}, _From, State) ->
case ets:lookup(?TAB, ClientId) of
[{_, Pid, _}] ->
lager:error("clientId '~s' has been registered with ~p", [ClientId, Pid]),
ignore;
[{_, OldPid, MRef}] ->
OldPid ! {stop, duplicate_id, Pid},
erlang:demonitor(MRef),
insert(ClientId, Pid);
[] ->
insert(ClientId, Pid)
end,
{reply, ok, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast({unregister, ClientId, Pid}, State) ->
case ets:lookup(?TAB, ClientId) of
[{_, Pid, MRef}] ->
erlang:demonitor(MRef),
ets:delete(?TAB, ClientId);
[_] ->
ignore;
[] ->
lager:error("cannot find clientId '~s' with ~p", [ClientId, Pid])
end,
{noreply, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
ets:match_delete(emqtt_client, {{'_', DownPid, MRef}}),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
@ -106,4 +134,6 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
insert(ClientId, Pid) ->
ets:insert(emqtt_client, {ClientId, Pid, erlang:monitor(process, Pid)}).

View File

@ -0,0 +1,82 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_config).
-export([lookup/1]).
-behaviour(gen_server).
-define(SERVER, ?MODULE).
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([start_link/0]).
%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%%TODO: fix later...
lookup(Key) -> {ok, Key}.
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
init(_Args) ->
ets:new(?MODULE, [set, protected, named_table]),
%%TODO: Load application config.
{ok, none}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,17 +22,20 @@
-module(emqtt_ctl).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-include("emqtt.hrl").
-include("emqtt_log.hrl").
-define(PRINT_MSG(Msg),
io:format(Msg)).
-define(PRINT(Format, Args),
io:format(Format, Args)).
-export([status/1,
cluster_info/1,
cluster/1,
add_user/1,
delete_user/1]).
useradd/1,
userdel/1]).
status([]) ->
{InternalStatus, _ProvidedStatus} = init:get_status(),
@ -44,26 +47,47 @@ status([]) ->
?PRINT_MSG("emqtt is running~n")
end.
cluster_info([]) ->
cluster([]) ->
Nodes = [node()|nodes()],
?PRINT("cluster nodes: ~p~n", [Nodes]).
?PRINT("cluster nodes: ~p~n", [Nodes]);
cluster([SNode]) ->
Node = list_to_atom(SNode),
Node = node_name(SNode),
case net_adm:ping(Node) of
pong ->
application:stop(emqtt),
application:stop(esockd),
mnesia:stop(),
mnesia:start(),
mnesia:change_config(extra_db_nodes, [Node]),
application:start(esockd),
application:start(emqtt),
?PRINT("cluster with ~p successfully.~n", [Node]);
pang ->
?PRINT("failed to connect to ~p~n", [Node])
end.
add_user([Username, Password]) ->
useradd([Username, Password]) ->
?PRINT("~p", [emqtt_auth:add(list_to_binary(Username), list_to_binary(Password))]).
delete_user([Username]) ->
userdel([Username]) ->
?PRINT("~p", [emqtt_auth:delete(list_to_binary(Username))]).
node_name(SNode) ->
SNode1 =
case string:tokens(SNode, "@") of
[_Node, _Server] ->
SNode;
_ ->
case net_kernel:longnames() of
true ->
SNode ++ "@" ++ inet_db:gethostname() ++
"." ++ inet_db:res_option(domain);
false ->
SNode ++ "@" ++ inet_db:gethostname();
_ ->
SNode
end
end,
list_to_atom(SNode1).

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,7 +22,7 @@
-module(emqtt_db).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-export([init/0, stop/0]).

View File

@ -0,0 +1,29 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_event).
-export([start_link/0]).
start_link() ->
gen_event:start_link({local, ?MODULE}).

View File

@ -1,266 +0,0 @@
%%------------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.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.
%%------------------------------------------------------------------------------
%%
%% The Original Code is from RabbitMQ.
%%
-module(emqtt_frame).
-include("emqtt_frame.hrl").
-export([parse/2, initial_state/0]).
-export([serialise/1]).
-define(RESERVED, 0).
-define(MAX_LEN, 16#fffffff).
-define(HIGHBIT, 2#10000000).
-define(LOWBITS, 2#01111111).
initial_state() -> none.
parse(<<>>, none) ->
{more, fun(Bin) -> parse(Bin, none) end};
parse(<<MessageType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
parse_remaining_len(Rest, #mqtt_frame_fixed{ type = MessageType,
dup = bool(Dup),
qos = QoS,
retain = bool(Retain) });
parse(Bin, Cont) -> Cont(Bin).
parse_remaining_len(<<>>, Fixed) ->
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed) end};
parse_remaining_len(Rest, Fixed) ->
parse_remaining_len(Rest, Fixed, 1, 0).
parse_remaining_len(_Bin, _Fixed, _Multiplier, Length)
when Length > ?MAX_LEN ->
{error, invalid_mqtt_frame_len};
parse_remaining_len(<<>>, Fixed, Multiplier, Length) ->
{more, fun(Bin) -> parse_remaining_len(Bin, Fixed, Multiplier, Length) end};
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
parse_remaining_len(Rest, Fixed, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Fixed, Multiplier, Value) ->
parse_frame(Rest, Fixed, Value + Len * Multiplier).
parse_frame(Bin, #mqtt_frame_fixed{ type = Type,
qos = Qos } = Fixed, Length) ->
case {Type, Bin} of
{?CONNECT, <<FrameBin:Length/binary, Rest/binary>>} ->
{ProtoName, Rest1} = parse_utf(FrameBin),
<<ProtoVersion : 8, Rest2/binary>> = Rest1,
<<UsernameFlag : 1,
PasswordFlag : 1,
WillRetain : 1,
WillQos : 2,
WillFlag : 1,
CleanSession : 1,
_Reserved : 1,
KeepAlive : 16/big,
Rest3/binary>> = Rest2,
{ClientId, Rest4} = parse_utf(Rest3),
{WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
case protocol_name_approved(ProtoVersion, ProtoName) of
true ->
wrap(Fixed,
#mqtt_frame_connect{
proto_ver = ProtoVersion,
will_retain = bool(WillRetain),
will_qos = WillQos,
will_flag = bool(WillFlag),
clean_sess = bool(CleanSession),
keep_alive = KeepAlive,
client_id = ClientId,
will_topic = WillTopic,
will_msg = WillMsg,
username = UserName,
password = PasssWord}, Rest);
false ->
{error, protocol_header_corrupt}
end;
{?PUBLISH, <<FrameBin:Length/binary, Rest/binary>>} ->
{TopicName, Rest1} = parse_utf(FrameBin),
{MessageId, Payload} = case Qos of
0 -> {undefined, Rest1};
_ -> <<M:16/big, R/binary>> = Rest1,
{M, R}
end,
wrap(Fixed, #mqtt_frame_publish {topic_name = TopicName,
message_id = MessageId },
Payload, Rest);
{?PUBACK, <<FrameBin:Length/binary, Rest/binary>>} ->
<<MessageId:16/big>> = FrameBin,
wrap(Fixed, #mqtt_frame_publish{message_id = MessageId}, Rest);
{?PUBREC, <<FrameBin:Length/binary, Rest/binary>>} ->
<<MessageId:16/big>> = FrameBin,
wrap(Fixed, #mqtt_frame_publish{message_id = MessageId}, Rest);
{?PUBREL, <<FrameBin:Length/binary, Rest/binary>>} ->
<<MessageId:16/big>> = FrameBin,
wrap(Fixed, #mqtt_frame_publish { message_id = MessageId }, Rest);
{?PUBCOMP, <<FrameBin:Length/binary, Rest/binary>>} ->
<<MessageId:16/big>> = FrameBin,
wrap(Fixed, #mqtt_frame_publish { message_id = MessageId }, Rest);
{Subs, <<FrameBin:Length/binary, Rest/binary>>}
when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
1 = Qos,
<<MessageId:16/big, Rest1/binary>> = FrameBin,
Topics = parse_topics(Subs, Rest1, []),
wrap(Fixed, #mqtt_frame_subscribe { message_id = MessageId,
topic_table = Topics }, Rest);
{Minimal, Rest}
when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
Length = 0,
wrap(Fixed, Rest);
{_, TooShortBin} ->
{more, fun(BinMore) ->
parse_frame(<<TooShortBin/binary, BinMore/binary>>,
Fixed, Length)
end}
end.
parse_topics(_, <<>>, Topics) ->
Topics;
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
{Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]);
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
{Name, <<Rest/binary>>} = parse_utf(Bin),
parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]).
wrap(Fixed, Variable, Payload, Rest) ->
{ok, #mqtt_frame { variable = Variable, fixed = Fixed, payload = Payload }, Rest}.
wrap(Fixed, Variable, Rest) ->
{ok, #mqtt_frame { variable = Variable, fixed = Fixed }, Rest}.
wrap(Fixed, Rest) ->
{ok, #mqtt_frame { fixed = Fixed }, Rest}.
parse_utf(Bin, 0) ->
{undefined, Bin};
parse_utf(Bin, _) ->
parse_utf(Bin).
parse_utf(<<Len:16/big, Str:Len/binary, Rest/binary>>) ->
{Str, Rest}.
parse_msg(Bin, 0) ->
{undefined, Bin};
parse_msg(<<Len:16/big, Msg:Len/binary, Rest/binary>>, _) ->
{Msg, Rest}.
bool(0) -> false;
bool(1) -> true.
%% serialisation
serialise(#mqtt_frame{ fixed = Fixed,
variable = Variable,
payload = Payload }) ->
serialise_variable(Fixed, Variable, serialise_payload(Payload)).
serialise_payload(undefined) -> <<>>;
serialise_payload(B) when is_binary(B) -> B.
serialise_variable(#mqtt_frame_fixed { type = ?CONNACK } = Fixed,
#mqtt_frame_connack { return_code = ReturnCode },
<<>> = PayloadBin) ->
VariableBin = <<?RESERVED:8, ReturnCode:8>>,
serialise_fixed(Fixed, VariableBin, PayloadBin);
serialise_variable(#mqtt_frame_fixed { type = SubAck } = Fixed,
#mqtt_frame_suback { message_id = MessageId,
qos_table = Qos },
<<>> = _PayloadBin)
when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
VariableBin = <<MessageId:16/big>>,
QosBin = << <<?RESERVED:6, Q:2>> || Q <- Qos >>,
serialise_fixed(Fixed, VariableBin, QosBin);
serialise_variable(#mqtt_frame_fixed { type = ?PUBLISH,
qos = Qos } = Fixed,
#mqtt_frame_publish { topic_name = TopicName,
message_id = MessageId },
PayloadBin) ->
TopicBin = serialise_utf(TopicName),
MessageIdBin = case Qos of
0 -> <<>>;
1 -> <<MessageId:16/big>>;
2 -> <<MessageId:16/big>>
end,
serialise_fixed(Fixed, <<TopicBin/binary, MessageIdBin/binary>>, PayloadBin);
serialise_variable(#mqtt_frame_fixed { type = ?PUBACK } = Fixed,
#mqtt_frame_publish { message_id = MessageId },
PayloadBin) ->
MessageIdBin = <<MessageId:16/big>>,
serialise_fixed(Fixed, MessageIdBin, PayloadBin);
serialise_variable(#mqtt_frame_fixed { type = ?PUBREC } = Fixed,
#mqtt_frame_publish{ message_id = MsgId},
PayloadBin) ->
serialise_fixed(Fixed, <<MsgId:16/big>>, PayloadBin);
serialise_variable(#mqtt_frame_fixed { type = ?PUBREL } = Fixed,
#mqtt_frame_publish{ message_id = MsgId},
PayloadBin) ->
serialise_fixed(Fixed, <<MsgId:16/big>>, PayloadBin);
serialise_variable(#mqtt_frame_fixed { type = ?PUBCOMP } = Fixed,
#mqtt_frame_publish{ message_id = MsgId},
PayloadBin) ->
serialise_fixed(Fixed, <<MsgId:16/big>>, PayloadBin);
serialise_variable(#mqtt_frame_fixed {} = Fixed,
undefined,
<<>> = _PayloadBin) ->
serialise_fixed(Fixed, <<>>, <<>>).
serialise_fixed(#mqtt_frame_fixed{ type = Type,
dup = Dup,
qos = Qos,
retain = Retain }, VariableBin, PayloadBin)
when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
Len = size(VariableBin) + size(PayloadBin),
true = (Len =< ?MAX_LEN),
LenBin = serialise_len(Len),
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
LenBin/binary, VariableBin/binary, PayloadBin/binary>>.
serialise_utf(String) ->
StringBin = unicode:characters_to_binary(String),
Len = size(StringBin),
true = (Len =< 16#ffff),
<<Len:16/big, StringBin/binary>>.
serialise_len(N) when N =< ?LOWBITS ->
<<0:1, N:7>>;
serialise_len(N) ->
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
opt(undefined) -> ?RESERVED;
opt(false) -> 0;
opt(true) -> 1;
opt(X) when is_integer(X) -> X.
protocol_name_approved(Ver, Name) ->
lists:member({Ver, Name}, ?PROTOCOL_NAMES).

View File

@ -1,5 +1,5 @@
%%------------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,12 +22,10 @@
-module(emqtt_http).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-include("emqtt.hrl").
-include("emqtt_log.hrl").
-import(proplists, [get_value/2, get_value/3]).
-export([handle/1]).
@ -45,14 +43,11 @@ handle(Req) ->
handle('POST', "/mqtt/publish", Req) ->
Params = mochiweb_request:parse_post(Req),
?INFO("~p~n", [Params]),
lager:info("~p~n", [Params]),
Topic = list_to_binary(get_value("topic", Params)),
Message = list_to_binary(get_value("message", Params)),
emqtt_pubsub:publish(#mqtt_msg {
retain = 0,
qos = ?QOS_0,
emqtt_pubsub:publish(#mqtt_message {
topic = Topic,
dup = 0,
payload = Message
}),
Req:ok({"text/plan", "ok"});

View File

@ -0,0 +1,71 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_keepalive).
-author('feng@emqtt.io').
-export([new/3, resume/1, cancel/1]).
-record(keepalive, {socket, recv_oct, timeout_sec, timeout_msg, timer_ref}).
%%
%% @doc create a keepalive.
%%
new(Socket, TimeoutSec, TimeoutMsg) when TimeoutSec > 0 ->
{ok, [{recv_oct, RecvOct}]} = inet:getstat(Socket, [recv_oct]),
Ref = erlang:send_after(TimeoutSec*1000, self(), TimeoutMsg),
#keepalive { socket = Socket,
recv_oct = RecvOct,
timeout_sec = TimeoutSec,
timeout_msg = TimeoutMsg,
timer_ref = Ref }.
%%
%% @doc try to resume keepalive, called when timeout.
%%
resume(KeepAlive = #keepalive { socket = Socket,
recv_oct = RecvOct,
timeout_sec = TimeoutSec,
timeout_msg = TimeoutMsg,
timer_ref = Ref }) ->
{ok, [{recv_oct, NewRecvOct}]} = inet:getstat(Socket, [recv_oct]),
if
NewRecvOct =:= RecvOct ->
timeout;
true ->
%need?
cancel(Ref),
NewRef = erlang:send_after(TimeoutSec*1000, self(), TimeoutMsg),
{resumed, KeepAlive#keepalive { recv_oct = NewRecvOct, timer_ref = NewRef }}
end.
%%
%% @doc cancel keepalive
%%
cancel(#keepalive { timer_ref = Ref }) ->
cancel(Ref);
cancel(undefined) ->
undefined;
cancel(Ref) ->
catch erlang:cancel_timer(Ref).

View File

@ -0,0 +1,133 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_message).
-author('feng@emqtt.io').
-include("emqtt.hrl").
-include("emqtt_packet.hrl").
-export([from_packet/1, to_packet/1]).
-export([set_flag/1, set_flag/2, unset_flag/1, unset_flag/2]).
-export([dump/1]).
%%----------------------------------------------------------------------------
-ifdef(use_specs).
-spec( from_packet( mqtt_packet() ) -> mqtt_message() | undefined ).
-spec( to_packet( mqtt_message() ) -> mqtt_packet() ).
-sepc( set_flag(atom(), mqtt_message() ) -> mqtt_message().
-sepc( unset_flag(atom(), mqtt_message() ) -> mqtt_message().
-endif.
%%----------------------------------------------------------------------------
%%
%% @doc message from packet
%%
from_packet(#mqtt_packet{ header = #mqtt_packet_header{ type = ?PUBLISH,
retain = Retain,
qos = Qos,
dup = Dup },
variable = #mqtt_packet_publish{ topic_name = Topic,
packet_id = PacketId },
payload = Payload }) ->
#mqtt_message{ msgid = PacketId,
qos = Qos,
retain = Retain,
dup = Dup,
topic = Topic,
payload = Payload };
from_packet(#mqtt_packet_connect{ will_flag = false }) ->
undefined;
from_packet(#mqtt_packet_connect{ will_retain = Retain,
will_qos = Qos,
will_topic = Topic,
will_msg = Msg }) ->
#mqtt_message{ retain = Retain,
qos = Qos,
topic = Topic,
dup = false,
payload = Msg }.
%%
%% @doc message to packet
%%
to_packet(#mqtt_message{ msgid = MsgId,
qos = Qos,
retain = Retain,
dup = Dup,
topic = Topic,
payload = Payload }) ->
PacketId = if
Qos =:= ?QOS_0 -> undefined;
true -> MsgId
end,
#mqtt_packet{ header = #mqtt_packet_header { type = ?PUBLISH,
qos = Qos,
retain = Retain,
dup = Dup },
variable = #mqtt_packet_publish { topic_name = Topic,
packet_id = PacketId },
payload = Payload }.
%%
%% @doc set dup, retain flag
%%
set_flag(Msg) ->
Msg#mqtt_message{dup = true, retain = true}.
set_flag(dup, Msg = #mqtt_message{dup = false}) ->
Msg#mqtt_message{dup = true};
set_flag(retain, Msg = #mqtt_message{retain = false}) ->
Msg#mqtt_message{retain = true};
set_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
unset_flag(Msg) ->
Msg#mqtt_message{dup = false, retain = false}.
unset_flag(dup, Msg = #mqtt_message{dup = true}) ->
Msg#mqtt_message{dup = false};
unset_flag(retain, Msg = #mqtt_message{retain = true}) ->
Msg#mqtt_message{retain = false};
unset_flag(Flag, Msg) when Flag =:= dup orelse Flag =:= retain -> Msg.
%%
%% @doc dump message
%%
dump(#mqtt_message{msgid= MsgId, qos = Qos, retain = Retain, dup = Dup, topic = Topic}) ->
io_lib:format("Message(MsgId=~p, Qos=~p, Retain=~s, Dup=~s, Topic=~s)",
[ MsgId, Qos, Retain, Dup, Topic ]).

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,9 +22,7 @@
-module(emqtt_monitor).
-author('feng@slimchat.io').
-include("emqtt_log.hrl").
-author('feng@emqtt.io').
-behavior(gen_server).
@ -50,7 +48,6 @@ start_link() ->
%%--------------------------------------------------------------------
init([]) ->
erlang:system_monitor(self(), [{long_gc, 5000}, {large_heap, 1000000}, busy_port]),
?INFO("monitor is started...[ok]", []),
{ok, #state{}}.
%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
@ -62,7 +59,7 @@ init([]) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call(Request, _From, State) ->
?ERROR("unexpected request: ~p", [Request]),
lager:error("unexpected request: ~p", [Request]),
{reply, {error, unexpected_request}, State}.
%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
@ -71,7 +68,7 @@ handle_call(Request, _From, State) ->
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(Msg, State) ->
?ERROR("unexpected msg: ~p", [Msg]),
lager:error("unexpected msg: ~p", [Msg]),
{noreply, State}.
%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
@ -80,22 +77,22 @@ handle_cast(Msg, State) ->
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info({monitor, GcPid, long_gc, Info}, State) ->
?ERROR("long_gc: gcpid = ~p, ~p ~n ~p", [GcPid, process_info(GcPid,
lager:error("long_gc: gcpid = ~p, ~p ~n ~p", [GcPid, process_info(GcPid,
[registered_name, memory, message_queue_len,heap_size,total_heap_size]), Info]),
{noreply, State};
handle_info({monitor, GcPid, large_heap, Info}, State) ->
?ERROR("large_heap: gcpid = ~p,~p ~n ~p", [GcPid, process_info(GcPid,
lager:error("large_heap: gcpid = ~p,~p ~n ~p", [GcPid, process_info(GcPid,
[registered_name, memory, message_queue_len,heap_size,total_heap_size]), Info]),
{noreply, State};
handle_info({monitor, SusPid, busy_port, Port}, State) ->
?ERROR("busy_port: suspid = ~p, port = ~p", [process_info(SusPid,
lager:error("busy_port: suspid = ~p, port = ~p", [process_info(SusPid,
[registered_name, memory, message_queue_len,heap_size,total_heap_size]), Port]),
{noreply, State};
handle_info(Info, State) ->
?ERROR("unexpected info: ~p", [Info]),
lager:error("unexpected info: ~p", [Info]),
{noreply, State}.
%%--------------------------------------------------------------------

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,11 +22,11 @@
-module(emqtt_net).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-export([tcp_name/3, tcp_host/1, getopts/2, setopts/2, getaddr/2, port_to_listeners/1]).
-export([connection_string/2]).
-export([peername/1, sockname/1, peer_string/1, connection_string/2]).
-include_lib("kernel/include/inet.hrl").
@ -196,6 +196,14 @@ setopts(Sock, Options) when is_port(Sock) ->
sockname(Sock) when is_port(Sock) -> inet:sockname(Sock).
peer_string(Sock) ->
case peername(Sock) of
{ok, {Addr, Port}} ->
{ok, lists:flatten(io_lib:format("~s:~p", [maybe_ntoab(Addr), Port]))};
Error ->
Error
end.
peername(Sock) when is_port(Sock) -> inet:peername(Sock).
ntoa({0,0,0,0,0,16#ffff,AB,CD}) ->

View File

@ -0,0 +1,353 @@
%%------------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
%%
%% The Original Code is from RabbitMQ.
%%
-module(emqtt_packet).
-include("emqtt_packet.hrl").
-export([initial_state/0]).
-export([parse/2, serialise/1]).
-export([dump/1]).
-define(MAX_LEN, 16#fffffff).
-define(HIGHBIT, 2#10000000).
-define(LOWBITS, 2#01111111).
initial_state() -> none.
parse(<<>>, none) ->
{more, fun(Bin) -> parse(Bin, none) end};
parse(<<PacketType:4, Dup:1, QoS:2, Retain:1, Rest/binary>>, none) ->
parse_remaining_len(Rest, #mqtt_packet_header{type = PacketType,
dup = bool(Dup),
qos = QoS,
retain = bool(Retain) });
parse(Bin, Cont) -> Cont(Bin).
parse_remaining_len(<<>>, Header) ->
{more, fun(Bin) -> parse_remaining_len(Bin, Header) end};
parse_remaining_len(Rest, Header) ->
parse_remaining_len(Rest, Header, 1, 0).
parse_remaining_len(_Bin, _Header, _Multiplier, Length)
when Length > ?MAX_LEN ->
{error, invalid_mqtt_frame_len};
parse_remaining_len(<<>>, Header, Multiplier, Length) ->
{more, fun(Bin) -> parse_remaining_len(Bin, Header, Multiplier, Length) end};
parse_remaining_len(<<1:1, Len:7, Rest/binary>>, Header, Multiplier, Value) ->
parse_remaining_len(Rest, Header, Multiplier * ?HIGHBIT, Value + Len * Multiplier);
parse_remaining_len(<<0:1, Len:7, Rest/binary>>, Header, Multiplier, Value) ->
parse_frame(Rest, Header, Value + Len * Multiplier).
parse_frame(Bin, #mqtt_packet_header{ type = Type,
qos = Qos } = Header, Length) ->
case {Type, Bin} of
{?CONNECT, <<FrameBin:Length/binary, Rest/binary>>} ->
{ProtoName, Rest1} = parse_utf(FrameBin),
<<ProtoVersion : 8, Rest2/binary>> = Rest1,
<<UsernameFlag : 1,
PasswordFlag : 1,
WillRetain : 1,
WillQos : 2,
WillFlag : 1,
CleanSession : 1,
_Reserved : 1,
KeepAlive : 16/big,
Rest3/binary>> = Rest2,
{ClientId, Rest4} = parse_utf(Rest3),
{WillTopic, Rest5} = parse_utf(Rest4, WillFlag),
{WillMsg, Rest6} = parse_msg(Rest5, WillFlag),
{UserName, Rest7} = parse_utf(Rest6, UsernameFlag),
{PasssWord, <<>>} = parse_utf(Rest7, PasswordFlag),
case protocol_name_approved(ProtoVersion, ProtoName) of
true ->
wrap(Header,
#mqtt_packet_connect{
proto_ver = ProtoVersion,
proto_name = ProtoName,
will_retain = bool(WillRetain),
will_qos = WillQos,
will_flag = bool(WillFlag),
clean_sess = bool(CleanSession),
keep_alive = KeepAlive,
client_id = ClientId,
will_topic = WillTopic,
will_msg = WillMsg,
username = UserName,
password = PasssWord}, Rest);
false ->
{error, protocol_header_corrupt}
end;
{?PUBLISH, <<FrameBin:Length/binary, Rest/binary>>} ->
{TopicName, Rest1} = parse_utf(FrameBin),
{PacketId, Payload} = case Qos of
0 -> {undefined, Rest1};
_ -> <<Id:16/big, R/binary>> = Rest1,
{Id, R}
end,
wrap(Header, #mqtt_packet_publish {topic_name = TopicName,
packet_id = PacketId },
Payload, Rest);
{?PUBACK, <<FrameBin:Length/binary, Rest/binary>>} ->
<<PacketId:16/big>> = FrameBin,
wrap(Header, #mqtt_packet_puback{packet_id = PacketId}, Rest);
{?PUBREC, <<FrameBin:Length/binary, Rest/binary>>} ->
<<PacketId:16/big>> = FrameBin,
wrap(Header, #mqtt_packet_puback{packet_id = PacketId}, Rest);
{?PUBREL, <<FrameBin:Length/binary, Rest/binary>>} ->
1 = Qos,
<<PacketId:16/big>> = FrameBin,
wrap(Header, #mqtt_packet_puback{ packet_id = PacketId }, Rest);
{?PUBCOMP, <<FrameBin:Length/binary, Rest/binary>>} ->
<<PacketId:16/big>> = FrameBin,
wrap(Header, #mqtt_packet_puback{ packet_id = PacketId }, Rest);
{Subs, <<FrameBin:Length/binary, Rest/binary>>}
when Subs =:= ?SUBSCRIBE orelse Subs =:= ?UNSUBSCRIBE ->
1 = Qos,
<<PacketId:16/big, Rest1/binary>> = FrameBin,
Topics = parse_topics(Subs, Rest1, []),
wrap(Header, #mqtt_packet_subscribe { packet_id = PacketId,
topic_table = Topics }, Rest);
{Minimal, Rest}
when Minimal =:= ?DISCONNECT orelse Minimal =:= ?PINGREQ ->
Length = 0,
wrap(Header, Rest);
{_, TooShortBin} ->
{more, fun(BinMore) ->
parse_frame(<<TooShortBin/binary, BinMore/binary>>,
Header, Length)
end}
end.
parse_topics(_, <<>>, Topics) ->
Topics;
parse_topics(?SUBSCRIBE = Sub, Bin, Topics) ->
{Name, <<_:6, QoS:2, Rest/binary>>} = parse_utf(Bin),
parse_topics(Sub, Rest, [#mqtt_topic { name = Name, qos = QoS } | Topics]);
parse_topics(?UNSUBSCRIBE = Sub, Bin, Topics) ->
{Name, <<Rest/binary>>} = parse_utf(Bin),
parse_topics(Sub, Rest, [#mqtt_topic { name = Name } | Topics]).
wrap(Header, Variable, Payload, Rest) ->
{ok, #mqtt_packet{ header = Header, variable = Variable, payload = Payload }, Rest}.
wrap(Header, Variable, Rest) ->
{ok, #mqtt_packet { header = Header, variable = Variable }, Rest}.
wrap(Header, Rest) ->
{ok, #mqtt_packet { header = Header }, Rest}.
parse_utf(Bin, 0) ->
{undefined, Bin};
parse_utf(Bin, _) ->
parse_utf(Bin).
parse_utf(<<Len:16/big, Str:Len/binary, Rest/binary>>) ->
{Str, Rest}.
parse_msg(Bin, 0) ->
{undefined, Bin};
parse_msg(<<Len:16/big, Msg:Len/binary, Rest/binary>>, _) ->
{Msg, Rest}.
bool(0) -> false;
bool(1) -> true.
%% serialisation
serialise(#mqtt_packet{ header = Header,
variable = Variable,
payload = Payload }) ->
serialise_header(Header,
serialise_variable(Header, Variable,
serialise_payload(Payload))).
serialise_payload(undefined) -> <<>>;
serialise_payload(B) when is_binary(B) -> B.
serialise_variable(#mqtt_packet_header { type = ?CONNACK },
#mqtt_packet_connack { ack_flags = AckFlags,
return_code = ReturnCode },
<<>> = PayloadBin) ->
VariableBin = <<AckFlags:8, ReturnCode:8>>,
{VariableBin, PayloadBin};
serialise_variable(#mqtt_packet_header { type = SubAck },
#mqtt_packet_suback { packet_id = PacketId,
qos_table = Qos },
<<>> = _PayloadBin)
when SubAck =:= ?SUBACK orelse SubAck =:= ?UNSUBACK ->
VariableBin = <<PacketId:16/big>>,
QosBin = << <<Q:8>> || Q <- Qos >>,
{VariableBin, QosBin};
serialise_variable(#mqtt_packet_header { type = ?PUBLISH,
qos = Qos },
#mqtt_packet_publish { topic_name = TopicName,
packet_id = PacketId },
PayloadBin) ->
TopicBin = serialise_utf(TopicName),
PacketIdBin = case Qos of
0 -> <<>>;
1 -> <<PacketId:16/big>>;
2 -> <<PacketId:16/big>>
end,
{<<TopicBin/binary, PacketIdBin/binary>>, PayloadBin};
serialise_variable(#mqtt_packet_header { type = PubAck },
#mqtt_packet_puback { packet_id = PacketId },
<<>> = _Payload)
when PubAck =:= ?PUBACK; PubAck =:= ?PUBREC;
PubAck =:= ?PUBREL; PubAck =:= ?PUBCOMP ->
{<<PacketId:16/big>>, <<>>};
serialise_variable(#mqtt_packet_header { },
undefined,
<<>> = _PayloadBin) ->
{<<>>, <<>>}.
serialise_header(#mqtt_packet_header{ type = Type,
dup = Dup,
qos = Qos,
retain = Retain },
{VariableBin, PayloadBin})
when is_integer(Type) andalso ?CONNECT =< Type andalso Type =< ?DISCONNECT ->
Len = size(VariableBin) + size(PayloadBin),
true = (Len =< ?MAX_LEN),
LenBin = serialise_len(Len),
<<Type:4, (opt(Dup)):1, (opt(Qos)):2, (opt(Retain)):1,
LenBin/binary, VariableBin/binary, PayloadBin/binary>>.
serialise_utf(String) ->
StringBin = unicode:characters_to_binary(String),
Len = size(StringBin),
true = (Len =< 16#ffff),
<<Len:16/big, StringBin/binary>>.
serialise_len(N) when N =< ?LOWBITS ->
<<0:1, N:7>>;
serialise_len(N) ->
<<1:1, (N rem ?HIGHBIT):7, (serialise_len(N div ?HIGHBIT))/binary>>.
opt(undefined) -> ?RESERVED;
opt(false) -> 0;
opt(true) -> 1;
opt(X) when is_integer(X) -> X.
protocol_name_approved(Ver, Name) ->
lists:member({Ver, Name}, ?PROTOCOL_NAMES).
dump(#mqtt_packet{header = Header, variable = Variable, payload = Payload}) when
Payload =:= undefined orelse Payload =:= <<>> ->
dump_header(Header, dump_variable(Variable));
dump(#mqtt_packet{header = Header, variable = Variable, payload = Payload}) ->
dump_header(Header, dump_variable(Variable, Payload)).
dump_header(#mqtt_packet_header{type = Type, dup = Dup, qos = QoS, retain = Retain}, S) ->
S1 =
if
S == undefined -> <<>>;
true -> [", ", S]
end,
io_lib:format("~s(Qos=~p, Retain=~s, Dup=~s~s)", [dump_type(Type), QoS, Retain, Dup, S1]).
dump_variable( #mqtt_packet_connect {
proto_ver = ProtoVer,
proto_name = ProtoName,
will_retain = WillRetain,
will_qos = WillQoS,
will_flag = WillFlag,
clean_sess = CleanSess,
keep_alive = KeepAlive,
client_id = ClientId,
will_topic = WillTopic,
will_msg = WillMsg,
username = Username,
password = Password} ) ->
Format = "ClientId=~s, ProtoName=~s, ProtoVsn=~p, CleanSess=~s, KeepAlive=~p, Username=~s, Password=~s",
Args = [ClientId, ProtoName, ProtoVer, CleanSess, KeepAlive, Username, dump_password(Password)],
{Format1, Args1} = if
WillFlag -> { Format ++ ", Will(Qos=~p, Retain=~s, Topic=~s, Msg=~s)",
Args ++ [ WillQoS, WillRetain, WillTopic, WillMsg ] };
true -> {Format, Args}
end,
io_lib:format(Format1, Args1);
dump_variable( #mqtt_packet_connack {
ack_flags = AckFlags,
return_code = ReturnCode } ) ->
io_lib:format("AckFlags=~p, RetainCode=~p", [AckFlags, ReturnCode]);
dump_variable( #mqtt_packet_publish {
topic_name = TopicName,
packet_id = PacketId} ) ->
io_lib:format("TopicName=~s, PacketId=~p", [TopicName, PacketId]);
dump_variable( #mqtt_packet_puback {
packet_id = PacketId } ) ->
io_lib:format("PacketId=~p", [PacketId]);
dump_variable( #mqtt_packet_subscribe {
packet_id = PacketId,
topic_table = TopicTable }) ->
L = [{Name, QoS} || #mqtt_topic{name = Name, qos = QoS} <- TopicTable],
io_lib:format("PacketId=~p, TopicTable=~p", [PacketId, L]);
dump_variable( #mqtt_packet_suback {
packet_id = PacketId,
qos_table = QosTable} ) ->
io_lib:format("PacketId=~p, QosTable=~p", [PacketId, QosTable]);
dump_variable(PacketId) when is_integer(PacketId) ->
io_lib:format("PacketId=~p", [PacketId]);
dump_variable(undefined) -> undefined.
dump_variable(undefined, undefined) ->
undefined;
dump_variable(undefined, <<>>) ->
undefined;
dump_variable(Variable, Payload) ->
io_lib:format("~s, Payload=~p", [dump_variable(Variable), Payload]).
dump_password(undefined) -> undefined;
dump_password(_) -> <<"******">>.
dump_type(?CONNECT) -> "CONNECT";
dump_type(?CONNACK) -> "CONNACK";
dump_type(?PUBLISH) -> "PUBLISH";
dump_type(?PUBACK) -> "PUBACK";
dump_type(?PUBREC) -> "PUBREC";
dump_type(?PUBREL) -> "PUBREL";
dump_type(?PUBCOMP) -> "PUBCOMP";
dump_type(?SUBSCRIBE) -> "SUBSCRIBE";
dump_type(?SUBACK) -> "SUBACK";
dump_type(?UNSUBSCRIBE) -> "UNSUBSCRIBE";
dump_type(?UNSUBACK) -> "UNSUBACK";
dump_type(?PINGREQ) -> "PINGREQ";
dump_type(?PINGRESP) -> "PINGRESP";
dump_type(?DISCONNECT) -> "DISCONNECT".

View File

@ -1,5 +1,356 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_protocol).
-include("emqtt_frame.hrl").
-include("emqtt.hrl").
-include("emqtt_packet.hrl").
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([initial_state/2, client_id/1]).
-export([handle_packet/2, send_message/2, send_packet/2, redeliver/2, shutdown/2]).
-export([info/1]).
%% ------------------------------------------------------------------
%% Protocol State
%% ------------------------------------------------------------------
-record(proto_state, {
socket,
peer_name,
connected = false, %received CONNECT action?
proto_vsn,
proto_name,
%packet_id,
client_id,
clean_sess,
session, %% session state or session pid
will_msg
}).
%%----------------------------------------------------------------------------
-ifdef(use_specs).
-type(proto_state() :: #proto_state{}).
-spec(send_message({pid() | tuple(), mqtt_message()}, proto_state()) -> {ok, proto_state()}).
-spec(handle_packet(mqtt_packet(), proto_state()) -> {ok, proto_state()} | {error, any()}).
-endif.
%%----------------------------------------------------------------------------
-define(PACKET_TYPE(Packet, Type),
Packet = #mqtt_packet { header = #mqtt_packet_header { type = Type }}).
-define(PUBACK_PACKET(PacketId), #mqtt_packet_puback { packet_id = PacketId }).
initial_state(Socket, Peername) ->
#proto_state{
socket = Socket,
peer_name = Peername
}.
client_id(#proto_state { client_id = ClientId }) -> ClientId.
%%SHOULD be registered in emqtt_cm
info(#proto_state{ proto_vsn = ProtoVsn,
proto_name = ProtoName,
client_id = ClientId,
clean_sess = CleanSess,
will_msg = WillMsg }) ->
[ {proto_vsn, ProtoVsn},
{proto_name, ProtoName},
{client_id, ClientId},
{clean_sess, CleanSess},
{will_msg, WillMsg} ].
%%CONNECT Client requests a connection to a Server
%%A Client can only send the CONNECT Packet once over a Network Connection.
handle_packet(?PACKET_TYPE(Packet, ?CONNECT), State = #proto_state{connected = false}) ->
handle_packet(?CONNECT, Packet, State#proto_state{connected = true});
handle_packet(?PACKET_TYPE(_Packet, ?CONNECT), State = #proto_state{connected = true}) ->
{error, protocol_bad_connect, State};
%%Received other packets when CONNECT not arrived.
handle_packet(_Packet, State = #proto_state{connected = false}) ->
{error, protocol_not_connected, State};
handle_packet(?PACKET_TYPE(Packet, Type),
State = #proto_state { peer_name = PeerName, client_id = ClientId }) ->
lager:info("RECV from ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]),
case validate_packet(Packet) of
ok ->
handle_packet(Type, Packet, State);
{error, Reason} ->
{error, Reason, State}
end.
handle_packet(?CONNECT, Packet = #mqtt_packet {
variable = #mqtt_packet_connect {
username = Username,
password = Password,
clean_sess = CleanSess,
keep_alive = KeepAlive,
client_id = ClientId } = Var },
State = #proto_state{ peer_name = PeerName } ) ->
lager:info("RECV from ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]),
{ReturnCode1, State1} =
case validate_connect(Var) of
?CONNACK_ACCEPT ->
case emqtt_auth:check(Username, Password) of
true ->
ClientId1 = clientid(ClientId, State),
start_keepalive(KeepAlive),
emqtt_cm:register(ClientId1, self()),
{?CONNACK_ACCEPT, State#proto_state{ will_msg = willmsg(Var),
clean_sess = CleanSess,
client_id = ClientId1 }};
false ->
lager:error("~s@~s: username '~s' login failed - no credentials", [ClientId, PeerName, Username]),
{?CONNACK_CREDENTIALS, State#proto_state{client_id = ClientId}}
end;
ReturnCode ->
{ReturnCode, State#proto_state{client_id = ClientId}}
end,
send_packet( #mqtt_packet {
header = #mqtt_packet_header { type = ?CONNACK },
variable = #mqtt_packet_connack{ return_code = ReturnCode1 }}, State1 ),
%%Starting session
{ok, Session} = emqtt_session:start({CleanSess, ClientId, self()}),
{ok, State1#proto_state { session = Session }};
handle_packet(?PUBLISH, Packet = #mqtt_packet {
header = #mqtt_packet_header {qos = ?QOS_0}},
State = #proto_state{session = Session}) ->
emqtt_session:publish(Session, {?QOS_0, emqtt_message:from_packet(Packet)}),
{ok, State};
handle_packet(?PUBLISH, Packet = #mqtt_packet {
header = #mqtt_packet_header { qos = ?QOS_1 },
variable = #mqtt_packet_publish{packet_id = PacketId }},
State = #proto_state { session = Session }) ->
emqtt_session:publish(Session, {?QOS_1, emqtt_message:from_packet(Packet)}),
send_packet( make_packet(?PUBACK, PacketId), State);
handle_packet(?PUBLISH, Packet = #mqtt_packet {
header = #mqtt_packet_header { qos = ?QOS_2 },
variable = #mqtt_packet_publish { packet_id = PacketId } },
State = #proto_state { session = Session }) ->
NewSession = emqtt_session:publish(Session, {?QOS_2, emqtt_message:from_packet(Packet)}),
send_packet( make_packet(?PUBREC, PacketId), State#proto_state {session = NewSession} );
handle_packet(Puback, #mqtt_packet{variable = ?PUBACK_PACKET(PacketId) },
State = #proto_state { session = Session })
when Puback >= ?PUBACK andalso Puback =< ?PUBCOMP ->
NewSession = emqtt_session:puback(Session, {Puback, PacketId}),
NewState = State#proto_state {session = NewSession},
if
Puback =:= ?PUBREC ->
send_packet( make_packet(?PUBREL, PacketId), NewState);
Puback =:= ?PUBREL ->
send_packet( make_packet(?PUBCOMP, PacketId), NewState);
true ->
ok
end,
{ok, NewState};
handle_packet(?SUBSCRIBE, #mqtt_packet {
variable = #mqtt_packet_subscribe{
packet_id = PacketId,
topic_table = TopicTable},
payload = undefined},
State = #proto_state { session = Session } ) ->
Topics = [{Name, Qos} || #mqtt_topic{name=Name, qos=Qos} <- TopicTable],
{ok, NewSession, GrantedQos} = emqtt_session:subscribe(Session, Topics),
send_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?SUBACK },
variable = #mqtt_packet_suback{ packet_id = PacketId,
qos_table = GrantedQos }},
State#proto_state{ session = NewSession });
handle_packet(?UNSUBSCRIBE, #mqtt_packet {
variable = #mqtt_packet_subscribe{
packet_id = PacketId,
topic_table = Topics },
payload = undefined},
State = #proto_state{session = Session}) ->
{ok, NewSession} = emqtt_session:unsubscribe(Session, [Name || #mqtt_topic{ name = Name } <- Topics]),
send_packet(#mqtt_packet { header = #mqtt_packet_header {type = ?UNSUBACK },
variable = #mqtt_packet_suback{packet_id = PacketId }},
State#proto_state { session = NewSession } );
handle_packet(?PINGREQ, #mqtt_packet{}, State) ->
send_packet(make_packet(?PINGRESP), State);
handle_packet(?DISCONNECT, #mqtt_packet{}, State) ->
%%TODO: how to handle session?
% clean willmsg
{stop, normal, State#proto_state{will_msg = undefined}}.
make_packet(Type) when Type >= ?CONNECT andalso Type =< ?DISCONNECT ->
#mqtt_packet{ header = #mqtt_packet_header { type = Type } }.
make_packet(PubAck, PacketId) when PubAck >= ?PUBACK andalso PubAck =< ?PUBCOMP ->
#mqtt_packet { header = #mqtt_packet_header { type = PubAck, qos = puback_qos(PubAck) },
variable = #mqtt_packet_puback { packet_id = PacketId}}.
puback_qos(?PUBACK) -> ?QOS_0;
puback_qos(?PUBREC) -> ?QOS_0;
puback_qos(?PUBREL) -> ?QOS_1;
puback_qos(?PUBCOMP) -> ?QOS_0.
%% qos0 message
send_message({_From, Message = #mqtt_message{ qos = ?QOS_0 }}, State) ->
send_packet(emqtt_message:to_packet(Message), State);
%% message from session
send_message({_From = SessPid, Message}, State = #proto_state{session = SessPid}) when is_pid(SessPid) ->
send_packet(emqtt_message:to_packet(Message), State);
%% message(qos1, qos2) not from session
send_message({_From, Message = #mqtt_message{ qos = Qos }}, State = #proto_state{ session = Session })
when (Qos =:= ?QOS_1) orelse (Qos =:= ?QOS_2) ->
{Message1, NewSession} = emqtt_session:store(Session, Message),
send_packet(emqtt_message:to_packet(Message1), State#proto_state{session = NewSession}).
send_packet(Packet, State = #proto_state{socket = Sock, peer_name = PeerName, client_id = ClientId}) ->
lager:info("SENT to ~s@~s: ~s", [ClientId, PeerName, emqtt_packet:dump(Packet)]),
Data = emqtt_packet:serialise(Packet),
lager:debug("SENT to ~s: ~p", [PeerName, Data]),
%%FIXME Later...
erlang:port_command(Sock, Data),
{ok, State}.
%%
%% @doc redeliver PUBREL PacketId
%%
redeliver({?PUBREL, PacketId}, State) ->
send_packet( make_packet(?PUBREL, PacketId), State).
shutdown(Error, #proto_state{peer_name = PeerName, client_id = ClientId, will_msg = WillMsg}) ->
send_willmsg(WillMsg),
try_unregister(ClientId, self()),
lager:info("Protocol ~s@~s Shutdown: ~p", [ClientId, PeerName, Error]),
ok.
willmsg(Packet) when is_record(Packet, mqtt_packet_connect) ->
emqtt_message:from_packet(Packet).
clientid(<<>>, #proto_state{peer_name = PeerName}) ->
<<"eMQTT/", (base64:encode(PeerName))/binary>>;
clientid(ClientId, _State) -> ClientId.
%%----------------------------------------------------------------------------
send_willmsg(undefined) -> ignore;
%%TODO:should call session...
send_willmsg(WillMsg) -> emqtt_router:route(WillMsg).
start_keepalive(0) -> ignore;
start_keepalive(Sec) when Sec > 0 ->
self() ! {keepalive, start, round(Sec * 1.5)}.
%%----------------------------------------------------------------------------
%% Validate Packets
%%----------------------------------------------------------------------------
validate_connect( Connect = #mqtt_packet_connect{} ) ->
case validate_protocol(Connect) of
true ->
case validate_clientid(Connect) of
true ->
?CONNACK_ACCEPT;
false ->
?CONNACK_INVALID_ID
end;
false ->
?CONNACK_PROTO_VER
end.
validate_protocol(#mqtt_packet_connect { proto_ver = Ver, proto_name = Name }) ->
lists:member({Ver, Name}, ?PROTOCOL_NAMES).
validate_clientid(#mqtt_packet_connect { client_id = ClientId })
when ( size(ClientId) >= 1 ) andalso ( size(ClientId) =< ?MAX_CLIENTID_LEN ) ->
true;
%% MQTT3.1.1 allow null clientId.
validate_clientid(#mqtt_packet_connect { proto_ver =?MQTT_PROTO_V311, client_id = ClientId })
when size(ClientId) =:= 0 ->
true;
validate_clientid(#mqtt_packet_connect { proto_ver = Ver, clean_sess = CleanSess, client_id = ClientId }) ->
lager:warning("Invalid ClientId: ~s, ProtoVer: ~p, CleanSess: ~s", [ClientId, Ver, CleanSess]),
false.
validate_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?PUBLISH },
variable = #mqtt_packet_publish{ topic_name = Topic }}) ->
case emqtt_topic:validate({publish, Topic}) of
true -> ok;
false -> lager:warning("Error publish topic: ~p", [Topic]), {error, badtopic}
end;
validate_packet(#mqtt_packet { header = #mqtt_packet_header { type = ?SUBSCRIBE },
variable = #mqtt_packet_subscribe{topic_table = Topics }}) ->
validate_topics(subscribe, Topics);
validate_packet(#mqtt_packet{ header = #mqtt_packet_header { type = ?UNSUBSCRIBE },
variable = #mqtt_packet_subscribe{ topic_table = Topics }}) ->
validate_topics(unsubscribe, Topics);
validate_packet(_Packet) ->
ok.
validate_topics(Type, []) when Type =:= subscribe orelse Type =:= unsubscribe ->
lager:error("Empty Topics!"),
{error, empty_topics};
validate_topics(Type, Topics) when Type =:= subscribe orelse Type =:= unsubscribe ->
ErrTopics = [Topic || #mqtt_topic{name=Topic, qos=Qos} <- Topics,
not (emqtt_topic:validate({Type, Topic}) and validate_qos(Qos))],
case ErrTopics of
[] -> ok;
_ -> lager:error("Error Topics: ~p", [ErrTopics]), {error, badtopic}
end.
validate_qos(undefined) -> true;
validate_qos(Qos) when Qos =< ?QOS_2 -> true;
validate_qos(_) -> false.
try_unregister(undefined, _) -> ok;
try_unregister(ClientId, _) -> emqtt_cm:unregister(ClientId, self()).

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,12 +22,10 @@
-module(emqtt_pubsub).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-include("emqtt.hrl").
-include("emqtt_log.hrl").
-include("emqtt_topic.hrl").
-include_lib("stdlib/include/qlc.hrl").
@ -62,6 +60,20 @@
terminate/2,
code_change/3]).
%%----------------------------------------------------------------------------
-ifdef(use_specs).
-spec topics() -> list(topic()).
-spec subscribe({binary(), mqtt_qos()} | list(), pid()) -> {ok, list(mqtt_qos())}.
-spec unsubscribe(binary() | list(binary()), pid()) -> ok.
-endif.
%%----------------------------------------------------------------------------
-record(state, {}).
%% ------------------------------------------------------------------
@ -77,32 +89,35 @@ start_link() ->
%%
%% @doc All topics
%%
-spec topics() -> list(topic()).
topics() ->
mnesia:dirty_all_keys(topic).
%%
%% @doc Subscribe Topic
%% @doc Subscribe Topic or Topics
%%
-spec subscribe({Topic :: binary(), Qos :: qos()}, SubPid :: pid()) -> any().
subscribe({Topic, Qos}, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
gen_server:call(?SERVER, {subscribe, {Topic, Qos}, SubPid}).
subscribe([{Topic, Qos}], SubPid);
subscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
gen_server:call(?SERVER, {subscribe, Topics, SubPid}).
%%
%% @doc Unsubscribe Topic
%% @doc Unsubscribe Topic or Topics
%%
-spec unsubscribe(Topic :: binary(), SubPid :: pid()) -> any().
unsubscribe(Topic, SubPid) when is_binary(Topic) and is_pid(SubPid) ->
gen_server:cast(?SERVER, {unsubscribe, Topic, SubPid}).
unsubscribe([Topic], SubPid);
unsubscribe(Topics, SubPid) when is_list(Topics) and is_pid(SubPid) ->
gen_server:cast(?SERVER, {unsubscribe, Topics, SubPid}).
%%
%% @doc Publish to cluster node.
%%
-spec publish(Msg :: mqtt_msg()) -> ok.
publish(Msg=#mqtt_msg{topic=Topic}) ->
-spec publish(Msg :: mqtt_message()) -> ok.
publish(Msg=#mqtt_message{topic=Topic}) ->
publish(Topic, Msg).
-spec publish(Topic :: binary(), Msg :: mqtt_msg()) -> any().
-spec publish(Topic :: binary(), Msg :: mqtt_message()) -> any().
publish(Topic, Msg) when is_binary(Topic) ->
lists:foreach(fun(#topic{name=Name, node=Node}) ->
case Node =:= node() of
@ -112,8 +127,14 @@ publish(Topic, Msg) when is_binary(Topic) ->
end, match(Topic)).
%dispatch locally, should only be called by publish
dispatch(Topic, Msg) when is_binary(Topic) ->
[SubPid ! {dispatch, Msg} || #topic_subscriber{subpid=SubPid} <- ets:lookup(topic_subscriber, Topic)].
dispatch(Topic, Msg = #mqtt_message{qos = Qos}) when is_binary(Topic) ->
lists:foreach(fun(#topic_subscriber{qos = SubQos, subpid=SubPid}) ->
Msg1 = if
Qos > SubQos -> Msg#mqtt_message{qos = SubQos};
true -> Msg
end,
SubPid ! {dispatch, {self(), Msg1}}
end, ets:lookup(topic_subscriber, Topic)).
-spec match(Topic :: binary()) -> [topic()].
match(Topic) when is_binary(Topic) ->
@ -143,29 +164,23 @@ init([]) ->
ets:new(topic_subscriber, [bag, named_table, {keypos, 2}]),
{ok, #state{}}.
handle_call({subscribe, {Topic, Qos}, SubPid}, _From, State) ->
case mnesia:transaction(fun trie_add/1, [Topic]) of
{atomic, _} ->
case get({subscriber, SubPid}) of
undefined ->
MonRef = erlang:monitor(process, SubPid),
put({subcriber, SubPid}, MonRef),
put({submon, MonRef}, SubPid);
_ ->
already_monitored
end,
ets:insert(topic_subscriber, #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid}),
{reply, ok, State};
{aborted, Reason} ->
{reply, {error, Reason}, State}
end;
handle_call({subscribe, Topics, SubPid}, _From, State) ->
Result = [subscribe_topic({Topic, Qos}, SubPid) || {Topic, Qos} <- Topics],
Reply =
case [Err || Err = {error, _} <- Result] of
[] -> {ok, [Qos || {ok, Qos} <- Result]};
Errors -> hd(Errors)
end,
{reply, Reply, State};
handle_call(Req, _From, State) ->
{stop, {badreq, Req}, State}.
handle_cast({unsubscribe, Topic, SubPid}, State) ->
ets:match_delete(topic_subscriber, #topic_subscriber{topic=Topic, qos ='_', subpid=SubPid}),
try_remove_topic(Topic),
handle_cast({unsubscribe, Topics, SubPid}, State) ->
lists:foreach(fun(Topic) ->
ets:match_delete(topic_subscriber, #topic_subscriber{topic=Topic, qos ='_', subpid=SubPid}),
try_remove_topic(Topic)
end, Topics),
{noreply, State};
handle_cast(Msg, State) ->
@ -174,7 +189,7 @@ handle_cast(Msg, State) ->
handle_info({'DOWN', Mon, _Type, _Object, _Info}, State) ->
case get({submon, Mon}) of
undefined ->
?ERROR("unexpected 'DOWN': ~p", [Mon]);
lager:error("unexpected 'DOWN': ~p", [Mon]);
SubPid ->
erase({submon, Mon}),
erase({subscriber, SubPid}),
@ -196,6 +211,42 @@ code_change(_OldVsn, State, _Extra) ->
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
subscribe_topic({Topic, Qos}, SubPid) ->
case mnesia:transaction(fun trie_add/1, [Topic]) of
{atomic, _} ->
case get({subscriber, SubPid}) of
undefined ->
%%TODO: refactor later...
MonRef = erlang:monitor(process, SubPid),
put({subcriber, SubPid}, MonRef),
put({submon, MonRef}, SubPid);
_ ->
already_monitored
end,
%% remove duplicated subscribers
try_remove_subscriber({Topic, Qos}, SubPid),
ets:insert(topic_subscriber, #topic_subscriber{topic=Topic, qos = Qos, subpid=SubPid}),
%TODO: GrantedQos??
{ok, Qos};
{aborted, Reason} ->
{error, Reason}
end.
try_remove_subscriber({Topic, Qos}, SubPid) ->
case ets:lookup(topic_subscriber, Topic) of
[] ->
not_found;
Subs ->
DupSubs = [Sub || Sub = #topic_subscriber{qos = OldQos, subpid = OldPid}
<- Subs, Qos =/= OldQos, OldPid =:= SubPid],
case DupSubs of
[] -> ok;
[DupSub] ->
lager:warning("PubSub: remove duplicated subscriber ~p", [DupSub]),
ets:delete(topic_subscriber, DupSub)
end
end.
try_remove_topic(Name) when is_binary(Name) ->
case ets:member(topic_subscriber, Name) of
false ->
@ -218,7 +269,7 @@ trie_add(Topic) when is_binary(Topic) ->
[TrieNode=#topic_trie_node{topic=undefined}] ->
mnesia:write(TrieNode#topic_trie_node{topic=Topic});
[#topic_trie_node{topic=Topic}] ->
ignore;
{atomic, already_exist};
[] ->
%add trie path
[trie_add_path(Triple) || Triple <- emqtt_topic:triples(Topic)],

View File

@ -0,0 +1,74 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_queue).
-include("emqtt.hrl").
-export([new/1, new/2, in/3, all/1, clear/1]).
%%----------------------------------------------------------------------------
-ifdef(use_specs).
-type(mqtt_queue() :: #mqtt_queue_wrapper{}).
-spec(new(non_neg_intger()) -> mqtt_queue()).
-spec(in(binary(), mqtt_message(), mqtt_queue()) -> mqtt_queue()).
-spec(all(mqtt_queue()) -> list()).
-spec(clear(mqtt_queue()) -> mqtt_queue()).
-endif.
%%----------------------------------------------------------------------------
-define(DEFAULT_MAX_LEN, 1000).
-record(mqtt_queue_wrapper, { queue = queue:new(), max_len = ?DEFAULT_MAX_LEN, store_qos0 = false }).
new(MaxLen) -> #mqtt_queue_wrapper{ max_len = MaxLen }.
new(MaxLen, StoreQos0) -> #mqtt_queue_wrapper{ max_len = MaxLen, store_qos0 = StoreQos0 }.
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.
all(#mqtt_queue_wrapper { queue = Queue }) -> queue:to_list(Queue).
clear(Queue) -> Queue#mqtt_queue_wrapper{ queue = queue:new() }.

View File

@ -0,0 +1,41 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_queue_sup).
-author('feng@emqtt.io').
-behavior(supervisor).
-export([start_link/0, start_queue/0, init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
start_queue() ->
supervisor:start_child(?MODULE, []).
init([]) ->
{ok, {{simple_one_for_one, 0, 1},
[{queue, {emqtt_queue, start_link, []},
transient, 10000, worker, [emqtt_queue]}]}}.

View File

@ -1,110 +0,0 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.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.
%%------------------------------------------------------------------------------
-module(emqtt_retained).
-author('feng@slimchat.io').
%%TODO: FIXME Later...
%%
%% <<MQTT_V3.1_Protocol_Specific>>
%% RETAIN
%% Position: byte 1, bit 0.
%% This flag is only used on PUBLISH messages. When a client sends a PUBLISH to a server, if the Retain flag is set (1), the server should hold on to the message after it has been delivered to the current subscribers.
%% When a new subscription is established on a topic, the last retained message on that topic should be sent to the subscriber with the Retain flag set. If there is no retained message, nothing is sent
%% This is useful where publishers send messages on a "report by exception" basis, where it might be some time between messages. This allows new subscribers to instantly receive data with the retained, or Last Known Good, value.
%% When a server sends a PUBLISH to a client as a result of a subscription that already existed when the original PUBLISH arrived, the Retain flag should not be set, regardless of the Retain flag of the original PUBLISH. This allows a client to distinguish messages that are being received because they were retained and those that are being received "live".
%% Retained messages should be kept over restarts of the server.
%% A server may delete a retained message if it receives a message with a zero-length payload and the Retain flag set on the same topic.
-include("emqtt.hrl").
-include("emqtt_log.hrl").
-export([start_link/0,
lookup/1,
insert/2,
delete/1,
send/2]).
-behaviour(gen_server).
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
-record(state, {}).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
lookup(Topic) ->
ets:lookup(retained_msg, Topic).
insert(Topic, Msg) ->
gen_server:cast(?MODULE, {insert, Topic, Msg}).
delete(Topic) ->
gen_server:cast(?MODULE, {delete, Topic}).
send(Topic, Client) ->
[Client ! {dispatch, Msg} ||{_, Msg} <- lookup(Topic)].
init([]) ->
ets:new(retained_msg, [set, protected, named_table]),
{ok, #state{}}.
handle_call(Req, _From, State) ->
{stop, {badreq,Req}, State}.
handle_cast({insert, Topic, Msg}, State) ->
ets:insert(retained_msg, {Topic, Msg}),
{noreply, State};
handle_cast({delete, Topic}, State) ->
ets:delete(retained_msg, Topic),
{noreply, State};
handle_cast(Msg, State) ->
{stop, {badmsg, Msg}, State}.
handle_info(Info, State) ->
{stop, {badinfo, Info}, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

View File

@ -0,0 +1,99 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
%%route chain... statistics
-module(emqtt_router).
-include("emqtt.hrl").
-behaviour(gen_server).
-define(SERVER, ?MODULE).
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([start_link/0]).
%%Router Chain-->
%%--->In
%%Out<---
-export([route/1]).
%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%%----------------------------------------------------------------------------
-ifdef(use_specs).
-spec(start_link/1 :: () -> {ok, pid()}).
-spec route(mqtt_message()) -> ok.
-endif.
%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
route(Msg) ->
lager:info("Route message: ~s", [emqtt_message:dump(Msg)]),
% need to retain?
emqtt_server:retain(Msg),
% unset flag and pubsub
emqtt_pubsub:publish( emqtt_message:unset_flag(Msg) ).
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
init(Args) ->
{ok, Args, hibernate}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------

View File

@ -0,0 +1,141 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_server).
-author('feng@slimpp.io').
-include("emqtt.hrl").
-include("emqtt_topic.hrl").
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(RETAINED_TAB, mqtt_retained).
-define(STORE_LIMIT, 100000).
-record(mqtt_retained, {topic, qos, payload}).
-record(state, {store_limit}).
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
%%TODO: subscribe
-export([start_link/1, retain/1, subscribe/2]).
%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------
start_link(RetainOpts) ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [RetainOpts], []).
retain(#mqtt_message{ retain = false }) -> ignore;
%% RETAIN flag set to 1 and payload containing zero bytes
retain(#mqtt_message{ retain = true, topic = Topic, payload = <<>> }) ->
mnesia:dirty_delete(?RETAINED_TAB, Topic);
retain(Msg = #mqtt_message{retain = true}) ->
gen_server:cast(?SERVER, {retain, Msg}).
%%
subscribe(Topics, CPid) when is_pid(CPid) ->
lager:info("Retained Topics: ~p", [match(Topics)]),
RetainedMsgs = lists:flatten([mnesia:dirty_read(?RETAINED_TAB, Topic) || Topic <- match(Topics)]),
lager:info("Retained Messages: ~p", [RetainedMsgs]),
lists:foreach(fun(Msg) ->
CPid ! {dispatch, {self(), retained_msg(Msg)}}
end, RetainedMsgs).
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
init([RetainOpts]) ->
mnesia:create_table(mqtt_retained, [
{type, ordered_set},
{ram_copies, [node()]},
{attributes, record_info(fields, mqtt_retained)}]),
mnesia:add_table_copy(mqtt_retained, node(), ram_copies),
Limit = proplists:get_value(store_limit, RetainOpts, ?STORE_LIMIT),
{ok, #state{store_limit = Limit}}.
handle_call(Req, _From, State) ->
{stop, {badreq, Req}, State}.
handle_cast({retain, Msg = #mqtt_message{ qos = Qos,
topic = Topic,
payload = Payload }}, State = #state{store_limit = Limit}) ->
case mnesia:table_info(?RETAINED_TAB, size) of
Size when Size >= Limit ->
lager:error("Server dropped message(retain) for table is full: ~p", [Msg]);
_ ->
lager:info("Server retained message: ~p", [Msg]),
mnesia:dirty_write(#mqtt_retained{ topic = Topic,
qos = Qos,
payload = Payload })
end,
{noreply, State};
handle_cast(Msg, State) ->
{stop, {badmsg, Msg}, State}.
handle_info(Info, State) ->
{stop, {badinfo, Info}, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
match(Topics) ->
RetainedTopics = mnesia:dirty_all_keys(?RETAINED_TAB),
lists:flatten([match(Topic, RetainedTopics) || Topic <- Topics]).
match(Topic, RetainedTopics) ->
case emqtt_topic:type(#topic{name=Topic}) of
direct -> %% FIXME
[Topic];
wildcard ->
[ T || T <- RetainedTopics, emqtt_topic:match(T, Topic) ]
end.
retained_msg(#mqtt_retained{topic = Topic, qos = Qos, payload = Payload}) ->
#mqtt_message { qos = Qos, retain = true, topic = Topic, payload = Payload }.

View File

@ -0,0 +1,341 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_session).
-include("emqtt.hrl").
-include("emqtt_packet.hrl").
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([start/1, resume/3, publish/2, puback/2, subscribe/2, unsubscribe/2, destroy/2]).
-export([store/2]).
%%start gen_server
-export([start_link/3]).
%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(session_state, {
client_id :: 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 }).
%% ------------------------------------------------------------------
%% Start Session
%% ------------------------------------------------------------------
start({true = _CleanSess, ClientId, _ClientPid}) ->
%%Destroy old session if CleanSess is true before.
ok = emqtt_sm:destroy_session(ClientId),
{ok, initial_state(ClientId)};
start({false = _CleanSess, ClientId, ClientPid}) ->
{ok, SessPid} = emqtt_sm:start_session(ClientId, ClientPid),
{ok, SessPid}.
%% ------------------------------------------------------------------
%% Session API
%% ------------------------------------------------------------------
resume(SessState = #session_state{}, _ClientId, _ClientPid) ->
SessState;
resume(SessPid, ClientId, ClientPid) when is_pid(SessPid) ->
gen_server:cast(SessPid, {resume, ClientId, ClientPid}),
SessPid.
publish(Session, {?QOS_0, Message}) ->
emqtt_router:route(Message), Session;
publish(Session, {?QOS_1, Message}) ->
emqtt_router:route(Message), Session;
publish(SessState = #session_state{awaiting_rel = AwaitingRel},
{?QOS_2, Message = #mqtt_message{ msgid = MsgId }}) ->
%% store in awaiting_rel
SessState#session_state{awaiting_rel = maps:put(MsgId, Message, AwaitingRel)};
publish(SessPid, {?QOS_2, Message}) when is_pid(SessPid) ->
gen_server:cast(SessPid, {publish, ?QOS_2, Message}),
SessPid.
%% PUBACK
puback(SessState = #session_state{client_id = 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{ client_id = 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{client_id = ClientId, awaiting_rel = Awaiting}, {?PUBREL, PacketId}) ->
case maps:find(PacketId, Awaiting) of
{ok, Msg} -> emqtt_router:route(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{ client_id = 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.
%% SUBSCRIBE
subscribe(SessState = #session_state{client_id = 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} = emqtt_pubsub:subscribe(Topics, self()),
%%TODO: should be gen_event and notification...
emqtt_server:subscribe([ Name || {Name, _} <- Topics ], self()),
{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
%%
unsubscribe(SessState = #session_state{client_id = 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 = emqtt_pubsub:unsubscribe(Topics, self()),
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}.
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 { client_id = ClientId,
submap = #{},
awaiting_ack = #{},
awaiting_rel = #{},
awaiting_comp = #{} }.
initial_state(ClientId, ClientPid) ->
State = initial_state(ClientId),
State#session_state{client_pid = ClientPid}.
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
start_link(SessOpts, ClientId, ClientPid) ->
gen_server:start_link(?MODULE, [SessOpts, ClientId, ClientPid], []).
init([SessOpts, ClientId, ClientPid]) ->
process_flag(trap_exit, true),
%%TODO: Is this OK?
true = link(ClientPid),
State = initial_state(ClientId, ClientPid),
Expires = proplists:get_value(expires, SessOpts, 1) * 3600,
MsgQueue = emqtt_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) ->
{stop, {badreq, Req}, State}.
handle_cast({resume, ClientId, ClientPid}, State = #session_state {
client_id = ClientId,
client_pid = undefined,
msg_queue = Queue,
awaiting_ack = AwaitingAck,
awaiting_comp = AwaitingComp,
expire_timer = ETimer}) ->
lager:info("Session ~s resumed by ~p", [ClientId, ClientPid]),
%cancel timeout timer
erlang: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, emqtt_queue:all(Queue)),
NewState = State#session_state{ client_pid = ClientPid,
msg_queue = emqtt_queue:clear(Queue),
expire_timer = undefined},
{noreply, NewState, hibernate};
handle_cast({publish, ?QOS_2, Message}, State) ->
NewState = publish(State, {?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{client_id = ClientId}) ->
lager:warning("Session ~s destroyed", [ClientId]),
{stop, normal, State};
handle_cast(Msg, State) ->
{stop, {badmsg, Msg}, State}.
handle_info({dispatch, {_From, Message}}, State) ->
{noreply, dispatch(Message, State)};
handle_info({'EXIT', ClientPid, Reason}, State = #session_state{
client_id = ClientId, client_pid = ClientPid, expires = Expires}) ->
lager:warning("Session: client ~s@~p exited, caused by ~p", [ClientId, ClientPid, Reason]),
Timer = erlang:send_after(Expires * 1000, self(), session_expired),
{noreply, State#session_state{ client_pid = undefined, expire_timer = Timer}};
handle_info(session_expired, State = #session_state{client_id = ClientId}) ->
lager:warning("Session ~s expired!", [ClientId]),
{stop, {shutdown, expired}, State};
handle_info(Info, State) ->
{stop, {badinfo, Info}, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% ------------------------------------------------------------------
%% Internal Function Definitions
%% ------------------------------------------------------------------
dispatch(Message, State = #session_state{ client_id = 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 = emqtt_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 }.

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -20,47 +20,38 @@
%% SOFTWARE.
%%------------------------------------------------------------------------------
-module(emqtt_keep_alive).
-module(emqtt_session_sup).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-export([new/2,
state/1,
activate/1,
reset/1,
cancel/1]).
-behavior(supervisor).
-record(keep_alive, {state, period, timer, msg}).
-export([start_link/1, start_session/2]).
new(undefined, _) ->
undefined;
new(0, _) ->
undefined;
new(Period, TimeoutMsg) when is_integer(Period) ->
Ref = erlang:send_after(Period, self(), TimeoutMsg),
#keep_alive{state=idle, period=Period, timer=Ref, msg=TimeoutMsg}.
-export([init/1]).
state(undefined) ->
undefined;
state(#keep_alive{state=State}) ->
State.
%%----------------------------------------------------------------------------
activate(undefined) ->
undefined;
activate(KeepAlive) when is_record(KeepAlive, keep_alive) ->
KeepAlive#keep_alive{state=active}.
-ifdef(use_specs).
reset(undefined) ->
undefined;
reset(KeepAlive=#keep_alive{period=Period, timer=Timer, msg=Msg}) ->
catch erlang:cancel_timer(Timer),
Ref = erlang:send_after(Period, self(), Msg),
KeepAlive#keep_alive{state=idle, timer = Ref}.
-spec(start_link/1 :: (list(tuple())) -> {ok, pid()}).
-spec(start_session/2 :: (binary(), pid()) -> {ok, pid()}).
-endif.
%%----------------------------------------------------------------------------
start_link(SessOpts) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [SessOpts]).
start_session(ClientId, ClientPid) ->
supervisor:start_child(?MODULE, [ClientId, ClientPid]).
init([SessOpts]) ->
{ok, {{simple_one_for_one, 0, 1},
[{session, {emqtt_session, start_link, [SessOpts]},
transient, 10000, worker, [emqtt_session]}]}}.
cancel(undefined) ->
undefined;
cancel(KeepAlive=#keep_alive{timer=Timer}) ->
catch erlang:cancel_timer(Timer),
KeepAlive#keep_alive{timer=undefined}.

162
apps/emqtt/src/emqtt_sm.erl Normal file
View File

@ -0,0 +1,162 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
%%------------------------------------------------------------------------------
%%
%% The Session state in the Server consists of:
%% The existence of a Session, even if the rest of the Session state is empty.
%% The Clients subscriptions.
%% QoS 1 and QoS 2 messages which have been sent to the Client, but have not been completely
%% acknowledged.
%% QoS 1 and QoS 2 messages pending transmission to the Client.
%% QoS 2 messages which have been received from the Client, but have not been completely
%% acknowledged.
%% Optionally, QoS 0 messages pending transmission to the Client.
%%
%%------------------------------------------------------------------------------
-module(emqtt_sm).
%%emqtt session manager...
%%cleanSess: true | false
-include("emqtt.hrl").
-behaviour(gen_server).
-define(SERVER, ?MODULE).
-define(TABLE, emqtt_session).
%% ------------------------------------------------------------------
%% API Function Exports
%% ------------------------------------------------------------------
-export([start_link/0]).
-export([lookup_session/1, start_session/2, destroy_session/1]).
%% ------------------------------------------------------------------
%% gen_server Function Exports
%% ------------------------------------------------------------------
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%%----------------------------------------------------------------------------
-ifdef(use_specs).
-spec(start_link/0 :: () -> {ok, pid()}).
-spec(lookup_session/1 :: (binary()) -> pid() | undefined).
-spec(start_session/2 :: (binary(), pid()) -> {ok, pid()} | {error, any()}).
-spec(destroy_session/1 :: (binary()) -> ok).
-endif.
%%----------------------------------------------------------------------------
-record(state, {}).
%% ------------------------------------------------------------------
%% API Function Definitions
%% ------------------------------------------------------------------
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
lookup_session(ClientId) ->
case ets:lookup(?TABLE, ClientId) of
[{_, SessPid, _}] -> SessPid;
[] -> undefined
end.
start_session(ClientId, ClientPid) ->
gen_server:call(?SERVER, {start_session, ClientId, ClientPid}).
destroy_session(ClientId) ->
gen_server:call(?SERVER, {destroy_session, ClientId}).
%% ------------------------------------------------------------------
%% gen_server Function Definitions
%% ------------------------------------------------------------------
init([]) ->
process_flag(trap_exit, true),
ets:new(?TABLE, [set, protected, named_table]),
{ok, #state{}}.
handle_call({start_session, ClientId, ClientPid}, _From, State) ->
Reply =
case ets:lookup(?TABLE, ClientId) of
[{_, SessPid, _MRef}] ->
emqtt_session:resume(SessPid, ClientId, ClientPid),
{ok, SessPid};
[] ->
case emqtt_session_sup:start_session(ClientId, ClientPid) of
{ok, SessPid} ->
MRef = erlang:monitor(process, SessPid),
ets:insert(?TABLE, {ClientId, SessPid, MRef}),
{ok, SessPid};
{error, Error} ->
{error, Error}
end
end,
{reply, Reply, State};
handle_call({destroy_session, ClientId}, _From, State) ->
case ets:lookup(?TABLE, ClientId) of
[{_, SessPid, MRef}] ->
erlang:demonitor(MRef),
emqtt_session:destroy(SessPid, ClientId),
ets:delete(?TABLE, ClientId);
[] ->
ignore
end,
{reply, ok, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({'DOWN', MRef, process, DownPid, _Reason}, State) ->
ets:match_delete(emqtt_client, {{'_', DownPid, MRef}}),
{noreply, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,7 +22,7 @@
-module(emqtt_sup).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-include("emqtt.hrl").

View File

@ -0,0 +1,26 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_throttle).

View File

@ -1,5 +1,5 @@
%%-----------------------------------------------------------------------------
%% Copyright (c) 2014, Feng Lee <feng@slimchat.io>
%% 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
@ -22,7 +22,7 @@
-module(emqtt_topic).
-author('feng@slimchat.io').
-author('feng@emqtt.io').
-import(lists, [reverse/1]).

View File

@ -0,0 +1,160 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_packet_tests).
-include("emqtt_packet.hrl").
-import(emqtt_packet, [initial_state/0, parse/2, serialise/1]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
parse_connect_test() ->
State = initial_state(),
%% CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined)
V31ConnBin = <<16,37,0,6,77,81,73,115,100,112,3,2,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,49,45,105,77,97,99,46,108,111,99,97>>,
?assertMatch({ok, #mqtt_packet{
header = #mqtt_packet_header { type = ?CONNECT,
dup = false,
qos = 0,
retain = false},
variable = #mqtt_packet_connect { proto_ver = 3,
proto_name = <<"MQIsdp">>,
client_id = <<"mosqpub/10451-iMac.loca">>,
clean_sess = true,
keep_alive = 60 } }, <<>>}, parse(V31ConnBin, State)),
%% CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10451-iMac.loca, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60, Username=undefined, Password=undefined)
V311ConnBin = <<16,35,0,4,77,81,84,84,4,2,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,49,45,105,77,97,99,46,108,111,99,97>>,
?assertMatch({ok, #mqtt_packet{
header = #mqtt_packet_header { type = ?CONNECT,
dup = false,
qos = 0,
retain = false},
variable = #mqtt_packet_connect { proto_ver = 4,
proto_name = <<"MQTT">>,
client_id = <<"mosqpub/10451-iMac.loca">>,
clean_sess = true,
keep_alive = 60 } }, <<>>}, parse(V311ConnBin, State)),
%% CONNECT(Qos=0, Retain=false, Dup=false, ClientId="", ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=60)
V311ConnWithoutClientId = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>,
?assertMatch({ok, #mqtt_packet{
header = #mqtt_packet_header { type = ?CONNECT,
dup = false,
qos = 0,
retain = false},
variable = #mqtt_packet_connect { proto_ver = 4,
proto_name = <<"MQTT">>,
client_id = <<>>,
clean_sess = true,
keep_alive = 60 } }, <<>>}, parse(V311ConnWithoutClientId, State)),
%%CONNECT(Qos=0, Retain=false, Dup=false, ClientId=mosqpub/10452-iMac.loca, ProtoName=MQIsdp, ProtoVsn=3, CleanSess=true, KeepAlive=60, Username=test, Password=******, Will(Qos=1, Retain=false, Topic=/will, Msg=willmsg))
ConnBinWithWill = <<16,67,0,6,77,81,73,115,100,112,3,206,0,60,0,23,109,111,115,113,112,117,98,47,49,48,52,53,50,45,105,77,97,99,46,108,111,99,97,0,5,47,119,105,108,108,0,7,119,105,108,108,109,115,103,0,4,116,101,115,116,0,6,112,117,98,108,105,99>>,
?assertMatch({ok, #mqtt_packet{
header = #mqtt_packet_header { type = ?CONNECT,
dup = false,
qos = 0,
retain = false},
variable = #mqtt_packet_connect { proto_ver = 3,
proto_name = <<"MQIsdp">>,
client_id = <<"mosqpub/10452-iMac.loca">>,
clean_sess = true,
keep_alive = 60,
will_retain = false,
will_qos = 1,
will_flag = true,
will_topic = <<"/will">>,
will_msg = <<"willmsg">> ,
username = <<"test">>,
password = <<"public">> } },
<<>> }, parse(ConnBinWithWill, State)),
ok.
parse_publish_test() ->
State = initial_state(),
%%PUBLISH(Qos=1, Retain=false, Dup=false, TopicName=a/b/c, PacketId=1, Payload=<<"hahah">>)
PubBin = <<50,14,0,5,97,47,98,47,99,0,1,104,97,104,97,104>>,
?assertMatch({ok, #mqtt_packet {
header = #mqtt_packet_header { type = ?PUBLISH,
dup = false,
qos = 1,
retain = false},
variable = #mqtt_packet_publish { topic_name = <<"a/b/c">>,
packet_id = 1 },
payload = <<"hahah">> }, <<>>}, parse(PubBin, State)),
%PUBLISH(Qos=0, Retain=false, Dup=false, TopicName=xxx/yyy, PacketId=undefined, Payload=<<"hello">>)
%DISCONNECT(Qos=0, Retain=false, Dup=false)
PubBin1 = <<48,14,0,7,120,120,120,47,121,121,121,104,101,108,108,111,224,0>>,
?assertMatch({ok, #mqtt_packet {
header = #mqtt_packet_header { type = ?PUBLISH,
dup = false,
qos = 0,
retain = false},
variable = #mqtt_packet_publish { topic_name = <<"xxx/yyy">>,
packet_id = undefined },
payload = <<"hello">> }, <<224,0>>}, parse(PubBin1, State)),
?assertMatch({ok, #mqtt_packet{
header = #mqtt_packet_header { type = ?DISCONNECT,
dup = false,
qos = 0,
retain = false}
}, <<>>}, parse(<<224, 0>>, State)).
parse_puback_test() ->
%%PUBACK(Qos=0, Retain=false, Dup=false, PacketId=1)
PubAckBin = <<64,2,0,1>>,
?assertMatch({ok, #mqtt_packet {
header = #mqtt_packet_header { type = ?PUBACK,
dup = false,
qos = 0,
retain = false }
}, <<>>}, parse(PubAckBin, initial_state())),
ok.
parse_subscribe_test() ->
ok.
parse_pingreq_test() ->
ok.
parse_disconnect_test() ->
%DISCONNECT(Qos=0, Retain=false, Dup=false)
Bin = <<224, 0>>,
?assertMatch({ok, #mqtt_packet {
header = #mqtt_packet_header { type = ?DISCONNECT,
dup = false,
qos = 0,
retain = false }
}, <<>>}, parse(Bin, initial_state())).
serialise_connack_test() ->
ConnAck = #mqtt_packet{ header = #mqtt_packet_header { type = ?CONNACK },
variable = #mqtt_packet_connack { ack_flags = 0, return_code = 0 } },
?assertEqual(<<32,2,0,0>>, emqtt_packet:serialise(ConnAck)).
serialise_puback_test() ->
ok.
-endif.

View File

@ -1,12 +1,34 @@
%%-----------------------------------------------------------------------------
%% 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.
%%------------------------------------------------------------------------------
-module(emqtt_topic_tests).
-include("emqtt_internal.hrl").
-include("emqtt_topic.hrl").
-import(emqtt_topic, [validate/1, type/1, match/2, triples/1, words/1]).
-ifdef(TEST).
-include_lib("enunit/include/enunit.hrl").
-include_lib("eunit/include/eunit.hrl").
validate_test() ->
?assert( validate({subscribe, <<"a/b/c">>}) ),
@ -17,8 +39,8 @@ validate_test() ->
type_test() ->
?assertEqual(direct, type(#topic{name = <<"/a/b/cdkd">>})),
?assertEqual(wildcard, type(#type{name = <<"/a/+/d">>})),
?assertEqual(wildcard, type(#type{name = <<"/a/b/#">>})).
?assertEqual(wildcard, type(#topic{name = <<"/a/+/d">>})),
?assertEqual(wildcard, type(#topic{name = <<"/a/b/#">>})).
-endif.

1
data/.placeholder Normal file
View File

@ -0,0 +1 @@
durable queue data...

BIN
doc/.retain.md.swp Normal file

Binary file not shown.

25
doc/cluster.md Normal file
View File

@ -0,0 +1,25 @@
zookeeper
|
eMQTT1 eMQTT2 eMQTT3
Bridge
eMQTT1 --> eMQTT2
Cluster
eMQTT1 <--> eMQTT2
Cluster and Bridge
eMQTT1 eMQTT3
---->
eMQTT2 eMQTT4
Mnesia Cluster

30
doc/design.md Normal file
View File

@ -0,0 +1,30 @@
# eMQTT Desgin Guide
## KeepAlive
## Retained
## QOS1
## QOS2
## Durable Subscriptions
Durable Sub:
Client->Queue->Router->Queue->Client
Normal Sub:
Client->Router->Client
Router to register queues
## Topic Tree
## Cluster
## Bridge
## Offline Message

36
doc/protocol.md Normal file
View File

@ -0,0 +1,36 @@
# MQTT Protocol Guide
## Server or Broker
A program or device that acts as an intermediary between Clients which publish Application Messages and Clients which have made Subscriptions.
A Server Accepts Network Connections from Clients.
Accepts Application Messages published by Clients.
Processes Subscribe and Unsubscribe requests from Clients.
Forwards Application Messages that match Client Subscriptions.
Client ----> Broker(Server) ----> Client
Publisher ----> Broker -----> Subscriber
## Subscription and Session
### Subscription
A Subscription comprises a Topic Filter and a maximum QoS. A Subscription is associated with a single Session. A Session can contain more than one Subscription. Each Subscription within a session has a different Topic Filter.
### Session
A stateful interaction between a Client and a Server. Some Sessions last only as long as the Network
Connection, others can span multiple consecutive Network Connections between a Client and a Server.
## Topic Name and Filter
An expression contained in a Subscription, to indicate an interest in one or more topics. A Topic Filter can include wildcard characters.
## Packet Identifier

19
doc/pubsub.md Normal file
View File

@ -0,0 +1,19 @@
# PubSub
## Qos
PubQos | SubQos | In Message | Out Message
-------|--------|------------|-------------
0 | 0 | - | -
0 | 1 | - | -
0 | 2 | - | -
1 | 0 | - | -
1 | 1 | - | -
1 | 2 | - | -
2 | 0 | - | -
2 | 1 | - | -
2 | 2 | - | -
## Publish

6
doc/retain.md Normal file
View File

@ -0,0 +1,6 @@
# Retained Message
## API
store(

50
doc/session.md Normal file
View File

@ -0,0 +1,50 @@
# Session Design
## session manager
```erlang
%% lookup sesssion
emqtt_sm:lookup_session(ClientId)
%% Start new or resume existing session
emqtt_sm:start_session(ClientId)
%% destroy session, discard all data
emqtt_sm:destory_session(ClientId)
%% close session, save all data
emqtt_sm:close_session(ClientId)
```
## session supervisor
usage?
## session
```
%%system process
process_flag(trap_exit, true),
session:start()
session:subscribe(
session:publish(
session:resume(
session:suspend(
%%destory all data
session:destory(
%%save all data
session:close()
```
## sm and session
sm manage and monitor session
## client and session
client(normal process)<--link to -->session(system process)

4
doc/state_design.md Normal file
View File

@ -0,0 +1,4 @@
client state --> parse_state
--> proto_state --> session_state

14
doc/user-guide.md Normal file
View File

@ -0,0 +1,14 @@
# eMQTT User Guide
## Introduction
## Installation
### Install Requirements
## Configuration
## Cluster
## Bridge

5
go Executable file
View File

@ -0,0 +1,5 @@
#!/bin/sh
# -*- tab-width:4;indent-tabs-mode:nil -*-
# ex: ts=4 sw=4 et
make && make dist && cd rel/emqtt && ./bin/emqtt console

View File

@ -14,7 +14,7 @@
{error_logger_redirect, false},
{crash_log, "log/emqtt_crash.log"},
{handlers, [
{lager_console_backend, info},
{lager_console_backend, debug},
{lager_file_backend, [
{file, "log/emqtt_error.log"},
{level, error},
@ -33,12 +33,20 @@
]},
{emqtt, [
{auth, {anonymous, []}}, %internal, anonymous
{session, [
{expires, 1},
{max_queue, 1000},
{store_qos0, false}
]},
{retain, [
{store_limit, 100000}
]},
{listen, [
{mqtt, 1883, [
{max_conns, 1024},
{acceptor_pool, 4}
]},
{http, 8883, [
{http, 8083, [
{max_conns, 512},
{acceptor_pool, 1}
]}

14
rel/files/emqtt.cfg Normal file
View File

@ -0,0 +1,14 @@
mqtt_ipaddr=0.0.0.0
mqtt_port=1883
max_clientid_len=1024
#Max Connections
max_connections=10000
#Max MQTT Message Size
max_message_size=64k
#Network ingoing limit
rate_ingoing_limit=64kb/s

View File

@ -97,27 +97,9 @@ case "$1" in
$NODETOOL rpc emqtt_ctl status $@
;;
cluster_info)
if [ $# -ne 1 ]; then
echo "Usage: $SCRIPT cluster_info"
exit 1
fi
# Make sure the local node IS running
RES=`$NODETOOL ping`
if [ "$RES" != "pong" ]; then
echo "Node is not running!"
exit 1
fi
shift
$NODETOOL rpc emqtt_ctl cluster_info $@
;;
cluster)
if [ $# -ne 2 ]; then
echo "Usage: $SCRIPT cluster <Node>"
if [ $# -gt 2 ]; then
echo "Usage: $SCRIPT cluster [<Node>]"
exit 1
fi
@ -132,9 +114,9 @@ case "$1" in
$NODETOOL rpc emqtt_ctl cluster $@
;;
add_user)
useradd)
if [ $# -ne 3 ]; then
echo "Usage: $SCRIPT add_user <Username> <Password>"
echo "Usage: $SCRIPT useradd <Username> <Password>"
exit 1
fi
@ -146,12 +128,12 @@ case "$1" in
fi
shift
$NODETOOL rpc emqtt_ctl add_user $@
$NODETOOL rpc emqtt_ctl useradd $@
;;
delete_user)
userdel)
if [ $# -ne 2 ]; then
echo "Usage: $SCRIPT delete_user <Username>"
echo "Usage: $SCRIPT userdel <Username>"
exit 1
fi
@ -163,16 +145,15 @@ case "$1" in
fi
shift
$NODETOOL rpc emqtt_ctl delete_user $@
$NODETOOL rpc emqtt_ctl userdel $@
;;
*)
echo "Usage: $SCRIPT"
echo " status #query emqtt status"
echo " cluster_info #query cluster nodes"
echo " cluster <Node> #cluster node"
echo " add_user <Username> <Password> #add user"
echo " delete_user <Username> #delete user"
echo " status #query emqtt status"
echo " cluster [<Node>] #query or cluster nodes"
echo " useradd <Username> <Password> #add user"
echo " userdel <Username> #delete user"
exit 1
;;

View File

@ -1,8 +1,8 @@
## Name of the node
-sname emqtt
-name emqtt@127.0.0.1
## Cookie for distributed erlang
-setcookie emqtt
-setcookie emqttsecretcookie
## Heartbeat management; auto-restarts VM if it dies or becomes unresponsive
## (Disabled by default..use with caution!)
@ -14,7 +14,7 @@
+A 32
## max process numbers
+P 100000
+P 1000000
## Increase number of concurrent ports/sockets
-env ERL_MAX_PORTS 4096
@ -25,3 +25,4 @@
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
#

19
scripts/mosquitto_test Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
# -*- tab-width:4;indent-tabs-mode:nil -*-
# ex: ts=4 sw=4 et
# slimple publish
mosquitto_pub -t xxx/yyy -m hello
if [ "$?" == 0 ]; then
echo "[Success]: slimple publish"
else
echo "[Failure]: slimple publish"
fi
# publish will willmsg
mosquitto_pub -q 1 -t a/b/c -m hahah -u test -P public --will-topic /will --will-payload willmsg --will-qos 1
if [ "$?" == 0 ]; then
echo "[Success]: publish with willmsg"
else
echo "[Failure]: publish with willmsg"
fi

View File